This repository has been archived on 2023-02-02. You can view files and clone it, but cannot push or open issues or pull requests.
barkshark-web/barkshark_web/widgets.py
2022-12-09 13:30:29 -05:00

415 lines
9.8 KiB
Python

import time
from functools import partial
from izzylib import ObjectBase
from izzylib.misc import class_name
from izzylib.path import Path
from .base import BuilderBase
from .config import scriptpath
from .enums import FileChooserAction, FileChooserResponse
from .functions import connect, run_in_gui_thread, set_image
from .objects import Notification
class Download(BuilderBase):
valid_response_types = [FileChooserResponse.OK, FileChooserResponse.CANCEL]
def __init__(self, download):
BuilderBase.__init__(self, scriptpath.join('resources/download.ui'))
with self.db.session as s:
download_dir = s.get_config('download_dir')
self.download = download
self.cancel = None
self.data = DotDict(
filename = None,
path = download_dir,
url = download.get_request().get_uri()
)
self['download-window'].set_transient_for(self.window)
download.set_allow_overwrite(True)
# Connect dialog signals
self.connect('download-filename', 'changed', self.handle_dialog_change_filename)
self.connect('download-filename-set', 'clicked', self.handle_dialog_set_filename)
self.connect('download-cancel', 'clicked', self.handle_dialog_close, True)
self.connect('download-save', 'clicked', self.handle_dialog_close, False)
self.connect('download-window', 'delete-event', self.handle_dialog_close, True)
# Connect download signals
connect(download, 'decide-destination', self.handle_download_decide_destination, original_args=True)
connect(download, 'created-destination', self.handle_download_created_destination)
connect(download, 'failed', self.handle_download_failed, original_args=True)
connect(download, 'finished', self.handle_download_finish)
@property
def context(self):
return self.app.context
@property
def filename(self):
return Path(self['download-filename'].get_text())
@filename.setter
def filename(self, text):
self['download-filename'].set_text(text or '')
@property
def url(self):
return self['download-url'].get_text()
@url.setter
def url(self, text):
self['download-url'].set_text(text or '')
def target_path(self, full=True):
path = self.data.path.join(self.data.filename)
if full:
return f'file://{path}'
return path
def handle_dialog_close(self, cancel):
self['download-window'].hide()
if self.cancel != None:
return
self.cancel = cancel
def handle_dialog_change_filename(self):
self.data.path, self.data.filename = self.filename.parent, self.filename.name
self['download-save'].set_label('Overwrite' if self.filename.exists() else 'Save')
def handle_dialog_set_filename(self):
with FileChooser(self.dialog, self.data.path, self.data.filename) as fc:
if not fc:
logging.verbose('Canceled file selector')
return
self.filename = fc
def handle_download_created_destination(self):
logging.verbose(f'Created desination for file: {self.target_path(False)}')
def handle_download_decide_destination(self, download, filename):
self.data.filename = filename
self.url = self.data.url
self.filename = self.target_path(False)
self.handle_dialog_change_filename()
self['download-window'].run()
if self.cancel:
download.cancel()
else:
download.set_destination(self.target_path())
self.destroy()
def handle_download_failed(self, download, error):
if error.code == 400:
self.cancel = True
return
data = DotDict({
'args': error.args,
'code': error.code,
'domain': error.domain,
'message': error.message
})
logging.debug(data.to_json(4))
self.window.notification(f'Download failed: {self.target_path(False)}', system=True)
def handle_download_finish(self):
if not self.cancel:
notif = Notification(f'Download finished: {self.target_path(False)}')
# notif.new_callback('Open', self.handle_notif_action,
# path = self.target_path(False)
# )
notif.show()
# self.window.notification(f'Download finished: {self.target_path(False)}', system=True)
def handle_notif_action(self, action, *args, **kwargs):
print(action, args, kwargs)
if action == 'open':
os.system(f'xdg-open {self.target_path(False)}')
else:
raise ValueError(f'Invalid notification action: {action}')
class FileChooser(BuilderBase):
valid_response_types = [FileChooserResponse.OK, FileChooserResponse.CANCEL]
def __init__(self, parent, path=None, filename=None, save=True, multiple=False, any_filter=True):
BuilderBase.__init__(self, scriptpath.join('resources/file_chooser.ui'))
self.response_callback = {}
self.responded = False
self.any_filter = any_filter
self['chooser-main'].set_current_folder(Path(path) or Path('~/Downloads'))
self.action = 'save' if save else 'open'
if filename:
self['chooser-main'].set_current_name(filename)
self['chooser'].set_transient_for(parent)
self.connect('chooser', 'delete-event', self.handle_response, FileChooserResponse.CANCEL)
self.connect('chooser', 'key-press-event', self.handle_key_press, original_args=True)
self.connect('chooser-main', 'update-preview', self.handle_image_preview)
self.connect('chooser-preview-size', 'state-set', self.handle_image_preview)
self.connect('chooser-okay', 'clicked', self.handle_response, FileChooserResponse.OK)
self.connect('chooser-cancel', 'clicked', self.handle_response, FileChooserResponse.CANCEL)
def __enter__(self):
return self.run()
def __exit__(self, *args):
self['chooser'].hide()
@property
def action(self):
return self['chooser-main'].get_action()
@action.setter
def action(self, value):
if not isinstance(value, FileChooserAction):
value = FileChooserAction[value.upper()]
self['chooser'].set_title(f'{value.name.title()} File')
self['chooser-okay'].set_label(value.name.title())
self['chooser-main'].set_action(value)
def add_filter(self, filter):
self['chooser-main'].add_filter(filter)
def get_result(self):
if self['chooser-main'].get_select_multiple():
return self['chooser-main'].get_filenames()
return self['chooser-main'].get_filename()
def new_filter(self, label, *patterns):
filter = Gtk.FileFilter()
filter.set_name(label)
for pattern in patterns:
filter.add_pattern(pattern)
self.add_filter(filter)
def run(self, wait=False):
if self.any_filter:
self.new_filter('Any File', '*')
self['chooser'].show()
self['chooser'].present()
if wait:
while not self.responded:
time.sleep(0.1)
return self.get_result()
def set_callback(self, response, callback, *args, **kwargs):
if not isinstance(response, FileChooserResponse):
response = FileChooserResponse[response.upper()]
if response not in self.valid_response_types:
raise ValueError(f'Not a valid response type: {response}')
self.response_callback[response] = partial(callback, *args, **kwargs)
def handle_key_press(self, _, event):
if event.keyval == Gdk.KEY_Escape:
self.do_response(FileChooserResponse.CANCEL)
def handle_image_preview(self, *_):
path = Path(self['chooser-main'].get_preview_filename())
widget = self['chooser-main'].get_preview_widget()
if path.isdir():
return self['chooser-preview'].hide()
try:
size = 256 if not self['chooser-preview-size'].get_active() else 512
image = set_image(self['chooser-preview-image'], path, size, keep='width')
#self['chooser-preview-filename'].set_text(path.name)
self['chooser-preview'].show()
except GLib.Error:
self['chooser-preview'].hide()
def handle_response(self, response):
if self.responded:
return
self['chooser'].hide()
try:
if response == FileChooserResponse.CANCEL:
self.response_callback[response]()
else:
self.response_callback[response](self.get_result())
except KeyError:
pass
self.responded = True
def do_response(self, response):
if not isinstance(response, FileChooserResponse):
response = FileChooserResponse[response.upper()]
if response not in self.valid_response_types:
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