Ejemplo n.º 1
0
    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))
Ejemplo n.º 2
0
 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)
Ejemplo n.º 3
0
 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)
Ejemplo n.º 4
0
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])
Ejemplo n.º 5
0
 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)
Ejemplo n.º 6
0
 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)
Ejemplo n.º 7
0
 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()
Ejemplo n.º 8
0
 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)
Ejemplo n.º 9
0
 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()
Ejemplo n.º 10
0
 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)
Ejemplo n.º 11
0
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
    )
Ejemplo n.º 12
0
 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()
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
        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())
Ejemplo n.º 15
0
Archivo: awk.py Proyecto: Mr-ZBin/ExCo
 def defaultFont(self, style):
     return data.QFont(data.current_font_name, data.current_font_size)
Ejemplo n.º 16
0
Archivo: awk.py Proyecto: Mr-ZBin/ExCo
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)
Ejemplo n.º 17
0
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"])
Ejemplo n.º 18
0
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)
Ejemplo n.º 19
0
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)
Ejemplo n.º 20
0
    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
Ejemplo n.º 21
0
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)
Ejemplo n.º 22
0
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)
Ejemplo n.º 23
0
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)
Ejemplo n.º 24
0
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
Ejemplo n.º 25
0
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)
Ejemplo n.º 26
0
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)
Ejemplo n.º 27
0
    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)