User Controls

Trying to build a simple IDE/Text editor in Python.

  1. #1
    Sophie Pedophile Tech Support
    I am using GTK/PyGTK

    Of all the GUI languages/frameworks/modules i find GTK to be the most forgiving. And so i am working on a simple text editor/IDE in preparation or another much bigger project. So far i got this, from examples and documentation and such.


    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk, Pango

    class SearchDialog(Gtk.Dialog):

    def __init__(self, parent):
    Gtk.Dialog.__init__(self, "Search", parent,
    Gtk.DialogFlags.MODAL, buttons=(
    Gtk.STOCK_FIND, Gtk.ResponseType.OK,
    Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL))

    box = self.get_content_area()

    label = Gtk.Label("Insert text you want to search for:")
    box.add(label)

    self.entry = Gtk.Entry()
    box.add(self.entry)

    self.show_all()

    class TextViewWindow(Gtk.Window):

    def __init__(self):
    Gtk.Window.__init__(self, title="TextView Example")

    self.set_default_size(-1, 350)

    self.grid = Gtk.Grid()
    self.add(self.grid)

    self.create_textview()
    self.create_toolbar()
    self.create_buttons()

    def create_toolbar(self):
    toolbar = Gtk.Toolbar()
    self.grid.attach(toolbar, 0, 0, 3, 1)

    button_bold = Gtk.ToolButton()
    button_bold.set_icon_name("format-text-bold-symbolic")
    toolbar.insert(button_bold, 0)

    button_italic = Gtk.ToolButton()
    button_italic.set_icon_name("format-text-italic-symbolic")
    toolbar.insert(button_italic, 1)

    button_underline = Gtk.ToolButton()
    button_underline.set_icon_name("format-text-underline-symbolic")
    toolbar.insert(button_underline, 2)

    button_bold.connect("clicked", self.on_button_clicked, self.tag_bold)
    button_italic.connect("clicked", self.on_button_clicked,
    self.tag_italic)
    button_underline.connect("clicked", self.on_button_clicked,
    self.tag_underline)

    toolbar.insert(Gtk.SeparatorToolItem(), 3)

    radio_justifyleft = Gtk.RadioToolButton()
    radio_justifyleft.set_icon_name("format-justify-left-symbolic")
    toolbar.insert(radio_justifyleft, 4)

    radio_justifycenter = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
    radio_justifycenter.set_icon_name("format-justify-center-symbolic")
    toolbar.insert(radio_justifycenter, 5)

    radio_justifyright = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
    radio_justifyright.set_icon_name("format-justify-right-symbolic")
    toolbar.insert(radio_justifyright, 6)

    radio_justifyfill = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
    radio_justifyfill.set_icon_name("format-justify-fill-symbolic")
    toolbar.insert(radio_justifyfill, 7)

    radio_justifyleft.connect("toggled", self.on_justify_toggled,
    Gtk.Justification.LEFT)
    radio_justifycenter.connect("toggled", self.on_justify_toggled,
    Gtk.Justification.CENTER)
    radio_justifyright.connect("toggled", self.on_justify_toggled,
    Gtk.Justification.RIGHT)
    radio_justifyfill.connect("toggled", self.on_justify_toggled,
    Gtk.Justification.FILL)

    toolbar.insert(Gtk.SeparatorToolItem(), 8)

    button_clear = Gtk.ToolButton()
    button_clear.set_icon_name("edit-clear-symbolic")
    button_clear.connect("clicked", self.on_clear_clicked)
    toolbar.insert(button_clear, 9)

    toolbar.insert(Gtk.SeparatorToolItem(), 10)

    button_search = Gtk.ToolButton()
    button_search.set_icon_name("system-search-symbolic")
    button_search.connect("clicked", self.on_search_clicked)
    toolbar.insert(button_search, 11)

    def create_textview(self):
    scrolledwindow = Gtk.ScrolledWindow()
    scrolledwindow.set_hexpand(True)
    scrolledwindow.set_vexpand(True)
    self.grid.attach(scrolledwindow, 0, 1, 3, 1)

    self.textview = Gtk.TextView()
    self.textbuffer = self.textview.get_buffer()
    self.textbuffer.set_text("This is some text inside of a Gtk.TextView. "
    + "Select text and click one of the buttons 'bold', 'italic', "
    + "or 'underline' to modify the text accordingly.")
    scrolledwindow.add(self.textview)

    self.tag_bold = self.textbuffer.create_tag("bold",
    weight=Pango.Weight.BOLD)
    self.tag_italic = self.textbuffer.create_tag("italic",
    style=Pango.Style.ITALIC)
    self.tag_underline = self.textbuffer.create_tag("underline",
    underline=Pango.Underline.SINGLE)
    self.tag_found = self.textbuffer.create_tag("found",
    background="yellow")

    def create_buttons(self):
    check_editable = Gtk.CheckButton("Editable")
    check_editable.set_active(True)
    check_editable.connect("toggled", self.on_editable_toggled)
    self.grid.attach(check_editable, 0, 2, 1, 1)

    check_cursor = Gtk.CheckButton("Cursor Visible")
    check_cursor.set_active(True)
    check_editable.connect("toggled", self.on_cursor_toggled)
    self.grid.attach_next_to(check_cursor, check_editable,
    Gtk.PositionType.RIGHT, 1, 1)

    radio_wrapnone = Gtk.RadioButton.new_with_label_from_widget(None,
    "No Wrapping")
    self.grid.attach(radio_wrapnone, 0, 3, 1, 1)

    radio_wrapchar = Gtk.RadioButton.new_with_label_from_widget(
    radio_wrapnone, "Character Wrapping")
    self.grid.attach_next_to(radio_wrapchar, radio_wrapnone,
    Gtk.PositionType.RIGHT, 1, 1)

    radio_wrapword = Gtk.RadioButton.new_with_label_from_widget(
    radio_wrapnone, "Word Wrapping")
    self.grid.attach_next_to(radio_wrapword, radio_wrapchar,
    Gtk.PositionType.RIGHT, 1, 1)

    radio_wrapnone.connect("toggled", self.on_wrap_toggled,
    Gtk.WrapMode.NONE)
    radio_wrapchar.connect("toggled", self.on_wrap_toggled,
    Gtk.WrapMode.CHAR)
    radio_wrapword.connect("toggled", self.on_wrap_toggled,
    Gtk.WrapMode.WORD)

    def on_button_clicked(self, widget, tag):
    bounds = self.textbuffer.get_selection_bounds()
    if len(bounds) != 0:
    start, end = bounds
    self.textbuffer.apply_tag(tag, start, end)

    def on_clear_clicked(self, widget):
    start = self.textbuffer.get_start_iter()
    end = self.textbuffer.get_end_iter()
    self.textbuffer.remove_all_tags(start, end)

    def on_editable_toggled(self, widget):
    self.textview.set_editable(widget.get_active())

    def on_cursor_toggled(self, widget):
    self.textview.set_cursor_visible(widget.get_active())

    def on_wrap_toggled(self, widget, mode):
    self.textview.set_wrap_mode(mode)

    def on_justify_toggled(self, widget, justification):
    self.textview.set_justification(justification)

    def on_search_clicked(self, widget):
    dialog = SearchDialog(self)
    response = dialog.run()
    if response == Gtk.ResponseType.OK:
    cursor_mark = self.textbuffer.get_insert()
    start = self.textbuffer.get_iter_at_mark(cursor_mark)
    if start.get_offset() == self.textbuffer.get_char_count():
    start = self.textbuffer.get_start_iter()

    self.search_and_mark(dialog.entry.get_text(), start)

    dialog.destroy()

    def search_and_mark(self, text, start):
    end = self.textbuffer.get_end_iter()
    match = start.forward_search(text, 0, end)

    if match is not None:
    match_start, match_end = match
    self.textbuffer.apply_tag(self.tag_found, match_start, match_end)
    self.search_and_mark(text, match_end)

    win = TextViewWindow()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()


    But the thing i am missing is syntax highlighting and i have no clue how i will go about making that happen for the statements i want. the only clue as to how to go about this comes from this piece of the code i posted above.



    def search_and_mark(self, text, start):
    end = self.textbuffer.get_end_iter()
    match = start.forward_search(text, 0, end)

    if match is not None:
    match_start, match_end = match
    self.textbuffer.apply_tag(self.tag_found, match_start, match_end)
    self.search_and_mark(text, match_end)


    In case you are interested, running what i have currently will look a little something like this.



    Any help would be greatly appreciated.
    The following users say it would be alright if the author of this post didn't die in a fire!
  2. #2
    gadzooks Dark Matter [keratinize my mild-tasting blossoming]
    I admire the ambition.

    As long as your primary goal is one of personal experimentation and learning, I'd say it might be well worth it. It's textbook wheel-reinvention, but I'm a consummate wheel re-inventor myself, so I actually really do get that.

    If it's simply about achieving better customization - which is also a fairly reasonable justification since DIY really does give you maximum customization potential - I would recommend at least considering how much work you have ahead of you when weighed against the potential benefit. The learning curve to achieve extreme efficiency with an existing full-featured IDE with years of multi-contributor releases might in all likelihood be dramatically shorter.

    But if you do decide to follow through, let us know how it turns out.

    Who knows.,. it could turn into a sizeable open-source repo that could rival JetBrains and Visual Studio and all that.
  3. #3
    gadzooks Dark Matter [keratinize my mild-tasting blossoming]
    Originally posted by Sophie But the thing i am missing is syntax highlighting and i have no clue how i will go about making that happen for the statements i want. the only clue as to how to go about this comes from this piece of the code i posted above.



    def search_and_mark(self, text, start):
    end = self.textbuffer.get_end_iter()
    match = start.forward_search(text, 0, end)

    if match is not None:
    match_start, match_end = match
    self.textbuffer.apply_tag(self.tag_found, match_start, match_end)
    self.search_and_mark(text, match_end)

    For syntax highlighting, I would imagine that it would require real-time syntax parsing? I dunno for sure. I'm not particularly familiar with GTK, nor have I gone through all your code above with a fine-toothed comb yet, so maybe there is another possible approach, but right now the only method I can think of is real-time parsing of the entire string contained in the TextViewWindow.

    Parsing, while not super straightforward, is also not some arcane, out-of-reach mystery. I've played around (just a little bit) with some decent AST libraries for Python that allow you to define custom syntax.,. syntaxes? Is that the plural for syntax? Anyway, they allow you to define your own languages, or use pre-existing definitions. Obviously languages like C++, C#, Java, etc, have such definitions already out there, so you don't have to literally reverse engineer entire languages or anything like that. But, with a little bit of work and experimentation, you should be able to apply your own custom parsing capabilities in your IDE.

    Then you just need to decide how frequently to do this parsing (with each keystroke? at a certain regular time interval? etc) - a decision that will greatly impact performance.

    Then you will just need to apply the actual text modification (font-color, squiggly red underlines, whatever) using built-in GTK methods.
  4. #4
    Sophie Pedophile Tech Support
    The big project this is a part of is a macro engine, i intend to create a simple language with which you can use certain statements and logic operators to automate your OS or parts of it. I need syntax highlighting for the statements and logic operators and such. With PyGTK i think you can set tags, so if the input in the text field matches one of the tags it changes the color of the words you have defined.
Jump to Top