def draw_filled_hexagon(self, position, fill_color, number=None): qpainter = self.painter pen = data.QPen(data.Qt.SolidLine) pen.setColor(fill_color) brush = data.QBrush(data.Qt.SolidPattern) brush.setColor(fill_color) qpainter.setBrush(brush) qpainter.setPen(pen) hex_points = list( HexBuilder.generate_hexagon_points(self.edge_length, position)) x_correction = self.edge_length / 2 y_correction = self.edge_length / (2 * math.tan(math.radians(30))) hex_points = [(x - x_correction, y - y_correction) for x, y in hex_points] hex_qpoints = [data.QPoint(*x) for x in hex_points] qpainter.drawPolygon(*hex_qpoints) if (self.SHOW_FIELD_NUMBERS == True) and (number != None): font = data.QFont('Courier', 8) font.setBold(True) qpainter.setFont(font) pen = data.QPen(data.Qt.SolidLine) pen.setColor(data.QColor(0, 0, 0)) qpainter.setPen(pen) font_metric = data.QFontMetrics(font) x = position[0] - font_metric.width(str(number)) / 2 y = position[1] + font_metric.height() / 4 qpainter.drawText(data.QPoint(x, y), str(number))
def show_sessions(self): """Show the current session in a tree structure""" #Initialize the display self.tree_model = data.QStandardItemModel() self.tree_model.setHorizontalHeaderLabels(["SESSIONS"]) self.header().hide() # self.clean_model() self.setModel(self.tree_model) self.setUniformRowHeights(True) #Connect the tree model signals self.tree_model.itemChanged.connect(self._item_changed) # font = data.QFont(data.current_font_name, data.current_font_size, data.QFont.Bold) font = data.QFont(data.current_font_name, data.current_font_size) #First add all of the groups, if any groups = self.settings_manipulator.get_sorted_groups() self.groups = [] # Create the Sessions menu main_group = self.settings_manipulator.Group("BASE", parent=None, reference=self.tree_model) for group in groups: current_node = main_group level_counter = 1 for folder in group: new_group = current_node.subgroup_get(folder) if new_group == None: item_group_node = self.SessionItem(folder) item_group_node.setFont(font) item_group_node.my_parent = self item_group_node.name = group[:level_counter] item_group_node.session = None item_group_node.type = self.ItemType.GROUP item_group_node.setEditable(False) item_group_node.setIcon(self.node_icon_group) current_node.reference.appendRow(item_group_node) current_node = current_node.subgroup_create( folder, item_group_node) self.groups.append(item_group_node) level_counter += 1 #Initialize the list of session nodes self.session_nodes = [] #Loop through the manipulators sessions and add them to the display for session in self.settings_manipulator.stored_sessions: item_session_node = self.SessionItem(session.name) item_session_node.my_parent = self item_session_node.name = session.name item_session_node.session = session item_session_node.type = self.ItemType.SESSION item_session_node.setEditable(False) item_session_node.setIcon(self.node_icon_session) #Check if the item is in a group if session.group != None: group = main_group.subgroup_get_recursive(session.group) group.reference.appendRow(item_session_node) else: main_group.reference.appendRow(item_session_node) #Append the session to the stored node list self.session_nodes.append(item_session_node)
def extra_button_leave_event(self, event): """Overloaded widget enter event""" #Check if the button is enabled if self.isEnabled() == True: self._set_extra_button_opacity(self.OPACITY_LOW) #Clear the function text extra_button_font = data.QFont('Courier', self.stored_font.pointSize() - 2, weight=data.QFont.Bold) self._parent.display("", extra_button_font)
def set_font(lexer, style_name, style_options): font, color, size, bold = style_options lexer.setColor(data.QColor(color), lexer.styles[style_name]) weight = data.QFont.Normal if bold == 1 or bold == True: weight = data.QFont.Bold elif bold == 2: weight = data.QFont.Black lexer.setFont(data.QFont(font, size, weight=weight), lexer.styles[style_name])
def customize_tab_bar(self): if data.custom_menu_scale != None and data.custom_menu_font != None: components.TheSquid.customize_menu_style(self.tabBar()) self.tabBar().setFont(data.QFont(*data.custom_menu_font)) new_icon_size = data.QSize(data.custom_menu_scale, data.custom_menu_scale) self.setIconSize(new_icon_size) else: components.TheSquid.customize_menu_style(self.tabBar()) self.tabBar().setFont(self.default_tab_font) self.setIconSize(self.default_icon_size)
def extra_button_enter_event(self, event): """Overloaded widget enter event""" #Check if the button is enabled if self.isEnabled() == True: self._set_extra_button_opacity(self.OPACITY_HIGH) #Display the stored extra buttons function text extra_button_font = data.QFont(data.current_font_name, self.stored_font.pointSize() - 2, weight=data.QFont.Bold) self._parent.display(self.extra_button_function_text, extra_button_font)
def customize_tab_bar(self): if data.custom_menu_scale != None and data.custom_menu_font != None: components.TheSquid.customize_menu_style(self.tabBar()) self.tabBar().setFont(data.QFont(*data.custom_menu_font)) new_icon_size = functions.create_size(data.custom_menu_scale, data.custom_menu_scale) self.setIconSize(new_icon_size) else: components.TheSquid.customize_menu_style(self.tabBar()) self.tabBar().setFont(data.get_current_font()) self.setIconSize(self.default_icon_size) self.tabBar().set_style()
def _init_info_label(self): self.display_label = data.QLabel(self) self.display_label.setGeometry(20, 50, 200, 100) font = data.QFont(data.current_font_name, 14) font.setBold(True) self.display_label.setFont(font) self.display_label.setStyleSheet('color: rgb({}, {}, {})'.format( data.theme.Font.Default.red(), data.theme.Font.Default.green(), data.theme.Font.Default.blue(), )) self.display_label.setAlignment(data.Qt.AlignHCenter | data.Qt.AlignVCenter)
def update_styles(): if TheSquid.main_form == None: # Do not update if the main form is not initialized return TheSquid.update_objects() TheSquid.customize_menu_style(TheSquid.main_form.menubar) if data.custom_menu_font != None: for action in TheSquid.main_form.menubar.stored_actions: action.setFont(data.QFont(*data.custom_menu_font)) TheSquid.customize_menu_style(TheSquid.main_form.sessions_menu) TheSquid.customize_menu_style(TheSquid.main_form.recent_files_menu) TheSquid.customize_menu_style(TheSquid.main_form.save_in_encoding) TheSquid.customize_menu_style(TheSquid.main_form.bookmark_menu) def set_style(menu): if hasattr(menu, "actions"): TheSquid.customize_menu_style(menu) for item in menu.actions(): if item.menu() != None: TheSquid.customize_menu_style(item.menu()) set_style(item) set_style(TheSquid.main_form.sessions_menu) windows = [ TheSquid.main_window, TheSquid.upper_window, TheSquid.lower_window ] for window in windows: window.customize_tab_bar() for i in range(window.count()): if hasattr(window.widget(i), "corner_widget"): TheSquid.customize_menu_style( window.widget(i).corner_widget ) if data.custom_menu_scale != None: window.widget(i).corner_widget.setIconSize( data.QSize( data.custom_menu_scale, data.custom_menu_scale ) ) else: window.widget(i).corner_widget.setIconSize( data.QSize(16, 16) ) if hasattr(window.widget(i), "icon_manipulator"): window.widget(i).icon_manipulator.restyle_corner_button_icons() if isinstance(window.widget(i), gui.TreeDisplay): window.widget(i).update_icon_size()
def __init__(self, style_name): super().__init__() self._style = data.QStyleFactory.create(style_name) if self._style == None: raise Exception( "Style '{}' is not valid on this system!".format(style_name)) """ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! This needs to happen on CustomStyle initialization, otherwise the font's bounding rectangle in not calculated correctly! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! """ self.scale_constant = data.custom_menu_scale self.custom_font = data.QFont(*data.custom_menu_font) self.custom_font_metrics = data.QFontMetrics(self.custom_font)
def set_font(lexer, style_name, style_options): font, color, size, bold = style_options try: style_index = lexer.styles[style_name]["index"] except: style_index = lexer.styles[style_name] lexer.setColor( data.QColor(color), style_index ) weight = data.QFont.Normal if bold == 1 or bold == True: weight = data.QFont.Bold elif bold == 2: weight = data.QFont.Black lexer.setFont( data.QFont(font, size, weight=weight), style_index )
def __init__(self, parent, main_form, interpreter_references=None): """Initialization""" # Initialize superclass class, from which the current class is inherited, THIS MUST BE DONE SO THAT THE SUPERCLASS EXECUTES ITS __init__ !!!!!! super().__init__() # Initialize the parent references and update the autocompletion lists self._parent = parent self.main_form = main_form # Set default font font = data.QFont(data.current_font_name, data.current_font_size + 2, data.QFont.Bold) self.setFont(font) # Initialize the interpreter self.interpreter = interpreter.CustomInterpreter( interpreter_references, main_form.display.repl_display_message) # Initialize interpreter reference list that will be used for autocompletions self._list_repl_references = [ str_ref for str_ref in interpreter_references ] # Initialize style self.update_style()
def set_application_font(name, size): # Load the fonts font_file_list = get_fonts_from_resources() for file in font_file_list: data.QFontDatabase.addApplicationFont(file) # Check if the font is available fdb = data.QFontDatabase() font_found = False for f in fdb.families(): if name.lower() in f.lower(): font_found = True name = f break if not font_found: raise ValueError( "Font '{}' is not installed on the system!".format(name) ) # Apply the font for the whole application font = data.QFont(name, size) data.current_font_name = name data.current_font_size = size data.application.setFont(font)
def draw(self, opacity): image = data.QImage(self.pixmap.size(), data.QImage.Format_ARGB32_Premultiplied) image.fill(data.Qt.transparent) painter = data.QPainter(image) painter.setOpacity(opacity) painter.drawPixmap(0, 0, self.pixmap) if opacity < 0.5: painter.setPen(data.theme.Font.Default) else: painter.setPen(data.QColor(255, 255, 255)) painter.setFont( data.QFont('Segoe UI', int(16 * self.scale), data.QFont.Bold)) painter.setOpacity(1.0) painter.drawText(self.pixmap.rect(), data.Qt.AlignCenter, self.text) painter.end() # Display the manipulated image self.setPixmap(data.QPixmap.fromImage(image)) # Set the button mask, which sets the button area to the shape of # the button image instead of a rectangle self.setMask(self.pixmap.mask())
def defaultFont(self, style): return data.QFont(data.current_font_name, data.current_font_size)
class AWK(data.QsciLexerCustom): """ Custom lexer for the AWK programming languages """ styles = { "Default": 0, "Comment": 1, "Keyword": 2, "BuiltInVariable": 3, "BuiltInFunction": 4, "String": 5, "Number": 6, "Operator": 7, } # Class variables default_color = data.QColor(data.theme.Font.AWK.Default[1]) default_paper = data.QColor(data.theme.Paper.AWK.Default) default_font = data.QFont(data.current_font_name, data.current_font_size) keyword_list = [ "BEGIN", "delete", "for", "in", "printf", "END", "do", "function", "next", "return", "break", "else", "getline", "print", "while", "continue", "exit", "if", ] builtin_variable_list = [ "ARGC", "ARGV", "CONVFMT", "ENVIRON", "FILENAME", "FNR", "FS", "NF", "NR", "OFMT", "OFS", "ORS", "RLENGTH", "RS", "RSTART", "SUBSEP", ] builtin_function_list = [ "atan2", "index", "match", "sprintf", "substr", "close", "int", "rand", "sqrt", "system", "cos", "length", "sin", "srand", "tolower", "exp", "log", "split", "sub", "toupper", "gsub", ] operator_list = [ "=", "+", "-", "*", "/", "<", ">", "@", "$", ".", "~", "&", "%", "|", "!", "?", "^", ".", ":", "\"", ] splitter = re.compile(r"(\{\.|\.\}|\#|\'|\"\"\"|\n|\s+|\w+|\W)") # Characters that autoindent one level on pressing Return/Enter autoindent_characters = ["{"] def __init__(self, parent=None): """ Overridden initialization """ # Initialize superclass super().__init__() # Set the default style values self.setDefaultColor(self.default_color) self.setDefaultPaper(self.default_paper) self.setDefaultFont(self.default_font) # Reset autoindentation style self.setAutoIndentStyle(0) # Set the theme self.set_theme(data.theme) def language(self): return "AWK" def description(self, style): if style < len(self.styles): description = "Custom lexer for the AWK programming languages" else: description = "" return description def defaultStyle(self): return self.styles["Default"] def braceStyle(self): return self.styles["Default"] def defaultFont(self, style): return data.QFont(data.current_font_name, data.current_font_size) def set_theme(self, theme): for style in self.styles: # Papers self.setPaper(data.QColor(theme.Paper.AWK.Default), self.styles[style]) # Fonts lexers.set_font(self, style, getattr(theme.Font.AWK, style)) def styleText(self, start, end): """ Overloaded method for styling text. """ # Style in pure Python, VERY SLOW! editor = self.editor() if editor is None: return # Initialize the styling self.startStyling(start) # Scintilla works with bytes, so we have to adjust # the start and end boundaries text = bytearray(editor.text(), "utf-8")[start:end].decode("utf-8") # Loop optimizations setStyling = self.setStyling operator_list = self.operator_list builtin_variable_list = self.builtin_variable_list builtin_function_list = self.builtin_function_list keyword_list = self.keyword_list DEFAULT = self.styles["Default"] COMMENT = self.styles["Comment"] KEYWORD = self.styles["Keyword"] BUILTINVARIABLE = self.styles["BuiltInVariable"] BUILTINFUNCTION = self.styles["BuiltInFunction"] STRING = self.styles["String"] NUMBER = self.styles["Number"] OPERATOR = self.styles["Operator"] # Initialize various states and split the text into tokens stringing = False commenting = False tokens = [(token, len(bytearray(token, "utf-8"))) for token in self.splitter.findall(text)] # Style the tokens accordingly for i, token in enumerate(tokens): if commenting == True: # Continuation of comment setStyling(token[1], COMMENT) # Check if comment ends if "\n" in token[0]: commenting = False elif stringing == True: # Continuation of a string setStyling(token[1], STRING) # Check if string ends if (token[0] == "\"" and (tokens[i - 1][0] != "\\") or "\n" in token[0]): stringing = False elif token[0] == "#": setStyling(token[1], COMMENT) commenting = True elif token[0] == "\"": # Start of a string setStyling(token[1], STRING) stringing = True elif token[0] in operator_list: setStyling(token[1], OPERATOR) elif token[0] in keyword_list: setStyling(token[1], KEYWORD) elif token[0] in builtin_variable_list: setStyling(token[1], BUILTINVARIABLE) elif token[0] in builtin_function_list: setStyling(token[1], BUILTINFUNCTION) else: setStyling(token[1], DEFAULT)
class CustomPython(data.QsciLexerCustom): class Sequence: def __init__(self, start, stop_sequences, stop_characters, style, add_to_style): self.start = start self.stop_sequences = stop_sequences self.stop_characters = stop_characters self.style = style self.add_to_style = add_to_style # Class variables # Lexer index counter for Nim styling _index = 0 index = 0 # Styles styles = { "Default": 0, "Comment": 1, "Number": 2, "DoubleQuotedString": 3, "SingleQuotedString": 4, "Keyword": 5, "TripleSingleQuotedString": 6, "TripleDoubleQuotedString": 7, "ClassName": 8, "FunctionMethodName": 9, "Operator": 10, "Identifier": 11, "CommentBlock": 12, "UnclosedString": 13, "HighlightedIdentifier": 14, "Decorator": 15, "CustomKeyword": 16, } default_color = data.QColor(data.theme.Font.Python.Default[1]) default_paper = data.QColor(data.theme.Paper.Python.Default) default_font = data.QFont(data.current_font_name, data.current_font_size) # Styling lists and characters keyword_list = list(set(keyword.kwlist + dir(builtins))) additional_list = [] sq = Sequence('\'', ['\'', '\n'], [], styles["SingleQuotedString"], True) dq = Sequence('"', ['"', '\n'], [], styles["DoubleQuotedString"], True) edq = Sequence('""', [], [], styles["DoubleQuotedString"], True) esq = Sequence('\'\'', [], [], styles["DoubleQuotedString"], True) tqd = Sequence('\'\'\'', ['\'\'\''], [], styles["TripleSingleQuotedString"], True) tqs = Sequence('"""', ['"""'], [], styles["TripleDoubleQuotedString"], True) cls = Sequence('class', [':'], ['(', '\n'], styles["ClassName"], False) defi = Sequence('def', [], ['('], styles["FunctionMethodName"], False) comment = Sequence('#', [], ['\n'], styles["Comment"], True) dcomment = Sequence('##', [], ['\n'], styles["CommentBlock"], True) decorator = Sequence('@', ['\n'], [' '], styles["Decorator"], True) sequence_lists = [ sq, dq, edq, esq, tqd, tqs, cls, defi, comment, dcomment, decorator ] multiline_sequence_list = [tqd, tqs] sequence_start_chrs = [x.start for x in sequence_lists] # Regular expression split sequence to tokenize text splitter = re.compile(r"(\\'|\\\"|\(\*|\*\)|\n|\"+|\'+|\#+|\s+|\w+|\W)") #Characters that autoindent one level on pressing Return/Enter autoindent_characters = [":"] def __init__(self, parent=None, additional_keywords=[]): """Overridden initialization""" # Initialize superclass super().__init__() # Set the lexer's index self.index = CustomPython._index CustomPython._index += 1 # Set the additional keywords self.additional_list = ["self"] self.additional_list.extend(additional_keywords) if lexers.nim_lexers_found == True: lexers.nim_lexers.python_set_keywords(self.index, additional_keywords) # Set the default style values self.setDefaultColor(self.default_color) self.setDefaultPaper(self.default_paper) self.setDefaultFont(self.default_font) # Reset autoindentation style self.setAutoIndentStyle(0) # Set the theme self.set_theme(data.theme) def language(self): return "Python" def description(self, style): if style <= 16: description = "Custom lexer for the Python programming languages" else: description = "" return description def defaultStyle(self): return self.styles["Default"] def braceStyle(self): return self.styles["Default"] def defaultFont(self, style): return data.QFont(data.current_font_name, data.current_font_size) def set_theme(self, theme): for style in self.styles: # Papers paper = data.QColor(getattr(theme.Paper.Python, style)) self.setPaper(paper, self.styles[style]) # Fonts lexers.set_font(self, style, getattr(theme.Font.Python, style)) if lexers.nim_lexers_found == True: def __del__(self): lexers.nim_lexers.python_delete_keywords(self.index) def styleText(self, start, end): editor = self.editor() if editor is None: return # lexers.nim_lexers.python_style_text( # self.index, start, end, self, editor # ) lexers.nim_lexers.python_style_test(self.index, start, end) else: def styleText(self, start, end): editor = self.editor() if editor is None: return # Initialize the styling self.startStyling(start) # Scintilla works with bytes, so we have to adjust the start and end boundaries text = bytearray(editor.text(), "utf-8")[start:end].decode("utf-8") # Loop optimizations setStyling = self.setStyling # Initialize comment state and split the text into tokens sequence = None tokens = [(token, len(bytearray(token, "utf-8"))) for token in self.splitter.findall(text)] # Check if there is a style(comment, string, ...) stretching on from the previous line if start != 0: previous_style = editor.SendScintilla(editor.SCI_GETSTYLEAT, start - 1) for i in self.multiline_sequence_list: if previous_style == i.style: sequence = i break # Style the tokens accordingly for i, token in enumerate(tokens): # print(token[0].encode("utf-8")) token_name = token[0] token_length = token[1] if sequence != None: if token_name in sequence.stop_sequences: if sequence.add_to_style == True: setStyling(token_length, sequence.style) else: setStyling(token_length, self.styles["Default"]) sequence = None elif any(ch in token_name for ch in sequence.stop_characters): if sequence.add_to_style == True: setStyling(token_length, sequence.style) else: setStyling(token_length, self.styles["Default"]) sequence = None else: setStyling(token_length, sequence.style) elif token_name in self.sequence_start_chrs: for i in self.sequence_lists: if token_name == i.start: if i.stop_sequences == [] and i.stop_characters == []: # Skip styling if both stop sequences and stop characters are empty setStyling(token_length, i.style) else: # Style the sequence and store the reference to it sequence = i if i.add_to_style == True: setStyling(token_length, sequence.style) else: if token_name in self.keyword_list: setStyling(token_length, self.styles["Keyword"]) elif token_name in self.additional_list: setStyling( token_length, self.styles["CustomKeyword"]) else: setStyling(token_length, self.styles["Default"]) break elif token_name in self.keyword_list: setStyling(token_length, self.styles["Keyword"]) elif token_name in self.additional_list: setStyling(token_length, self.styles["CustomKeyword"]) elif token_name[0].isdigit(): setStyling(token_length, self.styles["Number"]) else: setStyling(token_length, self.styles["Default"])
class CiCode(data.QsciLexerCustom): """ Custom lexer for the Citect CiCode programming language """ class Sequence: def __init__(self, start, stop_sequences, stop_characters, style, add_to_style): self.start = start self.stop_sequences = stop_sequences self.stop_characters = stop_characters self.style = style self.add_to_style = add_to_style styles = { "Default" : 0, "Comment" : 1, "MultilineComment" : 2, "Keyword" : 3, "BuiltInFunction" : 4, "String" : 5, "Number" : 6, "Operator" : 7, "Function" : 8, } # Class variables default_color = data.QColor(data.theme.Font.CiCode.Default[1]) default_paper = data.QColor(data.theme.Paper.CiCode.Default) default_font = data.QFont(data.current_font_name, data.current_font_size) keyword_list = [ "function", "end", "if", "else", "do", "then", "while", "for", "mod", "bitand", "bitor", "bitxor", "and", "or", "not", "bitand", "bitor", "bitxor", "global", "public", "private", "return", ] type_list = [ "string", "object", "int", "real", "quality", "timestamp", ] builtin_function_list = [ "abs", "acccontrol", "accumbrowseclose", "accumbrowsefirst", "accumbrowsegetfield", "accumbrowsenext", "accumbrowsenumrecords", "accumbrowseopen", "accumbrowseprev", "alarmack", "alarmackrec", "alarmacktag", "alarmactive", "alarmcatgetformat", "alarmclear", "alarmclearrec", "alarmcleartag", "alarmcomment", "alarmcount", "alarmcountequipment", "alarmcountlist", "alarmdelete", "alarmdisable", "alarmdisablerec", "alarmdisabletag", "alarmdsp", "alarmdspclusteradd", "alarmdspclusterinuse", "alarmdspclusterremove", "alarmdsplast", "alarmdspnext", "alarmdspprev", "alarmenable", "alarmenablerec", "alarmenabletag", "alarmeventque", "alarmfilterclose", "alarmfiltereditappend", "alarmfiltereditclose", "alarmfiltereditcommit", "alarmfiltereditfirst", "alarmfiltereditlast", "alarmfiltereditnext", "alarmfiltereditopen", "alarmfiltereditprev", "alarmfiltereditset", "alarmfilterform", "alarmfilteropen", "alarmfirstcatrec", "alarmfirstprirec", "alarmfirsttagrec", "alarmgetdelay", "alarmgetdelayrec", "alarmgetdsp", "alarmgetfieldrec", "alarmgetfiltername", "alarmgetinfo", "alarmgetorderbykey", "alarmgetthreshold", "alarmgetthresholdrec", "alarmhelp", "alarmnextcatrec", "alarmnextprirec", "alarmnexttagrec", "alarmnotifyvarchange", "alarmqueryfirstrec", "alarmquerynextrec", "alarmresetquery", "alarmsetdelay", "alarmsetdelayrec", "alarmsetinfo", "alarmsetquery", "alarmsetthreshold", "alarmsetthresholdrec", "alarmsplit", "alarmsumappend", "alarmsumcommit", "alarmsumdelete", "alarmsumfind", "alarmsumfirst", "alarmsumget", "alarmsumlast", "alarmsumnext", "alarmsumprev", "alarmsumset", "alarmsumsplit", "alarmsumtype", "almbrowseack", "almbrowseclear", "almbrowseclose", "almbrowsedisable", "almbrowseenable", "almbrowsefirst", "almbrowsegetfield", "almbrowsenext", "almbrowsenumrecords", "almbrowseopen", "almbrowseprev", "almsummaryack", "almsummaryclear", "almsummaryclose", "almsummarycommit", "almsummarydelete", "almsummarydeleteall", "almsummarydisable", "almsummaryenable", "almsummaryfirst", "almsummarygetfield", "almsummarylast", "almsummarynext", "almsummarynumrecords", "almsummaryopen", "almsummaryprev", "almsummarysetfieldvalue", "almtagsack", "almtagsclear", "almtagsclose", "almtagsdisable", "almtagsenable", "almtagsfirst", "almtagsgetfield", "almtagsnext", "almtagsnumrecords", "almtagsopen", "almtagsprev", "anbyname", "arccos", "arcsin", "arctan", "areacheck", "ass", "asschain", "asschainpage", "asschainpopup", "asschainwin", "asschainwinfree", "assgetproperty", "assgetscale", "assinfo", "assinfoex", "asspage", "asspopup", "assscalestr", "asstag", "asstitle", "assvartags", "asswin", "assert", "beep", "callevent", "chainevent", "chartostr", "citectcolourtopackedrgb", "citectinfo", "clipcopy", "clippaste", "clipreadln", "clipsetmode", "clipwriteln", "clusteractivate", "clusterdeactivate", "clusterfirst", "clustergetname", "clusterisactive", "clusternext", "clusterservertypes", "clustersetname", "clusterstatus", "clusterswapactive", "codesetmode", "codetrace", "comclose", "comopen", "comread", "comreset", "comwrite", "cos", "createcontrolobject", "createobject", "ddeexec", "ddepost", "dderead", "ddewrite", "ddehexecute", "ddehgetlasterror", "ddehinitiate", "ddehpoke", "ddehreadln", "ddehrequest", "ddehsetmode", "ddehterminate", "ddehwriteln", "dllcall", "dllcallex", "dllclose", "dllopen", "date", "dateadd", "dateday", "dateinfo", "datemonth", "datesub", "dateweekday", "dateyear", "debugbreak", "debugmsg", "debugmsgset", "degtorad", "delayshutdown", "devappend", "devclose", "devcontrol", "devcurr", "devdelete", "devdisable", "deveof", "devfind", "devfirst", "devflush", "devgetfield", "devhistory", "devinfo", "devmodify", "devnext", "devopen", "devopengrp", "devprev", "devprint", "devread", "devreadln", "devrecno", "devseek", "devsetfield", "devsize", "devwrite", "devwriteln", "devzap", "displayruntimemanager", "dllclasscallmethod", "dllclasscreate", "dllclassdispose", "dllclassgetproperty", "dllclassisvalid", "dllclasssetproperty", "driverinfo", "dspancreatecontrolobject", "dspanfree", "dspangetarea", "dspangetmetadata", "dspangetmetadataat", "dspangetpos", "dspangetprivilege", "dspaninrgn", "dspaninfo", "dspanmove", "dspanmoverel", "dspannew", "dspannewrel", "dspansetmetadata", "dspansetmetadataat", "dspbar", "dspbmp", "dspbutton", "dspbuttonfn", "dspchart", "dspcol", "dspdel", "dspdelayrenderbegin", "dspdelayrenderend", "dspdirty", "dsperror", "dspfile", "dspfilegetinfo", "dspfilegetname", "dspfilescroll", "dspfilesetname", "dspfont", "dspfonthnd", "dspfullscreen", "dspgetanbottom", "dspgetancur", "dspgetanextent", "dspgetanfirst", "dspgetanfrompoint", "dspgetanheight", "dspgetanleft", "dspgetannext", "dspgetanright", "dspgetantop", "dspgetanwidth", "dspgetenv", "dspgetmouse", "dspgetmouseover", "dspgetnearestan", "dspgetparentan", "dspgetslider", "dspgettip", "dspgraybutton", "dspinfo", "dspinfodestroy", "dspinfofield", "dspinfonew", "dspinfovalid", "dspisbuttongray", "dspkernel", "dspmci", "dspmarkermove", "dspmarkernew", "dspplaysound", "dsppopupconfigmenu", "dsppopupmenu", "dsprichtext", "dsprichtextedit", "dsprichtextenable", "dsprichtextgetinfo", "dsprichtextload", "dsprichtextpgscroll", "dsprichtextprint", "dsprichtextsave", "dsprichtextscroll", "dsprubend", "dsprubmove", "dsprubsetclip", "dsprubstart", "dspsetslider", "dspsettip", "dspsettooltipfont", "dspstatus", "dspstr", "dspsym", "dspsymanm", "dspsymanmex", "dspsymatsize", "dsptext", "dsptipmode", "dsptrend", "dsptrendinfo", "dumpkernel", "engtogeneric", "entercriticalsection", "equipbrowseclose", "equipbrowsefirst", "equipbrowsegetfield", "equipbrowsenext", "equipbrowsenumrecords", "equipbrowseopen", "equipbrowseprev", "equipcheckupdate", "equipgetproperty", "equipsetproperty", "equipstatebrowseclose", "equipstatebrowsefirst", "equipstatebrowsegetfield", "equipstatebrowsenext", "equipstatebrowsenumrecords", "equipstatebrowseopen", "equipstatebrowseprev", "errcom", "errdrv", "errgethw", "errhelp", "errinfo", "errlog", "errmsg", "errset", "errsethw", "errsetlevel", "errtrap", "exec", "executedtspkg", "exp", "ftpclose", "ftpfilecopy", "ftpfilefind", "ftpfilefindclose", "ftpopen", "fact", "fileclose", "filecopy", "filedelete", "fileeof", "fileexist", "filefind", "filefindclose", "filegettime", "filemakepath", "fileopen", "fileprint", "fileread", "filereadblock", "filereadln", "filerename", "filerichtextprint", "fileseek", "filesettime", "filesize", "filesplitpath", "filewrite", "filewriteblock", "filewriteln", "fmtclose", "fmtfieldhnd", "fmtgetfield", "fmtgetfieldcount", "fmtgetfieldhnd", "fmtgetfieldname", "fmtgetfieldwidth", "fmtopen", "fmtsetfield", "fmtsetfieldhnd", "fmttostr", "formactive", "formaddlist", "formbutton", "formcheckbox", "formcombobox", "formcurr", "formdestroy", "formedit", "formfield", "formgetcurrinst", "formgetdata", "formgetinst", "formgettext", "formgoto", "formgroupbox", "forminput", "formlistaddtext", "formlistbox", "formlistdeletetext", "formlistselecttext", "formnew", "formnumpad", "formopenfile", "formpassword", "formposition", "formprompt", "formradiobutton", "formread", "formsaveasfile", "formsecurepassword", "formselectprinter", "formsetdata", "formsetinst", "formsettext", "formwndhnd", "fullname", "fuzzyclose", "fuzzygetcodevalue", "fuzzygetshellvalue", "fuzzyopen", "fuzzysetcodevalue", "fuzzysetshellvalue", "fuzzytrace", "getarea", "getbluevalue", "getenv", "getevent", "getgreenvalue", "getlanguage", "getlogging", "getpriv", "getredvalue", "getwintitle", "grpclose", "grpdelete", "grpfirst", "grpin", "grpinsert", "grpmath", "grpname", "grpnext", "grpopen", "grptostr", "halt", "hextostr", "highbyte", "highword", "htmlhelp", "hwalarmque", "iodevicecontrol", "iodeviceinfo", "iodevicestats", "infoform", "infoforman", "input", "inttoreal", "inttostr", "iserror", "kercmd", "kernelqueuelength", "kerneltableinfo", "kerneltableitemcount", "keyallowcursor", "keybs", "keydown", "keyget", "keygetcursor", "keyleft", "keymove", "keypeek", "keyput", "keyputstr", "keyreplay", "keyreplayall", "keyright", "keysetcursor", "keysetseq", "keyup", "languagefiletranslate", "leavecriticalsection", "libalarmfilterform", "ln", "log", "login", "loginform", "logout", "logoutidle", "lowbyte", "lowword", "mailerror", "maillogoff", "maillogon", "mailread", "mailsend", "makecitectcolour", "max", "menugetchild", "menugetfirstchild", "menugetgenericnode", "menugetnextchild", "menugetpagenode", "menugetparent", "menugetprevchild", "menugetwindownode", "menunodeaddchild", "menunodegetproperty", "menunodehascommand", "menunodeisdisabled", "menunodeishidden", "menunoderemove", "menunoderuncommand", "menunodesetdisabledwhen", "menunodesethiddenwhen", "menunodesetproperty", "menureload", "message", "min", "msgbrdcst", "msgclose", "msggetcurr", "msgopen", "msgrpc", "msgread", "msgstate", "msgwrite", "multimonitorstart", "multisignatureform", "multisignaturetagwrite", "name", "oledatetotime", "objectassociateevents", "objectassociatepropertywithtag", "objectbyname", "objecthasinterface", "objectisvalid", "objecttostr", "onevent", "packedrgb", "packedrgbtocitectcolour", "pagealarm", "pageback", "pagedisabled", "pagedisplay", "pageexists", "pagefile", "pagefileinfo", "pageforward", "pagegetint", "pagegetstr", "pagegoto", "pagehardware", "pagehistorydspmenu", "pagehistoryempty", "pagehome", "pageinfo", "pagelast", "pagelistcount", "pagelistcurrent", "pagelistdelete", "pagelistdisplay", "pagelistinfo", "pagemenu", "pagenext", "pagepeekcurrent", "pagepeeklast", "pagepoplast", "pagepopup", "pageprev", "pageprocessanalyst", "pageprocessanalystpens", "pagepushlast", "pagerecall", "pagerichtextfile", "pagesoe", "pageselect", "pagesetint", "pagesetstr", "pagesummary", "pagetask", "pagetransformcoords", "pagetrend", "pagetrendex", "parameterget", "parameterput", "pathtostr", "pi", "plotclose", "plotdraw", "plotfile", "plotgetmarker", "plotgrid", "plotinfo", "plotline", "plotmarker", "plotopen", "plotscalemarker", "plotsetmarker", "plottext", "plotxyline", "pow", "print", "printfont", "println", "processanalystloadfile", "processanalystpopup", "processanalystselect", "processanalystsetpen", "processanalystwin", "processisclient", "processisserver", "processrestart", "productinfo", "projectinfo", "projectrestartget", "projectrestartset", "projectset", "prompt", "pulse", "qualitycreate", "qualitygetpart", "qualityisbad", "qualityiscontrolinhibit", "qualityisgood", "qualityisoverride", "qualityisuncertain", "qualitysetpart", "qualitytostr", "queclose", "quelength", "queopen", "quepeek", "queread", "quewrite", "radtodeg", "rand", "reread", "realtostr", "repgetcluster", "repgetcontrol", "repsetcontrol", "report", "round", "soearchive", "soedismount", "soeeventadd", "soemount", "spcalarms", "spcclientinfo", "spcgethistogramtable", "spcgetsubgrouptable", "spcplot", "spcprocessxrsget", "spcprocessxrsset", "spcsetlimit", "spcspeclimitget", "spcspeclimitset", "spcsubgroupsizeget", "spcsubgroupsizeset", "sqlappend", "sqlbegintran", "sqlcall", "sqlcancel", "sqlclose", "sqlcommit", "sqlconnect", "sqlcreate", "sqldisconnect", "sqldispose", "sqlend", "sqlerrmsg", "sqlexec", "sqlfieldinfo", "sqlgetfield", "sqlgetrecordset", "sqlgetscalar", "sqlinfo", "sqlisnullfield", "sqlnext", "sqlnofields", "sqlnumchange", "sqlnumfields", "sqlopen", "sqlparamsclearall", "sqlparamssetasint", "sqlparamssetasreal", "sqlparamssetasstring", "sqlprev", "sqlquerycreate", "sqlquerydispose", "sqlrollback", "sqlrowcount", "sqlset", "sqltraceoff", "sqltraceon", "schdclose", "schdconfigclose", "schdconfigfirst", "schdconfiggetfield", "schdconfignext", "schdconfignumrecords", "schdconfigopen", "schdconfigprev", "schdfirst", "schdnext", "schdnumrecords", "schdopen", "schdprev", "schdspecialadd", "schdspecialclose", "schdspecialdelete", "schdspecialfirst", "schdspecialgetfield", "schdspecialitemadd", "schdspecialitemclose", "schdspecialitemdelete", "schdspecialitemfirst", "schdspecialitemgetfield", "schdspecialitemmodify", "schdspecialitemnext", "schdspecialitemnumrecords", "schdspecialitemopen", "schdspecialitemprev", "schdspecialmodify", "schdspecialnext", "schdspecialnumrecords", "schdspecialopen", "schdspecialprev", "scheduleitemadd", "scheduleitemdelete", "scheduleitemmodify", "scheduleitemsetrepeat", "semclose", "semopen", "semsignal", "semwait", "sendkeys", "serialkey", "serverbrowseclose", "serverbrowsefirst", "serverbrowsegetfield", "serverbrowsenext", "serverbrowsenumrecords", "serverbrowseopen", "serverbrowseprev", "serverdumpkernel", "servergetproperty", "serverinfo", "serverinfoex", "serverisonline", "serverrpc", "serverreload", "serverrestart", "servicegetlist", "setarea", "setevent", "setlanguage", "setlogging", "shutdown", "shutdownform", "shutdownmode", "sign", "sin", "sleep", "sleepms", "sqrt", "strcalcwidth", "strclean", "strfill", "strformat", "strgetchar", "strleft", "strlength", "strlower", "strmid", "strpad", "strright", "strsearch", "strsetchar", "strtochar", "strtodate", "strtofmt", "strtogrp", "strtohex", "strtoint", "strtolines", "strtolocaltext", "strtoperiod", "strtoreal", "strtotime", "strtotimestamp", "strtovalue", "strtrim", "strtruncfont", "strtruncfonthnd", "strupper", "strword", "subscriptionaddcallback", "subscriptiongetattribute", "subscriptiongetinfo", "subscriptiongetquality", "subscriptiongettag", "subscriptiongettimestamp", "subscriptiongetvalue", "subscriptionremovecallback", "switchconfig", "systime", "systimedelta", "tablelookup", "tablemath", "tableshift", "tagbrowseclose", "tagbrowsefirst", "tagbrowsegetfield", "tagbrowsenext", "tagbrowsenumrecords", "tagbrowseopen", "tagbrowseprev", "tagdebug", "tagdebugform", "tageventformat", "tageventqueue", "taggetproperty", "taggetscale", "taginfo", "taginfoex", "tagrdbreload", "tagramp", "tagread", "tagreadex", "tagresolve", "tagscalestr", "tagsetoverridebad", "tagsetoverridegood", "tagsetoverridequality", "tagsetoverrideuncertain", "tagsubscribe", "tagunresolve", "tagunsubscribe", "tagwrite", "tagwriteeventque", "tagwriteintarray", "tagwriterealarray", "tan", "taskcluster", "taskgetsignal", "taskhnd", "taskkill", "tasknew", "tasknewex", "taskresume", "tasksetsignal", "tasksuspend", "testrandomwave", "testsawwave", "testsinwave", "testsquarewave", "testtriangwave", "time", "timecurrent", "timehour", "timeinfo", "timeinttotimestamp", "timemidnight", "timemin", "timesec", "timeset", "timetostr", "timeutcoffset", "timestampadd", "timestampcreate", "timestampcurrent", "timestampdifference", "timestampformat", "timestampgetpart", "timestampsub", "timestamptostr", "timestamptotimeint", "toggle", "tracemsg", "trenddspcursorcomment", "trenddspcursorscale", "trenddspcursortag", "trenddspcursortime", "trenddspcursorvalue", "trendgetan", "trendpopup", "trendrun", "trendsetdate", "trendsetscale", "trendsetspan", "trendsettime", "trendsettimebase", "trendwin", "trendzoom", "trnaddhistory", "trnbrowseclose", "trnbrowsefirst", "trnbrowsegetfield", "trnbrowsenext", "trnbrowsenumrecords", "trnbrowseopen", "trnbrowseprev", "trnclientinfo", "trncompareplot", "trndelhistory", "trndelete", "trnecho", "trneventgettable", "trneventgettablems", "trneventsettable", "trneventsettablems", "trnexportcsv", "trnexportclip", "trnexportdbf", "trnexportdde", "trnflush", "trngetbufevent", "trngetbuftime", "trngetbufvalue", "trngetcluster", "trngetcursorevent", "trngetcursormstime", "trngetcursorpos", "trngetcursortime", "trngetcursorvalue", "trngetcursorvaluestr", "trngetdefscale", "trngetdisplaymode", "trngetevent", "trngetformat", "trngetgatedvalue", "trngetinvalidvalue", "trngetmstime", "trngetmode", "trngetpen", "trngetpencomment", "trngetpenfocus", "trngetpenno", "trngetperiod", "trngetscale", "trngetscalestr", "trngetspan", "trngettable", "trngettime", "trngetunits", "trninfo", "trnisvalidvalue", "trnnew", "trnplot", "trnprint", "trnsamplesconfigured", "trnscroll", "trnselect", "trnsetcursor", "trnsetcursorpos", "trnsetdisplaymode", "trnsetevent", "trnsetpen", "trnsetpenfocus", "trnsetperiod", "trnsetscale", "trnsetspan", "trnsettable", "trnsettime", "usercreate", "usercreateform", "userdelete", "usereditform", "userinfo", "userlogin", "userpassword", "userpasswordexpirydays", "userpasswordform", "usersetstr", "userupdaterecord", "userverify", "variablequality", "variabletimestamp", "verifyprivilegeform", "verifyprivilegetagwrite", "version", "whoami", "wincopy", "winfile", "winfree", "wingetfocus", "wingetwndhnd", "wingoto", "winmode", "winmove", "winnew", "winnewat", "winnext", "winnumber", "winpos", "winprev", "winprint", "winprintfile", "winselect", "winsetname", "winsize", "winstyle", "wintitle", "wndfind", "wndgetfileprofile", "wndgetprofile", "wndhelp", "wndinfo", "wndmonitorinfo", "wndputfileprofile", "wndputprofile", "wndshow", "wndviewer", "xmlclose", "xmlcreate", "xmlgetattribute", "xmlgetattributecount", "xmlgetattributename", "xmlgetattributevalue", "xmlgetchild", "xmlgetchildcount", "xmlgetparent", "xmlgetroot", "xmlnodeaddchild", "xmlnodefind", "xmlnodegetname", "xmlnodegetvalue", "xmlnoderemove", "xmlnodesetvalue", "xmlopen", "xmlsave", "xmlsetattribute", "_objectcallmethod", "_objectgetproperty", "_objectsetproperty", ] operator_list = [ "=", "+", "-", "*", "/", "<", ">", "@", "$", ".", "~", "&", "%", "|", "!", "?", "^", ".", ":", "\"", ] func = Sequence('function', [], ['(', '\n'], styles["Function"], False) strseq = Sequence('"', ['"', '\n'], [], styles["String"], True) comment = Sequence('//', [], ['\n'], styles["Comment"], True) mcomment = Sequence('/*', ['*/'], [], styles["MultilineComment"], True) sequence_lists = [strseq, comment, mcomment, func] multiline_sequence_list = [mcomment] sequence_start_chrs = [x.start for x in sequence_lists] # splitter = re.compile(r"(/\*|\*/|\\\"|\(\*|\*\)|//|\n|\"+|\'+|\#+|\s+|\w+|\W)") splitter = re.compile(r"(/\*|\*/|\(\*|\*\)|//|\n|\"+|\'+|\#+|\s+|\w+|\W)") # Characters that autoindent one level on pressing Return/Enter autoindent_characters = ["then", "do", ")"] def __init__(self, parent=None): """ Overridden initialization """ # Initialize superclass super().__init__() # Set the default style values self.setDefaultColor(self.default_color) self.setDefaultPaper(self.default_paper) self.setDefaultFont(self.default_font) # Reset autoindentation style self.setAutoIndentStyle(0) # Set the theme self.set_theme(data.theme) def language(self): return "CiCode" def description(self, style): if style < len(self.styles): description = "Custom lexer for the CiCode programming languages" else: description = "" return description def defaultStyle(self): return self.styles["Default"] def braceStyle(self): return self.styles["Default"] def defaultFont(self, style): return data.QFont(data.current_font_name, data.current_font_size) def set_theme(self, theme): for style in self.styles: # Papers self.setPaper( data.QColor(theme.Paper.CiCode.Default), self.styles[style] ) # Fonts lexers.set_font(self, style, getattr(theme.Font.CiCode, style)) def styleText(self, start, end): """ Overloaded method for styling text. """ # Style in pure Python, VERY SLOW! editor = self.editor() if editor is None: return # Initialize the styling self.startStyling(start) # Scintilla works with bytes, so we have to adjust # the start and end boundaries text = bytearray(editor.text().lower(), "utf-8")[start:end].decode("utf-8") # Loop optimizations setStyling = self.setStyling DEFAULT = self.styles["Default"] COMMENT = self.styles["Comment"] KEYWORD = self.styles["Keyword"] BUILTINFUNCTION = self.styles["BuiltInFunction"] STRING = self.styles["String"] NUMBER = self.styles["Number"] OPERATOR = self.styles["Operator"] # Initialize various states and split the text into tokens stringing = False commenting = False multiline_commenting = False sequence = None tokens = [ (token.lower(), len(bytearray(token, "utf-8"))) for token in self.splitter.findall(text) ] # Check if there is a style(comment, string, ...) stretching on from the previous line if start != 0: previous_style = editor.SendScintilla(editor.SCI_GETSTYLEAT, start - 1) for i in self.multiline_sequence_list: if previous_style == i.style: sequence = i break # Style the tokens accordingly for i, token in enumerate(tokens): token_name = token[0] token_length = token[1] if sequence != None: if token_name in sequence.stop_sequences: if sequence.add_to_style == True: setStyling(token_length, sequence.style) else: setStyling(token_length, self.styles["Default"]) sequence = None elif any(ch in token_name for ch in sequence.stop_characters): if sequence.add_to_style == True: setStyling(token_length, sequence.style) else: setStyling(token_length, self.styles["Default"]) sequence = None else: setStyling(token_length, sequence.style) elif token_name in self.sequence_start_chrs: for i in self.sequence_lists: if token_name == i.start: if i.stop_sequences == [] and i.stop_characters == []: # Skip styling if both stop sequences and stop characters are empty setStyling(token_length, i.style) else: # Style the sequence and store the reference to it sequence = i if i.add_to_style == True: setStyling(token_length, sequence.style) else: if token_name in self.keyword_list: setStyling(token_length, KEYWORD) elif token_name in self.type_list: setStyling(token_length, KEYWORD) elif token_name in self.operator_list: setStyling(token_length, OPERATOR) elif token_name[0].isdigit(): setStyling(token_length, NUMBER) elif token_name in self.builtin_function_list: setStyling(token_length, BUILTINFUNCTION) else: setStyling(token_length, self.styles["Default"]) break elif token_name in self.keyword_list: setStyling(token_length, KEYWORD) elif token_name in self.type_list: setStyling(token_length, KEYWORD) elif token_name in self.operator_list: setStyling(token_length, OPERATOR) elif token_name[0].isdigit(): setStyling(token_length, NUMBER) elif token_name in self.builtin_function_list: setStyling(token_length, BUILTINFUNCTION) else: setStyling(token_length, DEFAULT)
class BasicWidget(data.QTabWidget): """Basic widget used for holding QScintilla/QTextEdit objects""" class CustomTabBar(data.QTabBar): """Custom tab bar used to capture tab clicks, ...""" # Reference to the parent widget _parent = None # Reference to the main form main_form = None # Reference to the tab menu tab_menu = None def __init__(self, parent): """Initialize the tab bar object""" #Initialize superclass super().__init__(parent) #Store the parent reference self._parent = parent #Store the main form reference self.main_form = self._parent._parent def mousePressEvent(self, event): #Execute the superclass event method super().mousePressEvent(event) event_button = event.button() key_modifiers = data.QApplication.keyboardModifiers() #Tab Drag&Drop functionality if event_button == data.Qt.LeftButton: if (key_modifiers == data.Qt.ControlModifier or key_modifiers == data.Qt.ShiftModifier): tab_number = self._parent.tabBar().tabAt(event.pos()) if tab_number != -1: mime_data = data.QMimeData() mime_data.setText("{} {:d}".format( self._parent.name, tab_number)) drag = data.QDrag(self._parent) drag.setMimeData(mime_data) drag.setHotSpot(event.pos()) drag.exec_(data.Qt.CopyAction | data.Qt.MoveAction) def mouseReleaseEvent(self, event): # Execute the superclass event method super().mouseReleaseEvent(event) event_button = event.button() # Check for a right click if event_button == data.Qt.RightButton: # Clean up the old menu if self.tab_menu != None: self.tab_menu.setParent(None) self.tab_menu = None # Create the popup tab context menu menu = self._parent.TabMenu( self, self.main_form, self._parent, self._parent.widget(self.tabAt(event.pos())), event.pos()) self.tab_menu = menu # Show the tab context menu cursor = data.QCursor.pos() menu.popup(cursor) # Accept the event event.accept() class TabMenu(data.QMenu): """Custom menu that appears when right clicking a tab""" def __init__(self, parent, main_form, basic_widget, editor_widget, cursor_position): #Nested function for creating a move or copy action def create_move_copy_action(action_name, window_name, move=True, focus_name=None): window = main_form.get_window_by_name(window_name) action = data.QAction(action_name, self) if move == True: func = window.move_editor_in action_func = functools.partial( func, basic_widget, parent.tabAt(cursor_position), ) icon = functions.create_icon( 'tango_icons/window-tab-move.png') else: func = window.copy_editor_in action_func = functools.partial( func, basic_widget, parent.tabAt(cursor_position), focus_name) icon = functions.create_icon( 'tango_icons/window-tab-copy.png') action.setIcon(icon) action.triggered.connect(action_func) return action #Nested function for creating text difference actions def create_diff_action(action_name, main_form, compare_tab_1, compare_tab_2): def difference_function(main_form, compare_tab_1, compare_tab_2): #Check for text documents in both tabs if (isinstance(compare_tab_1, CustomEditor) == False and isinstance(compare_tab_1, PlainEditor) == False): main_form.display.repl_display_message( "First tab is not a text document!", message_type=data.MessageType.ERROR) return elif (isinstance(compare_tab_2, CustomEditor) == False and isinstance(compare_tab_2, PlainEditor) == False): main_form.display.repl_display_message( "Second tab is not a text document!", message_type=data.MessageType.ERROR) return #Initialize the compare parameters text_1 = compare_tab_1.text() text_1_name = compare_tab_1.name text_2 = compare_tab_2.text() text_2_name = compare_tab_2.name #Display the text difference main_form.display.show_text_difference( text_1, text_2, text_1_name, text_2_name) diff_action = data.QAction(action_name, self) if "main" in action_name.lower(): diff_action.setIcon( functions.create_icon( 'tango_icons/compare-text-main.png')) elif "upper" in action_name.lower(): diff_action.setIcon( functions.create_icon( 'tango_icons/compare-text-upper.png')) else: diff_action.setIcon( functions.create_icon( 'tango_icons/compare-text-lower.png')) function = functools.partial(difference_function, main_form, compare_tab_1, compare_tab_2) diff_action.triggered.connect(function) return diff_action #Nested function for checking is the basic widgets current tab is an editor def check_for_editor(basic_widget): current_tab = basic_widget.currentWidget() if (isinstance(current_tab, CustomEditor) == True or isinstance(current_tab, PlainEditor) == True): return True else: return False #Nested function for updating the current working directory def update_cwd(): #Get the document path path = os.path.dirname(editor_widget.save_name) #Check if the path is not an empty string if path == "": message = "Document path is not valid!" main_form.display.repl_display_message( message, message_type=data.MessageType.WARNING) return main_form.set_cwd(path) #Initialize the superclass super().__init__(parent) #Change the basic widget name to lowercase basic_widget_name = basic_widget.name.lower() #Add actions according to the parent BasicWidget #Move actions move_to_main = create_move_copy_action("Move to main window", "main") move_to_upper = create_move_copy_action("Move to upper window", "upper") move_to_lower = create_move_copy_action("Move to lower window", "lower") #Copy action copy_to_main = create_move_copy_action("Copy to main window", "main", move=False, focus_name="main") copy_to_upper = create_move_copy_action("Copy to upper window", "upper", move=False, focus_name="upper") copy_to_lower = create_move_copy_action("Copy to lower window", "lower", move=False, focus_name="lower") #Clear REPL MESSAGES tab action clear_repl_action = data.QAction("Clear messages", self) clear_repl_action.setIcon( functions.create_icon('tango_icons/edit-clear.png')) clear_repl_action.triggered.connect( main_form.display.repl_clear_tab) #Text difference actions diff_main_action = create_diff_action( "Text diff to main window", main_form, main_form.main_window.currentWidget(), editor_widget) diff_upper_action = create_diff_action( "Text diff to upper window", main_form, main_form.upper_window.currentWidget(), editor_widget) diff_lower_action = create_diff_action( "Text diff to lower window", main_form, main_form.lower_window.currentWidget(), editor_widget) #Update current working directory action if hasattr(editor_widget, "save_name") == True: update_cwd_action = data.QAction("Update CWD", self) update_cwd_action.setIcon( functions.create_icon('tango_icons/update-cwd.png')) update_cwd_action.triggered.connect(update_cwd) self.addAction(update_cwd_action) self.addSeparator() # Add the 'copy file name to clipboard' action clipboard_copy_action = data.QAction( "Copy document name to clipboard", self) def clipboard_copy(): cb = data.application.clipboard() cb.clear(mode=cb.Clipboard) cb.setText(editor_widget.name, mode=cb.Clipboard) clipboard_copy_action.setIcon( functions.create_icon('tango_icons/edit-copy.png')) clipboard_copy_action.triggered.connect(clipboard_copy) self.addAction(clipboard_copy_action) self.addSeparator() #Nested function for adding diff actions def add_diff_actions(): #Diff to main window if (check_for_editor(main_form.main_window) == True and editor_widget != main_form.main_window.currentWidget()): self.addAction(diff_main_action) #Diff to upper window if (check_for_editor(main_form.upper_window) == True and editor_widget != main_form.upper_window.currentWidget()): self.addAction(diff_upper_action) #Diff to lower window if (check_for_editor(main_form.lower_window) == True and editor_widget != main_form.lower_window.currentWidget()): self.addAction(diff_lower_action) #Check which basic widget is the parent to the clicked tab if "main" in basic_widget_name: #Add the actions to the menu self.addAction(move_to_upper) self.addAction(move_to_lower) self.addSeparator() #Check the tab widget type if isinstance(editor_widget, CustomEditor) == True: #Copy functions are only available to custom editors self.addAction(copy_to_upper) self.addAction(copy_to_lower) elif (isinstance(editor_widget, PlainEditor) == True and editor_widget.name == "REPL MESSAGES"): #REPL MESSAGES tab clear option self.addAction(clear_repl_action) if (isinstance(editor_widget, CustomEditor) == True or isinstance(editor_widget, PlainEditor) == True): #Diff functions for plain and custom editors self.addSeparator() add_diff_actions() elif "upper" in basic_widget_name: #Add the actions to the menu self.addAction(move_to_main) self.addAction(move_to_lower) self.addSeparator() #Check the tab widget type if isinstance(editor_widget, CustomEditor) == True: #Copy functions are only available to custom editors self.addAction(copy_to_main) self.addAction(copy_to_lower) elif (isinstance(editor_widget, PlainEditor) == True and editor_widget.name == "REPL MESSAGES"): #REPL MESSAGES tab clear option self.addAction(clear_repl_action) if (isinstance(editor_widget, CustomEditor) == True or isinstance(editor_widget, PlainEditor) == True): #Diff functions for plain and custom editors self.addSeparator() add_diff_actions() elif "lower" in basic_widget_name: #Add the actions to the menu self.addAction(move_to_main) self.addAction(move_to_upper) self.addSeparator() #Check the tab widget type if isinstance(editor_widget, CustomEditor) == True: #Copy functions are only available to custom editors self.addAction(copy_to_main) self.addAction(copy_to_upper) elif (isinstance(editor_widget, PlainEditor) == True and editor_widget.name == "REPL MESSAGES"): #REPL MESSAGES tab clear option self.addAction(clear_repl_action) if (isinstance(editor_widget, CustomEditor) == True or isinstance(editor_widget, PlainEditor) == True): #Diff functions for plain and custom editors self.addSeparator() add_diff_actions() # Closing self.addSeparator() close_other_action = data.QAction( "Close all other tabs in this window", self) close_other_action.setIcon( functions.create_icon('tango_icons/close-all-tabs.png')) close_other_action.triggered.connect( functools.partial(main_form.close_window_tabs, basic_widget, editor_widget)) self.addAction(close_other_action) # Class variables # Name of the basic widget name = "" # Reference to the last file that was drag&dropped onto the main form drag_dropped_file = None # Drag&Dropped text data drag_text = None # The source widgets of the drag&drop event drag_source = None # QMainWindow _parent = None # Custom tab bar custom_tab_bar = None # Default font for textboxes default_editor_font = data.QFont('Courier', 10) # Default font and icon size for the tab bar default_tab_font = None default_icon_size = None # Attribute for indicating if the REPL is indicated indicated = False def __init__(self, parent): """Initialization""" # Initialize superclass, from which the current class is inherited, # THIS MUST BE DONE SO THAT THE SUPERCLASS EXECUTES ITS __init__ !!!!!! super().__init__(parent) # Set various events and attributes # Save parent as a reference self._parent = parent # Initialize the custom tab bar self.custom_tab_bar = self.CustomTabBar(self) self.setTabBar(self.custom_tab_bar) # Enable drag&drop events self.setAcceptDrops(True) # Add close buttons to tabs self.setTabsClosable(True) # Set tabs as movable, so that you can move tabs with the mouse self.setMovable(True) # Add signal for coling a tab to the EVT_tabCloseRequested function self.tabCloseRequested.connect(self._signal_editor_tabclose) # Connect signal that fires when the tab index changes self.currentChanged.connect(self._signal_editor_tabindex_change) # Store the default settings self.default_tab_font = self.tabBar().font() self.default_icon_size = self.tabBar().iconSize() def customize_tab_bar(self): if data.custom_menu_scale != None and data.custom_menu_font != None: components.TheSquid.customize_menu_style(self.tabBar()) self.tabBar().setFont(data.QFont(*data.custom_menu_font)) new_icon_size = data.QSize(data.custom_menu_scale, data.custom_menu_scale) self.setIconSize(new_icon_size) else: components.TheSquid.customize_menu_style(self.tabBar()) self.tabBar().setFont(self.default_tab_font) self.setIconSize(self.default_icon_size) def _drag_filter(self, event): self.drag_dropped_file = None self.drag_text = None self.drag_source = None if event.mimeData().hasUrls(): url = event.mimeData().urls()[0] if url.isValid(): # Filter out non file items if url.scheme() == "file": # "toLocalFile" returns path to file self.drag_dropped_file = url.toLocalFile() event.accept() elif event.mimeData().text() != None: try: name, index = event.mimeData().text().split() # Don't accept drags into self if name != self.name: self.drag_source = event.source() self.drag_text = event.mimeData().text() event.accept() except: return def event(self, event): # Execute the superclass event method super().event(event) # Only check indication if the current widget is not indicated if self.indicated == False: if (event.type() == data.QEvent.KeyPress or event.type() == data.QEvent.KeyRelease): # Check indication self._parent.view.indication_check() # Indicate that the event was processed by returning True return True def dragEnterEvent(self, event): """Qt Drag event that fires when you click and drag something onto the basic widget""" self._drag_filter(event) def dropEvent(self, event): """Qt Drop event""" if self.drag_dropped_file != None: #Displays the file name with path data.print_log("Drag&Dropped: " + str(self.drag_dropped_file)) #Open file in a new scintilla tab self._parent.open_file(self.drag_dropped_file, self) event.accept() elif self.drag_text != None: #Drag&drop widget event occured try: name, str_index = self.drag_text.split() index = int(str_index) key_modifiers = data.QApplication.keyboardModifiers() if (key_modifiers == data.Qt.ControlModifier or key_modifiers == data.Qt.AltModifier): self.copy_editor_in(self.drag_source, index) data.print_log( "Drag&Drop copied tab {:d} from the {} widget".format( index, name)) else: self.move_editor_in(self.drag_source, index) data.print_log( "Drag&Drop moved tab {:d} from the {} widget".format( index, name)) event.accept() except: self._parent.display.repl_display_message( traceback.format_exc(), message_type=data.MessageType.ERROR) #Reset the drag&drop data attributes self.drag_dropped_file = None self.drag_text = None def enterEvent(self, enter_event): """Event that fires when the focus shifts to the BasicWidget""" cw = self.currentWidget() if cw != None: #Check if the current widget is a custom editor or a QTextEdit widget if isinstance(cw, CustomEditor): #Get currently selected tab in the basic widget and display its name and lexer self._parent.display.write_to_statusbar(cw.name) else: #Display only the QTextEdit name self._parent.display.write_to_statusbar(cw.name) data.print_log("Entered BasicWidget: " + str(self.name)) def mousePressEvent(self, event): super().mousePressEvent(event) # Set focus to the clicked basic widget self.setFocus() # Set Save/SaveAs buttons in the menubar self._set_save_status() # Store the last focused widget to the parent self._parent.last_focused_widget = self data.print_log("Stored \"{}\" as last focused widget".format( self.name)) # Hide the function wheel if it is shown self._parent.view.hide_all_overlay_widgets() if (event.button() == data.Qt.RightButton and self.count() == 0): # Show the function wheel if right clicked self._parent.view.show_function_wheel() # Display the tab name in the log window if self.currentWidget() != None: tab_name = self.currentWidget().name data.print_log("Mouse click in: \"" + str(tab_name) + "\"") else: # Clear the cursor positions in the statusbar self._parent.display.update_cursor_position() data.print_log("Mouse click in: \"" + self.name + "\"") # Reset the click&drag context menu action components.ActionFilter.clear_action() def wheelEvent(self, wheel_event): """QScintilla mouse wheel rotate event""" key_modifiers = data.QApplication.keyboardModifiers() if data.PYQT_MODE == 4: delta = wheel_event.delta() else: delta = wheel_event.angleDelta().y() if delta < 0: data.print_log("Mouse rotate down event") if key_modifiers == data.Qt.ControlModifier: #Zoom out the scintilla tab view self.zoom_out() else: data.print_log("Mouse rotate up event") if key_modifiers == data.Qt.ControlModifier: #Zoom in the scintilla tab view self.zoom_in() #Handle the event if key_modifiers == data.Qt.ControlModifier: #Accept the event, the event will not be propageted(sent forward) to the parent wheel_event.accept() else: #Propagate(send forward) the wheel event to the parent wheel_event.ignore() def resizeEvent(self, event): """Resize basic widget event""" #First execute the superclass resize event function super().resizeEvent(event) #Save the size relations between basic widgets self._parent.view.save_layout() event.setAccepted(False) def setFocus(self): """Overridden focus event""" #Execute the supeclass focus function super().setFocus() #Check indication self._parent.view.indication_check() def _signal_editor_tabindex_change(self, change_event): """Signal when the tab index changes""" # Set Save/SaveAs buttons in the menubar self._set_save_status() # Check if there is a tab in the tab widget current_tab = self.currentWidget() if current_tab: data.print_log("Selected tab: " + str(self.currentWidget().name)) # Update the icons of the tabs for i in range(self.count()): self.update_tab_icon(self.widget(i)) # Update the corner widgets if current_tab != None and hasattr(current_tab, "icon_manipulator"): if current_tab.icon_manipulator.update_corner_widget( current_tab) == False: # Remove the corner widget if the current widget is of an unknown type self.setCornerWidget(None) else: # Remove the corner widget if there is no current tab active self.setCornerWidget(None) def _signal_editor_tabclose(self, emmited_tab_number, force=False): """Event that fires when a tab close""" #Nested function for clearing all bookmarks in the document def clear_document_bookmarks(): #Check if bookmarks need to be cleared if isinstance(self.widget(emmited_tab_number), CustomEditor): self._parent.bookmarks.remove_editor_all( self.widget(emmited_tab_number)) data.print_log("Closing tab: " + str(self.tabText(emmited_tab_number))) # Store the tab reference tab = self.widget(emmited_tab_number) #Check if the document is modified if tab.savable == data.CanSave.YES: if tab.save_status == data.FileStatus.MODIFIED and force == False: #Close the log window if it is displayed self._parent.view.set_log_window(False) #Display the close notification close_message = "Document '" + self.tabText(emmited_tab_number) close_message += "' has been modified!\nClose it anyway?" reply = YesNoDialog.question(close_message) if reply == data.QMessageBox.Yes: clear_document_bookmarks() #Close tab anyway self.removeTab(emmited_tab_number) else: #Cancel tab closing return else: clear_document_bookmarks() #The document is unmodified self.removeTab(emmited_tab_number) else: clear_document_bookmarks() #The document cannot be saved, close it self.removeTab(emmited_tab_number) # Delete the tab from memory if hasattr(tab, "clean_up"): tab.clean_up() # Just in case, decrement the refcount of the tab (that's what del does) del tab def _signal_editor_cursor_change(self, cursor_line=None, cursor_column=None): """Signal that fires when cursor position changes""" self._parent.display.update_cursor_position(cursor_line, cursor_column) def _set_save_status(self): """Enable/disable save/saveas buttons in the menubar""" cw = self.currentWidget() if cw != None: #Check if the current widget is a custom editor or a QTextEdit widget if isinstance(cw, CustomEditor): #Get currently selected tab in the basic widget and display its name and lexer self._parent.display.write_to_statusbar(cw.name) else: #Display only the QTextEdit name self._parent.display.write_to_statusbar(cw.name) #Set the Save/SaveAs status of the menubar if cw.savable == data.CanSave.YES: self._parent.set_save_file_state(True) else: self._parent.set_save_file_state(False) if self.count() == 0: self._parent.set_save_file_state(False) def _signal_text_changed(self): """Signal that is emmited when the document text changes""" #Check if the current widget is valid if self.currentWidget() == None: return #Update the save status of the current widget if self.currentWidget().savable == data.CanSave.YES: #Set document as modified self.currentWidget().save_status = data.FileStatus.MODIFIED #Check if special character is already in the name of the tab if not "*" in self.tabText(self.currentIndex()): #Add the special character to the tab name self.setTabText(self.currentIndex(), "*" + self.tabText(self.currentIndex()) + "*") #Update margin width self.editor_update_margin() def _set_wait_animation(self, index, show): tabBar = self.tabBar() if show: lbl = data.QLabel(self) movie = data.QMovie(os.path.join(data.resources_directory, "animations/wait.gif"), parent=lbl) movie.setCacheMode(data.QMovie.CacheAll) if data.custom_menu_scale != None: size = tuple([(x * data.custom_menu_scale / 16) for x in (16, 16)]) else: size = (16, 16) movie.setScaledSize(data.QSize(*size)) lbl.setMovie(movie) movie.start() tabBar.setTabButton(index, data.QTabBar.LeftSide, lbl) else: tabBar.setTabButton(index, data.QTabBar.LeftSide, None) def reset_text_changed(self, index=None): """Reset the changed status of the current widget (remove the * symbols from the tab name)""" #Update the save status of the current widget if index == None: if self.currentWidget().savable == data.CanSave.YES: self.currentWidget().save_status = data.FileStatus.OK self.setTabText(self.currentIndex(), self.tabText(self.currentIndex()).strip("*")) else: if self.widget(index).savable == data.CanSave.YES: self.widget(index).save_status = data.FileStatus.OK self.setTabText(index, self.tabText(index).strip("*")) def close_tab(self, tab=None, force=False): """Close a tab in the basic widget""" # Return if there are no tabs open if self.count == 0: return # First check if a tab name was given if isinstance(tab, str): for i in range(0, self.count()): if self.tabText(i) == tab: # Tab found, close it self._signal_editor_tabclose(i, force) break elif isinstance(tab, int): # Close the tab self._signal_editor_tabclose(tab, force) elif tab == None: # No tab number given, select the current tab for closing self._signal_editor_tabclose(self.currentIndex(), force) else: for i in range(0, self.count()): # Close tab by reference if self.widget(i) == tab: # Tab found, close it self._signal_editor_tabclose(i, force) break def zoom_in(self): """Zoom in view function (it is the same for the CustomEditor and QTextEdit)""" #Zoom in try: self.currentWidget().zoomIn() except: pass #Update the margin width self.editor_update_margin() def zoom_out(self): """Zoom out view function (it is the same for the CustomEditor and QTextEdit)""" #Zoom out try: self.currentWidget().zoomOut() except: pass #Update the margin width self.editor_update_margin() def zoom_reset(self): """Reset the zoom to default""" #Check is the widget is a scintilla custom editor if isinstance(self.currentWidget(), CustomEditor): #Reset zoom self.currentWidget().zoomTo(0) #Update the margin width self.editor_update_margin() elif isinstance(self.currentWidget(), data.QTextEdit): #Reset zoom self.currentWidget().setFont(self.default_editor_font) def plain_create_document(self, name): """Create a plain vanilla scintilla document""" #Initialize the custom editor new_scintilla_tab = PlainEditor(self, self._parent) new_scintilla_tab.setFont(self.default_editor_font) #Add attributes for status of the document (!!you can add attributes to objects that have the __dict__ attribute!!) new_scintilla_tab.name = name #Initialize the scrollbars new_scintilla_tab.SendScintilla( data.QsciScintillaBase.SCI_SETVSCROLLBAR, True) new_scintilla_tab.SendScintilla( data.QsciScintillaBase.SCI_SETHSCROLLBAR, True) #Hide the margin new_scintilla_tab.setMarginWidth(1, 0) #Disable drops new_scintilla_tab.setAcceptDrops(False) #Add needed signals new_scintilla_tab.cursorPositionChanged.connect( self._signal_editor_cursor_change) #Customize the mouse click event for the plain document with a decorator def custom_mouse_click(function_to_decorate): def decorated_function(*args, **kwargs): function_to_decorate(*args, **kwargs) #Set Save/SaveAs buttons in the menubar self._set_save_status() return decorated_function #Add the custom click decorator to the mouse click function new_scintilla_tab.mousePressEvent = custom_mouse_click( new_scintilla_tab.mousePressEvent) #Return the scintilla reference return new_scintilla_tab def plain_add_document(self, document_name): """Add a plain scintilla document to self(QTabWidget)""" #Create new scintilla object new_editor_tab = self.plain_create_document(document_name) #Add the scintilla document to the tab widget new_editor_tab_index = self.addTab(new_editor_tab, document_name) #Make new tab visible self.setCurrentIndex(new_editor_tab_index) data.print_log("Added new empty tab: " + document_name) #Return the reference to the new added scintilla tab widget return self.widget(new_editor_tab_index) def editor_create_document(self, file_with_path=None): """Create and initialize a custom scintilla document""" #Initialize the custom editor new_scintilla_tab = CustomEditor(self, self._parent, file_with_path) #Connect the signals new_scintilla_tab.textChanged.connect(new_scintilla_tab.text_changed) return new_scintilla_tab def editor_add_document(self, document_name, type=None, bypass_check=False): """Check tab type and add a document to self(QTabWidget)""" if type == "file": ## New tab is a file on disk file_type = "unknown" if bypass_check == False: file_type = functions.get_file_type(document_name) if file_type != "unknown" or bypass_check == True: # Test if file can be read if functions.test_text_file(document_name) == None: self._parent.display.repl_display_message( "Testing for TEXT file failed!", message_type=data.MessageType.ERROR) # File cannot be read return None # Create new scintilla document new_editor_tab = self.editor_create_document(document_name) # Set the lexer that colour codes the document new_editor_tab.choose_lexer(file_type) # Add the scintilla document to the tab widget new_editor_tab_index = self.addTab( new_editor_tab, os.path.basename(document_name)) # Make the new tab visible self.setCurrentIndex(new_editor_tab_index) data.print_log("Added file: " + document_name) # Return the reference to the new added scintilla tab widget return self.widget(new_editor_tab_index) else: data.print_log( "!! Document is not a text file or has an unsupported format" ) self._parent.display.write_to_statusbar( "Document is not a text file, doesn't exist or has an unsupported format!", 1500) return None else: ## New tab is an empty tab # Create new scintilla object new_editor_tab = self.editor_create_document(document_name) # Add the scintilla document to the tab widget new_editor_tab_index = self.addTab(new_editor_tab, document_name) # Make new tab visible self.setCurrentIndex(new_editor_tab_index) data.print_log("Added new empty tab: " + document_name) # Return the reference to the new added scintilla tab widget return self.widget(new_editor_tab_index) def tree_create_tab(self, tree_tab_name, tree_type=None): """Create and initialize a tree display widget""" # Initialize the custom editor if tree_type != None: new_tree_tab = tree_type(self, self._parent) else: new_tree_tab = TreeDisplay(self, self._parent) # Add attributes for status of the document new_tree_tab.name = tree_tab_name new_tree_tab.savable = data.CanSave.NO # Return the reference to the new added tree tab widget return new_tree_tab def tree_add_tab(self, tree_tab_name, tree_type=None): """Create and initialize a tree display widget""" # Initialize the custom editor new_tree_tab = self.tree_create_tab(tree_tab_name, tree_type) # Add the tree tab to the tab widget new_tree_tab_index = self.addTab(new_tree_tab, tree_tab_name) # Return the reference to the new added tree tab widget return self.widget(new_tree_tab_index) def editor_update_margin(self): """Update margin width according to the number of lines in the current document""" #Check is the widget is a scintilla custom editor if isinstance(self.currentWidget(), CustomEditor): self.currentWidget().update_margin() def set_tab_name(self, tab, new_text): """Set the name of a tab by passing a reference to it""" #Cycle through all of the tabs for i in range(self.count()): if self.widget(i) == tab: #Set the new text of the tab self.setTabText(i, new_text) break def select_tab(self, direction=data.Direction.RIGHT): """ Select tab left/right of the currently selected tab """ current_index = self.currentIndex() if direction == data.Direction.RIGHT: # Check if the widget is already at the far right if current_index < self.tabBar().count() - 1: new_index = current_index + 1 self.setCurrentIndex(new_index) else: # Check if the widget is already at the far left if current_index > 0: new_index = current_index - 1 self.setCurrentIndex(new_index) def move_tab(self, direction=data.Direction.RIGHT): """ Change the position of the current tab in the basic widget, according to the selected direction """ #Store the current index and widget current_index = self.currentIndex() #Check the move direction if direction == data.Direction.RIGHT: #Check if the widget is already at the far right if current_index < self.tabBar().count() - 1: new_index = current_index + 1 self.tabBar().moveTab(current_index, new_index) #This hack is needed to correctly focus the moved tab self.setCurrentIndex(current_index) self.setCurrentIndex(new_index) else: #Check if the widget is already at the far left if current_index > 0: new_index = current_index - 1 self.tabBar().moveTab(current_index, new_index) #This hack is needed to correctly focus the moved tab self.setCurrentIndex(current_index) self.setCurrentIndex(new_index) def update_tab_icon(self, tab): if (hasattr(tab, "current_icon") == True) and (tab.current_icon != None): self.setTabIcon(self.indexOf(tab), tab.current_icon) def copy_editor_in(self, source_basic_widget, source_index, focus_name=None): """Copy another CustomEditor widget into self""" #Create a new reference to the source custom editor source_widget = source_basic_widget.widget(source_index) #Check if the source tab is valid if source_widget == None: return #PlainEditor tabs should not be copied if isinstance(source_widget, CustomEditor) == False: self._parent.display.repl_display_message( "Only custom editor tabs can be copied!", message_type=data.MessageType.ERROR) return #Check if the source file already exists in the target basic widget check_index = self._parent.check_open_file(source_widget.save_name, self) if check_index != None: #File is already open, focus it self.setCurrentIndex(check_index) return #Create a new editor document new_widget = self.editor_create_document(source_widget.save_name) #Add the copied custom editor to the target basic widget new_index = self.addTab(new_widget, source_basic_widget.tabIcon(source_index), source_basic_widget.tabText(source_index)) #Set focus to the copied widget self.setCurrentIndex(new_index) #Copy the source editor text and set the lexer accordigly source_widget.copy_self(new_widget) #Also reset the text change self.reset_text_changed(new_index) #Set Focus to the copied widget parent if focus_name == None: self._parent.view.set_window_focus(self.drag_source.name) else: self._parent.view.set_window_focus(focus_name) #Update the margin in the copied widget self.editor_update_margin() def move_editor_in(self, source_basic_widget, source_index): """Move another CustomEditor widget into self without copying it""" moved_widget = source_basic_widget.widget(source_index) moved_widget_icon = source_basic_widget.tabIcon(source_index) moved_widget_text = source_basic_widget.tabText(source_index) # Check if the source tab is valid if moved_widget == None: return # PlainEditor tabs should not evaluate its name if isinstance(moved_widget, CustomEditor) == True: # Check if the source file already exists in the target basic widget check_index = self._parent.check_open_file(moved_widget.save_name, self) if check_index != None: # File is already open, focus it self.setCurrentIndex(check_index) return # Move the custom editor widget from source to target new_index = self.addTab(moved_widget, moved_widget_icon, moved_widget_text) # Set focus to the copied widget self.setCurrentIndex(new_index) # Change the custom editor parent self.widget(new_index)._parent = self self.widget(new_index).icon_manipulator.update_basic_widget(self) # Set Focus to the copied widget parent self._parent.view.set_window_focus(source_basic_widget.name) # Update corner widget """ This has to be done multiple times! Don't know why yet, maybe the PyQt parent transfer happens in the background??? """ for i in range(2): self._signal_editor_tabindex_change(None) source_basic_widget._signal_editor_tabindex_change(None)
def __init__(self, parent, main_form, input_pixmap, input_function=None, input_function_text="", input_font=data.QFont( 'Courier', 14, weight=data.QFont.Bold ), input_focus_last_widget=data.HexButtonFocus.NONE, input_no_tab_focus_disable=False, input_no_document_focus_disable=True, input_check_last_tab_type=False, input_check_text_differ=False, input_tool_tip=None, input_scale=(1, 1)): #Initialize superclass super().__init__(parent) #Store the reference to the parent self._parent = parent #Store the reference to the main form self.main_form = main_form #Store the reference to the group box that holds the parent #(the parent of the parent) self.group_box_parent = parent.parent #Store the main function image self.stored_pixmap = input_pixmap #Store the hex edge image self.stored_hex = data.QPixmap( os.path.join( data.resources_directory, "various/hex-button-edge.png" ) ) #Store the function that will be executed on the click event self.function = input_function #Store the function text self.function_text = input_function_text #Set the attribute that will be check if focus update is needed #when the custom button executes its function self.focus_last_widget = input_focus_last_widget #Set the attribute that checks if any tab is focused self.no_tab_focus_disable = input_no_tab_focus_disable #Set the attribute that will be check if button will be disabled #if there is no focused document tab self.no_document_focus_disable = input_no_document_focus_disable #Set checking the save state stored in the main form self.check_last_tab_type = input_check_last_tab_type #Set checking for if the focused tab is a text differer self.check_text_differ = input_check_text_differ #Set the font that will be used with the button self.stored_font = input_font #Enable mouse move events self.setMouseTracking(True) #Set the image for displaying # self.setPixmap(input_pixmap) # #Image should scale to the button size # self.setScaledContents(True) # # Set the button mask, which sets the button area to the shape of # # the button image instead of a rectangle # self.setMask(self.stored_hex.mask()) # #Set the initial opacity to low # self._set_opacity_with_hex_edge(self.OPACITY_LOW) #Set the tooltip if it was set if input_tool_tip != None: self.setToolTip(input_tool_tip) # Set the scaling self.scale = input_scale
class TextDiffer(data.QWidget): """A widget that holds two PlainEditors for displaying text difference""" #Class variables _parent = None main_form = None name = "" savable = data.CanSave.NO current_icon = None icon_manipulator = None focused_editor = None text_1 = None text_2 = None text_1_name = None text_2_name = None #Class constants DEFAULT_FONT = data.QFont(data.current_font_name, data.current_font_size) MARGIN_STYLE = data.QsciScintilla.STYLE_LINENUMBER INDICATOR_UNIQUE_1 = 1 Indicator_Unique_1_Color = data.QColor(0x72, 0x9f, 0xcf, 80) INDICATOR_UNIQUE_2 = 2 Indicator_Unique_2_Color = data.QColor(0xad, 0x7f, 0xa8, 80) INDICATOR_SIMILAR = 3 Indicator_Similar_Color = data.QColor(0x8a, 0xe2, 0x34, 80) GET_X_OFFSET = data.QsciScintillaBase.SCI_GETXOFFSET SET_X_OFFSET = data.QsciScintillaBase.SCI_SETXOFFSET UPDATE_H_SCROLL = data.QsciScintillaBase.SC_UPDATE_H_SCROLL UPDATE_V_SCROLL = data.QsciScintillaBase.SC_UPDATE_V_SCROLL #Diff icons icon_unique_1 = None icon_unique_2 = None icon_similar = None #Marker references marker_unique_1 = None marker_unique_2 = None marker_unique_symbol_1 = None marker_unique_symbol_2 = None marker_similar_1 = None marker_similar_2 = None marker_similar_symbol_1 = None marker_similar_symbol_2 = None #Child widgets splitter = None editor_1 = None editor_2 = None layout = None def clean_up(self): self.editor_1.mousePressEvent = None self.editor_1.wheelEvent = None self.editor_2.mousePressEvent = None self.editor_2.wheelEvent = None self.editor_1.actual_parent = None self.editor_2.actual_parent = None self.editor_1.clean_up() self.editor_2.clean_up() self.editor_1 = None self.editor_2 = None self.focused_editor = None self.splitter.setParent(None) self.splitter = None self.layout = None self._parent = None self.main_form = None self.icon_manipulator = None # Clean up self self.setParent(None) self.deleteLater() """ The actual clean up will occur when the next garbage collection cycle is executed, probably because of the nested functions and the focus decorator. """ def __init__(self, parent, main_form, text_1=None, text_2=None, text_1_name="", text_2_name=""): """Initialization""" # Initialize the superclass super().__init__(parent) # Initialize components self.icon_manipulator = components.IconManipulator(self, parent) # Initialize colors according to theme self.Indicator_Unique_1_Color = data.theme.TextDifferColors.Indicator_Unique_1_Color self.Indicator_Unique_2_Color = data.theme.TextDifferColors.Indicator_Unique_2_Color self.Indicator_Similar_Color = data.theme.TextDifferColors.Indicator_Similar_Color # Store the reference to the parent self._parent = parent # Store the reference to the main form self.main_form = main_form # Set the differ icon self.current_icon = functions.create_icon( 'tango_icons/compare-text.png') #Set the name of the differ widget if text_1_name != None and text_2_name != None: self.name = "Text difference: {:s} / {:s}".format( text_1_name, text_2_name) self.text_1_name = text_1_name self.text_2_name = text_2_name else: self.name = "Text difference" self.text_1_name = "TEXT 1" self.text_2_name = "TEXT 2" # Initialize diff icons self.icon_unique_1 = functions.create_icon( "tango_icons/diff-unique-1.png") self.icon_unique_2 = functions.create_icon( "tango_icons/diff-unique-2.png") self.icon_similar = functions.create_icon( "tango_icons/diff-similar.png") # Create the horizontal splitter and two editor widgets self.splitter = data.QSplitter(data.Qt.Horizontal, self) self.editor_1 = CustomEditor(self, main_form) self.init_editor(self.editor_1) self.editor_2 = CustomEditor(self, main_form) self.init_editor(self.editor_2) self.editor_1.choose_lexer("text") self.editor_2.choose_lexer("text") self.splitter.addWidget(self.editor_1) self.splitter.addWidget(self.editor_2) self.layout = data.QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.splitter) # Set the layout self.setLayout(self.layout) # Connect the necessary signals self.editor_1.SCN_UPDATEUI.connect(self._scn_updateui_1) self.editor_2.SCN_UPDATEUI.connect(self._scn_updateui_2) self.editor_1.cursorPositionChanged.connect(self._cursor_change_1) self.editor_2.cursorPositionChanged.connect(self._cursor_change_2) # Overwrite the CustomEditor parent widgets to point to the TextDiffers' PARENT self.editor_1._parent = self._parent self.editor_2._parent = self._parent # Add a new attribute to the CustomEditor that will hold the TextDiffer reference self.editor_1.actual_parent = self self.editor_2.actual_parent = self # Set the embedded flag self.editor_1.embedded = True self.editor_2.embedded = True # Add decorators to each editors mouse clicks and mouse wheel scrolls def focus_decorator(function_to_decorate, focused_editor): def decorated_function(*args, **kwargs): self.focused_editor = focused_editor function_to_decorate(*args, **kwargs) return decorated_function self.editor_1.mousePressEvent = focus_decorator( self.editor_1.mousePressEvent, self.editor_1) self.editor_1.wheelEvent = focus_decorator(self.editor_1.wheelEvent, self.editor_1) self.editor_2.mousePressEvent = focus_decorator( self.editor_2.mousePressEvent, self.editor_2) self.editor_2.wheelEvent = focus_decorator(self.editor_2.wheelEvent, self.editor_2) # Add corner buttons self.add_corner_buttons() # Focus the first editor on initialization self.focused_editor = self.editor_1 self.focused_editor.setFocus() # Initialize markers self.init_markers() # Set the theme self.set_theme(data.theme) # Set editor functions that have to be propagated from the TextDiffer # to the child editor self._init_editor_functions() # Check the text validity if text_1 == None or text_2 == None: #One of the texts is unspecified return # Create the diff self.compare(text_1, text_2) def _scn_updateui_1(self, sc_update): """Function connected to the SCN_UPDATEUI signal for scroll detection""" if self.focused_editor == self.editor_1: #Scroll the opposite editor if sc_update == self.UPDATE_H_SCROLL: current_x_offset = self.editor_1.SendScintilla( self.GET_X_OFFSET) self.editor_2.SendScintilla(self.SET_X_OFFSET, current_x_offset) elif sc_update == self.UPDATE_V_SCROLL: current_top_line = self.editor_1.firstVisibleLine() self.editor_2.setFirstVisibleLine(current_top_line) def _scn_updateui_2(self, sc_update): """Function connected to the SCN_UPDATEUI signal for scroll detection""" if self.focused_editor == self.editor_2: #Scroll the opposite editor if sc_update == self.UPDATE_H_SCROLL: current_x_offset = self.editor_2.SendScintilla( self.GET_X_OFFSET) self.editor_1.SendScintilla(self.SET_X_OFFSET, current_x_offset) elif sc_update == self.UPDATE_V_SCROLL: current_top_line = self.editor_2.firstVisibleLine() self.editor_1.setFirstVisibleLine(current_top_line) def _cursor_change_1(self, line, index): """ Function connected to the cursorPositionChanged signal for cursor position change detection """ if self.focused_editor == self.editor_1: #Update the cursor position on the opposite editor cursor_line, cursor_index = self.editor_1.getCursorPosition() #Check if the opposite editor line is long enough if self.editor_2.lineLength(cursor_line) > cursor_index: self.editor_2.setCursorPosition(cursor_line, cursor_index) else: self.editor_2.setCursorPosition(cursor_line, 0) #Update the first visible line, so that the views in both differs match current_top_line = self.editor_1.firstVisibleLine() self.editor_2.setFirstVisibleLine(current_top_line) def _cursor_change_2(self, line, index): """ Function connected to the cursorPositionChanged signal for cursor position change detection """ if self.focused_editor == self.editor_2: #Update the cursor position on the opposite editor cursor_line, cursor_index = self.editor_2.getCursorPosition() #Check if the opposite editor line is long enough if self.editor_1.lineLength(cursor_line) > cursor_index: self.editor_1.setCursorPosition(cursor_line, cursor_index) else: self.editor_1.setCursorPosition(cursor_line, 0) #Update the first visible line, so that the views in both differs match current_top_line = self.editor_2.firstVisibleLine() self.editor_1.setFirstVisibleLine(current_top_line) def _update_margins(self): """Update the text margin width""" self.editor_1.setMarginWidth(0, "0" * len(str(self.editor_1.lines()))) self.editor_2.setMarginWidth(0, "0" * len(str(self.editor_2.lines()))) def _signal_editor_cursor_change(self, cursor_line=None, cursor_column=None): """Signal that fires when cursor position changes in one of the editors""" self.main_form.display.update_cursor_position(cursor_line, cursor_column) def _init_editor_functions(self): """ Initialize the editor functions that are called on the TextDiffer widget, but need to be executed on one of the editors """ #Find text function propagated to the focused editor def enabled_function(*args, **kwargs): #Get the function function = getattr(self.focused_editor, args[0]) #Call the function˘, leaving out the "function name" argument function(*args[1:], **kwargs) #Unimplemented functions def uniplemented_function(*args, **kwargs): self.main_form.display.repl_display_message( "Function '{:s}' is not implemented by the TextDiffer!".format( args[0]), message_type=data.MessageType.ERROR) all_editor_functions = inspect.getmembers(CustomEditor, predicate=inspect.isfunction) skip_functions = [ "set_theme", "clean_up", ] enabled_functions = [ "find_text", ] disabled_functions = [ "__init__", "__setattr__", "_filter_keypress", "_filter_keyrelease", "_init_special_functions", "_set_indicator", "find_text", "keyPressEvent", "keyReleaseEvent", "mousePressEvent", "setFocus", "wheelEvent", ] #Check methods for function in all_editor_functions: if function[0] in skip_functions: #Use the TextDiffer implementation of this function continue if function[0] in enabled_functions: #Find text is enabled setattr(self, function[0], functools.partial(enabled_function, function[0])) elif function[0] in disabled_functions: #Disabled functions should be skipped, they are probably already #implemented by the TextDiffer continue else: #Unimplemented functions should display an error message setattr(self, function[0], functools.partial(uniplemented_function, function[0])) def mousePressEvent(self, event): """Overloaded mouse click event""" #Execute the superclass mouse click event super().mousePressEvent(event) #Set focus to the clicked editor self.setFocus() #Set the last focused widget to the parent basic widget self.main_form.last_focused_widget = self._parent data.print_log("Stored \"{:s}\" as last focused widget".format( self._parent.name)) #Hide the function wheel if it is shown self.main_form.view.hide_all_overlay_widgets() # Reset the click&drag context menu action components.ActionFilter.clear_action() def setFocus(self): """Overridden focus event""" #Execute the superclass focus function super().setFocus() #Check indication self.main_form.view.indication_check() #Focus the last focused editor self.focused_editor.setFocus() def init_margin(self, editor, marker_unique, marker_unique_symbol, marker_similar, marker_similar_symbol): """Initialize margin for coloring lines showing diff symbols""" editor.setMarginWidth(0, "0") #Setting the margin width to 0 makes the marker colour the entire line #to the marker background color editor.setMarginWidth(1, "00") editor.setMarginWidth(2, 0) editor.setMarginType(0, data.QsciScintilla.TextMargin) editor.setMarginType(1, data.QsciScintilla.SymbolMargin) editor.setMarginType(2, data.QsciScintilla.SymbolMargin) #I DON'T KNOW THE ENTIRE LOGIC BEHIND MARKERS AND MARGINS! If you set #something wrong in the margin mask, the markers on a different margin don't appear! #http://www.scintilla.org/ScintillaDoc.html#SCI_SETMARGINMASKN editor.setMarginMarkerMask(1, ~data.QsciScintillaBase.SC_MASK_FOLDERS) editor.setMarginMarkerMask(2, 0x0) def init_markers(self): """Initialize all markers for showing diff symbols""" #Set the images image_scale_size = functions.create_size(16, 16) image_unique_1 = functions.create_pixmap( 'tango_icons/diff-unique-1.png') image_unique_2 = functions.create_pixmap( 'tango_icons/diff-unique-2.png') image_similar = functions.create_pixmap('tango_icons/diff-similar.png') #Scale the images to a smaller size image_unique_1 = image_unique_1.scaled(image_scale_size) image_unique_2 = image_unique_2.scaled(image_scale_size) image_similar = image_similar.scaled(image_scale_size) #Markers for editor 1 self.marker_unique_1 = self.editor_1.markerDefine( data.QsciScintillaBase.SC_MARK_BACKGROUND, 0) self.marker_unique_symbol_1 = self.editor_1.markerDefine( image_unique_1, 1) self.marker_similar_1 = self.editor_1.markerDefine( data.QsciScintillaBase.SC_MARK_BACKGROUND, 2) self.marker_similar_symbol_1 = self.editor_1.markerDefine( image_similar, 3) #Set background colors only for the background markers self.editor_1.setMarkerBackgroundColor(self.Indicator_Unique_1_Color, self.marker_unique_1) self.editor_1.setMarkerBackgroundColor(self.Indicator_Similar_Color, self.marker_similar_1) #Margins for editor 1 self.init_margin(self.editor_1, self.marker_unique_1, self.marker_unique_symbol_1, self.marker_similar_1, self.marker_similar_symbol_1) #Markers for editor 2 self.marker_unique_2 = self.editor_2.markerDefine( data.QsciScintillaBase.SC_MARK_BACKGROUND, 0) self.marker_unique_symbol_2 = self.editor_2.markerDefine( image_unique_2, 1) self.marker_similar_2 = self.editor_2.markerDefine( data.QsciScintillaBase.SC_MARK_BACKGROUND, 2) self.marker_similar_symbol_2 = self.editor_2.markerDefine( image_similar, 3) #Set background colors only for the background markers self.editor_2.setMarkerBackgroundColor(self.Indicator_Unique_2_Color, self.marker_unique_2) self.editor_2.setMarkerBackgroundColor(self.Indicator_Similar_Color, self.marker_similar_2) #Margins for editor 2 self.init_margin(self.editor_2, self.marker_unique_2, self.marker_unique_symbol_2, self.marker_similar_2, self.marker_similar_symbol_2) def init_indicator(self, editor, indicator, color): """Set the indicator settings""" editor.indicatorDefine(data.QsciScintillaBase.INDIC_ROUNDBOX, indicator) editor.setIndicatorForegroundColor(color, indicator) editor.SendScintilla(data.QsciScintillaBase.SCI_SETINDICATORCURRENT, indicator) def init_editor(self, editor): """Initialize all of the PlainEditor settings for difference displaying""" editor.setLexer(None) editor.setUtf8(True) editor.setIndentationsUseTabs(False) editor.setFont(self.DEFAULT_FONT) editor.setBraceMatching(data.QsciScintilla.SloppyBraceMatch) editor.setMatchedBraceBackgroundColor(data.QColor(255, 153, 0)) editor.setAcceptDrops(False) editor.setEolMode(settings.Editor.end_of_line_mode) editor.setReadOnly(True) editor.savable = data.CanSave.NO def set_margin_text(self, editor, line, text): """Set the editor's margin text at the selected line""" editor.setMarginText(line, text, self.MARGIN_STYLE) def set_line_indicator(self, editor, line, indicator_index): """Set the editor's selected line color""" #Set the indicator if indicator_index == self.INDICATOR_UNIQUE_1: self.init_indicator(editor, self.INDICATOR_UNIQUE_1, self.Indicator_Unique_1_Color) elif indicator_index == self.INDICATOR_UNIQUE_2: self.init_indicator(editor, self.INDICATOR_UNIQUE_2, self.Indicator_Unique_2_Color) elif indicator_index == self.INDICATOR_SIMILAR: self.init_indicator(editor, self.INDICATOR_SIMILAR, self.Indicator_Similar_Color) #Color the line background scintilla_command = data.QsciScintillaBase.SCI_INDICATORFILLRANGE start = editor.positionFromLineIndex(line, 0) length = editor.lineLength(line) editor.SendScintilla(scintilla_command, start, length) def compare(self, text_1, text_2): """ Compare two text strings and display the difference !! This function uses Python's difflib which is not 100% accurate !! """ #Store the original text self.text_1 = text_1 self.text_2 = text_2 text_1_list = text_1.split("\n") text_2_list = text_2.split("\n") #Create the difference differer = difflib.Differ() list_sum = list(differer.compare(text_1_list, text_2_list)) #Assemble the two lists of strings that will be displayed in each editor list_1 = [] line_counter_1 = 1 line_numbering_1 = [] line_styling_1 = [] list_2 = [] line_counter_2 = 1 line_numbering_2 = [] line_styling_2 = [] #Flow control flags skip_next = False store_next = False for i, line in enumerate(list_sum): if store_next == True: store_next = False list_2.append(line[2:]) line_numbering_2.append(str(line_counter_2)) line_counter_2 += 1 line_styling_2.append(self.INDICATOR_SIMILAR) elif skip_next == False: if line.startswith(" "): #The line is the same in both texts list_1.append(line[2:]) line_numbering_1.append(str(line_counter_1)) line_counter_1 += 1 line_styling_1.append(None) list_2.append(line[2:]) line_numbering_2.append(str(line_counter_2)) line_counter_2 += 1 line_styling_2.append(None) elif line.startswith("- "): #The line is unique to text 1 list_1.append(line[2:]) line_numbering_1.append(str(line_counter_1)) line_counter_1 += 1 line_styling_1.append(self.INDICATOR_UNIQUE_1) list_2.append("") line_numbering_2.append("") line_styling_2.append(None) elif line.startswith("+ "): #The line is unique to text 2 list_1.append("") line_numbering_1.append("") line_styling_1.append(None) list_2.append(line[2:]) line_numbering_2.append(str(line_counter_2)) line_counter_2 += 1 line_styling_2.append(self.INDICATOR_UNIQUE_2) elif line.startswith("? "): #The line is similar if (list_sum[i - 1].startswith("- ") and len(list_sum) > (i + 1) and list_sum[i + 1].startswith("+ ") and len(list_sum) > (i + 2) and list_sum[i + 2].startswith("? ")): """ Line order: - ... ? ... + ... ? ... """ #Lines have only a few character difference, skip the #first '?' and handle the next '?' as a "'- '/'+ '/'? '" sequence pass elif list_sum[i - 1].startswith("- "): #Line in text 1 has something added """ Line order: - ... ? ... + ... """ line_styling_1[len(line_numbering_1) - 1] = self.INDICATOR_SIMILAR list_2.pop() line_numbering_2.pop() line_styling_2.pop() store_next = True elif list_sum[i - 1].startswith("+ "): #Line in text 2 has something added """ Line order: - ... + ... ? ... """ list_1.pop() line_numbering_1.pop() line_styling_1.pop() line_styling_1[len(line_numbering_1) - 1] = self.INDICATOR_SIMILAR pop_index_2 = (len(line_numbering_2) - 1) - 1 list_2.pop(pop_index_2) line_numbering_2.pop(pop_index_2) line_styling_2.pop() line_styling_2.pop() line_styling_2.append(self.INDICATOR_SIMILAR) else: skip_next = False #Display the results self.editor_1.setText("\n".join(list_1)) self.editor_2.setText("\n".join(list_2)) #Set margins and style for both editors for i, line in enumerate(line_numbering_1): self.set_margin_text(self.editor_1, i, line) line_styling = line_styling_1[i] if line_styling != None: if line_styling == self.INDICATOR_SIMILAR: self.editor_1.markerAdd(i, self.marker_similar_1) self.editor_1.markerAdd(i, self.marker_similar_symbol_1) else: self.editor_1.markerAdd(i, self.marker_unique_1) self.editor_1.markerAdd(i, self.marker_unique_symbol_1) for i, line in enumerate(line_numbering_2): self.set_margin_text(self.editor_2, i, line) line_styling = line_styling_2[i] if line_styling != None: if line_styling == self.INDICATOR_SIMILAR: self.editor_2.markerAdd(i, self.marker_similar_2) self.editor_2.markerAdd(i, self.marker_similar_symbol_2) else: self.editor_2.markerAdd(i, self.marker_unique_2) self.editor_2.markerAdd(i, self.marker_unique_symbol_2) #Check if there were any differences if (any(line_styling_1) == False and any(line_styling_2) == False): self.main_form.display.repl_display_message( "No differences between texts.", message_type=data.MessageType.SUCCESS) else: #Count the number of differences difference_counter_1 = 0 #Similar line count is the same in both editor line stylings similarity_counter = 0 for diff in line_styling_1: if diff != None: if diff == self.INDICATOR_SIMILAR: similarity_counter += 1 else: difference_counter_1 += 1 difference_counter_2 = 0 for diff in line_styling_2: if diff != None: if diff == self.INDICATOR_SIMILAR: #Skip the similar line, which were already counter above continue else: difference_counter_2 += 1 #Display the differences/similarities messages self.main_form.display.repl_display_message( "{:d} differences found in '{:s}'!".format( difference_counter_1, self.text_1_name), message_type=data.MessageType.DIFF_UNIQUE_1) self.main_form.display.repl_display_message( "{:d} differences found in '{:s}'!".format( difference_counter_2, self.text_2_name), message_type=data.MessageType.DIFF_UNIQUE_2) self.main_form.display.repl_display_message( "{:d} similarities found between documents!".format( similarity_counter, self.text_2_name), message_type=data.MessageType.DIFF_SIMILAR) self._update_margins() def find_next_unique_1(self): """Find and scroll to the first unique 1 difference""" self.focused_editor = self.editor_1 cursor_line, cursor_index = self.editor_1.getCursorPosition() next_unique_diff_line = self.editor_1.markerFindNext( cursor_line + 1, 0b0011) #Correct the line numbering to the 1..line_count display next_unique_diff_line += 1 self.editor_1.goto_line(next_unique_diff_line, skip_repl_focus=False) self.editor_2.goto_line(next_unique_diff_line, skip_repl_focus=False) #Check if we are back at the start of the document if next_unique_diff_line == 0: self.main_form.display.repl_display_message( "Scrolled back to the start of the document!", message_type=data.MessageType.DIFF_UNIQUE_1) self.main_form.display.write_to_statusbar( "Scrolled back to the start of the document!") def find_next_unique_2(self): """Find and scroll to the first unique 2 difference""" self.focused_editor = self.editor_2 cursor_line, cursor_index = self.editor_2.getCursorPosition() next_unique_diff_line = self.editor_2.markerFindNext( cursor_line + 1, 0b0011) #Correct the line numbering to the 1..line_count display next_unique_diff_line += 1 self.editor_1.goto_line(next_unique_diff_line, skip_repl_focus=False) self.editor_2.goto_line(next_unique_diff_line, skip_repl_focus=False) #Check if we are back at the start of the document if next_unique_diff_line == 0: self.main_form.display.repl_display_message( "Scrolled back to the start of the document!", message_type=data.MessageType.DIFF_UNIQUE_2) self.main_form.display.write_to_statusbar( "Scrolled back to the start of the document!") def find_next_similar(self): """Find and scroll to the first similar line""" self.focused_editor = self.editor_1 cursor_line, cursor_index = self.editor_1.getCursorPosition() next_unique_diff_line = self.editor_1.markerFindNext( cursor_line + 1, 0b1100) #Correct the line numbering to the 1..line_count display next_unique_diff_line += 1 self.editor_1.goto_line(next_unique_diff_line, skip_repl_focus=False) self.editor_2.goto_line(next_unique_diff_line, skip_repl_focus=False) #Check if we are back at the start of the document if next_unique_diff_line == 0: self.main_form.display.repl_display_message( "Scrolled back to the start of the document!", message_type=data.MessageType.DIFF_SIMILAR) self.main_form.display.write_to_statusbar( "Scrolled back to the start of the document!") def add_corner_buttons(self): # Unique 1 button self.icon_manipulator.add_corner_button( functions.create_icon("tango_icons/diff-unique-1.png"), "Scroll to next unique line\nin document: '{:s}'".format( self.text_1_name), self.find_next_unique_1) # Unique 2 button self.icon_manipulator.add_corner_button( functions.create_icon("tango_icons/diff-unique-2.png"), "Scroll to next unique line\nin document: '{:s}'".format( self.text_2_name), self.find_next_unique_2) # Similar button self.icon_manipulator.add_corner_button( functions.create_icon("tango_icons/diff-similar.png"), "Scroll to next similar line\nin both documents", self.find_next_similar) def set_theme(self, theme): def set_editor_theme(editor): if theme == themes.Air: editor.resetFoldMarginColors() elif theme == themes.Earth: editor.setFoldMarginColors(theme.FoldMargin.ForeGround, theme.FoldMargin.BackGround) editor.setMarginsForegroundColor(theme.LineMargin.ForeGround) editor.setMarginsBackgroundColor(theme.LineMargin.BackGround) editor.SendScintilla(data.QsciScintillaBase.SCI_STYLESETBACK, data.QsciScintillaBase.STYLE_DEFAULT, theme.Paper.Default) editor.SendScintilla(data.QsciScintillaBase.SCI_STYLESETBACK, data.QsciScintillaBase.STYLE_LINENUMBER, theme.LineMargin.BackGround) editor.SendScintilla(data.QsciScintillaBase.SCI_SETCARETFORE, theme.Cursor) editor.choose_lexer("text") set_editor_theme(self.editor_1) set_editor_theme(self.editor_2)
class RouterOS(data.QsciLexerCustom): """ Custom lexer for the RouterOS syntax for MikroTik routers (WinBox) """ styles = { "Default": 0, "Operator": 1, "Comment": 2, "Keyword1": 3, "Keyword2": 4, "Keyword3": 5, } #Class variables default_color = data.QColor(data.theme.Font.RouterOS.Default[1]) default_paper = data.QColor(data.theme.Paper.RouterOS.Default) default_font = data.QFont('Courier', 10) #All keywords, operators, ... operator_list = [ '!', '$', '(', ')', ',', ':', '[', ']', '{', '|', '}', "=" ] comment_list = ['#'] keyword1_list = [ 'ac-name', 'accept', 'accessible-via-web', 'account-local-traffic', 'accounting', 'action', 'active-flow-timeout', 'active-mode', 'add-default-route', 'address-list', 'address-pool', 'address', 'addresses-per-mac', 'admin-mac', 'advertise-dns', 'advertise-mac-address', 'ageing-time', 'allocate-udp-ports-from', 'allow-disable-external-interface', 'allow-guests', 'allow-remote-requests', 'allow', 'allowed-number', 'always-from-cache', 'area-id', 'area', 'arp', 'as', 'audio-max', 'audio-min', 'audio-monitor', 'auth-algorithms', 'auth-method', 'auth', 'authenticate', 'authentication-password', 'authentication-protocol', 'authentication-types', 'authentication', 'authoritative', 'auto-mac', 'auto-negotiation', 'auto-send-supout', 'automatic-supout', 'autonomous', 'backup-allowed', 'baud-rate', 'bidirectional-timeout', 'blank-interval', 'bootp-support', 'bridge-mode', 'bridge', 'broadcast-addresses', 'broadcast', 'bsd-syslog', 'cable-settings', 'cache-administrator', 'cache-entries', 'cache-hit-dscp', 'cache-max-ttl', 'cache-on-disk', 'cache-size', 'certificate', 'chain', 'change-tcp-mss', 'channel-time', 'channel', 'check-interval', 'cipher', 'client-to-client-reflection', 'comment', 'connection-bytes', 'connection-idle-timeout', 'connection-mark', 'connection-state', 'contact', 'contrast', 'cpu', 'data-bits', 'default-ap-tx-limit', 'default-authentication', 'default-client-tx-limit', 'default-forwarding', 'default-group', 'default-profile', 'default-route-distance', 'default', 'dh-group', 'dhcp-option', 'dial-on-demand', 'directory', 'disable-running-check', 'disabled', 'disk-file-count', 'disk-file-name', 'disk-lines-per-file', 'disk-stop-on-full', 'display-time', 'distance', 'distribute-default', 'distribute-for-default-route', 'dns-name', 'dns-server', 'domain', 'dpd-interval', 'dpd-maximum-failures', 'dst-address-list', 'dst-address', 'dst-delta', 'dst-end', 'dst-port', 'dst-start', 'dynamic-label-range', 'e', 'eap-methods', 'enabled', 'enc-algorithm', 'enc-algorithms', 'encryption-password', 'encryption-protocol', 'engine-id', 'exchange-mode', 'exclude-groups', 'file-limit', 'file-name', 'filter-ip-address', 'filter-ip-protocol', 'filter-mac-address', 'filter-mac-protocol', 'filter-mac', 'filter-port', 'filter-stream', 'flow-control', 'forward-delay', 'frame-size', 'frames-per-second', 'from', 'full-duplex', 'garbage-timer', 'gateway-class', 'gateway-keepalive', 'gateway-selection', 'gateway', 'generate-policy', 'generic-timeout', 'group-ciphers', 'group-key-update', 'hash-algorithm', 'hide-ssid', 'hop-limit', 'hotspot-address', 'html-directory', 'http-cookie-lifetime', 'http-proxy', 'i', 'icmp-timeout', 'idle-timeout', 'ignore-as-path-len', 'in-filter', 'in-interface', 'inactive-flow-timeout', 'instance', 'interface', 'interfaces', 'interim-update', 'interval', 'ipsec-protocols', 'jump-target', 'keep-max-sms', 'keepalive-timeout', 'kind', 'l2mtu', 'latency-distribution-scale', 'lease-time', 'level', 'lifebytes', 'lifetime', 'line-count', 'list', 'local-address', 'location', 'log-prefix', 'login-by', 'login', 'loop-detect', 'lsr-id', 'mac-address', 'managed-address-configuration', 'management-protection-key', 'management-protection', 'manycast', 'max-cache-size', 'max-client-connections', 'max-connections', 'max-fresh-time', 'max-message-age', 'max-mru', 'max-mtu', 'max-server-connections', 'max-sessions', 'max-station-count', 'max-udp-packet-size', 'memory-limit', 'memory-lines', 'memory-scroll', 'memory-stop-on-full', 'metric-bgp', 'metric-connected', 'metric-default', 'metric-ospf', 'metric-other-ospf', 'metric-rip', 'metric-static', 'min-rx', 'mode', 'mpls-mtu', 'mq-pfifo-limit', 'mrru', 'mtu', 'multi-cpu', 'multicast', 'multiple-channels', 'multiplier', 'my-id-user-fqdn', 'name', 'nat-traversal', 'netmask', 'network', 'new-connection-mark', 'new-packet-mark', 'new-routing-mark', 'no-ping-delay', 'note', 'ntp-server', 'on-backup', 'on-master', 'only-headers', 'only-one', 'origination-interval', 'other-configuration', 'out-filter', 'out-interface', 'page-refresh', 'parent-proxy-port', 'parent-proxy', 'parent', 'parity', 'passthrough', 'password', 'path-vector-limit', 'paypal-accept-pending', 'paypal-allowed', 'paypal-secure-response', 'permissions', 'pfifo-limit', 'pfs-group', 'policy', 'port', 'ports', 'preemption-mode', 'preferred-gateway', 'preferred-lifetime', 'prefix', 'primary-ntp', 'primary-server', 'priority', 'profile', 'propagate-ttl', 'proposal-check', 'proposal', 'proprietary-extensions', 'protocol-mode', 'protocol', 'query-interval', 'query-response-interval', 'queue', 'quick-leave', 'ra-delay', 'ra-interval', 'ra-lifetime', 'radius-eap-accounting', 'radius-mac-accounting', 'radius-mac-authentication', 'radius-mac-caching', 'radius-mac-format', 'radius-mac-mode', 'ranges', 'rate-limit', 'reachable-time', 'read-access', 'read-only', 'receive-all', 'receive-enabled', 'receive-errors', 'red-avg-packet', 'red-burst', 'red-limit', 'red-max-threshold', 'red-min-threshold', 'redistribute-bgp', 'redistribute-connected', 'redistribute-ospf', 'redistribute-other-bgp', 'redistribute-other-ospf', 'redistribute-rip', 'redistribute-static', 'remember', 'remote-address', 'remote-ipv6-prefix-pool', 'remote-port', 'remote', 'require-client-certificate', 'retransmit-interval', 'router-id', 'routing-mark', 'routing-table', 'sa-dst-address', 'sa-src-address', 'scope', 'secondary-ntp', 'secondary-server', 'secret', 'security-profile', 'security', 'send-initial-contact', 'serialize-connections', 'servers', 'service-name', 'set-system-time', 'sfq-allot', 'sfq-perturb', 'shared-users', 'show-at-login', 'show-dummy-rule', 'signup-allowed', 'sip-direct-media', 'skin', 'smtp-server', 'source', 'speed', 'split-user-domain', 'src-address-list', 'src-address', 'src-port', 'ssid-all', 'ssid', 'state-after-reboot', 'static-algo-0', 'static-algo-1', 'static-algo-2', 'static-algo-3', 'static-key-0', 'static-key-1', 'static-key-2', 'static-key-3', 'static-sta-private-algo', 'static-sta-private-key', 'static-transmit-key', 'status-autorefresh', 'stop-bits', 'store-every', 'store-leases-disk', 'streaming-enabled', 'streaming-max-rate', 'streaming-server', 'supplicant-identity', 'switch-to-spt-bytes', 'switch-to-spt-interval', 'switch-to-spt', 'syslog-facility', 'syslog-severity', 'target-scope', 'target', 'tcp-close-timeout', 'tcp-close-wait-timeout', 'tcp-established-timeout', 'tcp-fin-wait-timeout', 'tcp-last-ack-timeout', 'tcp-syn-received-timeout', 'tcp-syn-sent-timeout', 'tcp-syncookie', 'tcp-time-wait-timeout', 'term', 'test-id', 'threshold', 'time-zone-name', 'time-zone', 'timeout-timer', 'timeout', 'tls-certificate', 'tls-mode', 'to-addresses', 'topics', 'transmit-hold-count', 'transparent-proxy', 'transport-address', 'trap-generators', 'trap-target', 'trap-version', 'ttl', 'tunnel', 'type', 'udp-stream-timeout', 'udp-timeout', 'unicast-ciphers', 'update-stats-interval', 'update-timer', 'use-compression', 'use-encryption', 'use-explicit-null', 'use-ip-firewall-for-pppoe', 'use-ip-firewall-for-vlan', 'use-ip-firewall', 'use-ipv6', 'use-mpls', 'use-peer-dns', 'use-peer-ntp', 'use-radius', 'use-service-tag', 'use-vj-compression', 'user', 'v3-protocol', 'valid-lifetime', 'vcno', 'verify-client-certificate', 'version', 'vlan-id', 'vrid', 'watch-address', 'watchdog-timer', 'wds-cost-range', 'wds-default-bridge', 'wds-default-cost', 'wds-ignore-ssid', 'wds-mode', 'wins-server', 'wmm-support', 'wpa-pre-shared-key', 'wpa2-pre-shared-key', 'write-access', 'burst-limit', 'burst-threshold', 'burst-time', 'limit-at', 'priority', 'max-limit', 'tree', 'packet-mark', 'value', 'option', 'target-addresses', 'queue', 'encryption-password', 'always-broadcast', 'connect-to', 'adaptive-noise-immunity', 'compression', 'band', 'country', 'frequency', 'hw-retries', 'rate-selection', 'scan-list' ] keyword2_list = [ 'set', 'add', 'delay', 'do', 'error', 'execute', 'find', 'for', 'foreach', 'global', 'if', 'len', 'local', 'nothing', 'parse', 'pick', 'put', 'resolve', 'set', 'time', 'toarray', 'tobool', 'toid', 'toip', 'toip6', 'tonum', 'tostr', 'totime', 'typeof', 'while', 'beep', 'export', 'import', 'led', 'password', 'ping', 'quit', 'redo', 'setup', 'undo', 'print', 'detail', 'file', 'log', 'info', 'get', 'warning', 'critical' ] keyword3_list = [ '/', 'aaa', 'accounting', 'address', 'address-list', 'align', 'area', 'bandwidth-server', 'bfd', 'bgp', 'bridge', 'client', 'clock', 'community', 'config', 'connection', 'console', 'customer', 'default', 'dhcp-client', 'dhcp-server', 'discovery', 'dns', 'e-mail', 'ethernet', 'filter', 'firewall', 'firmware', 'gps', 'graphing', 'group', 'hardware', 'health', 'hotspot', 'identity', 'igmp-proxy', 'incoming', 'instance', 'interface0', 'ip', 'ipsec', 'ipv6', 'irq', 'l2tp-server', 'lcd', 'ldp', 'logging', 'mac-server', 'mac-winbox', 'mangle', 'manual', 'mirror', 'mme', 'mpls', 'nat', 'nd', 'neighbor', 'network', 'note', 'ntp', 'ospf', 'ospf-v3', 'ovpn-server', 'page', 'peer', 'pim', 'ping', 'policy', 'pool', 'port', 'ppp', 'pppoe-client', 'pptp-server', 'prefix', 'profile', 'proposal', 'proxy', 'queue', 'radius', 'resource', 'rip', 'ripng', 'route', 'routing', 'screen', 'script', 'security-profiles', 'server', 'service', 'service-port', 'settings', 'shares', 'smb', 'sms', 'sniffer', 'snmp', 'snooper', 'socks', 'sstp-server', 'system', 'tool', 'tracking', 'traffic-flow', 'traffic-generator', 'type', 'upgrade', 'upnp', 'user', 'user-manager', 'users', 'vlan', 'vrrp', 'watchdog', 'web-access', 'wireless', 'pptp', 'pppoe', 'lan', 'wan', 'layer7-protocol', 'eth-', 'wlan-', 'bridge-' ] splitter = re.compile(r"(\{\.|\.\}|\#|\'|\"\"\"|\n|\s+|\w+|\W)") #Characters that autoindent one level on pressing Return/Enter autoindent_characters = [":", "="] def __init__(self, parent=None): """Overridden initialization""" #Initialize superclass super().__init__() #Set the default style values self.setDefaultColor(self.default_color) self.setDefaultPaper(self.default_paper) self.setDefaultFont(self.default_font) #Reset autoindentation style self.setAutoIndentStyle(0) #Set the theme self.set_theme(data.theme) def language(self): return "RouterOS" def description(self, style): if style < len(self.styles): description = "Custom lexer for the RouterOS syntax by MikroTik" else: description = "" return description def set_theme(self, theme): for style in self.styles: # Papers self.setPaper(data.QColor(theme.Paper.RouterOS.Default), self.styles[style]) # Fonts lexers.set_font(self, style, getattr(theme.Font.RouterOS, style)) def styleText(self, start, end): """ Overloaded method for styling text. NOTE: Very slow if done in Python! Using the Cython version is better. The fastest would probably be adding the lexer directly into the QScintilla source. Maybe never :-) """ #Style in pure Python, VERY SLOW! editor = self.editor() if editor is None: return #Initialize the styling self.startStyling(start) #Scintilla works with bytes, so we have to adjust the start and end boundaries text = bytearray(editor.text().lower(), "utf-8")[start:end].decode("utf-8") #Loop optimizations setStyling = self.setStyling operator_list = self.operator_list comment_list = self.comment_list keyword1_list = self.keyword1_list keyword2_list = self.keyword2_list keyword3_list = self.keyword3_list DEFAULT = self.styles["Default"] OPERATOR = self.styles["Operator"] COMMENT = self.styles["Comment"] KEYWORD1 = self.styles["Keyword1"] KEYWORD2 = self.styles["Keyword2"] KEYWORD3 = self.styles["Keyword3"] #Initialize various states and split the text into tokens commenting = False tokens = [(token, len(bytearray(token, "utf-8"))) for token in self.splitter.findall(text)] #Style the tokens accordingly for i, token in enumerate(tokens): if commenting == True: #Continuation of comment setStyling(token[1], COMMENT) #Check if comment ends if "\n" in token[0]: commenting = False elif token[0] in operator_list: setStyling(token[1], OPERATOR) elif token[0] in comment_list: setStyling(token[1], COMMENT) commenting = True elif token[0] in keyword1_list: setStyling(token[1], KEYWORD1) elif token[0] in keyword2_list: setStyling(token[1], KEYWORD2) elif token[0] in keyword3_list: setStyling(token[1], KEYWORD3) else: setStyling(token[1], DEFAULT)
class Nim(data.QsciLexerCustom): """ Custom lexer for the Nim programming language """ styles = { "Default" : 0, "Comment" : 1, "BasicKeyword" : 2, "TopKeyword" : 3, "String" : 4, "LongString" : 5, "Number" : 6, "Pragma" : 7, "Operator" : 8, "Unsafe" : 9, "Type" : 10, "DocumentationComment" : 11, "Definition" : 12, "Class" : 13, "KeywordOperator" : 14, "CharLiteral" : 15, "CaseOf" : 16, "UserKeyword" : 17, "MultilineComment" : 18, "MultilineDocumentation" : 19 } #Class variables default_color = data.QColor(data.theme.Font.Nim.Default[1]) default_paper = data.QColor(data.theme.Paper.Nim.Default) default_font = data.QFont(data.current_font_name, data.current_font_size) #Basic keywords and built-in procedures and templates basic_keyword_list = [ "as", "atomic", "bind", "sizeof", "break", "case", "continue", "converter", "discard", "distinct", "do", "echo", "elif", "else", "end", "except", "finally", "for", "from", "defined", "if", "interface", "iterator", "macro", "method", "mixin", "of", "out", "proc", "func", "raise", "ref", "result", "return", "template", "try", "inc", "dec", "new", "quit", "while", "with", "without", "yield", "true", "false", "assert", "min", "max", "newseq", "len", "pred", "succ", "contains", "cmp", "add", "del","deepcopy", "shallowcopy", "abs", "clamp", "isnil", "open", "reopen", "close","readall", "readfile", "writefile", "endoffile", "readline", "writeline", ] #Custom keyword created with templates/macros user_keyword_list = [ "class", "namespace", "property", ] #Keywords that define a proc-like definition def_keyword_list = ["proc", "method", "func", "template", "macro", "converter", "iterator"] #Keywords that can define blocks top_keyword_list = [ "block", "const", "export", "import", "include", "let", "static", "type", "using", "var", "when", ] #Keywords that might be unsafe/dangerous unsafe_keyword_list = [ "asm", "addr", "cast", "ptr", "pointer", "alloc", "alloc0", "allocshared0", "dealloc", "realloc", "nil", "gc_ref", "gc_unref", "copymem", "zeromem", "equalmem", "movemem", "gc_disable", "gc_enable", ] #Built-in types type_keyword_list = [ "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "float", "float32", "float64", "bool", "char", "string", "cstring", "pointer", "ordinal", "ptr", "ref", "expr", "stmt", "typedesc", "void", "auto", "any", "untyped", "typed", "somesignedint", "someunsignedint", "someinteger", "someordinal", "somereal", "somenumber", "range", "array", "openarray", "varargs", "seq", "set", "slice", "shared", "guarded", "byte", "natural", "positive", "rootobj", "rootref", "rooteffect", "timeeffect", "ioeffect", "readioeffect", "writeioeffect", "execioeffect", "exception", "systemerror", "ioerror", "oserror", "libraryerror", "resourceexhaustederror", "arithmeticerror", "divbyzeroerror", "overflowerror", "accessviolationerror", "assertionerror", "valueerror", "keyerror", "outofmemerror", "indexerror", "fielderror", "rangeerror", "stackoverflowerror", "reraiseerror", "objectassignmenterror", "objectconversionerror", "floatingpointerror", "floatinvalidoperror", "floatdivbyzeroerror", "floatoverflowerror", "floatunderflowerror", "floatinexacterror", "deadthreaderror", "tresult", "endianness", "taintedstring", "libhandle", "procaddr", "byteaddress", "biggestint", "biggestfloat", "clong", "culong", "cchar", "cschar", "cshort", "cint", "csize", "clonglong", "cfloat", "cdouble", "clongdouble", "cuchar", "cushort", "cuint", "culonglong", "cstringarray", "pfloat32", "pfloat64", "pint64", "pint32", "gc_strategy", "pframe", "tframe", "file", "filemode", "filehandle", "thinstance", "aligntype", "refcount", "object", "tuple", "enum", ] #Sign operators operator_list = [ "=", "+", "-", "*", "/", "<", ">", "@", "$", ".", "~", "&", "%", "|", "!", "?", "^", ".", ":", "\"", ] #Keyword operators keyword_operator_list = [ "and", "or", "not", "xor", "shl", "shr", "div", "mod", "in", "notin", "is", "isnot", ] splitter = re.compile(r"(\{\.|\.\}|\#|\'|\"\"\"|\n|\s+|\w+|\W)") #Characters that autoindent one level on pressing Return/Enter autoindent_characters = [":", "="] def __init__(self, parent=None): """Overridden initialization""" #Initialize superclass super().__init__() #Set the default style values self.setDefaultColor(self.default_color) self.setDefaultPaper(self.default_paper) self.setDefaultFont(self.default_font) #Reset autoindentation style self.setAutoIndentStyle(0) #Set the theme self.set_theme(data.theme) def language(self): return "Nim" def description(self, style): if style < len(self.styles): description = "Custom lexer for the Nim programming languages" else: description = "" return description def defaultStyle(self): return self.styles["Default"] def braceStyle(self): return self.styles["Default"] def defaultFont(self, style): return data.QFont(data.current_font_name, data.current_font_size) def set_theme(self, theme): for style in self.styles.keys(): # Papers self.setPaper( data.QColor(theme.Paper.Nim.Default), self.styles[style] ) # Fonts set_font(self, style, getattr(theme.Font.Nim, style)) def styleText(self, start, end): """ Overloaded method for styling text. NOTE: Very slow if done in Python! Using the Cython version is better. The fastest would probably be adding the lexer directly into the QScintilla source. Maybe never :-) """ #Style in pure Python, VERY SLOW! editor = self.editor() if editor is None: return #Initialize the styling self.startStyling(start) #Scintilla works with bytes, so we have to adjust the start and end boundaries text = bytearray(editor.text().lower(), "utf-8")[start:end].decode("utf-8") #Loop optimizations setStyling = self.setStyling basic_kw_list = self.basic_keyword_list user_kw_list = self.user_keyword_list def_kw_list = self.def_keyword_list top_kw_list = self.top_keyword_list unsafe_kw_list = self.unsafe_keyword_list operator_list = self.operator_list keyword_operator_list = self.keyword_operator_list type_kw_list = self.type_keyword_list DEF = self.styles["Default"] B_KWD = self.styles["BasicKeyword"] T_KWD = self.styles["TopKeyword"] COM = self.styles["Comment"] STR = self.styles["String"] L_STR = self.styles["LongString"] NUM = self.styles["Number"] MAC = self.styles["Pragma"] OPE = self.styles["Operator"] UNS = self.styles["Unsafe"] TYP = self.styles["Type"] D_COM = self.styles["DocumentationComment"] DEFIN = self.styles["Definition"] CLS = self.styles["Class"] KOP = self.styles["KeywordOperator"] CHAR = self.styles["CharLiteral"] OF = self.styles["CaseOf"] U_KWD = self.styles["UserKeyword"] M_COM = self.styles["MultilineComment"] M_DOC = self.styles["MultilineDocumentation"] #Initialize various states and split the text into tokens commenting = False doc_commenting = False multi_doc_commenting= False new_commenting = False stringing = False long_stringing = False char_literal = False pragmaing = False case_of = False cls_descrition = False tokens = [(token, len(bytearray(token, "utf-8"))) for token in self.splitter.findall(text)] #Check if there is a style(comment, string, ...) stretching on from the previous line if start != 0: previous_style = editor.SendScintilla(editor.SCI_GETSTYLEAT, start - 1) if previous_style == L_STR: long_stringing = True elif previous_style == MAC: pragmaing = True elif previous_style == M_COM: new_commenting = True elif previous_style == M_DOC: multi_doc_commenting = True #Style the tokens accordingly for i, token in enumerate(tokens): # print(str(token) + " " + str(i)) if commenting == True: #Continuation of comment setStyling(token[1], COM) #Check if comment ends if "\n" in token[0]: commenting = False elif doc_commenting == True: #Continuation of comment setStyling(token[1], D_COM) #Check if comment ends if "\n" in token[0]: doc_commenting = False elif new_commenting == True: #Continuation of comment setStyling(token[1], M_COM) #Check if comment ends if "#" in token[0] and "]" in tokens[i-1][0]: new_commenting = False elif multi_doc_commenting == True: #Continuation of comment setStyling(token[1], M_DOC) #Check if comment ends if "#" in token[0] and "#" in tokens[i-1][0] and "]" in tokens[i-2][0]: multi_doc_commenting = False elif stringing == True: #Continuation of a string setStyling(token[1], STR) #Check if string ends if token[0] == "\"" and (tokens[i-1][0] != "\\") or "\n" in token[0]: stringing = False elif long_stringing == True: #Continuation of a string setStyling(token[1], L_STR) #Check if string ends if token[0] == "\"\"\"": long_stringing = False elif char_literal == True: #Check if string ends if ("\n" in token[0] or " " in token[0] or "(" in token[0] or ")" in token[0] or "," in token[0] or token[0] in operator_list): #Do not color the separator setStyling(token[1], DEF) char_literal = False elif token[0] == "'": #Continuation of a character setStyling(token[1], CHAR) char_literal = False else: setStyling(token[1], CHAR) elif pragmaing == True: #Continuation of a string setStyling(token[1], MAC) #Check if string ends if token[0] == ".}": pragmaing = False elif case_of == True: #'Case of' parameter if token[0] == ":" or "\n" in token[0]: setStyling(token[1], DEF) case_of = False else: setStyling(token[1], OF) elif cls_descrition == True: #Class/namespace description if token[0] == ":" or "\n" in token[0]: setStyling(token[1], DEF) cls_descrition = False else: setStyling(token[1], CLS) elif token[0] == "\"\"\"": #Start of a multi line (long) string setStyling(token[1], L_STR) long_stringing = True elif token[0] == "{.": #Start of a multi line (long) string setStyling(token[1], MAC) pragmaing = True elif token[0] == "\"": #Start of a string setStyling(token[1], STR) stringing = True elif token[0] == "'": #Start of a string setStyling(token[1], CHAR) char_literal = True elif token[0] in basic_kw_list: #Basic keyword setStyling(token[1], B_KWD) try: if ((token[0] == "of" and "\n" in tokens[i-2][0]) or ((token[0] == "of" and "\n" in tokens[i-1][0]))): #Start of a CASE case_of = True except IndexError: case_of = False elif token[0] in user_kw_list: #User keyword setStyling(token[1], U_KWD) elif token[0] in top_kw_list: #Top keyword setStyling(token[1], T_KWD) elif token[0] in unsafe_kw_list: #Unsafe/danger keyword setStyling(token[1], UNS) elif token[0] in operator_list: #Operator setStyling(token[1], OPE) elif token[0] in keyword_operator_list: #Operator setStyling(token[1], KOP) elif token[0] in type_kw_list: #Operator setStyling(token[1], TYP) elif token[0] == "#": #Start of a comment or documentation comment if len(tokens) > i+2 and tokens[i+1][0] == "#" and tokens[i+2][0] == "[": setStyling(token[1], M_DOC) multi_doc_commenting = True elif len(tokens) > i+1 and tokens[i+1][0] == "#": setStyling(token[1], D_COM) doc_commenting = True elif len(tokens) > i+1 and tokens[i+1][0] == "[": setStyling(token[1], M_COM) new_commenting = True else: setStyling(token[1], COM) commenting = True elif (i > 1) and (("\n" in tokens[i-2][0]) or (" " in tokens[i-2][0])) and (tokens[i-1][0] == "of"): #Case of statement case_of = True setStyling(token[1], OF) elif functions.is_number(token[0][0]): #Number #Check only the first character, because Nim has those weird constants e.g.: 12u8, ...) setStyling(token[1], NUM) elif ((i > 1) and (tokens[i-2][0] in user_kw_list) and token[0][0].isalpha()): #Class-like definition setStyling(token[1], CLS) cls_descrition = True elif (((i > 1) and (tokens[i-2][0] in def_kw_list and tokens[i-1][0] != "(") and token[0][0].isalpha()) or ((i > 2) and (tokens[i-3][0] in def_kw_list and tokens[i-1][0] == '`') and token[0][0].isalpha())): #Proc-like definition setStyling(token[1], DEFIN) else: setStyling(token[1], DEF)
class Editor: """ These are the built-in defaults, attributes should be changed in other modules! """ # Default EOL style in editors (EolWindows-CRLF, EolUnix-LF, EolMac-CR) end_of_line_mode = data.QsciScintilla.EolUnix # Font colors and styles font = data.QFont( data.current_editor_font_name, data.current_editor_font_size ) brace_color = data.QColor(255, 153, 0) comment_font = data.current_editor_font_name.encode("utf-8") # Edge marker edge_marker_color = data.QColor(180, 180, 180, alpha=255) edge_marker_column = 90 # Various cursor_line_visible = False # Maximum limit of highlighting instances maximum_highlights = 300 # Global width of tabs tab_width = 4 # Zoom factor when a new editor is created (default is 0) zoom_factor = 0 """ ------------------------------------------- Keyboard shortcuts ------------------------------------------- """ class Keys: # Custom editor commands copy = 'Ctrl+C' cut = 'Ctrl+X' paste = 'Ctrl+V' undo = 'Ctrl+Z' redo = 'Ctrl+Y' select_all = 'Ctrl+A' indent = 'Tab' unindent = 'Shift+Tab' delete_start_of_word = 'Ctrl+BackSpace' delete_end_of_word = 'Ctrl+Delete' delete_start_of_line = 'Ctrl+Shift+BackSpace' delete_end_of_line = 'Ctrl+Shift+Delete' go_to_start = 'Ctrl+Home' go_to_end = 'Ctrl+End' select_page_up = 'Shift+PageUp' select_page_down = 'Shift+PageDown' select_to_start = 'Ctrl+Shift+Home' select_to_end = 'Ctrl+Shift+End' scroll_up = 'PageUp' scroll_down = 'PageDown' line_cut = 'Ctrl+L' line_copy = 'Ctrl+Shift+T' line_delete = 'Ctrl+Shift+L' line_transpose = 'Ctrl+T' line_selection_duplicate = 'Ctrl+D' @staticmethod def check_function(function_name): check_list = [x for x in dir(Editor.Keys) if not x.startswith('__')] return function_name in check_list @staticmethod def check_combination(combination): if combination.startswith("#"): combination = combination[1:] check_list = [ (x, getattr(Editor.Keys, x)) for x in dir(Editor.Keys) if not x.startswith('__') ] for name, keys in check_list: if not(isinstance(keys, str)) and not(isinstance(keys, list)): continue if isinstance(combination, list): if isinstance(keys, list): for k in keys: for c in combination: if k.strip().lower() == c.strip().lower(): return True else: for c in combination: if keys.strip().lower() == c.strip().lower(): return True elif isinstance(keys, str): if keys.strip().lower() == combination.strip().lower(): return True return False
class Oberon(data.QsciLexerCustom): """ Custom lexer for the Oberon/Oberon-2/Modula/Modula-2 programming languages """ styles = { "Default": 0, "Comment": 1, "Keyword": 2, "String": 3, "Procedure": 4, "Module": 5, "Number": 6, "Type": 7 } #Class variables default_color = data.QColor(data.theme.Font.Oberon.Default[1]) default_paper = data.QColor(data.theme.Paper.Oberon.Default) default_font = data.QFont('Courier', 10) keyword_list = [ 'ARRAY', 'IMPORT', 'RETURN', 'BEGIN', 'IN', 'THEN', 'BY', 'IS', 'TO', 'CASE', 'LOOP', 'Type', 'CONST', 'MOD', 'UNTIL', 'DIV', 'MODULE', 'VAR', 'DO', 'NIL', 'WHILE', 'ELSE', 'OF', 'WITH', 'ELSIF', 'OR', 'END', 'POINTER', 'EXIT', 'PROCEDURE', 'FOR', 'RECORD', 'IF', 'REPEAT' ] types_list = [ 'BOOLEAN', 'CHAR', 'SHORTINT', 'INTEGER', 'LONGINT', 'REAL', 'LONGREAL', 'SET' ] splitter = re.compile(r"(\(\*|\*\)|\s+|\w+|\W)") def __init__(self, parent=None): """Overridden initialization""" #Initialize superclass super().__init__() #Set the default style values self.setDefaultColor(self.default_color) self.setDefaultPaper(self.default_paper) self.setDefaultFont(self.default_font) #Reset autoindentation style self.setAutoIndentStyle(0) #Set the theme self.set_theme(data.theme) def language(self): return "Oberon/Modula-2/Component Pascal" def description(self, style): if style <= 7: description = "Custom lexer for the Oberon/Oberon-2/Modula/Modula-2/Component Pascal programming languages" else: description = "" return description def set_theme(self, theme): for style in self.styles: # Papers self.setPaper(data.QColor(theme.Paper.Oberon.Default), self.styles[style]) # Fonts lexers.set_font(self, style, getattr(theme.Font.Oberon, style)) def styleText(self, start, end): """ Overloaded method for styling text. NOTE: Very slow if done in Python! Using the Cython version is better. The fastest would probably be adding the lexer directly into the QScintilla source. Maybe never :-) """ #Get the global cython flag if lexers.cython_lexers_found == True: #Cython module found lexers.cython_lexers.style_oberon(start, end, self, self.editor()) else: #Style in pure Python, VERY SLOW! editor = self.editor() if editor is None: return #Initialize the styling self.startStyling(start) #Scintilla works with bytes, so we have to adjust the start and end boundaries text = bytearray(editor.text(), "utf-8")[start:end].decode("utf-8") #Loop optimizations setStyling = self.setStyling kw_list = self.keyword_list types_list = self.types_list DEF = self.styles["Default"] KWD = self.styles["Keyword"] COM = self.styles["Comment"] STR = self.styles["String"] PRO = self.styles["Procedure"] MOD = self.styles["Module"] NUM = self.styles["Number"] TYP = self.styles["Type"] #Initialize comment state and split the text into tokens commenting = False stringing = False tokens = [(token, len(bytearray(token, "utf-8"))) for token in self.splitter.findall(text)] #Check if there is a style(comment, string, ...) stretching on from the previous line if start != 0: previous_style = editor.SendScintilla(editor.SCI_GETSTYLEAT, start - 1) if previous_style == COM: commenting = True #Style the tokens accordingly for i, token in enumerate(tokens): if commenting == True: #Continuation of comment setStyling(token[1], COM) #Check if comment ends if token[0] == "*)": commenting = False elif stringing == True: #Continuation of a string setStyling(token[1], STR) #Check if string ends if token[0] == "\"" or "\n" in token[0]: stringing = False elif token[0] == "\"": #Start of a string setStyling(token[1], STR) stringing = True elif token[0] in kw_list: #Keyword setStyling(token[1], KWD) elif token[0] in types_list: #Keyword setStyling(token[1], TYP) elif token[0] == "(*": #Start of a comment setStyling(token[1], COM) commenting = True elif i > 1 and tokens[i - 2][0] == "PROCEDURE": #Procedure name setStyling(token[1], PRO) elif i > 1 and tokens[i - 2][0] == "MODULE": #Module name (beginning) setStyling(token[1], MOD) elif (i > 1 and tokens[i - 2][0] == "END") and (len(tokens) - 1 >= i + 1): #Module or procedure name (name) if ";" in tokens[i + 1][0]: #Procedure end setStyling(token[1], PRO) elif "." in tokens[i + 1][0]: #Module end setStyling(token[1], MOD) else: setStyling(token[1], DEF) elif functions.is_number(token[0]): #Number setStyling(token[1], NUM) else: setStyling(token[1], DEF)
class Ada(data.QsciLexerCustom): """Custom lexer for the Ada programming languages""" styles = { "Default": 0, "Comment": 1, "Keyword": 2, "String": 3, "Procedure": 4, "Number": 5, "Type": 6, "Package": 7 } #Class variables default_color = data.QColor(data.theme.Font.Ada.Default[1]) default_paper = data.QColor(data.theme.Paper.Ada.Default) default_font = data.QFont(data.current_font_name, data.current_font_size) keyword_list = [ "abort", "else", "new", "return", "abs", "elsif", "not", "reverse", "abstract", "end", "null", "accept", "entry", "select", "access", "exception", "of", "separate", "aliased", "exit", "or", "some", "all", "others", "subtype", "and", "for", "out", "synchronized", "array", "function", "overriding", "at", "tagged", "generic", "package", "task", "begin", "goto", "pragma", "terminate", "body", "private", "then", "if", "procedure", "type", "case", "in", "protected", "constant", "interface", "until", "is", "raise", "use", "declare", "range", "delay", "limited", "record", "when", "delta", "loop", "rem", "while", "digits", "renames", "with", "do", "mod", "requeue", "xor", ] splitter = re.compile(r"(\-\-|\s+|\w+|\W)") def __init__(self, parent=None): """Overridden initialization""" #Initialize superclass super().__init__() #Set the default style values self.setDefaultColor(self.default_color) self.setDefaultPaper(self.default_paper) self.setDefaultFont(self.default_font) #Reset autoindentation style self.setAutoIndentStyle(0) #Set the theme self.set_theme(data.theme) def language(self): return "Ada" def description(self, style): if style <= 7: description = "Custom lexer for the Ada programming languages" else: description = "" return description def set_theme(self, theme): for style in self.styles: # Papers self.setPaper(data.QColor(theme.Paper.Ada.Default), self.styles[style]) # Fonts lexers.set_font(self, style, getattr(theme.Font.Ada, style)) def styleText(self, start, end): """ Overloaded method for styling text. NOTE: Very slow if done in Python! Using the Cython version is better. The fastest would probably be adding the lexer directly into the QScintilla source. Maybe never :-) """ #Get the global cython flag if lexers.cython_lexers_found == True: #Cython module found lexers.cython_lexers.style_ada(start, end, self, self.editor()) else: #Style in pure Python, VERY SLOW! editor = self.editor() if editor is None: return #Initialize the procedure/package counter pp_counter = [] #Initialize the styling self.startStyling(0) #Scintilla works with bytes, so we have to adjust the start and end boundaries text = bytearray(editor.text().lower(), "utf-8").decode("utf-8") #Loop optimizations setStyling = self.setStyling kw_list = self.keyword_list DEF = self.styles["Default"] KWD = self.styles["Keyword"] COM = self.styles["Comment"] STR = self.styles["String"] PRO = self.styles["Procedure"] NUM = self.styles["Number"] PAC = self.styles["Package"] # TYP = self.styles["Type"] #Initialize comment state and split the text into tokens commenting = False stringing = False tokens = [(token, len(bytearray(token, "utf-8"))) for token in self.splitter.findall(text)] #Style the tokens accordingly for i, token in enumerate(tokens): if commenting == True: #Continuation of comment setStyling(token[1], COM) #Check if comment ends if "\n" in token[0]: commenting = False elif stringing == True: #Continuation of a string setStyling(token[1], STR) #Check if string ends if token[0] == "\"" or "\n" in token[0]: stringing = False elif token[0] == "\"": #Start of a string setStyling(token[1], STR) stringing = True elif token[0] in kw_list: #Keyword setStyling(token[1], KWD) elif token[0] == "--": #Start of a comment setStyling(token[1], COM) commenting = True elif i > 1 and tokens[i - 2][0] == "procedure": #Procedure name setStyling(token[1], PRO) #Mark the procedure if tokens[i + 1][0] != ";": pp_counter.append("PROCEDURE") elif i > 1 and (tokens[i - 2][0] == "package" or tokens[i - 2][0] == "body"): #Package name setStyling(token[1], PAC) #Mark the package pp_counter.append("PACKAGE") elif (i > 1 and tokens[i - 2][0] == "end") and (len(tokens) - 1 >= i + 1): #Package or procedure name end if len(pp_counter) > 0: if pp_counter.pop() == "PACKAGE": setStyling(token[1], PAC) else: setStyling(token[1], PRO) else: setStyling(token[1], DEF) elif functions.is_number(token[0]): #Number setStyling(token[1], NUM) else: setStyling(token[1], DEF)
def init_layout(self, text, dialog_type): # Setup the image # First create the background image using the hex builder back_image = data.QImage(functions.create_size(246, 211), data.QImage.Format_ARGB32_Premultiplied) back_image.fill(data.Qt.transparent) painter = data.QPainter(back_image) painter.setRenderHints(data.QPainter.Antialiasing | data.QPainter.TextAntialiasing | data.QPainter.SmoothPixmapTransform) hex_builder = components.HexBuilder( painter, (123, 28), 30, 1.0, fill_color=data.theme.YesNoDialog_Background, line_width=3, line_color=data.theme.YesNoDialog_Edge, ) hex_builder.create_grid( False, 2, 2, 3, 4, 0, 5, 3, (3, True), 5, 0, 0, 4, 3 # OkDialog ) painter.end() original_dialog_image = data.QPixmap.fromImage(back_image) # Now add the images according to the type of dialog dialog_image = original_dialog_image.scaled( original_dialog_image.size() * self.scale, transformMode=data.Qt.SmoothTransformation) self.image = data.QLabel(self) self.image.setPixmap(dialog_image) self.image.setGeometry( 0, 0, int(dialog_image.rect().width() * self.scale), int(dialog_image.rect().height() * self.scale), ) self.image.setScaledContents(True) # Set the dialog mask to match the image mask self.setMask(dialog_image.mask()) # Setup the image behind the label if dialog_type != None: if dialog_type == "question": type_pixmap = data.QPixmap( os.path.join(data.resources_directory, "various/dialog-question.png")) elif dialog_type == "warning": type_pixmap = data.QPixmap( os.path.join(data.resources_directory, "various/dialog-warning.png")) elif dialog_type == "error": type_pixmap = data.QPixmap( os.path.join(data.resources_directory, "various/dialog-error.png")) else: raise Exception("Wrong dialog type!") image = data.QImage(type_pixmap.size(), data.QImage.Format_ARGB32_Premultiplied) image.fill(data.Qt.transparent) painter = data.QPainter(image) painter.setOpacity(0.2) painter.drawPixmap(0, 0, type_pixmap) painter.end() type_pixmap = data.QPixmap.fromImage(image) type_pixmap = type_pixmap.scaled( type_pixmap.size() * 2.0 * self.scale, transformMode=data.Qt.SmoothTransformation) self.type_label = data.QLabel(self) self.type_label.setPixmap(type_pixmap) type_label_rect = functions.create_rect( (self.image.rect().width() - type_pixmap.rect().width()) / 2 * self.scale, (self.image.rect().height() - type_pixmap.rect().height()) / 2 * self.scale, type_pixmap.rect().width() * self.scale, type_pixmap.rect().height() * self.scale, ) self.type_label.setGeometry(type_label_rect) # Setup the text label self.text = text self.label = data.QLabel(self) self.label.setFont( data.QFont('Segoe UI', int(12 * self.scale), data.QFont.Bold)) self.label.setWordWrap(True) self.label.setAlignment(data.Qt.AlignCenter) self.label.setStyleSheet('color: rgb({}, {}, {})'.format( data.theme.Font.Default.red(), data.theme.Font.Default.green(), data.theme.Font.Default.blue(), )) self.label.setText(text) width_diff = self.image.rect().width() - original_dialog_image.width() height_diff = self.image.rect().height( ) - original_dialog_image.height() x_offset = 20 * (self.scale - 1.0) y_offset = 60 * (self.scale - 1.0) label_rect = functions.create_rect( dialog_image.rect().x() + 20 + x_offset, dialog_image.rect().y() + 60 + y_offset, dialog_image.rect().width() - (40 * self.scale), dialog_image.rect().height() - (120 * self.scale), ) self.label.setGeometry(label_rect) # Shrink text if needed for i in range(10): label_width = label_rect.width() label_height = label_rect.height() font_metrics = data.QFontMetrics(self.label.font()) bounding_rectangle = font_metrics.boundingRect( functions.create_rect(0, 0, label_width, label_height), self.label.alignment() | data.Qt.TextWordWrap, text) if (bounding_rectangle.width() > label_width or bounding_rectangle.height() > label_height): self.label.setFont( data.QFont('Segoe UI', int((12 - i) * self.scale), data.QFont.Bold)) else: break # Setup the buttons self.button_no = self.Button( self, os.path.join(data.resources_directory, "various/hex-red.png"), "OK", data.QMessageBox.No, self.scale) x_offset = 93 * (self.scale - 1.0) y_offset = 158 * (self.scale - 1.0) self.button_no.setGeometry(int(93 + x_offset), int(158 + y_offset), int(59 * self.scale), int(50 * self.scale)) self.button_no.on_signal.connect(self.update_state_off) self.button_no.off_signal.connect(self.update_state_reset) # Setup the layout self.layout = data.QGridLayout() self.layout.setSpacing(0) self.layout.setContentsMargins(data.QMargins(0, 0, 0, 0)) self.layout.addWidget(self.image) self.setLayout(self.layout) # Setup tranparency and borders if data.on_rpi == True: self.image.setStyleSheet("border:0;" + "background-color:white;") else: self.image.setAttribute(data.Qt.WA_TranslucentBackground) self.image.setStyleSheet("border:0;" + "background-color:transparent;") self.setAttribute(data.Qt.WA_TranslucentBackground) self.setStyleSheet("border:0;" + "background-color:transparent;") self.setGeometry(dialog_image.rect()) self.center() self.setWindowFlags(data.Qt.FramelessWindowHint)