Created
October 25, 2016 19:36
-
-
Save sebastiaanfranken/a13c0933a24fc7890afd18c7215cf6ba to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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