-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
2,386 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
from PyQt6.QtWidgets import QLabel, QLineEdit, QVBoxLayout, QPushButton, QDialog, QTextEdit, QMessageBox | ||
from PyQt6.QtGui import QFontMetricsF | ||
import json | ||
import re | ||
|
||
class AdvancedPopup(QDialog): | ||
def __init__(self, rect=None, edit=False): | ||
super().__init__() | ||
|
||
form_layout = QVBoxLayout() | ||
state_label = QLabel('State:') | ||
|
||
if edit: | ||
self.setWindowTitle('Edit State') | ||
else: | ||
self.setWindowTitle('Add State') | ||
|
||
self.state_input = QTextEdit() | ||
if rect: | ||
self.state_input.setText(rect.get_state_string()) | ||
|
||
font = self.state_input.font() | ||
fontMetrics = QFontMetricsF(font) | ||
spaceWidth = fontMetrics.horizontalAdvance(' ') | ||
self.state_input.setTabStopDistance(spaceWidth * 4) | ||
|
||
self.save_button = QPushButton('Save') | ||
self.save_button.clicked.connect(self.check_state) | ||
|
||
form_layout.addWidget(state_label) | ||
form_layout.addWidget(self.state_input, 3) | ||
form_layout.addWidget(self.save_button) | ||
|
||
self.setLayout(form_layout) | ||
|
||
def get_state(self): | ||
state_text = self.state_input.toPlainText() | ||
|
||
try: | ||
state_json = json.loads(state_text) | ||
except json.decoder.JSONDecodeError: | ||
state_json = {} | ||
title = [key for key in state_json.keys()][0] | ||
|
||
state_dict = state_json[title] | ||
state_dict['Title'] = title | ||
return state_dict | ||
|
||
def check_state(self): | ||
state = self.get_state() | ||
state_type = state['Type'] | ||
|
||
def error_popup(text): | ||
error_msg = QMessageBox() | ||
error_msg.setIcon(QMessageBox.Icon.Critical) | ||
error_msg.setWindowTitle('Error in State') | ||
error_msg.setText(text) | ||
error_msg.exec() | ||
|
||
if state_type not in ['Choice', 'MethodCall']: | ||
error_popup('Invalid type') | ||
elif state_type == 'Choice' and 'Choices' not in state.keys(): | ||
error_popup('Choice type, but no Choices key') |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
import sys | ||
from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QHBoxLayout, QListWidget, QFrame, QDialog, QLabel, QToolBar, QMenu, QFileDialog | ||
from PyQt6.QtCore import Qt | ||
from PyQt6.QtGui import QAction | ||
from import_form import ImportForm | ||
from meta_form import MetaForm | ||
from workflow_diagram import WorkflowDiagram | ||
from rect_connect import CustomItem | ||
import json | ||
|
||
|
||
with open('dependencies.json') as file: | ||
data = json.load(file) | ||
|
||
form = {} | ||
|
||
class GUI(QMainWindow): | ||
def __init__(self, setting): | ||
"""QMainWindow to contain MetaForm, ImportForm, and Workflow Diagram. | ||
Args: | ||
setting: (optional) | ||
""" | ||
super().__init__() | ||
self.initialize_ui(setting) | ||
|
||
def initialize_ui(self, setting): | ||
self.setWindowTitle('ConStrain') | ||
|
||
self.meta_form = MetaForm() | ||
self.import_form = ImportForm() | ||
self.states_form = WorkflowDiagram(setting) | ||
|
||
self.column_list = QListWidget() | ||
self.column_list.addItems(['Meta', 'Imports', 'State']) | ||
self.column_list.currentItemChanged.connect(self.display_form) | ||
|
||
self.column_frame = QFrame() | ||
self.column_frame.setFrameStyle(QFrame.Shape.NoFrame) | ||
self.column_frame.setMaximumWidth(100) | ||
column_layout = QVBoxLayout() | ||
column_layout.addWidget(self.column_list) | ||
self.column_frame.setLayout(column_layout) | ||
|
||
middle_layout = QHBoxLayout() | ||
middle_layout.addWidget(self.column_frame) | ||
middle_layout.addWidget(self.meta_form) | ||
middle_layout.addWidget(self.import_form) | ||
middle_layout.addWidget(self.states_form) | ||
|
||
self.validate_button = QPushButton('Validate') | ||
self.validate_button.setFixedSize(100, 20) | ||
self.validate_button.clicked.connect(self.validate_form) | ||
|
||
self.submit_button = QPushButton('Submit') | ||
self.submit_button.setEnabled(False) | ||
self.submit_button.setFixedSize(100, 20) | ||
self.submit_button.clicked.connect(self.submit_form) | ||
|
||
buttons = QHBoxLayout() | ||
buttons.addWidget(self.validate_button) | ||
buttons.addWidget(self.submit_button) | ||
buttons.setAlignment(Qt.AlignmentFlag.AlignHCenter) | ||
|
||
main_layout = QVBoxLayout() | ||
main_layout.addLayout(middle_layout) | ||
main_layout.addLayout(buttons) | ||
|
||
central_widget = QWidget() | ||
central_widget.setLayout(main_layout) | ||
self.setCentralWidget(central_widget) | ||
|
||
self.initialize_toolbar() | ||
|
||
def initialize_toolbar(self): | ||
toolbar = QToolBar() | ||
self.addToolBar(toolbar) | ||
|
||
file_menu = QMenu('File', self) | ||
|
||
import_action = QAction('Import', self) | ||
import_action.triggered.connect(self.importFile) | ||
file_menu.addAction(import_action) | ||
|
||
export_action = QAction('Export', self) | ||
export_action.triggered.connect(self.exportFile) | ||
file_menu.addAction(export_action) | ||
|
||
toolbar.addAction(file_menu.menuAction()) | ||
|
||
def exportFile(self): | ||
|
||
fp, _ = QFileDialog.getSaveFileName(self, 'Save JSON File', '', 'JSON Files (*.json);;All Files (*)') | ||
|
||
if fp: | ||
try: | ||
workflow = self.get_workflow() | ||
with open(fp, 'w', encoding='utf-8') as f: | ||
json.dump(self.create_json(workflow), f, indent=4) | ||
except Exception: | ||
print('error') | ||
|
||
def importFile(self): | ||
file_dialog = QFileDialog() | ||
file_dialog.setWindowTitle('Select a JSON File') | ||
file_dialog.setFileMode(QFileDialog.FileMode.ExistingFile) | ||
file_dialog.setNameFilter('JSON files (*.json)') | ||
if file_dialog.exec() == QFileDialog.DialogCode.Accepted: | ||
file_path = file_dialog.selectedFiles()[0] | ||
with open(file_path, 'r') as f: | ||
workflow = json.load(f) | ||
if isinstance(workflow, dict): | ||
self.meta_form.read_import(workflow.get('workflow_name'), workflow.get('meta')) | ||
self.import_form.read_import(workflow.get('imports')) | ||
self.states_form.read_import(workflow.get('states')) | ||
self.get_workflow() | ||
else: | ||
print('error') | ||
|
||
|
||
|
||
|
||
def display_form(self, current_item): | ||
if current_item.text() == self.column_list.item(0).text(): | ||
self.meta_form.show() | ||
self.import_form.hide() | ||
self.states_form.hide() | ||
elif current_item.text() == self.column_list.item(1).text(): | ||
self.meta_form.hide() | ||
self.import_form.show() | ||
self.states_form.hide() | ||
else: | ||
self.meta_form.hide() | ||
self.import_form.hide() | ||
self.states_form.show() | ||
|
||
def create_json(self, workflow): | ||
data = {'workflow_name': self.meta_form.get_workflow_name(), 'meta': self.meta_form.get_meta(), | ||
'imports': self.import_form.get_imports(), 'states': {}} | ||
for item in workflow: | ||
copy_item = dict(item) | ||
title = copy_item.pop('Title') | ||
data['states'][title] = copy_item | ||
json_data = data | ||
return json_data | ||
|
||
def submit_form(self): | ||
self.submit_button.setEnabled(False) | ||
|
||
def get_workflow(self): | ||
items = [item for item in self.states_form.scene.items() if isinstance(item, CustomItem)] | ||
roots = [] | ||
for i in items: | ||
parent = True | ||
for j in items: | ||
if i in j.children: | ||
parent = False | ||
break | ||
if parent: | ||
roots.append(i) | ||
|
||
visited = set() | ||
paths = [] | ||
|
||
def dfs_helper(item, path): | ||
path.append(item) | ||
visited.add(item) | ||
|
||
if item not in items or not item.children: | ||
item.setBrush('red') | ||
item.state['End'] = 'True' | ||
paths.append(path[:]) | ||
else: | ||
item.setBrush() | ||
|
||
for child in item.children: | ||
if child not in visited: | ||
dfs_helper(child, path) | ||
|
||
path.pop() | ||
visited.remove(item) | ||
|
||
for root in roots: | ||
root.state['Start'] = 'True' | ||
self.states_form.view.arrange_tree(root, 0, 0, 150) | ||
if root not in visited: | ||
dfs_helper(root, []) | ||
root.setBrush('green') | ||
|
||
workflow_path = [] | ||
visited = set() | ||
for path in paths: | ||
for node in path: | ||
if node not in visited: | ||
workflow_path.append(node.state) | ||
visited.add(node) | ||
return workflow_path | ||
|
||
def validate_form(self, data): | ||
workflow_path = self.get_workflow() | ||
json_data = self.create_json(workflow_path) | ||
valid = True | ||
if valid: | ||
self.submit_button.setEnabled(True) | ||
|
||
class UserSetting(QDialog): | ||
def __init__(self): | ||
super().__init__() | ||
self.setWindowTitle('ConStrain') | ||
query = QLabel('Advanced or basic user settings?') | ||
self.advanced_button = QPushButton('Advanced') | ||
self.advanced_button.clicked.connect(self.showAdvanced) | ||
self.basic_button = QPushButton('Basic') | ||
self.basic_button.clicked.connect(self.showBasic) | ||
|
||
form_layout = QVBoxLayout() | ||
button_layout = QHBoxLayout() | ||
|
||
button_layout.addWidget(self.advanced_button) | ||
button_layout.addWidget(self.basic_button) | ||
|
||
form_layout.addWidget(query) | ||
form_layout.addLayout(button_layout) | ||
|
||
self.setLayout(form_layout) | ||
|
||
def showBasic(self): | ||
self.close() | ||
self.gui = GUI('basic') | ||
self.gui.show() | ||
|
||
def showAdvanced(self): | ||
self.close() | ||
self.gui = GUI('advanced') | ||
self.gui.show() | ||
|
||
|
||
app = QApplication(sys.argv) | ||
|
||
window = UserSetting() | ||
window.show() | ||
|
||
sys.exit(app.exec()) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# -*- mode: python ; coding: utf-8 -*- | ||
|
||
|
||
block_cipher = None | ||
|
||
|
||
a = Analysis( | ||
['app.py'], | ||
pathex=[], | ||
binaries=[], | ||
datas=[ | ||
('advanced_popup.py', '.'), | ||
('import_form.py', '.'), | ||
('meta_form.py', '.'), | ||
('popup_window.py', '.'), | ||
('rect_connect.py', '.'), | ||
('workflow_diagram.py', '.'), | ||
('dependencies.json', '.') | ||
], | ||
hiddenimports=[], | ||
hookspath=[], | ||
hooksconfig={}, | ||
runtime_hooks=[], | ||
excludes=[], | ||
win_no_prefer_redirects=False, | ||
win_private_assemblies=False, | ||
cipher=block_cipher, | ||
noarchive=False, | ||
) | ||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) | ||
|
||
exe = EXE( | ||
pyz, | ||
a.scripts, | ||
[], | ||
exclude_binaries=True, | ||
name='app', | ||
debug=False, | ||
bootloader_ignore_signals=False, | ||
strip=False, | ||
upx=True, | ||
console=True, | ||
disable_windowed_traceback=False, | ||
argv_emulation=False, | ||
target_arch=None, | ||
codesign_identity=None, | ||
entitlements_file=None, | ||
) | ||
coll = COLLECT( | ||
exe, | ||
a.binaries, | ||
a.zipfiles, | ||
a.datas, | ||
strip=False, | ||
upx=True, | ||
upx_exclude=[], | ||
name='app', | ||
) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"workflow_name": "", | ||
"meta": { | ||
"author": "", | ||
"date": "01/01/2000", | ||
"version": "", | ||
"description": "" | ||
}, | ||
"imports": [], | ||
"states": {} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"{\n \"workflow_name\": \"\",\n \"meta\": {\n \"author\": \"\",\n \"date\": \"01/01/2000\",\n \"version\": \"\",\n \"description\": \"\"\n },\n \"imports\": [],\n \"states\": {\n \"Load data\": {\n \"Type\": \"MethodCall\",\n \"MethodCall\": \"DataProcessing\",\n \"Parameters\": {\n \"data_path\": \"./tests/api/data/data_eplus.csv\",\n \"data_source\": \"EnergyPlus\"\n },\n \"Payloads\": {\n \"data\": \"$.data\",\n \"data_processing_obj\": \"$\"\n },\n \"Next\": \"Slice data\",\n \"Start\": \"True\"\n }\n }\n}" |
Oops, something went wrong.