Last active
January 7, 2026 11:29
-
-
Save lastforkbender/ef684691c7bf5bac798517ef07efbdb6 to your computer and use it in GitHub Desktop.
Cecil+ IDE / Spectral Multi-dispatch GUI Works
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Cecil+ Programme Lang /IDE /PyQt5 - Spectral Multi-Dispatching GUI Works - /Py /C /C++ /C# | |
| # | |
| #____________________________________________________________________________________________________________________ | |
| # | |
| # CECIL+ CORE FEATURES/SERVICES: | |
| # | |
| # Spectral Multi-Dispatch(SMD) allows dispatch merges along multiple orthogonal axes(type, capability, performance | |
| # classing, resource constraints and much more. A LLM-AI using the Cecil+ language can more easily assemble large | |
| # systems like a game engine by composing small, focused handlers and letting the dispatch system select best-fit | |
| # implementations at runtime or compile time to C. Those technical parsing mechanisms include a built-in AI system | |
| # for Cecil+ SMD composition, Kraken. Below is a more expanded detail of these circumstances: | |
| # | |
| # (1) Spectrum as multi-dimensional capability key | |
| # | |
| # Every service operation is indexed by a vector of axes - e.g. enity type, precision scope, memory budget, | |
| # feature flags, pipe render passes, deterministic/non-deterministic class ordering and etc. The SMD composes | |
| # the most appropriate handlers matching the full spectrum instead of requiring an exact tuple for every | |
| # combination. Handlers can inherit or extend other handlers along specific axes; or as ordered composition | |
| # hooks, weights specific per-axis controlled by the programmer thru keywords such as spawn, migrate and pipe. | |
| # This allows next-gen variable(s) priority with degradation inside matching dedicated handlers with scoring. | |
| # | |
| # (2) Spectrum composability & layered separations | |
| # | |
| # Cecil+ model is built upon deep layering cross-cutting capabilities such as direct/indirect logging, state- | |
| # sync with function params and network transport with piped variable access renderings. With complicated sys | |
| # designs, whereof complex incremental assembled multi-dispatching exist, dedictaed handlers implement those | |
| # concerns without having to duplicate the semantics to class structuring. A already running configuration, | |
| # without having to create every class/function params permutations on the base SMD configurations re-mapped. | |
| # Of this, Kraken's features, specialized variant constraints(registry) easily locates parameter deterministic | |
| # scope and resolve param mishaps via custom spectral object(s) or generating it's own from current objects. | |
| # | |
| # (3) Spectrum parallelism & independent coupling | |
| # | |
| # Instead of coupling concrete types authors, or a LLM-AI can focus upon subsystems independently with spectral | |
| # multi-dispatching. Whereas SMD core registry system composes them @ integration time chosen. Cecil+ keywords | |
| # with spectral swap performance, member, link and superlink always included of a class declaring. Resolving | |
| # profiling & adaptation on the axes or axes slices unit integrations with dispatch loaders. Allowing efficient | |
| # scoring cache dynamics, annotations extension functions/classes within default values already mapped and per | |
| # dimensional indexing vs method-chain composition resolutions more specialized to ahead-of-time error avoiding | |
| # and performance coverage of critical axes fallbacks where needed. | |
| from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QWidget, QPlainTextEdit, QTreeView, | |
| QFileSystemModel, QAction, QFileDialog, QMessageBox, QTabWidget, QHBoxLayout, | |
| QLabel, QDialog, QLineEdit, QPushButton, QToolBar, QTextEdit, QStyle, QMenu, | |
| QUndoStack, QUndoCommand, QCheckBox, QComboBox, QGridLayout, QSpacerItem, | |
| QSizePolicy, QListView, QFrame, QAbstractItemView, QTableView, QSplitter, | |
| QToolButton, QListWidget, QListWidgetItem, QHeaderView, QStyledItemDelegate, | |
| QStackedWidget, QProgressBar, QWidgetAction, QTabBar, QStyleOptionButton, | |
| QGraphicsDropShadowEffect, QStatusBar, QScrollArea, QShortcut, QActionGroup) | |
| from PyQt5.QtGui import (QSyntaxHighlighter, QTextCharFormat, QFont, QColor, QTextFormat, QTextCursor, QPainter, | |
| QPalette, QBrush, QLinearGradient, QIcon, QTextDocument, QStandardItemModel, QStandardItem, | |
| QPixmap, QImage, QKeySequence, QFontMetrics, QTextCharFormat, QPen, QPainterPath, | |
| QIntValidator, QPolygon, QPolygonF, QTransform, QPaintEvent, QCursor) | |
| from PyQt5.QtCore import (QRegularExpression, Qt, QRect, QRectF, QSize, QFileInfo, pyqtSignal, QObject, QTimer, QPoint, | |
| QStringListModel, QEvent, QDir, QModelIndex, QThreadPool, QRunnable, pyqtSlot, pyqtProperty, | |
| QPropertyAnimation, QEasingCurve, QCoreApplication) | |
| from datetime import datetime, timezone, timedelta | |
| from typing import List, Optional | |
| from functools import lru_cache | |
| from pathlib import Path | |
| import numpy as np | |
| import zoneinfo | |
| import base64 | |
| import math | |
| import time | |
| import gzip | |
| import json | |
| import sys | |
| import os | |
| import re | |
| ICO_ICO, CPL_ICO, INDENT = {}, {}, ' ' * 4 | |
| KEYWORDS = ["if", "elif", "else", "loop", "return", "break", "continue", "match", "case", "migrate", "func", | |
| "let", "var", "const", "import", "multiport", "pipe", "operator", "vector", "spawn", "member", | |
| "link", "superlink"] | |
| CTRL_KEYWORDS = {"if", "elif", "else", "loop", "break", "continue", "match", "case", "migrate", "pipe"} | |
| PALETTE = {"keyword": "#7F7F77", "keyword2": "#F50C25", "type": "#BBBB00", "string": "#AAFB88", | |
| "comment": "#AA8B88", | |
| "number": "#59E9EE", "line_highlight": "#505050", "line_number_bg": "#505050", | |
| "line_number_fg": "#7F7F77", | |
| "editor_bg": "#535353", "tab_active": "#59E9EE"} | |
| STRICT_TYPES = ["Int", "Float", "Complex", "String", "Bool", "Array", "Null"] | |
| MENU_STYLESHEET = """ | |
| QMenu {padding: 6px; margin: 2px; background: #FBFCFD; border: 0px solid rgba(0, 0, 0, 0.12); border-radius: 0px; color: #373737} | |
| QMenu::icon {width: 36px; margin-left: 6px;} | |
| QMenu { | |
| background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #59E9EE, stop:0.06 #E9EEF6, stop:0.06 #FBFCFD, stop:1 #FBFCFD)} | |
| QMenu::item {padding: 6px 12px 6px 6px; spacing: 4px;} | |
| QMenu::item:selected { | |
| background: qlineargradient(x:1.0, y1:0, x2:0, y2:1, stop:0 rgba(67, 177, 177, 0.21), stop:8 rgba(67, 188, 188, 0.06)); color: #00AAAA; | |
| margin: 1px; border: 0px solid rgba(0, 0, 0, 0.4); border-radius: 4px} | |
| QMenu::separator {height: 1px; background: rgba(0, 0, 0, 0.06); margin: 6px 4px;}""" | |
| DLG_STYLE_SHEET = """ | |
| QDialog QPushButton{border: 0px solid #111; border-radius: 4px; padding: 5px 12px; | |
| background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #59E9EE, stop:1 #575757);} | |
| QDialog QPushButton:hover {background: #F3F3F3; color: #535353; border: 1px solid #595959;} | |
| QDialog QPushButton:pressed {background: #E6E6E6; color: #505050; padding-top: 7px;} | |
| QListView::item:hover {background: #636363} QListView::item:selected {background: #535353}""" | |
| MDL_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| OPN_CFG_PTH = Path(MDL_DIR) / 'Cecil+' / 'ide-settings' / 'opn_cfg.cfg' | |
| FND_CFG_PTH = Path(MDL_DIR) / 'Cecil+' / 'ide-settings' / 'fnd_cfg.cfg' | |
| FND_LST_PTH = Path(MDL_DIR) / 'Cecil+' / 'ide-settings' / 'fnd_lst.cfg' | |
| RPL_CFG_PTH = Path(MDL_DIR) / 'Cecil+' / 'ide-settings' / 'rpl_cfg.cfg' | |
| TURQUOISE, BG_GRAY, PANEL_GRAY, LIGHT_GRAY, WHITE = "#59E9EE", "#2F3236", "#3B3F43", "#CFCFCF", "#FFFFFF" | |
| def update_cecil_plus_dirs(): | |
| fldr_lst = ['Cecil+', '32-bin', '64-bin', '3q-bin', 'ide-settings', 'error-logs', 'lang-bindings', | |
| 'lang-definitions', | |
| 'lang-task-described', 'lang-exposure', 'lang-task-undescribed', 'lang-segments-assignments', | |
| 'lang-segments-classes', 'lang-segments-methods', 'lang-spectral-notations', | |
| 'lang-spectral-orthonormals', | |
| 'lang-spectral-properties', 'lang-spectral-combinations', 'kraken-ai-allied-scaling', | |
| 'kraken-ai-allied-attainments', 'kraken-ai-allied-expressions', 'kraken-ai-allied-constants', | |
| 'kraken-ai-allied-sqtpp-disc'] | |
| pth = Path(MDL_DIR) / 'Cecil+' | |
| if not os.path.isdir(pth): os.makedirs(pth) | |
| for fldr in fldr_lst: | |
| pth = Path(MDL_DIR) / 'Cecil+' / fldr | |
| if not os.path.isdir(pth): os.makedirs(pth) | |
| update_cecil_plus_dirs() | |
| def get_cecil_plus_fldr_path(fldr): | |
| cp = 'Cecil+' | |
| if fldr == '32-bin': return Path(MDL_DIR) / cp / '32-bin' | |
| elif fldr == '64-bin': return Path(MDL_DIR) / cp / '64-bin' | |
| elif fldr == '3q-bin': return Path(MDL_DIR) / cp / '3q-bin' | |
| elif fldr == 'ide-settings': return Path(MDL_DIR) / cp / 'ide-settings' | |
| elif fldr == 'error-logs': return Path(MDL_DIR) / cp / 'error-logs' | |
| elif fldr == 'lang-bindings': return Path(MDL_DIR) / cp / 'lang-bindings' | |
| elif fldr == 'lang-definitions': return Path(MDL_DIR) / cp / 'lang-definitions' | |
| elif fldr == 'lang-task-described': return Path(MDL_DIR) / cp / 'lang-task-described' | |
| elif fldr == 'lang-exposure': return Path(MDL_DIR) / cp / 'lang-exposure' | |
| elif fldr == 'lang-task-undescribed': return Path(MDL_DIR) / cp / 'lang-task-undescribed' | |
| elif fldr == 'lang-segments-assignments': return Path(MDL_DIR) / cp / 'lang-segments-assignments' | |
| elif fldr == 'lang-segments-classes': return Path(MDL_DIR) / cp / 'lang-segments-classes' | |
| elif fldr == 'lang-segments-methods': return Path(MDL_DIR) / cp / 'lang-segments-methods' | |
| elif fldr == 'lang-spectral-notations': return Path(MDL_DIR) / cp / 'lang-spectral-notations' | |
| elif fldr == 'lang-spectral-orthonormals': return Path(MDL_DIR) / cp / 'lang-spectral-orthonormals' | |
| elif fldr == 'lang-spectral-properties': return Path(MDL_DIR) / cp / 'lang-spectral-properties' | |
| elif fldr == 'lang-spectral-combinations': return Path(MDL_DIR) / cp / 'lang-spectral-combinations' | |
| elif fldr == 'kraken-ai-allied-scaling': return Path(MDL_DIR) / cp / 'kraken-ai-allied-scaling' | |
| elif fldr == 'kraken-ai-allied-attainments': return Path(MDL_DIR) / cp / 'kraken-ai-allied-attainments' | |
| elif fldr == 'kraken-ai-allied-expressions': return Path(MDL_DIR) / cp / 'kraken-ai-allied-expressions' | |
| elif fldr == 'kraken-ai-allied-constants': return Path(MDL_DIR) / cp / 'kraken-ai-allied-constants' | |
| elif fldr == 'kraken-ai-allied-sqtpp-disc': return Path(MDL_DIR) / cp / 'kraken-ai-allied-sqtpp-disc' | |
| def read_file(path): | |
| src = None | |
| with open(path, 'r', encoding='utf-8') as f: src = f.read() | |
| return src | |
| def write_file(path, text): | |
| with open(path, 'w', encoding='utf-8') as f: f.write(text) | |
| def cecil_plus_open_files(parent=None, start_dir=None): | |
| dlg = IDEOpenDialogHorizontal() | |
| if dlg.exec_() == QDialog.Accepted: | |
| pass | |
| def _screen_for_point(pt): | |
| app = QApplication.instance() | |
| try: | |
| scr = app.screenAt(pt) | |
| if scr is None: scr = app.primaryScreen() | |
| except Exception: | |
| scr = app.primaryScreen() | |
| return scr | |
| def build_unicode_icon_png(size, char, c_str): | |
| pix = QPixmap(size, size) | |
| pix.fill(QColor(Qt.transparent)) | |
| painter = QPainter(pix) | |
| painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing) | |
| font = QFont("Segoe UI Symbol") | |
| font.setPixelSize(size) | |
| font.setWeight(QFont.Normal) | |
| painter.setFont(font) | |
| rect = QRect(0, 0, size, size) | |
| painter.setPen(QPen(QColor(c_str))) | |
| painter.drawText(rect, Qt.AlignCenter, char) | |
| painter.end() | |
| return pix | |
| def save_unicode_ico_png(ico_pth, ico_nm, sz, uni_c, c_str): | |
| ico_pth_ico = os.path.join(ico_pth, ico_nm) | |
| pix = build_unicode_icon_png(sz, uni_c, c_str) | |
| pix.save(ico_pth_ico, "PNG") | |
| def recolor_page_icon_for_treeview(): | |
| src_pix = ICO_ICO["page"] | |
| dst = QPixmap(src_pix.size()) | |
| dst.fill(Qt.transparent) | |
| p = QPainter(dst) | |
| p.drawPixmap(0, 0, src_pix) | |
| p.setCompositionMode(QPainter.CompositionMode_SourceIn) | |
| p.fillRect(dst.rect(), QColor(255, 255, 255)) | |
| p.end() | |
| return dst | |
| def _pixmap_from_chunks(chunks): | |
| gz = base64.b64decode("".join(chunks)) | |
| png_bytes = gzip.decompress(gz) | |
| pix = QPixmap() | |
| pix.loadFromData(png_bytes) | |
| return pix | |
| def preload_cpl_icons(): | |
| CPL_B64 = {"ki_resolver": ("H4sIAAAAAAAC/wE9DMLziVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IA", | |
| "rs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAvSSURBVGhDtVoJU1RX", | |
| "FvafRRBRccEFR01ck2iNmdKUYzI1mcliqpyZmkxi1aScsUboBZBFcKF3abpBFgUUBUVBZVFUXJCG", | |
| "pun19fJNfef1bRFRwCan6lS/5b77zne2e+55vWx5sRXLS+rm5Y+K3+TlM5jnK41WYRlfXIc8g35O", | |
| "zi2xCKtz8goD2fIW87kCs12Yx+T1ZQ4UVVySX3WPvNJkwzIO6B+bxMOJgM6TAQxPBPB0Kogn/mk8", | |
| "C4QwGgjJbyAaQyCqyfVXoQh8oQjGghFMRKIy/tFkQMbxvmJfKIqwFsdUNIaXwTAe+6cxFgzjxXQ4", | |
| "/RvCZDgKXziK59MhuT4w7s8wZRqemEL/2ARGJqcx7PPjwbhfrhWWO7Esp8SCQd8UxkMRTEU1BGIa", | |
| "JiIxvAiEZMKgpiEaTyCWSCKeJKegpY+TqRQSKWSuReIJxBNJGZtIppBMQX51TiKixeGPxASUPxKV", | |
| "Xwo+HdMQ0jQBOcn74agcU0k8nwhHMRmJyjkVRjkJLM9gwTKa3BeO6MKIIAkBEU0k8NQ/LZNTsFgi", | |
| "IYLymFbQkkkkBEAKKQCplM5JzpFMCUA+Q56OxeVaMEYAUbyc1jU/FYkhGNMElCgnkUwrJyXPUQZ5", | |
| "RzKJaCKJkBYXOWPxBIZ8U8g11BGABS8DIRkcSSTFNGU376Hl4TMRlNfDmiYPcmJqIBCJCQABkbEE", | |
| "X5pEWEuI9mg1ugZdaiwUETeilUW7AiIkiiMAKkXNx+eoSGVhKoVzR+JxkYVjCPjBq0nkGut0FxoP", | |
| "RvA8EBQBKPR3DZ3Yc84L54MRPPYHMRGOIRyPi8aUielm1BYnFSG0OIIaX5IUq0U1WkzXHEGSOT8t", | |
| "QSEJKBxPyFw85j1eF04fK9ekNRhvMi7tAVQkg14AMAAZvDT1dFTDqY472FXrwd5zXoxNh8Q/5QVp", | |
| "s3JyTqLMTtNSMwQSimmiaYJQ12gRao3XlJLo10wKmXhJA+PY8XBEhCZAPsf5Kby4mLiiJoBWGNMA", | |
| "GEyitXhCzMlssrWqATvPenDiyi30PBvDVCwmZuRE1BD9OSI+rvstn+fLtfQcYbmeQIjj0teeB0KZ", | |
| "rES30N8Xl7n0QNaP+QyVwHOJhbRlOJ+KD95bZbbpABhYupmZOVKIp1JoHh7Fl/Y2HHVcQd9Ln2hL", | |
| "XpA2d0TTJ6V/030kU8UT8mJmsGHfFKpuD6Kkqx+ewScZN2E2eRnU3SGUnoNMy5MpHMHqCtUtS8H5", | |
| "XlpDeQIzpLgQsxD9WWUNPiCBk0ziZHsvdtd68a+WbpmcWvNH9aBT6VEsQS2mzcxc3zc2IXF00NKC", | |
| "A3XN+Ka+A9W3BiTvK20TKONJYieWVoqyXjyRDmA9w9F1lIJJPKbVJY3SAu8iAmMwMxZqbg/IiwmU", | |
| "rkKBKbieYvXsQwCjU0F8YW3B/gtN2Fnjwb5aLw5cvIyDdc0SZwslSc/UJFM0gCQgCiYGCs+kklOS", | |
| "zkLvImrheGOXmGrPea8IPx8xbRIAl/4tFZewrswpQHjOFX4xxLeRleZn07wASDT73lov8o02nLp2", | |
| "d/btt4ha+sFzXQTeWO6U30/ONmBHtXvRAOajNSqI30cEwFhYZbRhQ6lD3GQ+OtV5RwTfXuXGxjMu", | |
| "bD7jxHcN15YcwLwxQIpqcdwbm8TJjl7kGa3Yf7EJkfj7Xck78AR7zjViXalDqtACkw1HXVcRW4AL", | |
| "LoZyFwKARBC3no9jtcmGnOI6fO26gurbA/AOjcJ0o1+Yx03DOlf1PJA4WGOyYaXRhnXlThxrvI7B", | |
| "cb/E0dwevXhiWb4gACT69o2nrySgqdX1pQ6sNTuQb7Qi32jBlsp6HHVe1TOOpQWHbK0SxCzXN59x", | |
| "YXu1G7+rqsf5O0OSkpeC1pY6Fg6AxPx7xHFFrLC7xoOD1hYJTgYrfZ0WUpsXri8Et7bcgcIyp4wp", | |
| "MNsE1OXhUcn/2RJdelEASK0Pn2G12Ya1pXYJ1vqBEfy34w5Krvfh+4ZO7DvfKHFSVFmPglIH8owW", | |
| "bChzYo3ZjnVldmyuuIQ/WJplnmyJcy8aADVn6uoXENuq3bKgMD7IN0bH5N439e0iMC2ypZJrgQOr", | |
| "DFZsr6zH5gqXpFX3wJPZUy+aPggAidr7wXMNq0w2CWgWWKTuZ68kuLdS0DMufHzWg9WlTuQaLeKv", | |
| "3Mcyd3Nve6pz/jVlPpIsxA3NYolWKO++j21VbtH0Mc81/K3phmQepk9qnkKyJOd6IBYw22UtoVsV", | |
| "mB044mjLOpjFAuwofAgpEMwyKitR2N21HikdtlZcEisdtrWIRej/BEar8Zl1pXacztIKWQEgEQSF", | |
| "pkDMQr+298J+f0TWghF/UH7t90aw91wjNpa7kG+0SwZjS2WV0SrjsyFxoWwAkA7b2iQ1kufKLP9p", | |
| "75UVmbUU44DWWkE2WPBHR1smfj6EONcyVnTZEK3w2fkmiYWLd4ffEujs7UERnjGxlgFdYpH8nW+y", | |
| "Yf/F7NKpVKNcjrMltTYww7CjMROEe+AxVhr0jl2+yYocg0UCOtdglZgovt73FuiFEudaRo1kS7TC", | |
| "Py7fEO0ySJuHda1SsL+6O0TbqiXJl+aW2JB7Wm8/7j/f+MFWoFI+aB2Yi6QCrfGA8zHfT8cS+Km1", | |
| "GzuqGyTQcw02MGXzPoHSYnklVgH876u981a4c9GSAqAV2ABQDd5Pz3lFeMYGA5Z1ERtRtMCaUjtW", | |
| "mx1yne/nM7+09izalXJUZ26pqHHgsWhfWWEtO8gGK/JVp9pkRZ7Rhi1V9Xrxxw600Srjt5xxiRUX", | |
| "Q5KFsk2jM4lW+NbdKQJTKLbZ+RIWc+vLXbLwFFa4ZE3gHmEVS/JSJwpMOpBPLzQuygoSxEsJgGS5", | |
| "OyQb+RUl1kymYU3EvQArWGagTdLrd2JHTQOKquqxtfKStMqLqtz4ynV1wSCWHMD1p2M4bGtFkewN", | |
| "rFhjdkgJ8UmtG59faMKuGo+sB5sqXdhW2YB9tY1ybUO5S+KhsMz+zgVxLlpyACwdWNBtKHfqBR3d", | |
| "w2zHIVsLynru4++Xb+BPrqsoqnDj4+oG/OjtQuXNARyytkqwc5POrPTn+o7ZU89JvwkAVqMUhi7C", | |
| "XRs1yjT6S2u3lNsNg0+wSUrtBhyouwzbvYfSpdt5tiGzXWXAMw3PR0sO4C/uDglYapFC1/YO4Ii9", | |
| "TY7ZqePnpe5n4zje1CUx8ZWzHY57j+XZEd+UVLQsDz4qsUgJPh8tOYAvLC2icVabLKlpEaZGtiYV", | |
| "CDaNf27txp5ar2w9jzffzHTeLvYOievxea7SP7e8f21YUgDcH392oQkFJmYeO/7ZeivTvDV19ckq", | |
| "TRDO+49gvtmPzy948b9rd1Hc2SdfhUhs33xb3yEpdYWRq7QVx7zX3ulOSwaAvs3dGZkuwBLhRNut", | |
| "zH0Kxq0mg3ZoMgDv4BNY+4fR93ISV0ZeyKcoRSy/d9V69QWuWO9s/L7uMnpf+KThO5OWBIBn8CmK", | |
| "Ki8hp/hi5rsxXeBEa88b49jQ4mch9T2NX1n4VYbfHWZuLZlCv3S0SXeD2YzJgG7JTkbX07E35pRa", | |
| "KBsAPc/H8Wv77cy2koKvL3fg+8Yu2ZEtlNi2V0Rr0aK1twdQ0fMAJ9vvoK7vIbzDo+KSMylrADTr", | |
| "T83doqnCMy587bqKKyPPZw/7zUg2NASgPrYplv7lLH+bizhuaGIKt1745LPnoM+PR5NB+Q48F+nf", | |
| "zGZ+ONc/3fLTFN1psZQBwDqE+9bCclc6j7ODoP8fgdcU0x/X8J7RJsytIpu3eQae6/9t4CLGpq7e", | |
| "2NWrUHXMAo/vU2mSm3LGDK/r/53Qr2f+UyHliE12ciwE1ULH52Q+KaeL62TR4CDW6RyoVsOZrMpe", | |
| "2YykBZI/Z0j9r/dC+at6o4p5n2Nz038C4RzMLJyDymBlyppJFGJ4vQaouakQFoGzmQrPAOCEfIAv", | |
| "4ipKzRIQX6KYdX1OWkuzwc1meYZ7gTRwdZ2AKOQ6s1M624p1MGmrcf408zobwjOtqpj3cqQvdFql", | |
| "P5pTN+n7+fUYPqueV8f6uQU5p+uwPDP3zL/ovH6ePkwWC856T2Y+Oec8r5nP0I147//H8JcLQkKo", | |
| "ZwAAAABJRU5ErkJgghHm+b09DAAA")} | |
| names = list(CPL_B64.keys()) | |
| for n in names: | |
| chunks = CPL_B64.get(n) | |
| pix = _pixmap_from_chunks(chunks) | |
| CPL_ICO[n] = QIcon(pix) | |
| CPL_B64 = None | |
| def build_unicode_icon_pngs(): | |
| unicode_tpl_lst = [("page", "\U0001F4C4"), ("folder", "\U0001F4C1"), ("open_folder", "\U0001F4C2"), | |
| ("save", "\U0001F4BE"), ("close", "\u274C"), ("stop", "\u23F9"), ("undo", "\u21B6"), | |
| ("redo", "\u21B7"), ("cut", "\u2702"), ("clipboard", "\U0001F4CB"), ("search", "\U0001F50D"), | |
| ("paste", "\U0001F501"), ("checkbox", "\u2611"), ("laptop", "\U0001F4BB"), | |
| ("package", "\U0001F4E6"), ("next", "\u25B6"), ("previous", "\u25C0"), ("goto", "\u2192"), | |
| ("enter", "\u23CE"), ("bug", "\U0001F41B"), ("fast_forward", "\u23E9"), ("triangle", "\u25B7"), | |
| ("rewind", "\u23EA"), ("halt", "\u26D4"), ("hammer_wrench", "\U0001F6E0"), | |
| ("wrench", "\U0001F527"), ("memo", "\U0001F4DD"), ("microscope", "\U0001F52C"), ("up_arrow", "\u2B06"), | |
| ("down_arrow", "\u2B07"),("pencil", "\u270F"), ("bubble", "\U0001F4AC"), ("gear", "\u2699"), | |
| ("question", "\u2753"), ("info", "\u2139"), ("tag", "\U0001F516"), ("star", "\u2B50"), | |
| ("small_arrow_forward", "\u27A1"), ("small_arrow_backward", "\u2B05"), ("page_curl", "\U0001F4C3"), | |
| ("receipt", "\U0001F9FE"), ("warning", "\u26A0"), ("refresh", "\U0001F504"), ("link", "\U0001F517"), | |
| ("check", "\u2714"), ("exit", "\u2716"), ("edit", "\u270E"), ("add", "\u2795"), ("remove", "\u2796"), | |
| ("delete", "\U0001F5D1"), ("bullet", "\u2022"), ("dot", "\u25CF"), ("circle", "\u25CB"), | |
| ("square", "\u25A0"), ("triangle_up", "\u25B2"), ("triangle_down", "\u25BC"), | |
| ("ellipsis", "\u22EF"), ("keyboard", "\u2328"), ("grid", "\U0001F4C5"), ("user", "\U0001F464"), | |
| ("small_star", "\u2605")] | |
| ico_pth = Path(MDL_DIR) / 'Cecil+' / 'ide-settings' / 'ico-ico' | |
| os.makedirs(ico_pth) | |
| for u in unicode_tpl_lst: save_unicode_ico_png(ico_pth, f"{u[0]}.png", 32, u[1], "#59E9EE") | |
| for u in unicode_tpl_lst: save_unicode_ico_png(ico_pth, f"gray_{u[0]}.png", 32, u[1], "#999999") | |
| def create_cecil_plus_page_icons(): | |
| src_pix = recolor_page_icon_for_treeview() | |
| ltr_color = QColor("#424242") | |
| src_ltr = ("Q+", "P+", "S+") | |
| cpl_page_icons = [QIcon(src_pix)] | |
| for i in range(3): | |
| dst = QPixmap(src_pix.size()) | |
| dst.fill(Qt.transparent) | |
| p = QPainter(dst) | |
| p.drawPixmap(0, 0, src_pix) | |
| p.setPen(ltr_color) | |
| font = QFont("Segoe UI") | |
| font.setBold(True) | |
| font.setPointSize(int(21 * 0.55)) | |
| p.setFont(font) | |
| rect = dst.rect() | |
| p.drawText(rect, Qt.AlignCenter, src_ltr[i]) | |
| p.end() | |
| cpl_page_icons.append(QIcon(dst)) | |
| return cpl_page_icons | |
| def addMenuDivider(menu, text): | |
| x = QAction(text, menu) | |
| x.setEnabled(False) | |
| x.setSeparator(False) | |
| x.setToolTip('') | |
| menu.addAction(x) | |
| return x | |
| if os.path.isfile(FND_CFG_PTH): | |
| pth = read_file(FND_CFG_PTH).split(':)^(:') | |
| if len(pth[1]) > 0 and len(pth[2]) > 0: | |
| write_file(FND_CFG_PTH, f'closed:)^(:{pth[1]}:)^(:{pth[2]}') | |
| else: write_file(FND_CFG_PTH, f'closed:)^(:null:)^(:null') | |
| else: write_file(FND_CFG_PTH, f'closed:)^(:null:)^(:null') | |
| class FoldRegion: | |
| def __init__(self, start, end, kind): | |
| self.start = start | |
| self.end = end | |
| self.kind = kind | |
| self.collapsed = False | |
| self._id = f"{start}:{end}:{kind}" | |
| class FoldManager: | |
| def __init__(self, editor): | |
| self.editor = editor | |
| self.regions = [] | |
| self._map = {} | |
| self._collapsed_ids = set() | |
| def clear(self): | |
| self.regions.clear() | |
| self._map.clear() | |
| def rebuild(self): | |
| try: | |
| doc = self.editor.document() | |
| self.clear() | |
| block = doc.firstBlock() | |
| bnum = 0 | |
| while block.isValid(): | |
| text = block.text() | |
| stripped = text.strip() | |
| parts = stripped.split() | |
| if stripped.startswith('func'): | |
| seeker = block | |
| seeker_num = bnum | |
| found_open = False | |
| depth = 0 | |
| found_end = None | |
| while seeker.isValid(): | |
| line = seeker.text() | |
| for ch in line: | |
| if ch == '{': | |
| found_open = True | |
| depth += 1 | |
| elif ch == '}' and found_open: | |
| depth -= 1 | |
| if depth == 0: | |
| found_end = seeker_num | |
| break | |
| if found_end is not None: | |
| break | |
| seeker = seeker.next() | |
| seeker_num += 1 | |
| if found_open and found_end is not None and found_end > bnum: | |
| r = FoldRegion(bnum, found_end, 'func') | |
| self._register_region(r) | |
| block = block.next() | |
| bnum += 1 | |
| for r in self.regions: | |
| if r._id in self._collapsed_ids: | |
| r.collapsed = True | |
| self._apply_visibility_for_region(r) | |
| except Exception: | |
| pass | |
| def _register_region(self, region): | |
| self.regions.append(region) | |
| self._map[region.start] = region | |
| def region_for_block(self, block_num): | |
| return self._map.get(block_num) | |
| def toggle_region_at_block(self, block_num, recursive=False): | |
| r = self.region_for_block(block_num) | |
| if not r: | |
| return | |
| if recursive: | |
| if r.collapsed: | |
| self._expand_recursive(r) | |
| else: | |
| self._collapse_recursive(r) | |
| else: | |
| if r.collapsed: | |
| self._expand(r) | |
| else: | |
| self._collapse(r) | |
| self._update_persisted() | |
| self._finalize() | |
| def fold_all(self): | |
| for r in self.regions: | |
| if not r.collapsed: | |
| self._collapse(r) | |
| self._update_persisted() | |
| self._finalize() | |
| def unfold_all(self): | |
| for r in self.regions: | |
| if r.collapsed: | |
| self._expand(r) | |
| self._update_persisted() | |
| self._finalize() | |
| def _collapse(self, region): | |
| doc = self.editor.document() | |
| for i in range(region.start + 1, region.end + 1): | |
| b = doc.findBlockByNumber(i) | |
| if b.isValid(): | |
| b.setVisible(False) | |
| b.setLineCount(0) | |
| region.collapsed = True | |
| def _expand(self, region): | |
| doc = self.editor.document() | |
| for i in range(region.start + 1, region.end + 1): | |
| b = doc.findBlockByNumber(i) | |
| if b.isValid(): | |
| b.setVisible(True) | |
| region.collapsed = False | |
| def _collapse_recursive(self, outer_region): | |
| for r in self.regions: | |
| if r.start > outer_region.start and r.end <= outer_region.end: | |
| if not r.collapsed: | |
| self._collapse(r) | |
| if not outer_region.collapsed: | |
| self._collapse(outer_region) | |
| def _expand_recursive(self, outer_region): | |
| for r in self.regions: | |
| if r.start >= outer_region.start and r.end <= outer_region.end: | |
| if r.collapsed: | |
| self._expand(r) | |
| if outer_region.collapsed: | |
| self._expand(outer_region) | |
| def _apply_visibility_for_region(self, region): | |
| if region.collapsed: | |
| self._collapse(region) | |
| else: | |
| self._expand(region) | |
| def _update_persisted(self): | |
| self._collapsed_ids = {r._id for r in self.regions if r.collapsed} | |
| def _finalize(self): | |
| doc = self.editor.document() | |
| doc.markContentsDirty(0, doc.characterCount()) | |
| self.editor.updateVisibleRangeHighlight() | |
| if hasattr(self.editor, 'highlighter'): | |
| try: | |
| self.editor.highlighter.invalidate_cache_from(0) | |
| except Exception: | |
| pass | |
| class CecilPlusHighlighterQuantum(QSyntaxHighlighter): | |
| def __init__(self, document): | |
| super().__init__(document) | |
| self.keywordFormat = QTextCharFormat() | |
| self.keywordFormat.setForeground(QColor(PALETTE["keyword"])) | |
| self.keywordFormat.setFontWeight(QFont.Bold) | |
| self.keywordFormat2 = QTextCharFormat() | |
| self.keywordFormat2.setForeground(QColor(PALETTE["keyword2"])) | |
| self.keywordFormat2.setFontWeight(QFont.Bold) | |
| self.typeFormat = QTextCharFormat() | |
| self.typeFormat.setForeground(QColor(PALETTE["type"])) | |
| self.typeFormat.setFontWeight(QFont.Bold) | |
| self.stringFormat = QTextCharFormat() | |
| self.stringFormat.setForeground(QColor(PALETTE["string"])) | |
| self.commentFormat = QTextCharFormat() | |
| self.commentFormat.setForeground(QColor(PALETTE["comment"])) | |
| self.commentFormat.setFontItalic(True) | |
| self.numberFormat = QTextCharFormat() | |
| self.numberFormat.setForeground(QColor(PALETTE["number"])) | |
| self.rules = [] | |
| for kw in KEYWORDS: | |
| rx = QRegularExpression(r"\b" + kw + r"\b") | |
| fmt = self.keywordFormat2 if kw in CTRL_KEYWORDS else self.keywordFormat | |
| self.rules.append((rx, fmt)) | |
| for t in STRICT_TYPES: | |
| rx = QRegularExpression(r"\b" + t + r"\b") | |
| self.rules.append((rx, self.typeFormat)) | |
| self.rules.append((QRegularExpression(r'"[^"\\]*(\\.[^"\\]*)*"'), self.stringFormat)) | |
| self.rules.append((QRegularExpression(r"'[^'\\]*(\\.[^'\\]*)*'"), self.stringFormat)) | |
| self.rules.append((QRegularExpression(r"\b\d+(\.\d+)?\b"), self.numberFormat)) | |
| self.rules.append((QRegularExpression(r"//[^\n]*"), self.commentFormat)) | |
| self.start_comment_rx = QRegularExpression(r"/\*") | |
| self.end_comment_rx = QRegularExpression(r"\*/") | |
| self._block_state_cache = {} | |
| def highlightBlock(self, text): | |
| self._highlight_one_block(self.currentBlock(), text) | |
| def _highlight_one_block(self, block, text): | |
| try: | |
| block_num = block.blockNumber() | |
| setFormat = self.setFormat | |
| for rx, fmt in self.rules: | |
| it = rx.globalMatch(text) | |
| while it.hasNext(): | |
| m = it.next() | |
| setFormat(m.capturedStart(), m.capturedLength(), fmt) | |
| prev_state = 0 | |
| if block_num > 0: | |
| prev_state = self._block_state_cache.get(block_num - 1, 0) | |
| if prev_state != 1: | |
| start_match = self.start_comment_rx.match(text) | |
| start_idx = start_match.capturedStart() if start_match.hasMatch() else -1 | |
| else: start_idx = 0 | |
| new_state = 0 | |
| while start_idx >= 0: | |
| end_match = self.end_comment_rx.match(text, start_idx) | |
| if end_match.hasMatch(): | |
| end_index = end_match.capturedStart() | |
| length = end_index - start_idx + end_match.capturedLength() | |
| setFormat(start_idx, length, self.commentFormat) | |
| nm = self.start_comment_rx.match(text, start_idx + length) | |
| start_idx = nm.capturedStart() if nm.hasMatch() else -1 | |
| new_state = 0 | |
| else: | |
| setFormat(start_idx, len(text) - start_idx, self.commentFormat) | |
| new_state = 1 | |
| break | |
| self.setCurrentBlockState(new_state) | |
| self._block_state_cache[block_num] = new_state | |
| except Exception: | |
| pass | |
| def highlight_visible_range(self, start_block, end_block): | |
| try: | |
| doc = self.document() | |
| scan_start = None | |
| for bnum in range(start_block - 1, -2, -1): | |
| if bnum == -1: | |
| scan_start = 0 | |
| break | |
| if bnum in self._block_state_cache: | |
| scan_start = bnum + 1 | |
| break | |
| if scan_start is None: scan_start = 0 | |
| block = doc.findBlockByNumber(scan_start) | |
| block_num = scan_start | |
| while block.isValid() and block_num <= end_block: | |
| text = block.text() | |
| apply_format = block_num >= start_block | |
| if apply_format: self._highlight_one_block(block, text) | |
| else: | |
| prev_state = 0 | |
| if block_num > 0: prev_state = self._block_state_cache.get(block_num - 1, 0) | |
| if prev_state != 1: | |
| start_match = self.start_comment_rx.match(text) | |
| start_idx = start_match.capturedStart() if start_match.hasMatch() else -1 | |
| else: start_idx = 0 | |
| new_state = 0 | |
| while start_idx >= 0: | |
| end_match = self.end_comment_rx.match(text, start_idx) | |
| if end_match.hasMatch(): | |
| end_index = end_match.capturedStart() | |
| length = end_index - start_idx + end_match.capturedLength() | |
| nm = self.start_comment_rx.match(text, start_idx + length) | |
| start_idx = nm.capturedStart() if nm.hasMatch() else -1 | |
| new_state = 0 | |
| else: | |
| new_state = 1 | |
| break | |
| self._block_state_cache[block_num] = new_state | |
| block = block.next() | |
| block_num += 1 | |
| except Exception: | |
| pass | |
| def invalidate_cache_from(self, block_number=0): | |
| keys = [k for k in self._block_state_cache if k >= block_number] | |
| for k in keys: | |
| del self._block_state_cache[k] | |
| class CecilPlusHighlighter(QSyntaxHighlighter): | |
| def __init__(self, document): | |
| super().__init__(document) | |
| self.keywordFormat = QTextCharFormat() | |
| self.keywordFormat.setForeground(QColor(PALETTE["keyword"])) | |
| self.keywordFormat.setFontWeight(QFont.Bold) | |
| self.keywordFormat2 = QTextCharFormat() | |
| self.keywordFormat2.setForeground(QColor(PALETTE["keyword2"])) | |
| self.keywordFormat2.setFontWeight(QFont.Bold) | |
| self.typeFormat = QTextCharFormat() | |
| self.typeFormat.setForeground(QColor(PALETTE["type"])) | |
| self.typeFormat.setFontWeight(QFont.Bold) | |
| self.stringFormat = QTextCharFormat() | |
| self.stringFormat.setForeground(QColor(PALETTE["string"])) | |
| self.commentFormat = QTextCharFormat() | |
| self.commentFormat.setForeground(QColor(PALETTE["comment"])) | |
| self.commentFormat.setFontItalic(True) | |
| self.numberFormat = QTextCharFormat() | |
| self.numberFormat.setForeground(QColor(PALETTE["number"])) | |
| self.types = STRICT_TYPES | |
| self.rules = [] | |
| for kw in KEYWORDS: | |
| rx = QRegularExpression(r"\b" + kw + r"\b") | |
| fmt = self.keywordFormat2 if kw in CTRL_KEYWORDS else self.keywordFormat | |
| self.rules.append((rx, fmt)) | |
| for t in self.types: | |
| rx = QRegularExpression(r"\b" + t + r"\b") | |
| self.rules.append((rx, self.typeFormat)) | |
| self.rules.append((QRegularExpression(r'"[^"\\]*(\\.[^"\\]*)*"'), self.stringFormat)) | |
| self.rules.append((QRegularExpression(r"'[^'\\]*(\\.[^'\\]*)*'"), self.stringFormat)) | |
| self.rules.append((QRegularExpression(r"\b\d+(\.\d+)?\b"), self.numberFormat)) | |
| self.rules.append((QRegularExpression(r"//[^\n]*"), self.commentFormat)) | |
| def highlightBlock(self, text): | |
| try: | |
| for rx, fmt in self.rules: | |
| it = rx.globalMatch(text) | |
| while it.hasNext(): | |
| m = it.next() | |
| start = m.capturedStart() | |
| length = m.capturedLength() | |
| self.setFormat(start, length, fmt) | |
| startExpr = QRegularExpression(r"/\*") | |
| endExpr = QRegularExpression(r"\*/") | |
| startIndex = 0 | |
| if self.previousBlockState() != 1: | |
| match = startExpr.match(text) | |
| startIndex = match.capturedStart() if match.hasMatch() else -1 | |
| else: startIndex = 0 | |
| while startIndex >= 0: | |
| endMatch = endExpr.match(text, startIndex) | |
| if endMatch.hasMatch(): | |
| endIndex = endMatch.capturedStart() | |
| length = endIndex-startIndex+endMatch.capturedLength() | |
| self.setFormat(startIndex, length, self.commentFormat) | |
| nextMatch = startExpr.match(text, startIndex+length) | |
| startIndex = nextMatch.capturedStart() if nextMatch.hasMatch() else -1 | |
| self.setCurrentBlockState(0) | |
| else: | |
| self.setFormat(startIndex, len(text) - startIndex, self.commentFormat) | |
| self.setCurrentBlockState(1) | |
| break | |
| except Exception: | |
| pass | |
| class LineNumberArea(QWidget): | |
| def __init__(self, editor): | |
| super().__init__(editor) | |
| self.codeEditor = editor | |
| def sizeHint(self): | |
| return QSize(self.codeEditor.lineNumberAreaWidth(), 0) | |
| def paintEvent(self, event): | |
| self.codeEditor.lineNumberAreaPaintEvent(event) | |
| class ClearableLineEdit(QLineEdit): | |
| def __init__(self, parent=None): | |
| super().__init__(parent) | |
| self._action = QAction(QIcon(ICO_ICO["gray_exit"]), '', self) | |
| self._action.triggered.connect(self.clear) | |
| self.addAction(self._action, QLineEdit.TrailingPosition) | |
| self.setClearButtonEnabled(False) | |
| self._icon_size = 16 | |
| self._update_action_visibility(self.text()) | |
| self.textChanged.connect(self._update_action_visibility) | |
| self.focused = False | |
| def _update_action_visibility(self, text): | |
| self._action.setVisible(bool(text)) | |
| def keyPressEvent(self, event): | |
| if event.key() == Qt.Key_Escape: | |
| self.clear() | |
| return | |
| super().keyPressEvent(event) | |
| class CodeEditor(QPlainTextEdit): | |
| modifiedChanged = pyqtSignal(bool) | |
| def __init__(self, parent=None): | |
| super().__init__(parent) | |
| self.setFont(QFont("Segoe UI Symbol", 13)) | |
| self.setStyleSheet(f"background-color: {PALETTE['editor_bg']};") | |
| self.highlighter = CecilPlusHighlighterQuantum(self.document()) | |
| self.setTabStopDistance(4 * self.fontMetrics().horizontalAdvance(' ')) | |
| self.setLineWrapMode(QPlainTextEdit.NoWrap) | |
| self.lineNumberArea = LineNumberArea(self) | |
| self.blockCountChanged.connect(self.updateLineNumberAreaWidth) | |
| self.updateRequest.connect(self.updateLineNumberArea) | |
| self.cursorPositionChanged.connect(self.highlightCurrentLine) | |
| self.textChanged.connect(self._onTextChanged) | |
| self._saved = True | |
| self.updateLineNumberAreaWidth(0) | |
| self.highlightCurrentLine() | |
| self.rect_selecting = False | |
| self.rect_anchor = None | |
| self.rect_rect = QRect() | |
| self.viewport().installEventFilter(self) | |
| self.fold_manager = FoldManager(self) | |
| self.fold_gutter_width = 16 | |
| self._fold_hover_block = None | |
| doc = self.document() | |
| doc.contentsChange.connect(self._onDocumentContentsChange) | |
| doc.blockCountChanged.connect(self._onBlockCountChanged) | |
| doc.contentsChange.connect(self._on_doc_for_folds) | |
| doc.blockCountChanged.connect(self._on_doc_for_folds) | |
| self.updateVisibleRangeHighlight() | |
| # ---- Context Menu ---- | |
| self.setContextMenuPolicy(Qt.CustomContextMenu) | |
| self.customContextMenuRequested.connect(self.show_context_menu) | |
| self.act_normal = QAction(" Scrollbar Normal width", self, checkable=True) | |
| self.act_double = QAction(" Scrollbar Double width", self, checkable=True) | |
| width_group = QActionGroup(self) | |
| width_group.setExclusive(True) | |
| width_group.addAction(self.act_normal) | |
| width_group.addAction(self.act_double) | |
| self.act_normal.setChecked(True) | |
| self.act_normal.triggered.connect(self.apply_normal_width_scrollbar) | |
| self.act_double.triggered.connect(self.apply_double_width_scrollbar) | |
| self.base_scrollbar_width = 12 | |
| self.double_scrollbar_width = self.base_scrollbar_width * 2 | |
| self.transparent = False | |
| self.apply_double_width_scrollbar() | |
| # ---------------------- | |
| self._on_doc_for_folds() | |
| def show_context_menu(self, pos): | |
| menu = QMenu(self) | |
| menu.addSeparator() | |
| menu.addAction(self.act_normal) | |
| menu.addAction(self.act_double) | |
| menu.addSeparator() | |
| menu.exec_(self.mapToGlobal(pos)) | |
| def build_scrollbar_stylesheet(self, width_px: int, transparent: bool): | |
| thumb_alpha = "60" if transparent else "FF" | |
| track_alpha = "20" if transparent else "30" | |
| hover_alpha = "80" if transparent else "CC" | |
| return f"""QPlainTextEdit {{ }} | |
| QPlainTextEdit QScrollBar:vertical {{width: {width_px}px; | |
| background: rgba(0, 0, 0, {int(track_alpha, 16) / 255.0}); margin: 0px;}} | |
| QPlainTextEdit QScrollBar::handle:vertical {{ | |
| background: rgba(100, 100, 100, {int(thumb_alpha, 16) / 255.0}); | |
| border-radius: {max(2, width_px // 4)}px; min-height: 20px;}} | |
| QPlainTextEdit QScrollBar::handle:vertical:hover {{ | |
| background: rgba(255, 255, 255, {int(hover_alpha, 16) / 255.0});}} | |
| QPlainTextEdit QScrollBar::add-line:vertical, QPlainTextEdit QScrollBar::sub-line:vertical {{height: 0px;}} | |
| QPlainTextEdit QScrollBar::add-page:vertical, QPlainTextEdit QScrollBar::sub-page:vertical {{background: transparent;}}""" | |
| def apply_normal_width_scrollbar(self): | |
| self.act_normal.setChecked(True); self.act_double.setChecked(False) | |
| w = self.base_scrollbar_width | |
| self.setStyleSheet(self.build_scrollbar_stylesheet(w, self.transparent)) | |
| def apply_double_width_scrollbar(self): | |
| self.act_double.setChecked(True); self.act_normal.setChecked(False) | |
| w = self.double_scrollbar_width | |
| self.setStyleSheet(self.build_scrollbar_stylesheet(w, self.transparent)) | |
| def _on_doc_for_folds(self, *args): | |
| old_ids = set(self.fold_manager._collapsed_ids) | |
| self.fold_manager.rebuild() | |
| for r in self.fold_manager.regions: | |
| if r.collapsed: | |
| self.fold_manager._apply_visibility_for_region(r) | |
| self.fold_manager._finalize() | |
| def _make_action(self, text, shortcut, callback): | |
| a = QAction(text, self) | |
| a.setShortcut(shortcut) | |
| a.triggered.connect(callback) | |
| return a | |
| def _fold_current(self, unfold=False, recursive=False): | |
| cursor = self.textCursor() | |
| block_num = cursor.block().blockNumber() | |
| region = self.fold_manager.region_for_block(block_num) | |
| if not region: | |
| for r in self.fold_manager.regions: | |
| if r.start < block_num <= r.end: | |
| region = r | |
| break | |
| if not region: | |
| return | |
| if unfold: | |
| if recursive: | |
| self.fold_manager._expand_recursive(region) | |
| else: | |
| self.fold_manager._expand(region) | |
| else: | |
| if recursive: | |
| self.fold_manager._collapse_recursive(region) | |
| else: | |
| self.fold_manager._collapse(region) | |
| self.fold_manager._update_persisted() | |
| self.fold_manager._finalize() | |
| def _fold_all(self): | |
| self.fold_manager.fold_all() | |
| def _unfold_all(self): | |
| self.fold_manager.unfold_all() | |
| def _onTextChanged(self): | |
| if not self._saved: | |
| return | |
| self._saved = False | |
| self.modifiedChanged.emit(True) | |
| def markSaved(self): | |
| self._saved = True | |
| self.modifiedChanged.emit(False) | |
| def isSaved(self): | |
| return self._saved | |
| def lineNumberAreaWidth(self): | |
| digits = len(str(max(1, self.blockCount()))) | |
| space = 8 + self.fontMetrics().horizontalAdvance('9') * digits | |
| return space + getattr(self, 'fold_gutter_width', 16) | |
| def resizeEvent(self, e): | |
| super().resizeEvent(e) | |
| cr = self.contentsRect() | |
| self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) | |
| self.updateVisibleRangeHighlight() | |
| def updateLineNumberAreaWidth(self, _): | |
| self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) | |
| def updateLineNumberArea(self, rect, dy): | |
| if dy: self.lineNumberArea.scroll(0, dy) | |
| else: self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) | |
| if rect.contains(self.viewport().rect()): self.updateLineNumberAreaWidth(0) | |
| self.updateVisibleRangeHighlight() | |
| def lineNumberAreaPaintEvent(self, event): | |
| painter = QPainter(self.lineNumberArea) | |
| painter.fillRect(event.rect(), QColor(PALETTE.get("line_number_bg", "#2B2B2B"))) | |
| fold_w = getattr(self, 'fold_gutter_width', 16) | |
| painter.fillRect(0, event.rect().top(), fold_w, event.rect().height(), QColor(PALETTE.get("fold_gutter_bg", "#2A2A2A"))) | |
| block = self.firstVisibleBlock() | |
| blockNumber = block.blockNumber() | |
| top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) | |
| bottom = top + int(self.blockBoundingRect(block).height()) | |
| fm = self.fontMetrics(); nums_x = fold_w | |
| hover_block = getattr(self, '_fold_hover_block', None) | |
| while block.isValid() and top <= event.rect().bottom(): | |
| if block.isVisible() and bottom >= event.rect().top(): | |
| number = str(blockNumber + 1) | |
| painter.setPen(QColor(PALETTE["line_number_fg"])) | |
| painter.drawText(nums_x, top, self.lineNumberArea.width() - nums_x - 6, fm.height(), Qt.AlignRight, number) | |
| region = self.fold_manager.region_for_block(blockNumber) | |
| if region and region.end > blockNumber: | |
| center_x = fold_w // 2 | |
| center_y = top + fm.height() // 2 | |
| size = 14 | |
| painter.setPen(Qt.NoPen) | |
| is_hover = (hover_block == blockNumber) | |
| bg_color = QColor(PALETTE.get("fold_hover_bg", "#FFFFFF")) if is_hover else QColor(PALETTE.get("fold_gutter_bg", "#2A2A2A")) | |
| painter.setBrush(bg_color) | |
| rect = QRect(center_x - size, center_y - size, size * 2, size * 2) | |
| painter.drawRoundedRect(rect, 2, 2) | |
| if region.collapsed: | |
| points = [QPoint(center_x - 3, center_y - 4), QPoint(center_x - 3, center_y + 4), | |
| QPoint(center_x + 3, center_y)] | |
| else: | |
| points = [QPoint(center_x - 4, center_y - 2), QPoint(center_x + 4, center_y - 2), | |
| QPoint(center_x, center_y + 4)] | |
| painter.setBrush(QColor(PALETTE.get("fold_icon_fg", "#59E9EE"))) | |
| painter.drawPolygon(QPolygon(points)) | |
| if not region.collapsed: | |
| line_x = center_x | |
| line_y1 = center_y + size // 2 | |
| end_block = self.document().findBlockByNumber(region.end) | |
| if end_block.isValid(): | |
| end_top = int(self.blockBoundingGeometry(end_block).translated(self.contentOffset()).top()) | |
| line_y2 = end_top + int(self.blockBoundingRect(end_block).height()) | |
| painter.setPen(QColor(PALETTE.get("fold_guide", "#FFFFFF"))) | |
| painter.drawLine(line_x, line_y1, line_x, line_y2) | |
| block = block.next() | |
| top = bottom | |
| bottom = top + int(self.blockBoundingRect(block).height()) | |
| blockNumber += 1 | |
| painter.end() | |
| def highlightCurrentLine(self): | |
| extraSelections = [] | |
| if not self.isReadOnly(): | |
| selection = QTextEdit.ExtraSelection() | |
| lineColor = QColor(PALETTE["line_highlight"]) | |
| selection.format.setBackground(lineColor) | |
| selection.format.setProperty(QTextFormat.FullWidthSelection, True) | |
| selection.cursor = self.textCursor() | |
| selection.cursor.clearSelection() | |
| extraSelections.append(selection) | |
| self.setExtraSelections(extraSelections) | |
| def keyPressEvent(self, e): | |
| if e.key() in (Qt.Key_Return, Qt.Key_Enter): | |
| cursor = self.textCursor() | |
| blockText = cursor.block().text() | |
| indent = '' | |
| for ch in blockText: | |
| if ch in [' ', '\t']: indent += ch | |
| else: | |
| break | |
| super().keyPressEvent(e) | |
| self.insertPlainText(indent) | |
| return | |
| if e.text() in ['{', '(', '[', '"', "'"]: | |
| pairs = {'{': '}', '(': ')', '[': ']', '"': '"', "'": "'"} | |
| closing = pairs.get(e.text()) | |
| super().keyPressEvent(e) | |
| self.insertPlainText(closing) | |
| self.moveCursor(QTextCursor.PreviousCharacter) | |
| return | |
| super().keyPressEvent(e) | |
| def eventFilter(self, obj, event): | |
| if obj is self.viewport(): | |
| if event.type() in (QEvent.Paint, QEvent.Resize, QEvent.UpdateRequest): self.updateVisibleRangeHighlight() | |
| return super().eventFilter(obj, event) | |
| def mousePressEvent(self, e): | |
| if e.button() == Qt.LeftButton: | |
| local_x = e.pos().x() | |
| if local_x < self.lineNumberArea.width(): | |
| y = e.pos().y() | |
| block = self.firstVisibleBlock() | |
| top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top()) | |
| bnum = block.blockNumber() | |
| while block.isValid(): | |
| bottom = top + int(self.blockBoundingRect(block).height()) | |
| if y >= top and y < bottom: | |
| region = self.fold_manager.region_for_block(bnum) | |
| if region and region.start == bnum: | |
| modifiers = QApplication.keyboardModifiers() | |
| recursive = bool(modifiers & Qt.ShiftModifier) | |
| self.fold_manager.toggle_region_at_block(bnum, recursive=recursive) | |
| self.updateLineNumberAreaWidth(0) | |
| self.viewport().update() | |
| return | |
| break | |
| block = block.next() | |
| bnum += 1 | |
| top = bottom | |
| self._on_doc_for_folds() | |
| return super().mousePressEvent(e) | |
| def updateVisibleRangeHighlight(self): | |
| block = self.firstVisibleBlock() | |
| if not block.isValid(): | |
| return | |
| top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top() | |
| height = self.viewport().height() | |
| start_block = block.blockNumber() | |
| b, y = block, top | |
| end_block = start_block | |
| while b.isValid() and y <= top + height: | |
| end_block = b.blockNumber() | |
| y += self.blockBoundingRect(b).height() | |
| b = b.next() | |
| last_block_num = self.blockCount() - 1 | |
| if end_block > last_block_num: end_block = last_block_num | |
| if self.highlighter: self.highlighter.highlight_visible_range(start_block, end_block) | |
| def _onDocumentContentsChange(self, position, charsRemoved, charsAdded): | |
| block = self.document().findBlock(position) | |
| if block.isValid(): self.highlighter.invalidate_cache_from(block.blockNumber()) | |
| else: self.highlighter.invalidate_cache_from(0) | |
| self.updateVisibleRangeHighlight() | |
| def _onBlockCountChanged(self, _): | |
| self.highlighter.invalidate_cache_from(0) | |
| self.updateVisibleRangeHighlight() | |
| class GridPopup(QFrame): | |
| itemSelected = pyqtSignal(str) | |
| def __init__(self, owner=None, items=None, cols=3, cell_size=120, margin=6): | |
| super().__init__(None, Qt.Popup | Qt.FramelessWindowHint) | |
| self._owner = owner | |
| self.items = items or [] | |
| self.cols = max(1, cols) | |
| self.cell_size = cell_size | |
| self.margin = margin | |
| layout = QGridLayout(self) | |
| layout.setSpacing(6) | |
| layout.setContentsMargins(margin, margin, margin, margin) | |
| self.setObjectName("gridPopup") | |
| self.setStyleSheet("""#gridPopup{background: #686868; border-radius: 1px; | |
| border: 1px solid #59E9EE; padding: 6px}""") | |
| self._build_buttons() | |
| self._close_on_focus_lost = True | |
| def _build_buttons(self): | |
| layout = self.layout() | |
| while layout.count(): | |
| it = layout.takeAt(0) | |
| w = it.widget() | |
| if w: w.deleteLater() | |
| font = QFont("Segoe UI", 9) | |
| fm = QFontMetrics(font) | |
| for idx, text in enumerate(self.items): | |
| btn = QPushButton(self) | |
| btn.setFont(font) | |
| elided = fm.elidedText(text, Qt.ElideRight, max(10, self.cell_size - 24)) | |
| btn.setText(elided) | |
| btn.setFixedSize(self.cell_size, 36) | |
| btn.setCursor(Qt.PointingHandCursor) | |
| btn.setFocusPolicy(Qt.NoFocus) | |
| btn.setStyleSheet("""QPushButton{border: 0px solid #59E9EE; border-radius: 4px; padding: 5px 12px; | |
| background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #626262, stop:1 #424242);} | |
| QPushButton:hover {background: #F9F9F9; color: #59E9EE; border: 1px solid #59E9EE;} | |
| QPushButton:pressed {background: #E6E6E6; color: #505050; padding-top: 7px;}""") | |
| btn.clicked.connect(lambda _, t=text: self._on_choose(t)) | |
| r = idx // self.cols | |
| c = idx % self.cols | |
| layout.addWidget(btn, r, c) | |
| def updateItems(self, items, cols=None, cell_size=None): | |
| if cols is not None: | |
| self.cols = max(1, cols) | |
| if cell_size is not None: | |
| self.cell_size = cell_size | |
| self.items = items or [] | |
| self._build_buttons() | |
| self.adjustSize() | |
| def _on_choose(self, text): | |
| if not self.isVisible(): | |
| return | |
| self.itemSelected.emit(text) | |
| self.close() | |
| def showAt(self, widget): | |
| if widget is None: | |
| gw = QPoint(0, 0) | |
| widget_h = 0 | |
| else: | |
| try: | |
| gw = widget.mapToGlobal(QPoint(0, 0)) | |
| widget_h = widget.height() | |
| except Exception: | |
| gw = QPoint(0, 0) | |
| widget_h = 0 | |
| x = gw.x(); y = gw.y() + widget_h + 4 | |
| self.adjustSize() | |
| screen = _screen_for_point(gw) | |
| screen_geom = screen.availableGeometry() if screen is not None else QApplication.primaryScreen().availableGeometry() | |
| if x + self.width() > screen_geom.right(): | |
| x = max(screen_geom.left(), screen_geom.right() - self.width() - 8) | |
| if y + self.height() > screen_geom.bottom(): | |
| alt_y = gw.y() - self.height() - 4 | |
| if alt_y >= screen_geom.top(): y = alt_y | |
| try: | |
| self.move(int(x), int(y)) | |
| except Exception: | |
| self.move(screen_geom.left(), screen_geom.top()) | |
| QTimer.singleShot(0, self.show) | |
| def sizeHint(self): | |
| rows = (len(self.items) + self.cols - 1) // self.cols | |
| w = self.cols * self.cell_size + (self.cols - 1) * 6 + self.margin * 2 | |
| h = rows * 36 + (rows - 1) * 6 + self.margin * 2 | |
| return QSize(w, h) | |
| def keyPressEvent(self, ev): | |
| if ev.key() == Qt.Key_Escape: self.close() | |
| else: super().keyPressEvent(ev) | |
| def focusOutEvent(self, ev): | |
| super().focusOutEvent(ev) | |
| QTimer.singleShot(60, self._maybe_close_on_focus_lost) | |
| def _maybe_close_on_focus_lost(self): | |
| if not self.isActiveWindow() and not any(w.hasFocus() for w in self.findChildren(QWidget)): | |
| self.close() | |
| class CecilPlusTGridBox(QWidget): | |
| submitted = pyqtSignal(str) | |
| def __init__(self, parent=None, placeholder="MergeOperator", grid_items=None): | |
| super().__init__(parent) | |
| self.setFixedWidth(410) | |
| self.setFixedHeight(56) | |
| self.line = QLineEdit(self) | |
| self.ctx_helper = MiniContextMenu(self, fg_color="#484848", bg_color="#89E9EE") | |
| self.ctx_helper.attach(self.line) | |
| self.line.setEchoMode(QLineEdit.Normal) | |
| self.line.setPlaceholderText(placeholder) | |
| self.line.setFrame(False) | |
| self.line.setGeometry(16, 10, 332, 36) | |
| self.line.setFont(QFont("Segoe UI", 11)) | |
| self.line.setObjectName("CPGT") | |
| self.line.setStyleSheet("""QLineEdit#CPGT {background: transparent; border: none; | |
| border-bottom: 1px solid #59E9EE;}""") | |
| self.line.returnPressed.connect(self._on_return) | |
| self.grid_btn = QToolButton(self) | |
| self.grid_btn.setCursor(Qt.PointingHandCursor) | |
| self.grid_btn.setGeometry(360, 10, 36, 36) | |
| self._set_grid_icon() | |
| self.grid_btn.clicked.connect(self._on_grid_clicked) | |
| self.grid_btn.setStyleSheet("QToolButton {background: rgba(255,255,255,12); border-radius: 8px;}") | |
| self._focus_progress = 0.0 | |
| self._focus_anim = QPropertyAnimation(self, b"focusProgress") | |
| self._focus_anim.setDuration(160) | |
| self.line.installEventFilter(self) | |
| default_items = ["merge_opA", "merge_opB", "merge_opC", | |
| "merge_opD", "merge_opE", "merge_opF", | |
| "merge_opG", "merge_opH", "merge_opI"] | |
| self.grid_items = grid_items or default_items | |
| self.popup = GridPopup(None, items=self.grid_items, cols=3, cell_size=144) | |
| self.popup.itemSelected.connect(self._on_grid_selected) | |
| self.setFocusProxy(self.line) | |
| def _set_grid_icon(self): | |
| app = QApplication.instance() | |
| screen = app.primaryScreen() | |
| dpr = screen.devicePixelRatio() if screen is not None else 1.0 | |
| size = int(24 * dpr) | |
| pix = QPixmap(size, size) | |
| pix.setDevicePixelRatio(dpr) | |
| pix.fill(Qt.transparent) | |
| p = QPainter(pix) | |
| p.setRenderHint(QPainter.Antialiasing) | |
| pen = QPen(QColor(180, 180, 180)) | |
| pen.setWidthF(1.6) | |
| p.setPen(pen) | |
| s, gap = 4, 4 | |
| for row in range(3): | |
| for col in range(3): | |
| rect = QRectF(col*(s+gap)+2, row*(s+gap)+2, s, s) | |
| p.fillRect(rect, QColor(220, 220, 220, 230)) | |
| p.end() | |
| self.grid_btn.setIcon(QIcon(pix)) | |
| self.grid_btn.setIconSize(QSize(18, 18)) | |
| def _on_grid_clicked(self): | |
| if self.popup.isVisible(): | |
| self.popup.close() | |
| return | |
| self.popup.updateItems(self.grid_items, cols=self.popup.cols, cell_size=self.popup.cell_size) | |
| self.popup.showAt(self.grid_btn) | |
| def _on_grid_selected(self, text): | |
| self.line.setText(text) | |
| self.line.setEchoMode(QLineEdit.Normal) | |
| self.submitted.emit(text) | |
| def _on_return(self): | |
| self.submitted.emit(self.line.text()) | |
| def eventFilter(self, obj, event): | |
| if obj is self.line: | |
| if event.type() == QEvent.FocusIn: | |
| self._focus_anim.stop() | |
| self._focus_anim.setStartValue(self._focus_progress) | |
| self._focus_anim.setEndValue(1.0) | |
| self._focus_anim.start() | |
| elif event.type() == QEvent.FocusOut: | |
| self._focus_anim.stop() | |
| self._focus_anim.setStartValue(self._focus_progress) | |
| self._focus_anim.setEndValue(0.0) | |
| self._focus_anim.start() | |
| return super().eventFilter(obj, event) | |
| def getFocusProgress(self): | |
| return self._focus_progress | |
| def setFocusProgress(self, v): | |
| self._focus_progress = v | |
| self.update() | |
| focusProgress = pyqtProperty(float, getFocusProgress, setFocusProgress) | |
| def paintEvent(self, e): | |
| try: | |
| r = QRectF(0, 0, self.width(), self.height()) | |
| painter = QPainter(self) | |
| painter.setRenderHint(QPainter.Antialiasing) | |
| path = QPainterPath() | |
| path.addRoundedRect(r.adjusted(0.5, 0.5, -0.5, -2.0), 10.0, 10.0) | |
| pen = QPen(QColor(255, 255, 255, 0.05)) | |
| pen.setWidthF(1.6) | |
| painter.setPen(pen) | |
| painter.setBrush(QColor(255, 255, 255, 8)) | |
| painter.drawPath(path) | |
| base_y = self.height() - 12 | |
| base_pen = QPen(QColor(200, 200, 200, 200)) | |
| base_pen.setWidthF(1.0) | |
| painter.setPen(base_pen) | |
| if self._focus_progress > 0.001: | |
| accent = QColor(255, 140, 0, int(230 * self._focus_progress)) | |
| acc_pen = QPen(accent) | |
| acc_pen.setWidthF(2.8 * self._focus_progress) | |
| acc_pen.setCapStyle(Qt.RoundCap) | |
| painter.setPen(acc_pen) | |
| total_w = (self.width() - 68) | |
| seg_w = total_w * 0.36 | |
| cx = 16 + total_w * 0.5 | |
| start = cx - seg_w / 2 | |
| end = cx + seg_w / 2 | |
| painter.end() | |
| except Exception: | |
| pass | |
| class SPSQuantumButton(QPushButton): | |
| UNCHECK, CHECK = "\u2610", "\u2611" | |
| stateChanged = pyqtSignal(bool, bool, bool, int, bool) | |
| xfactorComputed = pyqtSignal(float) | |
| def __init__(self, label, max_value=999, parent=None): | |
| super().__init__(parent) | |
| self.setCheckable(False) | |
| self.setCursor(Qt.PointingHandCursor) | |
| self.left_checked = False; self.right_checked = False; self.third_flag = False | |
| self._label = label; self.value = 0; self.max_value = max_value | |
| self._prev_vector = self._current_vector() | |
| self.line_edit = QLineEdit(self) | |
| fnt_ln_edt = QFont("Segoe UI", 14) | |
| self.line_edit.setFont(fnt_ln_edt) | |
| self.line_edit.setValidator(QIntValidator(0, self.max_value, self)) | |
| self.line_edit.setFixedWidth(60) | |
| self.line_edit.setFrame(False) | |
| self.line_edit.setAlignment(Qt.AlignCenter) | |
| self.line_edit.textChanged.connect(self._on_text_changed) | |
| self.line_edit.installEventFilter(self) | |
| self.ctx_helper = MiniContextMenu(self, fg_color="#484848", bg_color="#89E9EE") | |
| self.ctx_helper.attach(self.line_edit) | |
| self.setMinimumHeight(67) | |
| self.color_normal = QColor(170, 170, 170); self.color_checked = QColor("#59E9EE") | |
| self.glow_base_false = QColor("#555555"); self.glow_base_true = QColor("#59E9EE") | |
| self._last_xfactor = 0.0 | |
| def sizeHint(self): | |
| return QSize(360, 67) | |
| def _current_vector(self): | |
| return np.array([1.0 if self.left_checked else 0.0, 1.0 if self.right_checked else 0.0, | |
| 1.0 if self.third_flag else 0.0, float(self.value) / max(1.0, self.max_value)], dtype=float) | |
| def eventFilter(self, obj, event): | |
| if obj is self.line_edit: | |
| if event.type() in (QEvent.MouseButtonPress, QEvent.MouseButtonDblClick, QEvent.KeyPress): | |
| return False | |
| return super().eventFilter(obj, event) | |
| def _on_text_changed(self, txt): | |
| self.value = int(txt) if txt.isdigit() else 0 | |
| self.update() | |
| self._emit_state() | |
| def set_third_flag(self, value: bool): | |
| self.third_flag = bool(value) | |
| self.update() | |
| self._emit_state() | |
| def _emit_state(self): | |
| expr_result = self.evaluate_expression() | |
| self.stateChanged.emit(self.left_checked, self.right_checked, self.third_flag, self.value, expr_result) | |
| def evaluate_expression(self): | |
| base_min, base_max = 10, 20 | |
| min_v = base_min + (-5 if self.left_checked else 0) | |
| max_v = base_max + (10 if self.right_checked else 0) | |
| if self.third_flag: | |
| center = (min_v + max_v) / 2.0; half = max(0, (max_v - min_v) / 4.0) | |
| min_v = int(center - half); max_v = int(center + half) | |
| return (self.value >= min_v) and (self.value <= max_v) | |
| def mousePressEvent(self, event): | |
| r = self.rect() | |
| left_rect = QRect(int(r.left() + 8), int(r.top() + 8), 30, 30) | |
| right_rect = QRect(int(r.left() + 46), int(r.top() + 8), 30, 30) | |
| edit_rect = QRect(int(r.left() + 100), int(r.top() + 10), self.line_edit.width(), 30) | |
| if left_rect.contains(event.pos()): | |
| self.left_checked = not self.left_checked | |
| self.update() | |
| self._emit_state() | |
| return | |
| if right_rect.contains(event.pos()): | |
| self.right_checked = not self.right_checked | |
| self.update() | |
| self._emit_state() | |
| return | |
| if edit_rect.contains(event.pos()): | |
| self._place_line_edit(edit_rect) | |
| self.line_edit.setFocus() | |
| return | |
| if event.button() == Qt.LeftButton: | |
| self.left_checked = not self.left_checked | |
| self.right_checked = not self.right_checked | |
| self.update() | |
| self._emit_state() | |
| return | |
| def mouseDoubleClickEvent(self, event): | |
| prev = self._prev_vector.copy() | |
| curr = self._current_vector() | |
| A = np.vstack([prev, curr]).astype(float) | |
| col_std = A.std(axis=0, keepdims=True) + 1e-12 | |
| A_norm = A / col_std | |
| try: | |
| u, s, vt = np.linalg.svd(A_norm, full_matrices=False) | |
| ratio = float(s[1] / (s[0] + 1e-12)) | |
| except Exception: | |
| ratio = 0.0 | |
| diff_norm = float(np.linalg.norm(curr - prev)) | |
| xfactor = 0.6 * ratio + 0.4 * (diff_norm / (1.0 + diff_norm)) | |
| xfactor = float(max(0.0, min(1.0, xfactor))) | |
| self._last_xfactor = xfactor | |
| self._prev_vector = curr.copy() | |
| self.xfactorComputed.emit(xfactor) | |
| self.update() | |
| def _place_line_edit(self, edit_rect): | |
| self.line_edit.setGeometry(edit_rect) | |
| pal = self.line_edit.palette() | |
| pal.setColor(QPalette.Base, QColor(255, 255, 255, 230)) | |
| self.line_edit.setPalette(pal) | |
| def resizeEvent(self, event): | |
| edit_rect = QRect(int(self.rect().left() + 100), int(self.rect().top() + 10), self.line_edit.width(), 30) | |
| self.line_edit.setGeometry(edit_rect) | |
| super().resizeEvent(event) | |
| def _interpolate_color(self, c1: QColor, c2: QColor, t: float): | |
| t = max(0.0, min(1.0, t)) | |
| return QColor(int(c1.red() + (c2.red() - c1.red()) * t), int(c1.green() + (c2.green() - c1.green()) * t), | |
| int(c1.blue() + (c2.blue() - c1.blue()) * t), int(c1.alpha() + (c2.alpha() - c1.alpha()) * t),) | |
| def paintEvent(self, event): | |
| try: | |
| painter = QPainter(self) | |
| painter.setRenderHint(QPainter.Antialiasing) | |
| option = QStyleOptionButton() | |
| option.initFrom(self) | |
| self.style().drawControl(QStyle.CE_PushButtonBevel, option, painter, self) | |
| r = self.rect() | |
| left_rect = QRectF(r.left() + 12, r.top() + 8, 30, 30) | |
| right_rect = QRectF(r.left() + 46, r.top() + 8, 30, 30) | |
| edit_rect = QRectF(r.left() + 100, r.top() + 10, self.line_edit.width(), 30) | |
| text_x = edit_rect.right() + 10 | |
| text_rect = QRectF(text_x, r.top(), r.width() - text_x - 10, r.height()) | |
| glyph_font = QFont(self.font()) | |
| glyph_font.setPointSize(14) | |
| painter.setFont(glyph_font) | |
| painter.setPen(self.color_checked if self.left_checked else self.color_normal) | |
| painter.drawText(left_rect, Qt.AlignCenter, self.CHECK if self.left_checked else self.UNCHECK) | |
| painter.setPen(self.color_checked if self.right_checked else self.color_normal) | |
| painter.drawText(right_rect, Qt.AlignCenter, self.CHECK if self.right_checked else self.UNCHECK) | |
| lbl_fnt = QFont("Segoe UI", 9) | |
| painter.setFont(lbl_fnt) | |
| painter.setPen(self.palette().buttonText().color()) | |
| painter.drawText(text_rect, Qt.AlignVCenter | Qt.AlignLeft, self._label) | |
| line_y = left_rect.center().y() | |
| left_end_x = left_rect.left() - 14 | |
| painter.setPen(QPen(QColor(150, 150, 150), 2)) | |
| start_x = left_end_x + 8 | |
| mid_x = left_rect.center().x() | |
| expr_result = self.evaluate_expression() | |
| all_false = (not self.left_checked) and (not self.right_checked) and (not self.third_flag) | |
| any_true = self.left_checked or self.right_checked or self.third_flag | |
| glow_color = None | |
| if all_false and (not expr_result): glow_color = self.glow_base_false | |
| elif any_true or expr_result: glow_color = self.glow_base_true | |
| if glow_color is not None: | |
| t = self._last_xfactor | |
| base = QColor(glow_color) | |
| intense = QColor(min(255, base.red()+80), min(255, base.green()+80), min(255, base.blue()+80)) | |
| color = self._interpolate_color(base, intense, t) | |
| glow_rect = QRectF(left_rect.left() - 6, left_rect.bottom() + 4, | |
| (right_rect.right() - left_rect.left()) + 12, 8) | |
| for i in range(5, 0, -1): | |
| alpha = int(30 * i * (0.6 + 0.4 * t)) | |
| c = QColor(color) | |
| c.setAlpha(alpha) | |
| painter.setBrush(c) | |
| painter.setPen(Qt.NoPen) | |
| shrink = (7 - i) * 1.2 | |
| rect = glow_rect.adjusted(shrink, shrink, -shrink, -shrink) | |
| painter.drawRoundedRect(rect, 1, 1) | |
| except Exception as err: | |
| pass | |
| class CharButton(QPushButton): | |
| def __init__(self, ch, parent=None): | |
| super().__init__(ch, parent) | |
| self.ch = ch | |
| self.setFont(QFont("Segoe UI Symbol")) | |
| self.setFocusPolicy(Qt.StrongFocus) | |
| self.setCursor(QCursor(Qt.PointingHandCursor)) | |
| self.setFixedSize(40, 36) | |
| self.setStyleSheet("""QPushButton {border: 1px solid #59E9EE; border-radius: 2px; | |
| background: rgba(201, 201, 201, 0.2); font-size: 20px;} | |
| QPushButton:focus {border: 2px solid #59E9EE; background: #999999;} | |
| QPushButton:hover {background: rgba(122, 122, 122, 0.1);}""") | |
| class CharMapDialog(QDialog): | |
| def __init__(self, parent=None, initial_text=""): | |
| super().__init__(parent) | |
| self.setWindowTitle("Character Map") | |
| self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) | |
| self.setFixedSize(625, 420) | |
| self.setStyleSheet("QDialog {background: #555555}") | |
| input_fnt = QFont("Segoe UI Symbol", 12) | |
| txt_bx_style = """QLineEdit{border: 1px solid rgba(99,99,99,0.22); border-radius: 5px; | |
| padding: 3px 3px; background: #444444;}""" | |
| self.input = ClearableLineEdit() | |
| self.input.setFixedHeight(38) | |
| self.input.setFont(input_fnt) | |
| self.input.setStyleSheet(txt_bx_style) | |
| self.ctx_helper = MiniContextMenu(self, fg_color="#484848", bg_color="#89E9EE") | |
| self.ctx_helper.attach(self.input) | |
| self.CHRMAP_CFG_PTH = Path(MDL_DIR) / 'Cecil+' / 'ide-settings' / 'chrmap_cfg.cfg' | |
| if os.path.isfile(self.CHRMAP_CFG_PTH): | |
| chr_map_cfg = read_file(self.CHRMAP_CFG_PTH) | |
| if chr_map_cfg != 'null': self.input.setText(chr_map_cfg) | |
| else: write_file(self.CHRMAP_CFG_PTH, 'null') | |
| self.copy_btn = QPushButton(QIcon(ICO_ICO["gray_clipboard"]), "Copy") | |
| self.copy_btn.setFont(QFont("Segoe UI", 9)) | |
| self.copy_btn.setDefault(True) | |
| self.copy_btn.setFixedHeight(32) | |
| self.copy_btn.setCursor(QCursor(Qt.PointingHandCursor)) | |
| self.copy_btn.clicked.connect(self.copy_and_close_if_single()) | |
| top_layout = QHBoxLayout() | |
| top_layout.addWidget(self.input) | |
| top_layout.addWidget(self.copy_btn) | |
| self.grid_widget = QWidget() | |
| self.grid_layout = QGridLayout(self.grid_widget) | |
| self.grid_layout.setSpacing(8) | |
| self.grid_layout.setContentsMargins(8, 8, 8, 8) | |
| self.char_buttons = []; cols = 12 | |
| SPCL_CHARS = ["◑", "◓", "◐", "◒", "∓", "⋂", "⏆", "≁", "⍜", "∫", "ᴦ", | |
| "←", "→", "⋊", "⋉", "ͽ", "⇹", "⇼", "·", "•", "…", "—", | |
| "–", "‹", "›", "«", "»", "※", "§", "¶", "†", "‡", "°", "‰", | |
| "‱", "◊", "↑", "↓", "⇐", "⇒", "⇑", "⇓", "⊂", | |
| "⊃", "⊆", "⊇", "⊕", "⊗", "∅", "∈", "∉", "∀", "∃", | |
| "∧", "∨", "¬", "⇔", "≈", "≠", "≡", "≤", "≥", "√", | |
| "∑", "∏", "∂", "ℓ", "ℵ", "℘", "ƒ", "→=", "←=", "=>", | |
| "<=", "->", "<-", "Λ", "μ", "⊥", "°C", "°F", "±", "×", "÷"] | |
| for idx, ch in enumerate(SPCL_CHARS): | |
| btn = CharButton(ch) | |
| r = idx // cols; c = idx % cols | |
| self.grid_layout.addWidget(btn, r, c) | |
| btn.clicked.connect(self.make_insert_handler(ch)) | |
| btn.installEventFilter(self) | |
| btn.setFocusPolicy(Qt.StrongFocus) | |
| btn.setAutoDefault(False) | |
| btn.setDefault(False) | |
| btn.clicked.connect(lambda checked, b=btn: self.focus_button(b)) | |
| btn.doubleClicked = False | |
| btn.mouseDoubleClickEvent = self.make_double_click_handler(ch) | |
| self.char_buttons.append(btn) | |
| self.scroll = QScrollArea() | |
| self.scroll.setWidgetResizable(True) | |
| self.scroll.setWidget(self.grid_widget) | |
| self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) | |
| hint = QLabel(" Click to insert, double-click to copy & close.") | |
| hint.setFont(QFont("Segoe UI Symbol")) | |
| hint.setStyleSheet("color: #111111; font-size: 14px;") | |
| hint.setAlignment(Qt.AlignLeft) | |
| main_layout = QVBoxLayout(self) | |
| main_layout.addLayout(top_layout) | |
| main_layout.addWidget(self.scroll) | |
| main_layout.addWidget(hint) | |
| QShortcut(QKeySequence("Esc"), self, self.reject) | |
| QShortcut(QKeySequence("Ctrl+C"), self, self.copy_text) | |
| self._selected_button = None | |
| if self.char_buttons: self.set_selected(self.char_buttons[0]) | |
| self.input.returnPressed.connect(self.copy_text) | |
| def make_insert_handler(self, ch): | |
| def handler(): | |
| cur = self.input.cursorPosition() | |
| txt = self.input.text() | |
| new = txt[:cur] + ch + txt[cur:] | |
| self.input.setText(new) | |
| self.input.setCursorPosition(cur + len(ch)) | |
| self.input.setFocus() | |
| return handler | |
| def make_double_click_handler(self, ch): | |
| def ev(e): | |
| QApplication.clipboard().setText(ch) | |
| self.accept() | |
| return ev | |
| def focus_button(self, btn): | |
| if self._selected_button: | |
| self._selected_button.setStyleSheet(self._selected_button.styleSheet()) | |
| self.set_selected(btn) | |
| btn.setFocus() | |
| def set_selected(self, btn): | |
| self._selected_button = btn | |
| btn.setStyleSheet(btn.styleSheet() + " QPushButton { outline: 1px solid #59E9EE; }") | |
| def copy_and_close_if_single(self): | |
| def slot(): | |
| self.copy_text() | |
| return slot | |
| def copy_text(self): | |
| txt = self.input.text() | |
| if not txt: | |
| if self._selected_button: | |
| txt = self._selected_button.ch | |
| else: | |
| return | |
| QApplication.clipboard().setText(txt) | |
| if len(txt) <= 2 and self._selected_button and self._selected_button.ch == txt: | |
| self.accept() | |
| def keyPressEvent(self, event): | |
| if not self._selected_button: | |
| return super().keyPressEvent(event) | |
| idx = self.char_buttons.index(self._selected_button) | |
| cols = 12; key = event.key() | |
| if key in (Qt.Key_Right,): | |
| idx = min(idx + 1, len(self.char_buttons) - 1) | |
| self.set_selected(self.char_buttons[idx]) | |
| self.char_buttons[idx].setFocus() | |
| self.ensure_visible(self.char_buttons[idx]) | |
| return | |
| if key in (Qt.Key_Left,): | |
| idx = max(idx - 1, 0) | |
| self.set_selected(self.char_buttons[idx]) | |
| self.char_buttons[idx].setFocus() | |
| self.ensure_visible(self.char_buttons[idx]) | |
| return | |
| if key in (Qt.Key_Down,): | |
| idx = min(idx + cols, len(self.char_buttons) - 1) | |
| self.set_selected(self.char_buttons[idx]) | |
| self.char_buttons[idx].setFocus() | |
| self.ensure_visible(self.char_buttons[idx]) | |
| return | |
| if key in (Qt.Key_Up,): | |
| idx = max(idx - cols, 0) | |
| self.set_selected(self.char_buttons[idx]) | |
| self.char_buttons[idx].setFocus() | |
| self.ensure_visible(self.char_buttons[idx]) | |
| return | |
| if key in (Qt.Key_Return, Qt.Key_Enter): | |
| QApplication.clipboard().setText(self._selected_button.ch) | |
| self.accept() | |
| return | |
| super().keyPressEvent(event) | |
| def ensure_visible(self, widget): | |
| rect = self.grid_widget.rect() | |
| widget_rect = widget.geometry() | |
| self.scroll.ensureVisible(widget_rect.x(), widget_rect.y(), widget_rect.width(), widget_rect.height()) | |
| def showEvent(self, event): | |
| super().showEvent(event) | |
| def closeEvent(self, event): | |
| try: | |
| super().closeEvent(event) | |
| char_map_txt = self.input.text() | |
| if len(char_map_txt) < 1: char_map_txt = 'null' | |
| write_file(self.CHRMAP_CFG_PTH, char_map_txt) | |
| except Exception: | |
| pass | |
| class CodePageLabel(QLabel): | |
| def __init__(self, text, page_size:QSize, font:QFont, parent=None): | |
| super().__init__(parent) | |
| self.page_size = page_size | |
| self.font = font | |
| self.setStyleSheet("background: #535353; border:1px solid #59E9EE;") | |
| self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) | |
| self.render_text_to_pixmap(text) | |
| def render_text_to_pixmap(self, text): | |
| try: | |
| doc = QTextDocument() | |
| doc.setDefaultFont(self.font) | |
| doc.setPlainText(text) | |
| doc.setDocumentMargin(12) | |
| self._cpl_hgh = CecilPlusHighlighter(doc) | |
| self._cpl_hgh.rehighlight() | |
| inner_w = max(10, self.page_size.width() - 24) | |
| doc.setTextWidth(inner_w) | |
| pix = QPixmap(self.page_size) | |
| pix.fill(QColor("#535353")) | |
| painter = QPainter(pix) | |
| painter.setRenderHint(QPainter.TextAntialiasing) | |
| doc.drawContents(painter) | |
| painter.end() | |
| self.setPixmap(pix) | |
| self.setFixedSize(self.page_size) | |
| except Exception as e: | |
| pix = QPixmap(self.page_size) | |
| pix.fill(QColor("#535353")) | |
| painter = QPainter(pix) | |
| painter.drawText(12, 20, f"Render error: {e}") | |
| painter.end() | |
| self.setPixmap(pix) | |
| self.setFixedSize(self.page_size) | |
| class RotatingArrowButton(QToolButton): | |
| def __init__(self, parent=None, size=28): | |
| super().__init__(parent) | |
| self._rotation = 0.0 | |
| self.setFixedSize(QSize(size, size)) | |
| self.setCursor(Qt.PointingHandCursor) | |
| self.setStyleSheet("background: transparent; border: none;") | |
| self._color = QColor(WHITE) | |
| def paintEvent(self, evt): | |
| painter = QPainter(self) | |
| painter.setRenderHint(QPainter.Antialiasing) | |
| w, h = self.width(), self.height() | |
| painter.translate(w/2, h/2) | |
| painter.rotate(self._rotation) | |
| painter.translate(-w/2, -h/2) | |
| pen = QPen(self._color) | |
| pen.setWidthF(1.5) | |
| painter.setPen(pen) | |
| painter.setBrush(QBrush(self._color)) | |
| tri = QPolygonF([self._pt(0.3*w, 0.2*h), self._pt(0.7*w, 0.5*h), | |
| self._pt(0.3*w, 0.8*h)]) | |
| painter.drawPolygon(tri) | |
| def _pt(self, x, y): | |
| from PyQt5.QtCore import QPointF | |
| return QPointF(x, y) | |
| def getRotation(self): | |
| return self._rotation | |
| def setRotation(self, v): | |
| self._rotation = v | |
| self.update() | |
| rotation = pyqtProperty(float, getRotation, setRotation) | |
| class BottomBarWidget(QWidget): | |
| def __init__(self, parent=None, terminal_height=800): | |
| super().__init__(parent) | |
| self.terminal_height = terminal_height//0.32 | |
| self._terminal_open = False | |
| self._anim_duration = 350 | |
| self.vlayout = QVBoxLayout(self) | |
| self.vlayout.setContentsMargins(0, 0, 0, 0) | |
| self.vlayout.setSpacing(0) | |
| self.terminal_panel = QFrame(self) | |
| self.terminal_panel.setObjectName("terminal_panel") | |
| self.terminal_panel.setStyleSheet(f"""QFrame#terminal_panel {{background: {PANEL_GRAY}; | |
| border-top: 1px solid rgba(255,255,255,0.04);}}""") | |
| self.terminal_layout = QVBoxLayout(self.terminal_panel) | |
| self.terminal_layout.setContentsMargins(6, 6, 6, 6) | |
| self.terminal_layout.setSpacing(4) | |
| title_bar = QHBoxLayout() | |
| title_bar.setContentsMargins(4, 2, 4, 2) | |
| title_bar.setSpacing(6) | |
| self.menu_btn = QPushButton("Menu") | |
| self.menu_btn.setCursor(Qt.PointingHandCursor) | |
| self.menu_btn.setFixedHeight(22) | |
| self.menu_btn.setStyleSheet(self._mini_button_style()) | |
| menu = QMenu(self) | |
| menu.addAction("Clear", self._clear_terminal) | |
| menu.addAction("Copy All", self._copy_terminal) | |
| menu.addSeparator() | |
| menu.addAction("Show Projections", self._show_projections) | |
| menu.addAction("Show Sample Space", self._show_sample_space) | |
| menu.addSeparator() | |
| menu.addAction("Settings", self._show_settings) | |
| menu.addAction("Help", self._show_help) | |
| self.menu_btn.setMenu(menu) | |
| self.menu_btn.clicked.connect(lambda: menu.exec_(self.menu_btn.mapToGlobal(self.menu_btn.rect().bottomLeft()))) | |
| title_bar.addWidget(self.menu_btn) | |
| title_bar.addStretch() | |
| self.term_close_btn = QPushButton("✕") | |
| self.term_close_btn.setCursor(Qt.PointingHandCursor) | |
| self.term_close_btn.setFixedSize(24, 22) | |
| self.term_close_btn.setStyleSheet(self._mini_button_style()) | |
| self.term_close_btn.clicked.connect(self.hide_terminal) | |
| title_bar.addWidget(self.term_close_btn) | |
| self.terminal_layout.addLayout(title_bar) | |
| self.terminal_edit = QTextEdit() | |
| self.terminal_edit.setStyleSheet(f"""background: #121416; color: {WHITE}; border-radius: 4px; | |
| padding: 6px; font-family: Consolas, "Courier New", monospace; | |
| font-size: 18px;""") | |
| self.terminal_edit.setReadOnly(False) | |
| self.terminal_layout.addWidget(self.terminal_edit) | |
| self.terminal_panel.setMaximumHeight(0) | |
| self.vlayout.addWidget(self.terminal_panel) | |
| self.bar = QFrame(self) | |
| self.bar.setObjectName("bottom_bar") | |
| self.bar.setFixedHeight(36) | |
| self.bar.setStyleSheet(f"""QFrame#bottom_bar {{background: {BG_GRAY}; | |
| border-top: 1px solid rgba(199,199,199,0.5);}}""") | |
| self.bar_layout = QHBoxLayout(self.bar) | |
| self.bar_layout.setContentsMargins(8, 4, 8, 4) | |
| self.bar_layout.setSpacing(8) | |
| self.left_label = QLabel("!") | |
| self.left_label.setStyleSheet(f"color: {LIGHT_GRAY};") | |
| self.left_label.setMinimumWidth(200) | |
| self.bar_layout.addWidget(self.left_label) | |
| self.bar_layout.addStretch() | |
| self.btn_a = QPushButton("Process Symbol Defs") | |
| self.btn_b = QPushButton("Del Symbol Contains") | |
| for b in (self.btn_a, self.btn_b): | |
| b.setCursor(Qt.PointingHandCursor) | |
| b.setFixedHeight(26) | |
| b.setStyleSheet(self._toolbar_button_style()) | |
| self.bar_layout.addWidget(b) | |
| self.arrow_btn = RotatingArrowButton(size=30) | |
| self.arrow_btn.setCursor(Qt.PointingHandCursor) | |
| self.arrow_btn.clicked.connect(self.toggle_terminal) | |
| self.bar_layout.addWidget(self.arrow_btn) | |
| self.vlayout.addWidget(self.bar) | |
| self._height_anim = QPropertyAnimation(self.terminal_panel, b"maximumHeight", self) | |
| self._height_anim.setEasingCurve(QEasingCurve.InOutCubic) | |
| self._height_anim.setDuration(self._anim_duration) | |
| self._rot_anim = QPropertyAnimation(self.arrow_btn, b"rotation", self) | |
| self._rot_anim.setEasingCurve(QEasingCurve.InOutCubic) | |
| self._rot_anim.setDuration(self._anim_duration) | |
| self.arrow_btn.setRotation(0.0) | |
| self._apply_styles() | |
| def _apply_styles(self): | |
| accent = TURQUOISE | |
| self.btn_a.setStyleSheet(self._toolbar_button_style(accent=True)) | |
| self.btn_b.setStyleSheet(self._toolbar_button_style()) | |
| def _toolbar_button_style(self, accent=False): | |
| bg = TURQUOISE if accent else "#4a4d50" | |
| return f"""QPushButton {{background: {bg}; color: #0b0b0b; border-radius: 4px; | |
| padding: 4px 8px; font-weight: 600;}} QPushButton:hover {{ opacity: 0.95; }}""" | |
| def _mini_button_style(self): | |
| return f"""QPushButton {{background: rgba(255,255,255,0.04); color: {WHITE}; | |
| border: 1px solid rgba(255,255,255,0.04); border-radius: 4px; padding: 0 6px;}} | |
| QPushButton::menu-indicator {{ image: none; }}""" | |
| def toggle_terminal(self): | |
| if self._terminal_open: | |
| self._height_anim.stop() | |
| self._height_anim.setStartValue(self.terminal_panel.maximumHeight()) | |
| self._height_anim.setEndValue(0) | |
| self._height_anim.setDuration(self._anim_duration) | |
| self._height_anim.start() | |
| self._rot_anim.stop() | |
| self._rot_anim.setStartValue(self.arrow_btn.rotation) | |
| self._rot_anim.setEndValue(0.0) | |
| self._rot_anim.start() | |
| self._terminal_open = False | |
| else: | |
| self._height_anim.stop() | |
| self._height_anim.setStartValue(self.terminal_panel.maximumHeight()) | |
| self._height_anim.setEndValue(self.terminal_height) | |
| self._height_anim.setDuration(self._anim_duration) | |
| self._height_anim.start() | |
| self._rot_anim.stop() | |
| self._rot_anim.setStartValue(self.arrow_btn.rotation) | |
| self._rot_anim.setEndValue(90.0) | |
| self._rot_anim.start() | |
| self._terminal_open = True | |
| def _clear_terminal(self): | |
| self.terminal_edit.clear() | |
| def _copy_terminal(self): | |
| self.terminal_edit.selectAll() | |
| self.terminal_edit.copy() | |
| self.terminal_edit.moveCursor(self.terminal_edit.textCursor().End) | |
| def _show_projections(self): | |
| pass | |
| def _show_sample_space(self): | |
| pass | |
| def _show_settings(self): | |
| pass | |
| def _show_help(self): | |
| pass | |
| def set_status_text(self, text: str): | |
| self.left_label.setText(text) | |
| def append_terminal(self, text: str): | |
| self.terminal_edit.append(text) | |
| def hide_terminal(self): | |
| self.setVisible(False) | |
| class Paginator: | |
| def __init__(self, font:QFont, page_width:int, page_height:int, margin=24): | |
| self.font = font | |
| doc = QTextDocument() | |
| doc.setDefaultFont(self.font) | |
| doc.setPlainText("X\n") | |
| doc.setTextWidth(max(10, page_width - margin)) | |
| block = doc.firstBlock() | |
| line_h = 14 | |
| try: | |
| if block.isValid(): | |
| rb = doc.documentLayout().blockBoundingRect(block) | |
| if rb.height() > 0: | |
| line_h = rb.height() | |
| except Exception: | |
| line_h = 14 | |
| usable_h = max(10, page_height - margin) | |
| self.lines_per_page = max(1, int(usable_h // line_h)) | |
| def paginate(self, full_text: str): | |
| lines = full_text.splitlines() | |
| if not lines: | |
| return [""] | |
| total_pages = math.ceil(len(lines) / self.lines_per_page) | |
| pages = [] | |
| for p in range(total_pages): | |
| start = p * self.lines_per_page | |
| end = start + self.lines_per_page | |
| pages.append("\n".join(lines[start:end])) | |
| return pages or [""] | |
| class IDEOpenDialogHorizontal(QDialog): | |
| def __init__(self, start_dir=None, parent=None): | |
| super().__init__(parent) | |
| self.setFont(QFont('Segoe UI', 9)) | |
| self.setStyleSheet(DLG_STYLE_SHEET) | |
| self.setWindowTitle("Open Cecil+ File (*.cplq, *.cplp or *.cpls)") | |
| self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) | |
| self.setFixedSize(1200, 656) | |
| self.model = TreeIconFileSystemModel() | |
| self.model.setRootPath(QDir.rootPath()) | |
| filters = ["*.cplq", "*.cplp", "*.cpls"] | |
| self.model.setNameFilters(filters); self.model.setNameFilterDisables(False) | |
| self.tree = QTreeView(); self.tree.setModel(self.model) | |
| self.tree.setRootIndex(self.model.index(start_dir or QDir.homePath())) | |
| for c in range(1, self.model.columnCount()): self.tree.hideColumn(c) | |
| self.tree.setAnimated(True) | |
| self.page_width = 820; self.page_height = 500 | |
| self.font = QFont("Segoe UI", 10) | |
| self.paginator = Paginator(self.font, self.page_width, self.page_height) | |
| self.stack = QStackedWidget() | |
| self.stack.setFixedSize(self.page_width, self.page_height) | |
| nav_bar = QWidget() | |
| nav_h = QHBoxLayout(nav_bar); nav_h.setContentsMargins(0,0,0,0) | |
| txts_fnt = QFont("Segoe UI", 9) | |
| self.btn_prev = QPushButton("\u25C0 Prev Page") | |
| self.btn_prev.setFont(txts_fnt) | |
| self.btn_next = QPushButton("Next Page \u25B6") | |
| self.btn_next.setFont(txts_fnt) | |
| self.page_label = QLabel("Page Image 0 / 0") | |
| self.page_label.setFont(txts_fnt) | |
| self.btn_prev.setFixedHeight(32) | |
| self.btn_prev.setEnabled(False) | |
| self.btn_next.setFixedHeight(32) | |
| self.btn_next.setEnabled(False) | |
| nav_h.addWidget(self.btn_prev); nav_h.addWidget(self.page_label); nav_h.addWidget(self.btn_next) | |
| nav_h.addStretch() | |
| right_container = QWidget() | |
| right_v = QVBoxLayout(right_container); right_v.setContentsMargins(8,8,8,8) | |
| self.meta = QLabel("Preview Page Image(s)"); self.meta.setStyleSheet("font-weight:bold;") | |
| right_v.addWidget(self.meta) | |
| right_v.addWidget(self.stack) | |
| right_v.addWidget(nav_bar) | |
| splitter = QSplitter() | |
| splitter.addWidget(self.tree) | |
| splitter.addWidget(right_container) | |
| splitter.setStretchFactor(0,1); splitter.setStretchFactor(1,3) | |
| splitter.setSizes([360, 840]) | |
| self.btn_copy_path = QPushButton(QIcon(ICO_ICO["gray_clipboard"]), "Copy CPL File Path") | |
| self.btn_copy_path.setFont(txts_fnt) | |
| self.btn_copy_path.setEnabled(False) | |
| self.btn_open = QPushButton(QIcon(ICO_ICO["gray_open_folder"]), "Open CPL File") | |
| self.btn_open.setFont(txts_fnt) | |
| self.btn_open.setEnabled(False) | |
| btn_cancel = QPushButton(QIcon(ICO_ICO["gray_exit"]), "Cancel") | |
| btn_cancel.setFont(txts_fnt) | |
| btn_h = QHBoxLayout() | |
| btn_h.addStretch(); btn_h.addWidget(self.btn_copy_path) | |
| btn_h.addWidget(self.btn_open); btn_h.addWidget(btn_cancel) | |
| main = QVBoxLayout(self); main.addWidget(splitter); main.addLayout(btn_h) | |
| self.pages, self.byps_opn = [], False | |
| self.selected_path = None | |
| self.right_container = right_container | |
| self.tree.selectionModel().selectionChanged.connect(self.on_selection_changed) | |
| self.btn_prev.clicked.connect(self.on_prev) | |
| self.btn_next.clicked.connect(self.on_next) | |
| self.btn_copy_path.setFixedHeight(32) | |
| self.btn_copy_path.clicked.connect(self.copy_path) | |
| self.btn_open.setFixedHeight(32) | |
| self.btn_open.clicked.connect(self.on_accept) | |
| btn_cancel.setFixedHeight(32) | |
| btn_cancel.clicked.connect(self.on_cancelled) | |
| def on_selection_changed(self, selected, deselected): | |
| idxs = self.tree.selectionModel().selectedRows() | |
| if not idxs: | |
| self.clear_preview(); return | |
| idx = idxs[0] | |
| if not idx.isValid(): | |
| self.clear_preview(); return | |
| path = self.model.filePath(idx) | |
| if os.path.isdir(path): | |
| self.clear_preview(message=f"Directory: {path}"); return | |
| allowed = {".cplq", ".cplp", ".cpls"} | |
| _, ext = os.path.splitext(path); ext = ext.lower() | |
| if ext not in allowed: | |
| self.clear_preview(message=f"Unsupported: {ext}"); return | |
| try: | |
| size = os.path.getsize(path) | |
| max_preview = 900 * 1024 | |
| if size > max_preview: | |
| with open(path, "r", errors="replace") as f: | |
| text = f.read(450*1024) | |
| text += "\n\n// Preview truncated (file too large)" | |
| else: | |
| with open(path, "r", errors="replace") as f: | |
| text = f.read() | |
| except Exception as e: | |
| text = f"Could not read file: {e}" | |
| size = 0 | |
| self.populate_pages(text, path, size) | |
| def populate_pages(self, text, path, size): | |
| self.pages = self.paginator.paginate(text) or [""] | |
| for i in range(self.stack.count()-1, -1, -1): | |
| w = self.stack.widget(i) | |
| self.stack.removeWidget(w) | |
| w.deleteLater() | |
| for chunk in self.pages: | |
| lbl = CodePageLabel(chunk, QSize(self.page_width, self.page_height), self.font) | |
| self.stack.addWidget(lbl) | |
| self.current_index = 0 | |
| self.update_nav() | |
| self.meta.setText(f"{path} — {size} bytes") | |
| self.btn_open.setEnabled(True) | |
| self.btn_copy_path.setEnabled(True) | |
| self.selected_path = path | |
| def clear_preview(self, message="Preview Page Image(s)"): | |
| self.meta.setText(message) | |
| for i in range(self.stack.count()-1, -1, -1): | |
| w = self.stack.widget(i) | |
| self.stack.removeWidget(w) | |
| w.deleteLater() | |
| self.pages = [] | |
| self.btn_prev.setEnabled(False); self.btn_next.setEnabled(False) | |
| self.page_label.setText("Page Image 0 / 0") | |
| self.btn_open.setEnabled(False); self.btn_copy_path.setEnabled(False) | |
| self.selected_path = None | |
| def update_nav(self): | |
| total = len(self.pages) | |
| if total == 0: | |
| self.btn_prev.setEnabled(False); self.btn_next.setEnabled(False) | |
| self.page_label.setText("Page Image 0 / 0"); return | |
| if not hasattr(self, "current_index") or self.current_index < 0: | |
| self.current_index = 0 | |
| if self.current_index >= total: | |
| self.current_index = total - 1 | |
| self.stack.setCurrentIndex(self.current_index) | |
| self.page_label.setText(f"Page Image {self.current_index+1} / {total}") | |
| self.btn_prev.setEnabled(self.current_index > 0) | |
| self.btn_next.setEnabled(self.current_index < total-1) | |
| def on_prev(self): | |
| if getattr(self, "current_index", 0) > 0: | |
| self.current_index -= 1 | |
| self.update_nav() | |
| def on_next(self): | |
| if getattr(self, "current_index", 0) < len(self.pages)-1: | |
| self.current_index += 1 | |
| self.update_nav() | |
| def copy_path(self): | |
| if not self.selected_path: | |
| return | |
| QApplication.clipboard().setText(self.selected_path) | |
| def on_accept(self): | |
| if not self.selected_path: | |
| return | |
| self.byps_opn = True | |
| self.selected_file = self.selected_path | |
| write_file(OPN_CFG_PTH, f"open:)^(:{self.selected_file}") | |
| self.accept() | |
| def on_cancelled(self): | |
| self.close() | |
| def closeEvent(self, event): | |
| try: | |
| super().closeEvent(event) | |
| if not self.byps_opn: write_file(OPN_CFG_PTH, f"cancel:)^(:null") | |
| except Exception: | |
| pass | |
| class CecilPlusContextMenu: | |
| def __init__(self): | |
| pass | |
| class MiniContextMenu: | |
| def __init__(self, parent=None, fg_color="#454545", bg_color="#59E9EE"): | |
| self.fg = fg_color; self.bg = bg_color | |
| def attach(self, line_edit: QLineEdit): | |
| line_edit.setContextMenuPolicy(Qt.CustomContextMenu) | |
| line_edit.customContextMenuRequested.connect(lambda pos, le=line_edit: self.show_menu(le, pos)) | |
| def show_menu(self, line_edit: QLineEdit, pos: QPoint): | |
| menu = QMenu(line_edit) | |
| menu.setFont(QFont('Segoe UI', 10)) | |
| menu.setStyleSheet("QMenu {background-color: %s; color: %s;}" | |
| "QMenu::item:selected {background-color: %s; color: %s;}" | |
| % (self.bg, self.fg, self.fg, self.bg)) | |
| act_cut = QAction("Cut", menu); act_cut.triggered.connect(line_edit.cut) | |
| act_copy = QAction("Copy", menu); act_copy.triggered.connect(line_edit.copy) | |
| act_paste = QAction("Paste", menu); act_paste.triggered.connect(line_edit.paste) | |
| act_sel_all = QAction("Select Line", menu); act_sel_all.triggered.connect(line_edit.selectAll) | |
| act_cut.setEnabled(not line_edit.isReadOnly() and line_edit.hasSelectedText()) | |
| act_copy.setEnabled(line_edit.hasSelectedText()) | |
| clipboard = QApplication.clipboard() | |
| act_paste.setEnabled(not line_edit.isReadOnly() and bool(clipboard.text())) | |
| act_sel_all.setEnabled(len(line_edit.text()) > 0) | |
| menu.addAction(act_cut); menu.addAction(act_copy) | |
| menu.addAction(act_paste); menu.addAction(act_sel_all) | |
| menu.exec_(line_edit.mapToGlobal(pos)) | |
| class FindDialog(QDialog): | |
| def __init__(self, parent=None): | |
| try: | |
| super().__init__(parent) | |
| self.setFont(QFont('Segoe UI', 10)) | |
| self.setStyleSheet(DLG_STYLE_SHEET) | |
| self.setWindowTitle("Find / Replace") | |
| self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) | |
| self.setFixedSize(600, 285) | |
| main = QVBoxLayout(self) | |
| grid = QGridLayout() | |
| lbl_fnt = QFont("Segoe UI", 10) | |
| fnd_lbl = QLabel("Find:") | |
| fnd_lbl.setFont(lbl_fnt) | |
| grid.addWidget(fnd_lbl, 0, 0) | |
| input_fnt = QFont("Segoe UI", 10) | |
| bttn_fnt = QFont("Segoe UI", 10) | |
| txt_bx_style = "QLineEdit{border: 1px solid rgba(99,99,99,0.22); border-radius: 5px; padding: 3px 3px; background: #444444;}" | |
| self.find_input = ClearableLineEdit() | |
| self.find_input.setFixedHeight(34) | |
| self.find_input.setFont(input_fnt) | |
| self.find_input.setStyleSheet(txt_bx_style) | |
| grid.addWidget(self.find_input, 0, 1, 1, 3) | |
| rplc_lbl = QLabel("Reinsert with:") | |
| rplc_lbl.setFont(lbl_fnt) | |
| grid.addWidget(rplc_lbl, 1, 0) | |
| self.replace_input = ClearableLineEdit() | |
| self.replace_input.setFixedHeight(34) | |
| self.replace_input.setFont(input_fnt) | |
| self.replace_input.setStyleSheet(txt_bx_style) | |
| self.ctx_helper = MiniContextMenu(self, fg_color="#484848", bg_color="#89E9EE") | |
| self.ctx_helper.attach(self.find_input) | |
| self.ctx_helper.attach(self.replace_input) | |
| self.crnt_fnd_txt = "" | |
| self.crnt_fnd_cnt = 1 | |
| grid.addWidget(self.replace_input, 1, 1, 1, 3) | |
| main.addLayout(grid) | |
| btns = QHBoxLayout() | |
| btns2 = QHBoxLayout() | |
| spcr_lyt = QHBoxLayout() | |
| self.find_next_btn = QPushButton(QIcon(ICO_ICO["gray_search"]), "Find Next", self) | |
| self.find_next_btn.setFont(bttn_fnt) | |
| self.find_next_btn.setFixedHeight(29) | |
| self.pdl_btn = QPushButton("]|[", self) | |
| self.pdl_btn.setFont(bttn_fnt) | |
| self.pdl_btn.setFixedHeight(29) | |
| self.opr_btn = QPushButton(")^(", self) | |
| self.opr_btn.setFont(bttn_fnt) | |
| self.opr_btn.setFixedHeight(29) | |
| self.replace_btn = QPushButton("Reinsert", self) | |
| self.replace_btn.setFont(bttn_fnt) | |
| self.replace_btn.setFixedHeight(29) | |
| self.replace_all_btn = QPushButton("Reinsert All", self) | |
| self.replace_all_btn.setFont(bttn_fnt) | |
| self.replace_all_btn.setFixedHeight(29) | |
| self.close_btn = QPushButton(QIcon(ICO_ICO["gray_exit"]), "Close", self) | |
| self.close_btn.setFont(bttn_fnt) | |
| self.close_btn.setFixedHeight(29) | |
| self.crct_btn = QPushButton("//|_CRCT_|:") | |
| self.crct_btn.setFont(bttn_fnt) | |
| self.crct_btn.setFixedHeight(29) | |
| self.undo_btn = QPushButton(QIcon(ICO_ICO["gray_undo"]), "Undo Reinsert All") | |
| self.undo_btn.setFont(bttn_fnt) | |
| self.undo_btn.setFixedHeight(28) | |
| self.redo_btn = QPushButton(QIcon(ICO_ICO["gray_redo"]), "Redo Reinsert All") | |
| self.redo_btn.setFont(bttn_fnt) | |
| self.redo_btn.setFixedHeight(28) | |
| btns.addWidget(self.find_next_btn) | |
| self.find_next_btn.clicked.connect(self.find) | |
| btns.addWidget(self.pdl_btn) | |
| self.pdl_btn.clicked.connect(self.pipe_find) | |
| btns.addWidget(self.opr_btn) | |
| self.opr_btn.clicked.connect(self.operator_find) | |
| btns.addWidget(self.replace_btn) | |
| self.replace_btn.clicked.connect(self.replace) | |
| btns.addWidget(self.replace_all_btn) | |
| self.replace_all_btn.clicked.connect(self.replace_all_btn_act) | |
| spacer = QSpacerItem(20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) | |
| btns.addItem(spacer) | |
| btns.addWidget(self.close_btn) | |
| self.close_btn.clicked.connect(self.close) | |
| self.crct_btn.clicked.connect(self.crct_cmmnt) | |
| btns2.addWidget(self.crct_btn) | |
| self.undo_btn.clicked.connect(self.replace_undo) | |
| btns2.addWidget(self.undo_btn) | |
| self.redo_btn.clicked.connect(self.replace_redo) | |
| btns2.addWidget(self.redo_btn) | |
| main.addLayout(btns) | |
| spcr_lbl = QLabel(" ") | |
| spcr_lbl.setFixedHeight(10) | |
| spcr_lyt.addWidget(spcr_lbl) | |
| main.addLayout(spcr_lyt) | |
| main.addLayout(btns2) | |
| info_lbl = QHBoxLayout() | |
| fdbck_lbl_fnt = QFont("Segoe UI", 9) | |
| self.info_lbl_txt = QLabel(f' {self.format_time("Indian/Mahe")}', self) | |
| self.info_lbl_txt.setFont(fdbck_lbl_fnt) | |
| self.info_lbl_txt.setStyleSheet("color: #777777") | |
| feedback_lbl = QHBoxLayout() | |
| self.feedback_lbl_txt = QLabel(" > ready", self) | |
| self.feedback_lbl_txt.setFont(fdbck_lbl_fnt) | |
| self.feedback_lbl_txt.setStyleSheet("color: #777777") | |
| info_lbl.addWidget(self.info_lbl_txt) | |
| main.addLayout(info_lbl) | |
| feedback_lbl.addWidget(self.feedback_lbl_txt) | |
| main.addLayout(feedback_lbl) | |
| bttn_recent = QVBoxLayout(self) | |
| self.bttn_recent_find = QPushButton("\u25BC Stored recent search/find list item choices \u25BC", self) | |
| self.bttn_recent_find.setFont(bttn_fnt) | |
| self.bttn_recent_find.setFixedHeight(28) | |
| bttn_recent.addWidget(self.bttn_recent_find) | |
| self.popup = QFrame(self, Qt.Popup | Qt.FramelessWindowHint) | |
| popup_layout = QVBoxLayout(self.popup) | |
| self.list_view = QListView(self.popup) | |
| lst_vw_f = QFont() | |
| lst_vw_f.setPointSize(10) | |
| lst_vw_f.setBold(False) | |
| self.list_view.setFont(lst_vw_f) | |
| popup_layout.addWidget(self.list_view) | |
| if os.path.isfile(FND_LST_PTH): | |
| fnd_lst = read_file(FND_LST_PTH) | |
| fnd_lst = fnd_lst.split(':)^(:') | |
| if len(fnd_lst) > 0: self.model = QStringListModel(fnd_lst, self.list_view) | |
| else: self.model = QStringListModel(['member'], self.list_view) | |
| else: self.model = QStringListModel(['member'], self.list_view) | |
| self.list_view.setModel(self.model) | |
| self.list_view.setEditTriggers(QAbstractItemView.NoEditTriggers) | |
| self.list_view.setSelectionMode(QAbstractItemView.SingleSelection) | |
| self.bttn_recent_find.clicked.connect(self.show_list_popup) | |
| self.list_view.clicked.connect(self.on_recent_clicked) | |
| main.addLayout(bttn_recent) | |
| if os.path.isfile(FND_CFG_PTH): | |
| fnd_cfg = read_file(FND_CFG_PTH).split(':)^(:') | |
| if fnd_cfg[1] != 'null': self.find_input.setText(fnd_cfg[1]) | |
| if fnd_cfg[2] != 'null': self.replace_input.setText(fnd_cfg[2]) | |
| write_file(FND_CFG_PTH, f'open:)^(:{fnd_cfg[1]}:)^(:{fnd_cfg[2]}') | |
| else: write_file(FND_CFG_PTH, f'open:)^(:null:)^(:null') | |
| write_file(RPL_CFG_PTH, "null") | |
| self._rplc_undo, self._rplc_lmtd, self._rplc_byps = False, False, False | |
| self._rplc_luae, self._rplc_hlt = False, False | |
| self._rplc_ucnt, self._rplc_rcnt, self._rplc_lcnt = -1, -1, 0 | |
| self.editor = None | |
| except Exception: | |
| pass | |
| def find(self, isPipeInstances=False, isOperatorInstances=False, isCircuitComments=False): | |
| try: | |
| if not self.editor: | |
| self.feedback_lbl_txt.setText(f" > no editor open..") | |
| return | |
| else: self.editor.setExtraSelections([]) | |
| if isPipeInstances: text = "pipe" | |
| elif isOperatorInstances: text = "operator" | |
| elif isCircuitComments: text = "//|_CRCT_|:" | |
| else: text = self.find_input.text() | |
| if len(text) < 1: | |
| return | |
| cursor = self.editor.textCursor() | |
| pos = cursor.selectionEnd() | |
| doc = self.editor.document() | |
| found = doc.find(text, pos) | |
| fnd_cnd = False | |
| if not found.isNull(): fnd_cnd = True | |
| else: | |
| found = doc.find(text, 0) | |
| if not found.isNull(): fnd_cnd = True | |
| if fnd_cnd: | |
| self.editor.setTextCursor(found) | |
| start_pos = min(cursor.selectionStart(), cursor.selectionEnd()) | |
| if text == self.crnt_fnd_txt: | |
| self.crnt_fnd_cnt+=1 | |
| self.feedback_lbl_txt.setText(f" > found '{text}' ({self.crnt_fnd_cnt})") | |
| else: | |
| self.crnt_fnd_cnt = 1 | |
| self.crnt_fnd_txt = text | |
| self.feedback_lbl_txt.setText(f" > found '{text}' (1)") | |
| if not isPipeInstances and not isOperatorInstances and not isCircuitComments: | |
| fnd_items = self.model.stringList() | |
| if text not in fnd_items: fnd_items = [text]+fnd_items | |
| if len(fnd_items) > 66: fnd_items = fnd_items[:66] | |
| self.model.setStringList(fnd_items) | |
| else: self.feedback_lbl_txt.setText(f" > not found '{text}'") | |
| except Exception: | |
| return | |
| def pipe_find(self): | |
| self.find(True, False, False) | |
| def operator_find(self): | |
| self.find(False, True, False) | |
| def crct_cmmnt(self): | |
| self.find(False, False, True) | |
| def show_list_popup(self): | |
| try: | |
| btn_pos = self.bttn_recent_find.mapToGlobal(QPoint(0, self.bttn_recent_find.height())) | |
| self.popup.setFixedWidth(self.bttn_recent_find.width()) | |
| self.popup.move(btn_pos) | |
| self.popup.show() | |
| self.list_view.setFocus() | |
| except Exception: | |
| pass | |
| def on_recent_clicked(self, index: QModelIndex): | |
| try: | |
| txt = index.data(Qt.DisplayRole) | |
| self.find_input.setText(txt) | |
| self.popup.hide() | |
| except Exception: | |
| pass | |
| def replace(self): | |
| try: | |
| if not self.editor: | |
| self.feedback_lbl_txt.setText(f" > no editor open..") | |
| return | |
| rplc_txt = self.replace_input.text() | |
| fnd_txt = self.find_input.text() | |
| if len(rplc_txt) < 1: | |
| return | |
| cursor = self.editor.textCursor() | |
| selection = cursor.selectedText() | |
| match_current = False | |
| if cursor.hasSelection(): | |
| match_current = selection == fnd_txt | |
| if not match_current: | |
| self.find() | |
| cursor = self.editor.textCursor() | |
| if not cursor.hasSelection(): | |
| return | |
| cursor.beginEditBlock() | |
| cursor.insertText(rplc_txt) | |
| self.feedback_lbl_txt.setText(f" > total reinserted (1)") | |
| cursor.endEditBlock() | |
| except Exception: | |
| pass | |
| def replace_all_btn_act(self): | |
| if self._rplc_ucnt == -1: self.replace_all() | |
| else: self.feedback_lbl_txt.setText(f" > no reinsert all can be done(an undo action was called)") | |
| def replace_all(self): | |
| if not self.editor: | |
| self.feedback_lbl_txt.setText(f" > no editor open..") | |
| return | |
| count = None | |
| fnd_txt = self.find_input.text() | |
| rplc_txt = self.replace_input.text() | |
| if len(fnd_txt) < 1: | |
| return | |
| try: | |
| doc = self.editor.document() | |
| cursor, count = QTextCursor(doc), None | |
| cursor.beginEditBlock() | |
| try: | |
| src = doc.toPlainText() | |
| src_lenA = len(src) | |
| count = src.count(fnd_txt) | |
| new_text = src.replace(fnd_txt, rplc_txt) | |
| doc.setPlainText(new_text) | |
| src = doc.toPlainText() | |
| src_lenB = len(src) | |
| delta = src_lenB-src_lenA | |
| pct, cnt_prt = None, None | |
| if src_lenA > 0: pct = (delta/src_lenA)*100.0 | |
| if count == 0: cnt_prt = " > No reinserted" | |
| else: | |
| if not self._rplc_byps: | |
| rpl_cfg = read_file(RPL_CFG_PTH) | |
| if rpl_cfg == "null": write_file(RPL_CFG_PTH, f"{fnd_txt}:)^(:{rplc_txt}") | |
| else: write_file(RPL_CFG_PTH, f"{rpl_cfg}]^:^[{fnd_txt}:)^(:{rplc_txt}") | |
| cnt_prt = f" > Reinserted {count} occurrences" | |
| if not self._rplc_byps: | |
| sign = "+" if delta >= 0 else "-" | |
| delta = abs(delta) | |
| pct_prt = f" - net {sign}{abs(pct):.1f}%" if pct is not None else "" | |
| self.feedback_lbl_txt.setText(f"{cnt_prt} · (∆chars: {sign}{delta}{pct_prt})") | |
| except Exception: | |
| cursor.endEditBlock() | |
| return | |
| cursor.endEditBlock() | |
| except Exception: | |
| return | |
| def set_uri_cfg(self, isUndo): | |
| if isUndo: | |
| if self._rplc_ucnt < 0: | |
| if self._rplc_lcnt == 1: self._rplc_ucnt = 0 | |
| else: self._rplc_ucnt = self._rplc_lcnt-1 | |
| self._rplc_rcnt = self._rplc_ucnt | |
| self._rplc_undo = True | |
| return | |
| if self._rplc_luae: | |
| if self._rplc_ucnt-1 < 0: | |
| self.feedback_lbl_txt.setText(" > no more undo reinsert all available") | |
| self._rplc_hlt = True | |
| else: | |
| self._rplc_ucnt -= 1 | |
| self._rplc_rcnt = self._rplc_ucnt | |
| else: | |
| if not self._rplc_undo: | |
| self.feedback_lbl_txt.setText(" > no redo reinsert all to apply(no undo done)") | |
| self._rplc_hlt = True | |
| return | |
| if not self._rplc_luae: | |
| if self._rplc_rcnt+1 == self._rplc_lcnt: | |
| self.feedback_lbl_txt.setText(" > no more redo reinsert all available") | |
| self._rplc_hlt = True | |
| else: | |
| self._rplc_rcnt += 1 | |
| self._rplc_ucnt = self._rplc_rcnt | |
| def rpt_rpl(self, isUndo, rpl_cfg): | |
| self.set_uri_cfg(isUndo) | |
| if isUndo: | |
| cnt_idx, rpl_t, prv_c = self._rplc_ucnt, (1, 0), True | |
| else: | |
| cnt_idx, rpl_t, prv_c = self._rplc_rcnt, (0, 1), False | |
| if not self._rplc_hlt: | |
| if self._rplc_lcnt > 1: rpl_cfg = rpl_cfg[cnt_idx] | |
| rpl_cfg = rpl_cfg.split(":)^(:") | |
| self.find_input.setText(rpl_cfg[rpl_t[0]]) | |
| self.replace_input.setText(rpl_cfg[rpl_t[1]]) | |
| self._rplc_byps = True | |
| self.replace_all() | |
| self._rplc_byps = False | |
| self._rplc_luae = prv_c | |
| if isUndo: | |
| self.feedback_lbl_txt.setText(f" > undo reinsert all done(reinsert index [{cnt_idx}] used)") | |
| else: | |
| self.feedback_lbl_txt.setText(f" > redo reinsert all done(reinsert index [{cnt_idx}] used)") | |
| def set_rpl_cfg(self, isUndo): | |
| try: | |
| if not self.editor: | |
| self.feedback_lbl_txt.setText(f" > no editor open..") | |
| return | |
| self._rplc_hlt = False | |
| rpl_cfg = read_file(RPL_CFG_PTH) | |
| if rpl_cfg != "null": | |
| if rpl_cfg.find("]^:^[") > -1: | |
| rpl_cfg = rpl_cfg.split("]^:^[") | |
| self._rplc_lcnt = len(rpl_cfg) | |
| else: self._rplc_lcnt = 1 | |
| else: | |
| if isUndo: self.feedback_lbl_txt.setText(" > no undo reinsert all available") | |
| else: self.feedback_lbl_txt.setText(" > no redo reinsert all available") | |
| return | |
| self.rpt_rpl(isUndo, rpl_cfg) | |
| except Exception: | |
| pass | |
| def replace_undo(self): | |
| self.set_rpl_cfg(True) | |
| def replace_redo(self): | |
| self.set_rpl_cfg(False) | |
| def format_time(self, offset_hours): | |
| try: | |
| tz = zoneinfo.ZoneInfo(offset_hours) | |
| now = datetime.now(tz) | |
| hour_min = f"{now.hour:02d}:{now.minute:02d}" | |
| weekday = now.strftime("%a") | |
| month = str(now.month) | |
| year = str(now.year) | |
| off = now.utcoffset() or timedelta(0) | |
| total_minutes = int(off.total_seconds()//60) | |
| sign = "+" if total_minutes >= 0 else "-" | |
| hh = abs(total_minutes)//60 | |
| mm = abs(total_minutes)%60 | |
| offset_str = f"UTC{sign}{hh:02d}:{mm:02d}" | |
| week = now.isocalendar()[1] | |
| return f"{hour_min} {weekday}/{month}/{year} {offset_str} Week {week}" | |
| except Exception: | |
| return "error @format_time(), couldn't resolve valid UTC Time Format" | |
| def showEvent(self, event): | |
| super().showEvent(event) | |
| def closeEvent(self, event): | |
| try: | |
| super().closeEvent(event) | |
| fnd_txt = self.find_input.text() | |
| if len(fnd_txt) < 1: fnd_txt = 'null' | |
| rpl_txt = self.replace_input.text() | |
| if len(rpl_txt) < 1: rpl_txt = 'null' | |
| write_file(FND_CFG_PTH, f'closed:)^(:{fnd_txt}:)^(:{rpl_txt}') | |
| fnd_items = self.model.stringList() | |
| fnd_items = ":)^(:".join(fnd_items) | |
| write_file(FND_LST_PTH, fnd_items) | |
| except Exception: | |
| pass | |
| class AboutDialog(QDialog): | |
| def __init__(self, parent=None): | |
| super().__init__(parent) | |
| self.setStyleSheet(DLG_STYLE_SHEET) | |
| self.setWindowTitle("About") | |
| self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) | |
| self.setModal(True) | |
| icon_label = QLabel(self) | |
| qicon = CPL_ICO.get("ki_resolver") | |
| pix = qicon.pixmap(48, 48) | |
| icon_label.setPixmap(pix) | |
| icon_label.setFixedSize(QSize(48, 48)) | |
| title_label = QLabel("<b>Cecil+</b> <span style='font-weight:normal'>(v1.001.039)</span>", self) | |
| title_font = title_label.font() | |
| title_font.setBold(True) | |
| title_font.setPointSize(title_font.pointSize() + 2) | |
| title_label.setFont(title_font) | |
| line1 = QLabel("Build Date: ---------------", self) | |
| line2 = QLabel("Linker Libraries: ---------------", self) | |
| line3 = QLabel("Runtime CMUT Libraries: ---------------", self) | |
| line4 = QLabel("Spectral PMQ Libraries: ---------------", self) | |
| small_font = line1.font() | |
| small_font.setPointSize(max(8, small_font.pointSize() - 1)) | |
| small_font.setBold(False) | |
| line1.setFont(small_font); line2.setFont(small_font) | |
| line3.setFont(small_font); line4.setFont(small_font) | |
| ok_btn = QPushButton("ACCEPT", self) | |
| ok_btn.setDefault(True) | |
| ok_btn.clicked.connect(self.accept) | |
| text_v = QVBoxLayout() | |
| text_v.setContentsMargins(0, 0, 0, 0) | |
| text_v.addWidget(title_label) | |
| text_v.addWidget(line1); text_v.addWidget(line2) | |
| text_v.addWidget(line3); text_v.addWidget(line4) | |
| text_v.addStretch() | |
| bottom_row = QHBoxLayout() | |
| bottom_row.addStretch() | |
| bottom_row.addWidget(ok_btn) | |
| text_v.addLayout(bottom_row) | |
| main = QHBoxLayout(self) | |
| main.addWidget(icon_label, 0, Qt.AlignTop) | |
| main.addLayout(text_v) | |
| self.setLayout(main) | |
| self.setFixedSize(self.sizeHint().expandedTo(QSize(360, 140))) | |
| class TreeIconFileSystemModel(QFileSystemModel): | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| lst_page_icons = create_cecil_plus_page_icons() | |
| self.page_icons = {'folder': QIcon(ICO_ICO["folder"]), 'file': lst_page_icons[0], '.cplq': lst_page_icons[1], | |
| '.cplp': lst_page_icons[2], '.cpls': lst_page_icons[3]} | |
| def data(self, index, role=Qt.DisplayRole): | |
| if role == Qt.DecorationRole and index.column() == 0: | |
| if self.isDir(index): | |
| return self.page_icons['folder'] | |
| path = self.filePath(index) | |
| _, ext = os.path.splitext(path) | |
| ext = ext.lower() | |
| if ext == '.cplq' or ext == '.cplp' or ext == '.cpls': | |
| return self.page_icons[ext] | |
| else: | |
| return self.page_icons['file'] | |
| return super().data(index, role) | |
| class FileExplorer(QTreeView): | |
| fileOpened = pyqtSignal(str) | |
| def __init__(self, parent=None, rootPath=""): | |
| super().__init__(parent) | |
| self.model = TreeIconFileSystemModel() | |
| self.model.setRootPath(rootPath or os.path.expanduser("~")) | |
| self.setModel(self.model) | |
| self.setRootIndex(self.model.index(rootPath or os.path.expanduser("~"))) | |
| self.doubleClicked.connect(self.onDoubleClick) | |
| self.setColumnHidden(0, False) | |
| self.setColumnHidden(1, True) | |
| self.setColumnHidden(2, True) | |
| self.setColumnHidden(3, True) | |
| def onDoubleClick(self, idx): | |
| if not idx.isValid(): | |
| return | |
| path = self.model.filePath(idx) | |
| if QFileInfo(path).isFile(): self.fileOpened.emit(path) | |
| class StatusBar(QStatusBar): | |
| def __init__(self, parent=None): | |
| super().__init__(parent) | |
| container = QWidget(self) | |
| self._layout = QHBoxLayout(container) | |
| self._layout.setContentsMargins(4, 0, 4, 0) | |
| self._layout.setSpacing(6) | |
| self.left_btns = QWidget(container) | |
| left_layout = QHBoxLayout(self.left_btns) | |
| left_layout.setContentsMargins(0, 0, 0, 0) | |
| left_layout.setSpacing(4) | |
| btn_style = f"""QPushButton {{color: rgba(68, 68, 68, 1.0); background: rgba(78, 78, 78, 0.5); | |
| padding: 10px 16px}} QPushButton::hover {{color: rgba(42, 42, 42, 1.8); | |
| background: rgba(84, 84, 84, 0.3)}}""" | |
| self.btn_krknB = QPushButton(QIcon(ICO_ICO["tag"]), "", self.left_btns) | |
| self.btn_krknB.setStyleSheet(btn_style) | |
| self.btn_krknB.setFixedSize(68, 32) | |
| self.btn_krknF = QPushButton(QIcon(ICO_ICO["link"]), "", self.left_btns) | |
| self.btn_krknF.setStyleSheet(btn_style) | |
| self.btn_krknF.setFixedSize(68, 32) | |
| for b in(self.btn_krknB, self.btn_krknF): | |
| b.setFocusPolicy(Qt.NoFocus); left_layout.addWidget(b) | |
| self._layout.addWidget(self.left_btns, 0, Qt.AlignLeft) | |
| self.status_label = QLabel(" > Ready", container) | |
| self.status_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) | |
| self._layout.addWidget(self.status_label, 1) | |
| self.right_btns = QWidget(container) | |
| right_layout = QHBoxLayout(self.right_btns) | |
| right_layout.setContentsMargins(8, 0, 7, 11) | |
| right_layout.setSpacing(4) | |
| btn_style2 = f"QPushButton::hover {{ color: rgba(22, 22, 22, 0.8) }}" | |
| self.btn_a = QPushButton("BTTN-A", self.right_btns); self.btn_a.setStyleSheet(btn_style2) | |
| self.btn_b = QPushButton("BTTN-B", self.right_btns); self.btn_b.setStyleSheet(btn_style2) | |
| self.btn_c = QPushButton("BTTN-C", self.right_btns); self.btn_c.setStyleSheet(btn_style2) | |
| self.btn_d = QPushButton("BTTN-D", self.right_btns); self.btn_d.setStyleSheet(btn_style2) | |
| for b in(self.btn_a, self.btn_b, self.btn_c, self.btn_d): | |
| b.setFocusPolicy(Qt.NoFocus); right_layout.addWidget(b) | |
| self._layout.addWidget(self.right_btns, 0, Qt.AlignRight) | |
| self.addPermanentWidget(container, 1) | |
| self._busy = False | |
| self._pulse_alpha = 0.0 | |
| self._pulse_dir = 1 | |
| self._pulse_timer = QTimer(self) | |
| self._pulse_timer.setInterval(42) | |
| self._pulse_timer.timeout.connect(self._on_pulse_timer) | |
| self.btn_krknB.clicked.connect(self._on_pattern_clicked) | |
| self.btn_krknF.clicked.connect(self._on_build_clicked) | |
| def showMessage(self, message, timeout=0): | |
| self.status_label.setText(message) | |
| if timeout > 0: | |
| QTimer.singleShot(timeout, lambda: self.status_label.setText("")) | |
| def clearMessage(self): | |
| self.status_label.setText("") | |
| def setBusy(self, busy: bool): | |
| busy = bool(busy) | |
| if busy == self._busy: | |
| return | |
| self._busy = busy | |
| if busy: | |
| self._pulse_alpha = 0.0 | |
| self._pulse_dir = 1 | |
| self._pulse_timer.start() | |
| else: | |
| self._pulse_timer.stop() | |
| self._pulse_alpha = 0.0 | |
| self.update() | |
| def setLeftButtonsVisible(self, visible: bool): | |
| self.left_btns.setVisible(visible) | |
| def setRightButtonsVisible(self, visible: bool): | |
| self.right_btns.setVisible(visible) | |
| def _on_pulse_timer(self): | |
| step = 0.08 | |
| self._pulse_alpha += self._pulse_dir * step | |
| if self._pulse_alpha >= 1.0: | |
| self._pulse_alpha = 1.0 | |
| self._pulse_dir = -1 | |
| elif self._pulse_alpha <= 0.0: | |
| self._pulse_alpha = 0.0 | |
| self._pulse_dir = 1 | |
| self.update() | |
| def paintEvent(self, event: QPaintEvent): | |
| super().paintEvent(event) | |
| if not self._busy: | |
| return | |
| p = QPainter(self) | |
| p.setRenderHint(QPainter.Antialiasing, True) | |
| base = QColor(255, 200, 0) | |
| alpha = int(180 * (0.35 + 0.65 * self._pulse_alpha)) | |
| base.setAlpha(alpha) | |
| p.setPen(base) | |
| p.setBrush(base) | |
| height = 3 | |
| rect = QRectF(0, self.height() - height, self.width(), height) | |
| p.drawRect(rect) | |
| p.end() | |
| @pyqtSlot() | |
| def _on_pattern_clicked(self): | |
| self.showMessage(" > Kraken AI Process - Spectral Object(s) Patternize", 2000) | |
| @pyqtSlot() | |
| def _on_build_clicked(self): | |
| self.showMessage(" > Kraken AI Process - Spectral Object(s) Building...") | |
| self.setBusy(True) | |
| QTimer.singleShot(3000, lambda: (self.showMessage(" > Spectral Object(s) build finished", 2000), self.setBusy(False))) | |
| class CustomTabBar(QTabBar): | |
| tabClosedRequested = pyqtSignal(int) | |
| def __init__(self, parent=None): | |
| super().__init__(parent=None) | |
| pass | |
| def tabInserted(self, index): | |
| try: | |
| super().tabInserted(index) | |
| btn = QPushButton("X") | |
| btn.setCursor(Qt.PointingHandCursor) | |
| btn.setFixedSize(20, 20) | |
| btn.setFlat(True) | |
| btn.setStyleSheet("QPushButton {background: #888888; color: #303030; border: 1px solid #505050;}") | |
| self.setTabButton(index, QTabBar.RightSide, btn) | |
| btn.clicked.connect(lambda _checked, i=index: self.tabCloseRequested.emit(i)) | |
| except Exception: | |
| pass | |
| class MenuRegistry(QObject): | |
| def __init__(self): | |
| super().__init__() | |
| self.items = [] | |
| def register_menu_item(self, menu_path, descriptor): | |
| self.items.append((menu_path, descriptor)) | |
| MENU_REGISTRY = MenuRegistry() | |
| class CecilIDE(QMainWindow): | |
| def __init__(self): | |
| super().__init__() | |
| preload_cpl_icons() | |
| ico_pth = Path(f"{MDL_DIR}/Cecil+/ide-settings/ico-ico") | |
| if not os.path.isdir(ico_pth): build_unicode_icon_pngs() | |
| for ico_png in ico_pth.glob("*.png"): | |
| ico_key = ico_png.stem | |
| ICO_ICO[ico_key] = QPixmap(str(ico_png)) | |
| self.setWindowTitle('Cecil+') | |
| self.setGeometry(100, 100, 1100, 760) | |
| self._currentFiles, self._actions = {}, {} | |
| self._ut_cnt = 0 | |
| self.menu_structure = self.default_menu_structure() | |
| self._doc = None | |
| self._cur = None | |
| self._block = None | |
| self._start = None | |
| self._length = None | |
| self._end = None | |
| self.initUI() | |
| def default_menu_structure(self): | |
| return { | |
| "File": [{"id": "new", "label": "New", "shortcut": "Ctrl+Shift+N"}, | |
| {"id": "open", "label": "Open", "shortcut": "Ctrl+O"}, | |
| {"id": "open_template", "label": "Open Template", "shortcut": ""}, | |
| {"id": "open_linker_xml", "label": "Open Linker XML", "shortcut": ""}, | |
| {"id": "open_kraken_sqtpp", "label": "Open Kraken SQTPP", "shortcut": ""}, | |
| {"type": "separator"}, | |
| {"id": "new_project", "label": "New Project", "shortcut": ""}, | |
| {"id": "open_project", "label": "Open Project", "shortcut": ""}, | |
| {"id": "export", "label": "Export Project", "shortcut": ""}, | |
| {"id": "project_settings", "label": "Project Settings", "shortcut": ""}, | |
| {"id": "close_project", "label": "Close Project", "shortcut": ""}, | |
| {"type": "separator"}, | |
| {"id": "save", "label": "Save", "shortcut": "Ctrl+S"}, | |
| {"id": "save_as", "label": "Save As", "shortcut": ""}, | |
| {"id": "save_as_template", "label": "Save As Template", "shortcut": ""}, | |
| {"id": "save_all", "label": "Save All", "shortcut": "Ctrl+Shift+S"}, | |
| {"type": "separator"}, | |
| {"id": "apply_circuit_comments", "label": "Apply Circuit Comments", "shortcut": ""}, | |
| {"id": "remove_circuit_comments", "label": "Remove Circuit Comments", "shortcut": ""}, | |
| {"id": "refresh_circuit_comments", "label": "Refresh Circuit Comments", "shortcut": ""}, | |
| {"type": "separator"}, | |
| {"id": "settings", "label": "Settings", "shortcut": "Ctrl+Alt+S"}, | |
| {"id": "kraken_ai_settings", "label": "Kraken AI Settings", "shortcut": "Ctrl+Alt+K"}, | |
| {"id": "expression_unit_settings", "label": "Expression Unit Settings", "shortcut": "Ctrl+Alt+U"}, | |
| {"id": "linear_commutator_settings", "label": "Linear Commutator Settings", "shortcut": "Ctrl+Alt+L"}, | |
| {"type": "separator"}, | |
| {"id": "linker_properties", "label": "Linker Properties", "shortcut": "Ctrl+Alt+P"}, | |
| {"id": "linker_symbols", "label": "Linker Symbols", "shortcut": "Ctrl+Alt+O"}, | |
| {"id": "linker_methods_pmq", "label": "Linker Methods PMQ", "shortcut": "Ctrl+Alt+Q"}], | |
| "Edit": [ | |
| {"id": "undo", "label": "Undo", "shortcut": "Ctrl+Z"}, | |
| {"id": "redo", "label": "Redo", "shortcut": "Ctrl+Y"}, | |
| {"type": "separator"}, | |
| {"id": "cut", "label": "Cut", "shortcut": "Ctrl+X"}, | |
| {"id": "copy", "label": "Copy", "shortcut": "Ctrl+C"}, | |
| {"id": "paste", "label": "Paste", "shortcut": "Ctrl+V"}, | |
| {"id": "select_all", "label": "Select All", "shortcut": "Ctrl+A"}, | |
| {"type": "separator"}, | |
| {"id": "find", "label": "Find", "shortcut": "Ctrl+F"}, | |
| {"id": "find_in_files", "label": "Find in Files", "shortcut": "Ctrl+Shift+F"}, | |
| {"id": "copy_file_path", "label": "Copy Tab File Path", "shortcut": "Ctrl+Shift+C"}, | |
| {"id": "format", "label": "Format Document", "shortcut": "Shift+Alt+F"}, | |
| {"type": "separator"}, | |
| {"id": "insert_comment", "label": "Insert Comment", "shortcut": "Ctrl+/"}, | |
| {"id": "combine_strict_types_for_operator", "label": " Join Strict Types for Operator", "shortcut": ""}, | |
| {"type": "separator"}, | |
| {"id": "select_line", "label": "Select Line", "shortcut": "Ctrl+L"}, | |
| {"id": "indent_line", "label": "Indent Line", "shortcut": "Ctrl+["}, | |
| {"id": "unindent_line", "label": "Unindent Line", "shortcut": "Ctrl+]"}, | |
| {"id": "move_line_up", "label": "Move Line Up", "shortcut": "Ctrl+Shift+Up"}, | |
| {"id": "move_line_down", "label": "Move Line Down", "shortcut": "Ctrl+Shift+Down"}, | |
| {"id": "insert_blank_line_above", "label": "Insert Blank Line Above", "shortcut": "Ctrl+Alt+Enter"}, | |
| {"id": "insert_blank_line_below", "label": "Insert Blank Line Below", "shortcut": "Ctrl+Alt+Shift+Enter"}, | |
| {"id": "duplicate_line", "label": "Duplicate Line", "shortcut": "Ctrl+Shift+D"}, | |
| {"id": "del_trail_space", "label": "Del Trailing Line Space", "shortcut": "Ctrl+Backspace"}, | |
| {"id": "delete_line", "label": "Delete Line", "shortcut": "Ctrl+Shift+K"}, | |
| {"type": "separator"}, | |
| {"id": "char_map", "label": "Char Map", "shortcut": ""}], | |
| "View": [ | |
| {"id": "explorer", "label": "Explorer", "shortcut": "Ctrl+Shift+E"}, | |
| {"id": "terminal", "label": "Terminal", "shortcut": "Ctrl+`"}, | |
| {"type": "separator"}, | |
| {"id": "fold_all_functions", "label": "Fold All", "shortcut": "Alt+0"}, | |
| {"id": "unfold_all_functions", "label": "Unfold All", "shortcut": "Alt+Shift+0"}, | |
| {"id": "fold_current_function", "label": "Fold Current", "shortcut": "Ctrl+Alt+F"}, | |
| {"id": "unfold_current_function", "label": "Unfold Current", "shortcut": "Ctrl+Alt+Shift+F"}, | |
| {"type": "separator"}, | |
| {"id": "zen", "label": "Toggle Zen Mode", "shortcut": "F11"} | |
| ], | |
| "Navigate": [ | |
| {"id": "back", "label": "Back", "shortcut": "Alt+Left"}, | |
| {"id": "forward", "label": "Forward", "shortcut": "Alt+Right"}, | |
| {"id": "goto_def", "label": "Go to Definition", "shortcut": "F12"} | |
| ], | |
| "Build": [ | |
| {"id": "build", "label": "Build Project", "shortcut": "Ctrl+Shift+B"}, | |
| {"id": "rebuild", "label": "Rebuild", "shortcut": ""}, | |
| {"id": "clean", "label": "Clean", "shortcut": ""} | |
| ], | |
| "Run": [ | |
| {"id": "run", "label": "Run", "shortcut": "F5"}, | |
| {"id": "run_no_debug", "label": "Run Without Debugging", "shortcut": "Ctrl+F5"}, | |
| {"id": "run_tests", "label": "Run Tests", "submenu": [ | |
| {"id": "run_all_tests", "label": "Run All Tests"}, | |
| {"id": "run_current_test", "label": "Run Current Test"} | |
| ]} | |
| ], | |
| "Debug": [ | |
| {"id": "start_debug", "label": "Start Debugging", "shortcut": "F5"}, | |
| {"id": "stop_debug", "label": "Stop Debugging", "shortcut": "Shift+F5"}, | |
| {"id": "step_over", "label": "Step Over", "shortcut": "F10"} | |
| ], | |
| "Tools": [ | |
| {"id": "palette", "label": "Command Palette", "shortcut": "Ctrl+Shift+P"}, | |
| {"id": "extensions", "label": "Extensions...", "shortcut": ""} | |
| ], | |
| "Window": [ | |
| {"id": "next_editor", "label": "Next Editor", "shortcut": "Ctrl+Tab"}, | |
| {"id": "prev_editor", "label": "Previous Editor", "shortcut": "Ctrl+Shift+Tab"}, | |
| {"id": "reset_layout", "label": "Reset Layout", "shortcut": ""} | |
| ], | |
| "Help": [ | |
| {"id": "docs", "label": "Cecil+ Docs", "shortcut": "F1"}, | |
| {"id": "shortcuts", "label": "Keyboard Shortcuts", "shortcut": ""}, | |
| {"id": "about", "label": "About", "shortcut": ""} | |
| ] | |
| } | |
| def initUI(self): | |
| central = QWidget() | |
| self.setCentralWidget(central) | |
| hl = QHBoxLayout(central) | |
| # file explorer | |
| self.fileExplorer = FileExplorer(rootPath=os.path.expanduser("~")) | |
| self.fileExplorer.setMaximumWidth(320) | |
| self.fileExplorer.fileOpened.connect(self.openFile) | |
| # editor tabs | |
| self.tabs = QTabWidget() | |
| tab_bar = CustomTabBar() | |
| self.tabs.setTabBar(tab_bar) | |
| tab_bar.tabCloseRequested.connect(self.closeTab) | |
| hl.addWidget(self.fileExplorer) | |
| hl.addWidget(self.tabs) | |
| # terminal bar | |
| size = self.window() | |
| self.terminalBar = BottomBarWidget(None, size.height()) | |
| self.terminalBar.setVisible(False) | |
| hl.addWidget(self.terminalBar) | |
| self.terminalBar.set_status_text(":: Cecil+ terminal ready") | |
| self.terminalBar.append_terminal("/CMUT/v1.04 >>") | |
| self.sb = StatusBar(self) | |
| self.setStatusBar(self.sb) | |
| # toolbar & menu | |
| self.createActions() | |
| self.buildMenusFromStructure() | |
| self.createToolBar() | |
| self.createVertToolBar() | |
| # process any registered menu extensions | |
| self.apply_menu_registry() | |
| def createActions(self): | |
| a = self._actions | |
| # ---- FILE ---- | |
| a['new'] = QAction(QIcon(ICO_ICO["page"]), " New", self); a['new'].triggered.connect(self.newFile) | |
| a['open'] = QAction(QIcon(ICO_ICO["open_folder"]), " Open", self); a['open'].triggered.connect(self.cecilPlusOpenFile) | |
| a['open_template'] = QAction(QIcon(ICO_ICO["keyboard"]), " Open Template", self); a['open_template'].triggered.connect(self.openTemplate) | |
| a['open_linker_xml'] = QAction(QIcon(ICO_ICO["link"]), " Open Linker XML", self); a['open_linker_xml'].triggered.connect(self.openLinkerXML) | |
| a['open_kraken_sqtpp'] = QAction(QIcon(ICO_ICO["microscope"]), " Open Kraken SQTPP", self); a['open_kraken_sqtpp'].triggered.connect(self.openKrakenSQTPP) | |
| a['new_project'] = QAction(QIcon(ICO_ICO["package"]), " New Project", self); a['new_project'].triggered.connect(self.newProjectDialog) | |
| a['open_project'] = QAction(QIcon(ICO_ICO["user"]), " Open Project", self); a['open_project'].triggered.connect(self.openProjectDialog) | |
| a['export'] = QAction(QIcon(ICO_ICO["grid"]), " Export Project", self); a['export'].triggered.connect(self.exportProject) | |
| a['project_settings'] = QAction(QIcon(ICO_ICO["hammer_wrench"]), " Project Settings", self); a['project_settings'].triggered.connect(self.projectSettings) | |
| a['close_project'] = QAction(QIcon(ICO_ICO["close"]), " Close Project", self); a['close_project'].triggered.connect(self.closeProject) | |
| a['save'] = QAction(QIcon(ICO_ICO["save"]), " Save", self); a['save'].triggered.connect(self.saveFile) | |
| a['save_all'] = QAction(" Save All", self); a['save_all'].triggered.connect(self.saveAll) | |
| a['save_as'] = QAction(" Save As", self); a['save_as'].triggered.connect(self.saveFileAs) | |
| a['save_as_template'] = QAction(" Save As Template", self); a['save_as_template'].triggered.connect(self.saveAsTemplate) | |
| a['apply_circuit_comments'] = QAction(QIcon(ICO_ICO["add"]), " Apply Circuit Comments", self); a['apply_circuit_comments'].triggered.connect(self.applyCircuitComments) | |
| a['remove_circuit_comments'] = QAction(QIcon(ICO_ICO["remove"]), " Remove Circuit Comments", self); a['remove_circuit_comments'].triggered.connect(self.removeCircuitComments) | |
| a['refresh_circuit_comments'] = QAction(" Refresh Circuit Comments", self); a['refresh_circuit_comments'].triggered.connect(self.refreshCircuitComments) | |
| a['settings'] = QAction(QIcon(ICO_ICO["gear"]), " Settings", self); a['settings'].triggered.connect(self.settings) | |
| a['kraken_ai_settings'] = QAction(" Kraken AI Settings", self); a['kraken_ai_settings'].triggered.connect(self.krakenAiSettings) | |
| a['expression_unit_settings'] = QAction(" Expression Unit Settings", self); a['expression_unit_settings'].triggered.connect(self.expressionUnitSettings) | |
| a['linear_commutator_settings'] = QAction(" Linear CMUT Settings", self); a['linear_commutator_settings'].triggered.connect(self.linearCommutatorSettings) | |
| a['linker_properties'] = QAction(QIcon(ICO_ICO["info"]), " Linker Properties", self); a['linker_properties'].triggered.connect(self.linkerProperties) | |
| a['linker_symbols'] = QAction(" Linker Symbols", self); a['linker_symbols'].triggered.connect(self.linkerSymbols) | |
| a['linker_methods_pmq'] = QAction(" Linker Methods PMQ", self); a['linker_methods_pmq'].triggered.connect(self.linkerMethodsPMQ) | |
| # ---- EDIT ---- | |
| a['undo'] = QAction(QIcon(ICO_ICO["undo"]), " Undo", self); a['undo'].triggered.connect(self.undo) | |
| a['redo'] = QAction(QIcon(ICO_ICO["redo"]), " Redo", self); a['redo'].triggered.connect(self.redo) | |
| a['cut'] = QAction(QIcon(ICO_ICO["cut"]), " Cut", self); a['cut'].triggered.connect(self.cut) | |
| a['copy'] = QAction(QIcon(ICO_ICO["clipboard"]), " Copy", self); a['copy'].triggered.connect(self.copy) | |
| a['paste'] = QAction(QIcon(ICO_ICO["paste"]), " Paste", self); a['paste'].triggered.connect(self.paste) | |
| a['select_all'] = QAction(" Select All", self); a['select_all'].triggered.connect(self.select_all) | |
| a['find'] = QAction(QIcon(ICO_ICO["search"]), " Find", self); a['find'].triggered.connect(self.openFind) | |
| a['find_in_files'] = QAction(" Find in Files", self); a['find_in_files'].triggered.connect(self.findInFiles) | |
| a['copy_file_path'] = QAction(" Copy Tab File Path", self); a['copy_file_path'].triggered.connect(self.copyFilePath) | |
| a['format'] = QAction(" Format Document", self); a['format'].triggered.connect(self.formatDocument) | |
| a['insert_comment'] = QAction(QIcon(ICO_ICO["ellipsis"]), " Insert Comment", self); a['insert_comment'].triggered.connect(self.insert_comment) | |
| a['combine_strict_types_for_operator'] = QAction(" Join Strict Types for Operator", self); a['combine_strict_types_for_operator'].triggered.connect(self.join_strict_operator) | |
| a['select_line'] = QAction(QIcon(ICO_ICO["pencil"]), " Select Line", self); a['select_line'].triggered.connect(self.select_line) | |
| a['indent_line'] = QAction(" Indent Line", self); a['indent_line'].triggered.connect(self.indent_line) | |
| a['unindent_line'] = QAction(" Unindent Line", self); a['unindent_line'].triggered.connect(self.unindent_line) | |
| a['move_line_up'] = QAction(" Move Line Up", self); a['move_line_up'].triggered.connect(self.move_line_up) | |
| a['move_line_down'] = QAction(" Move Line Down", self); a['move_line_down'].triggered.connect(self.move_line_down) | |
| a['insert_blank_line_above'] = QAction(" Insert Blank Line Above", self); a['insert_blank_line_above'].triggered.connect(self.insert_blank_line_above) | |
| a['insert_blank_line_below'] = QAction(" Insert Blank Line Below", self); a['insert_blank_line_below'].triggered.connect(self.insert_blank_line_below) | |
| a['duplicate_line'] = QAction(" Duplicate Line", self); a['duplicate_line'].triggered.connect(self.duplicate_line) | |
| a['del_trail_space'] = QAction(" Del Trailing Line Space", self); a['del_trail_space'].triggered.connect(self.del_line_whitespace) | |
| a['delete_line'] = QAction(" Delete Line", self); a['delete_line'].triggered.connect(self.delete_line) | |
| a['char_map'] = QAction(QIcon(ICO_ICO["memo"]), " Character Map", self); a['char_map'].triggered.connect(self.char_map) | |
| # View | |
| a['explorer'] = QAction(QIcon(ICO_ICO["folder"]), " Explorer", self); a['explorer'].triggered.connect(self.toggleExplorer) | |
| a['terminal'] = QAction(QIcon(ICO_ICO["laptop"]), " Terminal", self); a['terminal'].triggered.connect(self.toggleTerminal) | |
| a['fold_all_functions'] = QAction(" Fold All", self); a['fold_all_functions'].triggered.connect(self.fold_all_func) | |
| a['unfold_all_functions'] = QAction(" Unfold All", self); a['unfold_all_functions'].triggered.connect(self.unfold_all_func) | |
| a['fold_current_function'] = QAction(" Fold Current", self); a['fold_current_function'].triggered.connect(self.fold_current_func) | |
| a['unfold_current_function'] = QAction(" Unfold Current", self); a['unfold_current_function'].triggered.connect(self.unfold_current_func) | |
| a['zen'] = QAction(" Full Screen", self); a['zen'].triggered.connect(self.toggleZen) | |
| # Build/Run/Debug | |
| a['build'] = QAction("Build", self); a['build'].triggered.connect(self.buildProject) | |
| a['rebuild'] = QAction("Rebuild", self); a['rebuild'].triggered.connect(self.rebuildProject) | |
| a['clean'] = QAction("Clean", self); a['clean'].triggered.connect(self.cleanProject) | |
| a['run'] = QAction("Run", self); a['run'].triggered.connect(self.runCurrent) | |
| a['run_no_debug'] = QAction("Run Without Debugging", self); a['run_no_debug'].triggered.connect(self.runWithoutDebug) | |
| a['start_debug'] = QAction("Start Debugging", self); a['start_debug'].triggered.connect(self.startDebugging) | |
| a['stop_debug'] = QAction("Stop Debugging", self); a['stop_debug'].triggered.connect(self.stopDebugging) | |
| # Tools/Git/Help | |
| a['palette'] = QAction("Command Palette", self); a['palette'].triggered.connect(self.openCommandPalette) | |
| a['extensions'] = QAction("Extensions...", self); a['extensions'].triggered.connect(self.openExtensions) | |
| a['docs'] = QAction("Cecil+ Docs", self); a['docs'].triggered.connect(self.openDocs) | |
| a['about'] = QAction("About", self); a['about'].triggered.connect(self.showAbout) | |
| def buildMenusFromStructure(self): | |
| sep_ucd = '\u2014' | |
| menu_sep = ''.join([sep_ucd for _ in range(13)]) | |
| menubar = self.menuBar() | |
| menubar.clear() | |
| for menuName, items in self.menu_structure.items(): | |
| menu = menubar.addMenu(menuName) | |
| menu.setStyleSheet(MENU_STYLESHEET) | |
| for it in items: | |
| if it.get('type') == 'separator': | |
| addMenuDivider(menu, it.get('text', ' ' + menu_sep)) | |
| else: | |
| if 'submenu' in it: | |
| sub = menu.addMenu(it['label']) | |
| for sit in it.get('submenu', []): | |
| act = self._actions.get(sit['id']) or QAction(sit['label'], self) | |
| sub.addAction(act) | |
| else: | |
| act = self._actions.get(it['id']) or QAction(it['label'], self) | |
| if it.get('shortcut'): | |
| act.setShortcut(it['shortcut']) | |
| menu.addAction(act) | |
| def apply_menu_registry(self): | |
| for path, desc in MENU_REGISTRY.items: | |
| parts = path.split('/') | |
| if not parts: | |
| continue | |
| menu = self.menuBar().findChild(type(self.menuBar()), parts[0]) | |
| targetMenu = None | |
| for m in self.menuBar().children(): | |
| if getattr(m, 'title', None) == parts[0] or (hasattr(m, 'windowTitle') and m.windowTitle() == parts[0]): | |
| targetMenu = m | |
| break | |
| if not targetMenu: targetMenu = self.menuBar().addMenu(parts[0]) | |
| label = desc.get('label', 'Plugin Item') | |
| act = QAction(label, self) | |
| if 'shortcut' in desc and desc['shortcut']: act.setShortcut(desc['shortcut']) | |
| if 'callback' in desc and callable(desc['callback']): act.triggered.connect(desc['callback']) | |
| targetMenu.addAction(act) | |
| def createToolBar(self): | |
| tb = QToolBar('Main') | |
| tb.setStyleSheet(f"QToolBar {{ background: {PALETTE['tab_active']}; }}") | |
| fnt = QFont('Segoe UI', 10) | |
| fnt.setBold(False) | |
| tb.setFont(fnt) | |
| tb.setFixedHeight(62) | |
| tb.setMovable(False) | |
| tb.setObjectName("mainToolBar") | |
| tb.setStyleSheet(""" #mainToolBar QToolBar {spacing: 1px; padding: 1px;} | |
| QToolBar::separator {background: rgba(222, 222, 222, 0.28); width: 2px; height 2px; | |
| margin-top: 10px; margin-left: 8px; margin-right: 8px; margin-bottom: 10px} | |
| QToolButton {min-height: 21; padding: 0px 10px; font-size: 10pt;} | |
| QToolButton:hover { background: rgba(48, 48, 48, 0.22);}""") | |
| self.addToolBar(tb) | |
| for key in ['new', 'new_project', 'open', 'open_project', 'open_template', 'save', 'cut', 'copy', 'paste', 'undo', 'redo', 'find', 'terminal', 'run', 'build']: | |
| if key in self._actions: tb.addAction(self._actions[key]) | |
| if key == 'save' or key == 'redo' or key == 'terminal': tb.addSeparator() | |
| sp = QWidget() | |
| sp.setFixedWidth(8) | |
| sp.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) | |
| tb.addWidget(sp) | |
| textbox = CecilPlusTGridBox(parent=None, placeholder="Merge Operator+Enter | Grid Select") | |
| textbox.submitted.connect(lambda text: print("M.O. on Enter:", text)) | |
| textbox.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) | |
| cptgb_action = QWidgetAction(tb) | |
| cptgb_action.setDefaultWidget(textbox) | |
| tb.addAction(cptgb_action) | |
| sp2 = QWidget() | |
| sp2.setFixedWidth(8) | |
| sp2.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) | |
| tb.addWidget(sp2) | |
| sps_btn = SPSQuantumButton("Set Superlink Priority State", max_value=999) | |
| sps_btn.stateChanged.connect(lambda l, r, t, v, e: print( | |
| f"L={'T' if l else 'F'} R={'T' if r else 'F'} T={'T' if t else 'F'} V={v} E={'T' if e else 'F'}")) | |
| sps_btn.setObjectName("SpsButton") | |
| sps_btn.setStyleSheet("""QPushButton#SpsButton {background: transparent; | |
| border: 1px solid rgba(122,122,122,0.00); | |
| border-radius: 0px; color: #999999; padding: 8px 12px;} QPushButton#SpsButton:hover { | |
| border: 1px solid #59E9EE; background: qlineargradient( | |
| x1:0, y1:0, x2:0, y2:1, stop:0 rgba(222,222,222,0.03), stop:1 rgba(255,255,255,0.03));} | |
| QPushButton#SpsButton:pressed {background-color: rgba(111,111,111,0.35); | |
| padding-top: 10px;} QPushButton#SpsButton QLineEdit { | |
| background: rgba(255,255,255,0.25); border: 1px solid #636363; | |
| color: rgba(230, 250, 250, 0.98); border-radius: 6px; padding: 3px 6px; | |
| selection-background-color: rgba(89,233,238,0.28);min-height: 20px;} | |
| QPushButton#SpsButton QLineEdit:focus {border: 1px solid rgba(89,233,238,0.9); | |
| background: rgba(89,233,238,0.10); color: #59E9EE;}""") | |
| sps_btn.setFixedHeight(52); sps_btn.setFixedWidth(320) | |
| tb.addWidget(sps_btn) | |
| def _iconWithLetter(self, ico, letter, pix_size=28, font_family='Segoe UI', | |
| font_point=7, letter_color=QColor(201, 201, 201)): | |
| pix = ico.pixmap(pix_size, pix_size) | |
| painter = QPainter(pix) | |
| painter.setRenderHint(QPainter.Antialiasing) | |
| font = QFont(font_family, font_point) | |
| font.setBold(True) | |
| painter.setFont(font) | |
| fm = painter.fontMetrics() | |
| txt = letter.upper() | |
| x = (pix.width() - fm.horizontalAdvance(txt)) // 4 | |
| y = (pix.height() + fm.ascent()) // 4 - 1 | |
| painter.setPen(letter_color) | |
| painter.drawText(x, y, txt) | |
| painter.end() | |
| return QIcon(pix) | |
| def createVertToolBar(self): | |
| tb_vert = QToolBar('Vert') | |
| tb_vert.setOrientation(Qt.Vertical) | |
| tb_vert.setMovable(False) | |
| tb_vert.setIconSize(QSize(32, 32)) | |
| tb_vert.setObjectName("vert1ToolBar") | |
| tb_vert.setStyleSheet(""" #vert1ToolBar QToolBar {spacing: 1px; padding: 1px;} | |
| QToolButton {min-height: 21; padding: 0px 10px; font-size: 10pt;} | |
| QToolButton:hover { background: rgba(48, 48, 48, 0.22); }""") | |
| self.addToolBar(Qt.LeftToolBarArea, tb_vert) | |
| icons = [QIcon(ICO_ICO['gray_folder']), QIcon(ICO_ICO['gray_fast_forward'])] | |
| for i, icon in enumerate(icons, start=1): | |
| if i == 1: act = QAction(icon, 'Store Multi-Spectral Span Member Object', self) | |
| else: act = QAction(icon, 'Record Multi-Spectral Span Member Object', self) | |
| act.triggered.connect(lambda checked=False, n=i: print(f'vert toolbar1 action {n}')) | |
| tb_vert.addAction(act) | |
| for ch in ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K'): | |
| ic = self._iconWithLetter(icons[1], ch) | |
| act = QAction(ic, f'Procedure Multi-Spectral Span Member Object {ch}', self) | |
| act.triggered.connect(lambda checked=False, c=ch: print(f'spectral multidispatch pre-link', c)) | |
| tb_vert.addAction(act) | |
| def currentTabPath(self): | |
| try: | |
| idx = self.tabs.currentIndex() | |
| pth = self._currentFiles[idx] | |
| if pth: return pth | |
| else: return None | |
| except Exception: | |
| return None | |
| def copyFilePath(self): | |
| pth = self.currentTabPath() | |
| if pth: QApplication.clipboard().setText(pth) | |
| def newFile(self): | |
| editor = CodeEditor() | |
| editor.modifiedChanged.connect(self.onModifiedChanged) | |
| self._ut_cnt+=1 | |
| idx = self.tabs.addTab(editor, f"untitled({self._ut_cnt})") | |
| self.tabs.setCurrentIndex(idx) | |
| self._currentFiles[idx] = None | |
| def cecilPlusOpenFile(self): | |
| cecil_plus_open_files(self) | |
| f_pth = read_file(OPN_CFG_PTH).split(':)^(:') | |
| if f_pth[0] != 'cancel': self.openFile(f_pth[1]) | |
| def openFile(self, path): | |
| for i in range(self.tabs.count()): | |
| if self._currentFiles.get(i) == path: | |
| self.tabs.setCurrentIndex(i) | |
| return | |
| try: | |
| with open(path, 'r', encoding='utf-8') as f: text = f.read() | |
| except Exception as e: | |
| QMessageBox.warning(self, "Open Failed", str(e)) | |
| return | |
| editor = CodeEditor() | |
| editor.setPlainText(text) | |
| editor.markSaved() | |
| editor.modifiedChanged.connect(self.onModifiedChanged) | |
| idx = self.tabs.addTab(editor, os.path.basename(path)) | |
| self.tabs.setCurrentIndex(idx) | |
| self._currentFiles[idx] = path | |
| self.sb.showMessage(f" > Opened {path}", 3000) | |
| def openTemplate(self): | |
| QMessageBox.information(self, "Open Template", "Open Template") | |
| def openLinkerXML(self): | |
| QMessageBox.information(self, "Open Linker XML", "Open Linker XML") | |
| def openKrakenSQTPP(self): | |
| QMessageBox.information(self, "Open Kraken SQTPP", "Open Kraken SQTPP") | |
| def newProjectDialog(self): | |
| QMessageBox.information(self, "New Project", "New Cecil+ Project...") | |
| def openProjectDialog(self): | |
| QMessageBox.information(self, "Open Project", "Open Cecil+ Project...") | |
| def exportProject(self): | |
| QMessageBox.information(self, "Export Project", "Export Cecil+ Project") | |
| def projectSettings(self): | |
| QMessageBox.information(self, "Project Settings", "Cecil+ Project Settings") | |
| def closeProject(self): | |
| QMessageBox.information(self, "Close Project", "Close Cecil+ Project") | |
| def saveFile(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| idx = self.tabs.currentIndex() | |
| path = self._currentFiles.get(idx) | |
| if not path: | |
| return self.saveFileAs() | |
| try: | |
| with open(path, 'w', encoding='utf-8') as f: | |
| f.write(editor.toPlainText()) | |
| editor.markSaved() | |
| self.tabs.setTabText(idx, os.path.basename(path)) | |
| self.sb.showMessage(f" > Saved {path}", 3000) | |
| except Exception as e: | |
| QMessageBox.warning(self, "Save Failed", str(e)) | |
| def saveFileAs(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| path, _ = QFileDialog.getSaveFileName(self, "Save As", os.path.expanduser("~")) | |
| if not path: | |
| return | |
| try: | |
| with open(path, 'w', encoding='utf-8') as f: | |
| f.write(editor.toPlainText()) | |
| idx = self.tabs.currentIndex() | |
| self._currentFiles[idx] = path | |
| editor.markSaved() | |
| self.tabs.setTabText(idx, os.path.basename(path)) | |
| self.sb.showMessage(f" > Saved {path}", 3000) | |
| except Exception as e: | |
| QMessageBox.warning(self, "Save Failed", str(e)) | |
| def saveAsTemplate(self): | |
| QMessageBox.information(self, "Save As Template", "Save As Template") | |
| def saveAll(self): | |
| for i in range(self.tabs.count()): | |
| editor = self.tabs.widget(i) | |
| if isinstance(editor, CodeEditor) and not editor.isSaved(): | |
| self.tabs.setCurrentIndex(i) | |
| self.saveFile() | |
| def applyCircuitComments(self): | |
| print('apply circuit comments executed') | |
| def refreshCircuitComments(self): | |
| print('refresh circuit comments executed') | |
| def removeCircuitComments(self): | |
| print('remove circuit comments executed') | |
| def settings(self): | |
| QMessageBox.information(self, "Settings", "Cecil+ Settings") | |
| def krakenAiSettings(self): | |
| QMessageBox.information(self, "Kraken AI Settings", "Cecil+ Kraken AI Settings") | |
| def expressionUnitSettings(self): | |
| QMessageBox.information(self, "Expression Unit Settings", "Cecil+ Expression Unit Settings(KRAKEN AI)") | |
| def linearCommutatorSettings(self): | |
| QMessageBox.information(self, "Linear Commutator Settings", "Cecil+ Linear Commutator Settings") | |
| def linkerProperties(self): | |
| QMessageBox.information(self, "Linker Properties", "Cecil+ Linker Properties") | |
| def linkerSymbols(self): | |
| QMessageBox.information(self, "Linker Symbols", "Cecil+ Linker Symbols") | |
| def linkerMethodsPMQ(self): | |
| QMessageBox.information(self, "Linker Methods PMQ", "Cecil+ Linker Methods PMQ") | |
| def currentEditor(self): | |
| w = self.tabs.currentWidget() | |
| if isinstance(w, CodeEditor): | |
| return w | |
| return None | |
| def closeTab(self, idx): | |
| editor, bypass, newMap = self.tabs.widget(idx), False, {} | |
| if isinstance(editor, CodeEditor) and not editor.isSaved(): | |
| resp = QMessageBox.question(self, "Unsaved", "Save changes before closing?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) | |
| if resp == QMessageBox.Cancel: bypass = True | |
| if resp == QMessageBox.Yes and not bypass: | |
| self.tabs.setCurrentIndex(idx) | |
| self.saveFile() | |
| if not bypass: | |
| t_cnt = self.tabs.count() | |
| if t_cnt == 0: self.tabs.removeTab(0) | |
| else: self.tabs.removeTab(idx) | |
| for i in range(t_cnt): newMap[i] = self._currentFiles.get(i if i < idx else i + 1) | |
| self._currentFiles = newMap | |
| def onModifiedChanged(self, modified): | |
| idx = self.tabs.currentIndex() | |
| if idx < 0: | |
| return | |
| if self._currentFiles.get(idx): base = os.path.basename(self._currentFiles.get(idx)) | |
| else: base = self.tabs.tabText(idx) | |
| if modified: | |
| if not self.tabs.tabText(idx).endswith('*'): self.tabs.setTabText(idx, base + "*") | |
| else: self.tabs.setTabText(idx, base) | |
| def openFind(self): | |
| if os.path.isfile(FND_CFG_PTH): | |
| fnd_cfg = read_file(FND_CFG_PTH) | |
| if fnd_cfg.find('closed:') > -1: | |
| fd = FindDialog(self) | |
| fd.editor = self.currentEditor() | |
| fd.show() | |
| else: | |
| fd = FindDialog(self) | |
| fd.editor = self.currentEditor() | |
| fd.show() | |
| def findInFiles(self): | |
| pass | |
| def formatDocument(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| pass | |
| def _is_readonly(self, editor): | |
| if not editor: | |
| return True | |
| if hasattr(editor, "isReadOnly"): | |
| try: | |
| return editor.isReadOnly() | |
| except Exception: | |
| pass | |
| return False | |
| def char_map(self): | |
| char_map_dlg = CharMapDialog(initial_text="") | |
| char_map_dlg.show() | |
| def copy(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| if hasattr(editor, "copy"): | |
| try: | |
| editor.copy() | |
| return | |
| except Exception: | |
| pass | |
| try: | |
| cursor = editor.textCursor() | |
| text = cursor.selectedText() | |
| if text: | |
| QApplication.clipboard().setText(text) | |
| except Exception: | |
| pass | |
| def cut(self): | |
| editor = self.currentEditor() | |
| if not editor or self._is_readonly(editor): | |
| return | |
| if hasattr(editor, "cut"): | |
| try: | |
| editor.cut() | |
| return | |
| except Exception: | |
| pass | |
| try: | |
| cursor = editor.textCursor() | |
| text = cursor.selectedText() | |
| if text: | |
| QApplication.clipboard().setText(text) | |
| cursor.removeSelectedText() | |
| editor.setTextCursor(cursor) | |
| except Exception: | |
| pass | |
| def paste(self): | |
| editor = self.currentEditor() | |
| if not editor or self._is_readonly(editor): | |
| return | |
| if hasattr(editor, "paste"): | |
| try: | |
| editor.paste() | |
| return | |
| except Exception: | |
| pass | |
| try: | |
| clip = QApplication.clipboard().text() | |
| if not clip: | |
| return | |
| if hasattr(editor, "insertPlainText"): | |
| cursor = editor.textCursor() | |
| cursor.insertText(clip) | |
| editor.setTextCursor(cursor) | |
| else: | |
| if hasattr(editor, "toPlainText") and hasattr(editor, "setPlainText"): | |
| txt = editor.toPlainText() | |
| cursor = editor.textCursor() | |
| pos = cursor.position() | |
| new = txt[:pos] + clip + txt[pos:] | |
| editor.setPlainText(new) | |
| c = editor.textCursor() | |
| c.setPosition(pos + len(clip)) | |
| editor.setTextCursor(c) | |
| except Exception: | |
| pass | |
| def select_all(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| editor.selectAll() | |
| def undo(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| if hasattr(editor, "undo"): | |
| try: | |
| editor.undo() | |
| return | |
| except Exception: | |
| pass | |
| def redo(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| if hasattr(editor, "redo"): | |
| try: | |
| editor.redo() | |
| return | |
| except Exception: | |
| pass | |
| def _current_line_cursor(self, editor): | |
| cursor = editor.textCursor() | |
| cursor.beginEditBlock() if hasattr(cursor, "beginEditBlock") else None | |
| cursor.movePosition(QTextCursor.StartOfBlock) | |
| cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) | |
| return cursor | |
| def insert_comment(self): | |
| try: | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| read_cur = editor.textCursor() | |
| read_cur.movePosition(QTextCursor.StartOfLine) | |
| read_cur.movePosition(QTextCursor.EndOfLine, QTextCursor.KeepAnchor) | |
| line_start = read_cur.selectionStart() | |
| line_end = read_cur.selectionEnd() | |
| line = read_cur.selectedText() | |
| m = re.match(r'(\s*)(//)(.*)', line) | |
| if m: | |
| leading = m.group(1) | |
| caret_pos = line_start+len(leading)+2 | |
| new_cursor = editor.textCursor | |
| new_cursor.setPosition(caret_pos) | |
| new_cursor.clearSelection() | |
| editor.setTextCursor(new_cursor) | |
| return | |
| m2 = re.match(r'(\s*)(.*)', line) | |
| leading, rest = m2.groups() | |
| new_line = leading + '//' + rest | |
| edit_cur = editor.textCursor() | |
| editor.blockSignals(True) | |
| try: | |
| edit_cur.beginEditBlock() | |
| edit_cur.setPosition(line_start) | |
| edit_cur.setPosition(line_end, QTextCursor.KeepAnchor) | |
| edit_cur.insertText(new_line) | |
| caret_pos = line_start+len(leading)+2 | |
| edit_cur.setPosition(caret_pos) | |
| edit_cur.clearSelection() | |
| edit_cur.endEditBlock() | |
| except Exception: | |
| try: | |
| edit_cur.endEditBlock() | |
| except Exception: | |
| pass | |
| finally: | |
| editor.blockSignals(False) | |
| editor.setTextCursor(edit_cur) | |
| except Exception: | |
| pass | |
| def combine_strict_modifier(self, line): | |
| r_lst, rc_lst = [r'Int\(.*?\)', r'Float\(.*?\)', r'Complex\(.*?\)', r'Bool\(.*?\)'], [] | |
| for rl in r_lst: | |
| fnd_lst = re.findall(rl, line) | |
| if len(fnd_lst) > 0: rc_lst.append(fnd_lst) | |
| if len(rc_lst) > 0: | |
| r_lst = [] | |
| for rc in rc_lst: | |
| if len(rc) == 1: | |
| line = line.replace(rc[0], '') | |
| r_lst.append(rc[0]) | |
| else: | |
| for rc_c in rc: | |
| line = line.replace(rc_c, '') | |
| r_lst.append(rc_c) | |
| return f"{line}operator[?]::<{','.join(r_lst)}>" | |
| else: | |
| return None | |
| def _get_editor_line(self): | |
| try: | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| self._doc = editor.document() | |
| self._cur = editor.textCursor() | |
| self._block = self._cur.block() | |
| self._start = self._block.position() | |
| self._length = max(0, self._block.length() - 1) | |
| self._end = self._start + self._length | |
| tc = QTextCursor(self._doc) | |
| tc.setPosition(self._start) | |
| tc.setPosition(self._end, QTextCursor.KeepAnchor) | |
| line = tc.selectedText() | |
| return line | |
| except Exception: | |
| return None | |
| def _set_editor_line(self, new_line): | |
| try: | |
| rc = QTextCursor(self._doc) | |
| rc.beginEditBlock() | |
| try: | |
| rc.setPosition(self._start) | |
| rc.setPosition(self._end, QTextCursor.KeepAnchor) | |
| rc.insertText(new_line) | |
| finally: | |
| rc.endEditBlock() | |
| orig_col = self._cur.position() - self._start | |
| new_col = max(0, min(len(new_line), orig_col)) | |
| nc = QTextCursor(self._doc) | |
| nc.setPosition(self._start + new_col) | |
| editor = self.currentEditor() | |
| editor.setTextCursor(nc) | |
| self._doc = None | |
| except Exception: | |
| return | |
| def join_strict_operator(self): | |
| line, new_line = self._get_editor_line(), None | |
| if line: | |
| new_line = self.combine_strict_modifier(line) | |
| else: | |
| return | |
| if not new_line: | |
| return | |
| self._set_editor_line(new_line) | |
| def select_line(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| try: | |
| cursor = editor.textCursor() | |
| cursor.movePosition(QTextCursor.StartOfBlock) | |
| cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) | |
| editor.setTextCursor(cursor) | |
| except Exception: | |
| pass | |
| def del_line_whitespace(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| try: | |
| cursor = editor.textCursor() | |
| cursor.movePosition(cursor.StartOfLine) | |
| cursor.movePosition(cursor.EndOfLine, cursor.KeepAnchor) | |
| line_text = cursor.selectedText() | |
| new_text = re.sub(r'[ \t]+$', '', line_text) | |
| if new_text != line_text: | |
| cursor.insertText(new_text) | |
| editor.setTextCursor(cursor) | |
| except Exception: | |
| pass | |
| def delete_line(self): | |
| editor = self.currentEditor() | |
| if not editor or self._is_readonly(editor): | |
| return | |
| try: | |
| doc = getattr(editor, "document", None) | |
| if callable(doc): | |
| doc = editor.document() | |
| if doc and hasattr(doc, "beginEditBlock"): | |
| doc.beginEditBlock() | |
| cursor = editor.textCursor() | |
| cursor.movePosition(QTextCursor.StartOfBlock) | |
| cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) | |
| cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor) | |
| cursor.removeSelectedText() | |
| editor.setTextCursor(cursor) | |
| if doc and hasattr(doc, "endEditBlock"): | |
| doc.endEditBlock() | |
| except Exception: | |
| pass | |
| def duplicate_line(self): | |
| editor = self.currentEditor() | |
| if not editor or self._is_readonly(editor): | |
| return | |
| try: | |
| doc = getattr(editor, "document", None) | |
| if callable(doc): | |
| doc = editor.document() | |
| if doc and hasattr(doc, "beginEditBlock"): | |
| doc.beginEditBlock() | |
| cursor = editor.textCursor() | |
| cursor.movePosition(QTextCursor.StartOfBlock) | |
| cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) | |
| line_text = cursor.selectedText() | |
| end_pos = cursor.selectionEnd() | |
| insert_cursor = QTextCursor(editor.document()) | |
| insert_cursor.setPosition(end_pos) | |
| insert_cursor.insertText("\n" + line_text) | |
| cursor.setPosition(end_pos + 1) | |
| editor.setTextCursor(cursor) | |
| if doc and hasattr(doc, "endEditBlock"): | |
| doc.endEditBlock() | |
| except Exception: | |
| pass | |
| def move_line_up(self): | |
| editor = self.currentEditor() | |
| if not editor or self._is_readonly(editor): | |
| return | |
| try: | |
| doc = getattr(editor, "document", None) | |
| if callable(doc): | |
| doc = editor.document() | |
| if doc and hasattr(doc, "beginEditBlock"): | |
| doc.beginEditBlock() | |
| cursor = editor.textCursor() | |
| cursor.movePosition(QTextCursor.StartOfBlock) | |
| start = cursor.position() | |
| cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) | |
| end = cursor.selectionEnd() | |
| line_text = cursor.selectedText() | |
| if start == 0: | |
| if doc and hasattr(doc, "endEditBlock"): | |
| doc.endEditBlock() | |
| return | |
| prev_cursor = QTextCursor(editor.document()) | |
| prev_cursor.setPosition(start - 1) | |
| prev_cursor.movePosition(QTextCursor.StartOfBlock) | |
| prev_cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) | |
| prev_text = prev_cursor.selectedText() | |
| prev_start = prev_cursor.selectionStart() | |
| prev_end = prev_cursor.selectionEnd() | |
| replace_cursor = QTextCursor(editor.document()) | |
| replace_cursor.setPosition(prev_start) | |
| replace_cursor.setPosition(end, QTextCursor.KeepAnchor) | |
| replace_cursor.insertText(line_text + "\n" + prev_text) | |
| new_pos = prev_start | |
| c = QTextCursor(editor.document()) | |
| c.setPosition(new_pos) | |
| editor.setTextCursor(c) | |
| if doc and hasattr(doc, "endEditBlock"): | |
| doc.endEditBlock() | |
| except Exception: | |
| pass | |
| def move_line_down(self): | |
| editor = self.currentEditor() | |
| if not editor or self._is_readonly(editor): | |
| return | |
| try: | |
| doc = getattr(editor, "document", None) | |
| if callable(doc): | |
| doc = editor.document() | |
| if doc and hasattr(doc, "beginEditBlock"): | |
| doc.beginEditBlock() | |
| cursor = editor.textCursor() | |
| cursor.movePosition(QTextCursor.StartOfBlock) | |
| start = cursor.position() | |
| cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) | |
| end = cursor.selectionEnd() | |
| line_text = cursor.selectedText() | |
| doc_len = editor.document().characterCount() | |
| if end >= doc_len - 1: | |
| if doc and hasattr(doc, "endEditBlock"): | |
| doc.endEditBlock() | |
| return | |
| next_cursor = QTextCursor(editor.document()) | |
| next_cursor.setPosition(end + 1) | |
| next_cursor.movePosition(QTextCursor.StartOfBlock) | |
| next_cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) | |
| next_text = next_cursor.selectedText() | |
| next_start = next_cursor.selectionStart() | |
| next_end = next_cursor.selectionEnd() | |
| replace_cursor = QTextCursor(editor.document()) | |
| replace_cursor.setPosition(start) | |
| replace_cursor.setPosition(next_end, QTextCursor.KeepAnchor) | |
| replace_cursor.insertText(next_text + "\n" + line_text) | |
| c = QTextCursor(editor.document()) | |
| c.setPosition(next_start) | |
| editor.setTextCursor(c) | |
| if doc and hasattr(doc, "endEditBlock"): | |
| doc.endEditBlock() | |
| except Exception: | |
| pass | |
| def insert_blank_line_above(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| try: | |
| cursor = editor.textCursor() | |
| if cursor.hasSelection(): | |
| cursor.setPosition(cursor.selectionStart()) | |
| cursor.movePosition(QTextCursor.StartOfLine) | |
| cursor.insertBlock() | |
| cursor.movePosition(QTextCursor.Up) | |
| editor.setTextCursor(cursor) | |
| except Exception: | |
| pass | |
| def insert_blank_line_below(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| try: | |
| cursor = editor.textCursor() | |
| if cursor.hasSelection(): | |
| cursor.setPosition(cursor.selectionEnd()) | |
| cursor.movePosition(QTextCursor.EndOfLine) | |
| cursor.insertBlock() | |
| cursor.movePosition(QTextCursor.Up) | |
| editor.setTextCursor(cursor) | |
| except Exception: | |
| pass | |
| def indent_line(self): | |
| editor = self.currentEditor() | |
| if not editor or self._is_readonly(editor): | |
| return | |
| try: | |
| doc = editor.document() if callable(getattr(editor, "document", None)) else None | |
| if doc and hasattr(doc, "beginEditBlock"): | |
| doc.beginEditBlock() | |
| cursor = editor.textCursor() | |
| if not cursor.hasSelection(): | |
| cursor.movePosition(QTextCursor.StartOfBlock) | |
| cursor.insertText(INDENT) | |
| editor.setTextCursor(cursor) | |
| else: | |
| start = cursor.selectionStart() | |
| end = cursor.selectionEnd() | |
| c = QTextCursor(editor.document()) | |
| c.setPosition(start) | |
| c.movePosition(QTextCursor.StartOfBlock) | |
| while c.position() <= end: | |
| c.insertText(INDENT) | |
| c.movePosition(QTextCursor.EndOfBlock) | |
| if c.atEnd(): | |
| break | |
| c.movePosition(QTextCursor.Right) | |
| if c.position() > end: | |
| break | |
| if doc and hasattr(doc, "endEditBlock"): | |
| doc.endEditBlock() | |
| except Exception: | |
| pass | |
| def unindent_line(self): | |
| editor = self.currentEditor() | |
| if not editor or self._is_readonly(editor): | |
| return | |
| try: | |
| doc = editor.document() if callable(getattr(editor, "document", None)) else None | |
| if doc and hasattr(doc, "beginEditBlock"): | |
| doc.beginEditBlock() | |
| cursor = editor.textCursor() | |
| has_sel = cursor.hasSelection() | |
| start = cursor.selectionStart() if has_sel else None | |
| end = cursor.selectionEnd() if has_sel else None | |
| c = QTextCursor(editor.document()) | |
| if has_sel: | |
| c.setPosition(start) | |
| else: | |
| c = editor.textCursor() | |
| c.movePosition(QTextCursor.StartOfBlock) | |
| while True: | |
| c.movePosition(QTextCursor.StartOfBlock) | |
| r = QTextCursor(editor.document()) | |
| r.setPosition(c.position()) | |
| r.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, len(INDENT)) | |
| s = r.selectedText() | |
| if s == INDENT: | |
| r.removeSelectedText() | |
| else: | |
| to_remove = 0 | |
| for ch in s: | |
| if ch == " ": | |
| to_remove += 1 | |
| else: | |
| break | |
| if to_remove: | |
| r = QTextCursor(editor.document()) | |
| r.setPosition(c.position()) | |
| r.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, to_remove) | |
| r.removeSelectedText() | |
| if has_sel: | |
| c.movePosition(QTextCursor.EndOfBlock) | |
| if c.atEnd(): | |
| break | |
| c.movePosition(QTextCursor.Right) | |
| if c.position() > end: | |
| break | |
| else: | |
| break | |
| if doc and hasattr(doc, "endEditBlock"): | |
| doc.endEditBlock() | |
| except Exception: | |
| pass | |
| def has_selection(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return False | |
| try: | |
| if hasattr(editor, "textCursor"): | |
| return bool(editor.textCursor().hasSelection()) | |
| if hasattr(editor, "hasSelection"): | |
| return editor.hasSelection() | |
| except Exception: | |
| pass | |
| return False | |
| def clipboard_has_text(self): | |
| return bool(QApplication.clipboard().text()) | |
| def toggleExplorer(self): | |
| visible = not self.fileExplorer.isVisible() | |
| self.fileExplorer.setVisible(visible) | |
| def toggleTerminal(self): | |
| visible = not self.terminalBar.isVisible() | |
| self.terminalBar.setVisible(visible) | |
| def openTerminal(self): | |
| pass | |
| def fold_current_func(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| try: | |
| editor._fold_current(False) | |
| except Exception as err: | |
| print(err) | |
| def unfold_current_func(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| try: | |
| editor._fold_current(True) | |
| except Exception as err: | |
| print(err) | |
| def fold_all_func(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| try: | |
| editor._fold_all() | |
| except Exception: | |
| return | |
| def unfold_all_func(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| try: | |
| editor._unfold_all() | |
| except Exception as err: | |
| return | |
| def toggleZen(self): | |
| is_full = self.isFullScreen() | |
| self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) if not is_full else self.showNormal() | |
| def buildProject(self): | |
| pass | |
| def rebuildProject(self): | |
| pass | |
| def cleanProject(self): | |
| pass | |
| def runWithoutDebug(self): | |
| self.runCurrent() | |
| def startDebugging(self): | |
| pass | |
| def stopDebugging(self): | |
| pass | |
| def openCommandPalette(self): | |
| pass | |
| def openExtensions(self): | |
| pass | |
| def openDocs(self): | |
| pass | |
| def showAbout(self): | |
| ad = AboutDialog(self) | |
| ad.show() | |
| def runCurrent(self): | |
| editor = self.currentEditor() | |
| if not editor: | |
| return | |
| idx = self.tabs.currentIndex() | |
| path = self._currentFiles.get(idx) | |
| if not path: | |
| resp = QMessageBox.question(self, "Run", "Save file before running?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) | |
| if resp == QMessageBox.Cancel: | |
| return | |
| if resp == QMessageBox.Yes: | |
| self.saveFileAs() | |
| path = self._currentFiles.get(self.tabs.currentIndex()) | |
| else: | |
| return | |
| QMessageBox.information(self, "Run", f"Run hook called for: {path}\nCecil+ Compiler.") | |
| self.sb.showMessage(f" > Run invoked for {path}", 3000) | |
| class WorkerSignals(QObject): | |
| progress = pyqtSignal(int) | |
| finished = pyqtSignal(object) | |
| class Worker(QRunnable): | |
| def __init__(self, tasks=10): | |
| super().__init__() | |
| self.signals = WorkerSignals() | |
| self.tasks = tasks | |
| self.setAutoDelete(True) | |
| @pyqtSlot() | |
| def run(self): | |
| for i in range(1, self.tasks + 1): | |
| time.sleep(0.11) | |
| pct = int(i / self.tasks * 100) | |
| self.signals.progress.emit(pct) | |
| result = {"status": "ok"} | |
| self.signals.finished.emit(result) | |
| class SplashWindow(QWidget): | |
| def __init__(self): | |
| super().__init__() | |
| self.init_ui() | |
| def init_ui(self): | |
| self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) | |
| self.setAttribute(Qt.WA_TranslucentBackground) | |
| self.resize(480, 220) | |
| qr = self.frameGeometry() | |
| cp = QApplication.primaryScreen().availableGeometry().center() | |
| qr.moveCenter(cp); self.move(qr.topLeft()) | |
| panel = QFrame(self) | |
| panel.setObjectName("panel") | |
| panel.setGeometry(0, 0, 480, 220) | |
| panel.setStyleSheet("""QFrame#panel {background: #59E9EE; border-radius: 5px;} | |
| QLabel#title {color: #FFFFFF; font-size: 56px;} | |
| QLabel#sub {color: #FFFFFF; font-size: 16px;} | |
| QProgressBar {height: 18px; border-radius: 5px; background: #535353; color:#FFFFFF;} | |
| QProgressBar::chunk {background: qlineargradient(x1:0,y1:0,x2:1,y2:0, stop:0 #59E9EE, stop:1 #FFFFFF); | |
| border-radius:5px;}""") | |
| v = QVBoxLayout(panel) | |
| v.setContentsMargins(20, 18, 20, 18) | |
| label_title = QLabel("Cecil+") | |
| label_title.setObjectName("title") | |
| label_sub = QLabel(" Loading...") | |
| label_sub.setObjectName("sub") | |
| v.addWidget(label_title) | |
| v.addWidget(label_sub) | |
| v.addStretch(1) | |
| self.progress = QProgressBar() | |
| self.progress.setRange(0, 100) | |
| self.progress.setValue(0) | |
| v.addWidget(self.progress) | |
| self._drag_pos = None | |
| if __name__ == "__main__": | |
| app = QApplication(sys.argv) | |
| app.setStyle('Fusion') | |
| _palette = QPalette() | |
| _palette.setColor(QPalette.Window, QColor(53, 53, 53)) | |
| _palette.setColor(QPalette.WindowText, Qt.white) | |
| _palette.setColor(QPalette.Base, QColor(51, 51, 51)) | |
| _palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53)) | |
| _palette.setColor(QPalette.ToolTipBase, Qt.black) | |
| _palette.setColor(QPalette.ToolTipText, Qt.white) | |
| _palette.setColor(QPalette.Text, Qt.white) | |
| _palette.setColor(QPalette.Button, QColor(66, 177, 177)) | |
| _palette.setColor(QPalette.ButtonText, Qt.white) | |
| _palette.setColor(QPalette.BrightText, Qt.red) | |
| _palette.setColor(QPalette.Link, QColor(42, 130, 218)) | |
| _palette.setColor(QPalette.Highlight, QColor(67, 67, 67)) | |
| _palette.setColor(QPalette.HighlightedText, QColor(67, 177, 199)) | |
| app.setPalette(_palette) | |
| splash = SplashWindow() | |
| splash.show() | |
| threadpool = QThreadPool.globalInstance() | |
| worker = Worker(tasks=12) | |
| worker.signals.progress.connect(lambda p: splash.progress.setValue(p)) | |
| def on_finished(result): | |
| main_win = CecilIDE() | |
| main_win.showMaximized() | |
| splash.close() | |
| app.main_win = main_win | |
| worker.signals.finished.connect(on_finished) | |
| threadpool.start(worker) | |
| sys.exit(app.exec_()) |
Author
Author
Cecil+ will include a extremely advanced B-Spline NN
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The toughest to learn about the new Cecil+ will be strict types vs pipe types. All else easy to understand if you already know multi-dispatching return basics.