remove unused classes
This commit is contained in:
parent
a4b362214a
commit
5b0689181e
213
barkshark_web/base.py
Normal file
213
barkshark_web/base.py
Normal 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)
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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))
|
|
@ -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
|
|
@ -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()
|
Reference in a new issue