remove unused classes

This commit is contained in:
Izalia Mae 2022-09-04 11:22:14 -04:00
parent a4b362214a
commit 5b0689181e
18 changed files with 349 additions and 1203 deletions

213
barkshark_web/base.py Normal file
View file

@ -0,0 +1,213 @@
from .functions import (
connect,
set_image
)
class PropertyBase:
_app = None
_db = None
_window = None
@property
def app(self):
return self._app or Gio.Application.get_default()
@app.setter
def app(self, value):
self._app = value
@property
def db(self):
return self._db or self.app.db
@db.setter
def db(self, value):
self._db = value
@property
def window(self):
return self._window or self.app.window
@window.setter
def window(self, value):
self._window = value
class BuilderBase(PropertyBase):
def __init__(self, path):
self._path = path
self._builder = Gtk.Builder.new_from_file(path)
self._connected_sigs = {}
def __getitem__(self, name):
if not (widget := self._builder.get_object(name)):
raise KeyError(f'Widget with ID "{name}" does not exist.')
return widget
def __setitem__(self, name, widget):
try:
self[name]
raise KeyError(f'Widget ID already exists: {name}')
except KeyError:
self._builder.expose_object(name, widget)
return widget
def __delitem__(self, name):
self[name].destroy()
def __del__(self):
self.destroy()
@property
def path(self):
return self._path
def block_signals(self, *names):
signals = {name: self.get_signals(name) for name in names}
return SignalBlock(self, signals)
def connect(self, name, signal, callback, *args, **kwargs):
widget = self[name]
if not self._connected_sigs.get(name):
self._connected_sigs[name] = {}
if signal in self._connected_sigs[name]:
raise KeyError(f'Signal for "{name}" already connected: {signal}')
self._connected_sigs[name][signal] = connect(widget, signal, callback, *args, **kwargs)
return self._connected_sigs[name][signal]
def disconnect(self, name, signal):
try:
sigid = self._connected_sigs[name].pop(signal)
self[name].disconnect(sigid)
except KeyError:
raise KeyError(f'Signal for "{name}" not connected: {signal}')
def destroy(self):
for widget in self._builder.get_objects():
try: widget.destroy()
except: pass
self._connected_sigs = {}
def get_signals(self, name):
return self._connected_sigs[name]
def reload(self):
self.destroy(False)
self._builder.add_from_file(self.path)
def set_icon(self, name, obj, size=16, keep='width'):
set_image(self[name], obj, size, keep)
def set_icon_from_resource(self, name, filename, size=16, keep='width'):
path = self.app.path.resources.join('icons').join(f'{filename}')
self.set_icon(name, path, size, keep)
class ComponentBase(PropertyBase):
signals = {}
def __init__(self, prefix=None):
self._prefix = prefix
def __getitem__(self, key):
key = f'{self._prefix}-{key}' if self._prefix else key
return self.window[key]
def __setitem__(self, key, widget):
key = f'{self._prefix}-{key}' if self._prefix else key
self.window[key] = widget
def __delitem__(self, key):
key = f'{self._prefix}-{key}' if self._prefix else key
del self.window[key]
def block_signals(self, *names):
return BuilderBase.block_signals(self, *names)
def connect(self, name, signal, callback, *args, **kwargs):
name = f'{self._prefix}-{name}' if self._prefix else name
return self.window.connect(name, signal, callback, *args, **kwargs)
def disconnect(self, name, signal):
name = f'{self._prefix}-{name}' if self._prefix else name
return self.window.disconnect(name, signal)
def get_signals(self, name):
return self._window._connected_sigs[f'{self._prefix}-{name}']
def set_icon(self, name, obj, size=16):
BuilderBase.set_icon(self, name, obj, size)
def set_icon_from_resource(self, name, filename, size=16):
Builder.set_icon_from_resource(self, name, filename, size)
def setup_signals(self):
for name, sigs in self.signals.items():
for data in sigs:
data = DotDict(data)
if name.startswith('menu'):
data.args.append('menu')
self.connect(name, data.signal, data.callback, *data.get('args', []), **data.get('kwargs', {}))
class SignalBlock:
def __init__(self, parent, signals):
self.parent = parent
self.signals = signals
def __enter__(self):
self.set_block(True)
def __exit__(self, *args):
self.set_block(False)
def set_block(self, block):
for name, signals in self.signals.items():
for signame, sigid in signals.items():
if block:
self.parent[name].handler_block(sigid)
else:
self.parent[name].handler_unblock(sigid)

View file

@ -1,9 +1,10 @@
from izzylib.misc import random_str
from .. import cache, var
from ..base import ComponentBase
from ..database import default_permissions
from ..exceptions import AccountNotFoundError, NoAccountsError
from ..functions import ComponentBase, connect, get_buffer_text
from ..functions import connect, get_buffer_text
from ..objects import SavedLoginRow

View file

@ -1,13 +1,13 @@
import string
from ..functions import ComponentBase
class WebContentManager(WebKit2.UserContentManager):
class WebContentManager(ComponentBase, WebKit2.UserContentManager):
def __init__(self, tab):
super().__init__()
self.app = tab.window.app
self.window = tab.window
self.tab = tab
self.scripts = DotDict()

View file

@ -11,11 +11,11 @@ from .web_tab_settings import WebSettings
from .web_tab_webview_handler import WebviewHandler
from .. import var
from ..base import BuilderBase
from ..enums import EditAction, Javascript, FileChooserResponse
from ..objects import WebviewState
from ..widgets import Box, FileChooser
from ..widgets import FileChooser
from ..functions import (
BuilderBase,
connect,
resolve_address,
run_in_gui_thread,

View file

@ -1,4 +1,4 @@
import asyncio, functools, json, mimetypes, ftplib
import functools
from izzylib.exceptions import HttpClientError
from jinja2.exceptions import TemplateNotFound
@ -7,8 +7,9 @@ from urllib.parse import urlparse, quote, unquote
from ssl import SSLCertVerificationError
from .. import cache, var
from ..base import ComponentBase
from ..enums import EditAction, Javascript, FileChooserResponse, WebviewContextActions
from ..functions import ComponentBase, Thread, connect, run_in_gui_thread
from ..functions import Thread, connect, run_in_gui_thread
from ..widgets import FileChooser
@ -29,7 +30,7 @@ class WebviewHandler(ComponentBase):
connect(self.webview, 'close', self.tab.close)
connect(self.webview, 'context-menu', self.handle_context_menu, original_args=True)
connect(self.webview, 'create', self.handle_new_window, original_args=True)
connect(self.webview, 'decide-policy', self.handle_decide_policy, original_args=True)
#connect(self.webview, 'decide-policy', self.handle_decide_policy, original_args=True)
connect(self.webview, 'enter-fullscreen', self.handle_fullscreen, 'enter')
connect(self.webview, 'leave-fullscreen', self.handle_fullscreen, 'exit')
#connect(self.webview, 'insecure-content-detected', self.handle_insecure_content, original_args=True)
@ -186,6 +187,7 @@ class WebviewHandler(ComponentBase):
return False
# Can't get the new url, so this is pointless
def handle_decide_policy(self, webview, decision, decision_type):
url = self.tab.url
@ -332,6 +334,7 @@ class WebviewHandler(ComponentBase):
with self.app.db.session as s:
permissions = s.get_permission(url.hostname())
## This only prevents non-muted media from playing :/
self.settings.set('media-playback-requires-user-gesture', not permissions.autoplay)
elif event.value_nick == 'redirected':

View file

@ -6,11 +6,12 @@ from .status_bar import StatusBar
from .web_tab import WebTab
from .. import var, __software__
from ..base import BuilderBase
from ..enums import LibraryPage, FileChooserResponse
from ..functions import BuilderBase, Thread, run_in_gui_thread, connect, get_app, icon_set
from ..functions import Thread, run_in_gui_thread, connect, get_app, icon_set
from ..objects import Notification, WebviewState
from ..themes import Themes
from ..widgets import FileChooser, Menu, MenuButtonRefresh
from ..widgets import FileChooser, Menu
class Window(BuilderBase, Gtk.ApplicationWindow):

View file

@ -125,20 +125,17 @@ def load_js_file(name, ext=False):
return js
def new_pixbuf(image, size=16):
def new_pixbuf(image):
if isinstance(image, (cairo.Surface, cairo.ImageSurface)):
image = Gdk.pixbuf_get_from_surface(image, 0, 0, image.get_width(), image.get_height())
elif isinstance(image, GdkPixbuf.Pixbuf):
pass
return Gdk.pixbuf_get_from_surface(image, 0, 0, image.get_width(), image.get_height())
elif isinstance(image, Path):
return GdkPixbuf.Pixbuf.new_from_file_at_scale(image, -1, size, True)
return GdkPixbuf.Pixbuf.new_from_file(image)
else:
elif not isinstance(image, GdkPixbuf.Pixbuf):
raise TypeError('Image is not a Cairo Surface, Pixbuf, or Path object')
return scale_pixbuf(image, size)
return image
def resolve_address(domain, type=None):
@ -182,9 +179,9 @@ def scale_pixbuf(pixbuf, new_size=16, keep='height'):
return pixbuf.scale_simple(new_width, new_height, GdkPixbuf.InterpType.BILINEAR)
def set_image(widget, image, size=16):
def set_image(widget, image, size=16, keep='height'):
try:
image = new_pixbuf(image, size)
image = scale_pixbuf(new_pixbuf(image), size, keep)
widget.set_from_pixbuf(image)
return widget
@ -232,245 +229,6 @@ def set_proc_name(name='pyweb'):
return ret
class BuilderBase:
def __init__(self, path):
self._path = path
self._builder = Gtk.Builder.new_from_file(path)
self._connected_sigs = {}
def __getitem__(self, name):
if not (widget := self._builder.get_object(name)):
raise KeyError(f'Widget with ID "{name}" does not exist.')
return widget
def __setitem__(self, name, widget):
try:
self[name]
raise KeyError(f'Widget ID already exists: {name}')
except KeyError:
self._builder.expose_object(name, widget)
return widget
def __delitem__(self, name):
self[name].destroy()
def __del__(self):
self.destroy()
@property
def app(self):
return Gio.Application.get_default()
@property
def db(self):
return self.app.db
@property
def path(self):
return self._path
def block_signals(self, *names):
signals = {name: self.get_signals(name) for name in names}
return SignalBlock(self, signals)
def connect(self, name, signal, callback, *args, **kwargs):
widget = self[name]
if not self._connected_sigs.get(name):
self._connected_sigs[name] = {}
if signal in self._connected_sigs[name]:
raise KeyError(f'Signal for "{name}" already connected: {signal}')
self._connected_sigs[name][signal] = connect(widget, signal, callback, *args, **kwargs)
return self._connected_sigs[name][signal]
def disconnect(self, name, signal):
try:
sigid = self._connected_sigs[name].pop(signal)
self[name].disconnect(sigid)
except KeyError:
raise KeyError(f'Signal for "{name}" not connected: {signal}')
def destroy(self):
for widget in self._builder.get_objects():
try: widget.destroy()
except: pass
self._connected_sigs = {}
def get_signals(self, name):
return self._connected_sigs[name]
def reload(self):
self.destroy(False)
self._builder.add_from_file(self.path)
def set_icon(self, name, obj, size=16):
widget = self[name]
if not isinstance(widget, Gtk.Image):
raise TypeError(f'Widget is not a Gtk.Image object: {name}')
if isinstance(obj, cairo.Surface):
image = surface_to_pixbuf(obj, size)
widget.set_from_pixbuf(image)
elif isinstance(obj, GdkPixbuf.Pixbuf):
image = scale_pixbuf(obj, size)
widget.set_from_pixbuf(image)
elif isinstance(obj, Path):
image = GdkPixbuf.Pixbuf.new_from_file_at_scale(obj, -1, size, True)
widget.set_from_pixbuf(image)
elif isinstance(obj, str):
widget.set_from_icon_name(obj, Gtk.IconSize.SMALL_TOOLBAR)
widget.set_pixel_size(size)
else:
raise TypeError(f'Invalid icon type for "{name}": {class_name(obj)}')
def set_icon_from_resource(self, name, filename, size=16):
path = self.app.path.resources.join('icons').join(f'{filename}')
self.set_icon(name, path, size)
class ComponentBase:
signals = {}
def __init__(self, prefix=None):
self._prefix = prefix
self._app = None
self._db = None
self._window = None
def __getitem__(self, key):
key = f'{self._prefix}-{key}' if self._prefix else key
return self.window[key]
def __setitem__(self, key, widget):
key = f'{self._prefix}-{key}' if self._prefix else key
self.window[key] = widget
def __delitem__(self, key):
key = f'{self._prefix}-{key}' if self._prefix else key
del self.window[key]
@property
def app(self):
return self._app or Gio.Application.get_default()
@app.setter
def app(self, value):
self._app = value
@property
def db(self):
return self._db or self.app.db
@db.setter
def db(self, value):
self._db = value
@property
def window(self):
return self._window or self.app.window
@window.setter
def window(self, value):
self._window = value
def block_signals(self, *names):
return BuilderBase.block_signals(self, *names)
def connect(self, name, signal, callback, *args, **kwargs):
name = f'{self._prefix}-{name}' if self._prefix else name
return self.window.connect(name, signal, callback, *args, **kwargs)
def disconnect(self, name, signal):
name = f'{self._prefix}-{name}' if self._prefix else name
return self.window.disconnect(name, signal)
def get_signals(self, name):
return self._window._connected_sigs[f'{self._prefix}-{name}']
def set_icon(self, name, obj, size=16):
BuilderBase.set_icon(self, name, obj, size)
def set_icon_from_resource(self, name, filename, size=16):
Builder.set_icon_from_resource(self, name, filename, size)
def setup_signals(self):
for name, sigs in self.signals.items():
for data in sigs:
data = DotDict(data)
if name.startswith('menu'):
data.args.append('menu')
self.connect(name, data.signal, data.callback, *data.get('args', []), **data.get('kwargs', {}))
class SignalBlock:
def __init__(self, parent, signals):
self.parent = parent
self.signals = signals
def __enter__(self):
self.set_block(True)
def __exit__(self, *args):
self.set_block(False)
def set_block(self, block):
for name, signals in self.signals.items():
for signame, sigid in signals.items():
if block:
self.parent[name].handler_block(sigid)
else:
self.parent[name].handler_unblock(sigid)
class Thread(threading.Thread):
def __init__(self, func, *args, **kwargs):
super().__init__(daemon=True)

View file

@ -1,7 +1,6 @@
from izzylib.misc import replace_strings
from mastodon import Mastodon
from .widgets import Box
from .functions import (
TimeoutCallback,
connect,
@ -10,196 +9,6 @@ from .functions import (
)
class Account:
def __init__(self, row):
self._row = row
self._api = None
@property
def api(self):
if not self._api:
self._set_api()
return self._api
@property
def handle(self):
return self.row.handle
@property
def domain(self):
return self.row.domain
@property
def fullhandle(self):
return f'{self.row.handle}@{self.row.domain}'
def _set_api(self):
self._api = Mastodon(access_token=self.row.token, api_base_url=f'https://{self.row.domain}')
logging.debug(f'logged into {self.fullhandle}')
def refresh_user(self):
user_data = self.api.me()
instance = self.api.instance()
if not user_data:
logging.warning('Failed to fetch new user data')
return
with db.session as s:
try: toot_limit = instance['max_toot_chars']
except: toot_limit = 500
self.row = s.update_row(self.row,
username = user_data.display_name,
data = DotDict(user_data).to_json(),
toot_limit = toot_limit
)
if not s.get_config('active_acct'):
s.put_config('active_acct', row.id)
self.fetch_avatar()
return self.row
def fetch_avatar(self):
byte = BytesIO()
with urlopen(Request(self.row.data.avatar_static, headers={'User-Agent': Client.useragent})) as resp:
if resp.status != 200:
logging.error(f'Failed to download {self.row.data.avatar_static}:', resp.status)
return False
image = Image.open(resp)
image.thumbnail((256,256))
image.save(byte, format='png')
with dirs.avatars.join(f'{self.row.id}.png').open('wb') as fd:
fd.write(byte.getvalue())
return True
def fetch_post(self, url):
data = self.api.search_v2(url, resolve=True, result_type='statuses')['statuses']
return data[0] if len(data) else None
class AccountRow(Box):
def setup(self, row=None):
assert row != None
SetMargin(self)
self.row = row
self._api = None
self.AddIconEmpty('default', size=24)
self.AddIconFile('avatar', dirs.avatars.join(f'{row.id}.png'), 24)
self.AddLabel('username', row.username, expand=True, align='left')
self.AddLabel('handle', f'{row.handle}@{row.domain}')
with db.session as s:
self.set_default(s.get_config('active_acct') == row.id)
@property
def api(self):
if not self._api:
self._set_api()
return self._api
@property
def handle(self):
return self.row.handle
@property
def domain(self):
return self.row.domain
@property
def fullhandle(self):
return f'{self.row.handle}@{self.row.domain}'
def _set_api(self):
self._api = Mastodon(access_token=self.row.token, api_base_url=f'https://{self.row.domain}')
logging.debug(f'logged into {self.fullhandle}')
def set_default(self, default):
if default:
self['default'].set_resource('check', size=24)
else:
self['default'].set_empty(size=24)
def update_data(self):
self.items.avatar.set_file(dirs.avatars.join(f'{self.row.id}.png'), 24)
self.items.username.set_text(self.row.username)
self.items.handle.set_text(self.fullhandle)
def refresh_user(self):
user_data = self.api.me()
instance = self.api.instance()
if not user_data:
logging.warning('Failed to fetch new user data')
return
with db.session as s:
try: toot_limit = instance['max_toot_chars']
except: toot_limit = 500
self.row = s.update_row(self.row,
username = user_data.display_name,
data = DotDict(user_data).to_json(),
toot_limit = toot_limit
)
if not s.get_config('active_acct'):
s.put_config('active_acct', row.id)
self.fetch_avatar()
return self.row
def fetch_avatar(self):
byte = BytesIO()
with urlopen(Request(self.row.data.avatar_static, headers={'User-Agent': Client.useragent})) as resp:
if resp.status != 200:
logging.error(f'Failed to download {self.row.data.avatar_static}:', resp.status)
return False
image = Image.open(resp)
image.thumbnail((256,256))
image.save(byte, format='png')
with dirs.avatars.join(f'{self.row.id}.png').open('wb') as fd:
fd.write(byte.getvalue())
return True
def fetch_post(self, url):
data = self.api.search_v2(url, resolve=True, result_type='statuses')['statuses']
return data[0] if len(data) else None
class LoginRowBase:
def __getitem__(self, key):
return self.ui.get_object(key)

View file

@ -12,8 +12,9 @@ from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
from . import var, __version__ as version, __software__ as swname
from .base import ComponentBase
from .enums import StylePriority
from .functions import ComponentBase, run_in_gui_thread
from .functions import run_in_gui_thread
class Themes(ComponentBase, ObjectBase):

View file

@ -5,9 +5,10 @@ from izzylib import ObjectBase
from izzylib.misc import class_name
from izzylib.path import Path
from ..config import scriptpath
from ..enums import FileChooserAction, FileChooserResponse
from ..functions import BuilderBase, connect, new_pixbuf
from .base import BuilderBase
from .config import scriptpath
from .enums import FileChooserAction, FileChooserResponse
from .functions import connect, set_image
class FileChooser(BuilderBase):
@ -110,7 +111,7 @@ class FileChooser(BuilderBase):
try:
size = 256 if not self['chooser-preview-size'].get_active() else 512
self['chooser-preview-image'].set_from_pixbuf(new_pixbuf(path, size))
image = set_image(self['chooser-preview-image'], path, size, keep='width')
#self['chooser-preview-filename'].set_text(path.name)
self['chooser-preview'].show()
@ -145,3 +146,109 @@ class FileChooser(BuilderBase):
raise ValueError(f'Not a valid response type: {response}')
self.handle_response(response)
class Menu(Gtk.Menu):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.items = DotDict()
self.signals = DotDict()
self.sepnum = 0
self.connect('show', lambda *args: self.handle_popup())
self.connect('hide', lambda *args: self.handle_popdown())
self._setup()
def __getitem__(self, name):
return self.items[name]
def __setitem__(self, name, widget):
self.items[name] = widget
widget.show()
def __delitem__(self, name):
self.remove_item(name)
def Connect(self, name, callback, *args, **kwargs):
self.signals[name] = connect(self.items[name], 'activate', callback, *args, **kwargs)
def Disconnect(self, name):
self[name].disconnect(self.signals[name])
del self.signals[name]
def new_action(self, name, label, callback, *args, **kwargs):
self[name] = Gtk.MenuItem.new_with_label(label or '')
self.Connect(name, callback, *args, **kwargs)
self.append(self.items[name])
def new_action_at_pos(self, position, name, label, callback, *args, **kwargs):
self[name] = Gtk.MenuItem.new_with_label(label)
self.Connect(name, callback, *args, **kwargs)
self.insert(self[name], position)
def new_sub(self, name, label):
self[name] = Gtk.MenuItem.new_with_label(label)
self[name].set_submenu(Menu())
return self[name]
def new_separator(self, position=None):
widget = Gtk.SeparatorMenuItem()
self.sepnum += 1
self[f'separator-{self.sepnum}'] = widget
if position != None:
self.insert(widget, position)
else:
self.append(widget)
def get_item(self, name):
return self.items[name]
def remove_item(self, name):
try:
self.Disconnect(name)
except:
pass
self.items[name].destroy()
del self.items[name]
def clear_items(self):
self.foreach(lambda item: item.destroy())
self.items = DotDict()
self.signals = DotDict()
self.sepnum = 0
def handle_popup(self):
pass
def handle_popdown(self):
pass
def _setup(self):
pass

View file

@ -1,10 +0,0 @@
from .entry import Entry
from .file_chooser import FileChooser
from .icon import Icon
from .label import Label
from .menu import Menu
from .tree_view import TreeView
## These depend on other widgets
from .box import Box
from .button import MenuButton, PopoverButton, MenuButtonRefresh

View file

@ -1,255 +0,0 @@
from .entry import Entry
from .icon import Icon
from ..functions import connect
class Box(Gtk.Box):
def __init__(self, parent=None, vertical=False, homogeneous=False, spacing=5, **kwargs):
self.spacing = spacing
options = {
'orientation': Gtk.Orientation.VERTICAL if vertical else Gtk.Orientation.HORIZONTAL,
'homogeneous': homogeneous,
'spacing': spacing
}
if parent:
options['parent'] = parent
super().__init__(**options)
self.items = DotDict()
self.signals = []
self.block_signals = lambda: SignalBlock(self.signals)
self.setup(**kwargs)
self.show_all()
def __getitem__(self, key):
return self.items[key]
def AddButton(self, name, callback, *args, label=None, icon=None, tooltip=None, icon_size=Gtk.IconSize.BUTTON, btn_callback=False, border=False, **kwargs):
if label:
widget = Gtk.Button.new_with_label(label)
else:
widget = Gtk.Button.new()
self.items[name.lower()] = widget
if icon:
if type(icon) == str:
icon = Gtk.Image.new_from_icon_name(icon, icon_size)
widget.set_image(icon)
if not border:
widget.set_relief(Gtk.ReliefStyle(2))
widget.set_tooltip_text(tooltip or label)
widget.show()
self.add(widget)
if btn_callback:
args = [widget, *args]
self.Connect(widget, 'clicked', callback, *args, **kwargs)
return widget
def AddEntry(self, name, callback=None, *args, expand=True, label=False, **kwargs):
widget = Entry(
placeholder_text = name,
#has_frame = False,
expand = expand
)
if callback:
self.Connect(widget, 'activate', callback, *args, widget.get_text(), **kwargs)
if label:
container = Gtk.Box(orientation='horizontal', homogeneous=False, spacing=self.spacing)
label = Gtk.Label(label=name)
container.add(label)
container.add(widget)
container.show_all()
self.add(container)
else:
widget.show()
self.add(widget)
self.items[name.lower()] = widget
return widget
def AddIconEmpty(self, name, size=16):
widget = Icon(size=size)
widget.show()
self.add(widget)
self.items[name.lower()] = widget
return widget
def AddIconResource(self, name, filename, size=16, ext='svg'):
widget = Icon()
widget.set_resource(filename, size, ext)
widget.show()
self.add(widget)
self.items[name.lower()] = widget
return widget
def AddIconFile(self, name, filename, size=16):
widget = Icon()
widget.set_file(filename, size)
widget.show()
self.add(widget)
self.items[name.lower()] = widget
return widget
def AddIconNamed(self, name):
widget = Icon()
widget.set_theme(name)
widget.show()
self.add(widget)
self.items[name.lower()] = widget
return widget
def AddLabel(self, name, label=None, expand=False, align='center'):
widget = Gtk.Label(label=label or name)
widget.set_ellipsize(3)
if align == 'left':
widget.set_xalign(0)
elif align == 'right':
widget.set_xalign(1)
if expand:
widget.set_hexpand(True)
widget.show()
self.add(widget)
self.items[name.lower()] = widget
return widget
def AddList(self, name, items, expand=False):
widget = Gtk.ComboBoxText.new()
if expand:
widget.set_hexpand(True)
for item in items:
widget.append_text(item)
widget.show()
self.add(widget)
self.items[name.lower()] = widget
def AddSeparator(self, expand=False):
widget = Gtk.Separator()
widget.set_hexpand(expand)
widget.show()
self.add(widget)
def AddSwitch(self, name, callback, *args, label=None, expand=False, default=False, **kwargs):
if label:
container = Gtk.Box(orientation='horizontal', homogeneous=False, spacing=self.spacing)
label = Gtk.Label(label=name)
label.set_hexpand(expand)
widget = Gtk.Switch.new()
widget.set_state(default)
self.Connect(widget, 'state-set', lambda *sigargs: callback(sigargs[1], *args, **kwargs), original_args=True)
self.items[name.lower()] = widget
if label:
self.add(container)
container.add(label)
container.add(widget)
container.show()
else:
widget.show()
self.add(widget)
def AddTextView(self, name, text, *args, expand=True, **kwargs):
widget = Gtk.TextView.new()
text_buffer = widget.get_buffer()
text_buffer.set_text(text)
widget.set_buffer(text_buffer)
widget.set_hexpand(True)
widget.set_wrap_mode(Gtk.WrapMode.WORD)
widget.show()
self.add(widget)
self.items[name.lower()] = widget
def AddWidget(self, name, widget, signal=None, callback=None, *args, **kwargs):
self.items[name.lower()] = widget
self.add(widget)
if signal:
self.Connect(widget, signal, callback, *args, **kwargs)
widget.show()
return widget
def Connect(self, name, signal, callback, *args, **kwargs):
if isinstance(name, Gtk.Widget):
widget = name
else:
widget = self.items[name.lower()]
sigid = connect(widget, signal, callback, *args, **kwargs)
self.signals.append(tuple([sigid, widget]))
def Remove(self, name):
item = self.items[name]
item.destroy()
del self.items[name]
def RemoveAll(self):
for name, widget in self.items.items():
widget.destroy()
self.items = DotDict()
def setup(self):
pass

View file

@ -1,78 +0,0 @@
from .box import Box
from ..functions import connect
class MenuButton(Gtk.MenuButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.menu = Menu()
self.set_popup(self.menu)
self.set_direction(Gtk.ArrowType.DOWN)
self.show()
connect(self.menu, 'show', self.handle_popup)
def handle_popup(self):
pass
class PopoverButton(Gtk.MenuButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.popover = Gtk.Popover()
self.box = Box(parent=self.popover)
self.set_popover(self.popover)
self.set_direction(Gtk.ArrowType.DOWN)
connect(self.popover, 'show', self.handle_popup)
def handle_popup(self):
pass
class MenuButtonRefresh(Gtk.MenuButton):
def __init__(self, func, *args, **kwargs):
super().__init__()
self.action = DotDict({'func': None, 'args': [], 'kwargs': {}})
self.popover = Gtk.Popover()
self.box = Box(parent=self.popover, vertical=True)
self.set_popover(self.popover)
self.set_direction(Gtk.ArrowType.DOWN)
connect(self.popover, 'show', self.handle_popup, func, *args, **kwargs)
def run_func(self, line):
self.popover.popdown()
self.action.func(line, *self.action.args, **self.action.kwargs)
def set_item_action(self, func, *args, **kwargs):
self.action.update({
'func': func,
'args': args,
'kwargs': kwargs
})
def handle_popup(self, func, *args, **kwargs):
if self.get_active():
self.box.RemoveAll()
for line in func(*args, **kwargs):
self.box.AddButton(line.lower(), self.run_func, line, label=line)
for widget in self.box.items[line.lower()].get_children():
if type(widget) == Gtk.Label:
widget.set_xalign(0)

View file

@ -1,37 +0,0 @@
from ..functions import connect
class Entry(Gtk.Entry):
def __init__(self, *args, expand=False, **kwargs):
super().__init__(*args, **kwargs)
self.set_hexpand(expand)
def set_icon(self, position, icon_name, callback, tooltip=None, *args, **kwargs):
position_num = 'left' if position == 'left' else 1
self.set_icon_from_icon_name(position_num, icon_name)
self.set_icon_tooltip_text(position_num, tooltip)
if position == 'left':
connect(self, 'icon-press', self.handle_icon_press_left)
else:
connect(self, 'icon-press', self.handle_icon_press_right)
def handle_icon_press_left(self):
pass
def handle_icon_press_right(self):
pass
def set_icon_left(self, *args, **kwargs):
self.set_icon('left', *args, **kwargs)
def set_icon_right(self, *args, **kwargs):
self.set_icon('right', *args, **kwargs)

View file

@ -1,34 +0,0 @@
from ..functions import get_app
class Icon(Gtk.Image):
def __init__(self, name=None, size=16, ext='svg'):
super().__init__()
if name:
self.set_file(name, size, ext)
else:
self.set_empty(size)
def set_empty(self, size=16):
self.set_resource('empty', size)
def set_file(self, filename, size=16):
try:
image = GdkPixbuf.Pixbuf.new_from_file_at_scale(str(filename), size, size, True)
self.set_from_pixbuf(image)
except GLib.Error:
logging.error('Failed to load icon:', filename)
def set_resource(self, name, size=16, ext='svg'):
filename = get_app().path.resources.join('icons').join(f'{name}.{ext}')
image = GdkPixbuf.Pixbuf.new_from_file_at_scale(filename, size, size, True)
self.set_from_pixbuf(image)
def set_theme(self, name):
self.set_from_icon_name(name, Gtk.IconSize.SMALL_TOOLBAR)

View file

@ -1,25 +0,0 @@
class Label(Gtk.Label):
ellipsize = {None: 0, 'left': 1, 'center': 2, 'right': 3}
align = {'left': 0, 'center': 0.5, 'right': 1}
def __init__(self, label='', ellipsize=None, align=0.5, expand=False, parent=None):
options = {'label': label}
if parent:
options['parent'] = parent
super().__init__(**options)
self.set_hexpand(expand)
self.set_xalign(self.align.get(align, 0))
self.set_ellipsize(self.ellipsize.get(ellipsize, 0))
@property
def text(self):
return self.get_text()
@text.setter
def text(self, text):
self.set_text(str(text))

View file

@ -1,106 +0,0 @@
from ..functions import connect
class Menu(Gtk.Menu):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.items = DotDict()
self.signals = DotDict()
self.sepnum = 0
self.connect('show', lambda *args: self.handle_popup())
self.connect('hide', lambda *args: self.handle_popdown())
self._setup()
def __getitem__(self, name):
return self.items[name]
def __setitem__(self, name, widget):
self.items[name] = widget
widget.show()
def __delitem__(self, name):
self.remove_item(name)
def Connect(self, name, callback, *args, **kwargs):
self.signals[name] = connect(self.items[name], 'activate', callback, *args, **kwargs)
def Disconnect(self, name):
self[name].disconnect(self.signals[name])
del self.signals[name]
def new_action(self, name, label, callback, *args, **kwargs):
self[name] = Gtk.MenuItem.new_with_label(label or '')
self.Connect(name, callback, *args, **kwargs)
self.append(self.items[name])
def new_action_at_pos(self, position, name, label, callback, *args, **kwargs):
self[name] = Gtk.MenuItem.new_with_label(label)
self.Connect(name, callback, *args, **kwargs)
self.insert(self[name], position)
def new_sub(self, name, label):
self[name] = Gtk.MenuItem.new_with_label(label)
self[name].set_submenu(Menu())
return self[name]
def new_separator(self, position=None):
widget = Gtk.SeparatorMenuItem()
self.sepnum += 1
self[f'separator-{self.sepnum}'] = widget
if position != None:
self.insert(widget, position)
else:
self.append(widget)
def get_item(self, name):
return self.items[name]
def remove_item(self, name):
try:
self.Disconnect(name)
except:
pass
self.items[name].destroy()
del self.items[name]
def clear_items(self):
self.foreach(lambda item: item.destroy())
self.items = DotDict()
self.signals = DotDict()
self.sepnum = 0
def handle_popup(self):
pass
def handle_popdown(self):
pass
def _setup(self):
pass

View file

@ -1,202 +0,0 @@
from gi.repository import Pango
from ..functions import connect, get_app
class TreeView(Gtk.TreeView):
def __init__(self, window, columns={}, search=None):
self.store = Gtk.ListStore(*list(col['type'] for col in columns.values()))
if search:
self.filter = self.store.filter_new()
super().__init__(model=self.filter)
self.filter.set_visible_func(self.handle_filter)
connect(search, 'changed', self.filter.refilter)
if search.get_icon_storage_type(1) != Gtk.ImageType.EMPTY:
connect(search, 'icon-press', search.set_text, '')
else:
super().__init__(model=self.store)
self.selected_row = None
self.selected_iter = None
self.selected_path = None
self.dbargs = DotDict({
'table': None,
'kwargs': {}
})
self.window = window
self.search = search
self.columns = DotDict()
self.rows = []
self.renderer = Gtk.CellRendererText()
self.renderer.set_padding(5, 5)
self.renderer.set_property('ellipsize', Pango.EllipsizeMode.MIDDLE)
self.set_hexpand(True)
self.set_vexpand(True)
self.set_grid_lines(Gtk.TreeViewGridLines.BOTH)
self.set_activate_on_single_click(False)
#self.set_headers_clickable(True)
self.set_reorderable(True)
for idx, col in enumerate(columns):
self.add_column(idx, col, DotDict(columns[col]))
connect(self, 'drag-data-received', self.handle_drag_data)
self.show_all()
def handle_drag_data(self, *args):
GObject.signal_stop_emission_by_name(self, 'drag-data-received')
return True
def add_column(self, idx, name, data):
column = Gtk.TreeViewColumn(name, self.renderer, text=idx)
column.set_resizable(True)
#column.set_clickable(True)
#column.set_sort_indicator(True)
if data.get('expand'):
column.set_expand(True)
if data.get('min'):
column.set_min_width(data.min)
if data.get('max'):
column.set_max_width(data.max)
self.append_column(column)
self.columns[name.lower()] = DotDict({
'id': idx,
'widget': column
})
def clear(self):
self.store.clear()
self.rows = []
def clear_selection(self):
self.get_selection().unselect_all()
self.selected_row = None
self.selected_path = None
self.selected_iter = None
def get_column_num(self, name):
return self.columns[name.lower()].id
def get_column(self, name):
return self.columns[name.lower()]
def insert_data(self, row):
data = []
for name, column in self.columns.items():
data.append(row.get(name, ''))
treeiter = self.store.append(data)
## Doesn't work
#tooltip = Gtk.Tooltip()
#tooltip.set_text(row.url)
#self.set_tooltip_row(tooltip, self.store.get_path(treeiter))
self.rows.append(row)
def insert(self, *rows, **kwargs):
for row in rows:
self.insert_data(row)
if kwargs:
with get_app().db.session as s:
for row in s.fetch(self.dbargs.table, **kwargs):
self.insert_data(row)
def remove(self, *rows, **kwargs):
with get_app().db.session as s:
for row in rows:
s.remove_row(row)
if kwargs:
for row in s.search(self.dbargs.table, **kwargs):
s.remove_row(row)
self.refresh()
def remove_all(self):
with get_app().db.session as s:
s.remove(self.dbargs.table)
self.refresh()
def update(self, row, treeiter, session, **data):
table_data = self.store if not self.search else self.filter
tablerow = table_data[treeiter]
session.update(row=row, **data)
for key, value in data.items():
tablerow[self.get_column_num(key)] = value
def refresh(self):
self.clear()
self.insert(**self.dbargs.kwargs)
def handle_filter(self, model, iter, data):
text = self.search.get_text()
data = []
for idx in range(0, len(self.columns)):
data.append(text.lower() in model[iter][idx].lower())
return any(data)
def get_selected_row(self):
path, column = self.get_cursor()
self.selected_row = None
self.selected_iter = None
if not path or not column:
return None, None
table_data = self.store if not self.search else self.filter
col_name = column.get_title().lower()
value = table_data.get_value(table_data.get_iter(path), self.get_column_num(col_name) or 0)
## This crashes the browser for some reason, but the path needs to be freed according to the documentation
## It doesn't cause a memory leak as far as I know though, so I don't know what will go wrong later
#if self.selected_path:
#self.selected_path.free()
self.selected_path = path
for row in self.rows:
if row[col_name] == value:
self.selected_row = row
self.selected_iter = table_data.get_iter(path)
return (self.selected_row, self.selected_iter)
def setup(self, table, **kwargs):
self.dbargs.table = table
self.dbargs.kwargs = kwargs
self.refresh()