Post New Code Cleanup

This commit is contained in:
2023-04-22 11:38:13 -05:00
parent 454a8b0cab
commit 53a9515901
74 changed files with 11 additions and 1394 deletions

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

@@ -0,0 +1,3 @@
"""
Gtk Bound Signal Module
"""

View File

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

View File

@@ -0,0 +1,35 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from .left_box import LeftBox
from .right_box import RightBox
class CoreWidget(Gtk.Box):
def __init__(self):
super(CoreWidget, self).__init__()
self._builder = settings.get_builder()
self._setup_styling()
self._setup_signals()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_orientation(Gtk.Orientation.HORIZONTAL)
def _setup_signals(self):
...
def _load_widgets(self):
self.add(LeftBox())
self.add(RightBox())

View File

@@ -0,0 +1,40 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from ..widgets.radio_buttons import RadioButtons
from ..widgets.delay_amount import DelayAmount
from ..widgets.preview_image import PreviewPane
from ..widgets.menu_popover import MenuPopover
class LeftBox(Gtk.Box):
def __init__(self):
super(LeftBox, self).__init__()
self._setup_styling()
self._setup_signals()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_orientation(Gtk.Orientation.VERTICAL)
def _setup_signals(self):
...
def _load_widgets(self):
menu = MenuPopover()
delay_amount = DelayAmount()
menu.set_relative_to(delay_amount)
menu.set_position(Gtk.PositionType.BOTTOM)
self.add(RadioButtons())
self.add(delay_amount)
self.add(PreviewPane())

View File

@@ -0,0 +1,35 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from ..widgets.snapshot_button import SnapshotButton
from ..widgets.monitor_list import MonitorList
from ..widgets.images_list import ImagesList
class RightBox(Gtk.Box):
def __init__(self):
super(RightBox, self).__init__()
self._setup_styling()
self._setup_signals()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_orientation(Gtk.Orientation.VERTICAL)
def _setup_signals(self):
...
def _load_widgets(self):
self.add(SnapshotButton())
self.add(MonitorList())
self.add(ImagesList())

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

@@ -0,0 +1,72 @@
# 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 GLib
# Application imports
from .mixins.signals_mixins import SignalsMixins
from .controller_data import ControllerData
from .screenshot_controller import ScreenshotController
from .containers.core_widget import CoreWidget
class Controller(SignalsMixins, ControllerData):
def __init__(self, args, unknownargs):
self.setup_controller_data()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
if args.no_plugins == "false":
self.plugins.launch_plugins()
for arg in unknownargs + [args.new_tab,]:
if os.path.isfile(arg):
message = f"FILE|{arg}"
event_system.emit("post_file_to_ipc", message)
if os.path.isdir(arg):
message = f"DIR|{arg}"
event_system.emit("post_file_to_ipc", message)
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("handle_file_from_ipc", self.handle_file_from_ipc)
event_system.subscribe("handle_dir_from_ipc", self.handle_dir_from_ipc)
event_system.subscribe("tggl_top_main_menubar", self._tggl_top_main_menubar)
def _load_widgets(self):
ScreenshotController()
def load_glade_file(self):
self.builder = Gtk.Builder()
# self.builder.add_from_file(settings.get_glade_file())
self.builder.expose_object("main_window", self.window)
settings.set_builder(self.builder)
self.core_widget = CoreWidget()
settings.register_signals_to_builder([self, self.core_widget])
def get_core_widget(self):
return self.core_widget
def _tggl_top_main_menubar(self):
print("_tggl_top_main_menubar > stub...")

View File

@@ -0,0 +1,68 @@
# Python imports
import os
import subprocess
# Lib imports
# Application imports
from plugins.plugins_controller import PluginsController
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:
self.window = settings.get_main_window()
self.builder = None
self.core_widget = None
self.was_midified_key = False
self.ctrl_down = False
self.shift_down = False
self.alt_down = False
self.load_glade_file()
self.plugins = PluginsController()
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,20 @@
# Python imports
# Lib imports
# Application imports
class IPCSignalsMixin:
""" IPCSignalsMixin handle messages from another starting solarfm process. """
def print_to_console(self, message=None):
print(message)
def handle_file_from_ipc(self, path: str) -> None:
print(f"File From IPC: {path}")
def handle_dir_from_ipc(self, path: str) -> None:
print(f"Dir 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,13 @@
# Python imports
# Lib imports
from .signals.ipc_signals_mixin import IPCSignalsMixin
from .signals.keyboard_signals_mixin import KeyboardSignalsMixin
# Application imports
class SignalsMixins(KeyboardSignalsMixin, IPCSignalsMixin):
...

View File

@@ -0,0 +1,122 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
from gi.repository import GLib
import pyscreenshot as capture
# Application imports
from .widgets.region.window import RegionWindow
class ScreenshotController:
def __init__(self):
super(ScreenshotController, 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("grab_entire_screen", self.grab_entire_screen)
event_system.subscribe("grab_active_window", self.grab_active_window)
event_system.subscribe("pass_to_region_handler", self.pass_to_region_handler)
event_system.subscribe("grab_region_hide", self._grab_region_hide)
event_system.subscribe("grab_region", self._grab_region)
event_system.subscribe("grab_selected_monitor", self.grab_selected_monitor)
def _load_widgets(self):
RegionWindow()
def grab_entire_screen(self):
logger.info("Grabbing Entire Screen...")
window = settings.get_main_window()
@daemon_threaded
def do_grab():
im = capture.grab(childprocess = False)
im.save( settings.generate_screenshot_name() )
GLib.idle_add(window.show)
window.hide()
do_grab()
def grab_active_window(self):
logger.info("Grabbing Active Window...")
event_system.emit("grab_delay")
screen = Gdk.get_default_root_window().get_screen()
w = screen.get_active_window()
pb = Gdk.pixbuf_get_from_window(w, *w.get_geometry())
pb.savev(settings.generate_screenshot_name(), "png", (), ())
def pass_to_region_handler(self):
logger.info("Passing to Region Handler...")
window = settings.get_main_window()
window.hide()
event_system.emit("show_region_window")
def _grab_region_hide(self, region_window):
region_window.hide()
window = settings.get_main_window()
window.show()
def _grab_region(self, region_window):
logger.info("Grabbing Selected Region...")
x, y = region_window.get_position()
w, h = region_window.get_size()
x2 = x + w
y2 = y + h
def show_region_window():
# NOTE: No clue why showing window has it move outta prior place.
# So, move back to original spot before showing...
region_window.move(x, y)
region_window.show()
@daemon_threaded
def do_bounding_box_grab(x1, y1, x2, y2):
while region_window.is_visible():
...
im = capture.grab(bbox = (x1, y1, x2, y2), childprocess = False)
im.save( settings.generate_screenshot_name() )
GLib.idle_add(show_region_window)
region_window.hide()
offset = 1
do_bounding_box_grab(x - offset, y - offset, x2 + offset, y2 + offset)
def grab_selected_monitor(self):
logger.info("Grabbing Monitor...")
window = settings.get_main_window()
monitor = event_system.emit_and_await("get_selected_monitor")
x2 = monitor.x + monitor.width
y2 = monitor.y + monitor.height
@daemon_threaded
def do_bounding_box_grab(x1, y1, x2, y2):
im = capture.grab(bbox = (x1, y1, x2, y2), childprocess = False)
im.save( settings.generate_screenshot_name() )
GLib.idle_add(window.show)
event_system.emit("grab_delay")
window.hide()
do_bounding_box_grab(monitor.x, monitor.y, x2, y2)

View File

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

View File

@@ -0,0 +1,61 @@
# Python imports
import time
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class DelayAmount(Gtk.Box):
def __init__(self):
super(DelayAmount, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_orientation(Gtk.Orientation.HORIZONTAL)
self.set_margin_top(5)
self.set_margin_bottom(5)
def _setup_signals(self):
...
def _subscribe_to_events(self):
event_system.subscribe("set_grab_delay", self.set_grab_delay)
event_system.subscribe("grab_delay", self.grab_delay)
def _load_widgets(self):
label = Gtk.Label("Timeout: ")
spinner = Gtk.SpinButton()
spinner.set_hexpand(True)
spinner.set_numeric(True)
spinner.set_snap_to_ticks(True)
spinner.set_digits(0)
spinner.set_range(0, 120)
spinner.set_increments(1, 5)
self.add(label)
self.add(spinner)
def set_grab_delay(self, wait = 0.0):
delay_amount = self.get_children()[1]
delay_amount.set_value(wait)
def grab_delay(self, wait = None):
delay_amount = self.get_children()[1]
if not wait:
wait = delay_amount.get_value_as_int()
time.sleep(wait)

View File

@@ -0,0 +1,114 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import GLib
# Application imports
from mixins.tree_nixin import TreeMixin
class ImagesList(TreeMixin, Gtk.ScrolledWindow):
def __init__(self):
super(ImagesList, self).__init__()
self._tree_view = None
self._store = None
self._dir_watcher = None
self._watch_dir = None
self._set_file_watcher()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_size_request(200, -1)
self.set_hexpand(False)
self.set_vexpand(True)
self.set_margin_top(15)
def _setup_signals(self):
...
def _subscribe_to_events(self):
...
def _load_widgets(self):
scroll, self._store = self._create_treeview_widget("Images")
self._tree_view = scroll.get_children()[0]
self._tree_view.connect("button-press-event", self._handle_clicks)
self._tree_view.connect("row-activated", self._set_active_image)
self._tree_view.set_property("activate-on-single-click", True)
self.referesh_directory_list()
self.add(scroll)
def _set_file_watcher(self):
if settings.is_debug():
logger.debug(f"Watcher Will Not Be Set...")
return
images_dir = settings.get_screenshots_dir()
self._watch_dir = Gio.File.new_for_uri(f"file://{images_dir}")
self._dir_watcher = self._watch_dir.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
self._dir_watcher.connect("changed", self._dir_watch_updates, ())
def _dir_watch_updates(self, file_monitor, file, other_file = None, eve_type = None, data = None):
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
Gio.FileMonitorEvent.MOVED_OUT]:
self._store.clear()
self.referesh_directory_list()
def _set_active_image(self, tree_view = None, path = None, column = None):
file = self.get_selected()
event_system.emit("set_image_to_view", (file,))
event_system.emit("set_revert_data", (file,))
def _handle_clicks(self, widget = None, eve = None):
if eve.button == 1 and eve.type == 5:
file = self.get_selected()
event_system.emit("set_revert_data", (file,))
event_system.emit("open_file")
return
if eve.button == 1 and eve.type == 4:
self._set_active_image()
return
if eve.button == 3:
file = self.get_selected()
event_system.emit("set_revert_data", (file,))
event_system.emit("show_menu", (file,))
return
@threaded
def referesh_directory_list(self):
images = settings.get_directory_list()
images.sort()
for image in images:
GLib.idle_add(self.add_to_store, (image,))
def add_to_store(self, image):
self._store.append(image)
def get_selected(self):
model, treeiter = self._tree_view.get_selection().get_selected()
if treeiter != None:
return model[treeiter][0]
return None

View File

@@ -0,0 +1,117 @@
# Python imports
import os
import subprocess
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class MenuPopover(Gtk.Popover):
def __init__(self):
super(MenuPopover, self).__init__()
self._revert_name = None
self._rename_entry = None
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
self.set_size_request(360, -1)
def _setup_signals(self):
...
def _subscribe_to_events(self):
event_system.subscribe("set_revert_data", self.set_revert_data)
event_system.subscribe("show_menu", self.show_menu)
event_system.subscribe("open_file", self.open_file)
def _load_widgets(self):
box = Gtk.Box()
box2 = Gtk.Box()
self._rename_entry = Gtk.Entry()
revert_button = Gtk.Button(label = "Revert")
rename_button = Gtk.Button(label = "Rename")
open_button = Gtk.Button(label = "Open")
delete_button = Gtk.Button(label = "Delete")
revert_button.set_image( Gtk.Image.new_from_icon_name("gtk-undo", 4) )
rename_button.set_image( Gtk.Image.new_from_icon_name("gtk-edit", 4) )
open_button.set_image( Gtk.Image.new_from_icon_name("gtk-open", 4) )
delete_button.set_image( Gtk.Image.new_from_icon_name("gtk-delete", 4) )
revert_button.set_always_show_image(True)
rename_button.set_always_show_image(True)
open_button.set_always_show_image(True)
delete_button.set_always_show_image(True)
box.set_orientation(Gtk.Orientation.VERTICAL)
box2.set_orientation(Gtk.Orientation.HORIZONTAL)
self._rename_entry.set_hexpand(True)
box2.add(self._rename_entry)
box2.add(revert_button)
box.add(box2)
box.add(rename_button)
box.add(open_button)
box.add(delete_button)
revert_button.connect("clicked", self.revert_name)
rename_button.connect("clicked", self.rename_file)
open_button.connect("clicked", self.open_file)
delete_button.connect("clicked", self.delete_file)
box.show_all()
self.add(box)
def set_revert_data(self, name):
if not name in ("", None):
self._revert_name = name
self._rename_entry.set_text(name)
def show_menu(self, name):
if not name in ("", None):
self.set_revert_data(name)
self.popup()
def revert_name(self, widget = None, data = None):
self._rename_entry.set_text(self._revert_name)
def rename_file(self, widget, data=None):
dir = settings.get_screenshots_dir()
new_name = self._rename_entry.get_text().strip()
old_file_path = os.path.join(dir, self._revert_name)
new_file_path = os.path.join(dir, new_name)
try:
if os.path.isfile(old_file_path) and not new_name in ("", None):
os.rename(old_file_path, new_file_path)
self._revert_name = new_name
except Exception as e:
logger.info(e)
def open_file(self, widget = None, data = None):
dir = settings.get_screenshots_dir()
file = os.path.join(dir, self._revert_name)
subprocess.Popen(['xdg-open', file], stdout = subprocess.PIPE)
def delete_file(self, widget, data=None):
try:
dir = settings.get_screenshots_dir()
file = os.path.join(dir, self._revert_name)
if os.path.isfile(file):
os.remove(file)
self.popdown()
event_system.emit("unset_image_preview")
except Exception as e:
logger.info(e)

View File

@@ -0,0 +1,68 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from mixins.tree_nixin import TreeMixin
class MonitorList(TreeMixin, Gtk.Box):
def __init__(self):
super(MonitorList, self).__init__()
self.MONITORS: [] = None
self._monitors_view = None
self._store = None
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_hexpand(False)
def _setup_signals(self):
...
def _subscribe_to_events(self):
event_system.subscribe("get_selected_monitor", self.get_selected_monitor)
event_system.subscribe("set_monitor_sensitive", self.set_monitor_sensitive)
def _load_widgets(self):
grid, self._store = self._create_treeview_widget("Monitors")
self._monitors_view = grid.get_children()[0]
self._load_monitor_store()
self.set_monitor_sensitive()
grid.set_hexpand(True)
self.add(grid)
def _load_monitor_store(self):
self.MONITORS = settings.get_monitor_data()
i = 0
for monitor in self.MONITORS:
if i > 0:
mon = str(monitor.width) + "x" + str(monitor.height) + "+" + str(monitor.x) + "+" + str(monitor.y)
self._store.append([mon])
i += 1
self._monitors_view.set_cursor(0)
def get_selected_monitor(self):
iter = self._monitors_view.get_selection().get_selected()[1]
path = self._store.get_path(iter)
# Slot 0 is ref monitor. Need to add 1 to get proper slot
return self.MONITORS[int(str(path)) + 1]
def set_monitor_sensitive(self, isSensitive = False):
self._monitors_view.set_sensitive(isSensitive)

View File

@@ -0,0 +1,56 @@
# Python imports
import os
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio
# Application imports
class PreviewPane(Gtk.AspectFrame):
def __init__(self):
super(PreviewPane, self).__init__()
self._preview_image = None
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_size_request(312, 312)
def _setup_signals(self):
...
def _subscribe_to_events(self):
event_system.subscribe("set_image_to_view", self.set_image_to_view)
event_system.subscribe("unset_image_preview", self.unset_image_preview)
def _load_widgets(self):
self._preview_image = Gtk.Image()
self.unset_image_preview()
self.add(self._preview_image)
def set_image_to_view(self, image_file):
if not image_file:
return
images_dir = settings.get_screenshots_dir()
path = os.path.join(images_dir, image_file)
pixbuf = Gtk.Image.new_from_file(path).get_pixbuf()
scaledPixBuf = pixbuf.scale_simple(480, 320, 2) # 2 = BILINEAR and is best by default
self._preview_image.set_from_pixbuf(scaledPixBuf)
def unset_image_preview(self):
pixbuf = Gtk.Image.new_from_icon_name("gtk-missing-image", 4).get_pixbuf()
self._preview_image.set_from_pixbuf(pixbuf)

View File

@@ -0,0 +1,76 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class RadioButtons(Gtk.Box):
def __init__(self):
super(RadioButtons, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_orientation(Gtk.Orientation.HORIZONTAL)
def _setup_signals(self):
...
def _subscribe_to_events(self):
event_system.subscribe("get_screenshot_type", self._get_active_type)
def _load_widgets(self):
radio_1 = Gtk.RadioButton(label = "Entire Screen")
radio_2 = Gtk.RadioButton(label = "Active Window")
radio_3 = Gtk.RadioButton(label = "Select Region")
radio_4 = Gtk.RadioButton(label = "Select Monitor")
self.add(radio_1)
self.add(radio_2)
self.add(radio_3)
self.add(radio_4)
self._setup_data()
def _setup_data(self):
last_child = None
for child in self.get_children():
if last_child:
child.join_group(last_child)
else:
last_child = child
child.connect("released", self._set_data_state)
def _get_active_type(self):
group = self.get_children()[0].get_group()
active_radio = [r for r in group if r.get_active()]
return active_radio[0]
def _set_data_state(self, widget = None, eve = None):
label = widget.get_label()
isSensitive = False
wait = 0.0
if label == "Entire Screen":
...
if label == "Active Window":
wait = 4.0
if label == "Select Region":
...
if label == "Select Monitor":
isSensitive = True
event_system.emit("set_grab_delay", (wait,))
event_system.emit("set_monitor_sensitive", (isSensitive,))

View File

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

View File

@@ -0,0 +1,177 @@
# Python imports
# 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 GLib
# Application imports
class BodyGrid(Gtk.Grid):
def __init__(self, window, gdk_window):
super(BodyGrid, self).__init__()
self._gdk_window = gdk_window
self._window = window
self._is_dragging = False
self._update_block = False
self._drag_start_x = 0
self._drag_start_y = 0
self._current_x = 0
self._current_y = 0
self._w1 = 0.0
self._h1 = 0.0
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
def _setup_styling(self):
ctx = self.get_style_context()
ctx.add_class("region-window")
self.set_vexpand(True)
self.set_hexpand(True)
def _setup_signals(self):
...
def _subscribe_to_events(self):
...
def _load_widgets(self):
drag_button = Gtk.Button("")
close_button = Gtk.Button("x")
grab_button = Gtk.Button("Grab")
bottom_right = Gtk.Button("")
box2 = Gtk.Box()
box3 = Gtk.Box()
box4 = Gtk.Box()
box5 = Gtk.Box()
ctx = drag_button.get_style_context()
ctx.add_class("expand-button")
ctx2 = bottom_right.get_style_context()
ctx2.add_class("expand-button")
col, row = 1, 1
self.attach(drag_button, col, row, 11, 1)
col, row = 12, 1
self.attach(close_button, col, row, 1, 1)
col, row = 1, 2
self.attach(box2, col, row, 5, 3)
col, row = 1, 5
self.attach(box3, col, row, 4, 1)
col, row = 1, 12
self.attach(box4, col, row, 3, 1)
col, row = 5, 12
self.attach(grab_button, col, row, 3, 1)
col, row = 9, 12
self.attach(box5, col, row, 3, 1)
col, row = 12, 12
self.attach(bottom_right, col, row, 1, 1)
close_button.set_vexpand(False)
close_button.set_hexpand(False)
box2.set_vexpand(True)
box2.set_hexpand(True)
box3.set_vexpand(True)
box3.set_hexpand(True)
box4.set_vexpand(False)
box4.set_hexpand(True)
box5.set_vexpand(False)
box5.set_hexpand(True)
drag_button.connect("button-press-event", self._press_event)
drag_button.connect("motion-notify-event", self._move_motion_event)
drag_button.connect("button-release-event", self._release_event)
close_button.connect("button-release-event", self._region_close)
grab_button.connect("button-release-event", self._region_grab)
bottom_right.connect("button-press-event", self._press_event)
bottom_right.connect("motion-notify-event", self._resize_motion_event)
bottom_right.connect("button-release-event", self._release_event)
def _region_close(self, widget = None, eve = None):
event_system.emit("grab_region_hide", (self._window,))
def _region_grab(self, widget = None, eve = None):
event_system.emit("grab_region", (self._window,))
def _press_event(self, widget = None, eve = None):
window = self.get_parent()
cursor = Gdk.Cursor(Gdk.CursorType.CROSSHAIR)
window.get_window().set_cursor(cursor)
self._is_dragging = True
self._drag_start_x = eve.x_root
self._drag_start_y = eve.y_root
if self._current_x == 0:
self._current_x, self._current_y = self._window.get_position()
self._w1, self._h1 = self._window.get_size()
def _resize_motion_event(self, widget = None, eve = None):
if self._update_block: return
x1 = self._drag_start_x
y1 = self._drag_start_y
x2 = eve.x_root
y2 = eve.y_root
w = 0
h = 0
if x2 > x1: # Is growing
w = self._w1 + (x2 - x1)
else: # Is shrinking
w = self._w1 - (x1 - x2)
if y2 > y1: # Is growing
h = self._h1 + (y2 - y1)
else: # Is shrinking
h = self._h1 - (y1 - y2)
self._update_block = True
self._window.resize(w, h)
self._update_block = False
def _move_motion_event(self, widget = None, eve = None):
if self._is_dragging:
if eve.x_root > self._drag_start_x:
self._current_x += (eve.x_root - self._drag_start_x)
elif eve.x_root < self._drag_start_x:
self._current_x -= (self._drag_start_x - eve.x_root)
else:
self._current_x = self._current_x
if eve.y_root > self._drag_start_y:
self._current_y += (eve.y_root - self._drag_start_y)
elif eve.y_root < self._drag_start_y:
self._current_y -= (self._drag_start_y - eve.y_root)
else:
self._current_y = self._current_y
self._drag_start_x = eve.x_root
self._drag_start_y = eve.y_root
self._update_block = True
self._window.move(self._current_x, self._current_y)
self._update_block = False
def _release_event(self, widget = None, eve = None):
window = self.get_parent()
watch_cursor = Gdk.Cursor(Gdk.CursorType.ARROW)
window.get_window().set_cursor(watch_cursor)
self._is_dragging = False
self._drag_start_x = 0
self._drag_start_y = 0

View File

@@ -0,0 +1,70 @@
# Python imports
# 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
# Application imports
from .body_grid import BodyGrid
class RegionWindow(Gtk.Window):
def __init__(self):
super(RegionWindow, self).__init__()
self._set_window_data()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
self.set_default_size(600, 480)
self.set_deletable(False)
self.set_decorated(False)
self.set_resizable(True)
self.set_skip_pager_hint(True)
self.set_skip_taskbar_hint(True)
self.set_has_resize_grip(True)
def _setup_signals(self):
...
def _subscribe_to_events(self):
event_system.subscribe("show_region_window", self._show_region_window)
def _load_widgets(self):
gdk_window = self.get_screen().get_root_window()
self.add( BodyGrid(self, gdk_window) )
def _set_window_data(self) -> None:
screen = self.get_screen()
visual = screen.get_rgba_visual()
if visual != None and screen.is_composited():
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.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( *(0, 0, 0, 0.0) )
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint()
cr.set_operator(cairo.OPERATOR_OVER)
def _show_region_window(self):
self.show()

View File

@@ -0,0 +1,48 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class SnapshotButton(Gtk.Button):
def __init__(self):
super(SnapshotButton, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self.show_all()
def _setup_styling(self):
self.set_always_show_image(True)
self.set_image_position(Gtk.PositionType.LEFT)
self.set_label("Take Snapshot")
def _setup_signals(self):
self.connect("clicked", self._take_snapshot)
def _subscribe_to_events(self):
...
def _load_widgets(self):
image = Gtk.Image.new_from_icon_name("gtk-media-play", 3)
self.set_image(image)
def _take_snapshot(self, widget = None, eve = None):
active = event_system.emit_and_await("get_screenshot_type").get_label()
if "Entire Screen" in active:
event_system.emit("grab_entire_screen")
if "Active Window" in active:
event_system.emit("grab_active_window")
if "Select Region" in active:
event_system.emit("pass_to_region_handler")
if "Select Monitor" in active:
event_system.emit("grab_selected_monitor")

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

@@ -0,0 +1,98 @@
# Python imports
import time
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__()
self._controller = None
self._set_window_data()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
settings.set_main_window(self)
self._load_widgets(args, unknownargs)
self.show()
def _setup_styling(self):
self.set_default_size(settings.get_main_window_width(),
settings.get_main_window_height())
self.set_size_request(settings.get_main_window_width(),
settings.get_main_window_height())
self.set_title(f"{app_name}")
self.set_icon_from_file( settings.get_window_icon() )
self.set_gravity(5) # 5 = CENTER
self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS
def _setup_signals(self):
self.connect("delete-event", self._tear_down)
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._tear_down)
def _subscribe_to_events(self):
event_system.subscribe("tear_down", self._tear_down)
def _load_widgets(self, args, unknownargs):
if settings.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_core_widget() )
def _set_window_data(self) -> None:
screen = self.get_screen()
visual = screen.get_rgba_visual()
if visual != None and screen.is_composited():
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.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.get_paint_bg_color() )
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint()
cr.set_operator(cairo.OPERATOR_OVER)
def _tear_down(self, widget=None, eve=None):
settings.clear_pid()
time.sleep(event_sleep_time)
Gtk.main_quit()