Initial push

This commit is contained in:
2023-10-22 20:14:19 -05:00
parent f82ac6b67e
commit 8aa3d96617
69 changed files with 3650 additions and 61 deletions

3
src/core/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
"""
Core Module
"""

75
src/core/controller.py Normal file
View File

@@ -0,0 +1,75 @@
# Python imports
import os
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from .mixins.signals_mixins import SignalsMixins
from .controller_data import ControllerData
from .widgets.transparency_scale import TransparencyScale
class Controller(SignalsMixins, ControllerData):
def __init__(self, args, unknownargs):
self.setup_controller_data()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
for arg in unknownargs + [args.new_tab,]:
if os.path.isfile(arg):
message = f"FILE|{arg}"
event_system.emit("post_file_to_ipc", message)
logger.info(f"Made it past {self.__class__} loading...")
def _setup_styling(self):
...
def _setup_signals(self):
self.window.connect("focus-out-event", self.unset_keys_and_data)
self.window.connect("key-press-event", self.on_global_key_press_controller)
self.window.connect("key-release-event", self.on_global_key_release_controller)
def _subscribe_to_events(self):
event_system.subscribe("shutting_down", lambda: print("Shutting down..."))
event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
event_system.subscribe("remove_transparency", self._remove_transparency)
event_system.subscribe("update_transparency", self._update_transparency)
def setup_builder_and_container(self):
self.builder = Gtk.Builder()
self.builder.add_from_file(settings_manager.get_glade_file())
self.builder.expose_object("main_window", self.window)
settings_manager.set_builder(self.builder)
glade_box = self.builder.get_object("glade_box")
self.base_container = glade_box
self.ctx = self.base_container.get_style_context()
self.ctx.add_class(f"mw_transparency_{settings.theming.transparency}")
settings_manager.register_signals_to_builder([self, self.base_container])
TransparencyScale()
self.window.add(glade_box)
def _interactive_debug(self, widget = None, eve = None):
event_system.emit("load_interactive_debug")
def _remove_transparency(self):
self.ctx.remove_class(f"mw_transparency_{settings.theming.transparency}")
def _update_transparency(self):
self.ctx.add_class(f"mw_transparency_{settings.theming.transparency}")
def _close_app(self):
event_system.emit("tear_down")

174
src/core/controller_data.py Normal file
View File

@@ -0,0 +1,174 @@
# Python imports
import os
import subprocess
import collections
import tempfile
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GtkSource
# Application imports
class ControllerData:
''' ControllerData contains most of the state of the app at ay given time. It also has some support methods. '''
def setup_controller_data(self) -> None:
# Needed hack to get GtkSourceView widget to run from glade file...
GtkSource.View()
self.window = settings_manager.get_main_window()
self.builder = None
self.base_container = None
self.setup_builder_and_container()
self.was_midified_key = False
self.ctrl_down = False
self.shift_down = False
self.alt_down = False
self.language_id = 'c'
self.language_name = 'C'
self.black_color = Gdk.color_parse('#000000');
self.gui_style_ids = [
'bracket-match', 'bracket-mismatch',
'current-line', 'cursor', 'line-numbers',
'secondary-cursor', 'selection',
'selection-unfocused', 'text'
]
self.styles_list = self.builder.get_object('styles_list')
self.languages_list = self.builder.get_object('languages_list')
self.languages_dropdown = self.builder.get_object('languages_dropdown')
self.styles_treeview = self.builder.get_object('styles_treeview')
self.bold_tggle_btn = self.builder.get_object('bold_tggle_btn')
self.italic_tggle_btn = self.builder.get_object('italic_tggle_btn')
self.underline_tggle_btn = self.builder.get_object('underline_tggle_btn')
self.strikethrough_tggle_btn = self.builder.get_object('strikethrough_tggle_btn')
self.fr_check_btn = self.builder.get_object('fr_check_btn')
self.br_check_btn = self.builder.get_object('br_check_btn')
self.fr_color_btn = self.builder.get_object('fr_color_btn')
self.br_color_btn = self.builder.get_object('br_color_btn')
self.reset_btn = self.builder.get_object('reset_btn')
self.name_entry = self.builder.get_object('name_entry')
self.id_entry = self.builder.get_object('id_entry')
self.desc_entry = self.builder.get_object('desc_entry')
self.authr_entry = self.builder.get_object('authr_entry')
self.sample_lbl = self.builder.get_object('sample_lbl')
self.src_buffer = GtkSource.Buffer(max_undo_levels = 0)
self.src_preview = self.builder.get_object('src_preview')
self.scheme_manager = GtkSource.StyleSchemeManager()
self.language_manager = GtkSource.LanguageManager()
self.all_styles_dict = collections.OrderedDict()
self.language = self.language_manager.get_language(self.language_id)
self.current_lang_id = self.language_id
self.current_lang_name = self.language_name
self.orig_scheme_file = None
self.bold_tggl_handler = self.get_handler_id(self.bold_tggle_btn, 'toggled')
self.italic_tggl_handler = self.get_handler_id(self.italic_tggle_btn, 'toggled')
self.underline_tggl_handler = self.get_handler_id(self.underline_tggle_btn, 'toggled')
self.strikethrough_tggl_handler = self.get_handler_id(self.strikethrough_tggle_btn, 'toggled')
self.foreground_tggl_handler = self.get_handler_id(self.fr_check_btn, 'toggled')
self.background_tggl_handler = self.get_handler_id(self.br_check_btn, 'toggled')
# watch temp directory to help the sample viewer
self.scheme_manager.append_search_path( tempfile.gettempdir() )
self.src_preview.set_buffer(self.src_buffer)
self.load_initial_data()
def load_initial_data(self, from_file = None):
if from_file and os.path.isfile(from_file):
self.load_scheme(from_file)
else:
self.load_scheme('cobalt')
languages = self.language_manager.get_language_ids()
languages.sort()
self.lang_map_name_to_id = {}
self.languages_list.append(['Default styles'])
self.lang_map_name_to_id['Default styles'] = 'def'
for lang in languages:
lang_name = self.language_manager.get_language(lang).get_name()
self.lang_map_name_to_id[lang_name] = lang
if lang_name != 'Defaults':
self.languages_list.append([lang_name])
self.styles_treeview_selection = self.styles_treeview.get_selection()
self.styles_treeview_selection.connect('changed', self.on_style_selected)
# select the first language
self.languages_dropdown.set_active(0)
# select the first style
model = self.styles_treeview.get_model()
tree_iter = model.get_iter_first()
self.styles_treeview_selection.select_iter(tree_iter)
self.selected_style_id = model[tree_iter][0]
def get_handler_id(self, obj, signal_name):
signal_id, detail = GObject.signal_parse_name(signal_name, obj, True)
return GObject.signal_handler_find(obj, GObject.SignalMatchType.ID, signal_id, detail, None, None, None)
def get_base_container(self):
return self.base_container
def clear_console(self) -> None:
''' Clears the terminal screen. '''
os.system('cls' if os.name == 'nt' else 'clear')
def call_method(self, _method_name: str, data: type) -> type:
'''
Calls a method from scope of class.
Parameters:
a (obj): self
b (str): method name to be called
c (*): Data (if any) to be passed to the method.
Note: It must be structured according to the given methods requirements.
Returns:
Return data is that which the calling method gives.
'''
method_name = str(_method_name)
method = getattr(self, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}")
return method(*data) if data else method()
def has_method(self, obj: type, method: type) -> type:
''' Checks if a given method exists. '''
return callable(getattr(obj, method, None))
def clear_children(self, widget: type) -> None:
''' Clear children of a gtk widget. '''
for child in widget.get_children():
widget.remove(child)
def get_clipboard_data(self, encoding = "utf-8") -> str:
proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout = subprocess.PIPE)
retcode = proc.wait()
data = proc.stdout.read()
return data.decode(encoding).strip()
def set_clipboard_data(self, data: type, encoding = "utf-8") -> None:
proc = subprocess.Popen(['xclip','-selection','clipboard'], stdin = subprocess.PIPE)
proc.stdin.write(data.encode(encoding))
proc.stdin.close()
retcode = proc.wait()

View File

@@ -0,0 +1,3 @@
"""
Generic Mixins Module
"""

View File

@@ -0,0 +1,3 @@
"""
Signals module
"""

View File

@@ -0,0 +1,17 @@
# Python imports
# Lib imports
# Application imports
class IPCSignalsMixin:
""" IPCSignalsMixin handle messages from another starting solarfm process. """
def print_to_console(self, message=None):
logger.debug(message)
def handle_file_from_ipc(self, path: str) -> None:
logger.debug(f"File From IPC: {path}")

View File

@@ -0,0 +1,94 @@
# Python imports
import re
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
# Application imports
valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]")
class KeyboardSignalsMixin:
""" KeyboardSignalsMixin keyboard hooks controller. """
# TODO: Need to set methods that use this to somehow check the keybindings state instead.
def unset_keys_and_data(self, widget=None, eve=None):
self.ctrl_down = False
self.shift_down = False
self.alt_down = False
def on_global_key_press_controller(self, eve, user_data):
keyname = Gdk.keyval_name(user_data.keyval).lower()
modifiers = Gdk.ModifierType(user_data.get_state() & ~Gdk.ModifierType.LOCK_MASK)
self.was_midified_key = True if modifiers != 0 else False
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
if "control" in keyname:
self.ctrl_down = True
if "shift" in keyname:
self.shift_down = True
if "alt" in keyname:
self.alt_down = True
def on_global_key_release_controller(self, widget, event):
""" Handler for keyboard events """
keyname = Gdk.keyval_name(event.keyval).lower()
modifiers = Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK)
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
should_return = self.was_midified_key and (self.ctrl_down or self.shift_down or self.alt_down)
if "control" in keyname:
self.ctrl_down = False
if "shift" in keyname:
self.shift_down = False
if "alt" in keyname:
self.alt_down = False
# NOTE: In effect a filter after releasing a modifier and we have a modifier mapped
if should_return:
self.was_midified_key = False
return
mapping = keybindings.lookup(event)
logger.debug(f"on_global_key_release_controller > key > {keyname}")
logger.debug(f"on_global_key_release_controller > keyval > {event.keyval}")
logger.debug(f"on_global_key_release_controller > mapping > {mapping}")
if mapping:
# See if in controller scope
try:
getattr(self, mapping)()
return True
except Exception:
# Must be plugins scope, event call, OR we forgot to add method to controller scope
if "||" in mapping:
sender, eve_type = mapping.split("||")
else:
sender = ""
eve_type = mapping
self.handle_key_event_system(sender, eve_type)
else:
logger.debug(f"on_global_key_release_controller > key > {keyname}")
if self.ctrl_down:
if not keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
self.handle_key_event_system(None, mapping)
else:
...
def handle_key_event_system(self, sender, eve_type):
event_system.emit(eve_type)
def keyboard_close_tab(self):
...

View File

@@ -0,0 +1,37 @@
# Python imports
# Lib imports
# Application imports
from utils.style_properties import StyleProperties
from .widget_signals.list_signals_mixin import ListSignalsMixin
from .widget_signals.tggle_and_check_box_signals_mixin import TggleAndCheckBoxSignals
from .widget_signals.sourceview_signals_mixin import SourceviewSignalsMixin
from .widget_signals.dialog_signals_mixin import DialogSignalsMixin
class MainSignalsMixin(ListSignalsMixin, TggleAndCheckBoxSignals, SourceviewSignalsMixin, DialogSignalsMixin):
def on_style_changed(self, data):
if self.selected_style_id not in self.all_styles_dict:
self.all_styles_dict[self.selected_style_id] = StyleProperties()
color_scale = 255.0/65535.0
if data == self.br_color_btn:
color = data.get_color()
self.all_styles_dict[self.selected_style_id].background = ('#%02x%02x%02x' % (color.red * color_scale, color.green * color_scale, color.blue * color_scale))
elif data == self.fr_color_btn:
color = data.get_color()
self.all_styles_dict[self.selected_style_id].foreground = ('#%02x%02x%02x' % (color.red * color_scale, color.green * color_scale, color.blue * color_scale))
elif data == self.bold_tggle_btn:
self.all_styles_dict[self.selected_style_id].bold = data.get_active()
elif data == self.italic_tggle_btn:
self.all_styles_dict[self.selected_style_id].italic = data.get_active()
elif data == self.underline_tggle_btn:
self.all_styles_dict[self.selected_style_id].underline = data.get_active()
elif data == self.strikethrough_tggle_btn:
self.all_styles_dict[self.selected_style_id].strikethrough = data.get_active()
self.update_sample_view()

View File

@@ -0,0 +1,3 @@
"""
Widget signals module
"""

View File

@@ -0,0 +1,139 @@
# Python imports
import os
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class DialogSignalsMixin():
def on_open_clicked(self, param):
file_chooser = Gtk.FileChooserDialog('Open', self.window, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
file_filter = Gtk.FileFilter()
file_filter.set_name('XML files')
file_filter.add_pattern('*.xml')
file_chooser.add_filter(file_filter)
file_filter = Gtk.FileFilter()
file_filter.set_name('All files')
file_filter.add_pattern('*')
file_chooser.add_filter(file_filter)
file_chooser.set_default_response(Gtk.ResponseType.OK)
file_chooser.set_current_folder(os.path.expanduser('~'))
path = None
response = file_chooser.run()
if response == Gtk.ResponseType.OK:
path = file_chooser.get_filename()
if path and not os.access(path, os.R_OK):
abspath = os.path.abspath(path)
self.message_dialog(Gtk.MessageType.ERROR, ('Could not open file "%s"') % abspath, ('The file "%s" could not be opened. ' 'Permission denied.') % abspath, file_chooser)
path = None
file_chooser.destroy()
if not path: return
self.load_scheme(path)
def on_save_clicked(self, param):
if not self.origSchemeFile:
filename = run_save_as_dialog(self.window, self.entryId.get_text() + '.xml')
if filename and not '.' in os.path.basename(filename):
filename = f"{filename}.xml"
if filename:
self.write_scheme(filename, self.entryId.get_text())
self.origSchemeFile = filename
else:
self.write_scheme(self.origSchemeFile, self.entryId.get_text())
def on_save_as_clicked(self, param):
filename = run_save_as_dialog(self.window, self.entryId.get_text() + '.xml')
if filename and not '.' in os.path.basename(filename):
filename = f"{filename}.xml"
if filename:
self.write_scheme(filename, self.entryId.get_text())
self.origSchemeFile = filename
def message_dialog(
dialog_type,
short_msg,
long_msg = None,
parent = None,
buttons = Gtk.ButtonsType.OK,
additional_buttons = None
):
dialog = Gtk.MessageDialog(parent = parent, flags = Gtk.DialogFlags.MODAL, type = dialog_type, buttons = buttons)
if additional_buttons:
dialog.add_buttons(*additional_buttons)
dialog.set_markup(short_msg)
if long_msg:
if isinstance(long_msg, Gtk.Widget):
widget = long_msg
elif isinstance(long_msg, str):
widget = Gtk.Label()
widget.set_markup(long_msg)
else:
raise TypeError('"long_msg" must be a Gtk.Widget or a string')
expander = Gtk.Expander(label = 'Click here for details')
expander.set_border_width(6)
expander.add(widget)
dialog.vbox.pack_end(expander, True, True, 0)
dialog.show_all()
response = dialog.run()
dialog.destroy()
return response
def run_save_as_dialog(parent, current_name):
file_chooser = Gtk.FileChooserDialog('Save As', parent, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
file_filter = Gtk.FileFilter()
file_filter.set_name('XML files')
file_filter.add_pattern('*.xml')
file_chooser.add_filter(file_filter)
file_filter = Gtk.FileFilter()
file_filter.set_name('All files')
file_filter.add_pattern('*')
file_chooser.add_filter(file_filter)
file_chooser.set_default_response(Gtk.ResponseType.OK)
file_chooser.set_current_folder( os.path.expanduser('~') )
if current_name:
file_chooser.set_current_name(current_name)
file_chooser.set_default_response(Gtk.ResponseType.OK)
path = None
while True:
response = file_chooser.run()
if response != Gtk.ResponseType.OK:
path = None
break
path = file_chooser.get_filename()
if not os.path.exists(path): break
sub_msg_1 = ('A file named "%s" already exists') % os.path.abspath(path)
sub_msg_2 = ('Do you which to replace it with the current project?')
text = '<span weight="bold" size="larger">%s</span>\n\n%s\n' % (sub_msg_1, sub_msg_2)
result = message_dialog(Gtk.MessageType.ERROR, text, parent = parent, buttons = Gtk.ButtonsType.NONE, additional_buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Replace', Gtk.ResponseType.YES))
if result == Gtk.ResponseType.YES: break
file_chooser.destroy()
return path

View File

@@ -0,0 +1,107 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gdk
# Application imports
from utils.languages import samples
class ListSignalsMixin:
def on_language_selected(self, combo):
tree_iter = combo.get_active_iter()
if tree_iter:
model = combo.get_model()
self.current_lang_name = model[tree_iter][0]
self.current_lang_id = self.lang_map_name_to_id[self.current_lang_name]
self.styles_list.clear()
remove_len = len(self.current_lang_id) + 1
lang = self.language_manager.get_language(self.current_lang_id)
if lang:
style_ids = lang.get_style_ids()
style_ids.sort()
if self.current_lang_id == 'def':
for style_id in self.gui_style_ids:
self.styles_list.append([style_id])
for style_id in style_ids:
self.styles_list.append([style_id[remove_len:]])
tree_iter = self.styles_treeview.get_model().get_iter_first()
self.styles_treeview_selection.select_iter(tree_iter)
model = self.styles_treeview.get_model()
self.selected_style_id = model[tree_iter][0]
if self.current_lang_id in samples:
self.src_buffer.set_language(lang);
self.src_buffer.set_text(samples[self.current_lang_id])
self.sample_lbl.set_text(f"{self.current_lang_name} sample")
else:
self.src_buffer.set_language(self.language);
self.src_buffer.set_text(samples[self.language_id])
self.sample_lbl.set_text(f"{self.language_name} sample")
def on_style_selected(self, selection):
model, tree_iter = selection.get_selected()
if not tree_iter: return
self.selected_style_id = f"{self.current_lang_id}:{model[tree_iter][0]}"
# handle the special case for GUI styles
if self.selected_style_id not in self.all_styles_dict and self.current_lang_id == 'def':
self.selected_style_id = model[tree_iter][0]
# block all the toggle handlers so they dont get triggered
self.bold_tggle_btn.handler_block(self.bold_tggl_handler)
self.italic_tggle_btn.handler_block(self.italic_tggl_handler)
self.underline_tggle_btn.handler_block(self.underline_tggl_handler)
self.strikethrough_tggle_btn.handler_block(self.strikethrough_tggl_handler)
self.fr_check_btn.handler_block(self.foreground_tggl_handler)
self.br_check_btn.handler_block(self.background_tggl_handler)
if self.selected_style_id in self.all_styles_dict:
this_style = self.all_styles_dict[self.selected_style_id]
# handle foreground and background colors
if this_style.foreground:
self.fr_color_btn.set_color(Gdk.color_parse(this_style.foreground))
self.fr_color_btn.set_sensitive(True)
self.fr_check_btn.set_active(True)
else:
self.fr_color_btn.set_color(self.black_color)
self.fr_color_btn.set_sensitive(False)
self.fr_check_btn.set_active(False)
if this_style.background:
self.br_color_btn.set_color(Gdk.color_parse(this_style.background))
self.br_color_btn.set_sensitive(True)
self.br_check_btn.set_active(True)
else:
self.br_color_btn.set_color(self.black_color)
self.br_color_btn.set_sensitive(False)
self.br_check_btn.set_active(False)
# handle text styling
self.italic_tggle_btn.set_active(this_style.italic)
self.bold_tggle_btn.set_active(this_style.bold)
self.strikethrough_tggle_btn.set_active(this_style.strikethrough)
self.underline_tggle_btn.set_active(this_style.underline)
self.reset_btn.set_sensitive(True)
else:
self.clear_and_disable_style_buttons()
self.bold_tggle_btn.handler_unblock(self.bold_tggl_handler)
self.italic_tggle_btn.handler_unblock(self.italic_tggl_handler)
self.underline_tggle_btn.handler_unblock(self.underline_tggl_handler)
self.strikethrough_tggle_btn.handler_unblock(self.strikethrough_tggl_handler)
self.fr_check_btn.handler_unblock(self.foreground_tggl_handler)
self.br_check_btn.handler_unblock(self.background_tggl_handler)

View File

@@ -0,0 +1,107 @@
# Python imports
import os
import tempfile
from xml.etree import ElementTree as ET
# Lib imports
# Application imports
from utils.style_properties import StyleProperties
class SourceviewSignalsMixin:
def update_sample_view(self):
"""
Update the sample shown in the GUI.
To do this we must write the scheme to disk and reload it from there.
"""
self.write_scheme(self.temp_scheme_file, self.temp_scheme_id)
self.scheme_manager.force_rescan()
new_scheme = self.scheme_manager.get_scheme(self.temp_scheme_id)
self.src_buffer.set_style_scheme(new_scheme);
def load_scheme(self, scheme_id_or_file):
xml_tree = None
if os.path.isfile(scheme_id_or_file):
directory = os.path.dirname(scheme_id_or_file)
if directory not in self.scheme_manager.get_search_path():
self.scheme_manager.prepend_search_path(directory)
with open(scheme_id_or_file, 'r') as f:
xml_tree = ET.parse(f)
if xml_tree.getroot().tag == 'style-scheme':
this_scheme = self.scheme_manager.get_scheme( xml_tree.getroot().attrib['id'] )
if not this_scheme: return False
testFilename = this_scheme.get_filename()
if testFilename != scheme_id_or_file:
text = '<span weight="bold" size="larger">There was a problem opening the file</span>\n\nYou appear to have schemes with the same IDs in different directories\n'
self.message_dialog(Gtk.MessageType.ERROR, text, buttons = Gtk.ButtonsType.NONE, additional_buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL))
return False
self.orig_scheme_file = scheme_id_or_file
else:
this_scheme = self.scheme_manager.get_scheme(scheme_id_or_file)
if not this_scheme: return False
self.currentScheme = this_scheme
self.name_entry.set_text( this_scheme.get_name() )
self.authr_entry.set_text( ', '.join( this_scheme.get_authors() ) )
self.desc_entry.set_text( this_scheme.get_description() )
self.id_entry.set_text( this_scheme.get_id() )
scheme_file = self.currentScheme.get_filename()
with open(scheme_file, 'r') as f:
xml_tree = ET.parse(f)
style_elems = xml_tree.findall('style')
self.all_styles_dict.clear()
for style_elem in style_elems:
this_style = self.currentScheme.get_style(style_elem.attrib['name'])
styleProps = StyleProperties()
styleProps.from_gtk_source_style(this_style)
self.all_styles_dict[style_elem.attrib['name']] = styleProps;
self.src_buffer.set_style_scheme(self.currentScheme);
# set up temp file so the sample view can be updated
self.temp_scheme_id = f"{this_scheme.get_id()}_temp"
self.temp_scheme_file = f"{tempfile.gettempdir()}/{self.temp_scheme_id}.xml"
return True
def write_scheme(self, location, scheme_id):
output = f'<style-scheme name="{self.name_entry.get_text()}" id="{scheme_id}" version="1.0">\n'
output += f' <author> {self.authr_entry.get_text()}</author>\n'
output += f' <description>{self.desc_entry.get_text()}</description>\n\n'
for k, v in self.all_styles_dict.items():
output += f' <style name="{k}"\t'
if (v.foreground): output += f'foreground="{v.foreground}" '
if (v.background): output += f'background="{v.background}" '
if (v.italic): output += 'italic="true" '
if (v.bold): output += 'bold="true" '
if (v.underline): output += 'underline="true" '
if (v.strikethrough): output += 'strikethrough="true" '
output += '/>\n'
output += '</style-scheme>\n'
with open(location, 'w') as f:
try:
f.write(output)
except:
return False
return True

View File

@@ -0,0 +1,47 @@
# Python imports
# Lib imports
# Application imports
class TggleAndCheckBoxSignals:
def on_reset_clicked(self, param):
if self.selected_style_id in self.all_styles_dict:
del self.all_styles_dict[self.selected_style_id]
self.clear_and_disable_style_buttons()
self.update_sample_view()
def on_background_toggled(self, param):
if param.get_active():
self.br_color_btn.set_sensitive(True)
self.br_color_btn.activate()
else:
self.br_color_btn.set_sensitive(False)
self.all_styles_dict[self.selected_style_id].background = None;
self.update_sample_view()
def on_foreground_toggled(self, param):
if param.get_active():
self.fr_color_btn.set_sensitive(True)
self.fr_color_btn.activate()
else:
self.fr_color_btn.set_sensitive(False)
self.all_styles_dict[self.selected_style_id].foreground = None;
self.update_sample_view()
def clear_and_disable_style_buttons(self):
self.fr_color_btn.set_color(self.black_color)
self.br_color_btn.set_color(self.black_color)
self.fr_color_btn.set_sensitive(False)
self.fr_check_btn.set_active(False)
self.br_color_btn.set_sensitive(False)
self.br_check_btn.set_active(False)
self.italic_tggle_btn.set_active(False)
self.bold_tggle_btn.set_active(False)
self.strikethrough_tggle_btn.set_active(False)
self.underline_tggle_btn.set_active(False)
self.reset_btn.set_sensitive(False)

View File

@@ -0,0 +1,13 @@
# Python imports
# Lib imports
# Application imports
from .signals.ipc_signals_mixin import IPCSignalsMixin
from .signals.keyboard_signals_mixin import KeyboardSignalsMixin
from .signals.main_signals_mixin import MainSignalsMixin
class SignalsMixins(KeyboardSignalsMixin, IPCSignalsMixin, MainSignalsMixin):
...

View File

@@ -0,0 +1,3 @@
"""
Widgets Module
"""

View File

@@ -0,0 +1,3 @@
"""
Widgets.Controls Module
"""

View File

@@ -0,0 +1,58 @@
# Python imports
import os
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gio
# Application imports
class OpenFileButton(Gtk.Button):
"""docstring for OpenFileButton."""
def __init__(self):
super(OpenFileButton, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
self.set_label("Open File(s)...")
self.set_image( Gtk.Image.new_from_icon_name("gtk-open", 4) )
self.set_always_show_image(True)
self.set_image_position(1) # Left - 0, Right = 1
self.set_hexpand(False)
def _setup_signals(self):
self.connect("button-release-event", self._open_files)
def _subscribe_to_events(self):
event_system.subscribe("open_files", self._open_files)
def _load_widgets(self):
...
def _open_files(self, widget = None, eve = None):
chooser = Gtk.FileChooserDialog("Open File...", None,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
response = chooser.run()
if response == Gtk.ResponseType.OK:
filename = chooser.get_filename()
if filename:
path = filename if os.path.isabs(filename) else os.path.abspath(filename)
_gfile = Gio.File.new_for_path(path)
event_system.emit("keyboard_open_file", (_gfile,))
chooser.destroy()

View File

@@ -0,0 +1,39 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class SaveAsButton(Gtk.Button):
def __init__(self):
super(SaveAsButton, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
self.set_label("Save As")
self.set_image( Gtk.Image.new_from_icon_name("gtk-save-as", 4) )
self.set_always_show_image(True)
self.set_image_position(1) # Left - 0, Right = 1
self.set_hexpand(False)
def _setup_signals(self):
self.connect("released", self._emit_save_as_eve)
def _subscribe_to_events(self):
...
def _load_widgets(self):
...
def _emit_save_as_eve(self, widget, eve = None):
event_system.emit('keyboard_save_file')

View File

@@ -0,0 +1,67 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio
# Application imports
class SaveFileDialog:
"""docstring for SaveFileDialog."""
def __init__(self):
super(SaveFileDialog, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
...
def _setup_signals(self):
...
def _subscribe_to_events(self):
event_system.subscribe("save_file_dialog", self.save_file_dialog)
def _load_widgets(self):
self._file_filter_text = Gtk.FileFilter()
self._file_filter_text.set_name("XML Files")
for p in settings.filters.xml:
self._file_filter_text.add_pattern(p)
self._file_filter_all = Gtk.FileFilter()
self._file_filter_all.set_name("All Files")
self._file_filter_all.add_pattern("*.*")
def save_file_dialog(self, current_filename: str = "", current_file: Gio.File = None) -> str:
# TODO: Move Chooser logic to own widget
dlg = Gtk.FileChooserDialog(title = "Please choose a file...", parent = None, action = 1)
dlg.add_buttons("Cancel", Gtk.ResponseType.CANCEL, "Save", Gtk.ResponseType.OK)
dlg.set_do_overwrite_confirmation(True)
dlg.add_filter(self._file_filter_text)
dlg.add_filter(self._file_filter_all)
if current_filename == "":
import os
dlg.set_current_name("new.xml")
filechooser.set_current_folder(os.path.expanduser('~'))
else:
dlg.set_current_folder(current_file.get_parent().get_path())
dlg.set_current_name(current_filename)
response = dlg.run()
file = dlg.get_filename() if response == Gtk.ResponseType.OK else ""
dlg.destroy()
return Gio.File.new_for_path(file) if not file == "" else None

View File

@@ -0,0 +1,51 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class TransparencyScale(Gtk.Scale):
def __init__(self):
super(TransparencyScale, self).__init__()
builder = settings_manager.get_builder()
builder.get_object("menu_box").add(self)
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_digits(0)
self.set_value_pos(Gtk.PositionType.RIGHT)
self.add_mark(0.0, Gtk.PositionType.LEFT, "Transparency")
self.add_mark(50.0, Gtk.PositionType.TOP, "50%")
self.set_hexpand(True)
def _setup_signals(self):
self.connect("value-changed", self._update_transparency)
def _subscribe_to_events(self):
...
def _load_widgets(self):
adjust = self.get_adjustment()
adjust.set_lower(0)
adjust.set_upper(99)
adjust.set_value(settings.theming.transparency)
adjust.set_step_increment(1.0)
def _update_transparency(self, range):
event_system.emit("remove_transparency")
tp = int(range.get_value())
settings.theming.transparency = tp
event_system.emit("update_transparency")

121
src/core/window.py Normal file
View File

@@ -0,0 +1,121 @@
# Python imports
import signal
# Lib imports
import gi
import cairo
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
# Application imports
from core.controller import Controller
class ControllerStartExceptiom(Exception):
...
class Window(Gtk.ApplicationWindow):
""" docstring for Window. """
def __init__(self, args, unknownargs):
super(Window, self).__init__()
settings_manager.set_main_window(self)
self._controller = None
self._set_window_data()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets(args, unknownargs)
self._set_size_constraints()
self.show()
def _setup_styling(self):
self.set_title(f"{app_name}")
self.set_icon_from_file( settings_manager.get_window_icon() )
self.set_gravity(5) # 5 = CENTER
self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS
ctx = self.get_style_context()
ctx.add_class("main-window")
ctx.add_class(f"mw_transparency_{settings.theming.transparency}")
def _setup_signals(self):
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._tear_down)
self.connect("delete-event", self._tear_down)
def _subscribe_to_events(self):
event_system.subscribe("tear_down", self._tear_down)
event_system.subscribe("load_interactive_debug", self._load_interactive_debug)
def _load_widgets(self, args, unknownargs):
if settings_manager.is_debug():
self.set_interactive_debugging(True)
self._controller = Controller(args, unknownargs)
if not self._controller:
raise ControllerStartException("Controller exited and doesn't exist...")
self.add( self._controller.get_base_container() )
def _set_size_constraints(self):
_window_x = settings.config.main_window_x
_window_y = settings.config.main_window_y
_min_width = settings.config.main_window_min_width
_min_height = settings.config.main_window_min_height
_width = settings.config.main_window_width
_height = settings.config.main_window_height
self.move(_window_x, _window_y - 28)
self.set_size_request(_min_width, _min_height)
self.set_default_size(_width, _height)
def _set_window_data(self) -> None:
screen = self.get_screen()
visual = screen.get_rgba_visual()
if visual and screen.is_composited() and settings.config.make_transparent == 0:
self.set_visual(visual)
self.set_app_paintable(True)
self.connect("draw", self._area_draw)
# bind css file
cssProvider = Gtk.CssProvider()
cssProvider.load_from_path( settings_manager.get_css_file() )
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
def _area_draw(self, widget: Gtk.ApplicationWindow, cr: cairo.Context) -> None:
cr.set_source_rgba( *settings_manager.get_paint_bg_color() )
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint()
cr.set_operator(cairo.OPERATOR_OVER)
def _load_interactive_debug(self):
self.set_interactive_debugging(True)
def _tear_down(self, widget = None, eve = None):
event_system.emit("shutting_down")
size = self.get_default_size()
pos = self.get_position()
settings_manager.set_main_window_width(size.width)
settings_manager.set_main_window_height(size.height)
settings_manager.set_main_window_x(pos.root_x)
settings_manager.set_main_window_y(pos.root_y)
settings_manager.save_settings()
settings_manager.clear_pid()
Gtk.main_quit()