class MainWindow(QMainWindow): def __init__(self): super().__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.__setupEvents() def __setupEvents(self): def on_cad_indicacoes(): indicacoes = CadIndicacoes() indicacoes.show() def on_cad_alunos(): alunos = CadAlunos() alunos.show() def on_cad_pacientes(): pacientes = CadPacientes() pacientes.show() def on_cad_panoramicas(): panoramicas = CadPanoramicas() panoramicas.show() def on_cad_tomografias(): tomografias = CadTomografias() print("Teste") tomografias.show() self.ui.actionIndica_es.triggered.connect(on_cad_indicacoes) self.ui.actionAlunos.triggered.connect(on_cad_alunos) self.ui.actionPacientes.triggered.connect(on_cad_pacientes) self.ui.actionPanoramicas.triggered.connect(on_cad_panoramicas) self.ui.actionTomografias.triggered.connect(on_cad_tomografias) def keyPressEvent(self, event): if not event.isAutoRepeat(): if event.key() == Qt.Key_Escape: self.close()
class MainWindow(QMainWindow): def __init__(self, config): super(MainWindow, self).__init__() QFontDatabase.addApplicationFont( 'resources/fonts/poppins/Poppins-Medium.ttf') QFontDatabase.addApplicationFont( 'resources/fonts/source code pro/SourceCodePro-Regular.ttf') self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.splitter.setSizes([120, 800]) self.setWindowTitle('Ryven') self.setWindowIcon(QIcon('resources/pics/program_icon2.png')) self.load_stylesheet('dark') self.ui.scripts_tab_widget.removeTab(0) # menu actions self.flow_design_actions = [] self.setup_menu_actions() # shortcuts save_shortcut = QShortcut(QKeySequence.Save, self) save_shortcut.activated.connect(self.on_save_project_triggered) import_nodes_shortcut = QShortcut(QKeySequence('Ctrl+i'), self) import_nodes_shortcut.activated.connect(self.on_import_nodes_triggered) # clear temp folder if not os.path.exists('temp'): os.mkdir('temp') for f in os.listdir('temp'): os.remove('temp/' + f) # GENERAL ATTRIBUTES self.scripts = [] self.custom_nodes = [] self.all_nodes = [ SetVariable_Node(), GetVariable_Node(), Val_Node(), Result_Node() ] self.package_names = [] # holds NI subCLASSES for imported nodes: self.all_node_instance_classes = { self.all_nodes[0]: SetVar_NodeInstance, self.all_nodes[1]: GetVar_NodeInstance, self.all_nodes[2]: Val_NodeInstance, self.all_nodes[3]: Result_NodeInstance } # (key: node obj, val: NI subclass) (used in Flow) # custom subclasses for input widgets # {node : {str: PortInstanceWidget-subclass}} (used in PortInstance) self.custom_node_input_widget_classes = {} # UI self.scripts_list_widget = ScriptsListWidget(self, self.scripts) self.ui.scripts_scrollArea.setWidget(self.scripts_list_widget) self.ui.add_new_script_pushButton.clicked.connect( self.create_new_script_button_pressed) self.ui.new_script_name_lineEdit.returnPressed.connect( self.create_new_script_LE_return_pressed) if config['config'] == 'create plain new project': self.try_to_create_new_script() elif config['config'] == 'open project': print('importing packages...') self.import_packages(config['required packages']) print('loading project...') self.parse_project(config['content']) print('finished') print(''' CONTROLS placing nodes: right mouse selecting components: left mouse panning: middle mouse saving: ctrl+s ''') Design.set_flow_theme() Design.set_flow_theme() # temporary # the double call is just a temporary fix for an issue I will address in a future release. # Problem: because the signal emitted when setting a flow theme is directly connected to the according slots # in NodeInstance as well as NodeInstance_TitleLabel, the NodeInstance's slot (which starts an animation which # uses the title label's current and theme dependent color) could get called before the title # label's slot has been called to reinitialize this color. This results in wrong color end points for the # title label when activating animations. # This is pretty nasty since I cannot think of a nice fix for this issue other that not letting the slot # methods be called directly from the emitted signal but instead through a defined procedure like before. # maybe this will be necessary due to scheduling issues when loading flows # for s in self.scripts: # s.flow.viewport().update() self.resize(1500, 800) def setup_menu_actions(self): # flow designs for d in Design.flow_themes: design_action = QAction(d, self) self.ui.menuFlow_Design_Style.addAction(design_action) design_action.triggered.connect(self.on_design_action_triggered) self.flow_design_actions.append(design_action) self.ui.actionImport_Nodes.triggered.connect( self.on_import_nodes_triggered) self.ui.actionSave_Project.triggered.connect( self.on_save_project_triggered) self.ui.actionEnableDebugging.triggered.connect( self.on_enable_debugging_triggered) self.ui.actionDisableDebugging.triggered.connect( self.on_disable_debugging_triggered) self.ui.actionSave_Pic_Viewport.triggered.connect( self.on_save_scene_pic_viewport_triggered) self.ui.actionSave_Pic_Whole_Scene_scaled.triggered.connect( self.on_save_scene_pic_whole_triggered) # performance mode self.action_set_performance_mode_fast = QAction('Fast', self) self.action_set_performance_mode_fast.setCheckable(True) self.action_set_performance_mode_pretty = QAction('Pretty', self) self.action_set_performance_mode_pretty.setCheckable(True) performance_mode_AG = QActionGroup(self) performance_mode_AG.addAction(self.action_set_performance_mode_fast) performance_mode_AG.addAction(self.action_set_performance_mode_pretty) self.action_set_performance_mode_fast.setChecked(True) performance_mode_AG.triggered.connect(self.on_performance_mode_changed) performance_menu = QMenu('Performance Mode', self) performance_menu.addAction(self.action_set_performance_mode_fast) performance_menu.addAction(self.action_set_performance_mode_pretty) self.ui.menuView.addMenu(performance_menu) # animations self.action_set_animation_active = QAction('Enabled', self) self.action_set_animation_active.setCheckable(True) self.action_set_animations_inactive = QAction('Disabled', self) self.action_set_animations_inactive.setCheckable(True) animation_enabled_AG = QActionGroup(self) animation_enabled_AG.addAction(self.action_set_animation_active) animation_enabled_AG.addAction(self.action_set_animations_inactive) self.action_set_animation_active.setChecked(True) animation_enabled_AG.triggered.connect( self.on_animation_enabling_changed) animations_menu = QMenu('Animations', self) animations_menu.addAction(self.action_set_animation_active) animations_menu.addAction(self.action_set_animations_inactive) self.ui.menuView.addMenu(animations_menu) def load_stylesheet(self, ss): ss_content = '' try: f = open('resources/stylesheets/' + ss + '.txt') ss_content = f.read() f.close() finally: Design.ryven_stylesheet = ss_content self.setStyleSheet(ss_content) def on_performance_mode_changed(self, action): if action == self.action_set_performance_mode_fast: Design.set_performance_mode('fast') else: Design.set_performance_mode('pretty') def on_animation_enabling_changed(self, action): if action == self.action_set_animation_active: Design.animations_enabled = True else: Design.animations_enabled = False def on_design_action_triggered(self): index = self.flow_design_actions.index(self.sender()) Design.set_flow_theme(Design.flow_themes[index]) def on_enable_debugging_triggered(self): Debugger.enable() def on_disable_debugging_triggered(self): Debugger.disable() def on_save_scene_pic_viewport_triggered(self): """Saves a picture of the currently visible viewport.""" if len(self.scripts) == 0: return file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0] img = self.scripts[ self.ui.scripts_tab_widget.currentIndex()].flow.get_viewport_img() img.save(file_path) def on_save_scene_pic_whole_triggered(self): """Saves a picture of the whole currently visible scene.""" if len(self.scripts) == 0: return file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0] img = self.scripts[self.ui.scripts_tab_widget.currentIndex( )].flow.get_whole_scene_img() img.save(file_path) def create_new_script_button_pressed(self): self.try_to_create_new_script( name=self.ui.new_script_name_lineEdit.text()) def create_new_script_LE_return_pressed(self): self.try_to_create_new_script( name=self.ui.new_script_name_lineEdit.text()) def try_to_create_new_script(self, name='fancy script', config=None): """Tries to create a new script with a given name. If the name is already used or '', it fails.""" if len(name) == 0: return for s in self.scripts: if s.name == name: return new_script = Script(self, name, config) new_script.name_changed.connect(self.rename_script) self.ui.scripts_tab_widget.addTab(new_script.widget, new_script.name) self.scripts.append(new_script) self.scripts_list_widget.recreate_ui() def rename_script(self, script, new_name): self.ui.scripts_tab_widget.setTabText(self.scripts.index(script), new_name) script.name = new_name def delete_script(self, script): index = self.scripts.index(script) self.ui.scripts_tab_widget.removeTab(index) del self.scripts[index] def on_import_nodes_triggered(self): file_path = QFileDialog.getOpenFileName( self, 'select nodes file', '../packages', 'Ryven Packages(*.rpc)', )[0] if file_path != '': self.import_nodes_package(file_path) def import_packages(self, packages_list): for p in packages_list: self.import_nodes_package(p) def import_nodes_package(self, file_path): j_str = '' try: f = open(file_path) j_str = f.read() f.close() except FileExistsError or FileNotFoundError: Debugger.debug('couldn\'t open file') return # don't import a package twice if it already has been imported filename = os.path.splitext(os.path.basename(file_path)) if filename in self.package_names: return # Important: translate the package first (metacore files -> src code files) PackageTranslator = self.get_class_from_file( file_path='../Ryven_PackageTranslator', file_name='Ryven_PackageTranslator', class_name='PackageTranslator') package_translator = PackageTranslator( os.path.dirname(os.path.abspath(file_path))) self.parse_nodes(j_str, package_path=os.path.dirname(file_path), package_name=os.path.splitext( os.path.basename(file_path))[0]) # self.package_names.append(filename) def parse_nodes(self, j_str, package_path, package_name): """Parses the nodes from a node package in JSON format. Here, all the classes get imported and for every node a Node object with specific attribute values gets created.""" import json # strict=False is necessary to allow 'control characters' like '\n' for newline when loading the json j_obj = json.loads(j_str, strict=False) Debugger.debug(j_obj['type']) if j_obj['type'] != 'Ryven nodes package' and j_obj[ 'type'] != 'vyScriptFP nodes package': # old syntax return # package_title = j_obj['title'] # package_description = j_obj['description'] j_nodes_list = j_obj['nodes'] num_nodes = len(j_nodes_list) for ni in range(num_nodes): j_node = j_nodes_list[ni] self.parse_node(j_node, package_name, package_path) Debugger.debug(len(self.custom_nodes), 'nodes imported') def parse_node(self, j_node, package_name, package_path): new_node = Node() # loading the node's specifications which get finally set below after importing the classes node_title = j_node['title'] node_class_name = j_node['class name'] node_description = j_node['description'] node_type = j_node['type'] node_has_main_widget = j_node['has main widget'] node_main_widget_pos = j_node[ 'widget position'] if node_has_main_widget else None node_design_style = j_node['design style'] node_color = j_node['color'] # Every node has a custom module name which differs from it's name to prevent import issues when using # multiple (different) Nodes with same titles. For further info: see node manager node_module_name = j_node['module name'] module_name_separator = '___' # CUSTOM CLASS IMPORTS ---------------------------------------------------------------------------- # creating all the necessary path variables here for all potentially imported classes # IMPORT NODE INSTANCE SUBCLASS node_instance_class_file_path = package_path + '/nodes/' + node_module_name + '/' node_instance_widgets_file_path = node_instance_class_file_path + '/widgets' node_instance_filename = node_module_name # the NI file's name is just the 'module name' new_node_instance_class = self.get_class_from_file( file_path=node_instance_class_file_path, file_name=node_instance_filename, class_name=node_class_name + '_NodeInstance') self.all_node_instance_classes[new_node] = new_node_instance_class # IMPORT MAIN WIDGET if node_has_main_widget: main_widget_filename = node_module_name + module_name_separator + 'main_widget' new_node.main_widget_class = self.get_class_from_file( file_path=node_instance_widgets_file_path, file_name=main_widget_filename, class_name=node_class_name + '_NodeInstance_MainWidget') # IMPORT CUSTOM INPUT WIDGETS # I need to create the dict for the node's potential custom input widgets already here self.custom_node_input_widget_classes[new_node] = {} for w_name in j_node['custom input widgets']: input_widget_filename = node_module_name + module_name_separator + w_name custom_widget_class = self.get_class_from_file( file_path=node_instance_widgets_file_path, file_name=input_widget_filename, class_name=w_name + '_PortInstanceWidget') self.custom_node_input_widget_classes[new_node][ w_name] = custom_widget_class # --------------------------------------------------------------------------------------------------- j_n_inputs = j_node['inputs'] inputs = [] num_inputs = len(j_n_inputs) for ii in range(num_inputs): # loading info j_input = j_n_inputs[ii] i_type = j_input['type'] i_label = j_input['label'] i_has_widget = None i_widget_name = '' i_widget_pos = None if i_type == 'data': i_has_widget = j_input['has widget'] if i_has_widget: i_widget_name = j_input['widget name'] i_widget_pos = j_input['widget position'] # creating port new_input = NodePort() new_input.type_ = i_type new_input.label = i_label if i_has_widget: new_input.widget_name = i_widget_name new_input.widget_pos = i_widget_pos inputs.append(new_input) j_n_outputs = j_node['outputs'] outputs = [] num_outputs = len(j_n_outputs) for oi in range(num_outputs): # loading info j_output = j_n_outputs[oi] o_type = j_output['type'] o_label = j_output['label'] # creating port new_output = NodePort() new_output.type_ = o_type new_output.label = o_label outputs.append(new_output) # setting the Node's attributes new_node.title = node_title new_node.description = node_description new_node.type_ = node_type new_node.package = package_name new_node.has_main_widget = node_has_main_widget if node_has_main_widget: new_node.main_widget_pos = node_main_widget_pos new_node.design_style = node_design_style new_node.color = QColor(node_color) new_node.inputs = inputs new_node.outputs = outputs self.custom_nodes.append(new_node) self.all_nodes.append(new_node) def get_class_from_file(self, file_path, file_name, class_name): """Returns a class with a given name from a file for instantiation by importing the module. Used for all the dynamically imported classes: - NodeInstances - A NodeInstance's main widget - A NodeInstance's custom input widgets """ # Debugger.debug(file_path) # Debugger.debug(file_name) # Debugger.debug(class_name) sys.path.append(file_path) try: new_module = __import__(file_name, fromlist=[class_name]) except ModuleNotFoundError as e: QMessageBox.warning(self, 'Missing Python module', str(e)) sys.exit() new_class = getattr(new_module, class_name) return new_class def parse_project(self, j_obj): if j_obj['general info']['type'] != 'Ryven project file': return for s in j_obj['scripts']: # fill flows self.try_to_create_new_script(config=s) def on_save_project_triggered(self): file_name = QFileDialog.getSaveFileName( self, 'select location and give file name', '../saves', 'Ryven Project(*.rpo)')[0] if file_name != '': self.save_project(file_name) def save_project(self, file_name): import json file = None try: if os.path.exists(file_name): os.remove(file_name) file = open(file_name, 'w') except FileNotFoundError: Debugger.debug('couldn\'t open file') return general_project_info_dict = {'type': 'Ryven project file'} scripts_data = [] for script in self.scripts: scripts_data.append(script.get_json_data()) whole_project_dict = { 'general info': general_project_info_dict, 'scripts': scripts_data } json_str = json.dumps(whole_project_dict) Debugger.debug(json_str) file.write(json_str) file.close()
class ThermostatWindow(QMainWindow): def __init__(self): super().__init__() # setup UI self.ui = Ui_MainWindow() self.ui.setupUi(self) # setup thermostat self.thermostat = Thermostat() self.thermostat.tick_event.connect(self.update_from_arduino) self.thermostat.target_temperature_changed.connect(self.update_from_arduino) self.thermostat.start() self.on_termostat_status_changed() # setup clock self.clock_timer = QTimer() self.clock_timer.setSingleShot(False) self.clock_timer.setInterval(1000) self.update_clock() self.clock_timer.timeout.connect(self.update_clock) self.clock_timer.start() # setup web interface self.web = WebThermostat(self.thermostat) self.web.start() # connections self.ui.pb_up.pressed.connect(self.on_up_target_button_clicked) self.ui.pb_down.pressed.connect(self.on_down_target_button_clicked) self.ui.pb_ac.clicked.connect(self.on_ac_boutton_clicked) self.ui.pb_off.clicked.connect(self.on_off_button_clicked) self.ui.pb_heat.clicked.connect(self.on_heat_button_clicked) self.thermostat.mode_changed.connect(self.on_termostat_status_changed) @pyqtSlot(int) def update_temp_display(self, value: int): self.ui.lb_temp.setText("{}°C".format(value)) @pyqtSlot(int) def update_humidity_display(self, value: int): self.ui.lb_humidity.setText("{:02d}% 💧️".format(value)) @pyqtSlot(int) def update_target_temp_display(self, value: int): self.ui.lb_temp_target.setText("{}°C".format(value)) def update_status_icons(self, fan_status: bool, ac_status: bool, heat_status: bool): self.ui.lb_status_ac.setVisible(ac_status) self.ui.lb_status_hot.setVisible(heat_status) self.ui.lb_status_vent.setVisible(fan_status) @pyqtSlot() def update_from_arduino(self): self.update_temp_display(int(self.thermostat.last_temperature)) self.update_humidity_display(int(self.thermostat.last_humidity)) self.update_target_temp_display(int(self.thermostat.temperature_target)) self.update_status_icons(self.thermostat.last_fan_enabled, self.thermostat.last_ac_enabled, self.thermostat.last_heat_enabled) def on_up_target_button_clicked(self): t = self.thermostat.temperature_target self.thermostat.set_temperature_target(t + 1.0) def on_down_target_button_clicked(self): t = self.thermostat.temperature_target self.thermostat.set_temperature_target(t - 1.0) def on_ac_boutton_clicked(self): self.update_termostat_status("ac") def on_heat_button_clicked(self): self.update_termostat_status("heat") def on_off_button_clicked(self): self.update_termostat_status("off") def update_termostat_status(self, mode: str): assert mode in ["ac", "off", "heat"] self.thermostat.set_mode(mode) def on_termostat_status_changed(self): mode = self.thermostat.mode self.ui.pb_ac.setChecked(mode == "ac") self.ui.pb_off.setChecked(mode == "off") self.ui.pb_heat.setChecked(mode == "heat") @pyqtSlot() def update_clock(self): time_string = time.strftime("%A %d %B %H:%M:%S") # time_string = time.strftime("%c") self.ui.lb_time.setText(time_string)
class MainWindow(QMainWindow): """MainWindow still lacks cleanup and documentation, sorry.""" def __init__(self, config): super(MainWindow, self).__init__() QFontDatabase.addApplicationFont('fonts/poppins/Poppins-Medium.ttf') QFontDatabase.addApplicationFont('fonts/source code pro/SourceCodePro-Regular.ttf') self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.splitter.setSizes([120, 800]) self.setWindowTitle('pyScript') self.setWindowIcon(QIcon('stuff/pics/program_icon.png')) self.load_stylesheet('dark') self.ui.scripts_tab_widget.removeTab(0) self.ui.actionImport_Nodes.triggered.connect(self.on_import_nodes_triggered) self.ui.actionSave_Project.triggered.connect(self.on_save_project_triggered) self.ui.actionDesignDark_Std.triggered.connect(self.on_dark_std_design_triggered) self.ui.actionDesignDark_Tron.triggered.connect(self.on_dark_tron_design_triggered) self.ui.actionEnableDebugging.triggered.connect(self.on_enable_debugging_triggered) self.ui.actionDisableDebugging.triggered.connect(self.on_disable_debugging_triggered) self.ui.actionSave_Pic_Viewport.triggered.connect(self.on_save_scene_pic_viewport_triggered) self.ui.actionSave_Pic_Whole_Scene_scaled.triggered.connect(self.on_save_scene_pic_whole_triggered) # Shortcuts save_shortcut = QShortcut(QKeySequence.Save, self) save_shortcut.activated.connect(self.on_save_project_triggered) self.custom_nodes = [] self.all_nodes = [SetVariable_Node(), GetVariable_Node()] # holds NI subCLASSES for imported nodes: self.all_node_instance_classes = { self.all_nodes[0]: SetVar_NodeInstance, self.all_nodes[1]: GetVar_NodeInstance } # (key: node obj, val: NI subclass) (used in Flow) # {node : {str: PortInstanceWidget-subclass}} (used in PortInstance) self.custom_node_input_widget_classes = {} # clear temp folder for f in os.listdir('temp'): os.remove('temp/'+f) self.scripts = [] self.scripts_list_widget = ScriptsListWidget(self, self.scripts) self.ui.scripts_scrollArea.setWidget(self.scripts_list_widget) self.ui.add_new_script_pushButton.clicked.connect(self.create_new_script_button_pressed) self.ui.new_script_name_lineEdit.returnPressed.connect(self.create_new_script_le_return_pressed) self.design_style = 'dark std' if config['config'] == 'create plain new project': self.try_to_create_new_script() elif config['config'] == 'open project': self.import_required_packages(config['required packages']) self.parse_project(config['content']) self.resize(1500, 800) def load_stylesheet(self, ss): ss_content = '' try: f = open('stuff/stylesheets/'+ss+'.txt') ss_content = f.read() f.close() finally: self.setStyleSheet(ss_content) def on_dark_std_design_triggered(self): self.set_design('dark std') def on_dark_tron_design_triggered(self): self.set_design('dark tron') def set_design(self, new_design): Design.flow_style = new_design self.design_style = new_design for script in self.scripts: script.flow.design_style_changed() def on_enable_debugging_triggered(self): Debugger.enable() def on_disable_debugging_triggered(self): Debugger.disable() def on_save_scene_pic_viewport_triggered(self): if len(self.scripts) == 0: return file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0] img = self.scripts[self.ui.scripts_tab_widget.currentIndex()].flow.get_viewport_img() img.save(file_path) def on_save_scene_pic_whole_triggered(self): if len(self.scripts) == 0: return file_path = QFileDialog.getSaveFileName(self, 'select file', '', 'PNG(*.png)')[0] img = self.scripts[self.ui.scripts_tab_widget.currentIndex()].flow.get_whole_scene_img() img.save(file_path) def create_new_script_button_pressed(self): self.try_to_create_new_script(name=self.ui.new_script_name_lineEdit.text()) def create_new_script_le_return_pressed(self): self.try_to_create_new_script(name=self.ui.new_script_name_lineEdit.text()) def try_to_create_new_script(self, name='fancy script', config=None): if len(name) == 0: return for s in self.scripts: if s.name == name: return new_script = Script(self, name, config) new_script.name_changed.connect(self.rename_script) self.ui.scripts_tab_widget.addTab(new_script.widget, new_script.name) self.scripts.append(new_script) self.scripts_list_widget.recreate_ui() def rename_script(self, script, new_name): self.ui.scripts_tab_widget.setTabText(self.scripts.index(script), new_name) script.name = new_name def delete_script(self, script): index = self.scripts.index(script) self.ui.scripts_tab_widget.removeTab(index) del self.scripts[index] def on_import_nodes_triggered(self): file_path = QFileDialog.getOpenFileName(self, 'select nodes file', '../packages', 'PyScript Packages(*.pypac)',)[0] if file_path != '': self.import_nodes_package_from_file(file_path) def import_required_packages(self, packages_list): for p in packages_list: self.import_nodes_package_from_file(p) def import_nodes_package_from_file(self, file_path): j_str = '' try: f = open(file_path) j_str = f.read() f.close() except FileExistsError or FileNotFoundError: Debugger.debug('couldn\'t open file') return # Important: translate the package first (metacore files -> src code files) PackageTranslator = self.get_class_from_file(file_path='../pyScript_PackageTranslator', file_name='pyScript_PackageTranslator', class_name='PackageTranslator') package_translator = PackageTranslator(os.path.dirname(os.path.abspath(file_path))) self.parse_nodes(j_str, os.path.dirname(file_path), os.path.splitext(os.path.basename(file_path))[0]) def parse_nodes(self, j_str, package_path, package_name): import json # strict=False is necessary to allow 'control characters' like '\n' for newline when loading the json j_obj = json.loads(j_str, strict=False) Debugger.debug(j_obj['type']) if j_obj['type'] != 'vyScriptFP nodes package': return # package_title = j_obj['title'] # package_description = j_obj['description'] j_nodes_list = j_obj['nodes'] num_nodes = len(j_nodes_list) for ni in range(num_nodes): # new node j_node = j_nodes_list[ni] new_node = Node() node_title = j_node['title'] node_class_name = j_node['class name'] node_description = j_node['description'] node_type = j_node['type'] node_has_main_widget = j_node['has main widget'] node_main_widget_pos = j_node['widget position'] if node_has_main_widget else None node_design_style = j_node['design style'] node_color = j_node['color'] # every node has a custom module name which differs from it's name to prevent import issues when using # multiple (different) Nodes with same titles # FOR FURTHER EXPLANATION: see node manager node_module_name = j_node['module name'] module_name_separator = '___' # CUSTOM CLASS IMPORTS ---------------------------------------------------------------------------- # creating all the necessary path variables here for all potentially imported classes # IMPORT NODE INSTANCE SUBCLASS node_instance_class_file_path = package_path+'/nodes/'+node_module_name+'/' node_instance_widgets_file_path = node_instance_class_file_path+'/widgets' node_instance_filename = node_module_name # the NI file's name is just the 'module name' new_node_instance_class = self.get_class_from_file(file_path=node_instance_class_file_path, file_name=node_instance_filename, class_name=node_class_name+'_NodeInstance') self.all_node_instance_classes[new_node] = new_node_instance_class # IMPORT MAIN WIDGET if node_has_main_widget: main_widget_filename = node_module_name+module_name_separator+'main_widget' new_node.main_widget_class = self.get_class_from_file(file_path=node_instance_widgets_file_path, file_name=main_widget_filename, class_name=node_class_name+'_NodeInstance_MainWidget') # I need to create the dict for the node's potential custom input widgets already here self.custom_node_input_widget_classes[new_node] = {} for w_name in j_node['custom input widgets']: input_widget_filename = node_module_name+module_name_separator+w_name custom_widget_class = self.get_class_from_file(file_path=node_instance_widgets_file_path, file_name=input_widget_filename, class_name=w_name+'_PortInstanceWidget') self.custom_node_input_widget_classes[new_node][w_name] = custom_widget_class # note: the input widget classes get imported below in the loop # --------------------------------------------------------------------------------------------------- j_n_inputs = j_node['inputs'] inputs = [] num_inputs = len(j_n_inputs) for ii in range(num_inputs): j_input = j_n_inputs[ii] i_type = j_input['type'] i_label = j_input['label'] i_has_widget = None i_widget_type = '' i_widget_name = '' i_widget_pos = None if i_type == 'data': i_has_widget = j_input['has widget'] if i_has_widget: i_widget_type = j_input['widget type'] i_widget_pos = j_input['widget position'] if i_widget_type == 'custom widget': i_widget_name = j_input['widget name'] new_input = NodePort() new_input.type_ = i_type new_input.label = i_label if i_has_widget: new_input.widget_type = i_widget_type new_input.widget_name = i_widget_name if i_widget_pos: new_input.widget_pos = i_widget_pos else: new_input.widget_type = 'None' inputs.append(new_input) j_n_outputs = j_node['outputs'] outputs = [] num_outputs = len(j_n_outputs) for oi in range(num_outputs): j_output = j_n_outputs[oi] o_type = j_output['type'] o_label = j_output['label'] new_output = NodePort() new_output.type_ = o_type new_output.label = o_label outputs.append(new_output) new_node.title = node_title new_node.description = node_description new_node.type_ = node_type new_node.package = package_name new_node.has_main_widget = node_has_main_widget if node_has_main_widget: new_node.main_widget_pos = node_main_widget_pos new_node.design_style = node_design_style new_node.color = QColor(node_color) new_node.inputs = inputs new_node.outputs = outputs self.custom_nodes.append(new_node) self.all_nodes.append(new_node) Debugger.debug(len(self.custom_nodes), 'nodes imported') def get_class_from_file(self, file_path, file_name, class_name): Debugger.debug(file_path) Debugger.debug(file_name) Debugger.debug(class_name) sys.path.append(file_path) new_module = __import__(file_name, fromlist=[class_name]) new_class = getattr(new_module, class_name) return new_class def parse_project(self, j_obj): if j_obj['general info']['type'] != 'pyScriptFP project file': return for s in j_obj['scripts']: # fill flows self.try_to_create_new_script(config=s) def on_save_project_triggered(self): file_name = '' file_name = QFileDialog.getSaveFileName(self, 'select location and give file name', '../saves', 'PyScript Project(*.pypro)')[0] if file_name != '': self.save_project(file_name) def save_project(self, file_name): import json file = None try: file = open(file_name, 'w') except FileNotFoundError: Debugger.debug('couldn\'t open file') return general_project_info_dict = {'type': 'pyScriptFP project file'} scripts_data = [] for script in self.scripts: scripts_data.append(script.get_json_data()) whole_project_dict = {'general info': general_project_info_dict, 'scripts': scripts_data} json_str = json.dumps(whole_project_dict) Debugger.debug(json_str) file.write(json_str) file.close()
class MainWindow(QMainWindow): n = 8 def __init__(self): super(MainWindow, self).__init__() # Set up the user interface from Designer. self.ui = Ui_MainWindow() self.ui.setupUi(self) # Initial stuff self.chessboard = [['#' for _ in range(MainWindow.n)] for _ in range(MainWindow.n)] self.chessboard_btns = [] self.ui.centralwidget.setStyleSheet("background-color: #CBCBCB") self.ui.menubar.setStyleSheet("background-color: #A8A8A8") self.create_chessboard() self.reset_params_form() # Connect up the buttons. self.ui.resetButton.clicked.connect(self.reset_chessboard) self.ui.actionLoad.triggered.connect(self.load_file) self.ui.actionSave.triggered.connect(self.save_file) self.ui.solveButton.clicked.connect(self.solve) self.ui.algorithmComboBox.currentIndexChanged.connect( self.reset_params_form) # self.ui.paramComboBox.currentIndexChanged.connect(self.reset_params_comboBox) # Populate chessboard grid def create_chessboard(self): first_tile_in_row_light = True for i in range(MainWindow.n): tile_light = first_tile_in_row_light for j in range(MainWindow.n): btn = QPushButton() cbk = partial(self.chess_tile_clicked, btn) btn.clicked.connect(cbk) btn.setIconSize(QSize(50, 50)) btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) if tile_light: btn.setStyleSheet( "border-radius: 0px; background-color: #E8EBEF") else: btn.setStyleSheet( "border-radius: 0px; background-color: #7D8796") tile_light = not tile_light self.ui.gridLayout.addWidget(btn, i, j) first_tile_in_row_light = not first_tile_in_row_light def chess_tile_clicked(self, btn): index = self.ui.gridLayout.indexOf(btn) i = index // MainWindow.n j = index % MainWindow.n if self.chessboard[i][j] == '#': self.chessboard[i][j] = 'Q' btn.setIcon(QIcon('./ui/queen-tile.png')) elif self.chessboard[i][j] == 'Q': self.chessboard[i][j] = '#' btn.setIcon(QIcon()) def reset_chessboard(self): s = ChessboardState(self.chessboard) self.chessboard = [['#' for _ in range(MainWindow.n)] for _ in range(MainWindow.n)] for i in range(self.ui.gridLayout.count()): pass btn = self.ui.gridLayout.itemAt(i).widget() btn.setIcon(QIcon()) def refresh_chessboard(self): for i in range(MainWindow.n): for j in range(MainWindow.n): index = i * MainWindow.n + j btn = self.ui.gridLayout.itemAt(index).widget() if self.chessboard[i][j] == '#': btn.setIcon(QIcon()) elif self.chessboard[i][j] == 'Q': btn.setIcon(QIcon('./ui/queen-tile.png')) def reset_params_form(self): algorithm = self.ui.algorithmComboBox.currentText() if algorithm == "Hill Climbing": self.ui.paramLabel_1.setHidden(False) self.ui.paramLabel_1.setText("Restarts") self.ui.paramLabel_2.setHidden(True) self.ui.paramLineEdit_1.setHidden(False) self.ui.paramLineEdit_1.setText("10") self.ui.paramLineEdit_2.setHidden(True) self.ui.paramSelectLabel.setHidden(False) self.ui.paramComboBox.setHidden(False) self.ui.paramComboBox.setCurrentIndex(0) elif algorithm == "Beam Search": self.ui.paramLabel_1.setHidden(False) self.ui.paramLabel_1.setText("Beam Width K") self.ui.paramLabel_2.setHidden(True) self.ui.paramLineEdit_1.setHidden(False) self.ui.paramLineEdit_1.setText("8") self.ui.paramLineEdit_2.setHidden(True) self.ui.paramSelectLabel.setHidden(True) self.ui.paramComboBox.setHidden(True) elif algorithm == "Genetic Algorithm": self.ui.paramLabel_1.setHidden(False) self.ui.paramLabel_1.setText("Population Count") self.ui.paramLabel_2.setHidden(False) self.ui.paramLabel_2.setText("Generations") self.ui.paramLineEdit_1.setHidden(False) self.ui.paramLineEdit_1.setText("16") self.ui.paramLineEdit_2.setHidden(False) self.ui.paramLineEdit_2.setText("2500") self.ui.paramSelectLabel.setHidden(True) self.ui.paramComboBox.setHidden(True) elif algorithm == "CSP": self.ui.paramLabel_1.setHidden(True) self.ui.paramLabel_2.setHidden(True) self.ui.paramLineEdit_1.setHidden(True) self.ui.paramLineEdit_2.setHidden(True) self.ui.paramSelectLabel.setHidden(True) self.ui.paramComboBox.setHidden(True) def reset_params_comboBox(self): i = self.ui.paramComboBox.currentIndex() if i == 0: # Random Restart self.ui.paramLabel_1.setHidden(False) self.ui.paramLineEdit_1.setHidden(False) else: self.ui.paramLabel_1.setHidden(True) self.ui.paramLineEdit_1.setHidden(True) def load_file(self): file, _ = QFileDialog.getOpenFileName(QFileDialog(), 'Open File') if file: self.chessboard = read_config(file) self.refresh_chessboard() def save_file(self): file, _ = QFileDialog.getSaveFileName(QFileDialog(), 'Save') if file: write_config(file, self.chessboard) def solve(self): algorithm = self.ui.algorithmComboBox.currentText() solver = None initial_state = ChessboardState(chessboard=self.chessboard) final_state = None if algorithm == "Hill Climbing": i = self.ui.paramComboBox.currentIndex() restarts_limit = int(self.ui.paramLineEdit_1.text()) solver = HillClimbingSolver(['rr', 'sa'][i], restarts_limit) final_state = solver.solve(ChessboardStateNode(initial_state)) elif algorithm == "Beam Search": k = int(self.ui.paramLineEdit_1.text()) solver = BeamSearchSolver(k) final_state = solver.solve() elif algorithm == "Genetic Algorithm": pop = int(self.ui.paramLineEdit_1.text()) gen = int(self.ui.paramLineEdit_2.text()) solver = GASolver(n_population=pop, n_generations=gen) final_state = solver.solve() elif algorithm == "CSP": solver = CSPSolver() final_state = solver.solve(initial_state) self.chessboard = [['#' for _ in range(MainWindow.n)] for _ in range(MainWindow.n)] for i, j in final_state.queen_positions: self.chessboard[i][j] = 'Q' self.refresh_chessboard() self.ui.runningTimeLineEdit.setText(str(solver.get_running_time())) self.ui.costLineEdit.setText(str(solver.get_cost())) self.ui.expandedNodesLineEdit.setText(str(solver.get_expanded_count()))
class AppWindow(QMainWindow): change_data_signal = pyqtSignal(str) # str - name of action scan_files_signal = pyqtSignal() def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) Shared['AppWindow'] = self self.old_size = None self.old_pos = None self.restore_setting() self.set_actions() self.set_menus() self.setup_context_menu() self.open_dialog = Shared['DB choice dialog'] def set_actions(self): """ Connect handlers to tool bar actions and widgets' events :return: """ self.ui.actionOpenDB.triggered.connect( lambda: self.open_dialog.exec_()) self.ui.actionScanFiles.triggered.connect( lambda: self.scan_files_signal.emit()) self.ui.actionFileFilter.triggered.connect( lambda: self.change_data_signal.emit('Select files')) self.ui.actionFavorites.triggered.connect( lambda: self.change_data_signal.emit('Favorites')) self.ui.commentField.anchorClicked.connect(self.ref_clicked) self.ui.filesList.doubleClicked.connect( lambda: self.change_data_signal.emit('File_doubleClicked')) self.ui.dirTree.startDrag = self._start_drag self.ui.dirTree.dropEvent = self._drop_event self.ui.dirTree.dragMoveEvent = self._drag_move_event self.ui.filesList.startDrag = self._start_drag_files self.ui.filesList.resizeEvent = self.resize_event def _drag_move_event(self, event: QDragMoveEvent): index = self.ui.dirTree.indexAt(event.pos()) mime_data = event.mimeData() if mime_data.hasFormat(MimeTypes[real_folder]): if not self.ui.dirTree.model().is_virtual(index): event.ignore() else: event.accept() else: event.accept() def _drop_event(self, event: QDropEvent): mime_data: QMimeData = event.mimeData() index = self.ui.dirTree.indexAt(event.pos()) act = self._set_action(index, mime_data, event.pos()) res = self.ui.dirTree.model().dropMimeData(mime_data, act, index) if res: event.accept() else: event.ignore() def _set_action(self, index, mime_data, pos): """ index: in dirTree at drop position mime_data: pos: position where menu to be shown return: DropNoAction, DropCopyFolder, DropMoveFolder, DropCopyFile, DropMoveFile """ if mime_data.hasFormat(MimeTypes[real_folder]): if self.ui.dirTree.model().is_virtual(index): return DropCopyFolder return DropNoAction if mime_data.hasFormat(MimeTypes[file_real]): if self.ui.dirTree.model().is_virtual(index): return DropCopyFile return self._ask_action_file(pos) if mime_data.hasFormat(MimeTypes[file_virtual]): return self._ask_action_file(pos) if mime_data.hasFormat(MimeTypes[virtual_folder]): return self._ask_action_folder(pos) return DropNoAction def _ask_action_file(self, pos): """ Menu: drag-drop action chooser when drop file(s) to directory tree item(folder) Actions: Copy | Move | Cancel Returns: int (DropCopyFile=4, DropMoveFile=8, DropNoAction=0) """ return self._ask_action_folder(pos) * 4 def _ask_action_folder(self, pos): """ Menu: drag-drop action chooser when drop folder(s) to directory tree item(folder) Actions: Copy | Move | Cancel Returns: int (DropCopyFolder=1, DropMoveFolder=2, DropNoAction=0) """ menu = QMenu(self) menu.addAction('Copy') menu.addAction('Move') menu.addSeparator() menu.addAction('Cancel') action = menu.exec_(self.ui.dirTree.mapToGlobal(pos)) if action: if action.text() == 'Copy': return DropCopyFolder elif action.text() == 'Move': return DropMoveFolder return DropNoAction def _start_drag_files(self, action): drag = QDrag(self) drag.setPixmap(QPixmap(":/image/List.png")) indexes = self.ui.filesList.selectionModel().selectedRows() mime_data = self.ui.filesList.model().mimeData(indexes) drag.setMimeData(mime_data) drag.exec_(Qt.CopyAction) def _start_drag(self, action): drag = QDrag(self) drag.setPixmap(QPixmap(":/image/Folder.png")) indexes = self.ui.dirTree.selectionModel().selectedRows() mime_data = self.ui.dirTree.model().mimeData(indexes) drag.setMimeData(mime_data) if mime_data.hasFormat(MimeTypes[real_folder]): drag.exec_(Qt.CopyAction) elif mime_data.hasFormat(MimeTypes[virtual_folder]): drag.exec_(Qt.MoveAction) def set_menus(self): """ Set actions of main menu :return: """ menu = QMenu(self) open_db = menu.addAction('Open DB') change_font = menu.addAction('Change Font') set_fields = menu.addAction('Set fields') self.ui.btnOption.setMenu(menu) open_db.triggered.connect(lambda: self.open_dialog.exec_()) change_font.triggered.connect( lambda: self.change_data_signal.emit('change_font')) set_fields.triggered.connect( lambda: self.change_data_signal.emit('Set fields')) menu2 = QMenu(self) sel_opt = menu2.addAction('Selection options') self.ui.btnGetFiles.setMenu(menu2) sel_opt.triggered.connect( lambda: self.change_data_signal.emit('Selection options')) def setup_context_menu(self): """ Set context menus for each widget :return: """ self.ui.dirTree.customContextMenuRequested.connect(self._dir_menu) self.ui.filesList.customContextMenuRequested.connect(self._file_menu) self.ui.extList.customContextMenuRequested.connect(self._ext_menu) self.ui.tagsList.customContextMenuRequested.connect(self._tag_menu) self.ui.authorsList.customContextMenuRequested.connect( self._author_menu) def _file_menu(self, pos): idx = self.ui.filesList.indexAt(pos) if idx.isValid(): menu = QMenu(self) menu.addAction('Open') menu.addAction('Open folder') menu.addAction('Add to favorites') menu.addAction('Delete row') menu.addSeparator() menu.addAction('Copy file name') menu.addAction('Copy path') menu.addSeparator() menu.addAction('Rename file') menu.addAction('Copy file(s)') if self.ui.filesList.model().in_real_folder(idx): menu.addAction('Move file(s)') menu.addAction('Delete file(s)') action = menu.exec_(self.ui.filesList.mapToGlobal(pos)) if action: self.change_data_signal.emit('File {}'.format(action.text())) def _ext_menu(self, pos): menu = QMenu(self) menu.addAction('Remove unused') menu.addAction('Create group') menu.addAction('Delete all files with current extension') action = menu.exec_(self.ui.extList.mapToGlobal(pos)) if action: self.change_data_signal.emit('Ext {}'.format(action.text())) def _tag_menu(self, pos): idx = self.ui.tagsList.indexAt(pos) menu = QMenu(self) menu.addAction('Remove unused') if idx.isValid(): menu.addAction('Scan in names') menu.addAction('Rename') action = menu.exec_(self.ui.tagsList.mapToGlobal(pos)) if action: self.change_data_signal.emit('Tag {}'.format(action.text())) def _author_menu(self, pos): menu = QMenu(self) menu.addAction('Remove unused') action = menu.exec_(self.ui.authorsList.mapToGlobal(pos)) if action: self.change_data_signal.emit('Author {}'.format(action.text())) def _dir_menu(self, pos): idx = self.ui.dirTree.indexAt(pos) menu = QMenu(self) menu.addAction('Remove empty folders') if idx.isValid(): if self.ui.dirTree.model().is_virtual(idx): if not self.ui.dirTree.model().is_favorites(idx): menu.addSeparator() menu.addAction('Rename folder') menu.addAction('Delete folder') else: parent = self.ui.dirTree.model().parent(idx) if self.ui.dirTree.model().is_virtual(parent): menu.addSeparator() menu.addAction('Delete folder') menu.addAction('Rescan dir') menu.addSeparator() menu.addAction('Group') menu.addSeparator() menu.addAction('Create virtual folder') menu.addAction('Create virtual folder as child') else: menu.addAction('Create virtual folder') action = menu.exec_(self.ui.dirTree.mapToGlobal(pos)) if action: self.change_data_signal.emit('Dirs {}'.format(action.text())) def ref_clicked(self, href): """ Invoke methods to change file information: tags, authors, comment :param href: :return: """ self.ui.commentField.setSource(QUrl()) self.change_data_signal.emit(href.toString()) def resizeEvent(self, event): """ resizeEvent - when changed main window. To save size for next run :param event: :return: """ super().resizeEvent(event) self.old_size = event.oldSize() settings = QSettings() settings.setValue("MainFlow/Size", QVariant(self.size())) def resize_event(self, event: QResizeEvent): """ resizeEvent of filesList, to change width of columns :param event: :return: """ old_w = event.oldSize().width() w = event.size().width() if not old_w == w: self.change_data_signal.emit('Resize columns') def changeEvent(self, event): """ Save size and position of window before it maximized :param event: :return: """ if event.type() == QEvent.WindowStateChange: settings = QSettings() if event.oldState() == Qt.WindowMaximized: settings.setValue("MainFlow/isFullScreen", QVariant(False)) elif event.oldState() == Qt.WindowNoState and \ self.windowState() == Qt.WindowMaximized: settings.setValue("MainFlow/isFullScreen", QVariant(True)) if self.old_size: settings.setValue("MainFlow/Size", QVariant(self.old_size)) if self.old_pos: settings.setValue("MainFlow/Position", QVariant(self.old_pos)) else: super().changeEvent(event) def moveEvent(self, event): """ Save new position of window :param event: :return: """ self.old_pos = event.oldPos() settings = QSettings() settings.setValue("MainFlow/Position", QVariant(self.pos())) super().moveEvent(event) def restore_setting(self): settings = QSettings() if settings.contains("MainFlow/Size"): size = settings.value("MainFlow/Size", QSize(640, 480)) self.resize(size) position = settings.value("MainFlow/Position") self.move(position) self.restoreState(settings.value("MainFlow/State")) self.restoreState(settings.value("MainFlow/State")) self.ui.splitter_files.restoreState( settings.value("FilesSplitter")) self.ui.opt_splitter.restoreState(settings.value("OptSplitter")) self.ui.main_splitter.restoreState(settings.value("MainSplitter")) if settings.value("MainFlow/isFullScreen", False, type=bool): self.showMaximized() else: self.ui.main_splitter.setStretchFactor(0, 2) self.ui.main_splitter.setStretchFactor(1, 5) self.ui.main_splitter.setStretchFactor(2, 1) self.ui.splitter_files.setStretchFactor(0, 5) self.ui.splitter_files.setStretchFactor(1, 2) def first_open_data_base(self): """ Open DB when application starts :return: """ if not self.open_dialog.skip_open_dialog(): self.open_dialog.exec_() else: self.open_dialog.emit_open_dialog() def closeEvent(self, event): self.open_dialog.save_init_data() settings = QSettings() settings.setValue("MainFlow/State", QVariant(self.saveState())) settings.setValue("FilesSplitter", QVariant(self.ui.splitter_files.saveState())) settings.setValue("OptSplitter", QVariant(self.ui.opt_splitter.saveState())) settings.setValue("MainSplitter", QVariant(self.ui.main_splitter.saveState())) super(AppWindow, self).closeEvent(event)