forked from M-Labs/artiq
1
0
Fork 0

Compare commits

..

123 Commits

Author SHA1 Message Date
architeuthidae 0ac9e77dc3 doc: Dashboard 'Load HDF5' button + nitpicks 2024-08-28 11:22:17 +08:00
architeuthidae 4235309ab7 doc: Additional keyboard shortcuts 2024-08-23 16:46:19 +08:00
architeuthidae 0311a37e59 doc: Minor fixes 2024-08-23 16:46:15 +08:00
abdul124 a7e1d2b1c1 coredevice: add UnwrapNoneError class 2024-08-23 16:26:12 +08:00
abdul124 808d9c8004 compiler: restore exception ids used 2024-08-23 16:26:12 +08:00
abdul124 5463d66dcd firmware/ksupport: restore exception id order 2024-08-23 16:26:12 +08:00
architeuthidae 24f28d0b4e doc: Add ZC706 to core device page 2024-08-22 13:52:57 +08:00
architeuthidae 0808f7bc72 doc: Management expansion, suggested edits 2024-08-20 16:29:54 +08:00
architeuthidae f9ee6d66e8 doc: Add Waveform/RTIO analyzer 2024-08-20 16:29:54 +08:00
architeuthidae c4bcdf5f4c doc: Add reference descriptions 2024-08-20 16:29:54 +08:00
architeuthidae 400fb0f317 doc: GUI applet groups 2024-08-20 16:29:54 +08:00
architeuthidae 0c92fe5aeb doc: using_data_interfaces associated changes 2024-08-20 16:29:54 +08:00
architeuthidae 04e55246e9 doc: Add 'Data and user interfaces' page 2024-08-20 16:29:54 +08:00
abdul124 3079a1915f firmware/ksupport: improve comments and syscall name 2024-08-20 15:23:44 +08:00
abdul124 5454e6f1a9 coredevice/test: add unittests for exceptions 2024-08-20 15:23:44 +08:00
abdul124 68cbb09a64 firmware/ksupport: add exception unittests 2024-08-20 15:23:44 +08:00
abdul124 9d6defcea1 coredevice/comm_kernel: map exceptions to correct names 2024-08-20 15:23:44 +08:00
abdul124 4b9a910c88 sync exception names and ids 2024-08-20 15:23:44 +08:00
architeuthidae ccc4598049 doc: More FAQs 2024-08-19 13:04:19 +08:00
architeuthidae c6216e4b4e doc: Refactor management system reference 2024-08-13 14:53:48 +08:00
architeuthidae d1f6e2f1c1 doc: Minor link fixes 2024-08-12 16:57:57 +08:00
Sebastien Bourdeauducq 85c03addbd flake: update dependencies 2024-08-10 00:04:19 +08:00
architeuthidae 08c1770b31 CONTRIBUTING: Doc section 2024-08-01 19:30:49 +08:00
architeuthidae fc52cc8e4b doc: Refactor FAQ slightly 2024-08-01 19:30:49 +08:00
architeuthidae dd940184b7 doc: Add helpdesk instructions to FAQ 2024-08-01 19:30:49 +08:00
architeuthidae ca6f5b5258 doc: Add extra resources to FAQ 2024-08-01 19:30:49 +08:00
architeuthidae 781dccef11 doc: Add note on nix profile deletion, garbage collect 2024-08-01 19:06:26 +08:00
architeuthidae ebab2c4d34 doc: reST formatting 2024-08-01 19:05:55 +08:00
architeuthidae 42b48598e5 flake/sphinx: fix errors in manual nix build 2024-08-01 19:00:50 +08:00
architeuthidae 4e800af410 doc: Subkernel exception fix 2024-07-31 17:17:50 +08:00
mwojcik 7f36c9e9c1 satman: pass exceptions from one subkernel to another 2024-07-31 17:17:50 +08:00
mwojcik 5f1e33198e subkernels: separate error messages 2024-07-31 17:17:50 +08:00
mwojcik ccc6ae524a compiler: add builtinInvoke for subkernel raising functions 2024-07-31 17:17:50 +08:00
mwojcik 505ddff15d subkernel: pass exceptions to kernel 2024-07-31 17:17:50 +08:00
architeuthidae 4e39a7db44 doc: Rewrite FAQ 2024-07-31 17:16:09 +08:00
architeuthidae 82f6e14b78 git(hub): update gitignore, pull request template 2024-07-31 13:42:59 +08:00
architeuthidae d934092ecb doc: Document core_log() in manual 2024-07-31 13:42:59 +08:00
Florian Agbuya 13250427e0 afws_client: fix unicode error in json handling
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-07-29 18:32:58 +08:00
architeuthidae 61986e4f2b doc: Add references to command line tools 2024-07-29 13:41:29 +08:00
architeuthidae a324d77e37 doc: Add artiq_session and artiq_ctlmgr to front-end tools 2024-07-29 13:41:29 +08:00
architeuthidae a0fde00258 doc: Add automodule to command line references 2024-07-29 13:41:29 +08:00
architeuthidae be8fb8cdbe doc: Rename main frontend tools page 2024-07-29 13:41:29 +08:00
architeuthidae 96bc4174cd doc: Building + developing rewrite (#2496)
Co-authored-by: architeuthidae <am@m-labs.hk>
2024-07-27 22:18:26 +08:00
architeuthidae cd12600c1c ad9910: Add bitsizes to docstrings (#2505)
Co-authored-by: architeuthidae <am@m-labs.hk>
2024-07-22 18:37:57 +08:00
architeuthidae d7e78aded3 doc: Add links for ddb_template and afws_client 2024-07-22 18:36:49 +08:00
architeuthidae 59bb478ca1 artiq_dashboard: Replace --port-notify 2024-07-22 16:58:13 +08:00
architeuthidae a40d45b4d8 doc: link fixes 2024-07-19 18:35:36 +08:00
architeuthidae 0cdb35f426 doc: Trailing spaces 2024-07-19 18:18:07 +08:00
architeuthidae d8ba204970 doc: Split installing page (#2495)
Co-authored-by: architeuthidae <am@m-labs.hk>
2024-07-19 17:53:26 +08:00
Sébastien Bourdeauducq c9e571d7d7 afws_client: cleanup 2024-07-19 11:22:47 +08:00
architeuthidae ab1aeb136f doc: Edit of manual reference pages (#2466)
Co-authored-by: architeuthis <am@m-labs.hk>
2024-07-18 17:54:31 +08:00
Florian Agbuya 9a5ca15b80 afws_client: extract get_argparser() for modular arg parsing 2024-07-18 13:00:15 +08:00
Sébastien Bourdeauducq da5de4b537 doc: rename release_notes -> releases 2024-07-17 12:15:19 +08:00
architeuthidae 1b44f7dddf doc: Reword heading 2024-07-17 12:13:40 +08:00
architeuthidae d74e9a2bfc doc: Refactor manual index, make sections 2024-07-17 12:13:40 +08:00
architeuthidae f635392edf doc: Add explanation of event spreading 2024-07-17 12:06:16 +08:00
architeuthidae c10e8935c1 doc: Reference link fixes 2024-07-17 11:55:15 +08:00
architeuthidae f20912c330 doc: Sphinx configuration nitpicky 2024-07-17 11:55:15 +08:00
architeuthidae 3a3ac1eb99 doc: Formatting and link fixes in docstrings 2024-07-17 11:42:00 +08:00
architeuthidae 94f7a23fe2 doc: Assorted typos and dead links 2024-07-17 11:42:00 +08:00
Florian Agbuya 554d7bda26 flake: use upstream nixpkgs openocd
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-07-15 19:31:55 +08:00
architeuthis 266a2ee9ea doc: Rewrite and overhaul of 'Compiler' page 2024-07-15 19:28:50 +08:00
mwojcik fb6c1364a6 ad9912: use pll doubler for refclk <11mhz 2024-07-15 19:28:46 +08:00
Simon Renblad 86a8d2d87a moninj: add underscore to dac channel labels 2024-07-15 11:08:00 +08:00
architeuthidae 099a0869d9 doc: Put back telnet exit hint 2024-07-15 10:55:48 +08:00
Egor Savkin e6b88c9b9d Update llvmlite to 0.43 and llvm to 15, as in MSYS2
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-07-12 13:08:06 +02:00
architeuthidae aa30640658 doc: Environment, suggested changes 2024-07-12 10:53:51 +02:00
architeuthidae 5b9db674d2 doc: Mention artiq_ddb_template in Environment manual page 2024-07-12 10:53:51 +02:00
architeuthis 0265151f1a doc: Improve description of environment in tutorials 2024-07-12 10:53:51 +02:00
architeuthis a38fabe465 doc: Update + expansion of Environment manual page 2024-07-12 10:53:51 +02:00
architeuthidae 4c8da27196 doc: NDSP page edit, fixes 2024-07-12 09:10:57 +02:00
architeuthis 23444d0c03 doc: Mention ping() in NDSP tutorial 2024-07-12 09:10:57 +02:00
architeuthis 114084b12e doc: Edit of 'Developing an NDSP' page 2024-07-12 09:10:57 +02:00
Harry Ying a6b76c03f2 test_scheduler: remove the exact ordering assertion
Currently the exact ordering is compared in the `pending_priorities`
test which is not necessary and is non-deterministic. This patch only
 asserts the middle priority experiment is ran before the high priority
 experiment scheduled in the future.

Signed-off-by: Kanyang Ying <lexugeyky@outlook.com>
2024-07-11 09:38:22 +02:00
Simon Renblad e8da7581bc browser: fix ctrl scroll type error 2024-07-10 10:42:04 +02:00
mwojcik 4bc23192f1 tests: fix sed lane distributor test, force enable spread 2024-07-10 14:46:12 +08:00
mwojcik bf108a653d repeater: handle async packets on forwarding 2024-07-09 17:05:55 +02:00
mwojcik 60b1edaf6b allow toggling SED spread with flash config key 2024-07-09 16:58:55 +02:00
Sebastien Bourdeauducq 05d02f3f00 flake: update dependencies 2024-07-09 16:58:46 +02:00
architeuthidae d244ac1baa doc: Change references to rtio_clock/now to _mu 2024-07-08 23:11:19 +02:00
architeuthis c879f9737b doc: Add warning on sequence error nondeterminism, for now 2024-07-08 23:11:19 +02:00
architeuthis 2b7d1742fb doc: RTIO manual page edit, suggested changes 2024-07-08 23:11:19 +02:00
architeuthis 07197433a9 docs: RTIO manual page edit 2024-07-08 23:11:19 +02:00
architeuthidae 6866bf298e doc: Fix sphinx 'duplicated reference' warnings 2024-07-08 22:42:47 +02:00
abdul124 d46e949f9f firmware/ksupport: add rint api 2024-07-05 08:53:02 +02:00
mwojcik 37db6cb6ae moninj: restore urukul TTL control 2024-07-04 13:44:22 +02:00
mwojcik 7bbba2f67f repeater: clear buffer after ping 2024-07-04 13:38:40 +02:00
architeuthidae ac86265c16 math_fns: Delete duplicated definition 2024-07-04 13:31:52 +02:00
Charles Baynham 240b238cac worker_impl - try/catch for exceptions in notify_run_end to avoid data loss 2024-07-02 13:55:53 +02:00
Simon Renblad 6c28159541 moninj: set parent to main window on widget delete 2024-07-02 08:23:16 +02:00
Simon Renblad de1da7efb3 moninj: fix visual bug 2024-07-02 08:23:16 +02:00
architeuthis f20b6051d3 doc: Fix heading levels in Releases page 2024-06-27 15:53:17 +08:00
architeuthis f0384ca885 doc: Add description of release model to manual 2024-06-27 14:43:53 +08:00
Sébastien Bourdeauducq 8e51d6520c doc: fix nix URLs 2024-06-27 13:13:26 +08:00
Egor Savkin 3c4c2dcf65 Update dead links, partially remove unavailable resources
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-06-27 13:08:48 +08:00
architeuthis c449c6f0de docs: Add details of shallow 'with parallel' to manual 2024-06-25 17:48:10 +08:00
Egor Savkin a1b5aed9e5 doc: add reference to artiq-zynq for developers
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-06-25 17:36:48 +08:00
architeuthis 5a39de106d doc: Management system overhaul, further corrections 2024-06-25 17:34:12 +08:00
architeuthis 3204c601ff doc: Clearer+more accurate overview of mgmt system 2024-06-25 17:34:12 +08:00
architeuthis 5e30d81fbb doc: Management system update, corrections 2024-06-25 17:34:12 +08:00
architeuthis 316e7c21e7 doc: Split running/management tool reference into separate page 2024-06-25 17:34:12 +08:00
architeuthis a2f6d2ed55 doc: 'Management system' manual page copy+clarity edit 2024-06-25 17:34:12 +08:00
architeuthis eb469e28cc doc: Elaboration on interactive args & controllers 2024-06-25 17:34:12 +08:00
architeuthis 4ec97ae1f6 doc: 'Getting started with management system' manual page overhaul 2024-06-25 17:34:12 +08:00
Sébastien Bourdeauducq 6935c8cfe6 doc: minor correction 2024-06-20 17:39:12 +08:00
architeuthis a19afaea96 doc: Add DRTIO-over-EEM to manual + phrasing fixes 2024-06-20 17:39:12 +08:00
architeuthis 5e67001ba5 doc: Add "Using Drtio" and overhaul DRTIO page 2024-06-20 17:39:12 +08:00
Egor Savkin 36fd45b05d Fix missing jquery in docs - fixes broken search
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-06-20 12:52:08 +08:00
Sebastien Bourdeauducq a68846d960 flake: update dependencies 2024-06-19 12:45:05 +08:00
morgan 56059250ce kasli: raise error when enabling WRPLL with v1.x 2024-06-19 12:44:12 +08:00
morgan 49a5c1b13c kasli: fix v1.0 & v1.1 compilation error 2024-06-19 12:44:12 +08:00
Florian Agbuya 69a48464c3 plot_xy: fix missing x values handling
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-06-13 12:19:57 +08:00
architeuthis a53e0506cd doc: fix formatting in Installing page 2024-06-13 12:19:50 +08:00
architeuthis 5328f56246 doc: Refactor manual table of contents 2024-06-12 15:48:49 +08:00
architeuthidae f94ec844d7 doc: 'Getting started with core device' manual page edit (#2431) 2024-06-12 15:45:51 +08:00
architeuthidae 8b531e5060 doc: 'Core device' manual page overhaul (#2430) 2024-06-12 14:52:11 +08:00
Florian Agbuya 5eacbeec5d test_embedding: add int boundary test from 25168422a
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-06-07 14:11:49 +08:00
Florian Agbuya 535654938f compiler: fix int boundary checks
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-06-07 14:11:49 +08:00
Sébastien Bourdeauducq 9e28ee63a6 ARTIQ-8 release 2024-06-06 13:51:40 +08:00
Sébastien Bourdeauducq 53abbd569c doc: update repos URLs 2024-06-06 13:50:05 +08:00
architeuthis f84b67829c Deleted reference to board packages 2024-06-06 13:47:14 +08:00
architeuthis ed294a99a9 Remove outdated references to examples/master, fix labels 2024-06-06 13:47:14 +08:00
architeuthis 06a3557cda docs: 'Installing ARTIQ' manual page overhaul 2024-06-06 13:47:14 +08:00
67 changed files with 1304 additions and 1301 deletions

View File

@ -7,18 +7,22 @@
ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is a leading-edge control and data acquisition system for quantum information experiments. ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is a leading-edge control and data acquisition system for quantum information experiments.
It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Many laboratories around the world have adopted ARTIQ as their control system and some have `contributed <https://m-labs.hk/experiment-control/funding/>`_ to it. It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Many laboratories around the world have adopted ARTIQ as their control system and some have `contributed <https://m-labs.hk/experiment-control/funding/>`_ to it.
The system features a high-level programming language, capable of describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results. The system features a high-level programming language that helps describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results.
ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware <https://github.com/sinara-hw>`_, and in particular the Kasli FPGA carrier, are designed to work with ARTIQ. ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers. Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort. ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware <https://github.com/sinara-hw>`_, and in particular the Kasli FPGA carrier, is designed to work with ARTIQ.
ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers.
Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and of a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort.
ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions. Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration. Like any open-source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_. ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions.
Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration.
Like any open source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_.
ARTIQ is supported by M-Labs and developed openly. Components, features, fixes, improvements, and extensions are often `funded <https://m-labs.hk/experiment-control/funding/>`_ by and developed for the partnering research groups. ARTIQ is supported by M-Labs and developed openly.
Components, features, fixes, improvements, and extensions are often `funded <https://m-labs.hk/experiment-control/funding/>`_ by and developed for the partnering research groups.
Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt6 <https://www.qt.io/>`_. Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt5 <https://www.qt.io/>`_.
| Website: https://m-labs.hk/experiment-control/artiq Website: https://m-labs.hk/artiq
| (US-hosted mirror: https://m-labs-intl.com/experiment-control/artiq)
`Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``. `Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``.

View File

@ -3,18 +3,6 @@
Release notes Release notes
============= =============
ARTIQ-9 (Unreleased)
--------------------
* GUI state files are now automatically backed up upon successful loading.
* Zotino monitoring in the dashboard now displays the values in volts.
* afws_client now uses the "happy eyeballs" algorithm (RFC 6555) for a faster and more
reliable connection to the server.
* The Zadig driver installer was added to the MSYS2 offline installer.
* Fastino monitoring with Moninj is now supported.
* Qt6 support.
* Python 3.12 support.
ARTIQ-8 ARTIQ-8
------- -------

View File

@ -4,4 +4,4 @@ def get_rev():
return os.getenv("VERSIONEER_REV", default="unknown") return os.getenv("VERSIONEER_REV", default="unknown")
def get_version(): def get_version():
return os.getenv("VERSIONEER_OVERRIDE", default="9.0+unknown.beta") return os.getenv("VERSIONEER_OVERRIDE", default="8.0+unknown.beta")

557
artiq/appdirs.py Normal file
View File

@ -0,0 +1,557 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2005-2010 ActiveState Software Inc.
# Copyright (c) 2013 Eddy Petrișor
"""Utilities for determining application-specific dirs.
See <http://github.com/ActiveState/appdirs> for details and usage.
"""
# Dev Notes:
# - MSDN on where to store app data files:
# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
__version_info__ = (1, 4, 1)
__version__ = '.'.join(map(str, __version_info__))
import sys
import os
PY3 = sys.version_info[0] == 3
if PY3:
unicode = str
if sys.platform.startswith('java'):
import platform
os_name = platform.java_ver()[3][0]
if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc.
system = 'win32'
elif os_name.startswith('Mac'): # "Mac OS X", etc.
system = 'darwin'
else: # "Linux", "SunOS", "FreeBSD", etc.
# Setting this to "linux2" is not ideal, but only Windows or Mac
# are actually checked for and the rest of the module expects
# *sys.platform* style strings.
system = 'linux2'
else:
system = sys.platform
def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
r"""Return full path to the user-specific data dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"roaming" (boolean, default False) can be set True to use the Windows
roaming appdata directory. That means that for users on a Windows
network setup for roaming profiles, this user data will be
sync'd on login. See
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
for a discussion of issues.
Typical user data directories are:
Mac OS X: ~/Library/Application Support/<AppName>
Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
That means, by default "~/.local/share/<AppName>".
"""
if system == "win32":
if appauthor is None:
appauthor = appname
const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
path = os.path.normpath(_get_win_folder(const))
if appname:
if appauthor is not False:
path = os.path.join(path, appauthor, appname)
else:
path = os.path.join(path, appname)
elif system == 'darwin':
path = os.path.expanduser('~/Library/Application Support/')
if appname:
path = os.path.join(path, appname)
else:
path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share"))
if appname:
path = os.path.join(path, appname)
if appname and version:
path = os.path.join(path, version)
return path
def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
"""Return full path to the user-shared data dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"multipath" is an optional parameter only applicable to *nix
which indicates that the entire list of data dirs should be
returned. By default, the first item from XDG_DATA_DIRS is
returned, or '/usr/local/share/<AppName>',
if XDG_DATA_DIRS is not set
Typical user data directories are:
Mac OS X: /Library/Application Support/<AppName>
Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7.
For Unix, this is using the $XDG_DATA_DIRS[0] default.
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
"""
if system == "win32":
if appauthor is None:
appauthor = appname
path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
if appname:
if appauthor is not False:
path = os.path.join(path, appauthor, appname)
else:
path = os.path.join(path, appname)
elif system == 'darwin':
path = os.path.expanduser('/Library/Application Support')
if appname:
path = os.path.join(path, appname)
else:
# XDG default for $XDG_DATA_DIRS
# only first, if multipath is False
path = os.getenv('XDG_DATA_DIRS',
os.pathsep.join(['/usr/local/share', '/usr/share']))
pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
if appname:
if version:
appname = os.path.join(appname, version)
pathlist = [os.sep.join([x, appname]) for x in pathlist]
if multipath:
path = os.pathsep.join(pathlist)
else:
path = pathlist[0]
return path
if appname and version:
path = os.path.join(path, version)
return path
def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
r"""Return full path to the user-specific config dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"roaming" (boolean, default False) can be set True to use the Windows
roaming appdata directory. That means that for users on a Windows
network setup for roaming profiles, this user data will be
sync'd on login. See
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
for a discussion of issues.
Typical user data directories are:
Mac OS X: same as user_data_dir
Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
Win *: same as user_data_dir
For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
That means, by deafult "~/.config/<AppName>".
"""
if system in ["win32", "darwin"]:
path = user_data_dir(appname, appauthor, None, roaming)
else:
path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))
if appname:
path = os.path.join(path, appname)
if appname and version:
path = os.path.join(path, version)
return path
def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
"""Return full path to the user-shared data dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"multipath" is an optional parameter only applicable to *nix
which indicates that the entire list of config dirs should be
returned. By default, the first item from XDG_CONFIG_DIRS is
returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set
Typical user data directories are:
Mac OS X: same as site_data_dir
Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in
$XDG_CONFIG_DIRS
Win *: same as site_data_dir
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
"""
if system in ["win32", "darwin"]:
path = site_data_dir(appname, appauthor)
if appname and version:
path = os.path.join(path, version)
else:
# XDG default for $XDG_CONFIG_DIRS
# only first, if multipath is False
path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg')
pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
if appname:
if version:
appname = os.path.join(appname, version)
pathlist = [os.sep.join([x, appname]) for x in pathlist]
if multipath:
path = os.pathsep.join(pathlist)
else:
path = pathlist[0]
return path
def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
r"""Return full path to the user-specific cache dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"opinion" (boolean) can be False to disable the appending of
"Cache" to the base app data dir for Windows. See
discussion below.
Typical user cache directories are:
Mac OS X: ~/Library/Caches/<AppName>
Unix: ~/.cache/<AppName> (XDG default)
Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
On Windows the only suggestion in the MSDN docs is that local settings go in
the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
app data dir (the default returned by `user_data_dir` above). Apps typically
put cache data somewhere *under* the given dir here. Some examples:
...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
...\Acme\SuperApp\Cache\1.0
OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
This can be disabled with the `opinion=False` option.
"""
if system == "win32":
if appauthor is None:
appauthor = appname
path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
if appname:
if appauthor is not False:
path = os.path.join(path, appauthor, appname)
else:
path = os.path.join(path, appname)
if opinion:
path = os.path.join(path, "Cache")
elif system == 'darwin':
path = os.path.expanduser('~/Library/Caches')
if appname:
path = os.path.join(path, appname)
else:
path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
if appname:
path = os.path.join(path, appname)
if appname and version:
path = os.path.join(path, version)
return path
def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
r"""Return full path to the user-specific log dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"opinion" (boolean) can be False to disable the appending of
"Logs" to the base app data dir for Windows, and "log" to the
base cache dir for Unix. See discussion below.
Typical user cache directories are:
Mac OS X: ~/Library/Logs/<AppName>
Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined
Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
On Windows the only suggestion in the MSDN docs is that local settings
go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
examples of what some windows apps use for a logs dir.)
OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
value for Windows and appends "log" to the user cache dir for Unix.
This can be disabled with the `opinion=False` option.
"""
if system == "darwin":
path = os.path.join(
os.path.expanduser('~/Library/Logs'),
appname)
elif system == "win32":
path = user_data_dir(appname, appauthor, version)
version = False
if opinion:
path = os.path.join(path, "Logs")
else:
path = user_cache_dir(appname, appauthor, version)
version = False
if opinion:
path = os.path.join(path, "log")
if appname and version:
path = os.path.join(path, version)
return path
class AppDirs(object):
"""Convenience wrapper for getting application dirs."""
def __init__(self, appname, appauthor=None, version=None, roaming=False,
multipath=False):
self.appname = appname
self.appauthor = appauthor
self.version = version
self.roaming = roaming
self.multipath = multipath
@property
def user_data_dir(self):
return user_data_dir(self.appname, self.appauthor,
version=self.version, roaming=self.roaming)
@property
def site_data_dir(self):
return site_data_dir(self.appname, self.appauthor,
version=self.version, multipath=self.multipath)
@property
def user_config_dir(self):
return user_config_dir(self.appname, self.appauthor,
version=self.version, roaming=self.roaming)
@property
def site_config_dir(self):
return site_config_dir(self.appname, self.appauthor,
version=self.version, multipath=self.multipath)
@property
def user_cache_dir(self):
return user_cache_dir(self.appname, self.appauthor,
version=self.version)
@property
def user_log_dir(self):
return user_log_dir(self.appname, self.appauthor,
version=self.version)
#---- internal support stuff
def _get_win_folder_from_registry(csidl_name):
"""This is a fallback technique at best. I'm not sure if using the
registry for this guarantees us the correct answer for all CSIDL_*
names.
"""
if PY3:
import winreg as _winreg
else:
import _winreg
shell_folder_name = {
"CSIDL_APPDATA": "AppData",
"CSIDL_COMMON_APPDATA": "Common AppData",
"CSIDL_LOCAL_APPDATA": "Local AppData",
}[csidl_name]
key = _winreg.OpenKey(
_winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
)
dir, type = _winreg.QueryValueEx(key, shell_folder_name)
return dir
def _get_win_folder_with_pywin32(csidl_name):
from win32com.shell import shellcon, shell
dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
# Try to make this a unicode path because SHGetFolderPath does
# not return unicode strings when there is unicode data in the
# path.
try:
dir = unicode(dir)
# Downgrade to short path name if have highbit chars. See
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
has_high_char = False
for c in dir:
if ord(c) > 255:
has_high_char = True
break
if has_high_char:
try:
import win32api
dir = win32api.GetShortPathName(dir)
except ImportError:
pass
except UnicodeError:
pass
return dir
def _get_win_folder_with_ctypes(csidl_name):
import ctypes
csidl_const = {
"CSIDL_APPDATA": 26,
"CSIDL_COMMON_APPDATA": 35,
"CSIDL_LOCAL_APPDATA": 28,
}[csidl_name]
buf = ctypes.create_unicode_buffer(1024)
ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
# Downgrade to short path name if have highbit chars. See
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
has_high_char = False
for c in buf:
if ord(c) > 255:
has_high_char = True
break
if has_high_char:
buf2 = ctypes.create_unicode_buffer(1024)
if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
buf = buf2
return buf.value
def _get_win_folder_with_jna(csidl_name):
import array
from com.sun import jna
from com.sun.jna.platform import win32
buf_size = win32.WinDef.MAX_PATH * 2
buf = array.zeros('c', buf_size)
shell = win32.Shell32.INSTANCE
shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf)
dir = jna.Native.toString(buf.tostring()).rstrip("\0")
# Downgrade to short path name if have highbit chars. See
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
has_high_char = False
for c in dir:
if ord(c) > 255:
has_high_char = True
break
if has_high_char:
buf = array.zeros('c', buf_size)
kernel = win32.Kernel32.INSTANCE
if kernel.GetShortPathName(dir, buf, buf_size):
dir = jna.Native.toString(buf.tostring()).rstrip("\0")
return dir
if system == "win32":
try:
import win32com.shell
_get_win_folder = _get_win_folder_with_pywin32
except ImportError:
try:
from ctypes import windll
_get_win_folder = _get_win_folder_with_ctypes
except ImportError:
try:
import com.sun.jna
_get_win_folder = _get_win_folder_with_jna
except ImportError:
_get_win_folder = _get_win_folder_from_registry
#---- self test code
if __name__ == "__main__":
appname = "MyApp"
appauthor = "MyCompany"
props = ("user_data_dir", "site_data_dir",
"user_config_dir", "site_config_dir",
"user_cache_dir", "user_log_dir")
print("-- app dirs %s --" % __version__)
print("-- app dirs (with optional 'version')")
dirs = AppDirs(appname, appauthor, version="1.0")
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (without optional 'version')")
dirs = AppDirs(appname, appauthor)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (without optional 'appauthor')")
dirs = AppDirs(appname)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (with disabled 'appauthor')")
dirs = AppDirs(appname, appauthor=False)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from PyQt6 import QtWidgets, QtCore, QtGui from PyQt5 import QtWidgets, QtCore, QtGui
from artiq.applets.simple import SimpleApplet from artiq.applets.simple import SimpleApplet
from artiq.tools import scale_from_metadata from artiq.tools import scale_from_metadata
from artiq.gui.tools import LayoutWidget from artiq.gui.tools import LayoutWidget
@ -17,7 +17,7 @@ class QCancellableLineEdit(QtWidgets.QLineEdit):
editCancelled = QtCore.pyqtSignal() editCancelled = QtCore.pyqtSignal()
def keyPressEvent(self, event): def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key.Key_Escape: if event.key() == QtCore.Qt.Key_Escape:
self.editCancelled.emit() self.editCancelled.emit()
else: else:
super().keyPressEvent(event) super().keyPressEvent(event)
@ -34,7 +34,7 @@ class NumberWidget(LayoutWidget):
self.addWidget(self.number_area, 0, 0) self.addWidget(self.number_area, 0, 0)
self.unit_area = QtWidgets.QLabel() self.unit_area = QtWidgets.QLabel()
self.unit_area.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignTop) self.unit_area.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop)
self.addWidget(self.unit_area, 0, 1) self.addWidget(self.unit_area, 0, 1)
self.lcd_widget = QResponsiveLCDNumber() self.lcd_widget = QResponsiveLCDNumber()
@ -44,7 +44,7 @@ class NumberWidget(LayoutWidget):
self.edit_widget = QCancellableLineEdit() self.edit_widget = QCancellableLineEdit()
self.edit_widget.setValidator(QtGui.QDoubleValidator()) self.edit_widget.setValidator(QtGui.QDoubleValidator())
self.edit_widget.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter) self.edit_widget.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.edit_widget.editCancelled.connect(self.cancel_edit) self.edit_widget.editCancelled.connect(self.cancel_edit)
self.edit_widget.returnPressed.connect(self.confirm_edit) self.edit_widget.returnPressed.connect(self.confirm_edit)
self.number_area.addWidget(self.edit_widget) self.number_area.addWidget(self.edit_widget)

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import PyQt6 # make sure pyqtgraph imports Qt6 import PyQt5 # make sure pyqtgraph imports Qt5
import pyqtgraph import pyqtgraph
from artiq.applets.simple import SimpleApplet from artiq.applets.simple import SimpleApplet

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import PyQt6 # make sure pyqtgraph imports Qt6 import PyQt5 # make sure pyqtgraph imports Qt5
from PyQt6.QtCore import QTimer from PyQt5.QtCore import QTimer
import pyqtgraph import pyqtgraph
from artiq.applets.simple import TitleApplet from artiq.applets.simple import TitleApplet

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import numpy as np import numpy as np
import PyQt6 # make sure pyqtgraph imports Qt6 import PyQt5 # make sure pyqtgraph imports Qt5
from PyQt6.QtCore import QTimer from PyQt5.QtCore import QTimer
import pyqtgraph import pyqtgraph
from artiq.applets.simple import TitleApplet from artiq.applets.simple import TitleApplet

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import numpy as np import numpy as np
from PyQt6 import QtWidgets from PyQt5 import QtWidgets
from PyQt6.QtCore import QTimer from PyQt5.QtCore import QTimer
import pyqtgraph import pyqtgraph
from artiq.applets.simple import SimpleApplet from artiq.applets.simple import SimpleApplet

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from PyQt6 import QtWidgets from PyQt5 import QtWidgets
from artiq.applets.simple import SimpleApplet from artiq.applets.simple import SimpleApplet

View File

@ -137,8 +137,9 @@ class AppletIPCClient(AsyncioChildComm):
logger.error("unexpected action reply to embed request: %s", logger.error("unexpected action reply to embed request: %s",
reply["action"]) reply["action"])
self.close_cb() self.close_cb()
else:
return reply["size_w"], reply["size_h"] def fix_initial_size(self):
self.write_pyon({"action": "fix_initial_size"})
async def listen(self): async def listen(self):
data = None data = None
@ -272,7 +273,7 @@ class SimpleApplet:
# HACK: if the window has a frame, there will be garbage # HACK: if the window has a frame, there will be garbage
# (usually white) displayed at its right and bottom borders # (usually white) displayed at its right and bottom borders
# after it is embedded. # after it is embedded.
self.main_widget.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint) self.main_widget.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.main_widget.show() self.main_widget.show()
win_id = int(self.main_widget.winId()) win_id = int(self.main_widget.winId())
self.loop.run_until_complete(self.ipc.embed(win_id)) self.loop.run_until_complete(self.ipc.embed(win_id))
@ -285,13 +286,12 @@ class SimpleApplet:
# 2. applet creates native window without showing it, and # 2. applet creates native window without showing it, and
# gets its ID # gets its ID
# 3. applet sends the ID to host, host embeds the widget # 3. applet sends the ID to host, host embeds the widget
# and returns embedded size # 4. applet shows the widget
# 4. applet is resized to that given size # 5. parent resizes the widget
# 5. applet shows the widget
win_id = int(self.main_widget.winId()) win_id = int(self.main_widget.winId())
size_w, size_h = self.loop.run_until_complete(self.ipc.embed(win_id)) self.loop.run_until_complete(self.ipc.embed(win_id))
self.main_widget.resize(size_w, size_h)
self.main_widget.show() self.main_widget.show()
self.ipc.fix_initial_size()
else: else:
self.main_widget.show() self.main_widget.show()

View File

@ -1,12 +1,12 @@
import logging import logging
import asyncio import asyncio
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtWidgets
from sipyco.pc_rpc import AsyncioClient as RPCClient from sipyco.pc_rpc import AsyncioClient as RPCClient
from artiq.tools import short_format from artiq.tools import short_format
from artiq.gui.tools import LayoutWidget from artiq.gui.tools import LayoutWidget, QRecursiveFilterProxyModel
from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.models import DictSyncTreeSepModel
# reduced read-only version of artiq.dashboard.datasets # reduced read-only version of artiq.dashboard.datasets
@ -62,8 +62,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
def __init__(self, dataset_sub, dataset_ctl): def __init__(self, dataset_sub, dataset_ctl):
QtWidgets.QDockWidget.__init__(self, "Datasets") QtWidgets.QDockWidget.__init__(self, "Datasets")
self.setObjectName("Datasets") self.setObjectName("Datasets")
self.setFeatures(self.DockWidgetFeature.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
self.DockWidgetFeature.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFloatable)
grid = LayoutWidget() grid = LayoutWidget()
self.setWidget(grid) self.setWidget(grid)
@ -74,9 +74,9 @@ class DatasetsDock(QtWidgets.QDockWidget):
grid.addWidget(self.search, 0, 0) grid.addWidget(self.search, 0, 0)
self.table = QtWidgets.QTreeView() self.table = QtWidgets.QTreeView()
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.setSelectionMode( self.table.setSelectionMode(
QtWidgets.QAbstractItemView.SelectionMode.SingleSelection) QtWidgets.QAbstractItemView.SingleSelection)
grid.addWidget(self.table, 1, 0) grid.addWidget(self.table, 1, 0)
metadata_grid = LayoutWidget() metadata_grid = LayoutWidget()
@ -85,13 +85,13 @@ class DatasetsDock(QtWidgets.QDockWidget):
"rid start_time".split()): "rid start_time".split()):
metadata_grid.addWidget(QtWidgets.QLabel(label), i, 0) metadata_grid.addWidget(QtWidgets.QLabel(label), i, 0)
v = QtWidgets.QLabel() v = QtWidgets.QLabel()
v.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse) v.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
metadata_grid.addWidget(v, i, 1) metadata_grid.addWidget(v, i, 1)
self.metadata[label] = v self.metadata[label] = v
grid.addWidget(metadata_grid, 2, 0) grid.addWidget(metadata_grid, 2, 0)
self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu) self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
upload_action = QtGui.QAction("Upload dataset to master", upload_action = QtWidgets.QAction("Upload dataset to master",
self.table) self.table)
upload_action.triggered.connect(self.upload_clicked) upload_action.triggered.connect(self.upload_clicked)
self.table.addAction(upload_action) self.table.addAction(upload_action)
@ -112,8 +112,7 @@ class DatasetsDock(QtWidgets.QDockWidget):
def set_model(self, model): def set_model(self, model):
self.table_model = model self.table_model = model
self.table_model_filter = QtCore.QSortFilterProxyModel() self.table_model_filter = QRecursiveFilterProxyModel()
self.table_model_filter.setRecursiveFilteringEnabled(True)
self.table_model_filter.setSourceModel(self.table_model) self.table_model_filter.setSourceModel(self.table_model)
self.table.setModel(self.table_model_filter) self.table.setModel(self.table_model_filter)

View File

@ -4,7 +4,7 @@ import os
from functools import partial from functools import partial
from collections import OrderedDict from collections import OrderedDict
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
import h5py import h5py
from sipyco import pyon from sipyco import pyon
@ -33,13 +33,13 @@ class _ArgumentEditor(EntryTreeWidget):
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments") recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
recompute_arguments.setIcon( recompute_arguments.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_BrowserReload)) QtWidgets.QStyle.SP_BrowserReload))
recompute_arguments.clicked.connect(self._recompute_arguments_clicked) recompute_arguments.clicked.connect(self._recompute_arguments_clicked)
load = QtWidgets.QPushButton("Set arguments from HDF5") load = QtWidgets.QPushButton("Set arguments from HDF5")
load.setToolTip("Set arguments from currently selected HDF5 file") load.setToolTip("Set arguments from currently selected HDF5 file")
load.setIcon(QtWidgets.QApplication.style().standardIcon( load.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogApplyButton)) QtWidgets.QStyle.SP_DialogApplyButton))
load.clicked.connect(self._load_clicked) load.clicked.connect(self._load_clicked)
buttons = LayoutWidget() buttons = LayoutWidget()
@ -86,7 +86,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
self.resize(100*qfm.averageCharWidth(), 30*qfm.lineSpacing()) self.resize(100*qfm.averageCharWidth(), 30*qfm.lineSpacing())
self.setWindowTitle(expurl) self.setWindowTitle(expurl)
self.setWindowIcon(QtWidgets.QApplication.style().standardIcon( self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileDialogContentsView)) QtWidgets.QStyle.SP_FileDialogContentsView))
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.layout = QtWidgets.QGridLayout() self.layout = QtWidgets.QGridLayout()
@ -126,22 +126,22 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
run = QtWidgets.QPushButton("Analyze") run = QtWidgets.QPushButton("Analyze")
run.setIcon(QtWidgets.QApplication.style().standardIcon( run.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton)) QtWidgets.QStyle.SP_DialogOkButton))
run.setToolTip("Run analysis stage (Ctrl+Return)") run.setToolTip("Run analysis stage (Ctrl+Return)")
run.setShortcut("CTRL+RETURN") run.setShortcut("CTRL+RETURN")
run.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, run.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding) QtWidgets.QSizePolicy.Expanding)
self.layout.addWidget(run, 2, 4) self.layout.addWidget(run, 2, 4)
run.clicked.connect(self._run_clicked) run.clicked.connect(self._run_clicked)
self._run = run self._run = run
terminate = QtWidgets.QPushButton("Terminate") terminate = QtWidgets.QPushButton("Terminate")
terminate.setIcon(QtWidgets.QApplication.style().standardIcon( terminate.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton)) QtWidgets.QStyle.SP_DialogCancelButton))
terminate.setToolTip("Terminate analysis (Ctrl+Backspace)") terminate.setToolTip("Terminate analysis (Ctrl+Backspace)")
terminate.setShortcut("CTRL+BACKSPACE") terminate.setShortcut("CTRL+BACKSPACE")
terminate.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, terminate.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding) QtWidgets.QSizePolicy.Expanding)
self.layout.addWidget(terminate, 3, 4) self.layout.addWidget(terminate, 3, 4)
terminate.clicked.connect(self._terminate_clicked) terminate.clicked.connect(self._terminate_clicked)
terminate.setEnabled(False) terminate.setEnabled(False)
@ -316,7 +316,7 @@ class ExperimentsArea(QtWidgets.QMdiArea):
asyncio.ensure_future(sub.load_hdf5_task(path)) asyncio.ensure_future(sub.load_hdf5_task(path))
def mousePressEvent(self, ev): def mousePressEvent(self, ev):
if ev.button() == QtCore.Qt.MouseButton.LeftButton: if ev.button() == QtCore.Qt.LeftButton:
self.select_experiment() self.select_experiment()
def paintEvent(self, event): def paintEvent(self, event):
@ -406,7 +406,7 @@ class ExperimentsArea(QtWidgets.QMdiArea):
exc_info=True) exc_info=True)
dock = _ExperimentDock(self, expurl, {}) dock = _ExperimentDock(self, expurl, {})
asyncio.ensure_future(dock._recompute_arguments()) asyncio.ensure_future(dock._recompute_arguments())
dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose) dock.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.addSubWindow(dock) self.addSubWindow(dock)
dock.show() dock.show()
dock.sigClosed.connect(partial(self.on_dock_closed, dock)) dock.sigClosed.connect(partial(self.on_dock_closed, dock))

View File

@ -3,7 +3,7 @@ import os
from datetime import datetime from datetime import datetime
import h5py import h5py
from PyQt6 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
from sipyco import pyon from sipyco import pyon
@ -69,15 +69,15 @@ class ZoomIconView(QtWidgets.QListView):
def __init__(self): def __init__(self):
QtWidgets.QListView.__init__(self) QtWidgets.QListView.__init__(self)
self._char_width = QtGui.QFontMetrics(self.font()).averageCharWidth() self._char_width = QtGui.QFontMetrics(self.font()).averageCharWidth()
self.setViewMode(self.ViewMode.IconMode) self.setViewMode(self.IconMode)
w = self._char_width*self.default_size w = self._char_width*self.default_size
self.setIconSize(QtCore.QSize(w, int(w*self.aspect))) self.setIconSize(QtCore.QSize(w, int(w*self.aspect)))
self.setFlow(self.Flow.LeftToRight) self.setFlow(self.LeftToRight)
self.setResizeMode(self.ResizeMode.Adjust) self.setResizeMode(self.Adjust)
self.setWrapping(True) self.setWrapping(True)
def wheelEvent(self, ev): def wheelEvent(self, ev):
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier: if ev.modifiers() & QtCore.Qt.ControlModifier:
a = self._char_width*self.min_size a = self._char_width*self.min_size
b = self._char_width*self.max_size b = self._char_width*self.max_size
w = self.iconSize().width()*self.zoom_step**( w = self.iconSize().width()*self.zoom_step**(
@ -88,16 +88,16 @@ class ZoomIconView(QtWidgets.QListView):
QtWidgets.QListView.wheelEvent(self, ev) QtWidgets.QListView.wheelEvent(self, ev)
class Hdf5FileSystemModel(QtGui.QFileSystemModel): class Hdf5FileSystemModel(QtWidgets.QFileSystemModel):
def __init__(self): def __init__(self):
QtGui.QFileSystemModel.__init__(self) QtWidgets.QFileSystemModel.__init__(self)
self.setFilter(QtCore.QDir.Filter.Drives | QtCore.QDir.Filter.NoDotAndDotDot | self.setFilter(QtCore.QDir.Drives | QtCore.QDir.NoDotAndDotDot |
QtCore.QDir.Filter.AllDirs | QtCore.QDir.Filter.Files) QtCore.QDir.AllDirs | QtCore.QDir.Files)
self.setNameFilterDisables(False) self.setNameFilterDisables(False)
self.setIconProvider(ThumbnailIconProvider()) self.setIconProvider(ThumbnailIconProvider())
def data(self, idx, role): def data(self, idx, role):
if role == QtCore.Qt.ItemDataRole.ToolTipRole: if role == QtCore.Qt.ToolTipRole:
info = self.fileInfo(idx) info = self.fileInfo(idx)
h5 = open_h5(info) h5 = open_h5(info)
if h5 is not None: if h5 is not None:
@ -114,7 +114,7 @@ class Hdf5FileSystemModel(QtGui.QFileSystemModel):
except: except:
logger.warning("unable to read metadata from %s", logger.warning("unable to read metadata from %s",
info.filePath(), exc_info=True) info.filePath(), exc_info=True)
return QtGui.QFileSystemModel.data(self, idx, role) return QtWidgets.QFileSystemModel.data(self, idx, role)
class FilesDock(QtWidgets.QDockWidget): class FilesDock(QtWidgets.QDockWidget):
@ -125,7 +125,7 @@ class FilesDock(QtWidgets.QDockWidget):
def __init__(self, datasets, browse_root=""): def __init__(self, datasets, browse_root=""):
QtWidgets.QDockWidget.__init__(self, "Files") QtWidgets.QDockWidget.__init__(self, "Files")
self.setObjectName("Files") self.setObjectName("Files")
self.setFeatures(self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable) self.setFeatures(self.DockWidgetMovable | self.DockWidgetFloatable)
self.splitter = QtWidgets.QSplitter() self.splitter = QtWidgets.QSplitter()
self.setWidget(self.splitter) self.setWidget(self.splitter)
@ -147,8 +147,8 @@ class FilesDock(QtWidgets.QDockWidget):
self.rt.setRootIndex(rt_model.mapFromSource( self.rt.setRootIndex(rt_model.mapFromSource(
self.model.setRootPath(browse_root))) self.model.setRootPath(browse_root)))
self.rt.setHeaderHidden(True) self.rt.setHeaderHidden(True)
self.rt.setSelectionBehavior(self.rt.SelectionBehavior.SelectRows) self.rt.setSelectionBehavior(self.rt.SelectRows)
self.rt.setSelectionMode(self.rt.SelectionMode.SingleSelection) self.rt.setSelectionMode(self.rt.SingleSelection)
self.rt.selectionModel().currentChanged.connect( self.rt.selectionModel().currentChanged.connect(
self.tree_current_changed) self.tree_current_changed)
self.rt.setRootIsDecorated(False) self.rt.setRootIsDecorated(False)
@ -252,7 +252,7 @@ class FilesDock(QtWidgets.QDockWidget):
100, 100,
lambda: self.rt.scrollTo( lambda: self.rt.scrollTo(
self.rt.model().mapFromSource(self.model.index(path)), self.rt.model().mapFromSource(self.model.index(path)),
self.rt.ScrollHint.PositionAtCenter) self.rt.PositionAtCenter)
) )
self.model.directoryLoaded.connect(scroll_when_loaded) self.model.directoryLoaded.connect(scroll_when_loaded)
idx = self.rt.model().mapFromSource(idx) idx = self.rt.model().mapFromSource(idx)

View File

@ -92,6 +92,7 @@ class EmbeddingMap:
# The exceptions declared here must be defined in `artiq.coredevice.exceptions` # The exceptions declared here must be defined in `artiq.coredevice.exceptions`
# Verify synchronization by running the test cases in `artiq.test.coredevice.test_exceptions` # Verify synchronization by running the test cases in `artiq.test.coredevice.test_exceptions`
self.preallocate_runtime_exception_names([ self.preallocate_runtime_exception_names([
"0:RuntimeError",
"RTIOUnderflow", "RTIOUnderflow",
"RTIOOverflow", "RTIOOverflow",
"RTIODestinationUnreachable", "RTIODestinationUnreachable",
@ -99,22 +100,10 @@ class EmbeddingMap:
"I2CError", "I2CError",
"CacheError", "CacheError",
"SPIError", "SPIError",
"SubkernelError",
"0:AssertionError",
"0:AttributeError",
"0:IndexError",
"0:IOError",
"0:KeyError",
"0:NotImplementedError",
"0:OverflowError",
"0:RuntimeError",
"0:TimeoutError",
"0:TypeError",
"0:ValueError",
"0:ZeroDivisionError", "0:ZeroDivisionError",
"0:LinAlgError", "0:IndexError",
"UnwrapNoneError", "UnwrapNoneError",
"SubkernelError",
]) ])
def preallocate_runtime_exception_names(self, names): def preallocate_runtime_exception_names(self, names):

View File

@ -181,25 +181,25 @@ class AnalyzerProxyReceiver:
async def _receive_cr(self): async def _receive_cr(self):
try: try:
while True: while True:
data = bytearray() endian_byte = await self.reader.read(1)
data.extend(await self.reader.read(1)) if endian_byte == b"E":
if len(data) == 0: endian = '>'
elif endian_byte == b"e":
endian = '<'
elif endian_byte == b"":
# EOF reached, connection lost # EOF reached, connection lost
return return
if data[0] == ord("E"):
endian = '>'
elif data[0] == ord("e"):
endian = '<'
else: else:
raise ValueError raise ValueError
data.extend(await self.reader.readexactly(4)) payload_length_word = await self.reader.readexactly(4)
payload_length = struct.unpack(endian + "I", data[1:5])[0] payload_length = struct.unpack(endian + "I", payload_length_word)[0]
if payload_length > 10 * 512 * 1024: if payload_length > 10 * 512 * 1024:
# 10x buffer size of firmware # 10x buffer size of firmware
raise ValueError raise ValueError
# The remaining header length is 11 bytes. # The remaining header length is 11 bytes.
data.extend(await self.reader.readexactly(payload_length + 11)) remaining_data = await self.reader.readexactly(payload_length + 11)
data = endian_byte + payload_length_word + remaining_data
self.receive_cb(data) self.receive_cb(data)
except Exception: except Exception:
logger.error("analyzer receiver connection terminating with exception", exc_info=True) logger.error("analyzer receiver connection terminating with exception", exc_info=True)

View File

@ -2,7 +2,6 @@ import builtins
import linecache import linecache
import re import re
import os import os
from numpy.linalg import LinAlgError
from artiq import __artiq_dir__ as artiq_dir from artiq import __artiq_dir__ as artiq_dir
from artiq.coredevice.runtime import source_loader from artiq.coredevice.runtime import source_loader
@ -183,7 +182,6 @@ class SPIError(Exception):
"""Raised when a SPI transaction fails.""" """Raised when a SPI transaction fails."""
artiq_builtin = True artiq_builtin = True
class UnwrapNoneError(Exception): class UnwrapNoneError(Exception):
"""Raised when unwrapping a none Option.""" """Raised when unwrapping a none Option."""
artiq_builtin = True artiq_builtin = True

View File

@ -1,7 +1,7 @@
import asyncio import asyncio
import logging import logging
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtWidgets
from artiq.gui import applets from artiq.gui import applets
@ -13,58 +13,58 @@ class AppletsCCBDock(applets.AppletsDock):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
applets.AppletsDock.__init__(self, *args, **kwargs) applets.AppletsDock.__init__(self, *args, **kwargs)
sep = QtGui.QAction(self.table) sep = QtWidgets.QAction(self.table)
sep.setSeparator(True) sep.setSeparator(True)
self.table.addAction(sep) self.table.addAction(sep)
ccbp_group_menu = QtWidgets.QMenu(self.table) ccbp_group_menu = QtWidgets.QMenu()
actiongroup = QtGui.QActionGroup(self.table) actiongroup = QtWidgets.QActionGroup(self.table)
actiongroup.setExclusive(True) actiongroup.setExclusive(True)
self.ccbp_group_none = QtGui.QAction("No policy", self.table) self.ccbp_group_none = QtWidgets.QAction("No policy", self.table)
self.ccbp_group_none.setCheckable(True) self.ccbp_group_none.setCheckable(True)
self.ccbp_group_none.triggered.connect(lambda: self.set_ccbp("")) self.ccbp_group_none.triggered.connect(lambda: self.set_ccbp(""))
ccbp_group_menu.addAction(self.ccbp_group_none) ccbp_group_menu.addAction(self.ccbp_group_none)
actiongroup.addAction(self.ccbp_group_none) actiongroup.addAction(self.ccbp_group_none)
self.ccbp_group_ignore = QtGui.QAction("Ignore requests", self.table) self.ccbp_group_ignore = QtWidgets.QAction("Ignore requests", self.table)
self.ccbp_group_ignore.setCheckable(True) self.ccbp_group_ignore.setCheckable(True)
self.ccbp_group_ignore.triggered.connect(lambda: self.set_ccbp("ignore")) self.ccbp_group_ignore.triggered.connect(lambda: self.set_ccbp("ignore"))
ccbp_group_menu.addAction(self.ccbp_group_ignore) ccbp_group_menu.addAction(self.ccbp_group_ignore)
actiongroup.addAction(self.ccbp_group_ignore) actiongroup.addAction(self.ccbp_group_ignore)
self.ccbp_group_create = QtGui.QAction("Create applets", self.table) self.ccbp_group_create = QtWidgets.QAction("Create applets", self.table)
self.ccbp_group_create.setCheckable(True) self.ccbp_group_create.setCheckable(True)
self.ccbp_group_create.triggered.connect(lambda: self.set_ccbp("create")) self.ccbp_group_create.triggered.connect(lambda: self.set_ccbp("create"))
ccbp_group_menu.addAction(self.ccbp_group_create) ccbp_group_menu.addAction(self.ccbp_group_create)
actiongroup.addAction(self.ccbp_group_create) actiongroup.addAction(self.ccbp_group_create)
self.ccbp_group_enable = QtGui.QAction("Create and enable/disable applets", self.ccbp_group_enable = QtWidgets.QAction("Create and enable/disable applets",
self.table) self.table)
self.ccbp_group_enable.setCheckable(True) self.ccbp_group_enable.setCheckable(True)
self.ccbp_group_enable.triggered.connect(lambda: self.set_ccbp("enable")) self.ccbp_group_enable.triggered.connect(lambda: self.set_ccbp("enable"))
ccbp_group_menu.addAction(self.ccbp_group_enable) ccbp_group_menu.addAction(self.ccbp_group_enable)
actiongroup.addAction(self.ccbp_group_enable) actiongroup.addAction(self.ccbp_group_enable)
self.ccbp_group_action = QtGui.QAction("Group CCB policy", self.table) self.ccbp_group_action = QtWidgets.QAction("Group CCB policy", self.table)
self.ccbp_group_action.setMenu(ccbp_group_menu) self.ccbp_group_action.setMenu(ccbp_group_menu)
self.table.addAction(self.ccbp_group_action) self.table.addAction(self.ccbp_group_action)
self.table.itemSelectionChanged.connect(self.update_group_ccbp_menu) self.table.itemSelectionChanged.connect(self.update_group_ccbp_menu)
self.update_group_ccbp_menu() self.update_group_ccbp_menu()
ccbp_global_menu = QtWidgets.QMenu(self.table) ccbp_global_menu = QtWidgets.QMenu()
actiongroup = QtGui.QActionGroup(self.table) actiongroup = QtWidgets.QActionGroup(self.table)
actiongroup.setExclusive(True) actiongroup.setExclusive(True)
self.ccbp_global_ignore = QtGui.QAction("Ignore requests", self.table) self.ccbp_global_ignore = QtWidgets.QAction("Ignore requests", self.table)
self.ccbp_global_ignore.setCheckable(True) self.ccbp_global_ignore.setCheckable(True)
ccbp_global_menu.addAction(self.ccbp_global_ignore) ccbp_global_menu.addAction(self.ccbp_global_ignore)
actiongroup.addAction(self.ccbp_global_ignore) actiongroup.addAction(self.ccbp_global_ignore)
self.ccbp_global_create = QtGui.QAction("Create applets", self.table) self.ccbp_global_create = QtWidgets.QAction("Create applets", self.table)
self.ccbp_global_create.setCheckable(True) self.ccbp_global_create.setCheckable(True)
self.ccbp_global_create.setChecked(True) self.ccbp_global_create.setChecked(True)
ccbp_global_menu.addAction(self.ccbp_global_create) ccbp_global_menu.addAction(self.ccbp_global_create)
actiongroup.addAction(self.ccbp_global_create) actiongroup.addAction(self.ccbp_global_create)
self.ccbp_global_enable = QtGui.QAction("Create and enable/disable applets", self.ccbp_global_enable = QtWidgets.QAction("Create and enable/disable applets",
self.table) self.table)
self.ccbp_global_enable.setCheckable(True) self.ccbp_global_enable.setCheckable(True)
ccbp_global_menu.addAction(self.ccbp_global_enable) ccbp_global_menu.addAction(self.ccbp_global_enable)
actiongroup.addAction(self.ccbp_global_enable) actiongroup.addAction(self.ccbp_global_enable)
ccbp_global_action = QtGui.QAction("Global CCB policy", self.table) ccbp_global_action = QtWidgets.QAction("Global CCB policy", self.table)
ccbp_global_action.setMenu(ccbp_global_menu) ccbp_global_action.setMenu(ccbp_global_menu)
self.table.addAction(ccbp_global_action) self.table.addAction(ccbp_global_action)
@ -196,7 +196,7 @@ class AppletsCCBDock(applets.AppletsDock):
logger.debug("Applet %s already exists and no update required", name) logger.debug("Applet %s already exists and no update required", name)
if ccbp == "enable": if ccbp == "enable":
applet.setCheckState(0, QtCore.Qt.CheckState.Checked) applet.setCheckState(0, QtCore.Qt.Checked)
def ccb_disable_applet(self, name, group=None): def ccb_disable_applet(self, name, group=None):
"""Disables an applet. """Disables an applet.
@ -216,7 +216,7 @@ class AppletsCCBDock(applets.AppletsDock):
return return
parent, applet = self.locate_applet(name, group, False) parent, applet = self.locate_applet(name, group, False)
if applet is not None: if applet is not None:
applet.setCheckState(0, QtCore.Qt.CheckState.Unchecked) applet.setCheckState(0, QtCore.Qt.Unchecked)
def ccb_disable_applet_group(self, group): def ccb_disable_applet_group(self, group):
"""Disables all the applets in a group. """Disables all the applets in a group.
@ -246,7 +246,7 @@ class AppletsCCBDock(applets.AppletsDock):
return return
else: else:
wi = nwi wi = nwi
wi.setCheckState(0, QtCore.Qt.CheckState.Unchecked) wi.setCheckState(0, QtCore.Qt.Unchecked)
def ccb_notify(self, message): def ccb_notify(self, message):
try: try:

View File

@ -2,11 +2,11 @@ import asyncio
import logging import logging
import numpy as np import numpy as np
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtWidgets
from sipyco import pyon from sipyco import pyon
from artiq.tools import scale_from_metadata, short_format, exc_to_warning from artiq.tools import scale_from_metadata, short_format, exc_to_warning
from artiq.gui.tools import LayoutWidget from artiq.gui.tools import LayoutWidget, QRecursiveFilterProxyModel
from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.models import DictSyncTreeSepModel
@ -63,11 +63,11 @@ class CreateEditDialog(QtWidgets.QDialog):
self.cancel = QtWidgets.QPushButton('&Cancel') self.cancel = QtWidgets.QPushButton('&Cancel')
self.buttons = QtWidgets.QDialogButtonBox(self) self.buttons = QtWidgets.QDialogButtonBox(self)
self.buttons.addButton( self.buttons.addButton(
self.ok, QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole) self.ok, QtWidgets.QDialogButtonBox.AcceptRole)
self.buttons.addButton( self.buttons.addButton(
self.cancel, QtWidgets.QDialogButtonBox.ButtonRole.RejectRole) self.cancel, QtWidgets.QDialogButtonBox.RejectRole)
grid.setRowStretch(6, 1) grid.setRowStretch(6, 1)
grid.addWidget(self.buttons, 7, 0, 1, 3, alignment=QtCore.Qt.AlignmentFlag.AlignHCenter) grid.addWidget(self.buttons, 7, 0, 1, 3, alignment=QtCore.Qt.AlignHCenter)
self.buttons.accepted.connect(self.accept) self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject) self.buttons.rejected.connect(self.reject)
@ -125,7 +125,7 @@ class CreateEditDialog(QtWidgets.QDialog):
pyon.encode(result) pyon.encode(result)
except: except:
pixmap = self.style().standardPixmap( pixmap = self.style().standardPixmap(
QtWidgets.QStyle.StandardPixmap.SP_MessageBoxWarning) QtWidgets.QStyle.SP_MessageBoxWarning)
self.data_type.setPixmap(pixmap) self.data_type.setPixmap(pixmap)
self.ok.setEnabled(False) self.ok.setEnabled(False)
else: else:
@ -181,8 +181,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
def __init__(self, dataset_sub, dataset_ctl): def __init__(self, dataset_sub, dataset_ctl):
QtWidgets.QDockWidget.__init__(self, "Datasets") QtWidgets.QDockWidget.__init__(self, "Datasets")
self.setObjectName("Datasets") self.setObjectName("Datasets")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFloatable)
self.dataset_ctl = dataset_ctl self.dataset_ctl = dataset_ctl
grid = LayoutWidget() grid = LayoutWidget()
@ -194,27 +194,27 @@ class DatasetsDock(QtWidgets.QDockWidget):
grid.addWidget(self.search, 0, 0) grid.addWidget(self.search, 0, 0)
self.table = QtWidgets.QTreeView() self.table = QtWidgets.QTreeView()
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.setSelectionMode( self.table.setSelectionMode(
QtWidgets.QAbstractItemView.SelectionMode.SingleSelection) QtWidgets.QAbstractItemView.SingleSelection)
grid.addWidget(self.table, 1, 0) grid.addWidget(self.table, 1, 0)
self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu) self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
create_action = QtGui.QAction("New dataset", self.table) create_action = QtWidgets.QAction("New dataset", self.table)
create_action.triggered.connect(self.create_clicked) create_action.triggered.connect(self.create_clicked)
create_action.setShortcut("CTRL+N") create_action.setShortcut("CTRL+N")
create_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) create_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.table.addAction(create_action) self.table.addAction(create_action)
edit_action = QtGui.QAction("Edit dataset", self.table) edit_action = QtWidgets.QAction("Edit dataset", self.table)
edit_action.triggered.connect(self.edit_clicked) edit_action.triggered.connect(self.edit_clicked)
edit_action.setShortcut("RETURN") edit_action.setShortcut("RETURN")
edit_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) edit_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.table.doubleClicked.connect(self.edit_clicked) self.table.doubleClicked.connect(self.edit_clicked)
self.table.addAction(edit_action) self.table.addAction(edit_action)
delete_action = QtGui.QAction("Delete dataset", self.table) delete_action = QtWidgets.QAction("Delete dataset", self.table)
delete_action.triggered.connect(self.delete_clicked) delete_action.triggered.connect(self.delete_clicked)
delete_action.setShortcut("DELETE") delete_action.setShortcut("DELETE")
delete_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.table.addAction(delete_action) self.table.addAction(delete_action)
self.table_model = Model(dict()) self.table_model = Model(dict())
@ -227,8 +227,7 @@ class DatasetsDock(QtWidgets.QDockWidget):
def set_model(self, model): def set_model(self, model):
self.table_model = model self.table_model = model
self.table_model_filter = QtCore.QSortFilterProxyModel() self.table_model_filter = QRecursiveFilterProxyModel()
self.table_model_filter.setRecursiveFilteringEnabled(True)
self.table_model_filter.setSourceModel(self.table_model) self.table_model_filter.setSourceModel(self.table_model)
self.table.setModel(self.table_model_filter) self.table.setModel(self.table_model_filter)

View File

@ -4,7 +4,7 @@ import os
from functools import partial from functools import partial
from collections import OrderedDict from collections import OrderedDict
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
import h5py import h5py
from sipyco import pyon from sipyco import pyon
@ -44,12 +44,12 @@ class _ArgumentEditor(EntryTreeWidget):
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments") recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
recompute_arguments.setIcon( recompute_arguments.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_BrowserReload)) QtWidgets.QStyle.SP_BrowserReload))
recompute_arguments.clicked.connect(dock._recompute_arguments_clicked) recompute_arguments.clicked.connect(dock._recompute_arguments_clicked)
load_hdf5 = QtWidgets.QPushButton("Load HDF5") load_hdf5 = QtWidgets.QPushButton("Load HDF5")
load_hdf5.setIcon(QtWidgets.QApplication.style().standardIcon( load_hdf5.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton)) QtWidgets.QStyle.SP_DialogOpenButton))
load_hdf5.clicked.connect(dock._load_hdf5_clicked) load_hdf5.clicked.connect(dock._load_hdf5_clicked)
buttons = LayoutWidget() buttons = LayoutWidget()
@ -101,7 +101,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
self.resize(100 * qfm.averageCharWidth(), 30 * qfm.lineSpacing()) self.resize(100 * qfm.averageCharWidth(), 30 * qfm.lineSpacing())
self.setWindowTitle(expurl) self.setWindowTitle(expurl)
self.setWindowIcon(QtWidgets.QApplication.style().standardIcon( self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileDialogContentsView)) QtWidgets.QStyle.SP_FileDialogContentsView))
self.layout = QtWidgets.QGridLayout() self.layout = QtWidgets.QGridLayout()
top_widget = QtWidgets.QWidget() top_widget = QtWidgets.QWidget()
@ -237,21 +237,21 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
submit = QtWidgets.QPushButton("Submit") submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon( submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton)) QtWidgets.QStyle.SP_DialogOkButton))
submit.setToolTip("Schedule the experiment (Ctrl+Return)") submit.setToolTip("Schedule the experiment (Ctrl+Return)")
submit.setShortcut("CTRL+RETURN") submit.setShortcut("CTRL+RETURN")
submit.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, submit.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding) QtWidgets.QSizePolicy.Expanding)
self.layout.addWidget(submit, 1, 4, 2, 1) self.layout.addWidget(submit, 1, 4, 2, 1)
submit.clicked.connect(self.submit_clicked) submit.clicked.connect(self.submit_clicked)
reqterm = QtWidgets.QPushButton("Terminate instances") reqterm = QtWidgets.QPushButton("Terminate instances")
reqterm.setIcon(QtWidgets.QApplication.style().standardIcon( reqterm.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton)) QtWidgets.QStyle.SP_DialogCancelButton))
reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)") reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)")
reqterm.setShortcut("CTRL+BACKSPACE") reqterm.setShortcut("CTRL+BACKSPACE")
reqterm.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, reqterm.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding) QtWidgets.QSizePolicy.Expanding)
self.layout.addWidget(reqterm, 3, 4) self.layout.addWidget(reqterm, 3, 4)
reqterm.clicked.connect(self.reqterm_clicked) reqterm.clicked.connect(self.reqterm_clicked)
@ -306,7 +306,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
menu = QtWidgets.QMenu(self) menu = QtWidgets.QMenu(self)
reset_sched = menu.addAction("Reset scheduler settings") reset_sched = menu.addAction("Reset scheduler settings")
action = menu.exec(self.mapToGlobal(event.pos())) action = menu.exec_(self.mapToGlobal(event.pos()))
if action == reset_sched: if action == reset_sched:
asyncio.ensure_future(self._recompute_sched_options_task()) asyncio.ensure_future(self._recompute_sched_options_task())
@ -423,7 +423,7 @@ class _QuickOpenDialog(QtWidgets.QDialog):
QtWidgets.QDialog.done(self, r) QtWidgets.QDialog.done(self, r)
def _open_experiment(self, exp_name, modifiers): def _open_experiment(self, exp_name, modifiers):
if modifiers & QtCore.Qt.KeyboardModifier.ControlModifier: if modifiers & QtCore.Qt.ControlModifier:
try: try:
self.manager.submit(exp_name) self.manager.submit(exp_name)
except: except:
@ -467,10 +467,10 @@ class ExperimentManager:
self.open_experiments = dict() self.open_experiments = dict()
self.is_quick_open_shown = False self.is_quick_open_shown = False
quick_open_shortcut = QtGui.QShortcut( quick_open_shortcut = QtWidgets.QShortcut(
QtGui.QKeySequence("Ctrl+P"), QtCore.Qt.CTRL + QtCore.Qt.Key_P,
main_window) main_window)
quick_open_shortcut.setContext(QtCore.Qt.ShortcutContext.ApplicationShortcut) quick_open_shortcut.setContext(QtCore.Qt.ApplicationShortcut)
quick_open_shortcut.activated.connect(self.show_quick_open) quick_open_shortcut.activated.connect(self.show_quick_open)
def set_dataset_model(self, model): def set_dataset_model(self, model):
@ -589,7 +589,7 @@ class ExperimentManager:
del self.submission_arguments[expurl] del self.submission_arguments[expurl]
dock = _ExperimentDock(self, expurl) dock = _ExperimentDock(self, expurl)
self.open_experiments[expurl] = dock self.open_experiments[expurl] = dock
dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose) dock.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.main_window.centralWidget().addSubWindow(dock) self.main_window.centralWidget().addSubWindow(dock)
dock.show() dock.show()
dock.sigClosed.connect(partial(self.on_dock_closed, expurl)) dock.sigClosed.connect(partial(self.on_dock_closed, expurl))

View File

@ -3,7 +3,7 @@ import logging
import re import re
from functools import partial from functools import partial
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtWidgets
from artiq.gui.tools import LayoutWidget from artiq.gui.tools import LayoutWidget
from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.models import DictSyncTreeSepModel
@ -37,8 +37,7 @@ class _OpenFileDialog(QtWidgets.QDialog):
self.file_list.doubleClicked.connect(self.accept) self.file_list.doubleClicked.connect(self.accept)
buttons = QtWidgets.QDialogButtonBox( buttons = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
QtWidgets.QDialogButtonBox.StandardButton.Cancel)
grid.addWidget(buttons, 2, 0, 1, 2) grid.addWidget(buttons, 2, 0, 1, 2)
buttons.accepted.connect(self.accept) buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject) buttons.rejected.connect(self.reject)
@ -53,7 +52,7 @@ class _OpenFileDialog(QtWidgets.QDialog):
item = QtWidgets.QListWidgetItem() item = QtWidgets.QListWidgetItem()
item.setText("..") item.setText("..")
item.setIcon(QtWidgets.QApplication.style().standardIcon( item.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileDialogToParent)) QtWidgets.QStyle.SP_FileDialogToParent))
self.file_list.addItem(item) self.file_list.addItem(item)
try: try:
@ -65,9 +64,9 @@ class _OpenFileDialog(QtWidgets.QDialog):
return return
for name in sorted(contents, key=lambda x: (x[-1] not in "\\/", x)): for name in sorted(contents, key=lambda x: (x[-1] not in "\\/", x)):
if name[-1] in "\\/": if name[-1] in "\\/":
icon = QtWidgets.QStyle.StandardPixmap.SP_DirIcon icon = QtWidgets.QStyle.SP_DirIcon
else: else:
icon = QtWidgets.QStyle.StandardPixmap.SP_FileIcon icon = QtWidgets.QStyle.SP_FileIcon
if name[-3:] != ".py": if name[-3:] != ".py":
continue continue
item = QtWidgets.QListWidgetItem() item = QtWidgets.QListWidgetItem()
@ -164,8 +163,8 @@ class ExplorerDock(QtWidgets.QDockWidget):
schedule_ctl, experiment_db_ctl, device_db_ctl): schedule_ctl, experiment_db_ctl, device_db_ctl):
QtWidgets.QDockWidget.__init__(self, "Explorer") QtWidgets.QDockWidget.__init__(self, "Explorer")
self.setObjectName("Explorer") self.setObjectName("Explorer")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFloatable)
top_widget = LayoutWidget() top_widget = LayoutWidget()
self.setWidget(top_widget) self.setWidget(top_widget)
@ -176,7 +175,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
top_widget.addWidget(QtWidgets.QLabel("Revision:"), 0, 0) top_widget.addWidget(QtWidgets.QLabel("Revision:"), 0, 0)
self.revision = QtWidgets.QLabel() self.revision = QtWidgets.QLabel()
self.revision.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse) self.revision.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
top_widget.addWidget(self.revision, 0, 1) top_widget.addWidget(self.revision, 0, 1)
self.stack = QtWidgets.QStackedWidget() self.stack = QtWidgets.QStackedWidget()
@ -188,14 +187,14 @@ class ExplorerDock(QtWidgets.QDockWidget):
self.el = QtWidgets.QTreeView() self.el = QtWidgets.QTreeView()
self.el.setHeaderHidden(True) self.el.setHeaderHidden(True)
self.el.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems) self.el.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
self.el.doubleClicked.connect( self.el.doubleClicked.connect(
partial(self.expname_action, "open_experiment")) partial(self.expname_action, "open_experiment"))
self.el_buttons.addWidget(self.el, 0, 0, colspan=2) self.el_buttons.addWidget(self.el, 0, 0, colspan=2)
open = QtWidgets.QPushButton("Open") open = QtWidgets.QPushButton("Open")
open.setIcon(QtWidgets.QApplication.style().standardIcon( open.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton)) QtWidgets.QStyle.SP_DialogOpenButton))
open.setToolTip("Open the selected experiment (Return)") open.setToolTip("Open the selected experiment (Return)")
self.el_buttons.addWidget(open, 1, 0) self.el_buttons.addWidget(open, 1, 0)
open.clicked.connect( open.clicked.connect(
@ -203,7 +202,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
submit = QtWidgets.QPushButton("Submit") submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon( submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton)) QtWidgets.QStyle.SP_DialogOkButton))
submit.setToolTip("Schedule the selected experiment (Ctrl+Return)") submit.setToolTip("Schedule the selected experiment (Ctrl+Return)")
self.el_buttons.addWidget(submit, 1, 1) self.el_buttons.addWidget(submit, 1, 1)
submit.clicked.connect( submit.clicked.connect(
@ -212,41 +211,41 @@ class ExplorerDock(QtWidgets.QDockWidget):
self.explist_model = Model(dict()) self.explist_model = Model(dict())
explist_sub.add_setmodel_callback(self.set_model) explist_sub.add_setmodel_callback(self.set_model)
self.el.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu) self.el.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
open_action = QtGui.QAction("Open", self.el) open_action = QtWidgets.QAction("Open", self.el)
open_action.triggered.connect( open_action.triggered.connect(
partial(self.expname_action, "open_experiment")) partial(self.expname_action, "open_experiment"))
open_action.setShortcut("RETURN") open_action.setShortcut("RETURN")
open_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) open_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.el.addAction(open_action) self.el.addAction(open_action)
submit_action = QtGui.QAction("Submit", self.el) submit_action = QtWidgets.QAction("Submit", self.el)
submit_action.triggered.connect( submit_action.triggered.connect(
partial(self.expname_action, "submit")) partial(self.expname_action, "submit"))
submit_action.setShortcut("CTRL+RETURN") submit_action.setShortcut("CTRL+RETURN")
submit_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) submit_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.el.addAction(submit_action) self.el.addAction(submit_action)
reqterm_action = QtGui.QAction("Request termination of instances", self.el) reqterm_action = QtWidgets.QAction("Request termination of instances", self.el)
reqterm_action.triggered.connect( reqterm_action.triggered.connect(
partial(self.expname_action, "request_inst_term")) partial(self.expname_action, "request_inst_term"))
reqterm_action.setShortcut("CTRL+BACKSPACE") reqterm_action.setShortcut("CTRL+BACKSPACE")
reqterm_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) reqterm_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.el.addAction(reqterm_action) self.el.addAction(reqterm_action)
set_shortcut_menu = QtWidgets.QMenu(self.el) set_shortcut_menu = QtWidgets.QMenu()
for i in range(12): for i in range(12):
action = QtGui.QAction("F" + str(i+1), self.el) action = QtWidgets.QAction("F" + str(i + 1), self.el)
action.triggered.connect(partial(self.set_shortcut, i)) action.triggered.connect(partial(self.set_shortcut, i))
set_shortcut_menu.addAction(action) set_shortcut_menu.addAction(action)
set_shortcut_action = QtGui.QAction("Set shortcut", self.el) set_shortcut_action = QtWidgets.QAction("Set shortcut", self.el)
set_shortcut_action.setMenu(set_shortcut_menu) set_shortcut_action.setMenu(set_shortcut_menu)
self.el.addAction(set_shortcut_action) self.el.addAction(set_shortcut_action)
sep = QtGui.QAction(self.el) sep = QtWidgets.QAction(self.el)
sep.setSeparator(True) sep.setSeparator(True)
self.el.addAction(sep) self.el.addAction(sep)
scan_repository_action = QtGui.QAction("Scan repository HEAD", scan_repository_action = QtWidgets.QAction("Scan repository HEAD",
self.el) self.el)
def scan_repository(): def scan_repository():
@ -254,14 +253,15 @@ class ExplorerDock(QtWidgets.QDockWidget):
scan_repository_action.triggered.connect(scan_repository) scan_repository_action.triggered.connect(scan_repository)
self.el.addAction(scan_repository_action) self.el.addAction(scan_repository_action)
scan_ddb_action = QtGui.QAction("Scan device database", self.el) scan_ddb_action = QtWidgets.QAction("Scan device database", self.el)
def scan_ddb(): def scan_ddb():
asyncio.ensure_future(device_db_ctl.scan()) asyncio.ensure_future(device_db_ctl.scan())
scan_ddb_action.triggered.connect(scan_ddb) scan_ddb_action.triggered.connect(scan_ddb)
self.el.addAction(scan_ddb_action) self.el.addAction(scan_ddb_action)
self.current_directory = "" self.current_directory = ""
open_file_action = QtGui.QAction("Open file outside repository", open_file_action = QtWidgets.QAction("Open file outside repository",
self.el) self.el)
open_file_action.triggered.connect( open_file_action.triggered.connect(
lambda: _OpenFileDialog(self, self.exp_manager, lambda: _OpenFileDialog(self, self.exp_manager,

View File

@ -1,7 +1,7 @@
import logging import logging
import asyncio import asyncio
from PyQt6 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
from artiq.gui.models import DictSyncModel from artiq.gui.models import DictSyncModel
from artiq.gui.entries import EntryTreeWidget, procdesc_to_entry from artiq.gui.entries import EntryTreeWidget, procdesc_to_entry
@ -44,11 +44,11 @@ class _InteractiveArgsRequest(EntryTreeWidget):
self.quickStyleClicked.connect(self.supply) self.quickStyleClicked.connect(self.supply)
cancel_btn = QtWidgets.QPushButton("Cancel") cancel_btn = QtWidgets.QPushButton("Cancel")
cancel_btn.setIcon(QtWidgets.QApplication.style().standardIcon( cancel_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton)) QtWidgets.QStyle.SP_DialogCancelButton))
cancel_btn.clicked.connect(self.cancel) cancel_btn.clicked.connect(self.cancel)
supply_btn = QtWidgets.QPushButton("Supply") supply_btn = QtWidgets.QPushButton("Supply")
supply_btn.setIcon(QtWidgets.QApplication.style().standardIcon( supply_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton)) QtWidgets.QStyle.SP_DialogOkButton))
supply_btn.clicked.connect(self.supply) supply_btn.clicked.connect(self.supply)
buttons = LayoutWidget() buttons = LayoutWidget()
buttons.addWidget(cancel_btn, 1, 1) buttons.addWidget(cancel_btn, 1, 1)
@ -78,7 +78,7 @@ class _InteractiveArgsView(QtWidgets.QStackedWidget):
QtWidgets.QStackedWidget.__init__(self) QtWidgets.QStackedWidget.__init__(self)
self.tabs = QtWidgets.QTabWidget() self.tabs = QtWidgets.QTabWidget()
self.default_label = QtWidgets.QLabel("No pending interactive arguments requests.") self.default_label = QtWidgets.QLabel("No pending interactive arguments requests.")
self.default_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.default_label.setAlignment(QtCore.Qt.AlignCenter)
font = QtGui.QFont(self.default_label.font()) font = QtGui.QFont(self.default_label.font())
font.setItalic(True) font.setItalic(True)
self.default_label.setFont(font) self.default_label.setFont(font)
@ -99,12 +99,9 @@ class _InteractiveArgsView(QtWidgets.QStackedWidget):
self._insert_widget(i) self._insert_widget(i)
def _insert_widget(self, row): def _insert_widget(self, row):
rid = self.model.data(self.model.index(row, 0), rid = self.model.data(self.model.index(row, 0), QtCore.Qt.DisplayRole)
QtCore.Qt.ItemDataRole.DisplayRole) title = self.model.data(self.model.index(row, 1), QtCore.Qt.DisplayRole)
title = self.model.data(self.model.index(row, 1), arglist_desc = self.model.data(self.model.index(row, 2), QtCore.Qt.DisplayRole)
QtCore.Qt.ItemDataRole.DisplayRole)
arglist_desc = self.model.data(self.model.index(row, 2),
QtCore.Qt.ItemDataRole.DisplayRole)
inter_args_request = _InteractiveArgsRequest(rid, arglist_desc) inter_args_request = _InteractiveArgsRequest(rid, arglist_desc)
inter_args_request.supplied.connect(self.supplied) inter_args_request.supplied.connect(self.supplied)
inter_args_request.cancelled.connect(self.cancelled) inter_args_request.cancelled.connect(self.cancelled)
@ -129,7 +126,7 @@ class InteractiveArgsDock(QtWidgets.QDockWidget):
QtWidgets.QDockWidget.__init__(self, "Interactive Args") QtWidgets.QDockWidget.__init__(self, "Interactive Args")
self.setObjectName("Interactive Args") self.setObjectName("Interactive Args")
self.setFeatures( self.setFeatures(
self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable)
self.interactive_args_rpc = interactive_args_rpc self.interactive_args_rpc = interactive_args_rpc
self.request_view = _InteractiveArgsView() self.request_view = _InteractiveArgsView()
self.request_view.supplied.connect(self.supply) self.request_view.supplied.connect(self.supply)

View File

@ -4,14 +4,13 @@ import textwrap
from collections import namedtuple from collections import namedtuple
from functools import partial from functools import partial
from PyQt6 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets
from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe
from artiq.coredevice.ad9912_reg import AD9912_SER_CONF from artiq.coredevice.ad9912_reg import AD9912_SER_CONF
from artiq.gui.tools import LayoutWidget, QDockWidgetCloseDetect, DoubleClickLineEdit from artiq.gui.tools import LayoutWidget, QDockWidgetCloseDetect, DoubleClickLineEdit
from artiq.gui.dndwidgets import VDragScrollArea, DragDropFlowLayoutWidget from artiq.gui.dndwidgets import VDragScrollArea, DragDropFlowLayoutWidget
from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.models import DictSyncTreeSepModel
from artiq.tools import elide
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,45 +22,39 @@ class _CancellableLineEdit(QtWidgets.QLineEdit):
def keyPressEvent(self, event): def keyPressEvent(self, event):
key = event.key() key = event.key()
if key == QtCore.Qt.Key.Key_Escape: if key == QtCore.Qt.Key_Escape:
self.esc_cb(event) self.esc_cb(event)
QtWidgets.QLineEdit.keyPressEvent(self, event) QtWidgets.QLineEdit.keyPressEvent(self, event)
class _MoninjWidget(QtWidgets.QFrame): class _TTLWidget(QtWidgets.QFrame):
def __init__(self, title):
QtWidgets.QFrame.__init__(self)
self.setFrameShape(QtWidgets.QFrame.Shape.Box)
self.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.setFixedHeight(100)
self.setFixedWidth(150)
self.grid = QtWidgets.QGridLayout()
self.grid.setContentsMargins(0, 0, 0, 0)
self.grid.setHorizontalSpacing(0)
self.grid.setVerticalSpacing(0)
self.setLayout(self.grid)
title = elide(title, 20)
label = QtWidgets.QLabel(title)
label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
label.setSizePolicy(QtWidgets.QSizePolicy.Policy.Ignored,
QtWidgets.QSizePolicy.Policy.Preferred)
self.grid.addWidget(label, 1, 1)
class _TTLWidget(_MoninjWidget):
def __init__(self, dm, channel, force_out, title): def __init__(self, dm, channel, force_out, title):
_MoninjWidget.__init__(self, title) QtWidgets.QFrame.__init__(self)
self.channel = channel self.channel = channel
self.set_mode = dm.ttl_set_mode self.set_mode = dm.ttl_set_mode
self.force_out = force_out self.force_out = force_out
self.title = title self.title = title
self.setFrameShape(QtWidgets.QFrame.Box)
self.setFrameShadow(QtWidgets.QFrame.Raised)
grid = QtWidgets.QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(0)
self.setLayout(grid)
label = QtWidgets.QLabel(title)
label.setAlignment(QtCore.Qt.AlignCenter)
label.setSizePolicy(QtWidgets.QSizePolicy.Ignored,
QtWidgets.QSizePolicy.Preferred)
grid.addWidget(label, 1, 1)
self.stack = QtWidgets.QStackedWidget() self.stack = QtWidgets.QStackedWidget()
self.grid.addWidget(self.stack, 2, 1) grid.addWidget(self.stack, 2, 1)
self.direction = QtWidgets.QLabel() self.direction = QtWidgets.QLabel()
self.direction.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.direction.setAlignment(QtCore.Qt.AlignCenter)
self.stack.addWidget(self.direction) self.stack.addWidget(self.direction)
grid_cb = LayoutWidget() grid_cb = LayoutWidget()
@ -81,13 +74,13 @@ class _TTLWidget(_MoninjWidget):
self.stack.addWidget(grid_cb) self.stack.addWidget(grid_cb)
self.value = QtWidgets.QLabel() self.value = QtWidgets.QLabel()
self.value.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.value.setAlignment(QtCore.Qt.AlignCenter)
self.grid.addWidget(self.value, 3, 1) grid.addWidget(self.value, 3, 1)
self.grid.setRowStretch(1, 1) grid.setRowStretch(1, 1)
self.grid.setRowStretch(2, 0) grid.setRowStretch(2, 0)
self.grid.setRowStretch(3, 0) grid.setRowStretch(3, 0)
self.grid.setRowStretch(4, 1) grid.setRowStretch(4, 1)
self.programmatic_change = False self.programmatic_change = False
self.override.clicked.connect(self.override_toggled) self.override.clicked.connect(self.override_toggled)
@ -193,7 +186,7 @@ class _DDSModel:
return ftw / self.ftw_per_hz return ftw / self.ftw_per_hz
class _DDSWidget(_MoninjWidget): class _DDSWidget(QtWidgets.QFrame):
def __init__(self, dm, title, bus_channel, channel, def __init__(self, dm, title, bus_channel, channel,
dds_type, ref_clk, cpld=None, pll=1, clk_div=0): dds_type, ref_clk, cpld=None, pll=1, clk_div=0):
self.dm = dm self.dm = dm
@ -204,7 +197,19 @@ class _DDSWidget(_MoninjWidget):
self.dds_model = _DDSModel(dds_type, ref_clk, cpld, pll, clk_div) self.dds_model = _DDSModel(dds_type, ref_clk, cpld, pll, clk_div)
self.title = title self.title = title
_MoninjWidget.__init__(self, title) QtWidgets.QFrame.__init__(self)
self.setFrameShape(QtWidgets.QFrame.Box)
self.setFrameShadow(QtWidgets.QFrame.Raised)
grid = QtWidgets.QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(0)
self.setLayout(grid)
label = QtWidgets.QLabel(title)
label.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(label, 1, 1)
# FREQ DATA/EDIT FIELD # FREQ DATA/EDIT FIELD
self.data_stack = QtWidgets.QStackedWidget() self.data_stack = QtWidgets.QStackedWidget()
@ -216,11 +221,11 @@ class _DDSWidget(_MoninjWidget):
grid_disp.layout.setVerticalSpacing(0) grid_disp.layout.setVerticalSpacing(0)
self.value_label = QtWidgets.QLabel() self.value_label = QtWidgets.QLabel()
self.value_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.value_label.setAlignment(QtCore.Qt.AlignCenter)
grid_disp.addWidget(self.value_label, 0, 1, 1, 2) grid_disp.addWidget(self.value_label, 0, 1, 1, 2)
unit = QtWidgets.QLabel("MHz") unit = QtWidgets.QLabel("MHz")
unit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) unit.setAlignment(QtCore.Qt.AlignCenter)
grid_disp.addWidget(unit, 0, 3, 1, 1) grid_disp.addWidget(unit, 0, 3, 1, 1)
self.data_stack.addWidget(grid_disp) self.data_stack.addWidget(grid_disp)
@ -232,14 +237,14 @@ class _DDSWidget(_MoninjWidget):
grid_edit.layout.setVerticalSpacing(0) grid_edit.layout.setVerticalSpacing(0)
self.value_edit = _CancellableLineEdit(self) self.value_edit = _CancellableLineEdit(self)
self.value_edit.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight) self.value_edit.setAlignment(QtCore.Qt.AlignRight)
grid_edit.addWidget(self.value_edit, 0, 1, 1, 2) grid_edit.addWidget(self.value_edit, 0, 1, 1, 2)
unit = QtWidgets.QLabel("MHz") unit = QtWidgets.QLabel("MHz")
unit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) unit.setAlignment(QtCore.Qt.AlignCenter)
grid_edit.addWidget(unit, 0, 3, 1, 1) grid_edit.addWidget(unit, 0, 3, 1, 1)
self.data_stack.addWidget(grid_edit) self.data_stack.addWidget(grid_edit)
self.grid.addWidget(self.data_stack, 2, 1) grid.addWidget(self.data_stack, 2, 1)
# BUTTONS # BUTTONS
self.button_stack = QtWidgets.QStackedWidget() self.button_stack = QtWidgets.QStackedWidget()
@ -272,11 +277,11 @@ class _DDSWidget(_MoninjWidget):
cancel.setToolTip("Cancel changes") cancel.setToolTip("Cancel changes")
apply_grid.addWidget(cancel, 0, 2, 1, 1) apply_grid.addWidget(cancel, 0, 2, 1, 1)
self.button_stack.addWidget(apply_grid) self.button_stack.addWidget(apply_grid)
self.grid.addWidget(self.button_stack, 3, 1) grid.addWidget(self.button_stack, 3, 1)
self.grid.setRowStretch(1, 1) grid.setRowStretch(1, 1)
self.grid.setRowStretch(2, 1) grid.setRowStretch(2, 1)
self.grid.setRowStretch(3, 1) grid.setRowStretch(3, 1)
set_btn.clicked.connect(self.set_clicked) set_btn.clicked.connect(self.set_clicked)
apply.clicked.connect(self.apply_changes) apply.clicked.connect(self.apply_changes)
@ -327,31 +332,39 @@ class _DDSWidget(_MoninjWidget):
return "dds/{}".format(self.title) return "dds/{}".format(self.title)
class _DACWidget(_MoninjWidget): class _DACWidget(QtWidgets.QFrame):
def __init__(self, dm, spi_channel, channel, title, vref, offset_dacs): def __init__(self, dm, spi_channel, channel, title):
_MoninjWidget.__init__(self, "{}_ch{}".format(title, channel)) QtWidgets.QFrame.__init__(self)
self.spi_channel = spi_channel self.spi_channel = spi_channel
self.channel = channel self.channel = channel
self.cur_value = 0x8000 self.cur_value = 0
self.title = title self.title = title
self.vref = vref
self.offset_dacs = offset_dacs self.setFrameShape(QtWidgets.QFrame.Box)
self.setFrameShadow(QtWidgets.QFrame.Raised)
grid = QtWidgets.QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(0)
self.setLayout(grid)
label = QtWidgets.QLabel("{}_ch{}".format(title, channel))
label.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(label, 1, 1)
self.value = QtWidgets.QLabel() self.value = QtWidgets.QLabel()
self.value.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignTop) self.value.setAlignment(QtCore.Qt.AlignCenter)
self.grid.addWidget(self.value, 2, 1, 6, 1) grid.addWidget(self.value, 2, 1, 6, 1)
self.grid.setRowStretch(1, 1) grid.setRowStretch(1, 1)
self.grid.setRowStretch(2, 1) grid.setRowStretch(2, 0)
grid.setRowStretch(3, 1)
self.refresh_display() self.refresh_display()
def mu_to_voltage(self, code):
return ((code - self.offset_dacs * 0x4) / (1 << 16)) * (4. * self.vref)
def refresh_display(self): def refresh_display(self):
self.value.setText("<font size=\"4\">{:+.3f} V</font>" self.value.setText("<font size=\"4\">{:.3f}</font><font size=\"2\"> %</font>"
.format(self.mu_to_voltage(self.cur_value))) .format(self.cur_value * 100 / 2**16))
def sort_key(self): def sort_key(self):
return (2, self.spi_channel, self.channel) return (2, self.spi_channel, self.channel)
@ -411,17 +424,9 @@ def setup_from_ddb(ddb):
while isinstance(spi_device, str): while isinstance(spi_device, str):
spi_device = ddb[spi_device] spi_device = ddb[spi_device]
spi_channel = spi_device["arguments"]["channel"] spi_channel = spi_device["arguments"]["channel"]
vref = v["arguments"].get("vref", 5.)
offset_dacs = v["arguments"].get("offset_dacs", 8192)
for channel in range(32): for channel in range(32):
widget = _WidgetDesc((k, channel), comment, _DACWidget, widget = _WidgetDesc((k, channel), comment, _DACWidget,
(spi_channel, channel, k, vref, offset_dacs)) (spi_channel, channel, k))
description.add(widget)
elif (v["module"] == "artiq.coredevice.fastino" and v["class"] == "Fastino"):
bus_channel = v["arguments"]["channel"]
for channel in range(0, 32):
widget = _WidgetDesc((k, channel), comment, _DACWidget,
(bus_channel, channel, k, 5, 8192))
description.add(widget) description.add(widget)
elif v["type"] == "controller" and k == "core_moninj": elif v["type"] == "controller" and k == "core_moninj":
mi_addr = v["host"] mi_addr = v["host"]
@ -761,7 +766,7 @@ class Model(DictSyncTreeSepModel):
class _AddChannelDialog(QtWidgets.QDialog): class _AddChannelDialog(QtWidgets.QDialog):
def __init__(self, parent, model): def __init__(self, parent, model):
QtWidgets.QDialog.__init__(self, parent=parent) QtWidgets.QDialog.__init__(self, parent=parent)
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu) self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.setWindowTitle("Add channels") self.setWindowTitle("Add channels")
layout = QtWidgets.QVBoxLayout() layout = QtWidgets.QVBoxLayout()
@ -771,15 +776,14 @@ class _AddChannelDialog(QtWidgets.QDialog):
self._tree_view = QtWidgets.QTreeView() self._tree_view = QtWidgets.QTreeView()
self._tree_view.setHeaderHidden(True) self._tree_view.setHeaderHidden(True)
self._tree_view.setSelectionBehavior( self._tree_view.setSelectionBehavior(
QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems) QtWidgets.QAbstractItemView.SelectItems)
self._tree_view.setSelectionMode( self._tree_view.setSelectionMode(
QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection) QtWidgets.QAbstractItemView.ExtendedSelection)
self._tree_view.setModel(self._model) self._tree_view.setModel(self._model)
layout.addWidget(self._tree_view) layout.addWidget(self._tree_view)
self._button_box = QtWidgets.QDialogButtonBox( self._button_box = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.StandardButton.Ok | \ QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
QtWidgets.QDialogButtonBox.StandardButton.Cancel
) )
self._button_box.setCenterButtons(True) self._button_box.setCenterButtons(True)
self._button_box.accepted.connect(self.add_channels) self._button_box.accepted.connect(self.add_channels)
@ -801,8 +805,8 @@ class _MonInjDock(QDockWidgetCloseDetect):
def __init__(self, name, manager): def __init__(self, name, manager):
QtWidgets.QDockWidget.__init__(self, "MonInj") QtWidgets.QDockWidget.__init__(self, "MonInj")
self.setObjectName(name) self.setObjectName(name)
self.setFeatures(self.DockWidgetFeature.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
self.DockWidgetFeature.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFloatable)
grid = LayoutWidget() grid = LayoutWidget()
self.setWidget(grid) self.setWidget(grid)
self.manager = manager self.manager = manager
@ -811,7 +815,7 @@ class _MonInjDock(QDockWidgetCloseDetect):
newdock = QtWidgets.QToolButton() newdock = QtWidgets.QToolButton()
newdock.setToolTip("Create new moninj dock") newdock.setToolTip("Create new moninj dock")
newdock.setIcon(QtWidgets.QApplication.style().standardIcon( newdock.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileDialogNewFolder)) QtWidgets.QStyle.SP_FileDialogNewFolder))
newdock.clicked.connect(lambda: self.manager.create_new_dock()) newdock.clicked.connect(lambda: self.manager.create_new_dock())
grid.addWidget(newdock, 0, 0) grid.addWidget(newdock, 0, 0)
@ -822,7 +826,7 @@ class _MonInjDock(QDockWidgetCloseDetect):
dialog_btn.setToolTip("Add channels") dialog_btn.setToolTip("Add channels")
dialog_btn.setIcon( dialog_btn.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileDialogListView)) QtWidgets.QStyle.SP_FileDialogListView))
dialog_btn.clicked.connect(self.channel_dialog.open) dialog_btn.clicked.connect(self.channel_dialog.open)
grid.addWidget(dialog_btn, 0, 1) grid.addWidget(dialog_btn, 0, 1)
@ -835,8 +839,7 @@ class _MonInjDock(QDockWidgetCloseDetect):
self.flow = DragDropFlowLayoutWidget() self.flow = DragDropFlowLayoutWidget()
scroll_area.setWidgetResizable(True) scroll_area.setWidgetResizable(True)
scroll_area.setWidget(self.flow) scroll_area.setWidget(self.flow)
self.flow.setContextMenuPolicy( self.flow.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.flow.customContextMenuRequested.connect(self.custom_context_menu) self.flow.customContextMenuRequested.connect(self.custom_context_menu)
def custom_context_menu(self, pos): def custom_context_menu(self, pos):
@ -844,7 +847,7 @@ class _MonInjDock(QDockWidgetCloseDetect):
if index == -1: if index == -1:
return return
menu = QtWidgets.QMenu() menu = QtWidgets.QMenu()
delete_action = QtGui.QAction("Delete widget", menu) delete_action = QtWidgets.QAction("Delete widget", menu)
delete_action.triggered.connect(partial(self.delete_widget, index)) delete_action.triggered.connect(partial(self.delete_widget, index))
menu.addAction(delete_action) menu.addAction(delete_action)
menu.exec_(self.flow.mapToGlobal(pos)) menu.exec_(self.flow.mapToGlobal(pos))
@ -933,7 +936,7 @@ class MonInj:
dock = _MonInjDock(name, self) dock = _MonInjDock(name, self)
self.docks[name] = dock self.docks[name] = dock
if add_to_area: if add_to_area:
self.main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock) self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
dock.setFloating(True) dock.setFloating(True)
dock.sigClosed.connect(partial(self.on_dock_closed, name)) dock.sigClosed.connect(partial(self.on_dock_closed, name))
self.update_closable() self.update_closable()
@ -947,10 +950,10 @@ class MonInj:
dock.deleteLater() dock.deleteLater()
def update_closable(self): def update_closable(self):
flags = (QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable | flags = (QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFloatable)
if len(self.docks) > 1: if len(self.docks) > 1:
flags |= QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetClosable flags |= QtWidgets.QDockWidget.DockWidgetClosable
for dock in self.docks.values(): for dock in self.docks.values():
dock.setFeatures(flags) dock.setFeatures(flags)
@ -970,8 +973,7 @@ class MonInj:
dock = _MonInjDock(name, self) dock = _MonInjDock(name, self)
self.docks[name] = dock self.docks[name] = dock
dock.restore_state(dock_state) dock.restore_state(dock_state)
self.main_window.addDockWidget( self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock)
dock.sigClosed.connect(partial(self.on_dock_closed, name)) dock.sigClosed.connect(partial(self.on_dock_closed, name))
self.update_closable() self.update_closable()

View File

@ -3,7 +3,7 @@ import time
from functools import partial from functools import partial
import logging import logging
from PyQt6 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
from artiq.gui.models import DictSyncModel from artiq.gui.models import DictSyncModel
from artiq.tools import elide from artiq.tools import elide
@ -61,31 +61,31 @@ class ScheduleDock(QtWidgets.QDockWidget):
def __init__(self, schedule_ctl, schedule_sub): def __init__(self, schedule_ctl, schedule_sub):
QtWidgets.QDockWidget.__init__(self, "Schedule") QtWidgets.QDockWidget.__init__(self, "Schedule")
self.setObjectName("Schedule") self.setObjectName("Schedule")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFloatable)
self.schedule_ctl = schedule_ctl self.schedule_ctl = schedule_ctl
self.table = QtWidgets.QTableView() self.table = QtWidgets.QTableView()
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection) self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.table.verticalHeader().setSectionResizeMode( self.table.verticalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.ResizeMode.ResizeToContents) QtWidgets.QHeaderView.ResizeToContents)
self.table.verticalHeader().hide() self.table.verticalHeader().hide()
self.setWidget(self.table) self.setWidget(self.table)
self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu) self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
request_termination_action = QtGui.QAction("Request termination", self.table) request_termination_action = QtWidgets.QAction("Request termination", self.table)
request_termination_action.triggered.connect(partial(self.delete_clicked, True)) request_termination_action.triggered.connect(partial(self.delete_clicked, True))
request_termination_action.setShortcut("DELETE") request_termination_action.setShortcut("DELETE")
request_termination_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) request_termination_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.table.addAction(request_termination_action) self.table.addAction(request_termination_action)
delete_action = QtGui.QAction("Delete", self.table) delete_action = QtWidgets.QAction("Delete", self.table)
delete_action.triggered.connect(partial(self.delete_clicked, False)) delete_action.triggered.connect(partial(self.delete_clicked, False))
delete_action.setShortcut("SHIFT+DELETE") delete_action.setShortcut("SHIFT+DELETE")
delete_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
self.table.addAction(delete_action) self.table.addAction(delete_action)
terminate_pipeline = QtGui.QAction( terminate_pipeline = QtWidgets.QAction(
"Gracefully terminate all in pipeline", self.table) "Gracefully terminate all in pipeline", self.table)
terminate_pipeline.triggered.connect(self.terminate_pipeline_clicked) terminate_pipeline.triggered.connect(self.terminate_pipeline_clicked)
self.table.addAction(terminate_pipeline) self.table.addAction(terminate_pipeline)

View File

@ -1,7 +1,7 @@
import logging import logging
from functools import partial from functools import partial
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtWidgets
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -11,8 +11,8 @@ class ShortcutsDock(QtWidgets.QDockWidget):
def __init__(self, main_window, exp_manager): def __init__(self, main_window, exp_manager):
QtWidgets.QDockWidget.__init__(self, "Shortcuts") QtWidgets.QDockWidget.__init__(self, "Shortcuts")
self.setObjectName("Shortcuts") self.setObjectName("Shortcuts")
self.setFeatures(self.DockWidgetFeature.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
self.DockWidgetFeature.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFloatable)
layout = QtWidgets.QGridLayout() layout = QtWidgets.QGridLayout()
top_widget = QtWidgets.QWidget() top_widget = QtWidgets.QWidget()
@ -36,25 +36,25 @@ class ShortcutsDock(QtWidgets.QDockWidget):
layout.addWidget(QtWidgets.QLabel("F" + str(i + 1)), row, 0) layout.addWidget(QtWidgets.QLabel("F" + str(i + 1)), row, 0)
label = QtWidgets.QLabel() label = QtWidgets.QLabel()
label.setSizePolicy(QtWidgets.QSizePolicy.Policy.Ignored, label.setSizePolicy(QtWidgets.QSizePolicy.Ignored,
QtWidgets.QSizePolicy.Policy.Ignored) QtWidgets.QSizePolicy.Ignored)
layout.addWidget(label, row, 1) layout.addWidget(label, row, 1)
clear = QtWidgets.QToolButton() clear = QtWidgets.QToolButton()
clear.setIcon(QtWidgets.QApplication.style().standardIcon( clear.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogDiscardButton)) QtWidgets.QStyle.SP_DialogDiscardButton))
layout.addWidget(clear, row, 2) layout.addWidget(clear, row, 2)
clear.clicked.connect(partial(self.set_shortcut, i, "")) clear.clicked.connect(partial(self.set_shortcut, i, ""))
open = QtWidgets.QToolButton() open = QtWidgets.QToolButton()
open.setIcon(QtWidgets.QApplication.style().standardIcon( open.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton)) QtWidgets.QStyle.SP_DialogOpenButton))
layout.addWidget(open, row, 3) layout.addWidget(open, row, 3)
open.clicked.connect(partial(self._open_experiment, i)) open.clicked.connect(partial(self._open_experiment, i))
submit = QtWidgets.QPushButton("Submit") submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon( submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton)) QtWidgets.QStyle.SP_DialogOkButton))
layout.addWidget(submit, row, 4) layout.addWidget(submit, row, 4)
submit.clicked.connect(partial(self._activated, i)) submit.clicked.connect(partial(self._activated, i))
@ -68,8 +68,8 @@ class ShortcutsDock(QtWidgets.QDockWidget):
"open": open, "open": open,
"submit": submit "submit": submit
} }
shortcut = QtGui.QShortcut("F" + str(i+1), main_window) shortcut = QtWidgets.QShortcut("F" + str(i + 1), main_window)
shortcut.setContext(QtCore.Qt.ShortcutContext.ApplicationShortcut) shortcut.setContext(QtCore.Qt.ApplicationShortcut)
shortcut.activated.connect(partial(self._activated, i)) shortcut.activated.connect(partial(self._activated, i))
def _activated(self, nr): def _activated(self, nr):

View File

@ -5,7 +5,7 @@ import bisect
import itertools import itertools
import math import math
from PyQt6 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
import pyqtgraph as pg import pyqtgraph as pg
import numpy as np import numpy as np
@ -130,7 +130,7 @@ class _BaseWaveform(pg.PlotWidget):
self.setMinimumHeight(WAVEFORM_MIN_HEIGHT) self.setMinimumHeight(WAVEFORM_MIN_HEIGHT)
self.setMaximumHeight(WAVEFORM_MAX_HEIGHT) self.setMaximumHeight(WAVEFORM_MAX_HEIGHT)
self.setMenuEnabled(False) self.setMenuEnabled(False)
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu) self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.name = name self.name = name
self.width = width self.width = width
@ -200,27 +200,24 @@ class _BaseWaveform(pg.PlotWidget):
self.cursor_y = self.y_data[ind] self.cursor_y = self.y_data[ind]
def mouseMoveEvent(self, e): def mouseMoveEvent(self, e):
if e.buttons() == QtCore.Qt.MouseButton.LeftButton \ if e.buttons() == QtCore.Qt.LeftButton \
and e.modifiers() == QtCore.Qt.KeyboardModifier.ShiftModifier: and e.modifiers() == QtCore.Qt.ShiftModifier:
drag = QtGui.QDrag(self) drag = QtGui.QDrag(self)
mime = QtCore.QMimeData() mime = QtCore.QMimeData()
drag.setMimeData(mime) drag.setMimeData(mime)
pixmapi = QtWidgets.QApplication.style().standardIcon( pixmapi = QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileIcon) QtWidgets.QStyle.SP_FileIcon)
drag.setPixmap(pixmapi.pixmap(32)) drag.setPixmap(pixmapi.pixmap(32))
drag.exec(QtCore.Qt.DropAction.MoveAction) drag.exec_(QtCore.Qt.MoveAction)
else: else:
super().mouseMoveEvent(e) super().mouseMoveEvent(e)
def wheelEvent(self, e): def wheelEvent(self, e):
if e.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier: if e.modifiers() & QtCore.Qt.ControlModifier:
super().wheelEvent(e) super().wheelEvent(e)
else:
e.ignore()
def mouseDoubleClickEvent(self, e): def mouseDoubleClickEvent(self, e):
pos = self.view_box.mapSceneToView(e.position()) pos = self.view_box.mapSceneToView(e.pos())
self.cursorMove.emit(pos.x()) self.cursorMove.emit(pos.x())
@ -396,13 +393,6 @@ class LogWaveform(_BaseWaveform):
self.plot_data_item.setData(x=[], y=[]) self.plot_data_item.setData(x=[], y=[])
# pg.GraphicsView ignores dragEnterEvent but not dragLeaveEvent
# https://github.com/pyqtgraph/pyqtgraph/blob/1e98704eac6b85de9c35371079f561042e88ad68/pyqtgraph/widgets/GraphicsView.py#L388
class _RefAxis(pg.PlotWidget):
def dragLeaveEvent(self, ev):
ev.ignore()
class _WaveformView(QtWidgets.QWidget): class _WaveformView(QtWidgets.QWidget):
cursorMove = QtCore.pyqtSignal(float) cursorMove = QtCore.pyqtSignal(float)
@ -418,7 +408,7 @@ class _WaveformView(QtWidgets.QWidget):
layout.setSpacing(0) layout.setSpacing(0)
self.setLayout(layout) self.setLayout(layout)
self._ref_axis = _RefAxis() self._ref_axis = pg.PlotWidget()
self._ref_axis.hideAxis("bottom") self._ref_axis.hideAxis("bottom")
self._ref_axis.hideAxis("left") self._ref_axis.hideAxis("left")
self._ref_axis.hideButtons() self._ref_axis.hideButtons()
@ -438,9 +428,8 @@ class _WaveformView(QtWidgets.QWidget):
scroll_area = VDragScrollArea(self) scroll_area = VDragScrollArea(self)
scroll_area.setWidgetResizable(True) scroll_area.setWidgetResizable(True)
scroll_area.setContentsMargins(0, 0, 0, 0) scroll_area.setContentsMargins(0, 0, 0, 0)
scroll_area.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) scroll_area.setFrameShape(QtWidgets.QFrame.NoFrame)
scroll_area.setVerticalScrollBarPolicy( scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
layout.addWidget(scroll_area) layout.addWidget(scroll_area)
self._splitter = VDragDropSplitter(parent=scroll_area) self._splitter = VDragDropSplitter(parent=scroll_area)
@ -455,11 +444,10 @@ class _WaveformView(QtWidgets.QWidget):
) )
self.confirm_delete_dialog.setText("Delete all waveforms?") self.confirm_delete_dialog.setText("Delete all waveforms?")
self.confirm_delete_dialog.setStandardButtons( self.confirm_delete_dialog.setStandardButtons(
QtWidgets.QMessageBox.StandardButton.Ok | QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel
QtWidgets.QMessageBox.StandardButton.Cancel
) )
self.confirm_delete_dialog.setDefaultButton( self.confirm_delete_dialog.setDefaultButton(
QtWidgets.QMessageBox.StandardButton.Ok QtWidgets.QMessageBox.Ok
) )
def setModel(self, model): def setModel(self, model):
@ -531,10 +519,10 @@ class _WaveformView(QtWidgets.QWidget):
w.setStoppedX(self._stopped_x) w.setStoppedX(self._stopped_x)
w.cursorMove.connect(self.cursorMove) w.cursorMove.connect(self.cursorMove)
w.onCursorMove(self._cursor_x) w.onCursorMove(self._cursor_x)
action = QtGui.QAction("Delete waveform", w) action = QtWidgets.QAction("Delete waveform", w)
action.triggered.connect(lambda: self._delete_waveform(w)) action.triggered.connect(lambda: self._delete_waveform(w))
w.addAction(action) w.addAction(action)
action = QtGui.QAction("Delete all waveforms", w) action = QtWidgets.QAction("Delete all waveforms", w)
action.triggered.connect(self.confirm_delete_dialog.open) action.triggered.connect(self.confirm_delete_dialog.open)
w.addAction(action) w.addAction(action)
return w return w
@ -560,7 +548,7 @@ class _WaveformModel(QtCore.QAbstractTableModel):
def columnCount(self, parent=QtCore.QModelIndex()): def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.headers) return len(self.headers)
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole): def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid(): if index.isValid():
return self.backing_struct[index.row()][index.column()] return self.backing_struct[index.row()][index.column()]
return None return None
@ -668,7 +656,7 @@ class Model(DictSyncTreeSepModel):
class _AddChannelDialog(QtWidgets.QDialog): class _AddChannelDialog(QtWidgets.QDialog):
def __init__(self, parent, model): def __init__(self, parent, model):
QtWidgets.QDialog.__init__(self, parent=parent) QtWidgets.QDialog.__init__(self, parent=parent)
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu) self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.setWindowTitle("Add channels") self.setWindowTitle("Add channels")
layout = QtWidgets.QVBoxLayout() layout = QtWidgets.QVBoxLayout()
@ -678,14 +666,14 @@ class _AddChannelDialog(QtWidgets.QDialog):
self._tree_view = QtWidgets.QTreeView() self._tree_view = QtWidgets.QTreeView()
self._tree_view.setHeaderHidden(True) self._tree_view.setHeaderHidden(True)
self._tree_view.setSelectionBehavior( self._tree_view.setSelectionBehavior(
QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems) QtWidgets.QAbstractItemView.SelectItems)
self._tree_view.setSelectionMode( self._tree_view.setSelectionMode(
QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection) QtWidgets.QAbstractItemView.ExtendedSelection)
self._tree_view.setModel(self._model) self._tree_view.setModel(self._model)
layout.addWidget(self._tree_view) layout.addWidget(self._tree_view)
self._button_box = QtWidgets.QDialogButtonBox( self._button_box = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
) )
self._button_box.setCenterButtons(True) self._button_box.setCenterButtons(True)
self._button_box.accepted.connect(self.add_channels) self._button_box.accepted.connect(self.add_channels)
@ -708,7 +696,7 @@ class WaveformDock(QtWidgets.QDockWidget):
QtWidgets.QDockWidget.__init__(self, "Waveform") QtWidgets.QDockWidget.__init__(self, "Waveform")
self.setObjectName("Waveform") self.setObjectName("Waveform")
self.setFeatures( self.setFeatures(
self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable)
self._channel_model = Model({}) self._channel_model = Model({})
self._waveform_model = _WaveformModel() self._waveform_model = _WaveformModel()
@ -736,14 +724,14 @@ class WaveformDock(QtWidgets.QDockWidget):
self._menu_btn = QtWidgets.QPushButton() self._menu_btn = QtWidgets.QPushButton()
self._menu_btn.setIcon( self._menu_btn.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileDialogStart)) QtWidgets.QStyle.SP_FileDialogStart))
grid.addWidget(self._menu_btn, 0, 0) grid.addWidget(self._menu_btn, 0, 0)
self._request_dump_btn = QtWidgets.QToolButton() self._request_dump_btn = QtWidgets.QToolButton()
self._request_dump_btn.setToolTip("Fetch analyzer data from device") self._request_dump_btn.setToolTip("Fetch analyzer data from device")
self._request_dump_btn.setIcon( self._request_dump_btn.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_BrowserReload)) QtWidgets.QStyle.SP_BrowserReload))
self._request_dump_btn.clicked.connect( self._request_dump_btn.clicked.connect(
lambda: asyncio.ensure_future(exc_to_warning(self.proxy_client.trigger_proxy_task()))) lambda: asyncio.ensure_future(exc_to_warning(self.proxy_client.trigger_proxy_task())))
grid.addWidget(self._request_dump_btn, 0, 1) grid.addWidget(self._request_dump_btn, 0, 1)
@ -755,7 +743,7 @@ class WaveformDock(QtWidgets.QDockWidget):
self._add_btn.setToolTip("Add channels...") self._add_btn.setToolTip("Add channels...")
self._add_btn.setIcon( self._add_btn.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileDialogListView)) QtWidgets.QStyle.SP_FileDialogListView))
self._add_btn.clicked.connect(self._add_channel_dialog.open) self._add_btn.clicked.connect(self._add_channel_dialog.open)
grid.addWidget(self._add_btn, 0, 2) grid.addWidget(self._add_btn, 0, 2)
@ -775,7 +763,7 @@ class WaveformDock(QtWidgets.QDockWidget):
self._reset_zoom_btn.setToolTip("Reset zoom") self._reset_zoom_btn.setToolTip("Reset zoom")
self._reset_zoom_btn.setIcon( self._reset_zoom_btn.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_TitleBarMaxButton)) QtWidgets.QStyle.SP_TitleBarMaxButton))
self._reset_zoom_btn.clicked.connect(self._waveform_view.resetZoom) self._reset_zoom_btn.clicked.connect(self._waveform_view.resetZoom)
grid.addWidget(self._reset_zoom_btn, 0, 3) grid.addWidget(self._reset_zoom_btn, 0, 3)
@ -785,7 +773,7 @@ class WaveformDock(QtWidgets.QDockWidget):
grid.addWidget(self._cursor_control, 0, 4, colspan=6) grid.addWidget(self._cursor_control, 0, 4, colspan=6)
def _add_async_action(self, label, coro): def _add_async_action(self, label, coro):
action = QtGui.QAction(label, self) action = QtWidgets.QAction(label, self)
action.triggered.connect( action.triggered.connect(
lambda: asyncio.ensure_future(exc_to_warning(coro()))) lambda: asyncio.ensure_future(exc_to_warning(coro())))
self._file_menu.addAction(action) self._file_menu.addAction(action)

View File

@ -1,4 +1,4 @@
from PyQt6 import QtWidgets from PyQt5 import QtWidgets
from artiq.applets.simple import SimpleApplet from artiq.applets.simple import SimpleApplet

View File

@ -329,29 +329,19 @@ extern fn stop_fn(_version: c_int,
} }
// Must be kept in sync with `artiq.compiler.embedding` // Must be kept in sync with `artiq.compiler.embedding`
static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [ static EXCEPTION_ID_LOOKUP: [(&str, u32); 12] = [
("RTIOUnderflow", 0), ("RuntimeError", 0),
("RTIOOverflow", 1), ("RTIOUnderflow", 1),
("RTIODestinationUnreachable", 2), ("RTIOOverflow", 2),
("DMAError", 3), ("RTIODestinationUnreachable", 3),
("I2CError", 4), ("DMAError", 4),
("CacheError", 5), ("I2CError", 5),
("SPIError", 6), ("CacheError", 6),
("SubkernelError", 7), ("SPIError", 7),
("AssertionError", 8), ("ZeroDivisionError", 8),
("AttributeError", 9), ("IndexError", 9),
("IndexError", 10), ("UnwrapNoneError", 10),
("IOError", 11), ("SubkernelError", 11),
("KeyError", 12),
("NotImplementedError", 13),
("OverflowError", 14),
("RuntimeError", 15),
("TimeoutError", 16),
("TypeError", 17),
("ValueError", 18),
("ZeroDivisionError", 19),
("LinAlgError", 20),
("UnwrapNoneError", 21),
]; ];
pub fn get_exception_id(name: &str) -> u32 { pub fn get_exception_id(name: &str) -> u32 {

View File

@ -914,7 +914,7 @@ pub fn thread(io: Io, aux_mutex: &Mutex,
Ok(()) => Ok(()) =>
info!("startup kernel finished"), info!("startup kernel finished"),
Err(Error::KernelNotFound) => Err(Error::KernelNotFound) =>
debug!("no startup kernel found"), info!("no startup kernel found"),
Err(err) => { Err(err) => {
congress.finished_cleanly.set(false); congress.finished_cleanly.set(false);
error!("startup kernel aborted: {}", err); error!("startup kernel aborted: {}", err);
@ -1009,7 +1009,7 @@ pub fn thread(io: Io, aux_mutex: &Mutex,
drtio::clear_buffers(&io, &aux_mutex); drtio::clear_buffers(&io, &aux_mutex);
} }
Err(Error::KernelNotFound) => { Err(Error::KernelNotFound) => {
debug!("no idle kernel found"); info!("no idle kernel found");
while io.relinquish().is_ok() {} while io.relinquish().is_ok() {}
} }
Err(err) => { Err(err) => {

View File

@ -4,7 +4,6 @@ import sys
import argparse import argparse
import os import os
import socket import socket
import asyncio
import ssl import ssl
import io import io
import zipfile import zipfile
@ -42,27 +41,22 @@ def zip_unarchive(data, directory):
class Client: class Client:
def __init__(self, server, port, cafile): def __init__(self, server, port, cafile):
self.server = server
self.port = port
self.ssl_context = ssl.create_default_context(cafile=cafile) self.ssl_context = ssl.create_default_context(cafile=cafile)
self.reader = None self.raw_socket = socket.create_connection((server, port))
self.writer = None self.init_websocket(server)
try:
self.socket = self.ssl_context.wrap_socket(self.raw_socket, server_hostname=server)
except:
self.raw_socket.close()
raise
self.fsocket = self.socket.makefile("rwb")
async def connect(self): def init_websocket(self, server):
self.reader, self.writer = await asyncio.open_connection( self.raw_socket.sendall("GET / HTTP/1.1\r\nHost: {}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n"
host=self.server, .format(server).encode())
port=self.port,
happy_eyeballs_delay=0.25
)
await self.init_websocket()
await self.writer.start_tls(self.ssl_context)
async def init_websocket(self):
self.writer.write("GET / HTTP/1.1\r\nHost: {}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n"
.format(self.server).encode())
crlf_count = 0 crlf_count = 0
while crlf_count < 4: while crlf_count < 4:
char = await self.reader.read(1) char = self.raw_socket.recv(1)
if not char: if not char:
return ValueError("Connection closed during WebSocket initialization") return ValueError("Connection closed during WebSocket initialization")
if char == b"\r" or char == b"\n": if char == b"\r" or char == b"\n":
@ -70,30 +64,30 @@ class Client:
else: else:
crlf_count = 0 crlf_count = 0
async def close(self): def close(self):
if self.writer: self.socket.close()
self.writer.close() self.raw_socket.close()
await self.writer.wait_closed()
async def send_command(self, *command): def send_command(self, *command):
self.writer.write((" ".join(command) + "\n").encode()) self.fsocket.write((" ".join(command) + "\n").encode())
self.fsocket.flush()
async def read_line(self): def read_line(self):
return (await self.reader.readline()).decode("ascii") return self.fsocket.readline().decode("ascii")
async def read_reply(self): def read_reply(self):
return (await self.reader.readline()).decode("ascii").split() return self.fsocket.readline().decode("ascii").split()
async def read_json(self): def read_json(self):
return json.loads((await self.reader.readline()).decode("ascii")) return json.loads(self.fsocket.readline().decode("ascii"))
async def login(self, username, password): def login(self, username, password):
await self.send_command("LOGIN", username, password) self.send_command("LOGIN", username, password)
return await self.read_reply() == ["HELLO"] return self.read_reply() == ["HELLO"]
async def build(self, major_ver, rev, variant, log, experimental_features): def build(self, major_ver, rev, variant, log, experimental_features):
if not variant: if not variant:
variant = await self.get_single_variant(error_msg="User can build more than 1 variant - need to specify") variant = self.get_single_variant(error_msg="User can build more than 1 variant - need to specify")
print("Building variant: {}".format(variant)) print("Building variant: {}".format(variant))
build_args = ( build_args = (
rev, rev,
@ -102,25 +96,25 @@ class Client:
major_ver, major_ver,
*experimental_features, *experimental_features,
) )
await self.send_command("BUILD", *build_args) self.send_command("BUILD", *build_args)
reply = (await self.read_reply())[0] reply = self.read_reply()[0]
if reply != "BUILDING": if reply != "BUILDING":
return reply, None return reply, None
print("Build in progress. This may take 10-15 minutes.") print("Build in progress. This may take 10-15 minutes.")
if log: if log:
line = await self.read_line() line = self.read_line()
while line != "" and line.startswith("LOG"): while line != "" and line.startswith("LOG"):
print(line[4:], end="") print(line[4:], end="")
line = await self.read_line() line = self.read_line()
reply, status = line.split() reply, status = line.split()
else: else:
reply, status = await self.read_reply() reply, status = self.read_reply()
if reply != "DONE": if reply != "DONE":
raise ValueError("Unexpected server reply: expected 'DONE', got '{}'".format(reply)) raise ValueError("Unexpected server reply: expected 'DONE', got '{}'".format(reply))
if status != "done": if status != "done":
return status, None return status, None
print("Build completed. Downloading...") print("Build completed. Downloading...")
reply, length = await self.read_reply() reply, length = self.read_reply()
if reply != "PRODUCT": if reply != "PRODUCT":
raise ValueError("Unexpected server reply: expected 'PRODUCT', got '{}'".format(reply)) raise ValueError("Unexpected server reply: expected 'PRODUCT', got '{}'".format(reply))
length = int(length) length = int(length)
@ -129,25 +123,25 @@ class Client:
total = 0 total = 0
while total != length: while total != length:
chunk_len = min(4096, length-total) chunk_len = min(4096, length-total)
contents += await self.reader.read(chunk_len) contents += self.fsocket.read(chunk_len)
total += chunk_len total += chunk_len
progress_bar.update(chunk_len) progress_bar.update(chunk_len)
print("Download completed.") print("Download completed.")
return "OK", contents return "OK", contents
async def passwd(self, password): def passwd(self, password):
await self.send_command("PASSWD", password) self.send_command("PASSWD", password)
return (await self.read_reply()) == ["OK"] return self.read_reply() == ["OK"]
async def get_variants(self): def get_variants(self):
await self.send_command("GET_VARIANTS") self.send_command("GET_VARIANTS")
reply = (await self.read_reply())[0] reply = self.read_reply()[0]
if reply != "OK": if reply != "OK":
raise ValueError("Unexpected server reply: expected 'OK', got '{}'".format(reply)) raise ValueError("Unexpected server reply: expected 'OK', got '{}'".format(reply))
return await self.read_json() return self.read_json()
async def get_single_variant(self, error_msg): def get_single_variant(self, error_msg):
variants = await self.get_variants() variants = self.get_variants()
if len(variants) != 1: if len(variants) != 1:
print(error_msg) print(error_msg)
table = PrettyTable() table = PrettyTable()
@ -158,15 +152,13 @@ class Client:
sys.exit(1) sys.exit(1)
return variants[0][0] return variants[0][0]
async def get_json(self, variant): def get_json(self, variant):
await self.send_command("GET_JSON", variant) self.send_command("GET_JSON", variant)
reply = await self.read_reply() reply = self.read_reply()
if reply[0] != "OK": if reply[0] != "OK":
return reply[0], None return reply[0], None
length = int(reply[1]) length = int(reply[1])
json_bytes = await self.reader.read(length) json_bytes = self.fsocket.read(length)
if length != len(json_bytes):
raise ValueError(f"Received data length ({len(json_bytes)}) doesn't match expected length ({length})")
return "OK", json_bytes return "OK", json_bytes
@ -194,10 +186,9 @@ def get_argparser():
return parser return parser
async def main_async(): def main():
args = get_argparser().parse_args() args = get_argparser().parse_args()
client = Client(args.server, args.port, args.cert) client = Client(args.server, args.port, args.cert)
await client.connect()
try: try:
if args.action == "build": if args.action == "build":
# do this before user enters password so errors are reported without unnecessary user action # do this before user enters password so errors are reported without unnecessary user action
@ -228,7 +219,7 @@ async def main_async():
password = getpass("Current password: ") password = getpass("Current password: ")
else: else:
password = getpass() password = getpass()
if not await client.login(args.username, password): if not client.login(args.username, password):
print("Login failed") print("Login failed")
sys.exit(1) sys.exit(1)
@ -241,12 +232,12 @@ async def main_async():
print("Passwords do not match") print("Passwords do not match")
password = getpass("New password: ") password = getpass("New password: ")
password_confirm = getpass("New password (again): ") password_confirm = getpass("New password (again): ")
if not await client.passwd(password): if not client.passwd(password):
print("Failed to change password") print("Failed to change password")
sys.exit(1) sys.exit(1)
elif args.action == "build": elif args.action == "build":
# build dir and version variables set up above # build dir and version variables set up above
result, contents = await client.build(major_ver, rev, args.variant, args.log, args.experimental) result, contents = client.build(major_ver, rev, args.variant, args.log, args.experimental)
if result != "OK": if result != "OK":
if result == "UNAUTHORIZED": if result == "UNAUTHORIZED":
print("You are not authorized to build this variant. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.") print("You are not authorized to build this variant. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.")
@ -257,7 +248,7 @@ async def main_async():
sys.exit(1) sys.exit(1)
zip_unarchive(contents, args.directory) zip_unarchive(contents, args.directory)
elif args.action == "get_variants": elif args.action == "get_variants":
variants = await client.get_variants() variants = client.get_variants()
table = PrettyTable() table = PrettyTable()
table.field_names = ["Variant", "Expiry date"] table.field_names = ["Variant", "Expiry date"]
for variant in variants: for variant in variants:
@ -267,8 +258,8 @@ async def main_async():
if args.variant: if args.variant:
variant = args.variant variant = args.variant
else: else:
variant = await client.get_single_variant(error_msg="User can get JSON of more than 1 variant - need to specify") variant = client.get_single_variant(error_msg="User can get JSON of more than 1 variant - need to specify")
result, json_bytes = await client.get_json(variant) result, json_bytes = client.get_json(variant)
if result != "OK": if result != "OK":
if result == "UNAUTHORIZED": if result == "UNAUTHORIZED":
print(f"You are not authorized to get JSON of variant {variant}. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.") print(f"You are not authorized to get JSON of variant {variant}. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.")
@ -284,10 +275,8 @@ async def main_async():
else: else:
raise ValueError raise ValueError
finally: finally:
await client.close() client.close()
def main():
asyncio.run(main_async())
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -7,7 +7,7 @@ import os
import logging import logging
import sys import sys
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from qasync import QEventLoop from qasync import QEventLoop
from sipyco.asyncio_tools import atexit_register_coroutine from sipyco.asyncio_tools import atexit_register_coroutine
@ -68,9 +68,9 @@ class Browser(QtWidgets.QMainWindow):
browse_root, dataset_sub) browse_root, dataset_sub)
smgr.register(self.experiments) smgr.register(self.experiments)
self.experiments.setHorizontalScrollBarPolicy( self.experiments.setHorizontalScrollBarPolicy(
QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded) QtCore.Qt.ScrollBarAsNeeded)
self.experiments.setVerticalScrollBarPolicy( self.experiments.setVerticalScrollBarPolicy(
QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded) QtCore.Qt.ScrollBarAsNeeded)
self.setCentralWidget(self.experiments) self.setCentralWidget(self.experiments)
self.files = files.FilesDock(dataset_sub, browse_root) self.files = files.FilesDock(dataset_sub, browse_root)
@ -91,29 +91,29 @@ class Browser(QtWidgets.QMainWindow):
self.log = log.LogDock(None, "log") self.log = log.LogDock(None, "log")
smgr.register(self.log) smgr.register(self.log)
self.log.setFeatures(self.log.DockWidgetFeature.DockWidgetMovable | self.log.setFeatures(self.log.DockWidgetMovable |
self.log.DockWidgetFeature.DockWidgetFloatable) self.log.DockWidgetFloatable)
self.addDockWidget(QtCore.Qt.DockWidgetArea.LeftDockWidgetArea, self.files) self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.files)
self.addDockWidget(QtCore.Qt.DockWidgetArea.BottomDockWidgetArea, self.applets) self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.applets)
self.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, self.datasets) self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.datasets)
self.addDockWidget(QtCore.Qt.DockWidgetArea.BottomDockWidgetArea, self.log) self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.log)
g = self.menuBar().addMenu("&Experiment") g = self.menuBar().addMenu("&Experiment")
a = QtGui.QAction("&Open", self) a = QtWidgets.QAction("&Open", self)
a.setIcon(QtWidgets.QApplication.style().standardIcon( a.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton)) QtWidgets.QStyle.SP_DialogOpenButton))
a.setShortcuts(QtGui.QKeySequence.StandardKey.Open) a.setShortcuts(QtGui.QKeySequence.Open)
a.setStatusTip("Open an experiment") a.setStatusTip("Open an experiment")
a.triggered.connect(self.experiments.select_experiment) a.triggered.connect(self.experiments.select_experiment)
g.addAction(a) g.addAction(a)
g = self.menuBar().addMenu("&View") g = self.menuBar().addMenu("&View")
a = QtGui.QAction("Cascade", self) a = QtWidgets.QAction("Cascade", self)
a.setStatusTip("Cascade experiment windows") a.setStatusTip("Cascade experiment windows")
a.triggered.connect(self.experiments.cascadeSubWindows) a.triggered.connect(self.experiments.cascadeSubWindows)
g.addAction(a) g.addAction(a)
a = QtGui.QAction("Tile", self) a = QtWidgets.QAction("Tile", self)
a.setStatusTip("Tile experiment windows") a.setStatusTip("Tile experiment windows")
a.triggered.connect(self.experiments.tileSubWindows) a.triggered.connect(self.experiments.tileSubWindows)
g.addAction(a) g.addAction(a)
@ -140,12 +140,7 @@ def main():
args.db_file = os.path.join(get_user_config_dir(), "artiq_browser.pyon") args.db_file = os.path.join(get_user_config_dir(), "artiq_browser.pyon")
widget_log_handler = log.init_log(args, "browser") widget_log_handler = log.init_log(args, "browser")
forced_platform = [] app = QtWidgets.QApplication(["ARTIQ Browser"])
if (QtGui.QGuiApplication.platformName() == "wayland" and
not os.getenv("QT_QPA_PLATFORM")):
# force XCB instead of Wayland due to applets not embedding
forced_platform = ["-platform", "xcb"]
app = QtWidgets.QApplication(["ARTIQ Browser"] + forced_platform)
loop = QEventLoop(app) loop = QEventLoop(app)
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
atexit.register(loop.close) atexit.register(loop.close)

View File

@ -7,7 +7,7 @@ import importlib
import os import os
import logging import logging
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from qasync import QEventLoop from qasync import QEventLoop
from sipyco.pc_rpc import AsyncioClient, Client from sipyco.pc_rpc import AsyncioClient, Client
@ -95,15 +95,14 @@ class MdiArea(QtWidgets.QMdiArea):
self.pixmap = QtGui.QPixmap(os.path.join( self.pixmap = QtGui.QPixmap(os.path.join(
artiq_dir, "gui", "logo_ver.svg")) artiq_dir, "gui", "logo_ver.svg"))
self.setActivationOrder( self.setActivationOrder(self.ActivationHistoryOrder)
QtWidgets.QMdiArea.WindowOrder.ActivationHistoryOrder)
self.tile = QtGui.QShortcut( self.tile = QtWidgets.QShortcut(
QtGui.QKeySequence('Ctrl+Shift+T'), self) QtGui.QKeySequence('Ctrl+Shift+T'), self)
self.tile.activated.connect( self.tile.activated.connect(
lambda: self.tileSubWindows()) lambda: self.tileSubWindows())
self.cascade = QtGui.QShortcut( self.cascade = QtWidgets.QShortcut(
QtGui.QKeySequence('Ctrl+Shift+C'), self) QtGui.QKeySequence('Ctrl+Shift+C'), self)
self.cascade.activated.connect( self.cascade.activated.connect(
lambda: self.cascadeSubWindows()) lambda: self.cascadeSubWindows())
@ -133,12 +132,7 @@ def main():
server=args.server.replace(":", "."), server=args.server.replace(":", "."),
port=args.port_notify)) port=args.port_notify))
forced_platform = [] app = QtWidgets.QApplication(["ARTIQ Dashboard"])
if (QtGui.QGuiApplication.platformName() == "wayland" and
not os.getenv("QT_QPA_PLATFORM")):
# force XCB instead of Wayland due to applets not embedding
forced_platform = ["-platform", "xcb"]
app = QtWidgets.QApplication(["ARTIQ Dashboard"] + forced_platform)
loop = QEventLoop(app) loop = QEventLoop(app)
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
atexit.register(loop.close) atexit.register(loop.close)
@ -192,8 +186,8 @@ def main():
main_window = MainWindow(args.server if server_name is None else server_name) main_window = MainWindow(args.server if server_name is None else server_name)
smgr.register(main_window) smgr.register(main_window)
mdi_area = MdiArea() mdi_area = MdiArea()
mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded) mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded) mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
main_window.setCentralWidget(mdi_area) main_window.setCentralWidget(mdi_area)
# create UI components # create UI components
@ -263,10 +257,10 @@ def main():
d_datasets, d_applets, d_datasets, d_applets,
d_waveform, d_interactive_args d_waveform, d_interactive_args
] ]
main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, right_docks[0]) main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, right_docks[0])
for d1, d2 in zip(right_docks, right_docks[1:]): for d1, d2 in zip(right_docks, right_docks[1:]):
main_window.tabifyDockWidget(d1, d2) main_window.tabifyDockWidget(d1, d2)
main_window.addDockWidget(QtCore.Qt.DockWidgetArea.BottomDockWidgetArea, d_schedule) main_window.addDockWidget(QtCore.Qt.BottomDockWidgetArea, d_schedule)
# load/initialize state # load/initialize state
if os.name == "nt": if os.name == "nt":

View File

@ -27,7 +27,6 @@ class Fastino(Module):
# dac data words # dac data words
dacs = [Signal(16) for i in range(32)] dacs = [Signal(16) for i in range(32)]
self.probes = dacs
header = Record([ header = Record([
("cfg", 4), ("cfg", 4),

View File

@ -9,7 +9,7 @@ from functools import partial
from itertools import count from itertools import count
from types import SimpleNamespace from types import SimpleNamespace
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from sipyco.pipe_ipc import AsyncioParentComm from sipyco.pipe_ipc import AsyncioParentComm
from sipyco.logging_tools import LogParser from sipyco.logging_tools import LogParser
@ -29,7 +29,7 @@ class EntryArea(EntryTreeWidget):
reset_all_button.setToolTip("Reset all to default values") reset_all_button.setToolTip("Reset all to default values")
reset_all_button.setIcon( reset_all_button.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_BrowserReload)) QtWidgets.QStyle.SP_BrowserReload))
reset_all_button.clicked.connect(self.reset_all) reset_all_button.clicked.connect(self.reset_all)
buttons = LayoutWidget() buttons = LayoutWidget()
buttons.layout.setColumnStretch(0, 1) buttons.layout.setColumnStretch(0, 1)
@ -137,7 +137,7 @@ class AppletIPCServer(AsyncioParentComm):
return return
self.write_pyon({"action": "mod", "mod": mod}) self.write_pyon({"action": "mod", "mod": mod})
async def serve(self, embed_cb): async def serve(self, embed_cb, fix_initial_size_cb):
self.dataset_sub.notify_cbs.append(self._on_mod) self.dataset_sub.notify_cbs.append(self._on_mod)
try: try:
while True: while True:
@ -145,11 +145,10 @@ class AppletIPCServer(AsyncioParentComm):
try: try:
action = obj["action"] action = obj["action"]
if action == "embed": if action == "embed":
size = embed_cb(obj["win_id"]) embed_cb(obj["win_id"])
if size is None:
self.write_pyon({"action": "embed_done"}) self.write_pyon({"action": "embed_done"})
else: elif action == "fix_initial_size":
self.write_pyon({"action": "embed_done", "size_h": size.height(), "size_w": size.width()}) fix_initial_size_cb()
elif action == "subscribe": elif action == "subscribe":
self.datasets = obj["datasets"] self.datasets = obj["datasets"]
self.dataset_prefixes = obj["dataset_prefixes"] self.dataset_prefixes = obj["dataset_prefixes"]
@ -177,9 +176,9 @@ class AppletIPCServer(AsyncioParentComm):
finally: finally:
self.dataset_sub.notify_cbs.remove(self._on_mod) self.dataset_sub.notify_cbs.remove(self._on_mod)
def start_server(self, embed_cb, *, loop=None): def start_server(self, embed_cb, fix_initial_size_cb, *, loop=None):
self.server_task = asyncio.ensure_future( self.server_task = asyncio.ensure_future(
self.serve(embed_cb), loop=loop) self.serve(embed_cb, fix_initial_size_cb), loop=loop)
async def stop_server(self): async def stop_server(self):
if hasattr(self, "server_task"): if hasattr(self, "server_task"):
@ -240,7 +239,7 @@ class _AppletDock(QDockWidgetCloseDetect):
asyncio.ensure_future( asyncio.ensure_future(
LogParser(self._get_log_source).stream_task( LogParser(self._get_log_source).stream_task(
self.ipc.process.stderr)) self.ipc.process.stderr))
self.ipc.start_server(self.embed) self.ipc.start_server(self.embed, self.fix_initial_size)
finally: finally:
self.starting_stopping = False self.starting_stopping = False
@ -269,9 +268,11 @@ class _AppletDock(QDockWidgetCloseDetect):
self.embed_widget = QtWidgets.QWidget.createWindowContainer( self.embed_widget = QtWidgets.QWidget.createWindowContainer(
self.embed_window) self.embed_window)
self.setWidget(self.embed_widget) self.setWidget(self.embed_widget)
# return the size after embedding. Applet must resize to that,
# otherwise the applet may not fit within the dock properly. # HACK: This function would not be needed if Qt window embedding
return self.embed_widget.size() # worked correctly.
def fix_initial_size(self):
self.embed_window.resize(self.embed_widget.size())
async def terminate(self, delete_self=True): async def terminate(self, delete_self=True):
if self.starting_stopping: if self.starting_stopping:
@ -387,8 +388,8 @@ class _CompleterDelegate(QtWidgets.QStyledItemDelegate):
completer = QtWidgets.QCompleter() completer = QtWidgets.QCompleter()
completer.splitPath = lambda path: path.replace("/", ".").split(".") completer.splitPath = lambda path: path.replace("/", ".").split(".")
completer.setModelSorting( completer.setModelSorting(
QtWidgets.QCompleter.ModelSorting.CaseSensitivelySortedModel) QtWidgets.QCompleter.CaseSensitivelySortedModel)
completer.setCompletionRole(QtCore.Qt.ItemDataRole.DisplayRole) completer.setCompletionRole(QtCore.Qt.DisplayRole)
if hasattr(self, "model"): if hasattr(self, "model"):
# "TODO: Optimize updates in the source model" # "TODO: Optimize updates in the source model"
# - Qt (qcompleter.cpp), never ceasing to disappoint. # - Qt (qcompleter.cpp), never ceasing to disappoint.
@ -419,8 +420,8 @@ class AppletsDock(QtWidgets.QDockWidget):
""" """
QtWidgets.QDockWidget.__init__(self, "Applets") QtWidgets.QDockWidget.__init__(self, "Applets")
self.setObjectName("Applets") self.setObjectName("Applets")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFloatable)
self.main_window = main_window self.main_window = main_window
self.dataset_sub = dataset_sub self.dataset_sub = dataset_sub
@ -434,18 +435,18 @@ class AppletsDock(QtWidgets.QDockWidget):
self.table = QtWidgets.QTreeWidget() self.table = QtWidgets.QTreeWidget()
self.table.setColumnCount(2) self.table.setColumnCount(2)
self.table.setHeaderLabels(["Name", "Command"]) self.table.setHeaderLabels(["Name", "Command"])
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows) self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection) self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.table.header().setStretchLastSection(True) self.table.header().setStretchLastSection(True)
self.table.header().setSectionResizeMode( self.table.header().setSectionResizeMode(
QtWidgets.QHeaderView.ResizeMode.ResizeToContents) QtWidgets.QHeaderView.ResizeToContents)
self.table.setTextElideMode(QtCore.Qt.TextElideMode.ElideNone) self.table.setTextElideMode(QtCore.Qt.ElideNone)
self.table.setDragEnabled(True) self.table.setDragEnabled(True)
self.table.viewport().setAcceptDrops(True) self.table.viewport().setAcceptDrops(True)
self.table.setDropIndicatorShown(True) self.table.setDropIndicatorShown(True)
self.table.setDragDropMode(QtWidgets.QAbstractItemView.DragDropMode.InternalMove) self.table.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
self.setWidget(self.table) self.setWidget(self.table)
@ -453,44 +454,44 @@ class AppletsDock(QtWidgets.QDockWidget):
self.table.setItemDelegateForColumn(1, completer_delegate) self.table.setItemDelegateForColumn(1, completer_delegate)
dataset_sub.add_setmodel_callback(completer_delegate.set_model) dataset_sub.add_setmodel_callback(completer_delegate.set_model)
self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu) self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
new_action = QtGui.QAction("New applet", self.table) new_action = QtWidgets.QAction("New applet", self.table)
new_action.triggered.connect(partial(self.new_with_parent, self.new)) new_action.triggered.connect(partial(self.new_with_parent, self.new))
self.table.addAction(new_action) self.table.addAction(new_action)
templates_menu = QtWidgets.QMenu(self.table) templates_menu = QtWidgets.QMenu()
for name, template in _templates: for name, template in _templates:
spec = {"ty": "command", "command": template} spec = {"ty": "command", "command": template}
action = QtGui.QAction(name, self.table) action = QtWidgets.QAction(name, self.table)
action.triggered.connect(partial( action.triggered.connect(partial(
self.new_with_parent, self.new, spec=spec)) self.new_with_parent, self.new, spec=spec))
templates_menu.addAction(action) templates_menu.addAction(action)
restart_action = QtGui.QAction("New applet from template", self.table) restart_action = QtWidgets.QAction("New applet from template", self.table)
restart_action.setMenu(templates_menu) restart_action.setMenu(templates_menu)
self.table.addAction(restart_action) self.table.addAction(restart_action)
restart_action = QtGui.QAction("Restart selected applet or group", self.table) restart_action = QtWidgets.QAction("Restart selected applet or group", self.table)
restart_action.setShortcut("CTRL+R") restart_action.setShortcut("CTRL+R")
restart_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) restart_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
restart_action.triggered.connect(self.restart) restart_action.triggered.connect(self.restart)
self.table.addAction(restart_action) self.table.addAction(restart_action)
delete_action = QtGui.QAction("Delete selected applet or group", self.table) delete_action = QtWidgets.QAction("Delete selected applet or group", self.table)
delete_action.setShortcut("DELETE") delete_action.setShortcut("DELETE")
delete_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
delete_action.triggered.connect(self.delete) delete_action.triggered.connect(self.delete)
self.table.addAction(delete_action) self.table.addAction(delete_action)
close_nondocked_action = QtGui.QAction("Close non-docked applets", self.table) close_nondocked_action = QtWidgets.QAction("Close non-docked applets", self.table)
close_nondocked_action.setShortcut("CTRL+ALT+W") close_nondocked_action.setShortcut("CTRL+ALT+W")
close_nondocked_action.setShortcutContext(QtCore.Qt.ShortcutContext.ApplicationShortcut) close_nondocked_action.setShortcutContext(QtCore.Qt.ApplicationShortcut)
close_nondocked_action.triggered.connect(self.close_nondocked) close_nondocked_action.triggered.connect(self.close_nondocked)
self.table.addAction(close_nondocked_action) self.table.addAction(close_nondocked_action)
new_group_action = QtGui.QAction("New group", self.table) new_group_action = QtWidgets.QAction("New group", self.table)
new_group_action.triggered.connect(partial(self.new_with_parent, self.new_group)) new_group_action.triggered.connect(partial(self.new_with_parent, self.new_group))
self.table.addAction(new_group_action) self.table.addAction(new_group_action)
self.table.itemChanged.connect(self.item_changed) self.table.itemChanged.connect(self.item_changed)
# HACK # HACK
self.table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) self.table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.table.itemDoubleClicked.connect(self.open_editor) self.table.itemDoubleClicked.connect(self.open_editor)
def open_editor(self, item, column): def open_editor(self, item, column):
@ -517,7 +518,7 @@ class AppletsDock(QtWidgets.QDockWidget):
del item.applet_code del item.applet_code
elif spec["ty"] == "code": elif spec["ty"] == "code":
item.setIcon(1, QtWidgets.QApplication.style().standardIcon( item.setIcon(1, QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileIcon)) QtWidgets.QStyle.SP_FileIcon))
item.applet_code = spec["code"] item.applet_code = spec["code"]
else: else:
raise ValueError raise ValueError
@ -529,7 +530,7 @@ class AppletsDock(QtWidgets.QDockWidget):
def create(self, item, name, spec): def create(self, item, name, spec):
dock = _AppletDock(self.dataset_sub, self.dataset_ctl, self.expmgr, item.applet_uid, name, spec, self.extra_substitutes) dock = _AppletDock(self.dataset_sub, self.dataset_ctl, self.expmgr, item.applet_uid, name, spec, self.extra_substitutes)
self.main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock) self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
dock.setFloating(True) dock.setFloating(True)
asyncio.ensure_future(dock.start(), loop=self._loop) asyncio.ensure_future(dock.start(), loop=self._loop)
dock.sigClosed.connect(partial(self.on_dock_closed, item, dock)) dock.sigClosed.connect(partial(self.on_dock_closed, item, dock))
@ -546,7 +547,7 @@ class AppletsDock(QtWidgets.QDockWidget):
dock.spec = self.get_spec(item) dock.spec = self.get_spec(item)
if column == 0: if column == 0:
if item.checkState(0) == QtCore.Qt.CheckState.Checked: if item.checkState(0) == QtCore.Qt.Checked:
if item.applet_dock is None: if item.applet_dock is None:
name = item.text(0) name = item.text(0)
spec = self.get_spec(item) spec = self.get_spec(item)
@ -571,7 +572,7 @@ class AppletsDock(QtWidgets.QDockWidget):
def on_dock_closed(self, item, dock): def on_dock_closed(self, item, dock):
item.applet_geometry = dock.saveGeometry() item.applet_geometry = dock.saveGeometry()
asyncio.ensure_future(dock.terminate(), loop=self._loop) asyncio.ensure_future(dock.terminate(), loop=self._loop)
item.setCheckState(0, QtCore.Qt.CheckState.Unchecked) item.setCheckState(0, QtCore.Qt.Unchecked)
def get_untitled(self): def get_untitled(self):
existing_names = set() existing_names = set()
@ -601,18 +602,18 @@ class AppletsDock(QtWidgets.QDockWidget):
name = self.get_untitled() name = self.get_untitled()
item = QtWidgets.QTreeWidgetItem([name, ""]) item = QtWidgets.QTreeWidgetItem([name, ""])
item.ty = "applet" item.ty = "applet"
item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemIsUserCheckable |
QtCore.Qt.ItemFlag.ItemIsEditable | QtCore.Qt.ItemIsEditable |
QtCore.Qt.ItemFlag.ItemIsDragEnabled | QtCore.Qt.ItemIsDragEnabled |
QtCore.Qt.ItemFlag.ItemNeverHasChildren | QtCore.Qt.ItemNeverHasChildren |
QtCore.Qt.ItemFlag.ItemIsEnabled) QtCore.Qt.ItemIsEnabled)
item.setCheckState(0, QtCore.Qt.CheckState.Unchecked) item.setCheckState(0, QtCore.Qt.Unchecked)
item.applet_uid = uid item.applet_uid = uid
item.applet_dock = None item.applet_dock = None
item.applet_geometry = None item.applet_geometry = None
item.setIcon(0, QtWidgets.QApplication.style().standardIcon( item.setIcon(0, QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_ComputerIcon)) QtWidgets.QStyle.SP_ComputerIcon))
self.set_spec(item, spec) self.set_spec(item, spec)
if parent is None: if parent is None:
self.table.addTopLevelItem(item) self.table.addTopLevelItem(item)
@ -625,15 +626,15 @@ class AppletsDock(QtWidgets.QDockWidget):
name = self.get_untitled() name = self.get_untitled()
item = QtWidgets.QTreeWidgetItem([name, attr]) item = QtWidgets.QTreeWidgetItem([name, attr])
item.ty = "group" item.ty = "group"
item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | item.setFlags(QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemFlag.ItemIsEditable | QtCore.Qt.ItemIsEditable |
QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemIsUserCheckable |
QtCore.Qt.ItemFlag.ItemIsAutoTristate | QtCore.Qt.ItemIsTristate |
QtCore.Qt.ItemFlag.ItemIsDragEnabled | QtCore.Qt.ItemIsDragEnabled |
QtCore.Qt.ItemFlag.ItemIsDropEnabled | QtCore.Qt.ItemIsDropEnabled |
QtCore.Qt.ItemFlag.ItemIsEnabled) QtCore.Qt.ItemIsEnabled)
item.setIcon(0, QtWidgets.QApplication.style().standardIcon( item.setIcon(0, QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DirIcon)) QtWidgets.QStyle.SP_DirIcon))
if parent is None: if parent is None:
self.table.addTopLevelItem(item) self.table.addTopLevelItem(item)
else: else:
@ -711,7 +712,7 @@ class AppletsDock(QtWidgets.QDockWidget):
cwi = wi.child(row) cwi = wi.child(row)
if cwi.ty == "applet": if cwi.ty == "applet":
uid = cwi.applet_uid uid = cwi.applet_uid
enabled = cwi.checkState(0) == QtCore.Qt.CheckState.Checked enabled = cwi.checkState(0) == QtCore.Qt.Checked
name = cwi.text(0) name = cwi.text(0)
spec = self.get_spec(cwi) spec = self.get_spec(cwi)
geometry = cwi.applet_geometry geometry = cwi.applet_geometry
@ -743,7 +744,7 @@ class AppletsDock(QtWidgets.QDockWidget):
geometry = QtCore.QByteArray(geometry) geometry = QtCore.QByteArray(geometry)
item.applet_geometry = geometry item.applet_geometry = geometry
if enabled: if enabled:
item.setCheckState(0, QtCore.Qt.CheckState.Checked) item.setCheckState(0, QtCore.Qt.Checked)
elif wis[0] == "group": elif wis[0] == "group":
_, name, attr, expanded, state_child = wis _, name, attr, expanded, state_child = wis
item = self.new_group(name, attr, parent=parent) item = self.new_group(name, attr, parent=parent)
@ -760,11 +761,11 @@ class AppletsDock(QtWidgets.QDockWidget):
for i in range(wi.childCount()): for i in range(wi.childCount()):
cwi = wi.child(i) cwi = wi.child(i)
if cwi.ty == "applet": if cwi.ty == "applet":
if cwi.checkState(0) == QtCore.Qt.CheckState.Checked: if cwi.checkState(0) == QtCore.Qt.Checked:
if cwi.applet_dock is not None: if cwi.applet_dock is not None:
if not cwi.applet_dock.isFloating(): if not cwi.applet_dock.isFloating():
continue continue
cwi.setCheckState(0, QtCore.Qt.CheckState.Unchecked) cwi.setCheckState(0, QtCore.Qt.Unchecked)
elif cwi.ty == "group": elif cwi.ty == "group":
walk(cwi) walk(cwi)
walk(self.table.invisibleRootItem()) walk(self.table.invisibleRootItem())

View File

@ -1,4 +1,4 @@
from PyQt6 import QtCore, QtWidgets, QtGui from PyQt5 import QtCore, QtWidgets, QtGui
from artiq.gui.flowlayout import FlowLayout from artiq.gui.flowlayout import FlowLayout
@ -10,7 +10,7 @@ class VDragDropSplitter(QtWidgets.QSplitter):
QtWidgets.QSplitter.__init__(self, parent=parent) QtWidgets.QSplitter.__init__(self, parent=parent)
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
self.setOrientation(QtCore.Qt.Orientation.Vertical) self.setOrientation(QtCore.Qt.Vertical)
self.setChildrenCollapsible(False) self.setChildrenCollapsible(False)
def resetSizes(self): def resetSizes(self):
@ -24,7 +24,7 @@ class VDragDropSplitter(QtWidgets.QSplitter):
e.accept() e.accept()
def dragMoveEvent(self, e): def dragMoveEvent(self, e):
pos = e.position() pos = e.pos()
src = e.source() src = e.source()
src_i = self.indexOf(src) src_i = self.indexOf(src)
self.setRubberBand(self.height()) self.setRubberBand(self.height())
@ -48,7 +48,7 @@ class VDragDropSplitter(QtWidgets.QSplitter):
def dropEvent(self, e): def dropEvent(self, e):
self.setRubberBand(-1) self.setRubberBand(-1)
pos = e.position() pos = e.pos()
src = e.source() src = e.source()
src_i = self.indexOf(src) src_i = self.indexOf(src)
for n in range(self.count()): for n in range(self.count()):
@ -78,10 +78,10 @@ class VDragScrollArea(QtWidgets.QScrollArea):
self._speed = speed self._speed = speed
def eventFilter(self, obj, e): def eventFilter(self, obj, e):
if e.type() == QtCore.QEvent.Type.DragMove: if e.type() == QtCore.QEvent.DragMove:
val = self.verticalScrollBar().value() val = self.verticalScrollBar().value()
height = self.viewport().height() height = self.viewport().height()
y = e.position().y() y = e.pos().y()
self._direction = 0 self._direction = 0
if y < val + self._margin: if y < val + self._margin:
self._direction = -1 self._direction = -1
@ -89,7 +89,7 @@ class VDragScrollArea(QtWidgets.QScrollArea):
self._direction = 1 self._direction = 1
if not self._timer.isActive(): if not self._timer.isActive():
self._timer.start() self._timer.start()
elif e.type() in (QtCore.QEvent.Type.Drop, QtCore.QEvent.Type.DragLeave): elif e.type() in (QtCore.QEvent.Drop, QtCore.QEvent.DragLeave):
self._timer.stop() self._timer.stop()
return False return False
@ -117,9 +117,9 @@ class DragDropFlowLayoutWidget(QtWidgets.QWidget):
return -1 return -1
def mousePressEvent(self, event): def mousePressEvent(self, event):
if event.buttons() == QtCore.Qt.MouseButton.LeftButton \ if event.buttons() == QtCore.Qt.LeftButton \
and event.modifiers() == QtCore.Qt.KeyboardModifier.ShiftModifier: and event.modifiers() == QtCore.Qt.ShiftModifier:
index = self._get_index(event.position()) index = self._get_index(event.pos())
if index == -1: if index == -1:
return return
drag = QtGui.QDrag(self) drag = QtGui.QDrag(self)
@ -127,7 +127,7 @@ class DragDropFlowLayoutWidget(QtWidgets.QWidget):
mime.setData("index", str(index).encode()) mime.setData("index", str(index).encode())
drag.setMimeData(mime) drag.setMimeData(mime)
pixmapi = QtWidgets.QApplication.style().standardIcon( pixmapi = QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileIcon) QtWidgets.QStyle.SP_FileIcon)
drag.setPixmap(pixmapi.pixmap(32)) drag.setPixmap(pixmapi.pixmap(32))
drag.exec_(QtCore.Qt.MoveAction) drag.exec_(QtCore.Qt.MoveAction)
event.accept() event.accept()
@ -136,7 +136,7 @@ class DragDropFlowLayoutWidget(QtWidgets.QWidget):
event.accept() event.accept()
def dropEvent(self, event): def dropEvent(self, event):
index = self._get_index(event.position()) index = self._get_index(event.pos())
source_layout = event.source() source_layout = event.source()
source_index = int(bytes(event.mimeData().data("index")).decode()) source_index = int(bytes(event.mimeData().data("index")).decode())
if source_layout == self: if source_layout == self:

View File

@ -2,7 +2,7 @@ import logging
from collections import OrderedDict from collections import OrderedDict
from functools import partial from functools import partial
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from artiq.gui.tools import LayoutWidget, disable_scroll_wheel, WheelFilter from artiq.gui.tools import LayoutWidget, disable_scroll_wheel, WheelFilter
from artiq.gui.scanwidget import ScanWidget from artiq.gui.scanwidget import ScanWidget
@ -23,13 +23,13 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
set_resize_mode = self.header().setSectionResizeMode set_resize_mode = self.header().setSectionResizeMode
else: else:
set_resize_mode = self.header().setResizeMode set_resize_mode = self.header().setResizeMode
set_resize_mode(0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents) set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents)
set_resize_mode(1, QtWidgets.QHeaderView.ResizeMode.Stretch) set_resize_mode(1, QtWidgets.QHeaderView.Stretch)
set_resize_mode(2, QtWidgets.QHeaderView.ResizeMode.ResizeToContents) set_resize_mode(2, QtWidgets.QHeaderView.ResizeToContents)
self.header().setVisible(False) self.header().setVisible(False)
self.setSelectionMode(self.SelectionMode.NoSelection) self.setSelectionMode(self.NoSelection)
self.setHorizontalScrollMode(self.ScrollMode.ScrollPerPixel) self.setHorizontalScrollMode(self.ScrollPerPixel)
self.setVerticalScrollMode(self.ScrollMode.ScrollPerPixel) self.setVerticalScrollMode(self.ScrollPerPixel)
self.setStyleSheet("QTreeWidget {background: " + self.setStyleSheet("QTreeWidget {background: " +
self.palette().midlight().color().name() + " ;}") self.palette().midlight().color().name() + " ;}")
@ -82,14 +82,14 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
reset_entry.setToolTip("Reset to default value") reset_entry.setToolTip("Reset to default value")
reset_entry.setIcon( reset_entry.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_BrowserReload)) QtWidgets.QStyle.SP_BrowserReload))
reset_entry.clicked.connect(partial(self.reset_entry, key)) reset_entry.clicked.connect(partial(self.reset_entry, key))
disable_other_scans = QtWidgets.QToolButton() disable_other_scans = QtWidgets.QToolButton()
widgets["disable_other_scans"] = disable_other_scans widgets["disable_other_scans"] = disable_other_scans
disable_other_scans.setIcon( disable_other_scans.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogResetButton)) QtWidgets.QStyle.SP_DialogResetButton))
disable_other_scans.setToolTip("Disable other scans") disable_other_scans.setToolTip("Disable other scans")
disable_other_scans.clicked.connect( disable_other_scans.clicked.connect(
partial(self._disable_other_scans, key)) partial(self._disable_other_scans, key))
@ -109,6 +109,7 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
group = QtWidgets.QTreeWidgetItem([key]) group = QtWidgets.QTreeWidgetItem([key])
for col in range(3): for col in range(3):
group.setBackground(col, self.palette().mid()) group.setBackground(col, self.palette().mid())
group.setForeground(col, self.palette().brightText())
font = group.font(col) font = group.font(col)
font.setBold(True) font.setBold(True)
group.setFont(col, font) group.setFont(col, font)
@ -539,8 +540,7 @@ class _ExplicitScan(LayoutWidget):
float_regexp = r"(([+-]?\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)" float_regexp = r"(([+-]?\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)"
regexp = "(float)?( +float)* *".replace("float", float_regexp) regexp = "(float)?( +float)* *".replace("float", float_regexp)
self.value.setValidator(QtGui.QRegularExpressionValidator( self.value.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp(regexp)))
QtCore.QRegularExpression(regexp)))
self.value.setText(" ".join([str(x) for x in state["sequence"]])) self.value.setText(" ".join([str(x) for x in state["sequence"]]))
def update(text): def update(text):

View File

@ -39,8 +39,9 @@
############################################################################# #############################################################################
from PyQt6.QtCore import QPoint, QRect, QSize, Qt from PyQt5.QtCore import QPoint, QRect, QSize, Qt
from PyQt6.QtWidgets import QLayout, QSizePolicy from PyQt5.QtWidgets import (QApplication, QLayout, QPushButton, QSizePolicy,
QWidget)
class FlowLayout(QLayout): class FlowLayout(QLayout):
@ -112,8 +113,8 @@ class FlowLayout(QLayout):
for item in self.itemList: for item in self.itemList:
wid = item.widget() wid = item.widget()
spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.ControlType.PushButton, QSizePolicy.ControlType.PushButton, Qt.Orientation.Horizontal) spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.ControlType.PushButton, QSizePolicy.ControlType.PushButton, Qt.Orientation.Vertical) spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
nextX = x + item.sizeHint().width() + spaceX nextX = x + item.sizeHint().width() + spaceX
if nextX - spaceX > rect.right() and lineHeight > 0: if nextX - spaceX > rect.right() and lineHeight > 0:
x = rect.x() x = rect.x()

View File

@ -2,7 +2,7 @@ import re
from functools import partial from functools import partial
from typing import List, Tuple from typing import List, Tuple
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtWidgets
from artiq.gui.tools import LayoutWidget from artiq.gui.tools import LayoutWidget
@ -19,7 +19,7 @@ class FuzzySelectWidget(LayoutWidget):
#: Raised when an entry has been selected, giving the label of the user #: Raised when an entry has been selected, giving the label of the user
#: choice and any additional QEvent.modifiers() (e.g. Ctrl key pressed). #: choice and any additional QEvent.modifiers() (e.g. Ctrl key pressed).
finished = QtCore.pyqtSignal(str, QtCore.Qt.KeyboardModifier) finished = QtCore.pyqtSignal(str, int)
def __init__(self, def __init__(self,
choices: List[Tuple[str, int]] = [], choices: List[Tuple[str, int]] = [],
@ -138,16 +138,16 @@ class FuzzySelectWidget(LayoutWidget):
first_action = None first_action = None
last_action = None last_action = None
for choice in filtered_choices: for choice in filtered_choices:
action = QtGui.QAction(choice, self.menu) action = QtWidgets.QAction(choice, self.menu)
action.triggered.connect(partial(self._finish, action, choice)) action.triggered.connect(partial(self._finish, action, choice))
action.modifiers = QtCore.Qt.KeyboardModifier.NoModifier action.modifiers = 0
self.menu.addAction(action) self.menu.addAction(action)
if not first_action: if not first_action:
first_action = action first_action = action
last_action = action last_action = action
if num_omitted > 0: if num_omitted > 0:
action = QtGui.QAction("<{} not shown>".format(num_omitted), action = QtWidgets.QAction("<{} not shown>".format(num_omitted),
self.menu) self.menu)
action.setEnabled(False) action.setEnabled(False)
self.menu.addAction(action) self.menu.addAction(action)
@ -239,9 +239,9 @@ class _FocusEventFilter(QtCore.QObject):
focus_lost = QtCore.pyqtSignal() focus_lost = QtCore.pyqtSignal()
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.Type.FocusIn: if event.type() == QtCore.QEvent.FocusIn:
self.focus_gained.emit() self.focus_gained.emit()
elif event.type() == QtCore.QEvent.Type.FocusOut: elif event.type() == QtCore.QEvent.FocusOut:
self.focus_lost.emit() self.focus_lost.emit()
return False return False
@ -251,8 +251,8 @@ class _EscapeKeyFilter(QtCore.QObject):
escape_pressed = QtCore.pyqtSignal() escape_pressed = QtCore.pyqtSignal()
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.Type.KeyPress: if event.type() == QtCore.QEvent.KeyPress:
if event.key() == QtCore.Qt.Key.Key_Escape: if event.key() == QtCore.Qt.Key_Escape:
self.escape_pressed.emit() self.escape_pressed.emit()
return False return False
@ -266,13 +266,13 @@ class _UpDownKeyFilter(QtCore.QObject):
self.last_item = last_item self.last_item = last_item
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.Type.KeyPress: if event.type() == QtCore.QEvent.KeyPress:
if event.key() == QtCore.Qt.Key.Key_Down: if event.key() == QtCore.Qt.Key_Down:
self.menu.setActiveAction(self.first_item) self.menu.setActiveAction(self.first_item)
self.menu.setFocus() self.menu.setFocus()
return True return True
if event.key() == QtCore.Qt.Key.Key_Up: if event.key() == QtCore.Qt.Key_Up:
self.menu.setActiveAction(self.last_item) self.menu.setActiveAction(self.last_item)
self.menu.setFocus() self.menu.setFocus()
return True return True
@ -286,16 +286,16 @@ class _NonUpDownKeyFilter(QtCore.QObject):
self.target = target self.target = target
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.Type.KeyPress: if event.type() == QtCore.QEvent.KeyPress:
k = event.key() k = event.key()
if k in (QtCore.Qt.Key.Key_Return, QtCore.Qt.Key.Key_Enter): if k in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
action = obj.activeAction() action = obj.activeAction()
if action is not None: if action is not None:
action.modifiers = event.modifiers() action.modifiers = event.modifiers()
return False return False
if (k != QtCore.Qt.Key.Key_Down and k != QtCore.Qt.Key.Key_Up if (k != QtCore.Qt.Key_Down and k != QtCore.Qt.Key_Up
and k != QtCore.Qt.Key.Key_Enter and k != QtCore.Qt.Key_Enter
and k != QtCore.Qt.Key.Key_Return): and k != QtCore.Qt.Key_Return):
QtWidgets.QApplication.sendEvent(self.target, event) QtWidgets.QApplication.sendEvent(self.target, event)
return True return True
return False return False

View File

@ -1,8 +1,9 @@
import logging import logging
import time import time
import re
from functools import partial from functools import partial
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from sipyco.logging_tools import SourceFilter from sipyco.logging_tools import SourceFilter
from artiq.gui.tools import (LayoutWidget, log_level_to_name, from artiq.gui.tools import (LayoutWidget, log_level_to_name,
@ -19,7 +20,7 @@ class _ModelItem:
class _LogFilterProxyModel(QtCore.QSortFilterProxyModel): class _LogFilterProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.setFilterCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive) self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.setRecursiveFilteringEnabled(True) self.setRecursiveFilteringEnabled(True)
self.filter_level = 0 self.filter_level = 0
@ -27,13 +28,13 @@ class _LogFilterProxyModel(QtCore.QSortFilterProxyModel):
source = self.sourceModel() source = self.sourceModel()
index0 = source.index(source_row, 0, source_parent) index0 = source.index(source_row, 0, source_parent)
index1 = source.index(source_row, 1, source_parent) index1 = source.index(source_row, 1, source_parent)
level = source.data(index0, QtCore.Qt.ItemDataRole.UserRole) level = source.data(index0, QtCore.Qt.UserRole)
if level >= self.filter_level: if level >= self.filter_level:
regex = self.filterRegularExpression() regex = self.filterRegExp()
index0_text = source.data(index0, QtCore.Qt.ItemDataRole.DisplayRole) index0_text = source.data(index0, QtCore.Qt.DisplayRole)
msg_text = source.data(index1, QtCore.Qt.ItemDataRole.DisplayRole) msg_text = source.data(index1, QtCore.Qt.DisplayRole)
return (regex.match(index0_text).hasMatch() or regex.match(msg_text).hasMatch()) return (regex.indexIn(index0_text) != -1 or regex.indexIn(msg_text) != -1)
else: else:
return False return False
@ -56,7 +57,7 @@ class _Model(QtCore.QAbstractItemModel):
timer.timeout.connect(self.timer_tick) timer.timeout.connect(self.timer_tick)
timer.start(100) timer.start(100)
self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.SystemFont.FixedFont) self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)
self.white = QtGui.QBrush(QtGui.QColor(255, 255, 255)) self.white = QtGui.QBrush(QtGui.QColor(255, 255, 255))
self.black = QtGui.QBrush(QtGui.QColor(0, 0, 0)) self.black = QtGui.QBrush(QtGui.QColor(0, 0, 0))
@ -65,8 +66,8 @@ class _Model(QtCore.QAbstractItemModel):
self.error_bg = QtGui.QBrush(QtGui.QColor(255, 150, 150)) self.error_bg = QtGui.QBrush(QtGui.QColor(255, 150, 150))
def headerData(self, col, orientation, role): def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Orientation.Horizontal if (orientation == QtCore.Qt.Horizontal
and role == QtCore.Qt.ItemDataRole.DisplayRole): and role == QtCore.Qt.DisplayRole):
return self.headers[col] return self.headers[col]
return None return None
@ -154,9 +155,9 @@ class _Model(QtCore.QAbstractItemModel):
else: else:
msgnum = item.parent.row msgnum = item.parent.row
if role == QtCore.Qt.ItemDataRole.FontRole and index.column() == 1: if role == QtCore.Qt.FontRole and index.column() == 1:
return self.fixed_font return self.fixed_font
elif role == QtCore.Qt.ItemDataRole.BackgroundRole: elif role == QtCore.Qt.BackgroundRole:
level = self.entries[msgnum][0] level = self.entries[msgnum][0]
if level >= logging.ERROR: if level >= logging.ERROR:
return self.error_bg return self.error_bg
@ -164,13 +165,13 @@ class _Model(QtCore.QAbstractItemModel):
return self.warning_bg return self.warning_bg
else: else:
return self.white return self.white
elif role == QtCore.Qt.ItemDataRole.ForegroundRole: elif role == QtCore.Qt.ForegroundRole:
level = self.entries[msgnum][0] level = self.entries[msgnum][0]
if level <= logging.DEBUG: if level <= logging.DEBUG:
return self.debug_fg return self.debug_fg
else: else:
return self.black return self.black
elif role == QtCore.Qt.ItemDataRole.DisplayRole: elif role == QtCore.Qt.DisplayRole:
v = self.entries[msgnum] v = self.entries[msgnum]
column = index.column() column = index.column()
if item.parent is self: if item.parent is self:
@ -183,7 +184,7 @@ class _Model(QtCore.QAbstractItemModel):
return "" return ""
else: else:
return v[3][item.row+1] return v[3][item.row+1]
elif role == QtCore.Qt.ItemDataRole.ToolTipRole: elif role == QtCore.Qt.ToolTipRole:
v = self.entries[msgnum] v = self.entries[msgnum]
if item.parent is self: if item.parent is self:
lineno = 0 lineno = 0
@ -192,7 +193,7 @@ class _Model(QtCore.QAbstractItemModel):
return (log_level_to_name(v[0]) + ", " + return (log_level_to_name(v[0]) + ", " +
time.strftime("%m/%d %H:%M:%S", time.localtime(v[2])) + time.strftime("%m/%d %H:%M:%S", time.localtime(v[2])) +
"\n" + v[3][lineno]) "\n" + v[3][lineno])
elif role == QtCore.Qt.ItemDataRole.UserRole: elif role == QtCore.Qt.UserRole:
return self.entries[msgnum][0] return self.entries[msgnum][0]
@ -217,13 +218,13 @@ class LogDock(QDockWidgetCloseDetect):
scrollbottom = QtWidgets.QToolButton() scrollbottom = QtWidgets.QToolButton()
scrollbottom.setToolTip("Scroll to bottom") scrollbottom.setToolTip("Scroll to bottom")
scrollbottom.setIcon(QtWidgets.QApplication.style().standardIcon( scrollbottom.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_ArrowDown)) QtWidgets.QStyle.SP_ArrowDown))
grid.addWidget(scrollbottom, 0, 3) grid.addWidget(scrollbottom, 0, 3)
scrollbottom.clicked.connect(self.scroll_to_bottom) scrollbottom.clicked.connect(self.scroll_to_bottom)
clear = QtWidgets.QToolButton() clear = QtWidgets.QToolButton()
clear.setIcon(QtWidgets.QApplication.style().standardIcon( clear.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogResetButton)) QtWidgets.QStyle.SP_DialogResetButton))
grid.addWidget(clear, 0, 4) grid.addWidget(clear, 0, 4)
clear.clicked.connect(lambda: self.model.clear()) clear.clicked.connect(lambda: self.model.clear())
@ -231,7 +232,7 @@ class LogDock(QDockWidgetCloseDetect):
newdock = QtWidgets.QToolButton() newdock = QtWidgets.QToolButton()
newdock.setToolTip("Create new log dock") newdock.setToolTip("Create new log dock")
newdock.setIcon(QtWidgets.QApplication.style().standardIcon( newdock.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileDialogNewFolder)) QtWidgets.QStyle.SP_FileDialogNewFolder))
# note the lambda, the default parameter is overriden otherwise # note the lambda, the default parameter is overriden otherwise
newdock.clicked.connect(lambda: manager.create_new_dock()) newdock.clicked.connect(lambda: manager.create_new_dock())
grid.addWidget(newdock, 0, 5) grid.addWidget(newdock, 0, 5)
@ -239,27 +240,27 @@ class LogDock(QDockWidgetCloseDetect):
self.log = QtWidgets.QTreeView() self.log = QtWidgets.QTreeView()
self.log.setHorizontalScrollMode( self.log.setHorizontalScrollMode(
QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel) QtWidgets.QAbstractItemView.ScrollPerPixel)
self.log.setVerticalScrollMode( self.log.setVerticalScrollMode(
QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel) QtWidgets.QAbstractItemView.ScrollPerPixel)
self.log.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.log.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
grid.addWidget(self.log, 1, 0, colspan=6 if manager else 5) grid.addWidget(self.log, 1, 0, colspan=6 if manager else 5)
self.scroll_at_bottom = False self.scroll_at_bottom = False
self.scroll_value = 0 self.scroll_value = 0
self.log.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu) self.log.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
copy_action = QtGui.QAction("Copy entry to clipboard", self.log) copy_action = QtWidgets.QAction("Copy entry to clipboard", self.log)
copy_action.triggered.connect(self.copy_to_clipboard) copy_action.triggered.connect(self.copy_to_clipboard)
self.log.addAction(copy_action) self.log.addAction(copy_action)
clear_action = QtGui.QAction("Clear", self.log) clear_action = QtWidgets.QAction("Clear", self.log)
clear_action.triggered.connect(lambda: self.model.clear()) clear_action.triggered.connect(lambda: self.model.clear())
self.log.addAction(clear_action) self.log.addAction(clear_action)
# If Qt worked correctly, this would be nice to have. Alas, resizeSections # If Qt worked correctly, this would be nice to have. Alas, resizeSections
# is broken when the horizontal scrollbar is enabled. # is broken when the horizontal scrollbar is enabled.
# sizeheader_action = QtGui.QAction("Resize header", self.log) # sizeheader_action = QtWidgets.QAction("Resize header", self.log)
# sizeheader_action.triggered.connect( # sizeheader_action.triggered.connect(
# lambda: self.log.header().resizeSections(QtWidgets.QHeaderView.ResizeMode.ResizeToContents)) # lambda: self.log.header().resizeSections(QtWidgets.QHeaderView.ResizeToContents))
# self.log.addAction(sizeheader_action) # self.log.addAction(sizeheader_action)
cw = QtGui.QFontMetrics(self.font()).averageCharWidth() cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
@ -278,7 +279,7 @@ class LogDock(QDockWidgetCloseDetect):
self.filter_level.currentIndexChanged.connect(self.apply_level_filter) self.filter_level.currentIndexChanged.connect(self.apply_level_filter)
def apply_text_filter(self): def apply_text_filter(self):
self.proxy_model.setFilterRegularExpression(self.filter_freetext.text()) self.proxy_model.setFilterRegExp(self.filter_freetext.text())
def apply_level_filter(self): def apply_level_filter(self):
self.proxy_model.apply_filter_level(self.filter_level.currentText()) self.proxy_model.apply_filter_level(self.filter_level.currentText())
@ -365,7 +366,7 @@ class LogDockManager:
dock = LogDock(self, name) dock = LogDock(self, name)
self.docks[name] = dock self.docks[name] = dock
if add_to_area: if add_to_area:
self.main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock) self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
dock.setFloating(True) dock.setFloating(True)
dock.sigClosed.connect(partial(self.on_dock_closed, name)) dock.sigClosed.connect(partial(self.on_dock_closed, name))
self.update_closable() self.update_closable()
@ -378,8 +379,8 @@ class LogDockManager:
self.update_closable() self.update_closable()
def update_closable(self): def update_closable(self):
flags = (QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable | flags = (QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFloatable)
if len(self.docks) > 1: if len(self.docks) > 1:
flags |= QtWidgets.QDockWidget.DockWidgetClosable flags |= QtWidgets.QDockWidget.DockWidgetClosable
for dock in self.docks.values(): for dock in self.docks.values():
@ -395,7 +396,7 @@ class LogDockManager:
dock = LogDock(self, name) dock = LogDock(self, name)
self.docks[name] = dock self.docks[name] = dock
dock.restore_state(dock_state) dock.restore_state(dock_state)
self.main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock) self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
dock.sigClosed.connect(partial(self.on_dock_closed, name)) dock.sigClosed.connect(partial(self.on_dock_closed, name))
self.update_closable() self.update_closable()

View File

@ -151,8 +151,15 @@
inkscape:connector-curvature="0" /></g><path inkscape:connector-curvature="0" /></g><path
d="M 28.084,368.98 0,429.872 v 1.124 h 14.16 l 4.202,-8.945 H 43.57 l 4.195,8.945 h 14.16 v -1.124 L 33.753,368.98 Z m -5.438,41.259 8.215,-19.134 8.424,19.134 z" d="M 28.084,368.98 0,429.872 v 1.124 h 14.16 l 4.202,-8.945 H 43.57 l 4.195,8.945 h 14.16 v -1.124 L 33.753,368.98 Z m -5.438,41.259 8.215,-19.134 8.424,19.134 z"
id="path493" id="path493"
style="fill:#ffffff;fill-opacity:1" /><path style="fill:#ffffff;fill-opacity:1"
d="m 313.73062,383.49498 10.87999,-18.07999 c 1.49333,-2.18667 1.97333,-4.8 1.97333,-7.36 0,-8.79999 -6.18666,-12.79999 -14.34666,-12.79999 -8.21332,0 -14.18665,4 -14.18665,12.79999 0,7.09333 5.11999,12.53333 13.01332,11.94666 l -7.94666,13.49333 z m -1.49334,-30.07999 c 2.93334,0 4.69333,2.02667 4.69333,4.64 0,2.88 -1.59999,4.69333 -4.69333,4.69333 -2.82666,0 -4.69333,-1.97333 -4.69333,-4.69333 0,-2.61333 1.86667,-4.64 4.69333,-4.64 z" inkscape:connector-curvature="0" /><g
id="text1" id="text3371"
style="font-size:53.3333px;font-family:Intro;-inkscape-font-specification:Intro;fill:#ffffff" style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:expanded;font-size:45px;line-height:125%;font-family:'Novecento sans wide';-inkscape-font-specification:'Novecento sans wide, Semi-Bold Expanded';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"><g
aria-label="9" /></svg> transform="translate(-1.7346398,0.84745763)"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:96px;line-height:25px;font-family:'Droid Sans Thai';-inkscape-font-specification:'Droid Sans Thai, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
id="text861"
aria-label="7"><path
style="font-size:53.3333px;line-height:13.8889px;font-family:Intro;-inkscape-font-specification:Intro;fill:#ffffff"
d="m 325.20597,358.80118 c 5.38667,-5.86667 2.18667,-16.74666 -9.43999,-16.74666 -11.62666,0 -14.87999,10.77333 -9.44,16.69332 -8.90666,6.4 -5.75999,21.91999 9.44,21.91999 15.09332,0 18.23999,-15.41332 9.43999,-21.86665 z m -9.43999,13.81332 c -6.4,0 -6.4,-9.27999 0,-9.27999 6.45333,0 6.45333,9.27999 0,9.27999 z m 0,-17.06665 c -4.64,0 -4.64,-5.97333 0,-5.97333 4.58666,0 4.58666,5.97333 0,5.97333 z"
id="text248"
aria-label="8" /></g>&#10;</g></svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,4 +1,4 @@
from PyQt6 import QtCore from PyQt5 import QtCore
from sipyco.sync_struct import Subscriber, process_mod from sipyco.sync_struct import Subscriber, process_mod
@ -91,15 +91,15 @@ class DictSyncModel(QtCore.QAbstractTableModel):
return len(self.headers) return len(self.headers)
def data(self, index, role): def data(self, index, role):
if not index.isValid() or role != QtCore.Qt.ItemDataRole.DisplayRole: if not index.isValid() or role != QtCore.Qt.DisplayRole:
return None return None
else: else:
k = self.row_to_key[index.row()] k = self.row_to_key[index.row()]
return self.convert(k, self.backing_store[k], index.column()) return self.convert(k, self.backing_store[k], index.column())
def headerData(self, col, orientation, role): def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Orientation.Horizontal and if (orientation == QtCore.Qt.Horizontal and
role == QtCore.Qt.ItemDataRole.DisplayRole): role == QtCore.Qt.DisplayRole):
return self.headers[col] return self.headers[col]
return None return None
@ -170,15 +170,15 @@ class ListSyncModel(QtCore.QAbstractTableModel):
return len(self.headers) return len(self.headers)
def data(self, index, role): def data(self, index, role):
if not index.isValid() or role != QtCore.Qt.ItemDataRole.DisplayRole: if not index.isValid() or role != QtCore.Qt.DisplayRole:
return None return None
else: else:
return self.convert(self.backing_store[index.row()], return self.convert(self.backing_store[index.row()],
index.column()) index.column())
def headerData(self, col, orientation, role): def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Orientation.Horizontal and if (orientation == QtCore.Qt.Horizontal and
role == QtCore.Qt.ItemDataRole.DisplayRole): role == QtCore.Qt.DisplayRole):
return self.headers[col] return self.headers[col]
return None return None
@ -271,8 +271,8 @@ class DictSyncTreeSepModel(QtCore.QAbstractItemModel):
return len(self.headers) return len(self.headers)
def headerData(self, col, orientation, role): def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Orientation.Horizontal and if (orientation == QtCore.Qt.Horizontal and
role == QtCore.Qt.ItemDataRole.DisplayRole): role == QtCore.Qt.DisplayRole):
return self.headers[col] return self.headers[col]
return None return None
@ -394,19 +394,19 @@ class DictSyncTreeSepModel(QtCore.QAbstractItemModel):
return key return key
def data(self, index, role): def data(self, index, role):
if not index.isValid() or (role != QtCore.Qt.ItemDataRole.DisplayRole if not index.isValid() or (role != QtCore.Qt.DisplayRole
and role != QtCore.Qt.ItemDataRole.ToolTipRole): and role != QtCore.Qt.ToolTipRole):
return None return None
else: else:
column = index.column() column = index.column()
if column == 0 and role == QtCore.Qt.ItemDataRole.DisplayRole: if column == 0 and role == QtCore.Qt.DisplayRole:
return index.internalPointer().name return index.internalPointer().name
else: else:
key = self.index_to_key(index) key = self.index_to_key(index)
if key is None: if key is None:
return None return None
else: else:
if role == QtCore.Qt.ItemDataRole.DisplayRole: if role == QtCore.Qt.DisplayRole:
convert = self.convert convert = self.convert
else: else:
convert = self.convert_tooltip convert = self.convert_tooltip

View File

@ -1,6 +1,6 @@
import logging import logging
from PyQt6 import QtGui, QtCore, QtWidgets from PyQt5 import QtGui, QtCore, QtWidgets
import numpy as np import numpy as np
from .ticker import Ticker from .ticker import Ticker
@ -23,15 +23,15 @@ class ScanWidget(QtWidgets.QWidget):
self.ticker = Ticker() self.ticker = Ticker()
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu) self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
action = QtGui.QAction("V&iew range", self) action = QtWidgets.QAction("V&iew range", self)
action.setShortcut(QtGui.QKeySequence("CTRL+i")) action.setShortcut(QtGui.QKeySequence("CTRL+i"))
action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) action.setShortcutContext(QtCore.Qt.WidgetShortcut)
action.triggered.connect(self.viewRange) action.triggered.connect(self.viewRange)
self.addAction(action) self.addAction(action)
action = QtGui.QAction("Sna&p range", self) action = QtWidgets.QAction("Sna&p range", self)
action.setShortcut(QtGui.QKeySequence("CTRL+p")) action.setShortcut(QtGui.QKeySequence("CTRL+p"))
action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut) action.setShortcutContext(QtCore.Qt.WidgetShortcut)
action.triggered.connect(self.snapRange) action.triggered.connect(self.snapRange)
self.addAction(action) self.addAction(action)
@ -143,56 +143,56 @@ class ScanWidget(QtWidgets.QWidget):
if ev.buttons() ^ ev.button(): # buttons changed if ev.buttons() ^ ev.button(): # buttons changed
ev.ignore() ev.ignore()
return return
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ShiftModifier: if ev.modifiers() & QtCore.Qt.ShiftModifier:
self._drag = "select" self._drag = "select"
self.setStart(self._pixelToAxis(ev.position().x())) self.setStart(self._pixelToAxis(ev.x()))
self.setStop(self._start) self.setStop(self._start)
elif ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier: elif ev.modifiers() & QtCore.Qt.ControlModifier:
self._drag = "zoom" self._drag = "zoom"
self._offset = QtCore.QPoint(ev.position().x(), 0) self._offset = QtCore.QPoint(ev.x(), 0)
self._rubber = QtWidgets.QRubberBand( self._rubber = QtWidgets.QRubberBand(
QtWidgets.QRubberBand.Rectangle, self) QtWidgets.QRubberBand.Rectangle, self)
self._rubber.setGeometry(QtCore.QRect( self._rubber.setGeometry(QtCore.QRect(
self._offset, QtCore.QPoint(ev.position().x(), self.height() - 1))) self._offset, QtCore.QPoint(ev.x(), self.height() - 1)))
self._rubber.show() self._rubber.show()
else: else:
qfm = QtGui.QFontMetrics(self.font()) qfm = QtGui.QFontMetrics(self.font())
if ev.position().y() <= 2.5*qfm.lineSpacing(): if ev.y() <= 2.5*qfm.lineSpacing():
self._drag = "axis" self._drag = "axis"
self._offset = ev.position().x() - self._axisView[0] self._offset = ev.x() - self._axisView[0]
# testing should match inverse drawing order for start/stop # testing should match inverse drawing order for start/stop
elif abs(self._axisToPixel(self._stop) - elif abs(self._axisToPixel(self._stop) -
ev.position().x()) < qfm.lineSpacing()/2: ev.x()) < qfm.lineSpacing()/2:
self._drag = "stop" self._drag = "stop"
self._offset = ev.position().x() - self._axisToPixel(self._stop) self._offset = ev.x() - self._axisToPixel(self._stop)
elif abs(self._axisToPixel(self._start) - elif abs(self._axisToPixel(self._start) -
ev.position().x()) < qfm.lineSpacing()/2: ev.x()) < qfm.lineSpacing()/2:
self._drag = "start" self._drag = "start"
self._offset = ev.position().x() - self._axisToPixel(self._start) self._offset = ev.x() - self._axisToPixel(self._start)
else: else:
self._drag = "both" self._drag = "both"
self._offset = (ev.position().x() - self._axisToPixel(self._start), self._offset = (ev.x() - self._axisToPixel(self._start),
ev.position().x() - self._axisToPixel(self._stop)) ev.x() - self._axisToPixel(self._stop))
def mouseMoveEvent(self, ev): def mouseMoveEvent(self, ev):
if not self._drag: if not self._drag:
ev.ignore() ev.ignore()
return return
if self._drag == "select": if self._drag == "select":
self.setStop(self._pixelToAxis(ev.position().x())) self.setStop(self._pixelToAxis(ev.x()))
elif self._drag == "zoom": elif self._drag == "zoom":
self._rubber.setGeometry(QtCore.QRect( self._rubber.setGeometry(QtCore.QRect(
self._offset, QtCore.QPoint(ev.position().x(), self.height() - 1) self._offset, QtCore.QPoint(ev.x(), self.height() - 1)
).normalized()) ).normalized())
elif self._drag == "axis": elif self._drag == "axis":
self._setView(ev.position().x() - self._offset, self._axisView[1]) self._setView(ev.x() - self._offset, self._axisView[1])
elif self._drag == "start": elif self._drag == "start":
self.setStart(self._pixelToAxis(ev.position().x() - self._offset)) self.setStart(self._pixelToAxis(ev.x() - self._offset))
elif self._drag == "stop": elif self._drag == "stop":
self.setStop(self._pixelToAxis(ev.position().x() - self._offset)) self.setStop(self._pixelToAxis(ev.x() - self._offset))
elif self._drag == "both": elif self._drag == "both":
self.setStart(self._pixelToAxis(ev.position().x() - self._offset[0])) self.setStart(self._pixelToAxis(ev.x() - self._offset[0]))
self.setStop(self._pixelToAxis(ev.position().x() - self._offset[1])) self.setStop(self._pixelToAxis(ev.x() - self._offset[1]))
def mouseReleaseEvent(self, ev): def mouseReleaseEvent(self, ev):
if self._drag == "zoom": if self._drag == "zoom":
@ -217,10 +217,10 @@ class ScanWidget(QtWidgets.QWidget):
y = round(ev.angleDelta().y()/120.) y = round(ev.angleDelta().y()/120.)
if not y: if not y:
return return
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ShiftModifier: if ev.modifiers() & QtCore.Qt.ShiftModifier:
self.setNum(max(1, self._num + y)) self.setNum(max(1, self._num + y))
else: else:
self._zoom(self.zoomFactor**y, ev.position().x()) self._zoom(self.zoomFactor**y, ev.x())
def resizeEvent(self, ev): def resizeEvent(self, ev):
if not ev.oldSize().isValid() or not ev.oldSize().width(): if not ev.oldSize().isValid() or not ev.oldSize().width():
@ -245,8 +245,8 @@ class ScanWidget(QtWidgets.QWidget):
ticks, prefix, labels = self.ticker(self._pixelToAxis(0), ticks, prefix, labels = self.ticker(self._pixelToAxis(0),
self._pixelToAxis(self.width())) self._pixelToAxis(self.width()))
rect = QtCore.QRect(0, 0, self.width(), lineSpacing) rect = QtCore.QRect(0, 0, self.width(), lineSpacing)
painter.drawText(rect, QtCore.Qt.AlignmentFlag.AlignLeft, prefix) painter.drawText(rect, QtCore.Qt.AlignLeft, prefix)
painter.drawText(rect, QtCore.Qt.AlignmentFlag.AlignRight, self.suffix) painter.drawText(rect, QtCore.Qt.AlignRight, self.suffix)
painter.translate(0, lineSpacing + ascent) painter.translate(0, lineSpacing + ascent)
@ -264,7 +264,7 @@ class ScanWidget(QtWidgets.QWidget):
painter.drawLine(int(p), 0, int(p), int(lineSpacing/2)) painter.drawLine(int(p), 0, int(p), int(lineSpacing/2))
painter.translate(0, int(lineSpacing/2)) painter.translate(0, int(lineSpacing/2))
for x, c in (self._start, QtCore.Qt.GlobalColor.blue), (self._stop, QtCore.Qt.GlobalColor.red): for x, c in (self._start, QtCore.Qt.blue), (self._stop, QtCore.Qt.red):
x = self._axisToPixel(x) x = self._axisToPixel(x)
painter.setPen(c) painter.setPen(c)
painter.setBrush(c) painter.setBrush(c)

View File

@ -1,6 +1,6 @@
import re import re
from math import inf, copysign from math import inf, copysign
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
_float_acceptable = re.compile( _float_acceptable = re.compile(
@ -16,8 +16,8 @@ class ScientificSpinBox(QtWidgets.QDoubleSpinBox):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.setGroupSeparatorShown(False) self.setGroupSeparatorShown(False)
self.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhNone) self.setInputMethodHints(QtCore.Qt.ImhNone)
self.setCorrectionMode(self.CorrectionMode.CorrectToPreviousValue) self.setCorrectionMode(self.CorrectToPreviousValue)
# singleStep: resolution for step, buttons, accelerators # singleStep: resolution for step, buttons, accelerators
# decimals: absolute rounding granularity # decimals: absolute rounding granularity
# sigFigs: number of significant digits shown # sigFigs: number of significant digits shown
@ -69,11 +69,11 @@ class ScientificSpinBox(QtWidgets.QDoubleSpinBox):
clean = clean.rsplit(self.suffix(), 1)[0] clean = clean.rsplit(self.suffix(), 1)[0]
try: try:
float(clean) # faster than matching float(clean) # faster than matching
return QtGui.QValidator.State.Acceptable, text, pos return QtGui.QValidator.Acceptable, text, pos
except ValueError: except ValueError:
if re.fullmatch(_float_intermediate, clean): if re.fullmatch(_float_intermediate, clean):
return QtGui.QValidator.State.Intermediate, text, pos return QtGui.QValidator.Intermediate, text, pos
return QtGui.QValidator.State.Invalid, text, pos return QtGui.QValidator.Invalid, text, pos
def stepBy(self, s): def stepBy(self, s):
if abs(s) < 10: # unaccelerated buttons, keys, wheel/trackpad if abs(s) < 10: # unaccelerated buttons, keys, wheel/trackpad

View File

@ -1,7 +1,6 @@
import asyncio import asyncio
from collections import OrderedDict from collections import OrderedDict
import logging import logging
import shutil
from sipyco.asyncio_tools import TaskObject from sipyco.asyncio_tools import TaskObject
from sipyco import pyon from sipyco import pyon
@ -46,8 +45,6 @@ class StateManager(TaskObject):
logger.info("State database '%s' not found, using defaults", logger.info("State database '%s' not found, using defaults",
self.filename) self.filename)
return return
else:
shutil.copy2(self.filename, self.filename + ".backup")
# The state of one object may depend on the state of another, # The state of one object may depend on the state of another,
# e.g. the display state may create docks that are referenced in # e.g. the display state may create docks that are referenced in
# the area state. # the area state.

View File

@ -1,7 +1,7 @@
import asyncio import asyncio
import logging import logging
from PyQt6 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
class DoubleClickLineEdit(QtWidgets.QLineEdit): class DoubleClickLineEdit(QtWidgets.QLineEdit):
@ -56,9 +56,9 @@ class WheelFilter(QtCore.QObject):
self.ignore_with_modifier = ignore_with_modifier self.ignore_with_modifier = ignore_with_modifier
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if event.type() != QtCore.QEvent.Type.Wheel: if event.type() != QtCore.QEvent.Wheel:
return False return False
has_modifier = event.modifiers() != QtCore.Qt.KeyboardModifier.NoModifier has_modifier = event.modifiers() != QtCore.Qt.NoModifier
if has_modifier == self.ignore_with_modifier: if has_modifier == self.ignore_with_modifier:
event.ignore() event.ignore()
return True return True
@ -66,7 +66,7 @@ class WheelFilter(QtCore.QObject):
def disable_scroll_wheel(widget): def disable_scroll_wheel(widget):
widget.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus) widget.setFocusPolicy(QtCore.Qt.StrongFocus)
widget.installEventFilter(WheelFilter(widget)) widget.installEventFilter(WheelFilter(widget))
@ -91,8 +91,8 @@ class LayoutWidget(QtWidgets.QWidget):
async def get_open_file_name(parent, caption, dir, filter): async def get_open_file_name(parent, caption, dir, filter):
"""like QtWidgets.QFileDialog.getOpenFileName(), but a coroutine""" """like QtWidgets.QFileDialog.getOpenFileName(), but a coroutine"""
dialog = QtWidgets.QFileDialog(parent, caption, dir, filter) dialog = QtWidgets.QFileDialog(parent, caption, dir, filter)
dialog.setFileMode(dialog.FileMode.ExistingFile) dialog.setFileMode(dialog.ExistingFile)
dialog.setAcceptMode(dialog.AcceptMode.AcceptOpen) dialog.setAcceptMode(dialog.AcceptOpen)
fut = asyncio.Future() fut = asyncio.Future()
def on_accept(): def on_accept():
@ -119,3 +119,20 @@ async def get_save_file_name(parent, caption, dir, filter, suffix=None):
dialog.open() dialog.open()
return await fut return await fut
# Based on:
# http://stackoverflow.com/questions/250890/using-qsortfilterproxymodel-with-a-tree-model
class QRecursiveFilterProxyModel(QtCore.QSortFilterProxyModel):
def filterAcceptsRow(self, source_row, source_parent):
regexp = self.filterRegExp()
if not regexp.isEmpty():
source_index = self.sourceModel().index(
source_row, self.filterKeyColumn(), source_parent)
if source_index.isValid():
for i in range(self.sourceModel().rowCount(source_index)):
if self.filterAcceptsRow(i, source_index):
return True
key = self.sourceModel().data(source_index, self.filterRole())
return regexp.indexIn(key) != -1
return QtCore.QSortFilterProxyModel.filterAcceptsRow(
self, source_row, source_parent)

View File

@ -27,9 +27,9 @@ SOFTWARE.
import math import math
from PyQt6.QtCore import * from PyQt5.QtCore import *
from PyQt6.QtGui import * from PyQt5.QtGui import *
from PyQt6.QtWidgets import * from PyQt5.QtWidgets import *
class QtWaitingSpinner(QWidget): class QtWaitingSpinner(QWidget):
@ -37,7 +37,7 @@ class QtWaitingSpinner(QWidget):
super().__init__() super().__init__()
# WAS IN initialize() # WAS IN initialize()
self._color = Qt.GlobalColor.black self._color = QColor(Qt.black)
self._roundness = 100.0 self._roundness = 100.0
self._minimumTrailOpacity = 3.14159265358979323846 self._minimumTrailOpacity = 3.14159265358979323846
self._trailFadePercentage = 80.0 self._trailFadePercentage = 80.0
@ -54,17 +54,17 @@ class QtWaitingSpinner(QWidget):
self.updateTimer() self.updateTimer()
# END initialize() # END initialize()
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.setAttribute(Qt.WA_TranslucentBackground)
def paintEvent(self, QPaintEvent): def paintEvent(self, QPaintEvent):
painter = QPainter(self) painter = QPainter(self)
painter.fillRect(self.rect(), Qt.GlobalColor.transparent) painter.fillRect(self.rect(), Qt.transparent)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) painter.setRenderHint(QPainter.Antialiasing, True)
if self._currentCounter >= self._numberOfLines: if self._currentCounter >= self._numberOfLines:
self._currentCounter = 0 self._currentCounter = 0
painter.setPen(Qt.PenStyle.NoPen) painter.setPen(Qt.NoPen)
for i in range(0, self._numberOfLines): for i in range(0, self._numberOfLines):
painter.save() painter.save()
painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength) painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength)
@ -76,7 +76,7 @@ class QtWaitingSpinner(QWidget):
self._minimumTrailOpacity, self._color) self._minimumTrailOpacity, self._color)
painter.setBrush(color) painter.setBrush(color)
painter.drawRoundedRect(QRect(0, int(-self._lineWidth / 2), self._lineLength, self._lineWidth), self._roundness, painter.drawRoundedRect(QRect(0, int(-self._lineWidth / 2), self._lineLength, self._lineWidth), self._roundness,
self._roundness, Qt.SizeMode.RelativeSize) self._roundness, Qt.RelativeSize)
painter.restore() painter.restore()
def start(self): def start(self):
@ -136,7 +136,7 @@ class QtWaitingSpinner(QWidget):
def setRoundness(self, roundness): def setRoundness(self, roundness):
self._roundness = max(0.0, min(100.0, roundness)) self._roundness = max(0.0, min(100.0, roundness))
def setColor(self, color=Qt.GlobalColor.black): def setColor(self, color=Qt.black):
self._color = QColor(color) self._color = QColor(color)
def setRevolutionsPerSecond(self, revolutionsPerSecond): def setRevolutionsPerSecond(self, revolutionsPerSecond):

View File

@ -206,9 +206,10 @@ class GitBackend:
a git hash a git hash
""" """
commit, _ = self.git.resolve_refish(rev) commit, _ = self.git.resolve_refish(rev)
commit_id = str(commit.id)
logger.debug('Resolved git ref "%s" into "%s"', rev, commit_id) logger.debug('Resolved git ref "%s" into "%s"', rev, commit.hex)
return commit_id
return commit.hex
def request_rev(self, rev): def request_rev(self, rev):
rev = self._get_pinned_rev(rev) rev = self._get_pinned_rev(rev)

View File

@ -8,7 +8,7 @@ def catch(f):
except Exception as e: except Exception as e:
print(e) print(e)
# CHECK-L: 19(0, 0, 0) # CHECK-L: 8(0, 0, 0)
catch(lambda: 1/0) catch(lambda: 1/0)
# CHECK-L: 10(10, 1, 0) # CHECK-L: 9(10, 1, 0)
catch(lambda: [1.0][10]) catch(lambda: [1.0][10])

View File

@ -10,7 +10,7 @@ def catch(f):
except IndexError as ie: except IndexError as ie:
print(ie) print(ie)
# CHECK-L: 19(0, 0, 0) # CHECK-L: 8(0, 0, 0)
catch(lambda: 1/0) catch(lambda: 1/0)
# CHECK-L: 10(10, 1, 0) # CHECK-L: 9(10, 1, 0)
catch(lambda: [1.0][10]) catch(lambda: [1.0][10])

View File

@ -3,7 +3,7 @@
# REQUIRES: exceptions # REQUIRES: exceptions
def f(): def f():
# CHECK-L: Uncaught 19 # CHECK-L: Uncaught 8
# CHECK-L: at input.py:${LINE:+1}: # CHECK-L: at input.py:${LINE:+1}:
1/0 1/0

View File

@ -9,7 +9,7 @@ def g():
try: try:
f() f()
except Exception as e: except Exception as e:
# CHECK-L: Uncaught 19 # CHECK-L: Uncaught 8
# CHECK-L: at input.py:${LINE:+1}: # CHECK-L: at input.py:${LINE:+1}:
raise e raise e

View File

@ -2,6 +2,6 @@
# RUN: OutputCheck %s --file-to-check=%t # RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions # REQUIRES: exceptions
# CHECK-L: Uncaught 19: cannot divide by zero (0, 0, 0) # CHECK-L: Uncaught 8: cannot divide by zero (0, 0, 0)
# CHECK-L: at input.py:${LINE:+1}: # CHECK-L: at input.py:${LINE:+1}:
1/0 1/0

View File

@ -44,8 +44,7 @@ class TestClient(unittest.TestCase):
self.device_db_path, *args], encoding="utf8", env=get_env(), self.device_db_path, *args], encoding="utf8", env=get_env(),
text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while self.master.stdout.readline().strip() != "ARTIQ master is now ready.": while self.master.stdout.readline().strip() != "ARTIQ master is now ready.":
if self.master.poll() is not None: pass
raise IOError("master terminated unexpectedly")
def check_and_terminate_master(self): def check_and_terminate_master(self):
while not ("test content" in self.master.stdout.readline()): while not ("test content" in self.master.stdout.readline()):

View File

@ -3,6 +3,7 @@ import importlib.util
import importlib.machinery import importlib.machinery
import inspect import inspect
import logging import logging
import os
import pathlib import pathlib
import string import string
import sys import sys
@ -10,9 +11,9 @@ import sys
import numpy as np import numpy as np
from sipyco import pyon from sipyco import pyon
from platformdirs import user_config_dir
from artiq import __version__ as artiq_version from artiq import __version__ as artiq_version
from artiq.appdirs import user_config_dir
from artiq.language.environment import is_public_experiment from artiq.language.environment import is_public_experiment
from artiq.language import units from artiq.language import units
@ -194,4 +195,6 @@ def get_windows_drives():
def get_user_config_dir(): def get_user_config_dir():
major = artiq_version.split(".")[0] major = artiq_version.split(".")[0]
return user_config_dir("artiq", "m-labs", major, ensure_exists=True) dir = user_config_dir("artiq", "m-labs", major)
os.makedirs(dir, exist_ok=True)
return dir

View File

@ -34,7 +34,7 @@ mock_modules = ["artiq.gui.waitingspinnerwidget",
"artiq.compiler.embedding", "artiq.compiler.embedding",
"artiq.dashboard.waveform", "artiq.dashboard.waveform",
"artiq.coredevice.jsondesc", "artiq.coredevice.jsondesc",
"qasync", "lmdb", "dateutil.parser", "prettytable", "PyQt6", "qasync", "lmdb", "dateutil.parser", "prettytable", "PyQt5",
"h5py", "llvmlite", "pythonparser", "tqdm", "jsonschema"] "h5py", "llvmlite", "pythonparser", "tqdm", "jsonschema"]
for module in mock_modules: for module in mock_modules:
@ -70,8 +70,7 @@ extensions = [
'sphinx.ext.napoleon', 'sphinx.ext.napoleon',
'sphinxarg.ext', 'sphinxarg.ext',
'sphinxcontrib.wavedrom', # see also below for config 'sphinxcontrib.wavedrom', # see also below for config
'sphinxcontrib.jquery', "sphinxcontrib.jquery",
'sphinxcontrib.tikz' # see also below for config
] ]
mathjax_path = "https://m-labs.hk/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML.js" mathjax_path = "https://m-labs.hk/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML.js"
@ -327,10 +326,3 @@ texinfo_documents = [
offline_skin_js_path = '_static/default.js' offline_skin_js_path = '_static/default.js'
offline_wavedrom_js_path = '_static/WaveDrom.js' offline_wavedrom_js_path = '_static/WaveDrom.js'
render_using_wavedrompy = True render_using_wavedrompy = True
# -- Options for sphinxcontrib-tikz ---------------------------------------
# tikz_proc_suite = pdf2svg
# tikz_transparent = True
# these are the defaults
tikz_tikzlibraries = 'positioning, shapes, arrows.meta'

View File

@ -1,272 +0,0 @@
Extending RTIO
==============
.. warning::
This page is for users who want to extend or modify ARTIQ RTIO. Broadly speaking, one of the core intentions of ARTIQ is to provide a high-level, easy-to-use interface for experimentation, while the infrastructure handles the technological challenges of the high-resolution, timing-critical operations required. Rather than worrying about the details of timing in hardware, users can outline tasks quickly and efficiently in ARTIQ Python, and trust the system to carry out those tasks in real time. It is not normally, or indeed ever, necessary to make modifications on a gateware level.
However, ARTIQ is an open-source project, and welcomes interest and agency from its users, as well as from experienced developers. This page is intended to serve firstly as a broad introduction to the internal structure of ARTIQ, and secondly as a tutorial for how RTIO extensions in ARTIQ can be made. Experience with FPGAs or hardware description languages is not strictly necessary, but additional research on the topic will likely be required to make serious modifications of your own.
For instructions on setting up the ARTIQ development environment and on building gateware and firmware binaries, first see :doc:`building_developing` in the main part of the manual.
Introduction to the ARTIQ internal stack
----------------------------------------
.. tikz::
:align: center
:libs: arrows.meta
:xscale: 70
\definecolor{primary}{HTML}{0d3547} % ARTIQ blue
\pgfdeclarelayer{bg} % declare background layer
\pgfsetlayers{bg,main} % set layer order
\node[draw, dotted, thick] (frontend) at (0, 6) {Host machine: Compiler, master, GUI...};
\node[draw=primary, fill=white] (software) at (0, 5) {Software: \it{Kernel code}};
\node[draw=blue, fill=white, thick] (firmware) at (0, 4) {Firmware: \it{ARTIQ runtime}};
\node[draw=blue, fill=white, thick] (gateware) at (0, 3) {Gateware: \it{Migen and Misoc}};
\node[draw=primary, fill=white] (hardware) at (0, 2) {Hardware: \it{Sinara ecosystem}};
\begin{pgfonlayer}{bg}
\draw[primary, -Stealth, dotted, thick] (frontend.south) to [out=180, in=180] (firmware.west);
\draw[primary, -Stealth, dotted, thick] (frontend) to (software);
\end{pgfonlayer}
\draw[primary, -Stealth] (firmware) to (software);
\draw[primary, -Stealth] (gateware) to (firmware);
\draw[primary, -Stealth] (hardware) to (gateware);
Like any other modern piece of software, kernel code running on an ARTIQ core device rests upon a layered infrastructure, starting with the hardware: the physical carrier board and its peripherals. Generally, though not exclusively, this is the `Sinara device family <https://m-labs.hk/experiment-control/sinara-core/>`_, which is designed to work with ARTIQ. Other carrier boards, such as the Xilinx KC705 and ZC706, are also supported.
All of the ARTIQ core device carrier boards necessarily center around a physical field-programmable gate array, or FPGA. If you have never worked with FPGAs before, it is easiest to understand them as 'rearrangeable' circuits. Ideally, they are capable of approaching the tremendous speed and timing precision advantages of custom-designed, application-specific hardware, while still being reprogrammable, allowing development and revision to continue after manufacturing.
The 'configuration' of an FPGA, the circuit design it is programmed with, is its *gateware*. Gateware is not software, and is not written in programming languages. Rather, it is written in a *hardware description language,* of which the most common are VHDL and Verilog. The ARTIQ codebase uses a set of tools called `Migen <https://m-labs.hk/gateware/migen/>`_ to write hardware description in a subset of Python, which is later translated to Verilog behind the scenes. This has the advantage of preserving much of the flexibility and convenience of Python as a programming language, but shouldn't be mistaken for it *being* Python, or functioning like Python. (MiSoC, built on Migen, is used to implement softcore -- i.e. 'programmed', on-FPGA, not hardwired -- CPUs on Kasli and KC705. Zynq devices contain 'hardcore' ARM CPUs already and correspondingly make relatively less intensive use of MiSoC.)
The low-level software that runs directly on the core device's CPU, softcore or hardcore, is its *firmware.* This is the 'operating system' of the core device. The firmware is tasked, among other things, with handling the low-level communication between the core device and the host machine, as well as between the core devices in a DRTIO setting. It is written in bare-metal `Rust <https://www.rust-lang.org/>`__. There are currently two active versions of the ARTIQ firmware (the version used for ARTIQ-Zynq, NAR3, is more modern than that used on Kasli and KC705, and will likely eventually replace it) but they are functionally equivalent except for internal details.
Experiment kernels themselves -- ARTIQ Python, processed by the ARTIQ compiler and loaded from the host machine -- rest on top of and are framed and supported by the firmware, in the same sense way that application software on your PC rests on top of an operating system. All together, software kernels communicate with the firmware to set parameters for the gateware, which passes signals directly to the hardware.
These frameworks are built to be self-contained and extensible. To make additions to the gateware and software, for example, we do not need to make changes to the firmware; we can interact purely with the interfaces provided on either side.
Extending gateware logic
------------------------
As briefly explained in :doc:`rtio`, when we talk about RTIO infrastructure, we are primarily speaking of structures implemented in gateware. The FIFO banks which hold scheduled output events or recorded input events, for example, are in gateware. Sequence errors, overflow exceptions, event spreading, and so on, happen in the gateware. In some cases, you may want to make relatively simple, parametric changes to existing RTIO, like changing the sizes of certain queues. In this case, it can be as simple as tracking down the part of the code where this parameter is set, changing it, and :doc:`rebuilding the binaries <building_developing>`.
.. warning::
Note that FPGA resources are finite, and buffer sizes, lane counts, etc., are generally chosen to maximize available resources already, with different values depending on the core device in use. Depending on the peripherals you include (some are more resource-intensive than others) blanket increases will likely quickly outstrip the capacity of your FPGA and fail to build. Increasing the depth of a particular channel you know to be heavily used is more likely to succeed; the easiest way to find out is to attempt the build and observe what results.
Gateware in ARTIQ is housed in ``artiq/gateware`` on the main ARTIQ repository and (for Zynq-specific additions) in ``artiq-zynq/src/gateware`` on ARTIQ-Zynq. The starting point for figuring out your changes will often be the *target file*, which is core device-specific and which you may recognize as the primary module called when building gateware. Depending on your core device, simply track down the file named after it, as in ``kasli.py``, ``kasli_soc.py``, and so on. Note that the Kasli and Kasli-SoC targets are designed to take JSON description files as input, whereas their KC705 and ZC706 equivalents work with hardcoded variants instead.
To change parameters related to particular peripherals, see also the files ``eem.py`` and ``eem_7series.py``, which describe the core device's interface with other EEM cards in Migen terms, and contain ``add_std`` methods that in turn reference specific gateware modules and assign RTIO channels.
Adding a module to gateware
^^^^^^^^^^^^^^^^^^^^^^^^^^^
To demonstrate how RTIO can be *extended,* on the other hand, we will develop a new interface entirely for the control of certain hardware -- in our case, for a simple example, the core device LEDs. If you haven't already, follow the instructions in :doc:`building_developing` to clone the ARTIQ repository and set up a development environment. The first part of our addition will be a module added to ``gateware/rtio/phy`` (PHY, for interaction with the physical layer), written in the Migen Fragmented Hardware Description Language (FHDL).
.. seealso::
To find reference material for FHDL and the Migen constructs we will use, see the Migen manual, in particular the page `The FHDL domain-specific language <https://m-labs.hk/migen/manual/fhdl.html>`_.
.. warning::
If you have never worked with a hardware description language before, it is important to understand that hardware description is fundamentally different to programming in a language like Python or Rust. At its most basic, a program is a set of instructions: a step-by-step guide to a task you want to see performed, where each step is written, and executed, principally in sequence. In contrast, hardware description is *a description*. It specifies the static state of a piece of hardware. There are no 'steps', and no chronological execution, only stated facts about how the system should be built.
The examples we will handle in this tutorial are simple, and you will likely find Migen much more readable than traditional languages like VHDL and Verilog, but keep in mind that we are describing how a system connects and interlocks its signals, *not* operations it should perform.
Normally, the PHY module used for LEDs is the ``Output`` of ``ttl_simple.py``. Take a look at its source code. Note that values like ``override`` and ``probes`` exist to support RTIO MonInj -- ``probes`` for monitoring, ``override`` for injection -- and are not involved with normal control of the output. Note also that ``pad``, among FPGA engineers, refers to an input/output pad, i.e. a physical connection through which signals are sent. ``pad_n`` is its negative pair, necessary only for certain kinds of TTLs and not applicable to LEDs.
Interface and signals
"""""""""""""""""""""
To get started, create a new file in ``gateware/rtio/phy``. Call it ``linked_leds.py``. In it, create a class ``Output``, which will inherit from Migen's ``Module``, and give it an ``init`` method, which takes two pads as input: ::
from migen import *
class Output(Module):
def __init__(self, pad0, pad1):
``pad0`` and ``pad1`` will represent output pads, in our case ultimately connecting to the board's user LEDs. On the other side, to receive output events from a RTIO FIFO queue, we will use an ``Interface`` provided by the ``rtlink`` module, also found in ``artiq/gateware``. Both output and input interfaces are available, and both can be combined into one link, but we are only handling output events. We use the ``data_width`` parameter to request an interface that is 2 bits wide: ::
from migen import *
from artiq.gateware.rtio import rtlink
class Output(Module):
def __init__(self, pad0, pad1):
self.rtlink = rtlink.Interface(rtlink.OInterface(2))
In our example, rather than controlling both LEDs manually using ``on`` and ``off``, which is the functionality ``ttl_simple.py`` provides, we will control one LED manually and have the gateware determine the value of the other based on the first. This same logic would be easy (in fact, much easier) to implement in ARTIQ Python; the advantage of placing it in gateware is that logic in gateware is *extremely fast,* in effect 'instant', i.e., completed within a single clock cycle. Rather than waiting for a CPU to process and respond to instructions, a response can happen at the speed of a dedicated logic circuit.
.. note::
Naturally, the truth is more complicated, and depends heavily on how complex the logic in question is. An overlong chain of gateware logic will fail to settle within a single RTIO clock cycle, causing a wide array of potential problems that are difficult to diagnose and difficult to fix; the only solutions are to simplify the logic, deliberately split it across multiple clock cycles (correspondingly increasing latency for the operation), or to decrease the speed of the clock (increasing latency for *everything* the device does).
For now, it's enough to say that you are unlikely to encounter timing failures with the kind of simple logic demonstrated in this tutorial. Indeed, designing gateware logic to run in as few cycles as possible without 'failing timing' is an engineering discipline in itself, and much of what FPGA developers spend their time on.
In practice, of course, since ARTIQ explicitly allows scheduling simultaneous output events to different channels, there's still no reason to make gateware modifications to accomplish this. After all, leveraging the real-time capabilities of customized gateware without making it necessary to *write* it is much of the point of ARTIQ as a system. Only in more complex cases, such as directly binding inputs to outputs without feeding back through the CPU, might gateware-level additions become necessary.
For now, add two intermediate signals for our logic, instances of the Migen ``Signal`` construct: ::
def __init__(self, pad0, pad1):
self.rtlink = rtlink.Interface(rtlink.OInterface(2))
reg = Signal()
pad0_o = Signal()
.. note::
A gateware 'signal' is not a signal in the sense of being a piece of transmitted information. Rather, it represents a channel, which bits of information can be held in. To conceptualize a Migen ``Signal``, take it as a kind of register: a box that holds a certain number of bits, and can update those bits from an input, or broadcast them to an output connection. The number of bits is arbitrary, e.g., a ``Signal(2)`` will be two bits wide, but in our example we handle only single-bit registers.
These are our inputs, outputs, and intermediate signals. By convention, in Migen, these definitions are all made at the beginning of a module, and separated from the logic that interconnects them with a line containing the three symbols ``###``. See also ``ttl_simple.py`` and other modules.
Since hardware description is not linear or chronological, nothing conceptually prevents us from making these statements in any other order -- in fact, except for the practicalities of code execution, nothing particularly prevents us from defining the connections between the signals before we define the signals themselves -- but for readable and maintainable code, this format is vastly preferable.
Combinatorial and synchronous statements
""""""""""""""""""""""""""""""""""""""""
After the ``###`` separator, we will set the connecting logic. A Migen ``Module`` has several special attributes, to which different logical statements can be assigned. We will be using ``self.sync``, for synchronous statements, and ``self.comb``, for combinatorial statements. If a statement is *synchronous*, it is only updated once per clock cycle, i.e. when the clock ticks. If a statement is *combinatorial*, it is updated whenever one of its inputs change, i.e. 'instantly'.
Add a synchronous block as follows: ::
self.sync.rio_phy += [
If(self.rtlink.o.stb,
pad0_o.eq(self.rtlink.o.data[0] ^ pad0_o),
reg.eq(self.rtlink.o.data[1])
)
]
In other words, at every tick of the ``rtio_phy`` clock, if the ``rtlink`` strobe signal (which is set to high when the data is valid, i.e., when an output event has just reached the PHY) is high, the ``pad0_o`` and ``reg`` registers are updated according to the input data on ``rtlink``.
.. note::
Notice that, in a standard synchronous block, it makes no difference how or how many times the inputs to an ``.eq()`` statement change or fluctuate. The output is updated *exactly once* per cycle, at the tick, according to the instantaneous state of the inputs in that moment. In between ticks and during the clock cycle, it remains stable at the last updated level, no matter the state of the inputs. This stability is vital for the broader functioning of synchronous circuits, even though 'waiting for the tick' adds latency to the update.
``reg`` is simply set equal to the incoming bit. ``pad0_o``, on the other hand, flips its old value if the input is ``1``, and keeps it if the input is ``0``. Note that ``^``, which you may know as the Python notation for a bitwise XOR operation, here simply represents a XOR gate. In summary, we can flip the value of ``pad0`` with the first bit of the interface, and set the value of ``reg`` with the other.
Add the combinatorial block as follows: ::
self.comb += [
pad0.eq(pad0_o),
If(reg,
pad1.eq(pad0_k)
)
]
The output ``pad0`` is continuously connected to the value of the ``pad0_o`` register. The output of ``pad1`` is set equal to that of ``pad0``, but only if the ``reg`` register is high, or ``1``.
The module is now capable of accepting RTIO output events and applying them to the hardware outputs. What we can't yet do is generate these output events in an ARTIQ kernel. To do that, we need to add a core device driver.
Adding a core device driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you have been writing ARTIQ experiments for any length of time, you will already be familiar with the core device drivers. Their reference is kept in this manual on the page :doc:`core_drivers_reference`; their methods are commonly used to manipulate the core device and its close peripherals. Source code for these drivers is kept in the directory ``artiq/coredevice``. Create a new file, again called ``linked_led.py``, in this directory.
The drivers are software, not gateware, and they are written in regular ARTIQ Python. They use methods given in ``coredevice/rtio.py`` to queue input and output events to RTIO channels. We will start with its ``__init__``, the method ``get_rtio_channels`` (which is formulaic, and exists only to be used by :meth:`~artiq.frontend.artiq_rtiomap`), and a output set method ``set_o``: ::
from artiq.language.core import *
from artiq.language.types import *
from artiq.coredevice.rtio import rtio_output
class LinkedLED:
def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device)
self.channel = channel
self.target_o = channel << 8
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel
def set_o(self, o):
rtio_output(self.target_o, o)
.. note::
``rtio_output()`` is one of four methods given in ``coredevice/rtio.py``, which provides an interface with lower layers of the system. You can think of it ultimately as representing the other side of the ``Interface`` we requested in our Migen module. Notably, in between the two, events pass through the SED and its FIFO lanes, where they are held until the exact real-time moment the events were scheduled for, as originally described in :doc:`rtio`.
Now we can write the kernel API. In the gateware, bit 0 flips the value of the first pad: ::
@kernel
def flip_led(self):
self.set_o(0b01)
and bit 1 connects the second pad to the first: ::
@kernel
def link_up(self):
self.set_o(0b10)
There's no reason we can't do both at the same time: ::
@kernel
def flip_together(self):
self.set_o(0b11)
Target and device database
^^^^^^^^^^^^^^^^^^^^^^^^^^
Our ``linked_led`` PHY module exists, but in order for it to be generated as part of a set of ARTIQ binaries, we need to add it to one of the target files. Find the target file for your core device, as described above. Each target file is structured differently; track down the part of the file where channels and PHY modules are assigned to the user LEDs. Depending on your core device, there may be two or more LEDs that are available. Look for lines similar to: ::
for i in (0, 1):
user_led = self.platform.request("user_led", i)
phy = ttl_simple.Output(user_led)
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
Edit the code so that, rather than assigning a separate PHY and channel to each LED, two of the LEDs are grouped together in ``linked_led``. You might use something like: ::
print("Linked LEDs at:", len(rtio_channels))
phy = linked_led.Output(self.platform.request("user_led", 0), self.platform.request("user_led", 1))
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
Save the target file, under a different name if you prefer. Follow the instructions in :doc:`building_developing` to build a set of binaries, being sure to use your edited target file for the gateware, and flash your core device, for simplicity preferably in a standalone configuration without peripherals.
Now, before you can access your new core device driver from a kernel, it must be added to your device database. Find your ``device_db.py``. Delete the entries dedicated to the user LEDs that you have repurposed; if you tried to control those LEDs using the standard TTL interfaces now, the corresponding gateware would be missing anyway. Add an entry with your new driver, as in: ::
device_db["leds"] = {
"type": "local",
"module": "artiq.coredevice.linked_led",
"class": "LinkedLED",
"arguments": {"channel": 0x000008}
}
.. warning::
Channel numbers are assigned sequentially each time ``rtio_channels.append()`` is called. Since we assigned the channel for our linked LEDs in the same location as the old user LEDs, the correct channel number is likely simply the one previously used in your device database for the first LED. In any other case, however, the ``print()`` statement we added to the target file should tell us the exact canonical channel. Search through the console logs produced when generating the gateware to find the line starting with ``Linked LEDs at:``.
Depending on how your device database was written, note that the channel numbers for other peripherals, if they are present, *will have changed*, and :meth:`~artiq.frontend.artiq_ddb_template` will not generate their numbers correctly unless it is edited to match the new assignments of the user LEDs. For a longer-term gateware change, especially the addition of a new EEM card, ``artiq/frontend/artiq_ddb_template.py`` and ``artiq/coredevice/coredevice_generic.schema`` should be edited accordingly, so that system descriptions and device databases can continue to be parsed and generated correctly.
Test experiments
^^^^^^^^^^^^^^^^
Now the device ``leds`` can be called from your device database, and its corresponding driver accessed, just as with any other device. Try writing some miniature experiments, for instance ``flip.py``: ::
from artiq.experiment import *
class flip(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("leds")
@kernel
def run(self):
self.core.reset()
self.leds.flip_led()
and ``linkup.py``: ::
from artiq.experiment import *
class sync(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("leds")
@kernel
def run(self):
self.core.reset()
self.leds.link_up()
Run these and observe the results. Congratulations! You have successfully constructed an extension to the ARTIQ RTIO.
.. + 'Adding custom EEMs' and 'Merging support'

View File

@ -50,6 +50,11 @@ Some things to consider:
- Is some other device in your network already using the configured IP address? Turn off the core device and try pinging the configured IP address; if it responds, you have a culprit. One of the two will need a different networking configuration. - Is some other device in your network already using the configured IP address? Turn off the core device and try pinging the configured IP address; if it responds, you have a culprit. One of the two will need a different networking configuration.
- Are there restrictions or issues in your router or subnet that are preventing the core device from connecting? It may help to try connecting the core device to your PC directly. - Are there restrictions or issues in your router or subnet that are preventing the core device from connecting? It may help to try connecting the core device to your PC directly.
fix 'no startup kernel found' / 'no idle kernel found' in the core log?
-----------------------------------------------------------------------
Don't. Note that these are ``INFO`` messages, and not ``ERROR`` or even ``WARN``. If you haven't flashed an idle or startup kernel yet, this is normal, and will not cause any problems; between experiments the core device will simply do nothing. The same applies to most other messages in the style of 'no configuration found' or 'falling back to default'. Your system will generally run just fine on its defaults until you get around to setting these configurations, though certain features may be limited until properly set up. See :doc:`configuring` and the list of keys in :ref:`core-device-flash-storage`.
fix 'Mismatch between gateware and software versions'? fix 'Mismatch between gateware and software versions'?
------------------------------------------------------ ------------------------------------------------------
@ -184,12 +189,10 @@ Experiment windows can be organized by using the following hotkeys:
The windows will be organized in the order they were last interacted with. The windows will be organized in the order they were last interacted with.
fix errors when restarting the management system after a crash? fix errors when restarting management system after a crash?
--------------------------------------------------------------- -----------------------------------------------------------
On Windows in particular, abnormal shutdowns such as power outages or bluescreens can sometimes corrupt the organizational files used by the management system, resulting in errors to the tune of ``ValueError: source code string cannot contain null bytes`` when restarting. The easiest way to handle these problems is to delete the corrupted files and start from scratch. On Windows in particular, abnormal shutdowns such as power outages or bluescreens sometimes corrupt the organizational files used by the management system, resulting in errors to the tune of ``ValueError: source code string cannot contain null bytes`` when restarting. The easiest way to handle these problems is to delete the corrupted files and start from scratch. Note that GUI configuration ``.pyon`` files are kept in the user configuration directory, see below at :ref:`gui-config-files`
If the master itself fails to start, it may be necessary to delete the dataset database or even ``last_rid.pyon`` to restart properly, but if the dashboard or browser fail, the problem is probably in the GUI configuration files, where the state of the GUI (arrangement of docks, applets, etc.) is kept. These files are backed up once whenever they are successfully loaded. Navigate to the user configuration directory (see :ref:`gui-config-files`) and look for a file suffixed ``.backup``. To restore the GUI, simply delete the corrupted configuration file and rename the backup to replace it.
create and use variable-length arrays in kernels? create and use variable-length arrays in kernels?
------------------------------------------------- -------------------------------------------------

View File

@ -31,7 +31,6 @@ ARTIQ manual
:caption: ARTIQ components :caption: ARTIQ components
:maxdepth: 2 :maxdepth: 2
overview
environment environment
compiler compiler
management_system management_system
@ -60,5 +59,4 @@ ARTIQ manual
:caption: Addenda :caption: Addenda
:maxdepth: 2 :maxdepth: 2
extending_rtio
faq faq

View File

@ -17,7 +17,7 @@ See also the different options for enabling flakes on `the NixOS wiki <https://n
The easiest way to obtain ARTIQ is to install it into the user environment with :: The easiest way to obtain ARTIQ is to install it into the user environment with ::
$ nix profile install git+https://github.com/m-labs/artiq.git $ nix profile install git+https://github.com/m-labs/artiq.git?ref=release-8
Answer "Yes" to the questions about setting Nix configuration options (for more details see 'Troubleshooting' below.) You should now have a minimal installation of ARTIQ, where the usual front-end commands (:mod:`~artiq.frontend.artiq_run`, :mod:`~artiq.frontend.artiq_master`, :mod:`~artiq.frontend.artiq_dashboard`, etc.) are all available to you. Answer "Yes" to the questions about setting Nix configuration options (for more details see 'Troubleshooting' below.) You should now have a minimal installation of ARTIQ, where the usual front-end commands (:mod:`~artiq.frontend.artiq_run`, :mod:`~artiq.frontend.artiq_master`, :mod:`~artiq.frontend.artiq_dashboard`, etc.) are all available to you.
@ -28,7 +28,7 @@ Installing multiple packages and making them visible to the ARTIQ commands requi
:: ::
{ {
inputs.extrapkg.url = "git+https://git.m-labs.hk/M-Labs/artiq-extrapkg.git"; inputs.extrapkg.url = "git+https://git.m-labs.hk/M-Labs/artiq-extrapkg.git?ref=release-8";
outputs = { self, extrapkg }: outputs = { self, extrapkg }:
let let
pkgs = extrapkg.pkgs; pkgs = extrapkg.pkgs;
@ -154,13 +154,13 @@ This will set your user as a trusted user, allowing the use of any untrusted sub
Installing via MSYS2 (Windows) Installing via MSYS2 (Windows)
------------------------------ ------------------------------
We recommend using our `offline installer <https://nixbld.m-labs.hk/job/artiq/extra-beta/msys2-offline-installer/latest>`_, which contains all the necessary packages and requires no additional configuration. After installation, simply launch ``MSYS2 with ARTIQ`` from the Windows Start menu. We recommend using our `offline installer <https://nixbld.m-labs.hk/job/artiq/extra/msys2-offline-installer/latest>`_, which contains all the necessary packages and requires no additional configuration. After installation, simply launch ``MSYS2 with ARTIQ`` from the Windows Start menu.
Alternatively, you may install `MSYS2 <https://msys2.org>`_, then edit ``C:\MINGW64\etc\pacman.conf`` and add at the end: :: Alternatively, you may install `MSYS2 <https://msys2.org>`_, then edit ``C:\MINGW64\etc\pacman.conf`` and add at the end: ::
[artiq] [artiq]
SigLevel = Optional TrustAll SigLevel = Optional TrustAll
Server = https://msys2.m-labs.hk/artiq-beta Server = https://msys2.m-labs.hk/artiq
Launch ``MSYS2 CLANG64`` from the Windows Start menu to open the MSYS2 shell, and enter the following commands: :: Launch ``MSYS2 CLANG64`` from the Windows Start menu to open the MSYS2 shell, and enter the following commands: ::
@ -189,7 +189,7 @@ Controllers for third-party devices (e.g. Thorlabs TCube, Lab Brick Digital Atte
Set up the Conda channel and install ARTIQ into a new Conda environment: :: Set up the Conda channel and install ARTIQ into a new Conda environment: ::
$ conda config --prepend channels https://conda.m-labs.hk/artiq-beta $ conda config --prepend channels https://conda.m-labs.hk/artiq
$ conda config --append channels conda-forge $ conda config --append channels conda-forge
$ conda create -n artiq artiq $ conda create -n artiq artiq
@ -237,3 +237,4 @@ Switching between Conda environments using commands such as ``$ conda deactivate
You can list the environments you have created using:: You can list the environments you have created using::
$ conda env list $ conda env list

View File

@ -5,21 +5,25 @@ Introduction
.. include:: ../../README.rst .. include:: ../../README.rst
and including in README.rst does not work on github therefore just keep this content synchronized with README.rst and including in README.rst does not work on github therefore just keep this content synchronized with README.rst
ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is a leading-edge control and data acquisition system for quantum information experiments. ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is the next-generation control system for quantum information experiments.
It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Many laboratories around the world have adopted ARTIQ as their control system and some have `contributed <https://m-labs.hk/experiment-control/funding/>`_ to it. It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Several other laboratories (e.g. at the University of Oxford, the Army Research Lab, and the University of Maryland) have later adopted ARTIQ as their control system and have contributed to it.
The system features a high-level programming language, capable of describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results. The system features a high-level programming language that helps describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results.
ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware <https://github.com/sinara-hw>`_, and in particular the Kasli FPGA carrier, are designed to work with ARTIQ. ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers. Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort. ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware <https://github.com/sinara-hw>`_, and in particular the Kasli FPGA carrier, is designed to work with ARTIQ.
ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers.
Several different configurations of a `high-end FPGA evaluation kit <http://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort.
ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions. Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration. Like any open-source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_. ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and Conda packages (for Windows and Linux).
Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration.
Like any open source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_.
ARTIQ is supported by M-Labs and developed openly. Components, features, fixes, improvements, and extensions are often `funded <https://m-labs.hk/experiment-control/funding/>`_ by and developed for the partnering research groups. ARTIQ is supported by M-Labs and developed openly.
Components, features, fixes, improvements, and extensions are funded by and developed for the partnering research groups.
Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt6 <https://www.qt.io/>`_. Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt5 <https://www.qt.io/>`_.
| Website: https://m-labs.hk/experiment-control/artiq Website: https://m-labs.hk/artiq
| (US-hosted mirror: https://m-labs-intl.com/experiment-control/artiq)
`Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``. `Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``.

View File

@ -1,20 +0,0 @@
Overview diagram
================
.. Kept in a separate file for the luxury of good syntax highlighting
.. tikz:: Structure of an ARTIQ system. Note that most components can also be used independently of each other. Network-connected components can be distributed across multiple machines.
:align: left
:libs: positioning, shapes, arrows
:xscale: 100
:include: overview.tex
Footnotes:
1. As described in the :doc:`management tutorial <getting_started_mgmt>`, the clients can simply be run on the same machine as the master. It is common however to set up several other 'client machines' to allow distributed access to the system, each with an installation of ARTIQ and (potentially) a Git clone of the experiment repository. The communication between components is the same. Other ARTIQ commands and utilities can also be used from these machines.
2. It is common to have controllers for one or several 'slow' devices running on other machines. In this case, it's simply necessary to have one controller manager per machine. Note that running controllers and controller managers does not require a full ARTIQ install: the minimal package ``artiq-comtools`` can be installed instead. Otherwise, if these controllers are run side-by-side to the built-in controllers, they can be handled by the same controller manager, as depicted by the non-dashed lines.
3. The various other ARTIQ utilities may also connect to the master, read the device database, communicate with the core device, etc., depending on their specific functionalities. See their :doc:`individual references <utilities>`.
4. These are the :ref:`built-in controllers <built-in-ctlrs>` used by the ARTIQ dashboard to provide certain functionalities which require access to the core device. They can be found listed in :ref:`Utilities <utilities-ctrls>` (the commands prefixed with ``aqctl_`` rather than ``artiq_``). It is possible to use the ARTIQ management system without them, and the other abilities of the dashboard will still function, but important features will be unavailable.

View File

@ -1,212 +0,0 @@
[
thin/.style={line width=1.2pt}, % default line thickness
thick/.style={line width=1.8pt},
% ampersand replacement apparently necessary for sphinx latexpdf output (but not for html!)
ampersand replacement=\&
]
% artiq colors
\definecolor{brand}{HTML}{715ec7} % 'brand colour' violet
\definecolor{brand-light}{HTML}{a88cfd} % lighter brand colour
\definecolor{primary}{HTML}{0d3547}
\definecolor{secondary}{HTML}{1a6d93}
\definecolor{dingle}{HTML}{76c5d2}
\definecolor{rtm}{HTML}{3084bc} % RTM blue
% other colors used are tikz standard issue
% tikzstyle settings
\tikzstyle{every node} = [anchor = center, thin, minimum size = 1cm]
% the every matrix style is odd and can't be overriden (??) so its usefulness is limited
\tikzstyle{every matrix} = [anchor = north west, rounded corners]
\tikzstyle{matrices} = [thick, row sep = 0.3cm, column sep=0.3cm]
\tikzstyle{legend} = [draw = primary, thin, row sep=0.1cm, minimum width = 5cm, column sep = 0.2cm]
% particular node styles
\tikzstyle{label} = [minimum size = 0.5cm]
\tikzstyle{single} = [draw = brand, minimum width = 2 cm]
\tikzstyle{splits} = [draw = brand, rectangle split, solid]
\tikzstyle{linelabel} = [anchor = west, font=\sc\footnotesize]
\tikzstyle{colorbox} = [rectangle, very thin, draw=gray, sharp corners, minimum height = 0.4 cm, minimum width = 1.8 cm]
% color coding
\tikzstyle{line} = [thick]
\tikzstyle{network} = [line, draw = magenta]
\tikzstyle{git} = [line, draw = violet]
\tikzstyle{rtio} = [line, draw = brand-light]
\tikzstyle{drtio} = [line, draw = blue]
\tikzstyle{slow} = [line, draw = dingle]
\tikzstyle{jtag} = [line, draw = gray]
\tikzstyle{direct} = [line, draw = primary, thin]
\tikzstyle{master} = [draw = purple]
\tikzstyle{host} = [row sep = 0.5 cm, column sep = 0.7 cm]
\tikzstyle{pc} = [matrices, thin, inner sep = 0.2cm, draw = gray, dashed]
\tikzstyle{peripheral} = [matrices, draw = secondary]
\tikzstyle{core} = [matrices, draw = purple]
% PERIPHERALS (SLOW)
\matrix[peripheral](peri-slow) at (0, 15.75)
{
\node[label] (label){Peripherals (slow)};\\
\node [splits, rectangle split parts = 4](slow-list){
\nodepart{one} Optical translation stages
\nodepart{two} Optical rotation stages
\nodepart{three} Temperature controllers
\nodepart{four} etc...
};\\
};
% OTHER MACHINES
\matrix[pc](comtools) at (6.38, 15.75)
{
\node [splits, rectangle split parts = 3](remote-ctlrs){
\nodepart{one} Controller
\nodepart{two} Controller
\nodepart{three} etc...
}; \\
\node[single, minimum height = 0.5cm, font=\tt](remote-ctlmgr){
artiq\_ctlmgr\textsuperscript{2}
}; \\
};
\node[anchor = north west] (label) at (11, 16) {Client machines, NB: see footnote 1.};
\matrix[pc](clients) at (10.75, 15)
{
\node[single, draw=rtm, solid](remote-repo) {Cloned repository};
\&
\node [splits, master, rectangle split parts = 2, minimum width = 3 cm](remote-client){
\nodepart[font=\tt]{one} artiq\_client
\nodepart[font=\tt]{two} artiq\_dashboard
};
\\
};
% HOST MACHINE
\matrix[host](host-machine) at (0, 11)
{
\node[font=\tt, single](browser) {artiq\_browser};
\&
\node[single, master](results){Results};
\&
\node[splits, rectangle split parts = 2, master](master) {
\nodepart[font=\bf]{one} ARTIQ master
\nodepart{two} Datasets
};
\&
\node [single, master](repo){Repository};
\&
\node[font=\tt, single, master](ctlmgr) {artiq\_ctlmgr};
\\
\node[font=\tt, single](flash) {artiq\_flash};
\&
\node[font=\tt, single](run) {artiq\_run};
\&
\node[single, master](ddb) {Device database};
\&
\node[font=\tt, single](coremgmt) {artiq\_coremgmt\textsuperscript{3}};
\&
\node[single, master](builtin) {Built-in controllers\textsuperscript{4}};
\\
};
% BORDER LINE
\node[linelabel] (label1) at (-0.25, 6.3) {PCs AND REMOTE DEVICES};
\draw[primary, thick, dotted] (-0.5, 6) -- (19, 6);
\node[linelabel] (label2) at (0, 5.7) {REAL TIME HARDWARE};
% PERIPHERALS (FAST)
\matrix[peripheral](peri-fast) at (0, 5)
{
\node[label] (label){Peripherals (fast)};\\
\node [splits, rectangle split parts = 5](fast-list){
\nodepart{one} TTL in/out
\nodepart{two} DDS/Urukul
\nodepart{three} AWG/Phaser
\nodepart{four} DAC/Zotino
\nodepart{five} etc...
};\\
};
% CORE DEVICES
\matrix[core](core) at (6.75, 5)
{
\node[label] (label){Core device};\\
\node [splits, rectangle split parts = 2](core-list){
\nodepart{one} CPU
\nodepart{two} RTIO gateware
};\\
};
% SATELLITE
\matrix[core](satellite) at (6.25, 2)
{
\node[label] (label){Satellite core devices};\\
};
% legend
\matrix[legend] (legend) at (12, 5.5) {
\node [colorbox, fill=magenta] (network) {}; \& \node[label] (network-label) {Network}; \\
\node [colorbox, fill=brand-light] (rtio) {}; \& \node[label] (rtio-label) {RTIO}; \\
\node [colorbox, fill=dingle] (slow) {}; \& \node[label] (slow-label) {Slow IO}; \\
\node [colorbox, fill=blue] (drtio) {}; \& \node[label] (drtio-label) {DRTIO}; \\
\node [colorbox, fill=gray] (jtag) {}; \& \node[label] (jtag-label) {JTAG}; \\
\node [colorbox, fill=primary] (direct) {}; \& \node[label] (direct-label) {Local}; \\
\node [colorbox, fill=violet] (git) {}; \& \node[label] (git-label) {Git}; \\
};
%controllers
\draw[direct, dashed] (remote-ctlmgr.north) -- (remote-ctlrs);
\draw[slow] (slow-list.one east) -| +(0.5,0) |- (remote-ctlrs.one west);
\draw[slow] (slow-list.two east) -| +(0.8,0) |- (remote-ctlrs.two west);
\draw[slow] (slow-list.three east) -| +(1.1,0) |- (remote-ctlrs.three west);
\draw[direct] (remote-ctlrs.east) -| + (1, -2.25) -|(ctlmgr.70);
%ctlmgr
\draw[network, dashed] (remote-ctlmgr.west) -| +(-0.5, -1) -| (master.120);
% client
\draw[git] (remote-repo.south) -- +(0, -1) -| (repo.110);
\draw[network] (remote-client.south) -- +(0, -1.5) -| (master.90);
\draw[network] (remote-client.two east) -- +(0.7, 0) |- (builtin.east);
%host
\draw[direct] (browser) -- (results);
%master
\draw[direct] (results) -- (master);
\draw[direct] (master) -- (ddb);
\draw[direct] (master) -- (repo);
\draw[direct] (run) -- (ddb);
\draw[direct] (coremgmt) -- (ddb);
% ctlmgr
\draw[network] (master.60) -- +(0, 0.25) -| (ctlmgr.110);
\draw[network] (ctlmgr) -- (builtin);
% core connections
\draw[jtag] (flash.south) |- +(1, -1) -| (core.125);
\draw[network] (run.south) |- +(1, -0.75) -| (core.110);
\draw[network] (master.230) |- +(-1.25, -0.25) |- +(0, -2) -| (core.north);
\draw[network] (coremgmt.south) |- +(-0.75, -0.75) -| (core.70);
\draw[network] (builtin.south) |- +(-2, -1.1) -| (core.55);
%rtio
\node (branch) at (5, 2.5){};
\draw[rtio] (core-list.two west) |- +(-1, 0) |- (branch.center);
\draw[rtio] (branch.center) |- (fast-list.one east);
\draw[rtio] (branch.center) |- (fast-list.two east);
\draw[rtio] (branch.center) |- (fast-list.three east);
\draw[rtio] (branch.center) |- (fast-list.four east);
\draw[rtio] (branch.center) |- (fast-list.five east);
%drtio
\draw[drtio] (core-list.one east) |- +(1, 0) |- (satellite.east);
\draw[rtio] (satellite.west) -| +(-0.5, 0) |- (5, 2);

View File

@ -105,8 +105,6 @@ Note however that in order for the controller manager to be able to start a cont
Once a device is correctly listed in ``device_db.py``, it can be added to an experiment using ``self.setattr_device([device_name])`` and the methods its API offers called straightforwardly as ``self.[device_name].[method_name]``. As long as the requisite controllers are running and available, the experiment can then be executed with :mod:`~artiq.frontend.artiq_run` or through the management system. To understand how to add controllers to the device database, see also :ref:`device-db`. Once a device is correctly listed in ``device_db.py``, it can be added to an experiment using ``self.setattr_device([device_name])`` and the methods its API offers called straightforwardly as ``self.[device_name].[method_name]``. As long as the requisite controllers are running and available, the experiment can then be executed with :mod:`~artiq.frontend.artiq_run` or through the management system. To understand how to add controllers to the device database, see also :ref:`device-db`.
.. _built-in-ctlrs:
ARTIQ built-in controllers ARTIQ built-in controllers
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -42,18 +42,34 @@
"type": "github" "type": "github"
} }
}, },
"mozilla-overlay": {
"flake": false,
"locked": {
"lastModified": 1704373101,
"narHash": "sha256-+gi59LRWRQmwROrmE1E2b3mtocwueCQqZ60CwLG+gbg=",
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"rev": "9b11a87c0cc54e308fa83aac5b4ee1816d5418a2",
"type": "github"
},
"original": {
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1724224976, "lastModified": 1722987190,
"narHash": "sha256-Z/ELQhrSd7bMzTO8r7NZgi9g5emh+aRKoCdaAv5fiO0=", "narHash": "sha256-68hmex5efCiM2aZlAAEcQgmFI4ZwWt8a80vOeB/5w3A=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c374d94f1536013ca8e92341b540eba4c22f9c62", "rev": "21cc704b5e918c5fbf4f9fff22b4ac2681706d90",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-unstable", "ref": "nixos-24.05",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@ -61,35 +77,14 @@
"root": { "root": {
"inputs": { "inputs": {
"artiq-comtools": "artiq-comtools", "artiq-comtools": "artiq-comtools",
"mozilla-overlay": "mozilla-overlay",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay",
"sipyco": "sipyco", "sipyco": "sipyco",
"src-migen": "src-migen", "src-migen": "src-migen",
"src-misoc": "src-misoc", "src-misoc": "src-misoc",
"src-pythonparser": "src-pythonparser" "src-pythonparser": "src-pythonparser"
} }
}, },
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1719454714,
"narHash": "sha256-MojqG0lyUINkEk0b3kM2drsU5vyaF8DFZe/FAlZVOGs=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "d1c527659cf076ecc4b96a91c702d080b213801e",
"type": "github"
},
"original": {
"owner": "oxalica",
"ref": "snapshot/2024-08-01",
"repo": "rust-overlay",
"type": "github"
}
},
"sipyco": { "sipyco": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@ -113,11 +108,11 @@
"src-migen": { "src-migen": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1724304798, "lastModified": 1721561053,
"narHash": "sha256-tQ02N0eXY5W/Z7CrOy3Cu4WjDZDQWb8hYlzsFzr3Mus=", "narHash": "sha256-z3LRhNmKZrjr6rFD0yxtccSa/SWvFIYmb+G/D5d2Jd8=",
"owner": "m-labs", "owner": "m-labs",
"repo": "migen", "repo": "migen",
"rev": "832a7240ba32af9cbd4fdd519ddcb4f912534726", "rev": "9279e8623f8433bc4f23ac51e5e2331bfe544417",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -1,11 +1,8 @@
{ {
description = "A leading-edge control system for quantum information experiments"; description = "A leading-edge control system for quantum information experiments";
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable; inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-24.05;
inputs.rust-overlay = { inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; };
url = "github:oxalica/rust-overlay?ref=snapshot/2024-08-01";
inputs.nixpkgs.follows = "nixpkgs";
};
inputs.sipyco.url = github:m-labs/sipyco; inputs.sipyco.url = github:m-labs/sipyco;
inputs.sipyco.inputs.nixpkgs.follows = "nixpkgs"; inputs.sipyco.inputs.nixpkgs.follows = "nixpkgs";
inputs.src-pythonparser = { url = github:m-labs/pythonparser; flake = false; }; inputs.src-pythonparser = { url = github:m-labs/pythonparser; flake = false; };
@ -16,25 +13,35 @@
inputs.src-migen = { url = github:m-labs/migen; flake = false; }; inputs.src-migen = { url = github:m-labs/migen; flake = false; };
inputs.src-misoc = { type = "git"; url = "https://github.com/m-labs/misoc.git"; submodules = true; flake = false; }; inputs.src-misoc = { type = "git"; url = "https://github.com/m-labs/misoc.git"; submodules = true; flake = false; };
outputs = { self, nixpkgs, rust-overlay, sipyco, src-pythonparser, artiq-comtools, src-migen, src-misoc }: outputs = { self, nixpkgs, mozilla-overlay, sipyco, src-pythonparser, artiq-comtools, src-migen, src-misoc }:
let let
pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import rust-overlay) ]; }; pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; };
pkgs-aarch64 = import nixpkgs { system = "aarch64-linux"; }; pkgs-aarch64 = import nixpkgs { system = "aarch64-linux"; };
artiqVersionMajor = 9; artiqVersionMajor = 8;
artiqVersionMinor = self.sourceInfo.revCount or 0; artiqVersionMinor = self.sourceInfo.revCount or 0;
artiqVersionId = self.sourceInfo.shortRev or "unknown"; artiqVersionId = self.sourceInfo.shortRev or "unknown";
artiqVersion = (builtins.toString artiqVersionMajor) + "." + (builtins.toString artiqVersionMinor) + "+" + artiqVersionId + ".beta"; artiqVersion = (builtins.toString artiqVersionMajor) + "." + (builtins.toString artiqVersionMinor) + "+" + artiqVersionId;
artiqRev = self.sourceInfo.rev or "unknown"; artiqRev = self.sourceInfo.rev or "unknown";
rust = pkgs.rust-bin.nightly."2021-09-01".default.override { rustManifest = pkgs.fetchurl {
extensions = [ "rust-src" ]; url = "https://static.rust-lang.org/dist/2021-09-01/channel-rust-nightly.toml";
targets = [ ]; sha256 = "sha256-KYLZHfOkotnM6BZd7CU+vBA3w/VtiWxth3ngJlmA41U=";
}; };
rustPlatform = pkgs.makeRustPlatform {
targets = [];
rustChannelOfTargets = _channel: _date: targets:
(pkgs.lib.rustLib.fromManifestFile rustManifest {
inherit (pkgs) stdenv lib fetchurl patchelf;
}).rust.override {
inherit targets;
extensions = ["rust-src"];
};
rust = rustChannelOfTargets "nightly" null targets;
rustPlatform = pkgs.recurseIntoAttrs (pkgs.makeRustPlatform {
rustc = rust; rustc = rust;
cargo = rust; cargo = rust;
}; });
vivadoDeps = pkgs: with pkgs; let vivadoDeps = pkgs: with pkgs; let
# Apply patch from https://github.com/nix-community/nix-environments/pull/54 # Apply patch from https://github.com/nix-community/nix-environments/pull/54
@ -69,14 +76,14 @@
qasync = pkgs.python3Packages.buildPythonPackage rec { qasync = pkgs.python3Packages.buildPythonPackage rec {
pname = "qasync"; pname = "qasync";
version = "0.25.0"; version = "0.24.1";
src = pkgs.fetchFromGitHub { src = pkgs.fetchFromGitHub {
owner = "CabbageDevelopment"; owner = "CabbageDevelopment";
repo = "qasync"; repo = "qasync";
rev = "v${version}"; rev = "v${version}";
sha256 = "sha256-lfH8FNA8cP7dmxR+ihoe2Gr8uOxXHdqn1AhNLIkX5ko="; sha256 = "sha256-DAzmobw+c29Pt/URGO3bWXHBxgu9bDHhdTUBE9QJDe4=";
}; };
propagatedBuildInputs = [ pkgs.python3Packages.pyqt6 ]; propagatedBuildInputs = [ pkgs.python3Packages.pyqt5 ];
nativeCheckInputs = [ pkgs.python3Packages.pytest-runner pkgs.python3Packages.pytestCheckHook ]; nativeCheckInputs = [ pkgs.python3Packages.pytest-runner pkgs.python3Packages.pytestCheckHook ];
disabledTestPaths = [ "tests/test_qeventloop.py" ]; disabledTestPaths = [ "tests/test_qeventloop.py" ];
}; };
@ -132,10 +139,10 @@
export VERSIONEER_REV=${artiqRev} export VERSIONEER_REV=${artiqRev}
''; '';
nativeBuildInputs = [ pkgs.qt6.wrapQtAppsHook ]; nativeBuildInputs = [ pkgs.qt5.wrapQtAppsHook ];
# keep llvm_x and lld_x in sync with llvmlite # keep llvm_x and lld_x in sync with llvmlite
propagatedBuildInputs = [ pkgs.llvm_15 pkgs.lld_15 sipyco.packages.x86_64-linux.sipyco pythonparser llvmlite-new pkgs.qt6.qtsvg artiq-comtools.packages.x86_64-linux.artiq-comtools ] propagatedBuildInputs = [ pkgs.llvm_15 pkgs.lld_15 sipyco.packages.x86_64-linux.sipyco pythonparser llvmlite-new pkgs.qt5.qtsvg artiq-comtools.packages.x86_64-linux.artiq-comtools ]
++ (with pkgs.python3Packages; [ pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial levenshtein h5py pyqt6 qasync tqdm lmdb jsonschema platformdirs ]); ++ (with pkgs.python3Packages; [ pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial levenshtein h5py pyqt5 qasync tqdm lmdb jsonschema ]);
dontWrapQtApps = true; dontWrapQtApps = true;
postFixup = '' postFixup = ''
@ -309,7 +316,7 @@
inherit (pkgs.texlive) inherit (pkgs.texlive)
scheme-basic latexmk cmap collection-fontsrecommended fncychap scheme-basic latexmk cmap collection-fontsrecommended fncychap
titlesec tabulary varwidth framed fancyvrb float wrapfig parskip titlesec tabulary varwidth framed fancyvrb float wrapfig parskip
upquote capt-of needspace etoolbox booktabs pgf pgfplots; upquote capt-of needspace etoolbox booktabs;
}; };
artiq-frontend-dev-wrappers = pkgs.runCommandNoCC "artiq-frontend-dev-wrappers" {} artiq-frontend-dev-wrappers = pkgs.runCommandNoCC "artiq-frontend-dev-wrappers" {}
@ -344,11 +351,9 @@
version = artiqVersion; version = artiqVersion;
src = self; src = self;
buildInputs = with pkgs.python3Packages; [ buildInputs = with pkgs.python3Packages; [
sphinx sphinx_rtd_theme sphinxcontrib-tikz sphinx sphinx_rtd_theme
sphinx-argparse sphinxcontrib-wavedrom sphinx-argparse sphinxcontrib-wavedrom
] ++ [ latex-artiq-manual artiq-comtools.packages.x86_64-linux.artiq-comtools ] ++ [ artiq-comtools.packages.x86_64-linux.artiq-comtools ];
pkgs.pdf2svg
];
buildPhase = '' buildPhase = ''
export VERSIONEER_OVERRIDE=${artiqVersion} export VERSIONEER_OVERRIDE=${artiqVersion}
export SOURCE_DATE_EPOCH=${builtins.toString self.sourceInfo.lastModified} export SOURCE_DATE_EPOCH=${builtins.toString self.sourceInfo.lastModified}
@ -366,11 +371,9 @@
version = artiqVersion; version = artiqVersion;
src = self; src = self;
buildInputs = with pkgs.python3Packages; [ buildInputs = with pkgs.python3Packages; [
sphinx sphinx_rtd_theme sphinxcontrib-tikz sphinx sphinx_rtd_theme
sphinx-argparse sphinxcontrib-wavedrom sphinx-argparse sphinxcontrib-wavedrom
] ++ [ latex-artiq-manual artiq-comtools.packages.x86_64-linux.artiq-comtools ] ++ [ latex-artiq-manual artiq-comtools.packages.x86_64-linux.artiq-comtools ];
pkgs.pdf2svg
];
buildPhase = '' buildPhase = ''
export VERSIONEER_OVERRIDE=${artiq.version} export VERSIONEER_OVERRIDE=${artiq.version}
export SOURCE_DATE_EPOCH=${builtins.toString self.sourceInfo.lastModified} export SOURCE_DATE_EPOCH=${builtins.toString self.sourceInfo.lastModified}
@ -412,14 +415,13 @@
packages.x86_64-linux.vivadoEnv packages.x86_64-linux.vivadoEnv
packages.x86_64-linux.vivado packages.x86_64-linux.vivado
packages.x86_64-linux.openocd-bscanspi packages.x86_64-linux.openocd-bscanspi
pkgs.python3Packages.sphinx pkgs.python3Packages.sphinx_rtd_theme pkgs.pdf2svg pkgs.python3Packages.sphinx pkgs.python3Packages.sphinx_rtd_theme
pkgs.python3Packages.sphinx-argparse pkgs.python3Packages.sphinxcontrib-wavedrom latex-artiq-manual pkgs.python3Packages.sphinx-argparse pkgs.python3Packages.sphinxcontrib-wavedrom latex-artiq-manual
pkgs.python3Packages.sphinxcontrib-tikz
]; ];
shellHook = '' shellHook = ''
export LIBARTIQ_SUPPORT=`libartiq-support` export LIBARTIQ_SUPPORT=`libartiq-support`
export QT_PLUGIN_PATH=${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.dev.qtPluginPrefix}:${pkgs.qt6.qtsvg}/${pkgs.qt6.qtbase.dev.qtPluginPrefix} export QT_PLUGIN_PATH=${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.dev.qtPluginPrefix}:${pkgs.qt5.qtsvg.bin}/${pkgs.qt5.qtbase.dev.qtPluginPrefix}
export QML2_IMPORT_PATH=${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.dev.qtQmlPrefix} export QML2_IMPORT_PATH=${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.dev.qtQmlPrefix}
export PYTHONPATH=`git rev-parse --show-toplevel`:$PYTHONPATH export PYTHONPATH=`git rev-parse --show-toplevel`:$PYTHONPATH
''; '';
}; };

View File

@ -6,17 +6,16 @@ import sys
import versioneer import versioneer
if sys.version_info[:2] < (3, 11): if sys.version_info[:2] < (3, 7):
raise Exception("You need Python 3.11+") raise Exception("You need Python 3.7+")
# Depends on PyQt6, but setuptools cannot check for it. # Depends on PyQt5, but setuptools cannot check for it.
requirements = [ requirements = [
"numpy", "scipy", "numpy", "scipy",
"python-dateutil", "prettytable", "h5py", "lmdb", "python-dateutil", "prettytable", "h5py", "lmdb",
"qasync", "pyqtgraph", "pygit2", "qasync", "pyqtgraph", "pygit2",
"llvmlite", "pythonparser", "levenshtein", "llvmlite", "pythonparser", "levenshtein",
"platformdirs",
] ]
console_scripts = [ console_scripts = [

View File

@ -11,7 +11,7 @@ def get_rev():
""" """
def get_version(): def get_version():
return os.getenv("VERSIONEER_OVERRIDE", default="9.0+unknown.beta") return os.getenv("VERSIONEER_OVERRIDE", default="8.0+unknown")
def get_rev(): def get_rev():
return os.getenv("VERSIONEER_REV", default="unknown") return os.getenv("VERSIONEER_REV", default="unknown")