diff --git a/CHANGELOG.md b/CHANGELOG.md index 130e19f..dd5a993 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,12 @@ ### @TODO: - improve the code -- change the interfaces and reimplement the callback method + + +## Version 0.1.8 +- BRAKE: the mode how the ProgressBar is reimplemented will brake the old code. Use the example from README.md +- reword on the progress bar and threads. For now seams to work OK +- small fixes for documentation and added some images ## Version 0.1.7 - refactored all the code diff --git a/README.md b/README.md index f45e099..2f01afa 100644 --- a/README.md +++ b/README.md @@ -36,23 +36,6 @@ $ python setup.py install Simple dialog: -```python -from src.sgzenity import calendar - -result = calendar(title="Awesome Calendar", text="Your birthday ?") -print(result) -``` -This code show a calendar dialog : - -![dialog_01](docs/img/screen_01.png) - -And display the result : - -```bash -$ python test.py -$ (year=2017, month=6, day=4) -``` - ## API ### Simple message @@ -97,6 +80,9 @@ warning(title='', text='', width=330, height=120, timeout=None) >* **timeout** (*int*) – close the window after n seconds ### Question + +![basic_dialog_01](docs/img/basic_dialog.png) + ```python question(title='', text='', width=330, height=120, timeout=None) ``` @@ -114,6 +100,9 @@ question(title='', text='', width=330, height=120, timeout=None) >_Return type_: bool ### Progress Bar + +![basic_dialog_01](docs/img/progressbar.png) + ```python progress_bar(title, text, pulse_mode, callback) ``` @@ -128,21 +117,40 @@ progress_bar(title, text, pulse_mode, callback) ### Demo + ```python -def callback_progress_bar(fraction=None): - global counter - counter += 0.01 - if counter <= _max: - return counter - return True - -def demo_progress_bar(): - progress = progress_bar( - "DEMO TITLE", "DEMO TEXT", False, callback_progress_bar, 350, 30, 10 - ) - progress.run_progressbar() - -demo_progress_bar() +import time +from sgzenity.thread import WorkerThread +from sgzenity import ProgressBar + +class WorkingThread(WorkerThread): + def payload(self): + loading = self.data + steps = 10 + for s in range(steps): + if self.stop: + break + loading.heartbeat() + print('Pulse {}.'.format(s)) + time.sleep(1) + if self.stop: + print('Working thread canceled.') + else: + print('Working thread ended.') + loading.close() + + +def sg_progress_bar(): + loading = ProgressBar("DEMO TITLE", "DEMO TEXT", pulse_mode=True) + + workthread = WorkingThread(loading) + loading.show(workthread) + workthread.start() + + Gtk.main() + + +sg_progress_bar() ``` ### Entry @@ -244,6 +252,15 @@ calendar(text='', day=None, month=None, title='', width=330, height=120, timeout > >_Return type_: tuple +![calendar_dialog_01](docs/img/calendar_dialog.png) + +And display the result : + +```bash +$ python demo.py +$ (year=2017, month=6, day=4) +``` + ### Color selection ```python diff --git a/demo.py b/demo.py index f847665..eb28fe9 100644 --- a/demo.py +++ b/demo.py @@ -1,37 +1,51 @@ #! /usr/bin/env python3 # -*- coding:utf-8 -*- -import random + import time import gi gi.require_version("Gtk", "3.0") -from gi.repository import GLib, Gtk +from gi.repository import GLib, GObject, Gtk + +from src.sgzenity.SGProgresBar import ProgressBar +from src.sgzenity.sgszenity import calendar, question +from src.sgzenity.thread import WorkerThread -from src.sgzenity.sgszenity import progress_bar, question -counter = 0 -_max = 1 +class WorkingThread(WorkerThread): + def payload(self): + loading = self.data + steps = 10 + for s in range(steps): + if self.stop: + break + loading.heartbeat() + print('Pulse {}.'.format(s)) + time.sleep(1) + if self.stop: + print('Working thread canceled.') + else: + print('Working thread ended.') + loading.close() -def callback_progress_bar(fraction=None): - global counter - counter += 0.01 - if counter <= _max: - return counter - return True +def sg_progress_bar(): + loading = ProgressBar("DEMO TITLE", "DEMO TEXT", pulse_mode=True) -def demo_progress_bar(): - progress = progress_bar( - "DEMO TITLE", "DEMO TEXT", False, callback_progress_bar, 350, 30, 10 - ) - progress.run_progressbar() + workthread = WorkingThread(loading) + loading.show(workthread) + workthread.start() + Gtk.main() -demo_progress_bar() + +sg_progress_bar() _error = question( "something went wrong", text="Some big text in small space", height=150, width=400 ) -# print(_error) + +_calendar = calendar("DEMO CALENDAR") +print(_calendar) diff --git a/docs/AUTHORS.md b/docs/AUTHORS.md index 7ed4bf2..afa5ddf 100644 --- a/docs/AUTHORS.md +++ b/docs/AUTHORS.md @@ -1,2 +1,5 @@ -Authors\n=======\n\nA huge thanks to all of our contributors:\n +Authors +======= + +A huge thanks to all of our contributors: - Zaharia Constantin diff --git a/docs/img/basic_dialog.png b/docs/img/basic_dialog.png new file mode 100644 index 0000000..d9d6d79 Binary files /dev/null and b/docs/img/basic_dialog.png differ diff --git a/docs/img/calendar_dialog.png b/docs/img/calendar_dialog.png new file mode 100644 index 0000000..876c1be Binary files /dev/null and b/docs/img/calendar_dialog.png differ diff --git a/docs/img/progressbar.png b/docs/img/progressbar.png new file mode 100644 index 0000000..2de9136 Binary files /dev/null and b/docs/img/progressbar.png differ diff --git a/pyproject.toml b/pyproject.toml index 6eea080..844e647 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "sgzenity" -version = "0.1.7" +version = "0.1.8" description = "sgzentry is a library for python which was inspired by Zenity. When you write scripts, you can use sgzentry to create simple dialogs that interact graphically with the user." homepage="https://github.com/SoftGeekRO/sgzenity" repository="https://github.com/SoftGeekRO/sgzenity.git" diff --git a/src/sgzenity/SGProgresBar.py b/src/sgzenity/SGProgresBar.py index 04d27cb..01a95a8 100644 --- a/src/sgzenity/SGProgresBar.py +++ b/src/sgzenity/SGProgresBar.py @@ -7,77 +7,129 @@ from gi.repository import GLib, GObject, Gtk -from .base import Base from .thread import WorkerThread +DEFAULT_WIDTH = 300 +DEFAULT_HEIGHT = 60 +BORDER_WIDTH = 10 -class SGProgressBar(Base): - def __init__(self, title, text, pulse_mode=False, callback=None, *args, **kwargs): +class ProgressBar(Gtk.Window): + + def __init__( + self, + title, + text=None, + pulse_mode=True, + width=DEFAULT_WIDTH, + height=DEFAULT_HEIGHT, + border_width=BORDER_WIDTH, + parent=None, + **kwargs, + ): super().__init__(**kwargs) self.title = title self.text = text - self.show_text = True if self.text else False self.pulse_mode = pulse_mode - self.callback = callback - - self.dialog = Gtk.ProgressBar() - self.vbox.pack_start(self.dialog, True, True, 0) + self.border_width = border_width + self.width = width + self.height = height + self.pulses = 10 + self._count = 0 + self.workthread = None - self.timeout_id = GLib.timeout_add(50, self.update_progress, None) + # Create the GUI + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + self.add(vbox) - self.init_progressbar() + self.progressbar = Gtk.ProgressBar() + vbox.pack_start(self.progressbar, True, True, 0) - # Start the background thread. - self.worker = WorkerThread(self) - self.worker.start() + self.timeout_id = GLib.timeout_add(50, self.on_timeout, None) - def run_progressbar(self): - GObject.threads_init() - self.show_all() - Gtk.main() + self.connect("destroy", self.cancel) - def refresh_in_thread(self): - GObject.idle_add(self.update_progress) + self.init() - def init_progressbar(self): - # global config for progress bar - self.set_border_width(10) + def init(self): + self.set_border_width(self.border_width) self.set_resizable(False) self.set_default_size(self.width, self.height) if self.pulse_mode: - self.dialog.pulse() + self.progressbar.pulse() else: - self.dialog.set_fraction(0.0) + self.progressbar.set_fraction(0.0) if self.title: self.set_title(self.title) - if self.show_text: - self.dialog.set_text(self.text) - self.dialog.set_show_text(self.show_text) + if self.text: + self.progressbar.set_text(self.text) + self.progressbar.set_show_text(True if self.text else False) - self.connect("destroy", self._destroy) + self.connect("destroy", self.cancel) - def update_progress(self, progress=None): - """ + def show(self, workthread): + """Show loading window. + This needs to be called frm Gtk main thread. + + Show the loading dialog just before starting the workthread. + :param workthread: :return: """ + if self.workthread is not None: + print( + 'There is a workthread active. Please call close() or cancel() before starting a new loading event.' + ) + return False - callback = self.callback() if callable(self.callback) else None - if callback >= 1: - time.sleep(0.1) - self._destroy() + if workthread is not None: + if not isinstance(workthread, WorkerThread): + raise Exception('The thread needs to be a subclass of WorkingThread.') + self.workthread = workthread + self.show_all() + + return False + + def on_timeout(self, progress=None): if self.pulse_mode: - self.dialog.pulse() + self.progressbar.pulse() else: - self.dialog.set_fraction(callback) - return False + self.progressbar.set_fraction(self._count) + return self.pulse_mode + + def heartbeat(self, text=None): - def _destroy(self, widget=None): - self.worker.done = True + if self.pulse_mode: + return False + + self._count += 0.1 + + if text is None: + text = '{0:0.1f}%'.format(self._count * 100) + + GLib.idle_add(self.progressbar.set_fraction, self._count) + GLib.idle_add(self.progressbar.set_text, text) + + def close(self): + """Close the loading window. + + This should be called when the workthread has finished it's work. + This can be called outside the Gtk main thread. + """ + self.workthread = None Gtk.main_quit() + + def cancel(self, widget=None): + """Close the loading window. + + This should be called when the workthread has finished it's work. + This can be called outside the Gtk main thread. + """ + if self.workthread is not None: + self.workthread.cancel() + self.close() diff --git a/src/sgzenity/__init__.py b/src/sgzenity/__init__.py index 4458d26..1f00bcc 100644 --- a/src/sgzenity/__init__.py +++ b/src/sgzenity/__init__.py @@ -1,4 +1,5 @@ from .sgszenity import ( + ProgressBar, calendar, color_selection, entry, @@ -6,7 +7,6 @@ file_selection, message, password, - progress_bar, question, scale, sglist, diff --git a/src/sgzenity/loading.glade b/src/sgzenity/loading.glade new file mode 100644 index 0000000..6bfa9bd --- /dev/null +++ b/src/sgzenity/loading.glade @@ -0,0 +1,52 @@ + + + + + False + 10 + popup + Please wait... + True + center-always + True + dialog + True + True + True + False + False + + + True + False + 5 + + + True + False + <span size="x-large">Processing. Please wait...</span> + True + + + True + True + 0 + + + + + 50 + True + False + True + + + False + True + 1 + + + + + + diff --git a/src/sgzenity/sgszenity.py b/src/sgzenity/sgszenity.py index 52c72ce..a94e44e 100644 --- a/src/sgzenity/sgszenity.py +++ b/src/sgzenity/sgszenity.py @@ -1,39 +1,18 @@ #! /usr/bin/env python3 # -*- coding:utf-8 -*- -from .base import DEFAULT_HEIGHT, DEFAULT_WIDTH, ZLIST_HEIGHT, Gtk +from .base import DEFAULT_HEIGHT, DEFAULT_WIDTH, ZLIST_HEIGHT, GObject, Gtk from .SGCalendar import SGCalendar from .SGColorSelection import SGColorSelection from .SGEntryMessage import SGEntryMessage from .SGEntryPassword import SGEntryPassword from .SGFileSection import SGFileSelection from .SGList import SGList -from .SGProgresBar import SGProgressBar +from .SGProgresBar import ProgressBar from .SGScale import SGScale from .simpleDialog import SGSimpleDialog -def progress_bar( - title, - text, - pulse_mode, - callback=None, - width=DEFAULT_WIDTH, - height=DEFAULT_HEIGHT, - timeout=None, -): - progress = SGProgressBar( - title, - text, - pulse_mode, - callback, - width=width, - height=height, - timeout=timeout, - ) - return progress - - def _simple_dialog(dialog_type, text, title, width, height, timeout): dialog = SGSimpleDialog(dialog_type, text, title, width, height, timeout) dialog.run() diff --git a/src/sgzenity/simpleDialog.py b/src/sgzenity/simpleDialog.py index 90f2e4e..c95adac 100644 --- a/src/sgzenity/simpleDialog.py +++ b/src/sgzenity/simpleDialog.py @@ -26,6 +26,7 @@ def __init__(self, dialog_type, text, *args, **kwargs): message_format=None, ) self.vbox.pack_start(self.dialog, True, True, 0) + self.init_dialog() def init_dialog(self): diff --git a/src/sgzenity/thread.py b/src/sgzenity/thread.py index 76b0b56..416e169 100644 --- a/src/sgzenity/thread.py +++ b/src/sgzenity/thread.py @@ -5,18 +5,27 @@ class WorkerThread(threading.Thread): - def __init__(self, delegate): + def __init__(self, data=None): threading.Thread.__init__(self) - self.delegate = delegate - self.done = False + self.data = data + self.stop = False def run(self): - while True: - if self.done: - print('Background thread shutting down cleanly') - break - if hasattr(self.delegate, "refresh_in_thread"): - self.delegate.refresh_in_thread() + # while True: + # if self.done: + # print('Background thread shutting down cleanly') + # break + # self.delegate() + # # Sleep for a little bit ... + # time.sleep(random.uniform(0.01, 0.1)) + self.payload() - # Sleep for a little bit ... - time.sleep(random.uniform(0.01, 0.1)) + def cancel(self): + """Cancel the execution task + + :return: + """ + self.stop = True + + def payload(self): + raise Exception('Please subclass and implement WorkingThread.payload()')