Beispiel #1
0
 def __init__(self, *args, **kwargs):
     loader = QuietLoaders()
     default_theme = loader.load_default_theme()
     settings = loader.load_settings_data()
     settings['menu_active_bg'] = default_theme['menu_bg_active']
     settings['menu_active_fg'] = default_theme['menu_fg_active']
     settings['menu_fg'] = default_theme['comment_color']
     settings['menu_bg'] = default_theme['bg_color']
     super().__init__(bg=settings["menu_bg"],
                      fg=settings['menu_fg'],
                      activeforeground=settings['menu_active_fg'],
                      activebackground=settings['menu_active_bg'],
                      activeborderwidth=0,
                      bd=0,
                      *args, **kwargs)
Beispiel #2
0
class QuietText(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
        master.title('untitled - Quiet Text')
        # defined size of the editer window
        master.geometry('1920x1080')
        self.loader = QuietLoaders()
        user_operating_system = system()

        # start editor according to defined settings in settings.yaml
        self.settings = self.loader.load_settings_data()
        self.default_theme = self.loader.load_default_theme()

        self.settings['font_color'] = self.default_theme['font_color']
        self.settings['textarea_background_color'] = self.default_theme['bg_color']
        self.settings['menubar_active_bg'] = self.default_theme['menu_bg_active']
        self.settings['menubar_active_fg'] = self.default_theme['menu_fg_active']  
        self.settings['menu_active_bg'] = self.default_theme['menu_bg_active']
        self.settings['menu_active_fg'] = self.default_theme['menu_fg_active']
        self.settings['menu_bg'] = self.default_theme['bg_color']
        self.settings['font_color'] = self.default_theme['font_color']

        self.font_family = self.settings['font_family']
        self.bg_color = self.settings['textarea_background_color']
        self.font_color = self.settings['font_color']
        self.tab_size = self.settings['tab_size']
        self.font_size = int(self.settings['font_size'])
        self.top_spacing = self.settings['text_top_lineheight']
        self.bottom_spacing = self.settings['text_bottom_lineheight']
        self.padding_x = self.settings['textarea_padding_x']
        self.padding_y = self.settings['textarea_padding_y']
        self.insertion_blink = 300 if self.settings['insertion_blink'] else 0
        self.insertion_color = self.settings['insertion_color']
        self.tab_size_spaces = self.settings['tab_size']
        self.text_wrap = self.settings['text_wrap']
        self.autoclose_parens = self.settings['autoclose_parentheses']
        self.autoclose_curlybraces = self.settings['autoclose_curlybraces']
        self.autoclose_squarebrackets = self.settings['autoclose_squarebrackets']
        self.autoclose_singlequotes = self.settings['autoclose_singlequotes']
        self.autoclose_doublequotes = self.settings['autoclose_doublequotes']
        self.border = self.settings['textarea_border']
        self.text_selection_bg = self.settings['text_selection_bg']
        self.scrollx_clr = self.settings['horizontal_scrollbar_color']
        self.troughx_clr = self.settings['horizontal_scrollbar_trough_color']
        self.scrollx_width = self.settings['horizontal_scrollbar_width']
        self.scrollx_active_bg = self.settings['horizontal_scrollbar_active_bg']
        self.scrolly_clr = self.settings['vertical_scrollbar_color']
        self.troughy_clr = self.settings['vertical_scrollbar_trough_color']
        self.scrolly_width = self.settings['vertical_scrollbar_width']
        self.scrolly_active_bg = self.settings['vertical_scrollbar_active_bg']
        self.menu_fg = self.settings['menu_fg']
        self.menu_bg = self.settings['menu_bg']



        master.tk_setPalette(background=self.bg_color, foreground='black')
        self.font_style = tk_font.Font(family=self.font_family,
                                       size=self.settings['font_size'])

        #configuration of the file dialog text colors.

        self.italics = tk_font.Font(family=self.font_family, slant='italic')
        self.master = master
        self.filename = None
                                
        self.textarea = CustomText(self)

        self.scrolly = tk.Scrollbar(master,
                                    command=self.textarea.yview,
                                    bg=self.scrolly_clr,
                                    troughcolor=self.troughy_clr,
                                    bd=0,
                                    width=self.scrolly_width,
                                    highlightthickness=0,
                                    activebackground=self.scrolly_active_bg,
                                    orient='vertical')

        self.scrollx = tk.Scrollbar(master,
                                    command=self.textarea.xview,
                                    bg=self.scrollx_clr,
                                    troughcolor=self.troughx_clr,
                                    bd=0,
                                    width=self.scrollx_width,
                                    highlightthickness=0,
                                    activebackground=self.scrollx_active_bg,
                                    orient='horizontal')

        self.textarea.configure(yscrollcommand=self.scrolly.set,
                                xscrollcommand=self.scrollx.set,
                                bg=self.bg_color,
                                fg=self.font_color,
                                wrap= self.text_wrap,
                                spacing1=self.top_spacing, 
                                spacing3=self.bottom_spacing,
                                selectbackground= self.text_selection_bg,
                                insertbackground=self.insertion_color,
                                insertofftime=self.insertion_blink,
                                bd=self.border,
                                highlightthickness=self.border,
                                highlightbackground='black',
                                font=self.font_family,
                                undo=True,
                                autoseparators=True,
                                maxundo=-1,
                                padx=self.padding_x,
                                pady=self.padding_y)

        self.initial_content = self.textarea.get("1.0", tk.END)

        #retrieving the font from the text area and setting a tab width
        self._font = tk_font.Font(font=self.textarea['font'])
        self._tab_width = self._font.measure(' ' * self.tab_size_spaces)
        self.textarea.config(tabs=(self._tab_width,))

        self.menu_hidden = False
        self.context_menu = ContextMenu(self)
        self.statusbar = Statusbar(self)
        self.linenumbers = TextLineNumbers(self)
        self.syntax_highlighter = SyntaxHighlighting(self, self.textarea, self.initial_content)
        self.menubar = Menubar(self)

        self.linenumbers.attach(self.textarea)
        self.scrolly.pack(side=tk.RIGHT, fill=tk.Y)
        self.scrollx.pack(side=tk.BOTTOM, fill='both')
        self.linenumbers.pack(side=tk.LEFT, fill=tk.Y)
        self.textarea.pack(side=tk.RIGHT, fill='both', expand=True)
        
        self.textarea.tag_configure('find_match', background='#75715e')
        self.textarea.find_match_index = None
        self.textarea.find_search_starting_index = 1.0

        self.tags_configured = False
        #calling function to bind hotkeys.
        self.bind_shortcuts()
        self.control_key = False

    def clear_and_replace_textarea(self):
            self.textarea.delete(1.0, tk.END)
            try:
                with open(self.filename, 'r') as f:
                    self.textarea.insert(1.0, f.read())
            except TypeError:
                pass

    #reconfigure the tab_width depending on changes.
    def set_new_tab_width(self, tab_spaces = 'default'):
        if tab_spaces == 'default':
            space_count = self.tab_size_spaces
        else:
            space_count = tab_spaces
        _font = tk_font.Font(font=self.textarea['font'])
        _tab_width = _font.measure(' ' * int(space_count))
        self.textarea.config(tabs=(_tab_width,))

    # editor basic settings can be altered here
    #function used to reload settings after the user changes in settings.yaml
    def reconfigure_settings(self, overwrite_with_default=False):
            if overwrite_with_default:
                _settings = self.loader.load_settings_data(default=True)
            else:
                _settings = self.loader.load_settings_data()
            font_family = _settings['font_family']
            bg_color = _settings['textarea_background_color']
            font_color = _settings['font_color']
            top_spacing = _settings['text_top_lineheight']
            bottom_spacing = _settings['text_bottom_lineheight']
            insertion_blink = 300 if _settings['insertion_blink'] else 0
            insertion_color = _settings['insertion_color']
            tab_size_spaces = _settings['tab_size']
            padding_x = _settings['textarea_padding_x']
            padding_y = _settings['textarea_padding_y']
            text_wrap = _settings['text_wrap']
            border = _settings['textarea_border']
            text_selection_bg = _settings['text_selection_bg']
            scrollx_clr = _settings['horizontal_scrollbar_color']
            troughx_clr = _settings['horizontal_scrollbar_trough_color']
            scrollx_width = _settings['horizontal_scrollbar_width']
            scrollx_active_bg = _settings['horizontal_scrollbar_active_bg']
            menu_fg = _settings['menu_fg']
            menu_bg = _settings['menu_bg']

            font_style = tk_font.Font(family=font_family,
                                      size=_settings['font_size'])

            self.textarea.configure(font=font_style,
                                    bg=bg_color,
                                    pady=padding_y,
                                    padx=padding_x,
                                    fg=font_color,
                                    spacing1=top_spacing,
                                    spacing3=bottom_spacing,
                                    insertbackground=insertion_color,
                                    selectbackground= text_selection_bg,
                                    insertofftime=insertion_blink,
                                    bd=border,
                                    highlightthickness=border,
                                    wrap=text_wrap)

            self.set_new_tab_width(tab_size_spaces)
            self.menubar.reconfigure_settings()
            self.linenumbers.font_color = menu_fg
            self.linenumbers.config(bg=bg_color, highlightbackground=bg_color)
            self.statusbar._label.config(bg=bg_color)
            self.linenumbers.redraw()

            if overwrite_with_default:
                MsgBox = tk.messagebox.askquestion('Reset Settings?',
                                                   'Are you sure you want to reset the editor settings to their default value?',
                                                    icon='warning')
                if MsgBox == 'yes':
                    self.loader.store_settings_data(_settings)
                else:
                    self.save('config/settings.yaml')

    # editor quiet mode calling which removes status bar and menu bar
    def enter_quiet_mode(self, *args):
        self.statusbar.hide_status_bar()
        self.menubar.hide_menu()
        self.scrollx.configure(width=0)
        self.scrolly.configure(width=0)
        self.statusbar.update_status('quiet')

    # editor leaving quite enu to bring back status bar and menu bar
    def leave_quiet_mode(self, *args):
        self.statusbar.show_status_bar()
        self.menubar.show_menu()
        self.scrollx.configure(width=8)
        self.scrolly.configure(width=8)
        self.statusbar.update_status('hide')

    #hide status bar for text class so it can be used in menu class
    def hide_status_bar(self, *args):
        self.statusbar.hide_status_bar()

    # toggle the visibility of line numbers
    def toggle_linenumbers(self, *args):
        self.linenumbers.visible = not self.linenumbers.visible

    # setting up the editor title
    #Renames the window title bar to the name of the current file.
    def set_window_title(self, name=None):
        if name:
            self.master.title(f'{name} - QuietText')
        else:
            self.master.title('Untitled - QuietText')

    # new file creating in the editor feature
    #Deletes all of the text in the current area and sets window title to default.
    def new_file(self, *args):
        self.textarea.delete(1.0, tk.END)
        self.filename = None
        self.set_window_title()

    # opening an existing file in the editor
    def open_file(self, *args):
        # various file types that editor can support
        self.filename = filedialog.askopenfilename(
            defaultextension='.txt',
            filetypes=[('All Files', '*.*'),
                       ('Text Files', '*.txt'),
                       ('Python Scripts', '*.py'),
                       ('Markdown Documents', '*.md'),
                       ('Javascript Files', '*.js'),
                       ('HTML Documents', '*.html'),
                       ('CSS Documents', '*.css')])

        if self.filename:
            self.clear_and_replace_textarea()
            self.set_window_title(name=self.filename)
            self.syntax_highlighter.initial_highlight()

    # opening an existing file without TK filedialog
    def open_file_without_dialog(self, path):
        if os.path.isdir(path):
            self.statusbar.update_status('Unable to open directory.')
            return

        if not os.path.exists(path):
            self.statusbar.update_status('File does not exists.')
            return

        self.filename = path
        self.clear_and_replace_textarea()
        self.syntax_highlighter.initial_highlight()

    # saving changes made in the file
    def save(self,*args):
        if self.filename:
            try:
                textarea_content = self.textarea.get(1.0, tk.END)
                with open(self.filename, 'w') as f:
                    f.write(textarea_content)
                self.statusbar.update_status('saved')
                if self.filename == 'config/settings.yaml':
                    self.reconfigure_settings()
                    self.menubar.reconfigure_settings()
            except Exception as e:
                print(e)
        else:
            self.save_as()

    # saving file as a particular name
    def save_as(self, *args):
        try:
            new_file = filedialog.asksaveasfilename(
                initialfile='untitled.txt',
                defaultextension='.txt',
                filetypes=[('All Files', '*.*'),
                           ('Text Files', '*.txt'),
                           ('Python Scripts', '*.py'),
                           ('Markdown Documents', '*.md'),
                           ('Javascript Files', '*.js'),
                           ('HTML Documents', '*.js'),
                           ('CSS Documents', '*.css')])

            textarea_content = self.textarea.get(1.0, tk.END)
            with open(new_file, 'w') as f:
                f.write(textarea_content)
            self.filename = new_file
            self.set_window_title(self.filename)
            self.statusbar.update_status('saved')
        except Exception as e:
            print(e)
            
    #On exiting the Program
    def quit_save(self):
        try:
            os.path.isfile(self.filename)
            self.save()          
        except:
            self.save_as()
        quit()
                        

    def on_closing(self):
        message = tk.messagebox.askyesnocancel("Save On Close", "Do you want to save the changes before closing?")
        if message == True:
            self.quit_save()
        elif message == False:
            quit()
        else:
            return

    # running the python file
    def run(self, *args):
        try:
            if self.filename[-3:] == '.py':
                #run separate commands for different os
                if os.name == 'nt':
                    os.system(f'start cmd.exe @cmd /k "python {self.filename}"')
                else:
                    os.system(f"gnome-terminal -- python3.8 {self.filename}")
            else:
                self.statusbar.update_status('no python')
        except TypeError:
            self.statusbar.update_status('no file run')

    # opens the main setting file of the editor
    def open_settings_file(self):
        self.filename = 'config/settings.yaml'
        self.textarea.delete(1.0, tk.END)
        with open(self.filename, 'r') as f:
            self.textarea.insert(1.0, f.read())
        self.syntax_highlighter.initial_highlight()
        self.set_window_title(name=self.filename)

    # reset the settings set by the user to the default settings
    def reset_settings_file(self):
        self.reconfigure_settings(overwrite_with_default=True)
        self.clear_and_replace_textarea()
        self.syntax_highlighter.initial_highlight()

    # select all written text in the editor
    def select_all_text(self, *args):
        self.textarea.tag_add(tk.SEL, '1.0', tk.END)
        self.textarea.mark_set(tk.INSERT, '1.0')
        self.textarea.see(tk.INSERT)
        return 'break'

    # give hex colors to the file content for better understanding
    def apply_hex_color(self, key_event):
        new_color = self.menubar.open_color_picker()
        try:
            sel_start = self.textarea.index(tk.SEL_FIRST)
            sel_end = self.textarea.index(tk.SEL_LAST)
            self.textarea.delete(sel_start, sel_end)
            self.textarea.insert(sel_start, new_color)
        except tk.TclError:
            pass

    def _on_change(self, key_event):
        self.linenumbers.redraw()

    def _on_mousewheel(self, event):
        if self.control_key:
            self.change_font_size(1 if event.delta > 0 else -1)

    def _on_linux_scroll_up(self, _):
        if self.control_key:
            self.change_font_size(1)
            if self.filename == 'config/settings.yaml':
                self.syntax_highlighter.initial_highlight()

    def _on_linux_scroll_down(self, _):
        if self.control_key:
            self.change_font_size(-1)
            if self.filename == 'config/settings.yaml':
                self.syntax_highlighter.initial_highlight()

    def change_font_size(self, delta):
        self.font_size = self.font_size + delta
        min_font_size = 6
        self.font_size = min_font_size if self.font_size < min_font_size else self.font_size
        self.font_style = tk_font.Font(family=self.font_family,
                                       size=self.font_size)

        self.italics = tk_font.Font(family=self.font_family,
                                    size=self.font_size,
                                    slant='italic')

        self.textarea.configure(font=self.font_style)
        self.syntax_highlighter.text.tag_configure("Token.Name.Builtin.Pseudo",font=self.italics)
        self.set_new_tab_width()
        
        _settings = self.loader.load_settings_data()
        _settings['font_size'] = self.font_size
        self.loader.store_settings_data(_settings)

        if self.filename == 'config/settings.yaml':
            self.clear_and_replace_textarea()


    # control_l = 37
    # control_r = 109
    # mac_control = 262401 #control key in mac keyboard
    # mac_control_l = 270336 #tk.LEFT control key in mac os with normal keyboard
    # mac_control_r = 262145 #tk.RIGHT control key in mac os with normal keyboard
    def _on_keydown(self, event):
        if event.keycode in [17, 37, 109, 262401, 270336, 262145]:
            self.control_key = True
            self.textarea.isControlPressed = True
        else:
            self.statusbar.update_status('hide')

    def syntax_highlight(self, *args):
        self.syntax_highlighter.default_highlight()
        if not self.tags_configured:
            self.syntax_highlighter.syntax_theme_configuration()
        self.control_key = False
        self.textarea.isControlPressed = False

    def show_find_window(self, event=None):
        FindWindow(self.textarea)
        self.control_key = False
        self.textarea.isControlPressed = False

    def select_all(self):
        self.selection_set(0, 'end')

    def autoclose_base(self, symbol):
        index = self.textarea.index(tk.INSERT)
        self.textarea.insert(index, symbol)
        self.textarea.mark_set(tk.INSERT, index)

    def autoclose_parentheses(self, event):
        if self.autoclose_parentheses:
            self.autoclose_base(')')

    def autoclose_curly_brackets(self, event):
        if self.autoclose_curlybraces:
            self.autoclose_base('}')

    def autoclose_square_brackets(self, event):
        if self.autoclose_squarebrackets:
            self.autoclose_base(']')

    def autoclose_double_quotes(self, event):
        if self.autoclose_doublequotes:
            self.autoclose_base('"')

    def autoclose_single_quotes(self, event):
        if self.autoclose_singlequotes:
            self.autoclose_base("'")

    def auto_indentation(self, event):
        text = self.textarea
        line = text.get('insert linestart', 'insert lineend')
        new_indent = self.get_indent_level(line) * 4
        text.insert('insert', '\n' + ' ' * new_indent)
        return 'break'

    def get_indent_level(self, line):
        num_leading_whitespaces = len(line) - len(line.lstrip())
        return num_leading_whitespaces // 4

    def auto_block_indentation(self, event):
        text = self.textarea
        line = text.get('insert linestart', 'insert lineend')
        match = re.match(r'^(\s+)', line)
        current_indent = len(match.group(0)) if match else 0
        new_indent = current_indent + 4
        text.insert('insert', event.char + '\n' + ' ' * new_indent)
        return 'break'


    def get_chars_in_front_and_back(self):
        index = self.textarea.index(tk.INSERT)
        first_pos = f'{str(index)}-1c'
        end_second_pos = f'{str(index)}+1c'
        first_char = self.textarea.get(first_pos, index)
        second_char = self.textarea.get(index, end_second_pos)
        return (first_char, second_char, index, end_second_pos)
        
    def backspace_situations(self, event):
        first_char, second_char, index, end_second_pos = self.get_chars_in_front_and_back()

        if first_char == "'" and second_char == "'":
            self.textarea.delete(index, end_second_pos)
        elif first_char == '"' and second_char == '"':
            self.textarea.delete(index, end_second_pos)
        elif first_char == '(' and second_char == ')':
            self.textarea.delete(index, end_second_pos)
        elif first_char == '{' and second_char == '}':
            self.textarea.delete(index, end_second_pos)
        elif first_char == '[' and second_char == ']':
            self.textarea.delete(index, end_second_pos)

    def hide_and_unhide_menubar(self, key_event):
        if self.menu_hidden:
            self.menubar.show_menu()
        else:
            self.menubar.hide_menu()
        self.menu_hidden = not self.menu_hidden

    def tab_text(self, event):
        index = self.textarea.index("sel.first linestart")
        last = self.textarea.index("sel.last linestart")
        if last != index:
            while self.textarea.compare(index,"<=", last):
                self.textarea.insert(index, '\t')
                index = self.textarea.index("%s + 1 line" % index)
        else:
            index = self.textarea.index(tk.INSERT)
            self.textarea.insert(index, '\t')
        return "break"


    def bind_shortcuts(self, *args):
        text = self.textarea
        text.bind('<Control-n>', self.new_file)
        text.bind('<Control-o>', self.open_file)
        text.bind('<Control-s>', self.save)
        text.bind('<Control-S>', self.save_as)
        text.bind('<Control-b>', self.context_menu.bold)
        text.bind('<Control-h>', self.context_menu.hightlight)
        text.bind('<Control-a>', self.select_all_text)
        text.bind('<Control-m>', self.apply_hex_color)
        text.bind('<Control-r>', self.run)
        text.bind('<Control-q>', self.enter_quiet_mode)
        text.bind('<Control-f>', self.show_find_window)
        text.bind('<Control-z>', self.textarea.edit_undo())
        text.bind('<Control-Shift-z', self.textarea.edit_redo())
        text.bind('<Escape>', self.leave_quiet_mode)
        text.bind('<<Change>>', self._on_change)
        text.bind('<Configure>', self._on_change)
        text.bind('<Button-3>', self.context_menu.popup)
        text.bind('<MouseWheel>', self._on_mousewheel)
        text.bind('<Button-4>', self._on_linux_scroll_up)
        text.bind('<Button-5>', self._on_linux_scroll_down)
        text.bind('<Key>', self._on_keydown)
        text.bind('<KeyRelease>', self.syntax_highlight)
        text.bind_all('<<Paste>>', self.context_menu.paste)
        text.bind('<Shift-asciitilde>', self.syntax_highlighter.initial_highlight)
        text.bind('<Control-Shift-KeyRelease>', self.syntax_highlighter.initial_highlight)
        text.bind('<Shift-parenleft>', self.autoclose_parentheses)
        text.bind('<bracketleft>', self.autoclose_square_brackets)
        text.bind('<quoteright>', self.autoclose_single_quotes)
        text.bind('<quotedbl>', self.autoclose_double_quotes)
        text.bind('<braceleft>', self.autoclose_curly_brackets)
        text.bind('<Return>', self.auto_indentation)
        text.bind('<Shift-colon>', self.auto_block_indentation)
        text.bind('<BackSpace>', self.backspace_situations)
        text.bind('<Alt_L>', self.hide_and_unhide_menubar)
        text.bind('<Control-L>', self.toggle_linenumbers)
        text.bind('<KeyPress-Tab>', self.tab_text)