New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[next_release] removeAllWidgets() doesn't include toolbar. #339

Closed
mpmc opened this Issue Jan 5, 2018 · 14 comments

Comments

Projects
None yet
2 participants
@mpmc
Contributor

mpmc commented Jan 5, 2018

While tinkering I came across an interesting development, removeAllWidgets() doesn't remove toolbars so you end up with something like this..

image

WIP code

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  gui.py
#
#  Copyright 2018 Mark Clarkstone <git@markclarkstone.co.uk>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#
#
import gettext
import traceback
import appJar
import hs602

gettext.install('Hs602util')


class Gui(appJar.gui):
    """Simple HS602 utility using appJar."""
    def __init__(self, *args):
        # Initialise parent.
        super().__init__()
        self.title = _('HS602 Utility')
        # GUI Settings.
        self.setTitle('{}'.format(self.title))
        # Theme.
        self.useTtk()
        self.setTtkTheme('clam')

        # HS602.
        self.controller = hs602.Controller()
        self.controller_cache = {}

        # Launch!
        self.launch(*args)

    # Helper methods.
    def widget_defaults(self, **kwargs):
        """Set widget defaults.

        :param inside: Set inside padding instead?
        :param sticky: Sticky value (default nesw).
        :param stretch: Stretch value (default both).
        :param expand: Expand value (default none).
        """
        # Outside the widget.
        out_x = 5
        out_y = 5
        # Inside the widget.
        in_x = 5
        in_y = 5

        # Get the values.
        stretch, inside, sticky, expand = [
            kwargs.get('stretch', 'both'),
            kwargs.get('inside', False),
            kwargs.get('sticky', 'nesw'),
            kwargs.get('expand'),
        ]
        # Call!
        self.setSticky(sticky)
        self.setStretch(stretch)

        if expand:
            self.setExpand(expand)

        if inside is True:
            self.setInPadding(in_x, in_y)
        else:
            self.setPadding(out_x, out_y)

    def error(self, exc):
        """Display an error."""
        # Properties.
        # Label.
        label_text = _('Sorry there\'s a problem, the details of '
                       'which are below.\nIf the problem persists, '
                       'please report it.\n\nThank you.')
        # Button.
        button_text = _('Quit')
        button_tooltip = _('Close the utility')
        # Textarea.
        textarea_text = _('No traceback available')
        # Attempt to get the traceback.
        try:
            textarea_text = ''.join(traceback.format_tb(
                                    exc.__traceback__))
        except AttributeError:
            pass

        # Format.
        textarea_text = _('{}\n\n{}').format(exc, textarea_text)
        label_text = '{}'.format(label_text)
        button_text = '{}'.format(button_text)

        def show():
            """Display the error."""
            # Reset the GUI!
            self.removeAllWidgets()

            self.widget_defaults(sticky='ew', stretch='none')
            # Add widgets - error label (top).
            self.addLabel(label_text, label_text, 0, 0)
            self.setLabelAlign(label_text, 'left')

            # Quit button.
            self.addButton(button_text, self.stop, 2, 0)
            self.setButtonTooltip(button_text,
                                  '{}'.format(button_tooltip))

            # Text area.
            self.widget_defaults()
            self.addScrolledTextArea(textarea_text, 1, 0)
            self.setTextArea(textarea_text, textarea_text)
            self.disableTextArea(textarea_text)
            self.addMenuEdit(inMenuBar=False)

        # To prevent weird things happening, queue.
        self.queueFunction(show)

    def interlude(self, text=None):
        """Clear the widgets and a show message - used during callbacks.

        text: Optional message to show.
        """
        text = text or _('\u231B Please wait..')
        text = '{}'.format(text)

        # Clear and display the message.
        self.removeAllWidgets()
        self.widget_defaults()
        self.addLabel(text, text)
        self.setLabelAlign(text, 'center')

    def callback(self, method, callback=None, *args, **kwargs):
        """Simple (queued) callback.

        :param method: Method to thread.
        :param callback: Optional method to receive the result.
        :param args: Positional arguments (for the threaded method).
        :param kwargs: Keyword arguments (for the threaded method).
        """
        text = _('Callback "{}" isn\'t callable').format(callback)

        def run():
            """Run the method in a new thread, catch errors."""
            try:
                ret = method(*args, **kwargs)
                # Queue the run_callback!
                self.queueFunction(run_callback, ret)
            except Exception as exc:
                self.error(exc)

        def run_callback(result):
            """Run the callback, pass the result, catch errors."""
            try:
                # Only run if callable!
                if callable(callback):
                    callback(result)
                else:
                    self.warn(text)
            except Exception as exc:
                self.error(exc)

        # Run!
        self.thread(run)

    def discover(self, *args, **kwargs):
        """Discover devices."""
        # Interlude message.
        interlude = _('\u23F2 Probing devices, please wait..')
        # No devices found prompt.
        nodev_title = _('No Devices')
        nodev_text = _('No devices were found on your network. :(\n'
                       'Scan again (Retry) or connect manually '
                       '(Cancel)?')
        # Frame title.
        frame = _('Available Devices')
        # Info label.
        label = _('Which device do you want to connect to?')
        # Connect button.
        connect = _('\u260D Connect')
        # Re-scan button.
        rescan = _('\u26B2 Re-scan')
        # Manual button.
        manual = _('\u25C9 Manual connection')

        def probe():
            """Probe for devices."""
            return self.controller.devices

        def show(result):
            """Show devices."""
            # No devices?
            if len(result) == 0:
                question = self.retryBox('{}'.format(nodev_title),
                                         '{}'.format(nodev_text))
                if question in ['yes', True, _('yes')]:
                    self.discover()
                else:
                    self.manual()
                return

            # Which do we connect to?
            self.removeAllWidgets()

            # These buttons are outside the frame.
            self.widget_defaults(sticky='nesw')
            self.addButton('{}'.format(rescan), self.discover, 2, 0, 1)
            self.addButton('{}'.format(manual), self.manual, 2, 1, 1)

            # Display the address.
            with self.labelFrame('{}'.format(frame), 0, 0, 2):
                self.widget_defaults()
                # Info label.
                info = '{}'.format(label)
                self.addLabel(info, info, 0, 0, 2)
                self.setLabelAlign(info, 'center')
                # Option box & connect button.
                self.addOptionBox('addr', result, 1, 0, 1)
                self.addButton('{}'.format(connect), self.connect,
                               1, 1, 1)

        self.interlude(interlude)
        self.callback(probe, show)

    def launch(self, *args):
        """Start the app - this just launches discover for now."""
        self.discover()

    def values(self, **kwargs):
        """Get all values, merge, convert & return as dictionary.

        kwargs will be _appended_ to the input list.

        :param convert: Convert.
        """
        merge = dict()
        convert = kwargs.get('convert', True)

        # All available inputs.
        inputs = [
                  self.getAllOptionBoxes(),
                  self.getAllEntries(),
                  self.getAllCheckBoxes(),
                  self.getAllRadioButtons(),
                  self.getAllProperties(),
                  self.getAllScales(),
                  self.getAllMeters(),
                  self.getAllSpinBoxes(),
                  self.getAllListBoxes(),
                  self.getAllDatePickers(),
                  kwargs,
        ]

        for dictionary in inputs:
            merge.update(dictionary)
            for key, value in merge.items():
                # Remove start/end spaces from values.
                if isinstance(value, str):
                    merge[key] = value.strip()
                if convert is True:
                    if isinstance(value, float):
                        merge[key] = int(round(value))

        return merge

    def manual(self, btn=None):
        """Manual connection.

        :param btn: The button that called this method (not used).
        """
        rescan = _('\u2753 Re-scan')
        connect = _('\u260D Connect')
        frame = _('Manual Connection')
        address = _('Address')
        tcp = _('TCP')
        timeout = _('Timeout')
        info = _('\u2139 Just an (IP) address is required, leave the '
                 'rest blank to use the defaults.')
        # Reset to defaults.
        self.removeAllWidgets()
        self.widget_defaults()

        # Buttons!
        self.addButton('{}'.format(rescan), self.discover, 5, 0, 1)
        self.addButton('{}'.format(connect), self.connect, 5, 1, 1)

        # Frame - before the above buttons.
        with self.labelFrame('{}'.format(frame), 0, 0, 2):
            self.widget_defaults()
            # Entry.
            self.addEntry('addr', 0, 1)
            self.addNumericEntry('tcp', 1, 1)
            self.addNumericEntry('timeout', 3, 1)
            self.setFocus('addr')
            self.setEntryMaxLength('tcp', 5)
            self.setEntryMaxLength('timeout', 3)
            # Labels.
            self.addLabel('addr', '{}'.format(address), 0, 0)
            self.addLabel('tcp', '{}'.format(tcp), 1, 0)
            self.addLabel('to',  '{}'.format(timeout), 3, 0)
            self.addLabel('info', '{}'.format(info), 4, 0, 2)
            self.setLabelAlign('info', 'center')
            self.setLabelAlign('addr', 'center')
            self.setLabelAlign('tcp', 'center')
            self.setLabelAlign('to', 'center')
            self.addMenuEdit(inMenuBar=False)

    def connect(self, *args, **kwargs):
        """Connect to the device."""
        # Interlude message.
        interlude = _('\u260D Connecting, please wait..')
        # Address.
        address_title = _('Oops!')
        address_text = _('An address is required/Address is '
                         'Invalid!')
        # Invalid response.
        settings_title = _('Sorry!')
        settings_text = _('There was a problem getting settings! :(\n'
                          'This may indicate that your device has '
                          'crashed, or you have a network issue.\n\n'
                          '- If you entered an address, check it\'s '
                          'valid.\n'
                          '- Check your firewall isn\'t blocking '
                          'ports.\n'
                          '- As a last resort, reboot the device.\n\n'
                          'Try again (Yes) or view the debug log (No)?')

        def connect(**kwargs):
            """Get device information.

            :param kwargs: Connection info.
            """
            try:
                self.controller.__init__(**kwargs)
                # Cache the data & return
                self.controller_cache = self.controller.settings
                return self.controller_cache
            except Exception as exc:
                return exc

        def result(result):
            """Check the result before calling the main window.

            :param result: response from the (threaded) method.
            """
            if not result or not isinstance(result, dict):
                # Do we try again or show the result as an error?
                question = self.questionBox('{}'.format(settings_title),
                                            '{}'.format(settings_text))
                if question in ['yes', True, _('yes')]:
                    return self.manual()
                # If we hit here, the user pressed no.
                return self.error(result)
            # Call the main window.
            self.main()

        # First get & merge all user-input sources - inc keywords.
        values = self.values(**kwargs)
        addr = values.get('addr', '')
        tcp = values.get('tcp', 0)
        timeout = values.get('timeout', 0)

        # Check the address.
        if not len(addr) > 0:
            self.errorBox('{}'.format(address_title),
                          '{}'.format(address_text))
            # This is a bit ugly, but at least the user can enter info.
            # And it prevents getting stuck in an interlude.
            return self.manual()
        try:
            # Just remove invalid tcp/timeout.
            if tcp not in range(1, 65535):
                values.pop('tcp')
            if timeout not in range(1, 120):
                values.pop('timeout')
        except KeyError:
            pass

        # Display the main window!
        self.interlude(interlude)
        self.callback(connect, result, **values)

    def toolbar(self, default=None):
        """Toolbar."""
        # Toolbar titles & methods.
        toolbar = {
            _('Status'): 'main',
            _('Settings'): 'settings',
            _('About'): 'about',
        }
        # Combined toolbar.
        toolbar_list = [name for name, method in toolbar.items()]

        def menu_action(button=None):
            button = '{}'.format(toolbar.get(button))
            method = getattr(self, button)
            method()

        # Add toolbar.
        self.addToolbar(toolbar_list, menu_action)

    def main(self):
        """The status window - this is the default."""
        self.removeAllWidgets()
        self.toolbar()

    def settings(self):
        """The settings window."""
        self.removeAllWidgets()
        self.toolbar()


def main(args):
    app = Gui(*args)
    app.go()
    return 0


if __name__ == '__main__':
    import sys
    sys.exit(main(sys.argv))
@jarvisteach

This comment has been minimized.

Owner

jarvisteach commented Jan 5, 2018

Yeah, I think at the moment it won't remove any of the bars - toolbar, menubar, or statusbar - they're not really classed as widgets behind the scenes.

I don't really think I want it to either... I think it should stay just being widgets, and instead there could be separate functions to remove each of the bars.

There could be a new function, that removes absolutely everything from the GUI - widgets, bars, etc...

However - having two toolbar buttons with the same text looks wrong! I'm not sure that should be possible...

@mpmc

This comment has been minimized.

Contributor

mpmc commented Jan 6, 2018

There could be a new function, that removes absolutely everything from the GUI - widgets, bars, etc...

reset()?

I don't really think I want it to either... I think it should stay just being widgets, and instead there could be separate functions to remove each of the bars.

or perhaps two functions, removeAllBars() and removeBar('tool|status|menu')?

@jarvisteach jarvisteach added this to the 1.0 milestone Jan 6, 2018

@mpmc

This comment has been minimized.

Contributor

mpmc commented Jan 6, 2018

I tried to recreate this so I could easily remove the toolbar, but it seems addButtons completely ignores any sticky/stretch options :(

@jarvisteach

This comment has been minimized.

Owner

jarvisteach commented Jan 6, 2018

There are functions to .hideToolbar() and .showToolbar() - they won't let you change the buttons, but will hide the toolbar...

@mpmc

This comment has been minimized.

Contributor

mpmc commented Jan 6, 2018

@jarvisteach Yep the problem is I'd like to be able to generate a different menu on the fly depending on what 'mode' the app is in as well as prevent the user from trying to switch to another during a network action.

jarvisteach added a commit that referenced this issue Jan 7, 2018

New toolbar functions #339
Three new functions:
* `.addToolbarButton()` - adds a single button to the toolbar
* `.removeToolbarButton()` - removes the named button from the toolbar
* `.removeToolbar()` - removes all buttons from the toolbar

Also:
* moved declaration of `self.pinBut` to constructor - it was in
`.addToolbar()`
* moved packing of toolbar from constructor to `.addToolbar()` so that
it gets shown again when new buttons added, also gets rid of toolbar
from other GUIs
* updated docs & testing
@jarvisteach

This comment has been minimized.

Owner

jarvisteach commented Jan 7, 2018

@mpmc - I've added new functions to remove toolbars, see the commit above.

jarvisteach added a commit that referenced this issue Jan 7, 2018

New statusbar functions #339
Three new functions:
* `.removeStatusbarField(field)` - removes the specified field from the
statusbar
* `.removeStatusbar()` - removes the statusbar

Also:
* added check to stop multiple statusbars being added
* updated docs & testing
@jarvisteach

This comment has been minimized.

Owner

jarvisteach commented Jan 7, 2018

New functions to remove statusbars

@mpmc

This comment has been minimized.

Contributor

mpmc commented Jan 7, 2018

.removeToolbar doesn't seem to work for me, only change to the above WIP code is..

        # Add toolbar.
        self.removeToolbar()
        self.addToolbar(toolbar_list, menu_action)

Example:
image

@jarvisteach

This comment has been minimized.

Owner

jarvisteach commented Jan 8, 2018

Maybe you didn't get the latest code?

When I run this:

from appJar import gui 
buttons = ["Status", "Settings", "About"]

def add(btn=None):
    app.addToolbar(buttons, delete)

def delete(btn=None):
    app.removeToolbar()

with gui() as app:
    app.label("title", "title")
    app.addButton("ADD", add)
    add()

Pressing any of the buttons on the toolbar removes them all, and pressing the ADD button brings them back.

If I press the ADD button a second time, I get the following error: Exception: Invalid toolbar button name: Status already exists

jarvisteach added a commit that referenced this issue Jan 8, 2018

testing #339
Added option so `.label()` can receive a single parameter, and will use
it as its text & title #235.
@mpmc

This comment has been minimized.

Contributor

mpmc commented Jan 9, 2018

Your code works but mine doesn't 😕

Here's the full code that'll run without hs602 :) for you to try :)

The installed appjar version is @next_release 4a3d44c

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  gui.py
#
#  Copyright 2018 Mark Clarkstone <git@markclarkstone.co.uk>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#
#
import gettext
import traceback
import appJar

gettext.install('Hs602util')


class Gui(appJar.gui):
    """Simple HS602 utility using appJar."""
    def __init__(self, *args):
        # Initialise parent.
        super().__init__()
        self.title = _('HS602 Utility')
        # GUI Settings.
        self.setTitle('{}'.format(self.title))
        # Theme.
        #self.useTtk()
        #self.setTtkTheme('clam')

        # HS602.
        #self.controller = hs602.Controller()
        self.controller_cache = {}

        # Launch!
        self.launch(*args)

    # Helper methods.
    def widget_defaults(self, **kwargs):
        """Set widget defaults.

        :param inside: Set inside padding instead?
        :param sticky: Sticky value (default nesw).
        :param stretch: Stretch value (default both).
        :param expand: Expand value (default none).
        """
        # Outside the widget.
        out_x = 5
        out_y = 5
        # Inside the widget.
        in_x = 5
        in_y = 5

        # Get the values.
        stretch, inside, sticky, expand = [
            kwargs.get('stretch', 'both'),
            kwargs.get('inside', False),
            kwargs.get('sticky', 'nesw'),
            kwargs.get('expand'),
        ]
        # Call!
        self.setSticky(sticky)
        self.setStretch(stretch)

        if expand:
            self.setExpand(expand)

        if inside is True:
            self.setInPadding(in_x, in_y)
        else:
            self.setPadding(out_x, out_y)

    def error(self, exc):
        """Display an error."""
        # Properties.
        # Label.
        label_text = _('Sorry there\'s a problem, the details of '
                       'which are below.\nIf the problem persists, '
                       'please report it.\n\nThank you.')
        # Button.
        button_text = _('Quit')
        button_tooltip = _('Close the utility')
        # Textarea.
        textarea_text = _('No traceback available')
        # Attempt to get the traceback.
        try:
            textarea_text = ''.join(traceback.format_tb(
                                    exc.__traceback__))
        except AttributeError:
            pass

        # Format.
        textarea_text = _('{}\n\n{}').format(exc, textarea_text)
        label_text = '{}'.format(label_text)
        button_text = '{}'.format(button_text)

        def show():
            """Display the error."""
            # Reset the GUI!
            self.removeAllWidgets()

            self.widget_defaults(sticky='ew', stretch='none')
            # Add widgets - error label (top).
            self.addLabel(label_text, label_text, 0, 0)
            self.setLabelAlign(label_text, 'left')

            # Quit button.
            self.addButton(button_text, self.stop, 2, 0)
            self.setButtonTooltip(button_text,
                                  '{}'.format(button_tooltip))

            # Text area.
            self.widget_defaults()
            self.addScrolledTextArea(textarea_text, 1, 0)
            self.setTextArea(textarea_text, textarea_text)
            self.disableTextArea(textarea_text)
            self.addMenuEdit(inMenuBar=False)

        # To prevent weird things happening, queue.
        self.queueFunction(show)

    def interlude(self, text=None):
        """Clear the widgets and a show message - used during callbacks.

        text: Optional message to show.
        """
        text = text or _('\u231B Please wait..')
        text = '{}'.format(text)

        # Clear and display the message.
        self.removeAllWidgets()
        self.widget_defaults()
        self.addLabel(text, text)
        self.setLabelAlign(text, 'center')

    def callback(self, method, callback=None, *args, **kwargs):
        """Simple (queued) callback.

        :param method: Method to thread.
        :param callback: Optional method to receive the result.
        :param args: Positional arguments (for the threaded method).
        :param kwargs: Keyword arguments (for the threaded method).
        """
        text = _('Callback "{}" isn\'t callable').format(callback)

        def run():
            """Run the method in a new thread, catch errors."""
            try:
                ret = method(*args, **kwargs)
                # Queue the run_callback!
                self.queueFunction(run_callback, ret)
            except Exception as exc:
                self.error(exc)

        def run_callback(result):
            """Run the callback, pass the result, catch errors."""
            try:
                # Only run if callable!
                if callable(callback):
                    callback(result)
                else:
                    self.warn(text)
            except Exception as exc:
                self.error(exc)

        # Run!
        self.thread(run)

    def discover(self, *args, **kwargs):
        """Discover devices."""
        # Interlude message.
        interlude = _('\u23F2 Probing devices, please wait..')
        # No devices found prompt.
        nodev_title = _('No Devices')
        nodev_text = _('No devices were found on your network. :(\n'
                       'Scan again (Retry) or connect manually '
                       '(Cancel)?')
        # Frame title.
        frame = _('Available Devices')
        # Info label.
        label = _('Which device do you want to connect to?')
        # Connect button.
        connect = _('\u260D Connect')
        # Re-scan button.
        rescan = _('\u26B2 Re-scan')
        # Manual button.
        manual = _('\u25C9 Manual connection')

        def probe():
            """Probe for devices."""
            return ['test', 'test2']

        def show(result):
            """Show devices."""
            # No devices?
            if len(result) == 0:
                question = self.retryBox('{}'.format(nodev_title),
                                         '{}'.format(nodev_text))
                if question in ['yes', True, _('yes')]:
                    self.discover()
                else:
                    self.manual()
                return

            # Which do we connect to?
            self.removeAllWidgets()

            # These buttons are outside the frame.
            self.widget_defaults(sticky='nesw')
            self.addButton('{}'.format(rescan), self.discover, 2, 0, 1)
            self.addButton('{}'.format(manual), self.manual, 2, 1, 1)

            # Display the address.
            with self.labelFrame('{}'.format(frame), 0, 0, 2):
                self.widget_defaults()
                # Info label.
                info = '{}'.format(label)
                self.addLabel(info, info, 0, 0, 2)
                self.setLabelAlign(info, 'center')
                # Option box & connect button.
                self.addOptionBox('addr', result, 1, 0, 1)
                self.addButton('{}'.format(connect), self.connect,
                               1, 1, 1)

        self.interlude(interlude)
        self.callback(probe, show)

    def launch(self, *args):
        """Start the app - this just launches discover for now."""
        self.discover()

    def values(self, **kwargs):
        """Get all values, merge, convert & return as dictionary.

        kwargs will be _appended_ to the input list.

        :param convert: Convert.
        """
        merge = dict()
        convert = kwargs.get('convert', True)

        # All available inputs.
        inputs = [
                  self.getAllOptionBoxes(),
                  self.getAllEntries(),
                  self.getAllCheckBoxes(),
                  self.getAllRadioButtons(),
                  self.getAllProperties(),
                  self.getAllScales(),
                  self.getAllMeters(),
                  self.getAllSpinBoxes(),
                  self.getAllListBoxes(),
                  self.getAllDatePickers(),
                  kwargs,
        ]

        for dictionary in inputs:
            merge.update(dictionary)
            for key, value in merge.items():
                # Remove start/end spaces from values.
                if isinstance(value, str):
                    merge[key] = value.strip()
                if convert is True:
                    if isinstance(value, float):
                        merge[key] = int(round(value))

        return merge

    def manual(self, btn=None):
        """Manual connection.

        :param btn: The button that called this method (not used).
        """
        rescan = _('\u2753 Re-scan')
        connect = _('\u260D Connect')
        frame = _('Manual Connection')
        address = _('Address')
        tcp = _('TCP')
        timeout = _('Timeout')
        info = _('\u2139 Just an (IP) address is required, leave the '
                 'rest blank to use the defaults.')
        # Reset to defaults.
        self.removeAllWidgets()
        self.widget_defaults()

        # Buttons!
        self.addButton('{}'.format(rescan), self.discover, 5, 0, 1)
        self.addButton('{}'.format(connect), self.connect, 5, 1, 1)

        # Frame - before the above buttons.
        with self.labelFrame('{}'.format(frame), 0, 0, 2):
            self.widget_defaults()
            # Entry.
            self.addEntry('addr', 0, 1)
            self.addNumericEntry('tcp', 1, 1)
            self.addNumericEntry('timeout', 3, 1)
            self.setFocus('addr')
            self.setEntryMaxLength('tcp', 5)
            self.setEntryMaxLength('timeout', 3)
            # Labels.
            self.addLabel('addr', '{}'.format(address), 0, 0)
            self.addLabel('tcp', '{}'.format(tcp), 1, 0)
            self.addLabel('to',  '{}'.format(timeout), 3, 0)
            self.addLabel('info', '{}'.format(info), 4, 0, 2)
            self.setLabelAlign('info', 'center')
            self.setLabelAlign('addr', 'center')
            self.setLabelAlign('tcp', 'center')
            self.setLabelAlign('to', 'center')
            self.addMenuEdit(inMenuBar=False)

    def connect(self, *args, **kwargs):
        """Connect to the device."""
        # Interlude message.
        interlude = _('\u260D Connecting, please wait..')
        # Address.
        address_title = _('Oops!')
        address_text = _('An address is required/Address is '
                         'Invalid!')
        # Invalid response.
        settings_title = _('Sorry!')
        settings_text = _('There was a problem getting settings! :(\n'
                          'This may indicate that your device has '
                          'crashed, or you have a network issue.\n\n'
                          '- If you entered an address, check it\'s '
                          'valid.\n'
                          '- Check your firewall isn\'t blocking '
                          'ports.\n'
                          '- As a last resort, reboot the device.\n\n'
                          'Try again (Yes) or view the debug log (No)?')

        def connect(**kwargs):
            """Get device information.

            :param kwargs: Connection info.
            """
            try:
                self.controller.__init__(**kwargs)
                # Cache the data & return
                return {'demo':'demo'}
            except Exception as exc:
                return exc

        def result(result):
            """Check the result before calling the main window.

            :param result: response from the (threaded) method.
            """
            if not result or not isinstance(result, dict):
                # Do we try again or show the result as an error?
                question = self.questionBox('{}'.format(settings_title),
                                            '{}'.format(settings_text))
                if question in ['yes', True, _('yes')]:
                    return self.manual()
                # If we hit here, the user pressed no.
                return self.error(result)
            # Call the main window.
            self.main()

        # First get & merge all user-input sources - inc keywords.
        values = self.values(**kwargs)
        addr = values.get('addr', '')
        tcp = values.get('tcp', 0)
        timeout = values.get('timeout', 0)

        # Check the address.
        if not len(addr) > 0:
            self.errorBox('{}'.format(address_title),
                          '{}'.format(address_text))
            # This is a bit ugly, but at least the user can enter info.
            # And it prevents getting stuck in an interlude.
            return self.manual()
        try:
            # Just remove invalid tcp/timeout.
            if tcp not in range(1, 65535):
                values.pop('tcp')
            if timeout not in range(1, 120):
                values.pop('timeout')
        except KeyError:
            pass

        # Display the main window!
        self.interlude(interlude)
        self.callback(connect, result, **values)

    def window_toolbar(self):
        """Generic window toolbar."""
        # Toolbar titles & methods.
        toolbar = {
            _('Status'): 'main',
            _('Settings'): 'settings',
            _('About'): 'about',
        }
        # Combined toolbar.
        toolbar_list = [name for name, method in toolbar.items()]

        def menu_action(button=None):
            button = '{}'.format(toolbar.get(button))
            method = getattr(self, button)
            method()

        # Add toolbar.
        self.removeToolbar()
        self.addToolbar(toolbar_list, menu_action)

    def main(self):
        """The status window - this is the default."""
        # Clear all widgets.
        self.removeAllWidgets()
        # Add toolbar.
        self.window_toolbar()

    def settings(self):
        """The settings window."""
        # Clear all widgets.
        self.removeAllWidgets()
        # Add toolbar.
        self.window_toolbar()


def main(args):
    app = Gui(*args)
    app.go()
    return 0


if __name__ == '__main__':
    import sys
    sys.exit(main(sys.argv))
@jarvisteach

This comment has been minimized.

Owner

jarvisteach commented Jan 9, 2018

Ah, I think I worked it out - .removeAllWidgets() is deleting the toolbar details form the WidgetStore (like it was for menus). So, when you cal /removeToolbar() there's nothing to remove, and when you then call .addToolbar() there's no conflict...

I'll clean it up.

@jarvisteach

This comment has been minimized.

Owner

jarvisteach commented Jan 9, 2018

@mpmc - Actually, this is going to be a bit more work than I thought.

A short-term fix would be to remove the toolbar before removing all widgets.

jarvisteach added a commit that referenced this issue Jan 9, 2018

@jarvisteach jarvisteach referenced this issue Jan 9, 2018

Open

Refactor menus #305

6 of 7 tasks complete

jarvisteach added a commit that referenced this issue Jan 9, 2018

Added flag to not include icons
Changed `.removeAllWidgets()` to not  remove all items from
WidgetManager - menus, accelerators, toolbars, images, etc are left in
now #339 & #305

Also added additional constructor flag, to prevent adding a favicon #343

jarvisteach added a commit that referenced this issue Jan 9, 2018

@jarvisteach

This comment has been minimized.

Owner

jarvisteach commented Jan 9, 2018

A RootPage group isn't created, so this wasn't necessary.
ContainerLog includes all containers - including the rootPage, which might cause an issue - but it should be flushed, as it also has labelFrames, etc...

jarvisteach added a commit that referenced this issue Mar 24, 2018

@jarvisteach

This comment has been minimized.

Owner

jarvisteach commented Aug 11, 2018

Covered by issue #462

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment