Skip to content

Instantly share code, notes, and snippets.

@sebastiaanfranken
Created October 25, 2016 19:36
Show Gist options
  • Save sebastiaanfranken/a13c0933a24fc7890afd18c7215cf6ba to your computer and use it in GitHub Desktop.
Save sebastiaanfranken/a13c0933a24fc7890afd18c7215cf6ba to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3.5
import os
import sys
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
gi.require_version("Notify", "0.7")
gi.require_version("GtkSource", "3.0")
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import Notify
from gi.repository import GtkSource
from gi.repository import Gio
from configparser import ConfigParser
APP_NAME = "Editor"
DEBUG = True
# Configuration skeleton. Get's used in the configuration file
# later on
CONFIG_FILE_SKELETON = """[window]
dark-mode = false
show-actionbar = false
global-actionmenu = true
[editor]
editable = false
use-monospace-font = true
highlight-current-line = false
show-line-numbers = true
auto-indent = false
spaces-instead-of-tabs = false
"""
def print_r(message):
import datetime
print("[%s] %s" % (datetime.datetime.now().strftime("%X"), message))
class Settings():
def __init__(self, configFolder):
directory = os.path.join(os.path.expanduser("~"), ".config", configFolder)
if not os.path.exists(directory):
os.makedirs(directory)
self.configfile = os.path.join(directory, "settings.ini")
if not os.path.isfile(self.configfile):
with open(self.configfile, "w") as cfg:
cfg.write(CONFIG_FILE_SKELETON)
self.config = ConfigParser()
self.config.read(self.configfile)
def getConfig(self):
return self.config
def save(self):
with open(self.configfile, "w") as cfg:
self.config.write(cfg)
class Editor(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
# Bootstrap the editor settings
self.configparser = Settings(APP_NAME.lower())
self.config = self.configparser.getConfig()
# Bootstrap the windows' settings
self.get_settings().set_property("gtk-application-prefer-dark-theme", self.config.getboolean("window", "dark-mode"))
# Bootstrap the notification system
Notify.init(APP_NAME)
self.notification = None
if os.environ.get("XDG_SESSION_TYPE") == "x11":
screen = self.get_screen()
monitor = screen.get_monitor_at_window(screen.get_active_window())
geometry = screen.get_monitor_geometry(monitor)
width = (geometry.width / 100) * 50
height = (width / 100) * 75
self.set_size_request(width, height)
self.set_wmclass(APP_NAME, APP_NAME)
else:
self.set_size_request(1024, 768)
self.set_icon_name("edit-clear-all-symbolic")
self.set_title(APP_NAME)
self.set_position(Gtk.WindowPosition.CENTER)
# Build the headerbar
self.headerbar = Gtk.HeaderBar()
self.headerbar.set_title(self.get_title())
self.headerbar.set_show_close_button(True)
self.set_titlebar(self.headerbar)
self.headerbar.pack_start(Gtk.Image.new_from_icon_name(self.get_icon_name(), Gtk.IconSize.BUTTON))
if self.config.getboolean("window", "global-actionmenu"):
self.headerbar.pack_start(Gtk.Separator())
# Define the Gtk.Notebook which is going to house all pages,
# define the Gtk.Box that's going to house that and add that
# to the main window
self.notebook = Gtk.Notebook()
self.body = Gtk.Box(orientation = Gtk.Orientation.VERTICAL)
self.add(self.body)
# Build the main editor
self.buildEditor()
# Build the actionbar
self.buildActionbar()
# Build the menu last
self.buildMenu()
if DEBUG:
handle = open(__file__, "r")
contents = handle.read()
handle.close()
self.buffer.set_text(contents)
self.buffer.set_language(self.languages.guess_language(filename = __file__))
self.editor.set_buffer(self.buffer)
# Connect signals
self.connect("destroy", Gtk.main_quit)
# Show everything
self.show_all()
def createNotification(self, title, message, type = "dialog-information"):
if self.notification is not None:
self.notification.close()
self.notification = Notify.Notification.new(title, message, type)
self.notification.show()
self.notification = None
def buildEditor(self):
self.languages = GtkSource.LanguageManager()
self.buffer = GtkSource.Buffer()
self.editor = GtkSource.View()
# Bootstrap editor settings
self.editor.set_monospace(self.config.getboolean("editor", "use-monospace-font"))
self.editor.set_highlight_current_line(self.config.getboolean("editor", "highlight-current-line"))
self.editor.set_show_line_numbers(self.config.getboolean("editor", "show-line-numbers"))
self.editor.set_editable(self.config.getboolean("editor", "editable"))
self.editor.set_cursor_visible(self.config.getboolean("editor", "editable"))
self.editor.set_auto_indent(self.config.getboolean("editor", "auto-indent"))
self.editor.set_insert_spaces_instead_of_tabs(self.config.getboolean("editor", "spaces-instead-of-tabs"))
# Create a revealer for the Gtk.Label housing the "read only" message
self.infobarRevealer = Gtk.Revealer()
self.infobarRevealer.set_reveal_child(not self.editor.get_editable())
self.body.pack_start(self.infobarRevealer, False, False, 0)
infobar = Gtk.InfoBar()
infobar.set_show_close_button(False)
infobar.get_content_area().add(Gtk.Label(label = "Dit bestand is alleen-lezen"))
self.infobarRevealer.add(infobar)
scrolled = Gtk.ScrolledWindow()
scrolled.add(self.editor)
self.body.pack_start(scrolled, True, True, 0)
def buildActionbar(self):
self.revealer = Gtk.Revealer()
self.revealer.set_reveal_child(self.config.getboolean("window", "show-actionbar"))
self.body.pack_end(self.revealer, False, False, 0)
self.actionbar = Gtk.ActionBar()
self.actionbar.set_hexpand(True)
self.revealer.add(self.actionbar)
self.filelabel = Gtk.Label(label = __file__)
self.actionbar.pack_start(self.filelabel)
def buildMenu(self):
popoverToggle = Gtk.MenuButton(label = None, image = Gtk.Image.new_from_icon_name("open-menu-symbolic", Gtk.IconSize.BUTTON))
self.headerbar.pack_end(popoverToggle)
self.popover = Gtk.Popover()
self.popover.set_relative_to(popoverToggle)
self.popover.set_border_width(10)
popoverToggle.set_popover(self.popover)
# Main box
box = Gtk.Box(orientation = Gtk.Orientation.VERTICAL, spacing = 5)
# Submenu
submenu = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
Gtk.StyleContext.add_class(submenu.get_style_context(), "linked")
btnNew = Gtk.Button(label = None, image = Gtk.Image.new_from_icon_name("document-new-symbolic", Gtk.IconSize.BUTTON))
submenu.pack_start(btnNew, True, True, 0)
btnOpen = Gtk.Button(label = None, image = Gtk.Image.new_from_icon_name("document-open-symbolic", Gtk.IconSize.BUTTON))
submenu.pack_start(btnOpen, True, True, 0)
btnSave = Gtk.Button(label = None, image = Gtk.Image.new_from_icon_name("document-save-symbolic", Gtk.IconSize.BUTTON))
submenu.pack_start(btnSave, True, True, 0)
# Pack the submenu into the main menu
if self.config.getboolean("window", "global-actionmenu"):
self.headerbar.pack_start(submenu)
else:
box.add(submenu)
box.add(Gtk.Separator())
# Add menu buttons
box.add(self.createCheckButton("Alleen-lezen", self.onCheckReadonly, not self.config.getboolean("editor", "editable")))
box.add(self.createCheckButton("Toon regelnummers", self.onCheckShowLinenumbers, self.config.getboolean("editor", "show-line-numbers")))
box.add(self.createCheckButton("Monospace lettertypen gebruiken", self.onCheckMonospace, self.config.getboolean("editor", "use-monospace-font")))
box.add(self.createCheckButton("Markeer huidige regel", self.onCheckHighlightCurrentLine, self.config.getboolean("editor", "highlight-current-line")))
box.add(self.createCheckButton("Automatisch inspringen", self.onCheckAutoIndent, self.config.getboolean("editor", "auto-indent")))
box.add(self.createCheckButton("Spaties invoegen inplaats van tabs", self.onCheckSpacesInsteadOfTabs, self.config.getboolean("editor", "spaces-instead-of-tabs")))
box.add(Gtk.Separator())
box.add(self.createCheckButton("Toon metadata", self.onCheckShowActionbar, self.config.getboolean("window", "show-actionbar")))
box.add(self.createCheckButton("Donkere UI", self.onCheckDarkUI, self.config.getboolean("window", "dark-mode")))
box.add(self.createCheckButton("Globaal actiemenu", self.onCheckGlobalMenu, self.config.getboolean("window", "global-actionmenu")))
box.add(self.createSplitButton("Over", Gtk.Image.new_from_icon_name("dialog-question-symbolic", Gtk.IconSize.BUTTON), True, self.onClickAbout))
box.show_all()
self.popover.add(box)
def createCheckButton(self, label, callback = None, active = False):
button = Gtk.CheckButton(label = label)
button.set_active(active)
if callback:
button.connect("clicked", callback)
return button
def setAndSaveConfig(self, hyve, key, value):
self.config.set(hyve, key, value)
self.configparser.save()
def createSplitButton(self, label, image, imageFirst = True, callback = None):
box = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL)
if imageFirst:
box.pack_start(image, False, False, 0)
else:
box.pack_end(image, False, False, 0)
box.pack_start(Gtk.Label(label = label), True, True, 0)
button = Gtk.Button()
button.add(box)
if callback:
button.connect("clicked", callback)
return button
"""Callbacks"""
def onCheckReadonly(self, button):
current = self.editor.get_editable()
self.editor.set_editable(not current)
self.editor.set_cursor_visible(not current)
self.setAndSaveConfig("editor", "editable", str(not current).lower())
self.infobarRevealer.set_reveal_child(current)
def onCheckMonospace(self, button):
current = self.editor.get_monospace()
self.editor.set_monospace(not current)
self.setAndSaveConfig("editor", "use-monospace-font", str(not current).lower())
def onCheckHighlightCurrentLine(self, button):
current = self.editor.get_highlight_current_line()
self.editor.set_highlight_current_line(not current)
self.setAndSaveConfig("editor", "highlight-current-line", str(not current).lower())
def onCheckShowLinenumbers(self, button):
current = self.editor.get_show_line_numbers()
self.editor.set_show_line_numbers(not current)
self.setAndSaveConfig("editor", "show-line-numbers", str(not current).lower())
def onCheckShowActionbar(self, button):
current = self.revealer.get_reveal_child()
self.revealer.set_reveal_child(not current)
self.setAndSaveConfig("window", "show-actionbar", str(not current).lower())
image = "go-down-symbolic" if self.config.getboolean("window", "show-actionbar") else "go-up-symbolic"
button.set_image(Gtk.Image.new_from_icon_name(image, Gtk.IconSize.BUTTON))
def onCheckDarkUI(self, button):
settings = self.get_settings()
current = settings.get_property("gtk-application-prefer-dark-theme")
settings.set_property("gtk-application-prefer-dark-theme", not current)
self.setAndSaveConfig("window", "dark-mode", str(not current).lower())
def onCheckAutoIndent(self, button):
current = self.editor.get_auto_indent()
self.editor.set_auto_indent(not current)
self.setAndSaveConfig("editor", "auto-indent", str(not current).lower())
def onCheckSpacesInsteadOfTabs(self, button):
current = self.editor.get_insert_spaces_instead_of_tabs()
self.editor.set_insert_spaces_instead_of_tabs(not current)
self.setAndSaveConfig("editor", "spaces-instead-of-tabs", str(not current).lower())
def onCheckGlobalMenu(self, button):
current = self.config.getboolean("window", "global-actionmenu")
self.setAndSaveConfig("window", "global-actionmenu", str(not current).lower())
self.createNotification("Opnieuw opstarten nodig", "Om deze wijziging door te voeren moet je de editor opnieuw starten.")
# Uh-ooh?
# self.destroy()
def onClickAbout(self, button):
about = Gtk.AboutDialog()
about.set_authors(["Sebastiaan Franken"])
about.show()
if __name__ == "__main__":
Gtk.init(sys.argv)
editor = Editor()
Gtk.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment