def overlay_text( self, message: str, color: int, size: int, x: int, y: int, timeout: int, font_name: str, centered: bool, shadow: bool, ): gfx = QGraphicsTextItem(message) gfx.setDefaultTextColor(decode_color(color)) font = QFont(font_name, min(50, size)) font.setStyleHint(QFont.SansSerif) gfx.setFont(font) if shadow: effect = QGraphicsDropShadowEffect(gfx) effect.setBlurRadius(0) effect.setColor(Qt.GlobalColor.black) effect.setOffset(1, 1) gfx.setGraphicsEffect(effect) if centered: # The provided x, y is at the center of the text bound = gfx.boundingRect() gfx.setPos(x - (bound.width() / 2), y - (bound.height() / 2)) else: gfx.setPos(x, y) self._finalize_gfx(gfx, timeout)
def css_button(qtwindow, button, color=None, shadow=True): ''' apply style to a button widget ''' # default bg color is gray 80 if color == 'red': bg_color = 'rgb(160, 20, 20)' # red/white tx_color = 'rgb(255, 255, 255)' elif color == 'disabled': bg_color = 'rgb(80, 80, 80)' # gray/gray tx_color = 'rgb(180, 180, 180)' elif color == 'blue': bg_color = 'rgb(46, 134, 193)' # blue arcane/white tx_color = 'rgb(230, 230, 230)' else: bg_color = 'rgb(80, 80, 80)' # gray tx_color = 'rgb(230, 230, 230)' css = "border-radius:3px;color:{};background:{};font-size:12px;font-family:Segoe UI;".format( tx_color, bg_color) button.setStyleSheet(css) if shadow: shadow = QGraphicsDropShadowEffect(qtwindow) shadow.setBlurRadius(6) shadow.setOffset(4) shadow.setColor(QColor(20, 20, 20, 200)) button.setGraphicsEffect(shadow)
def card_shadow(self): effect = QGraphicsDropShadowEffect(self.main_widget) effect.setColor(QColor(0, 0, 0, 100)) effect.setXOffset(2) effect.setYOffset(2) effect.setBlurRadius(5) return effect
def button_shadow(self): effect = QGraphicsDropShadowEffect(self.window) effect.setColor(QColor(0, 0, 0, 100)) effect.setXOffset(1) effect.setYOffset(1) effect.setBlurRadius(5) return effect
def __init__(self, text: str, object_name: str = "blue"): super().__init__(text) self.setCursor(Qt.PointingHandCursor) if object_name: self.setObjectName(object_name) effect = QGraphicsDropShadowEffect(self) effect.setColor(QColor(0, 0, 0, 0.25 * 255)) effect.setOffset(2, 4) effect.setBlurRadius(4) self.setGraphicsEffect(effect)
def add_window_drop_shadow() -> None: """Adds a drop-shadow behind the window""" if self.__use_shadow: self.layout().setMargin(self.__style.window.SHADOW_RADIUS_PX) drop_shadow_effect = QGraphicsDropShadowEffect(self) drop_shadow_effect.setEnabled(True) drop_shadow_effect.setBlurRadius( self.__style.window.SHADOW_RADIUS_PX) color = QColor(self.__style.window.SHADOW_COLOR_RGB) color.setAlpha(self.__style.window.SHADOW_OPACITY_HEX) drop_shadow_effect.setColor(color) drop_shadow_effect.setOffset(0) self.setGraphicsEffect(drop_shadow_effect)
class MainWindow(QtWidgets.QMainWindow): role_value = None DATA_PATH = 'D:\\SASTRA\Code\\Dean Mam Project\\assignment\\Data\\Medical_Records.csv' def __init__(self): print('Main init') QtWidgets.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.stackedWidget.setCurrentIndex(0) for i in RBACClass.permission_dict['Administrator']: self.ui.tableWidget.setColumnHidden(i, True) ## ==> MAXIMIZE RESTORE FUNCTION def maximize_restore(): global GLOBAL_STATE status = GLOBAL_STATE # IF NOT MAXIMIZED if status == 0: self.showMaximized() # SET GLOBAL TO 1 GLOBAL_STATE = 1 # IF MAXIMIZED REMOVE MARGINS AND BORDER RADIUS self.ui.drop_shadow_frame.setContentsMargins(0, 0, 0, 0) self.ui.drop_shadow_frame.setStyleSheet( "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(42, 44, 111, 255), stop:0.521368 rgba(28, 29, 73, 255)); border-radius: 0px;" ) self.ui.maximize_btn.setToolTip("Restore") else: GLOBAL_STATE = 0 self.showNormal() self.resize(self.width() + 1, self.height() + 1) self.ui.drop_shadow_frame.setContentsMargins(10, 10, 10, 10) self.ui.drop_shadow_frame.setStyleSheet( "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(42, 44, 111, 255), stop:0.521368 rgba(28, 29, 73, 255)); border-radius: 10px;" ) self.ui.maximize_btn.setToolTip("Maximize") ## ==> UI DEFINITIONS def uiDefinitions(): # REMOVE TITLE BAR self.setWindowFlag(QtCore.Qt.FramelessWindowHint) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # SET DROPSHADOW WINDOW self.shadow = QGraphicsDropShadowEffect(self) self.shadow.setBlurRadius(20) self.shadow.setXOffset(0) self.shadow.setYOffset(0) self.shadow.setColor(QColor(0, 0, 0, 100)) # APPLY DROPSHADOW TO FRAME self.ui.drop_shadow_frame.setGraphicsEffect(self.shadow) # MAXIMIZE / RESTORE self.ui.maximize_btn.clicked.connect(lambda: maximize_restore()) # MINIMIZE self.ui.minimize_btn.clicked.connect(lambda: self.showMinimized()) # CLOSE self.ui.close_btn.clicked.connect(lambda: self.close()) ## ==> CREATE SIZE GRIP TO RESIZE WINDOW self.sizegrip = QSizeGrip(self.ui.frame_grip) self.sizegrip.setStyleSheet( "QSizeGrip { width: 10px; height: 10px; margin: 5px } QSizeGrip:hover { background-color: rgb(50, 42, 94) }" ) self.sizegrip.setToolTip("Resize Window") def rolePermission(role): for i in role: self.ui.tableWidget.setColumnHidden(i, False) # MOVE WINDOW def moveWindowMain(event): # RESTORE BEFORE MOVE if self.returnStatus() == 1: self.maximize_restore(self) # IF LEFT CLICK MOVE WINDOW if event.buttons() == Qt.LeftButton: self.move(self.pos() + event.globalPos() - self.dragPos) self.dragPos = event.globalPos() event.accept() def view_details(): patient_id = self.ui.patientIdLineEdit_2.text() print(patient_id) self.ui.stackedWidget.setCurrentIndex(1) with open(MainWindow.DATA_PATH, 'r') as datafile: print('inside file') readData = csv.reader(datafile) rowValue = 0 for row in readData: print('inside row') if row[1] == patient_id: self.ui.tableWidget.insertRow(rowValue) print(row[1], type(row[1])) for i in MainWindow.role_value: print('set') self.ui.tableWidget.setItem( rowValue, i, QTableWidgetItem(row[i])) rowValue = rowValue + 1 def go_back(): self.ui.stackedWidget.setCurrentIndex(0) ## ==> SET UI DEFINITIONS uiDefinitions() print(MainWindow.role_value, "rolePerm") rolePermission(MainWindow.role_value) self.ui.viewDetailsPushButton_2.clicked.connect(view_details) self.ui.back_btn.clicked.connect(go_back) # SET TITLE BAR self.ui.title_bar_2.mouseMoveEvent = moveWindowMain ## SHOW ==> MAIN WINDOW ######################################################################## self.show() ## RETURN STATUS IF WINDOWS IS MAXIMIZE OR RESTAURED def returnStatus(self): return GLOBAL_STATE ## APP EVENTS ######################################################################## def mousePressEvent(self, event): self.dragPos = event.globalPos()
class LoginWindow(QtWidgets.QWidget): def __init__(self): QtWidgets.QWidget.__init__(self) self.ui = Ui_Form() self.ui.setupUi(self) def uiDefinition(): # REMOVE TITLE BAR self.shadow = QGraphicsDropShadowEffect(self) self.setWindowFlag(QtCore.Qt.FramelessWindowHint) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # SET DROPSHADOW WINDOW self.shadow.setBlurRadius(20) self.shadow.setXOffset(0) self.shadow.setYOffset(0) self.shadow.setColor(QColor(0, 0, 0, 100)) # APPLY DROPSHADOW TO FRAME self.ui.login_frame.setGraphicsEffect(self.shadow) # CLOSE self.ui.closePushButton.clicked.connect(lambda: self.close()) # MOVE WINDOW def moveWindowLogin(event): # IF LEFT CLICK MOVE WINDOW if event.buttons() == Qt.LeftButton(): self.move(self.pos() + event.globalPos() - self.dragPos) self.dragPos = event.globalPos() event.accept() def show_new_window(): print(self.ui.userNameLineEdit.text(), self.ui.passwordLineEdit.text()) r = RBACClass() MainWindow.role_value = r.logon( UserName=self.ui.userNameLineEdit.text(), Password=self.ui.passwordLineEdit.text()) print(MainWindow.role_value, "show") if MainWindow.role_value != None: print('here') self.close() w = MainWindow() w.show() else: print('no') self.ui.userNameLineEdit.setPlaceholderText('Try Again') self.ui.passwordLineEdit.setPlaceholderText('Try Again') # SET TITLE BAR self.ui.login_frame.mouseMoveEvent = moveWindowLogin self.ui.loginPushButton.clicked.connect(show_new_window) ## ==> SET UI DEFINITIONS uiDefinition() ## SHOW ==> MAIN WINDOW ######################################################################## self.show() ## APP EVENTS ######################################################################## def mousePressEvent(self, event): self.dragPos = event.globalPos()
class NodeInstance(QGraphicsItem): def __init__(self, params): super(NodeInstance, self).__init__() self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemSendsScenePositionChanges) self.setAcceptHoverEvents(True) # GENERAL ATTRIBUTES # the constructor parameters are stored in a tuple to make the source code of custom NIs cleaner parent_node, flow, config = params self.parent_node = parent_node self.flow = flow self.movement_state = None self.movement_pos_from = None self.painted_once = False self.inputs = [] self.outputs = [] self.color = self.parent_node.color # manipulated by self.animator # self.node_instance_painter = NodeInstancePainter(self) self.default_actions = { 'remove': { 'method': self.action_remove }, 'update shape': { 'method': self.update_shape } } # for context menus self.special_actions = { } # only gets written in custom NodeInstance-subclasses self.personal_logs = [] # 'initializing' will be set to False below. It's needed for the ports setup, to prevent shape updating stuff self.initializing = True self.temp_state_data = None self.init_config = config # UI self.shadow_effect = None self.width = -1 self.height = -1 self.title_label = TitleLabel(self) self.animator = NodeInstanceAnimator(self) # needs self.title_label self.main_widget = None self.main_widget_proxy: FlowProxyWidget = None if self.parent_node.has_main_widget: self.main_widget = self.parent_node.main_widget_class(self) self.main_widget_proxy = FlowProxyWidget(self.flow) self.main_widget_proxy.setWidget(self.main_widget) # LOADING UI self.body_layout: QGraphicsLinearLayout = None self.inputs_layout: QGraphicsLinearLayout = None self.outputs_layout: QGraphicsLinearLayout = None self.layout: QGraphicsLinearLayout = self.setup_ui() self.widget = QGraphicsWidget(self) self.widget.setLayout(self.layout) # TOOLTIP if self.parent_node.description != '': self.setToolTip('<html><head/><body><p>' + self.parent_node.description + '</p></body></html>') self.setCursor(Qt.SizeAllCursor) # DESIGN THEME Design.flow_theme_changed.connect(self.theme_changed) def initialized(self): """All ports and the main widget get finally created here.""" # LOADING CONFIG if self.init_config is not None: # self.setPos(config['position x'], config['position y']) self.setup_ports(self.init_config['inputs'], self.init_config['outputs']) if self.main_widget: try: self.main_widget.set_data( self.init_config['main widget data']) except Exception as e: print('Exception while setting data in', self.parent_node.title, 'NodeInstance\'s main widget:', e, ' (was this intended?)') self.special_actions = self.set_special_actions_data( self.init_config['special actions']) self.temp_state_data = self.init_config['state data'] else: self.setup_ports() # LOADING DATA if self.temp_state_data is not None: try: self.set_data(self.temp_state_data) except Exception as e: print('Exception while setting data in', self.parent_node.title, 'NodeInstance:', e, ' (was this intended?)') self.initializing = False # No self.update_shape() here because for some reason, the bounding rect hasn't been initialized yet, so # self.update_shape() gets called when the item is being drawn the first time (see paint event in NI painter) # TODO: change that ^ once there is a solution for this: https://forum.qt.io/topic/117179/force-qgraphicsitem-to-update-immediately-wait-for-update-event self.update_design() # load current design, update QGraphicsItem self.update() # and finally update the NodeInstance once def setup_ui(self): """Creates the empty layouts for the NI's widget.""" # main layout layout = QGraphicsLinearLayout(Qt.Vertical) layout.setSpacing(10) if self.parent_node.design_style == 'extended': layout.addItem(self.title_label) layout.setAlignment(self.title_label, Qt.AlignTop) # inputs self.inputs_layout = QGraphicsLinearLayout(Qt.Vertical) self.inputs_layout.setSpacing(2) # outputs self.outputs_layout = QGraphicsLinearLayout(Qt.Vertical) self.outputs_layout.setSpacing(2) # body self.body_layout = QGraphicsLinearLayout(Qt.Horizontal) self.body_layout.setSpacing(4) self.body_layout.addItem(self.inputs_layout) self.body_layout.setAlignment(self.inputs_layout, Qt.AlignVCenter | Qt.AlignLeft) self.body_layout.addStretch() self.body_layout.addItem(self.outputs_layout) self.body_layout.setAlignment(self.outputs_layout, Qt.AlignVCenter | Qt.AlignRight) if self.main_widget is not None: if self.parent_node.main_widget_pos == 'between ports': self.body_layout.insertItem(1, self.main_widget_proxy) self.body_layout.insertStretch(2) layout.addItem(self.body_layout) elif self.parent_node.main_widget_pos == 'under ports': layout.addItem(self.body_layout) layout.addItem(self.main_widget_proxy) layout.setAlignment(self.main_widget_proxy, Qt.AlignHCenter) else: layout.addItem(self.body_layout) return layout def rebuild_ui(self): """Due to some really strange and annoying behaviour of these QGraphicsWidgets, they don't want to shrink automatically when content is removed, they just stay large, even with a Minimum SizePolicy. I didn't find a way around that yet, so for now I have to recreate the whole layout and make sure the widget uses the smallest size possible.""" # if I don't manually remove the ports from the layouts, # they get deleted when setting the widget's layout to None below for inp in self.inputs: self.inputs_layout.removeAt(0) for out in self.outputs: self.outputs_layout.removeAt(0) self.layout = self.setup_ui() # recreate layout # forcefully making the widget shrink self.widget.setLayout(None) self.widget.resize(self.widget.minimumSize()) self.widget.setLayout(self.layout) # add inputs to new layout for inp in self.inputs: self.add_input_to_layout(inp) for out in self.outputs: self.add_output_to_layout(out) # __ _ __ __ # ____ _ / / ____ _ ____ _____ (_) / /_ / /_ ____ ___ # / __ `/ / / / __ `/ / __ \ / ___/ / / / __/ / __ \ / __ `__ \ # / /_/ / / / / /_/ / / /_/ / / / / / / /_ / / / / / / / / / / # \__,_/ /_/ \__, / \____/ /_/ /_/ \__/ /_/ /_/ /_/ /_/ /_/ # /____/ def update(self, input_called=-1, output_called=-1): """This is the method used to activate a NodeInstance. Note that this signature shadows the update() method from QGraphicsItem used to graphically update a QGraphicsItem which can be accessed via QGraphicsItem.update(self).""" if Design.animations_enabled: self.animator.start() Debugger.debug('update in', self.parent_node.title, 'on input', input_called) try: self.update_event(input_called) except Exception as e: Debugger.debug('EXCEPTION IN', self.parent_node.title, 'NI:', e) def update_event(self, input_called=-1): """Gets called when an input received a signal. This is where the magic begins in subclasses.""" pass def input(self, index): """Returns the value of a data input. If the input is connected, the value of the connected output is used: If not, the value of the widget is used.""" Debugger.debug('input called in', self.parent_node.title, 'NI:', index) return self.inputs[index].get_val() def exec_output(self, index): """Executes an execution output, sending a signal to all connected execution inputs causing the connected NIs to update.""" self.outputs[index].exec() def set_output_val(self, index, val): """Sets the value of a data output. self.data_outputs_updated() has to be called manually after all values are set.""" if not self.flow.viewport_update_mode.sync: # asynchronous viewport updates vp = self.flow.viewport() vp.repaint(self.flow.mapFromScene(self.sceneBoundingRect())) self.outputs[index].set_val(val) def data_outputs_updated(self): """(outdated!) Sends update signals to all data outputs causing connected NIs to update.""" Debugger.debug('updating data outputs in', self.parent_node.title) for o in self.outputs: if o.type_ == 'data': o.updated_val() Debugger.debug('data outputs in', self.parent_node.title, 'updated') def remove_event(self): """Method to stop all threads in hold of the NI itself.""" pass # _ # ____ _ ____ (_) # / __ `/ / __ \ / / # / /_/ / / /_/ / / / # \__,_/ / .___/ /_/ # /_/ # # all algorithm-unrelated api methods: # LOGGING def new_log(self, title): """Requesting a new personal Log. Handy method for subclasses.""" new_log = self.flow.parent_script.logger.new_log(self, title) self.personal_logs.append(new_log) return new_log def disable_personal_logs(self): """Disables personal Logs. They remain visible unless the user closes them via the appearing button.""" for log in self.personal_logs: log.disable() def enable_personal_logs(self): """Resets personal Logs to normal state (hiding close button, changing style sheet).""" for log in self.personal_logs: log.enable() def log_message(self, message: str, target='global'): """Access to global_tools Script Logs ('global' or 'error').""" self.flow.parent_script.logger.log_message(message, target) # SHAPE def update_shape(self): """Causes recompilation of the whole shape.""" # if not self.initializing: # just to make sure # self.rebuild_ui() # (hopefully) temporary fix -> see rebuild_ui() docstring if self.main_widget is not None: # maybe the main_widget got resized self.main_widget_proxy.setMaximumSize(self.main_widget.size()) self.widget.adjustSize() self.widget.adjustSize() self.body_layout.invalidate() self.layout.invalidate() self.layout.activate() # very essential; repositions everything in case content has changed (inputs/outputs/widget) if self.parent_node.design_style == 'minimalistic': # making it recompute its true minimumWidth here self.widget.adjustSize() if self.layout.minimumWidth() < self.title_label.width + 15: self.layout.setMinimumWidth(self.title_label.width + 15) self.layout.activate() self.width = self.boundingRect().width() self.height = self.boundingRect().height() rect = QRectF(QPointF(-self.width / 2, -self.height / 2), QPointF(self.width / 2, self.height / 2)) self.widget.setPos(rect.left(), rect.top()) if not self.parent_node.design_style == 'extended': self.title_label.setPos( QPointF(-self.title_label.boundingRect().width() / 2, -self.title_label.boundingRect().height() / 2)) self.flow.viewport().update() # PORTS def create_new_input(self, type_, label, widget_name=None, widget_pos='under', pos=-1, config=None): """Creates and adds a new input. Handy for subclasses.""" Debugger.debug('create_new_input called') pi = InputPortInstance(self, type_, label, config_data=config, widget_name=widget_name, widget_pos=widget_pos) if pos < -1: pos += len(self.inputs) if pos == -1: self.inputs.append(pi) self.add_input_to_layout(pi) else: self.inputs.insert(pos, pi) self.insert_input_into_layout(pos, pi) if not self.initializing: self.update_shape() self.update() def add_input_to_layout(self, i): if self.inputs_layout.count() > 0: self.inputs_layout.addStretch() self.inputs_layout.addItem(i) self.inputs_layout.setAlignment(i, Qt.AlignLeft) def insert_input_into_layout(self, index, i): self.inputs_layout.insertItem(index * 2, i) # *2 because of the stretches self.inputs_layout.setAlignment(i, Qt.AlignLeft) if len(self.inputs) > 1: self.inputs_layout.insertStretch( index * 2 + 1) # *2+1 because of the stretches, too def delete_input(self, i): """Disconnects and removes input. Handy for subclasses.""" inp: InputPortInstance = None if type(i) == int: inp = self.inputs[i] elif type(i) == InputPortInstance: inp = i for cpi in inp.connected_port_instances: self.flow.connect_gates(inp.gate, cpi.gate) # for some reason, I have to remove all widget items manually from the scene too. setting the items to # ownedByLayout(True) does not work, I don't know why. self.scene().removeItem(inp.gate) self.scene().removeItem(inp.label) if inp.proxy is not None: self.scene().removeItem(inp.proxy) self.inputs_layout.removeItem(inp) self.inputs.remove(inp) # just a temporary workaround for the issues discussed here: # https://forum.qt.io/topic/116268/qgraphicslayout-not-properly-resizing-to-change-of-content self.rebuild_ui() if not self.initializing: self.update_shape() self.update() def create_new_output(self, type_, label, pos=-1): """Creates and adds a new output. Handy for subclasses.""" pi = OutputPortInstance(self, type_, label) if pos < -1: pos += len(self.outputs) if pos == -1: self.outputs.append(pi) self.add_output_to_layout(pi) else: self.outputs.insert(pos, pi) self.insert_output_into_layout(pos, pi) if not self.initializing: self.update_shape() self.update() def add_output_to_layout(self, o): if self.outputs_layout.count() > 0: self.outputs_layout.addStretch() self.outputs_layout.addItem(o) self.outputs_layout.setAlignment(o, Qt.AlignRight) def insert_output_into_layout(self, index, o): self.outputs_layout.insertItem(index * 2, o) # *2 because of the stretches self.outputs_layout.setAlignment(o, Qt.AlignRight) if len(self.outputs) > 1: self.outputs_layout.insertStretch( index * 2 + 1) # *2+1 because of the stretches, too def delete_output(self, o): """Disconnects and removes output. Handy for subclasses.""" out: OutputPortInstance = None if type(o) == int: out = self.outputs[o] elif type(o) == OutputPortInstance: out = o for cpi in out.connected_port_instances: self.flow.connect_gates(out.gate, cpi.gate) # see delete_input() for info! self.scene().removeItem(out.gate) self.scene().removeItem(out.label) self.outputs_layout.removeItem(out) self.outputs.remove(out) # just a temporary workaround for the issues discussed here: # https://forum.qt.io/topic/116268/qgraphicslayout-not-properly-resizing-to-change-of-content self.rebuild_ui() if not self.initializing: self.update_shape() self.update() # GET, SET DATA def get_data(self): """ This method gets subclassed and specified. If the NI has states (so, the behavior depends on certain values), all these values must be stored in JSON-able format in a dict here. This dictionary will be used to reload the node's state when loading a project or pasting copied/cut nodes in the Flow (the states get copied too), see self.set_data(self, data) below. Unfortunately, I can't use pickle or something like that due to PySide2 which runs on C++, not Python. :return: Dictionary representing all values necessary to determine the NI's current state """ return {} def set_data(self, data): """ If the NI has states, it's state should get reloaded here according to what was previously provided by the same class in get_data(), see above. :param data: Dictionary representing all values necessary to determine the NI's current state """ pass @staticmethod def get_default_stylesheet(): """Handy method for subclasses to access the application window's stylesheet for UI content.""" return Design.ryven_stylesheet # VARIABLES def get_vars_handler(self): return self.flow.parent_script.variables_handler def get_var_val(self, name): return self.get_vars_handler().get_var_val(name) def set_var_val(self, name, val): return self.get_vars_handler().set_var(name, val) def register_var_receiver(self, name, method): self.get_vars_handler().register_receiver(self, name, method) def unregister_var_receiver(self, name): self.get_vars_handler().unregister_receiver(self, name) # -------------------------------------------------------------------------------------- # UI STUFF ---------------------------------------- def theme_changed(self, new_theme): self.title_label.theme_changed(new_theme) self.update_design() def update_design(self): """Loads the shadow effect option and causes redraw with active theme.""" if Design.node_instance_shadows_shown: self.shadow_effect = QGraphicsDropShadowEffect() self.shadow_effect.setXOffset(12) self.shadow_effect.setYOffset(12) self.shadow_effect.setBlurRadius(20) self.shadow_effect.setColor(QColor('#2b2b2b')) self.setGraphicsEffect(self.shadow_effect) else: self.setGraphicsEffect(None) self.title_label.update_design() # print(self.title_label.color) self.animator.reload_values() QGraphicsItem.update(self) def boundingRect(self): # remember: (0, 0) shall be the NI's center! rect = QRectF() w = self.layout.geometry().width() h = self.layout.geometry().height() rect.setLeft(-w / 2) rect.setTop(-h / 2) rect.setWidth(w) rect.setHeight(h) return rect # PAINTING def paint(self, painter, option, widget=None): """All painting is done by NodeInstancePainter""" # in order to access a meaningful geometry of GraphicsWidget contents in update_shape(), the paint event # has to be called once. See here: # https://forum.qt.io/topic/117179/force-qgraphicsitem-to-update-immediately-wait-for-update-event/4 if not self.painted_once: self.title_label.update_design() # also necessary self.update_shape() # self.node_instance_painter.paint(painter, option, self.color, self.width, self.height, self.boundingRect(), # Design.flow_theme, widget) Design.flow_theme.node_inst_painter.paint_NI( design_style=self.parent_node.design_style, painter=painter, option=option, c=self.color, w=self.width, h=self.height, bounding_rect=self.boundingRect(), title_rect=self.title_label.boundingRect()) self.painted_once = True def get_context_menu(self): menu = QMenu(self.flow) for a in self.get_actions(self.get_extended_default_actions(), menu): # menu needed for 'parent' if type(a) == NodeInstanceAction: menu.addAction(a) elif type(a) == QMenu: menu.addMenu(a) menu.addSeparator() actions = self.get_actions(self.special_actions, menu) for a in actions: # menu needed for 'parent' if type(a) == NodeInstanceAction: menu.addAction(a) elif type(a) == QMenu: menu.addMenu(a) return menu def itemChange(self, change, value): """This method ensures that all connections, selection borders etc. that get drawn in the Flow are constantly redrawn during a NI drag. Should get disabled when running in performance mode - not implemented yet.""" if change == QGraphicsItem.ItemPositionChange: if Design.performance_mode == 'pretty': self.flow.viewport().update() if self.movement_state == MovementEnum.mouse_clicked: self.movement_state = MovementEnum.position_changed return QGraphicsItem.itemChange(self, change, value) def hoverEnterEvent(self, event): self.title_label.set_NI_hover_state(hovering=True) QGraphicsItem.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): self.title_label.set_NI_hover_state(hovering=False) QGraphicsItem.hoverLeaveEvent(self, event) def mousePressEvent(self, event): """Used for Moving-Commands in Flow - may be replaced later with a nicer determination of a moving action.""" self.movement_state = MovementEnum.mouse_clicked self.movement_pos_from = self.pos() return QGraphicsItem.mousePressEvent(self, event) def mouseReleaseEvent(self, event): """Used for Moving-Commands in Flow - may be replaced later with a nicer determination of a moving action.""" if self.movement_state == MovementEnum.position_changed: self.flow.selected_components_moved(self.pos() - self.movement_pos_from) self.movement_state = None return QGraphicsItem.mouseReleaseEvent(self, event) # ACTIONS def get_extended_default_actions(self): actions_dict = self.default_actions.copy() for index in range(len(self.inputs)): inp = self.inputs[index] if inp.type_ == 'exec': actions_dict['exec input ' + str(index)] = { 'method': self.action_exec_input, 'data': { 'input index': index } } return actions_dict def action_exec_input(self, data): self.update(data['input index']) def get_actions(self, actions_dict, menu): actions = [] for k in actions_dict: v_dict = actions_dict[k] try: method = v_dict['method'] data = None try: data = v_dict['data'] except KeyError: pass action = NodeInstanceAction(k, menu, data) action.triggered_with_data.connect( method) # see NodeInstanceAction for explanation action.triggered_without_data.connect( method) # see NodeInstanceAction for explanation actions.append(action) except KeyError: action_menu = QMenu(k, menu) sub_actions = self.get_actions(v_dict, action_menu) for a in sub_actions: action_menu.addAction(a) actions.append(action_menu) return actions def action_remove(self): self.flow.remove_node_instance_triggered(self) def get_special_actions_data(self, actions): cleaned_actions = actions.copy() for key in cleaned_actions: v = cleaned_actions[key] if type(v) == M: # callable(v): cleaned_actions[key] = v.method_name elif callable(v): cleaned_actions[key] = v.__name__ elif type(v) == dict: cleaned_actions[key] = self.get_special_actions_data(v) else: cleaned_actions[key] = v return cleaned_actions def set_special_actions_data(self, actions_data): actions = {} for key in actions_data: if type(actions_data[key]) != dict: if key == 'method': try: actions['method'] = M(getattr(self, actions_data[key])) except AttributeError: # outdated method referenced pass elif key == 'data': actions['data'] = actions_data[key] else: actions[key] = self.set_special_actions_data(actions_data[key]) return actions # PORTS def setup_ports(self, inputs_config=None, outputs_config=None): if not inputs_config and not outputs_config: for i in range(len(self.parent_node.inputs)): inp = self.parent_node.inputs[i] self.create_new_input( inp.type_, inp.label, widget_name=self.parent_node.inputs[i].widget_name, widget_pos=self.parent_node.inputs[i].widget_pos) for o in range(len(self.parent_node.outputs)): out = self.parent_node.outputs[o] self.create_new_output(out.type_, out.label) else: # when loading saved NIs, the port instances might not be synchronised to the parent's ports anymore for inp in inputs_config: has_widget = inp['has widget'] self.create_new_input( inp['type'], inp['label'], widget_name=inp['widget name'] if has_widget else None, widget_pos=inp['widget position'] if has_widget else None, config=inp['widget data'] if has_widget else None) for out in outputs_config: self.create_new_output(out['type'], out['label']) def get_input_widget_class(self, widget_name): """Returns a reference to the widget class of a given name for instantiation.""" custom_node_input_widget_classes = self.flow.parent_script.main_window.custom_node_input_widget_classes widget_class = custom_node_input_widget_classes[ self.parent_node][widget_name] return widget_class def add_input_to_scene(self, i): self.flow.scene().addItem(i.gate) self.flow.scene().addItem(i.label) if i.widget: self.flow.scene().addItem(i.proxy) def del_and_remove_input_from_scene(self, i_index): i = self.inputs[i_index] for p in self.inputs[i_index].connected_port_instances: self.flow.connect_gates(i.gate, p.gate) self.flow.scene().removeItem(i.gate) self.flow.scene().removeItem(i.label) if i.widget: self.flow.scene().removeItem(i.proxy) i.widget.remove_event() self.inputs.remove(i) def add_output_to_scene(self, o): self.flow.scene().addItem(o.gate) self.flow.scene().addItem(o.label) def del_and_remove_output_from_scene(self, o_index): o = self.outputs[o_index] for p in self.outputs[o_index].connected_port_instances: self.flow.connect_gates(o.gate, p.gate) self.flow.scene().removeItem(o.gate) self.flow.scene().removeItem(o.label) self.outputs.remove(o) # GENERAL def about_to_remove_from_scene(self): """Called from Flow when the NI gets removed from the scene to stop all running threads and disable personal logs.""" if self.main_widget: self.main_widget.remove_event() self.remove_event() self.disable_personal_logs() def is_active(self): for i in self.inputs: if i.type_ == 'exec': return True for o in self.outputs: if o.type_ == 'exec': return True return False def has_main_widget(self): """Might be used later in CodePreview_Widget to enable not only showing the NI's class but also it's main_widget's class.""" return self.main_widget is not None def get_input_widgets(self): """Might be used later in CodePreview_Widget to enable not only showing the NI's class but its input widgets' classes.""" input_widgets = [] for i in range(len(self.inputs)): inp = self.inputs[i] if inp.widget is not None: input_widgets.append({i: inp.widget}) return input_widgets def get_json_data(self): """Returns all metadata of the NI including position, package etc. in a JSON-able dict format. Used to rebuild the Flow when loading a project.""" # general attributes node_instance_dict = { 'parent node title': self.parent_node.title, 'parent node type': self.parent_node.type_, 'parent node package': self.parent_node.package, 'parent node description': self.parent_node.description, 'position x': self.pos().x(), 'position y': self.pos().y() } if self.main_widget: node_instance_dict['main widget data'] = self.main_widget.get_data( ) node_instance_dict['state data'] = self.get_data() node_instance_dict['special actions'] = self.get_special_actions_data( self.special_actions) # inputs node_instance_inputs_list = [] for i in self.inputs: input_dict = i.get_json_data() node_instance_inputs_list.append(input_dict) node_instance_dict['inputs'] = node_instance_inputs_list # outputs node_instance_outputs_list = [] for o in self.outputs: output_dict = o.get_json_data() node_instance_outputs_list.append(output_dict) node_instance_dict['outputs'] = node_instance_outputs_list return node_instance_dict
def set_shadow(QtWindow, button): shadow = QGraphicsDropShadowEffect(QtWindow) shadow.setBlurRadius(6) shadow.setOffset(3) shadow.setColor(QColor(0, 0, 0, 160)) button.setGraphicsEffect(shadow)
class UIFunctions(MainWindow): ## ==> MAXIMIZE RESTORE FUNCTION def maximize_restore(self): global GLOBAL_STATE status = GLOBAL_STATE # IF NOT MAXIMIZED if status == 0: self.showMaximized() # SET GLOBAL TO 1 GLOBAL_STATE = 1 # IF MAXIMIZED REMOVE MARGINS AND BORDER RADIUS self.ui.drop_shadow_layout.setContentsMargins(0, 0, 0, 0) self.ui.drop_shadow_frame.setStyleSheet( "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(42, 44, 111, 255), stop:0.521368 rgba(28, 29, 73, 255)); border-radius: 0px;") self.ui.maximize_btn.setToolTip("Restore") else: GLOBAL_STATE = 0 self.showNormal() self.resize(self.width() + 1, self.height() + 1) self.ui.drop_shadow_layout.setContentsMargins(10, 10, 10, 10) self.ui.drop_shadow_frame.setStyleSheet( "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(42, 44, 111, 255), stop:0.521368 rgba(28, 29, 73, 255)); border-radius: 10px;") self.ui.maximize_btn.setToolTip("Maximize") ## ==> UI DEFINITIONS def uiDefinitions(self): # REMOVE TITLE BAR self.setWindowFlag(QtCore.Qt.FramelessWindowHint) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # SET DROPSHADOW WINDOW self.shadow = QGraphicsDropShadowEffect(self) self.shadow.setBlurRadius(20) self.shadow.setXOffset(0) self.shadow.setYOffset(0) self.shadow.setColor(QColor(0, 0, 0, 100)) # APPLY DROPSHADOW TO FRAME self.ui.drop_shadow_frame.setGraphicsEffect(self.shadow) # MAXIMIZE / RESTORE self.ui.maximize_btn.clicked.connect(lambda: UIFunctions.maximize_restore(self)) # MINIMIZE self.ui.minimize_btn.clicked.connect(lambda: self.showMinimized()) # CLOSE self.ui.close_btn.clicked.connect(lambda: self.close()) ## ==> CREATE SIZE GRIP TO RESIZE WINDOW self.sizegrip = QSizeGrip(self.ui.frame_grip) self.sizegrip.setStyleSheet( "QSizeGrip { width: 10px; height: 10px; margin: 5px } QSizeGrip:hover { background-color: rgb(50, 42, 94) }") self.sizegrip.setToolTip("Resize Window") ## RETURN STATUS IF WINDOWS IS MAXIMIZE OR RESTAURED def returnStatus(self): return GLOBAL_STATE
class Central(QFrame): '''Initializes, styles, and connects the various classes''' def __init__(self): super().__init__() # Objects self.overallLayout = QVBoxLayout(self) self.contentLayout = QHBoxLayout() self.dropShadow = QGraphicsDropShadowEffect(self) self.boxManager = BoxManager.BoxManager() self.topBar = TopBar.TopBar() self.selectorArea = QFrame() self.selectorLayout = QVBoxLayout(self.selectorArea) self.folderArea = QFrame() self.folderLayout = QHBoxLayout(self.folderArea) self.folderList = FolderList.FolderList() self.folderBar = ScrollBar.ScrollBar(self.folderList) self.canvas = Canvas.Canvas(self.boxManager) self.imageArea = QFrame() self.imageList = ImageList.ImageList() self.imageLayout = QHBoxLayout(self.imageArea) self.imageBar = ScrollBar.ScrollBar(self.imageList) # Styling self.setStyleSheet('Central { background: transparent; }') self.overallLayout.setMargin(20) self.overallLayout.setSpacing(0) self.dropShadow.setOffset(QPointF(0,4)) self.dropShadow.setColor(QColor(0,0,0,100)) self.dropShadow.setBlurRadius(10) self.setGraphicsEffect(self.dropShadow) self.contentLayout.setAlignment(Qt.AlignCenter) self.contentLayout.setMargin(0) self.contentLayout.setSpacing(0) self.selectorLayout.setMargin(0) self.selectorLayout.setSpacing(0) self.folderLayout.setMargin(0) self.folderLayout.setSpacing(0) self.imageLayout.setMargin(0) self.imageLayout.setSpacing(0) self.folderList.setVerticalScrollBar(self.folderBar) self.imageList.setVerticalScrollBar(self.imageBar) self.selectorArea.setMaximumWidth(400) self.selectorArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # Layout self.folderLayout.addWidget(self.folderList) self.folderLayout.addSpacerItem(QSpacerItem(-7, 0)) self.folderLayout.addWidget(self.folderBar) self.imageLayout.addWidget(self.imageList) self.imageLayout.addSpacerItem(QSpacerItem(-7, 0)) self.imageLayout.addWidget(self.imageBar) self.selectorLayout.addWidget(self.folderArea, 15) self.selectorLayout.addWidget(self.imageArea, 85) self.contentLayout.addWidget(self.selectorArea, 30) self.contentLayout.addWidget(self.canvas, 70) self.overallLayout.addLayout(self.contentLayout) self.overallLayout.insertWidget(0, self.topBar) # Connections self.folderList.selectedFolderChanged.connect(self.handleSelectedFolderChanged) self.imageList.selectedImageChanged.connect(self.handleSelectedImageChanged) def handleSelectedFolderChanged(self, folder): self.imageList.populate(folder) self.canvas.changeImage(None) self.canvas.setMessage('Switching Folders - {}'.format(folder.data(role=Qt.DisplayRole))) self.topBar.setSelectedFolder(str(folder.data(role=Qt.UserRole+1))) self.topBar.setSelectedImage('') def handleSelectedImageChanged(self, image): self.canvas.changeImage(image) self.canvas.setMessage('Switching Images - {}'.format(image.data(role=Qt.DisplayRole))) self.topBar.setSelectedImage(str(image.data(role=Qt.DisplayRole)))
class StyleWindow(QWidget): def __init__(self, parent=None): """ 基于QWidget的窗口,作为一些窗口的基类,提供一些基础的属性 background_color_: 背景色, 目前只支持单色 border_width_: 边框宽度, 为0表示没有边框 border_color_: opacity_: 窗口透明度 shadow_on_: 是否开启阴影 shadow_color_: shadow_blur_: 阴影半径 border_radius_: 四个角的弧度 分别设置4个角的radius left_top_border_radius: left_bottom_border_radius_: right_top_border_radius_: right_bottom_border_radius_ """ QWidget.__init__(self, parent) self._border_width = 0 self._border_indicator_width = 5 self._mouse_left_button_pressed = False self._rt_pre_geometry = self.geometry() self._mouse_move_pre_pos = QPoint() self._background_color = Qt.white self._border_color = Qt.transparent self._border_width = 0 self._border_radius = 0 self._opacity = 1.0 self._shadow = None self._main_v_layout_ = None self._cursor_calc_type = MousePosition.CenterPos # border self._left_top_border_radius = 0 self._left_bottom_border_radius = 0 self._right_top_border_radius = 0 self._right_bottom_border_radius = 0 self._enabled_drag = False self.setMouseTracking(True) # self.setAttribute(Qt.WA_TranslucentBackground) self._init_default_layout() def paint_style_widget(self, painter): style_opt = QStyleOption() style_opt.init(self) self.style().drawPrimitive(QStyle.PE_Widget, style_opt, painter) def paintEvent(self, event): """ paintEvent(self, event:PySide2.QtGui.QPaintEvent) """ painter = QPainter(self) self.paint_border_background(painter) def border_width(self): return self._border_width def set_border_width(self, width): self._border_width = parse_pix_width(width) if self._main_v_layout_ is not None: self._main_v_layout_.setContentsMargins(self._border_width, self._border_width, self._border_width, self._border_width) border_width_ = Property(str, fget=border_width, fset=set_border_width) def border_indicator_width(self): return self._border_indicator_width def set_border_indicator_width(self, width): self._border_indicator_width = parse_pix_width(width) border_indicator_width_ = Property(str, fget=border_indicator_width, fset=set_border_indicator_width) def mouseMoveEvent(self, event): """ mouseMoveEvent(self, event:PySide2.QtGui.QMouseEvent) """ if self._enabled_drag: if self.isMaximized() is not True and self.isFullScreen( ) is not True: self._set_cursor_shape( self._calc_cursor_pos(event.pos(), self._calc_cursor_col(event.pos()))) if self._mouse_left_button_pressed and MousePosition.CenterPos != self._cursor_calc_type: self._drag_resize() super(StyleWindow, self).mouseMoveEvent(event) def _set_cursor_shape(self, pos): """setCurSorShape(self, pos:StyleWindow.MousePosition)""" cursor_shape = Qt.ArrowCursor if MousePosition.TopLeftPos == pos or MousePosition.BottomRightPos == pos: cursor_shape = Qt.SizeFDiagCursor elif MousePosition.LeftPos == pos or MousePosition.RightPos == pos: cursor_shape = Qt.SizeHorCursor elif MousePosition.BottomLeftPos == pos or MousePosition.TopRightPos == pos: cursor_shape = Qt.SizeBDiagCursor elif MousePosition.BottomPos == pos or MousePosition.TopPos == pos: cursor_shape = Qt.SizeVerCursor self.setCursor(cursor_shape) def _calc_cursor_col(self, pt): """calCursorCol(self, pt:PySide2.QtCore.QPoint)""" res = 3 x = pt.x() if x < self.border_indicator_width(): res = 1 elif x < self.width() - self.border_indicator_width(): res = 2 return res def _calc_cursor_pos(self, pt, bit): """calCursorCol(self, pt:PySide2.QtCore.QPoint, bit)""" result = bit y = pt.y() if y < self.border_indicator_width(): result += 10 elif y > self.height() - self.border_indicator_width(): result += 30 else: result += 20 return result def mousePressEvent(self, event): """ mousePressEvent(self, event:PySide2.QtGui.QMouseEvent) """ if self._enabled_drag: self.set_calc_mouse_type( self._calc_cursor_pos(event.pos(), self._calc_cursor_col(event.pos()))) if Qt.LeftButton == event.button( ) and MousePosition.CenterPos != self._cursor_calc_type: self._mouse_left_button_pressed = True self._rt_pre_geometry = self.geometry() self._mouse_move_pre_pos = event.globalPos() super(StyleWindow, self).mousePressEvent(event) def mouseReleaseEvent(self, event): """ mousePressEvent(self, event:PySide2.QtGui.QMouseEvent) """ self._mouse_left_button_pressed = False QApplication.restoreOverrideCursor() super(StyleWindow, self).mouseReleaseEvent(event) def _drag_resize(self): mouse_cur_pos = QCursor.pos() move_pos = mouse_cur_pos - self._mouse_move_pre_pos after_resize_geometry = self._rt_pre_geometry if MousePosition.TopLeftPos == self._cursor_calc_type: after_resize_geometry.setTopLeft(self._rt_pre_geometry.topLeft() + move_pos) elif MousePosition.LeftPos == self._cursor_calc_type: after_resize_geometry.setLeft(self._rt_pre_geometry.left() + move_pos.x()) elif MousePosition.BottomLeftPos == self._cursor_calc_type: after_resize_geometry.setBottomLeft( self._rt_pre_geometry.bottomLeft() + move_pos) elif MousePosition.BottomPos == self._cursor_calc_type: after_resize_geometry.setBottom(self._rt_pre_geometry.bottom() + move_pos.y()) elif MousePosition.BottomRightPos == self._cursor_calc_type: after_resize_geometry.setBottomRight( self._rt_pre_geometry.bottomRight() + move_pos) elif MousePosition.RightPos == self._cursor_calc_type: after_resize_geometry.setRight(self._rt_pre_geometry.right() + move_pos.x()) elif MousePosition.TopRightPos == self._cursor_calc_type: after_resize_geometry.setTopRight( self._rt_pre_geometry.topRight() + move_pos) elif MousePosition.TopPos == self._cursor_calc_type: after_resize_geometry.setTop(self._rt_pre_geometry.top() + move_pos.y()) self.setGeometry(after_resize_geometry) self._mouse_move_pre_pos = mouse_cur_pos self._rt_pre_geometry = after_resize_geometry def background_color(self): return self._background_color def set_background_color(self, clr): self._background_color = clr background_color_ = Property(QColor, fget=background_color, fset=set_background_color) def border_color(self): return self._border_color def set_border_color(self, clr): self._border_color = clr border_color_ = Property(QColor, fget=border_color, fset=set_border_color) def border_radius(self): return self._border_radius def set_border_radius(self, radius): self._border_radius = parse_pix_width(radius) border_radius_ = Property(str, fget=border_radius, fset=set_border_radius) def paint_border_background(self, painter): """paint_border_background(self, painter:PySide2.QtGui.QPainter)""" painter.save() pen = QPen() pen.setColor(self.border_color()) pen.setWidth(self.border_width()) painter.setPen(pen) brush = QBrush(self.background_color()) painter.setBrush(brush) painter.setOpacity(self.opacity()) # painter.setRenderHint(QPainter.Antialiasing) rc = self.rect() paint_path = QPainterPath() # adjust shadow if self._shadow is not None: rc.adjust(self.shadow_blur(), self.shadow_blur(), -self.shadow_blur(), -self.shadow_blur()) # self._calc_background_path(rc, paint_path) if self._calc_background_path(rc, paint_path): painter.setRenderHint(QPainter.Antialiasing) if self.border_width() > 0: painter.drawPath(paint_path) else: painter.fillPath(paint_path, brush) painter.restore() def set_opacity(self, opacity): self._opacity = opacity def opacity(self) -> float: return self._opacity opacity_ = Property(float, fget=opacity, fset=set_opacity) def resizeEvent(self, event): super(StyleWindow, self).resizeEvent(event) # self.setGeometry(5, 5, self.width() - 5, self.height() - 5) def set_shadow(self, on): if on: self._shadow = QGraphicsDropShadowEffect(self) self._shadow.setOffset(0.0, 0.0) self._shadow.color() self.setGraphicsEffect(self._shadow) self.setGeometry(5, 5, self.width() - 5, self.height() - 5) def shadow(self): return self._shadow shadow_on_ = Property(bool, fget=shadow, fset=set_shadow) def shadow_color(self): if self._shadow is not None: return self._shadow.color() def set_shadow_color(self, color): if self._shadow is not None: self._shadow.setColor(color) shadow_color_ = Property(QColor, fget=shadow_color, fset=set_shadow_color) def shadow_blur(self): if self._shadow is not None: return self._shadow.blurRadius() return 0 def set_shadow_blur(self, pix_blur): if self._shadow is not None: blur = parse_pix_width(pix_blur) self._shadow.setBlurRadius(blur) self.set_border_indicator_width(blur + self.border_indicator_width()) if self._main_v_layout_ is not None: self._main_v_layout_.setContentsMargins( blur + self.border_width(), blur + self.border_width(), blur - 1 + self.border_width(), blur + self.border_width()) shadow_blur_ = Property(str, fget=shadow_blur, fset=set_shadow_blur) def _init_default_layout(self): """setting a default QVBoxLayout""" self._main_v_layout_ = QVBoxLayout(self) self._main_v_layout_.setContentsMargins(0, 0, 0, 0) self.setLayout(self._main_v_layout_) def set_left_top_border_radius(self, radius): self._left_top_border_radius = parse_pix_width(radius) def left_top_border_radius(self): if self._left_top_border_radius: return self._left_top_border_radius return self.border_radius() left_top_border_radius_ = Property(str, fget=left_top_border_radius, fset=set_left_top_border_radius) def set_left_bottom_border_radius(self, radius): self._left_bottom_border_radius = parse_pix_width(radius) def left_bottom_border_radius(self): if self._left_bottom_border_radius: return self._left_bottom_border_radius return self.border_radius() left_bottom_border_radius_ = Property(str, fget=left_bottom_border_radius, fset=set_left_bottom_border_radius) def set_right_top_border_radius(self, radius): self._right_top_border_radius = parse_pix_width(radius) def right_top_border_radius(self): if self._right_top_border_radius: return self._right_top_border_radius return self.border_radius() right_top_border_radius_ = Property(str, fget=right_top_border_radius, fset=set_right_top_border_radius) def set_right_bottom_border_radius(self, radius): self._right_bottom_border_radius = parse_pix_width(radius) def right_bottom_border_radius(self): if self._right_bottom_border_radius: return self._right_bottom_border_radius return self.border_radius() right_bottom_border_radius_ = Property(str, fget=right_bottom_border_radius, fset=set_right_bottom_border_radius) def _calc_background_path(self, rc, painter_path): render_hint = False if self.border_radius(): painter_path.addRoundedRect(rc, self.border_radius(), self.border_radius()) render_hint = True else: if self.left_top_border_radius() or self.left_bottom_border_radius() \ or self.right_top_border_radius() or self.right_bottom_border_radius(): # add radius render_hint = True radius_angle = 90.0 rotate_angle = 90.0 painter_path.moveTo(rc.left(), rc.top()) painter_path.arcTo(rc.left(), rc.top(), self.left_top_border_radius(), self.left_top_border_radius(), rotate_angle, radius_angle) painter_path.lineTo( rc.left(), rc.bottom() - self.left_bottom_border_radius() / 2) painter_path.arcTo(rc.left(), rc.bottom() - self.left_bottom_border_radius(), self.left_bottom_border_radius(), self.left_bottom_border_radius(), rotate_angle * 2, radius_angle) painter_path.lineTo( rc.right() - self.right_bottom_border_radius() / 2, rc.bottom()) painter_path.arcTo(rc.right() - self.right_bottom_border_radius(), rc.bottom() - self.right_bottom_border_radius(), self.right_bottom_border_radius(), self.right_bottom_border_radius(), rotate_angle * 3, radius_angle) painter_path.lineTo(rc.right(), rc.top() - self.right_top_border_radius() / 2) painter_path.arcTo(rc.right() - self.right_top_border_radius(), rc.top(), self.right_top_border_radius(), self.right_top_border_radius(), rotate_angle * 4 % rotate_angle, radius_angle) painter_path.lineTo(rc.left() + self.left_top_border_radius() / 2, rc.top()) return render_hint def set_layout(self, layout): if self._main_v_layout_ is not None: self._main_v_layout_.addLayout(layout) @property def mouse_drag_enabled(self): return self._enabled_drag @mouse_drag_enabled.setter def mouse_drag_enabled(self, drag): self._enabled_drag = drag def _mouse_drag_is_center_type(self): if self._cursor_calc_type == MousePosition.CenterPos: return True return False def set_calc_mouse_type(self, mouse_type: MousePosition): self._cursor_calc_type = mouse_type
class Piece(QFrame): def __init__(self, parent, piece_type, row, col, move=False): super(Piece, self).__init__(parent) self.main_window = parent.parent().parent().parent( ) # CenterWidget >> AspectRatioWidget >> MainWindow # self.move(0, 0) # self.resize(50, 50) self.setStyleSheet("background: " + "red; border-radius: 50px") self.border_radius = 10 self.offset = QPoint() self.crs = QCursor() self.table_width = 50 self.piece_type = piece_type self.col = col self.row = row self.movable = move self.possible_jumps = [] self.animator = QPropertyAnimation(self, b"geometry") self.styles = {} self.shadow = QGraphicsDropShadowEffect() self.shadow.setBlurRadius(0) self.shadow.setXOffset(0) self.shadow.setYOffset(0) self.setGraphicsEffect(self.shadow) self.confirm_jump_stop = False def update_size(self, w, h): side_width = self.main_window.aspect_ratio_widget.side_widget.width() if w - side_width > h: self.table_width = h else: self.table_width = w - side_width width = (self.table_width - 20) / 8 # tile_size = grid[self.col][self.row].width() self.move(width * self.col + 10, width * self.row + 10) self.resize(width, width) self.border_radius = int(width / 2) self.paint_piece() # StckOverflow # https://stackoverflow.com/questions/18344135/why-do-stylesheets-not-work-when-subclassing-qwidget-and-using-q-object def paintEvent(self, pe): o = QStyleOption() o.initFrom(self) p = QPainter(self) self.style().drawPrimitive(QStyle.PE_Widget, o, p, self) def resizeEvent(self, event): super(Piece, self).resizeEvent(event) width = event.size().width() if self.styles: self.setStyleSheet("background: " + self.styles["background"] + ";" + "border-radius: " + str(width / 2 - 2) + "px;" + "border-style: outset;" + "border-width: " + self.styles["border-width"] + "px;" + "border-color: " + self.styles["border-color"] + ";") def enterEvent(self, event): if self.movable: self.move(self.pos().x() - 10, self.pos().y() - 10) self.shadow.setBlurRadius(5) self.shadow.setXOffset(10) self.shadow.setYOffset(10) def leaveEvent(self, event): if self.movable: self.update_position() self.shadow.setBlurRadius(0) self.shadow.setXOffset(0) self.shadow.setYOffset(0) def mousePressEvent(self, event): super(Piece, self).mousePressEvent(event) self.main_window.remove_marks(True) if self.movable: self.setCursor(Qt.CursorShape.ClosedHandCursor) self.main_window.create_marks(self.possible_jumps) self.main_window.mark_source = [self.row, self.col] self.offset = event.globalPos() - self.pos() # Cancel all animation and replace matrix if self.main_window.animation_timer_list: for timer in self.main_window.animation_timer_list: if timer.objectName() == "end": self.main_window.replace_matrix() timer.setInterval(10) return timer.stop() self.possible_jumps.clear() self.main_window.replace_matrix(None, self) self.main_window.animation_timer_list = [] def mouseMoveEvent(self, event): super(Piece, self).mouseMoveEvent(event) self.raise_() # self.confirm_jump_stop = True if self.movable: self.move(event.globalPos() - self.offset) def mouseReleaseEvent(self, event): super(Piece, self).mouseReleaseEvent(event) self.shadow.setBlurRadius(0) self.shadow.setXOffset(0) self.shadow.setYOffset(0) # self.offset = event.globalPos()-self.pos() if self.movable: self.setCursor(Qt.CursorShape.OpenHandCursor) self.calc_position(True) def update_position(self): width = (self.table_width - 20) / 8 self.move(width * self.col + 10, width * self.row + 10) def calc_position(self, user=False): widget_size = self.table_width - 20 cell_size = widget_size / 8 x_center = self.pos().x() - 10 + cell_size / 2 y_center = self.pos().y() - 10 + cell_size / 2 new_col = int(x_center / cell_size) new_row = int(y_center / cell_size) if user and new_row == self.row and new_col == self.col: if [new_row, new_col] in self.possible_jumps: if not self.confirm_jump_stop: self.confirm_jump_stop = True return else: self.update_position() return self.confirm_jump_stop = False if y_center >= 10 and new_row <= 7 and x_center >= 10 and new_col <= 7: if self.is_position_valid(new_row, new_col): self.main_window.remove_marks(True) self.movable = False self.set_cursor_pointing(False) self.main_window.pieces_matrix[self.row][self.col] = 0 self.main_window.pieces_matrix[new_row][new_col] = self old = [self.row, self.col] self.row = new_row self.col = new_col self.main_window.lock_pieces() if self.row == 0: if self.piece_type == 1: self.piece_type = 4 self.paint_piece() QTimer.singleShot( 100, lambda: self.main_window.game.playerMove.stop_waiting( [old, [self.row, self.col]])) if abs(new_row - old[0]) == 2: ate_x = int((new_row + old[0]) / 2) ate_y = int((new_col + old[1]) / 2) self.main_window.pieces_matrix[ate_x][ ate_y].shrink_animation() self.main_window.pl_last_eat = [new_row, new_col] else: self.main_window.pl_last_eat = [] self.update_position() def is_position_valid(self, row, col): for jmp in self.possible_jumps: if row == jmp[0] and col == jmp[1]: return True return False def paint_piece(self): color = get_piece_color(self.piece_type) border_width = (self.table_width - 20) / 8 / 8 if self.piece_type == 4 or self.piece_type == 5: border_radius = self.border_radius - 5 border_color = "#a32" else: border_radius = self.border_radius if self.piece_type == 1: border_color = "#444" else: border_color = "#999" border_width /= 2 self.styles["background"] = str(color) self.styles["border-radius"] = str(border_radius) self.styles["border-style"] = "outset" self.styles["border-width"] = str(border_width) self.styles["border-color"] = border_color self.setStyleSheet("background: " + str(color) + ";" + "border-radius: " + str(border_radius) + "px;" + "border-style: outset;" + "border-width: " + str(border_width) + "px;" + "border-color: " + border_color + ";") self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) def animate_move(self, i, j): self.raise_() self.animator.stop() y = (self.table_width - 20) / 8 * i + 10 x = (self.table_width - 20) / 8 * j + 10 old_rect = QRect(self.pos().x(), self.pos().y(), self.width(), self.height()) rect = QRect(x, y, self.height(), self.width()) self.animator.setDuration(500) self.animator.setStartValue(old_rect) self.animator.setEndValue(rect) self.animator.start() self.main_window.pieces_matrix[i][j] = self.main_window.pieces_matrix[ self.row][self.col] self.main_window.pieces_matrix[self.row][self.col] = 0 self.row = i self.col = j if self.row == 7: if self.piece_type == 1: self.piece_type = 4 elif self.piece_type == 2: self.piece_type = 5 QTimer.singleShot(500, self.paint_piece) def animate_pl_move(self, i, j): if not self.is_position_valid(i, j): return self.raise_() self.animator.stop() self.animator = QPropertyAnimation(self, b"geometry") y = (self.table_width - 20) / 8 * i + 10 x = (self.table_width - 20) / 8 * j + 10 old_rect = QRect(self.pos().x(), self.pos().y(), self.width(), self.height()) rect = QRect(x, y, self.height(), self.width()) self.animator.setDuration(500) self.animator.setStartValue(old_rect) self.animator.setEndValue(rect) self.animator.finished.connect(self.calc_position) self.movable = False self.set_cursor_pointing(False) self.animator.start() def shrink_animation(self): tile_width = (self.table_width - 20) / 8 self.animator.stop() old_rect = QRect(self.pos().x(), self.pos().y(), self.width(), self.height()) rect = QRect(self.pos().x() + tile_width / 2, self.pos().y() + tile_width / 2, 0, 0) self.animator.setDuration(250) self.animator.setStartValue(old_rect) self.animator.setEndValue(rect) self.styleSheet() self.animator.start() def set_cursor_pointing(self, clickable): if clickable: self.setCursor(Qt.CursorShape.PointingHandCursor) self.setCursor(Qt.CursorShape.OpenHandCursor) else: self.setCursor(Qt.CursorShape.ArrowCursor)
class PreferencesGUI(App): ''' Main class for the Preferences Window ''' keys = list() finished = Signal() def __init__(self, appctxt, windowInst): super().__init__() self.appctxt = appctxt self.initUI(windowInst) # self.windowInst.setWindowFlags(QtCore.Qt.WindowTitleHint | QtCore.Qt.FramelessWindowHint) # self.windowInst.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.settings = QSettings(self.COMPANY_NAME, self.APPLICATION_NAME) self.receiversInst = Receivers(self.ui) self.receiversInst.confirmSignal.connect(self.handleExit) self.connectReceivers() self.guiHelper = GuiHelper() self.loadKeys() self.loadSettings() def terminate(self): self.storeSettings() del self.settings def initUI(self, windowInst): # self.app = QtWidgets.QApplication(sys.argv) self.ui = Ui_PreferencesDialog() self.ui.windowInst = windowInst self.ui.windowInst.hide() self.ui.setupUi(self.ui.windowInst) windowInst.setAttribute(Qt.WA_TranslucentBackground) self.backgroundEffect = QGraphicsDropShadowEffect(windowInst) self.backgroundEffect.setBlurRadius(30) self.backgroundEffect.setOffset(0, 0) self.backgroundEffect.setEnabled(True) self.ui.centralwidget.setGraphicsEffect(self.backgroundEffect) self.ui.comboBoxAutosaveMode.addItem("Don't Autosave") self.ui.comboBoxAutosaveMode.addItem("5 min Autosave") self.ui.comboBoxAutosaveMode.addItem("10 min Autosave") self.ui.comboBoxAutosaveMode.addItem("15 min Autosave") self.ui.comboBoxAutosaveMode.addItem("20 min Autosave") self.ui.comboBoxThemeSelect.addItem("Dark Theme") self.ui.comboBoxThemeSelect.addItem("Light Theme") # self.ui.comboBoxThemeSelect.addItem("Ambient Theme") def run(self): ''' Starts the Preferences Window ''' self.loadSettings() self.ui.windowInst.show() @Slot(bool) def handleExit(self, confirmed): if confirmed: self.storeLooseEntries() self.storeSettings() print('Settings saved') else: self.loadSettings() print('Settings discarded') self.finished.emit() def connectReceivers(self): ''' Connects all the buttons to the right receivers ''' self.ui.radioButtonAffectsPDF.clicked.connect( self.receiversInst.setRadioButtonAffectsPDF) self.ui.comboBoxThemeSelect.currentIndexChanged.connect( self.receiversInst.setComboBoxThemeSelect) self.ui.radioButtonSmoothLines.clicked.connect( self.receiversInst.setradioButtonSmoothLines) self.ui.radioButtonSaveOnExit.clicked.connect( self.receiversInst.setRadioButtonSaveOnExit) self.ui.comboBoxAutosaveMode.currentIndexChanged.connect( self.receiversInst.setComboBoxAutosaveMode) self.ui.pushButtonOk.clicked.connect( lambda: self.receiversInst.confirmReceiver()) self.ui.pushButtonCancel.clicked.connect( lambda: self.receiversInst.rejectReceiver()) self.receiversInst.confirmSignal.connect(self.onClose) def loadKeys(self): ''' Load the preferences keys ''' scriptPath = os.path.dirname(os.path.abspath(__file__)) + '\\' # absKeysFilePath = os.path.normpath(scriptPath + KEYSFILEPATH) absKeysFilePath = self.appctxt.get_resource('preferences.keys') keysFileContent = readFile(absKeysFilePath) for key in keysFileContent['lines']: self.keys.append(key.replace('\n', '')) def storeSettings(self): ''' Store the settings from the gui to the local dict and then to the settings instance ''' for key in self.keys: self.settings.setValue(key, str(Preferences.data[key])) self.settings.sync() def storeLooseEntries(self): ''' Saves all entries, which have been entered without explicit confirmation ''' Preferences.updateKeyValue( "radioButtonAffectsPDF", str(self.ui.radioButtonAffectsPDF.isChecked())) Preferences.updateKeyValue( "comboBoxThemeSelect", str(self.ui.comboBoxThemeSelect.currentIndex())) Preferences.updateKeyValue( "radioButtonSmoothLines", str(self.ui.radioButtonSmoothLines.isChecked())) Preferences.updateKeyValue( "radioButtonUsePenAsDefault", str(self.ui.radioButtonSmoothLines.isChecked())) Preferences.updateKeyValue( "radioButtonSaveOnExit", int(self.ui.radioButtonSaveOnExit.isChecked())) Preferences.updateKeyValue( "comboBoxAutosaveMode", int(self.ui.comboBoxAutosaveMode.currentIndex())) Preferences.updateKeyValue( "radioButtonNoInteractionWhileEditing", str(self.ui.radioButtonNoInteractionWhileEditing.isChecked())) @Slot(bool) def onClose(self, store): if store: self.saveSettings() else: self.discardSettings() def saveSettings(self): self.storeLooseEntries() self.storeSettings() def discardSettings(self): self.loadSettings() def loadSettings(self): ''' Load the settings from the settings instance to the local dict ''' for key in self.keys: Preferences.updateKeyValue( key, self.settings.value(key, defaultValue=None, type=str)) self.ensureValidData() self.ui.radioButtonAffectsPDF.setChecked( toBool(Preferences.data["radioButtonAffectsPDF"])) self.ui.comboBoxThemeSelect.setCurrentIndex( int(Preferences.data["comboBoxThemeSelect"])) self.receiversInst.setComboBoxThemeSelect( int(Preferences.data["comboBoxThemeSelect"])) self.ui.radioButtonUsePenAsDefault.setChecked( toBool(Preferences.data["radioButtonUsePenAsDefault"])) self.ui.radioButtonSmoothLines.setChecked( toBool(Preferences.data["radioButtonSmoothLines"])) self.ui.radioButtonSaveOnExit.setChecked( toBool(Preferences.data["radioButtonSaveOnExit"])) self.ui.comboBoxAutosaveMode.setCurrentIndex( int(Preferences.data["comboBoxAutosaveMode"])) self.ui.radioButtonNoInteractionWhileEditing.setChecked( toBool(Preferences.data["radioButtonNoInteractionWhileEditing"])) def ensureValidData(self): # Apply all default preferences if necessary if Preferences.data['radioButtonAffectsPDF'] == "": Preferences.updateKeyValue('radioButtonAffectsPDF', str(True)) if Preferences.data['comboBoxThemeSelect'] == "": Preferences.updateKeyValue('comboBoxThemeSelect', 1) if Preferences.data['radioButtonSmoothLines'] == "": Preferences.updateKeyValue('radioButtonSmoothLines', str(True)) if Preferences.data['radioButtonUsePenAsDefault'] == "": Preferences.updateKeyValue('radioButtonUsePenAsDefault', str(True)) if Preferences.data['comboBoxDrawingMode'] == "": Preferences.updateKeyValue('comboBoxDrawingMode', 0) if Preferences.data['radioButtonSaveOnExit'] == "": Preferences.updateKeyValue('radioButtonSaveOnExit', str(True)) if Preferences.data['comboBoxAutosaveMode'] == "": Preferences.updateKeyValue('comboBoxAutosaveMode', 0) if Preferences.data['radioButtonNoInteractionWhileEditing'] == "": Preferences.updateKeyValue('radioButtonNoInteractionWhileEditing', str(True)) if Preferences.data['textSize'] == "": Preferences.updateKeyValue('textSize', "('0', '0', '0')") if Preferences.data['markerSize'] == "": Preferences.updateKeyValue('markerSize', "70") if Preferences.data['markerColor'] == "": Preferences.updateKeyValue('markerColor', "('0', '0', '0')") if Preferences.data['freehandSize'] == "": Preferences.updateKeyValue('freehandSize', "70") if Preferences.data['freehandColor'] == "": Preferences.updateKeyValue('freehandColor', "('0', '0', '0')") if Preferences.data['formSize'] == "": Preferences.updateKeyValue('formSize', "70") if Preferences.data['formColor'] == "": Preferences.updateKeyValue('formColor', "('0', '0', '0')")