Skip to content
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

Config file support #16

Closed
stevenj2019 opened this issue Nov 7, 2020 · 32 comments
Closed

Config file support #16

stevenj2019 opened this issue Nov 7, 2020 · 32 comments

Comments

@stevenj2019
Copy link
Member

Config File to manage various settings within the application,
such as Trigger and Device.
This will allow for:
-> Modular Trigger System (expanding to a DIY Trigger oppurunity)
-> Preferred Device (i.e. if you have a device you routinely removed and would like BusKill to ignore its event)
-> a "First Start" sequence to allow for first time configuration

Once Configuration Files are an option within BusKill we can perform further enhancements such as:
-> update frequency
-> Trigger EcoSystem

@mrx23dot
Copy link

Just have a config.txt, look for
pre_shutdown = 'os calls' if exists execute

`
import configparser

def configLoad(initConf):
""" creates file if doesn't exist """
global CONFIG_FILE
readDict = dict()
config = configparser.ConfigParser()
try:
config.read_file(open(CONFIG_FILE))
readDict = dict(config[configparser.DEFAULTSECT])
except Exception as e:
print(' exception [config load]',e)
return dict(initConf, **readDict)

`

@maltfield
Copy link
Member

maltfield commented Oct 16, 2022

TODO: Once this is done, please call set_trigger() inside __init__() of the BusKill object, based on the default trigger that's defined in the config file.

See also

@maltfield
Copy link
Member

I started working on this in the Trigger Selector ticket. See here for more info:

@maltfield
Copy link
Member

maltfield commented Nov 12, 2022

It appears that I'm already using this kivy Config module!

  • from kivy.config import Config
    Config.set('kivy', 'exit_on_escape', '0')
    Config.set('input', 'mouse', 'mouse,multitouch_on_demand')

I set two options to change the following behaviours:

  1. exit_on_escape = 0 to prevent the default behaviour where if the user presses the esc key on their keyboard, the app closes 56dc42b
  2. disable multitouch emulation to prevent the default behaviour where a strange orange circle would appear if the user right-clicked anywhere on the app (it's multitouch emulation) 3c8625d

Interestingly these settings didn't persist in the kivy config file. My guess is that's because I'm only applying them at runtime at the start of buskill_gui.py every time. I didn't even know there was a config file that these options mapped-to for persistence. I think I'd just need to call write() to make them persist to the file.

user@buskill:~$ cat .kivy/config.ini 
[kivy]
keyboard_repeat_delay = 300
keyboard_repeat_rate = 30
log_dir = logs
log_enable = 1
log_level = info
log_name = kivy_%y-%m-%d_%_.txt
window_icon = 
keyboard_mode = 
keyboard_layout = qwerty
desktop = 1
exit_on_escape = 1
pause_on_minimize = 0
kivy_clock = default
default_font = ['Roboto', 'data/fonts/Roboto-Regular.ttf', 'data/fonts/Roboto-Italic.ttf', 'data/fonts/Roboto-Bold.ttf', 'data/fonts/Roboto-BoldItalic.ttf']
log_maxfiles = 100
window_shape = data/images/defaultshape.png
config_version = 21

[graphics]
display = -1
fullscreen = 0
height = 600
left = 0
maxfps = 60
multisamples = 2
position = auto
rotation = 0
show_cursor = 0
top = 0
width = 800
resizable = 1
borderless = 0
window_state = visible
minimum_width = 0
minimum_height = 0
min_state_time = .035
allow_screensaver = 1
shaped = 0
vsync = 
verify_gl_main_thread = 1
custom_titlebar = 0
custom_titlebar_border = 5

[input]
mouse = mouse
%(name)s = probesysfs

[postproc]
double_tap_distance = 20
double_tap_time = 250
ignore = []
jitter_distance = 0
jitter_ignore_devices = mouse,mactouch,
retain_distance = 50
retain_time = 0
triple_tap_distance = 20
triple_tap_time = 375

[widgets]
scroll_timeout = 250
scroll_distance = 20
scroll_friction = 1.
scroll_stoptime = 300
scroll_moves = 5

[modules]

[network]
useragent = curl

user@buskill:~$

maltfield added a commit that referenced this issue Nov 13, 2022
…rser object and the App's build_config() method

 * #16

TODO: update the `setupDatDir()` to prioritize $XDG_CONFIG_HOME on linux systems

 * https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables
 * #39
@maltfield
Copy link
Member

In the above commits, I was successfully able to combine the kivy and buskill files into one ini config file, but unfortunately I cannot prevent kivy from creating the ~/.kivy/ dir because the logic for determining the buskill DATA_DIR is in the BusKill object's __init__() function. But the kivy dir is setup earlier, before that object is instantiated.

Kivy will allow you to specify where its data dir is with the environment variable KIVY_HOME.

So the only way to catch this early enough would be to move the logic for determining DATA_DIR to main.py, and then set KIVY_HOME and BUSKILL_DATA_DIR at that time. This would have the added benefit of being able to put out buskill log files there, too.

@maltfield
Copy link
Member

Today I successfully moved the instantiation of the buskill object into main.py, so it happens before the GUI or CLI scripts are called. This allowed me to set the KIVY_HOME environment variable to the buskill DATA_DIR directory before the kivy app is ever created, and so now the kivy config.ini directory (and all other kivy files) are being created in our main buskill DATA_DIR as desired

@maltfield
Copy link
Member

Today I successfully got the kivy built-in settings widget to be loaded in the Settings screen of the BusKill app.

TODO: figure-out how to limit the settings items that are enumerated to only those in the [buskill] section of the config.ini file

@maltfield
Copy link
Member

maltfield commented Nov 15, 2022

ok, using the built-in settings widget and a new json file, I was successfully able to limit the config items appearing in the Settings Screen to only the "trigger" key/value, as defined in a json template (which is actually distinct from where the value is stored-to--that's still always the .ini file)

Though this is very robust, I still need to extend the Settings widget because:

  1. When the user chooses between lock-screen and soft-shutdown, there is no subtitle between the text that explains what each option does
  2. On the main "Settings" screen, there is a subtitle, but it doesn't change with each option. It should say what the currently-selected option does, not generically what the trigger option means.
  3. I should add an icon to the left of the option title
  4. I might also want to display an icon to the left of each value title (eg lock-screen and soft-shutdown)
  5. I need to provide the ability to display an interstitial "confirmation" modal before changing some options. For example, I shouldn't let the user set the trigger to soft-shutdown until I warn them of the risk of data loss (but not corruption), and I shouldn't let them set the trigger to hard-shutdown until i warn them of the risk of data loss and corruption. Setting trigger to lock-screen shouldn't require a confirmation, however.
  6. I may also want to subsequent prompt asking them (if BusKill is currently armed) if they want to go ahead and immediately disarm and re-arm with the newly-selected trigger). But probably I should prompt for this one when the user leaves the Settings screen.
  7. Rebind F1 so that it opens the actual Settings Screen (currently it toggles showing the kivy settings somehow)

maltfield added a commit that referenced this issue Nov 15, 2022
This commit is the fruitful effort of tying to figure out the structure of the Kivy Settings classes. Finally, this commit, I was able to remove the title of the Settings panel so it no longer says 'buskill' at the top -- not needed since we only have one "section"

  s.children[0].children[0].children[0].remove_widget( s.children[0].children[0].children[0].children[1] )

This is pretty ridiculious, but I learend:

 1. the 'children' attribute of a given kivy widget is not recursive, so you have to use walk()
 2. If you run remove_widget() directly on a root's grandparent, it won't work. You need to find the right widget and remove it directly from its parent
 3. Somehow the title of the section appears before the options inside that title?

This commit also overrides three Settings classes from kivy/uix/setings.py, but I'm still experiementing. My goal was to just change the way things are displayed, but it's buried in so many levels of inheretance that it's not trival.

TODO: figure out the structure better and document it here:

 * #16
@maltfield
Copy link
Member

maltfield commented Nov 16, 2022

Understanding kivy's SettingsWithNoMenu() class

Today I've been trying to override the methods of the Kivy Settings widget so that I can customize it a bit, but it's a gnarly mess.

I spent some time trying to understand what the heck is in this s variable's SettingsWithNoMenu() class instance. For that, I hacked this code

Code

                        print( str( s.children ) )
                        print( str( s.children[0].children ) )
                        print( str( s.children[0].children[0].children ) )
                        print( str( s.children[0].children[0].children[0].children ) )
                        print( "SettingOptions: " +str( s.children[0].children[0].children[0].children[0].children ) )
                        print( "\t" + str( s.children[0].children[0].children[0].children[0].children[0].children ) )
                        print( "\tBoxLayout: " + str( s.children[0].children[0].children[0].children[0].children[0].children[0].children ) )
                        print( "\t\t" + str( s.children[0].children[0].children[0].children[0].children[0].children[0].children[0].children ) )
                        print( "\tLabel: " + str( s.children[0].children[0].children[0].children[0].children[0].children[1].children ) )
                        print( "Label: " +str( s.children[0].children[0].children[0].children[1].children ) )

Output

There's probably a better, built-in pythonic way to see this, but I just manually added these over-and-over until I was able to see the whole tree. Here's the output:

<kivy.uix.settings.InterfaceWithNoMenu object at 0x717f55169600>]
[<kivy.uix.gridlayout.GridLayout object at 0x717f55172ad0>]
[<kivy.uix.settings.SettingsPanel object at 0x717f551077c0>]
[<kivy.uix.settings.SettingOptions object at 0x717f5511abb0>, <kivy.uix.label.Label object at 0x717f5510a4b0>]
SettingOptions: [<kivy.uix.boxlayout.BoxLayout object at 0x717f551229f0>]
	[<kivy.uix.boxlayout.BoxLayout object at 0x717f55135980>, <kivy.uix.label.Label object at 0x717f5512ba60>]
	BoxLayout: [<kivy.uix.label.Label object at 0x717f5513bbb0>]
		[]
	Label: []
Label: []

Human explanation

Here's the human-readable version: what we have is

The s variable itself is a BusKillSettingsWithNoMenu. That object is just my custom object that overrides the kivy.uix.settings.SettingsWithNoMenu.

The BusKillSettingsWithNoMenu object has one child = a kivy.uix.settings.InterfaceWithNoMenu object

The InterfaceWithNoMenu object has one child = a kivy GridLayout

This GridLayout has one child = a kivy.uix.settings.SettingsPanel object

The SettingsPanel has two children: a kivy.uix.settings.SettingOptions object and a kivy Label object.

The Label has no children.

The SettingOptions object has one child = a kivy BoxLayout

The BoxLayout two children: another BoxLayout and a Label.

The Label has no children.

This nested BoxLayout has one child = another kivy Label.

The Label has no children.

Conclusion

So probably the classes that I need to override are, at least, SettingsPanel and SettingOptions

@maltfield
Copy link
Member

maltfield commented Nov 16, 2022

From above, I'd guess that the Label child of the SettingsPanel is the title that I wanted to remove

s.interface has to useful attributes so I can cut straight through this tree to find the SettingsPanel. Currently we only have one panel, but you can get a list of the panels and also the current panel as follows:

                        print( "s.interface.panels:|" +str(s.interface.panels)+ "|" )
                        print( "s.interface.current_panel:|" +str(s.interface.current_panel)+ "|" )

Example execution:

s.interface:|<kivy.uix.settings.InterfaceWithNoMenu object at 0x7fe9c5eff600>|
s.interface.panels:|{602: <kivy.uix.settings.SettingsPanel object at 0x7fe9c5e9d7c0>}|
s.interface.current_panel:|<kivy.uix.settings.SettingsPanel object at 0x7fe9c5e9d7c0>|
s.interface.children:|[<kivy.uix.gridlayout.GridLayout object at 0x7fe9c5f07ad0>]|
s.interface.walk():|[<kivy.uix.settings.InterfaceWithNoMenu object at 0x7fe9c5eff600>, <kivy.uix.gridlayout.GridLayout object at 0x7fe9c5f07ad0>, <kivy.uix.settings.SettingsPanel object at 0x7fe9c5e9d7c0>, <kivy.uix.label.Label object at 0x7fe9c5ea04b0>, <kivy.uix.settings.SettingOptions object at 0x7fe9c5eb0bb0>, <kivy.uix.boxlayout.BoxLayout object at 0x7fe9c5eb89f0>, <kivy.uix.label.Label object at 0x7fe9c5ec0a60>, <kivy.uix.boxlayout.BoxLayout object at 0x7fe9c5ecb980>, <kivy.uix.label.Label object at 0x7fe9c5ed1bb0>]|
dir(s.interface):|['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__events__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__metaclass__', '__module__', '__ne__', '__new__', '__proxy_getter', '__proxy_setter', '__pyx_vtable__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '_apply_transform', '_bar_color', '_bind_inactive_bar_color', '_bind_inactive_bar_color_ev', '_change_bar_color', '_change_touch_mode', '_context', '_disabled_count', '_disabled_value', '_do_touch_up', '_effect_x_start_width', '_effect_y_start_height', '_get_do_scroll', '_get_hbar', '_get_uid', '_get_vbar', '_handle_x_pos', '_handle_x_size', '_handle_y_pos', '_handle_y_size', '_kwargs_applied_init', '_proxy_ref', '_set_do_scroll', '_set_viewport_size', '_touch', '_touch_in_handle', '_trigger_update_from_scroll', '_update_effect_bounds', '_update_effect_bounds_ev', '_update_effect_widget', '_update_effect_x', '_update_effect_x_bounds', '_update_effect_y', '_update_effect_y_bounds', '_viewport', '_walk', '_walk_reverse', 'add_panel', 'add_widget', 'apply_class_lang_rules', 'apply_property', 'bar_color', 'bar_inactive_color', 'bar_margin', 'bar_pos', 'bar_pos_x', 'bar_pos_y', 'bar_width', 'bind', 'canvas', 'canvas_viewport', 'center', 'center_x', 'center_y', 'children', 'clear_widgets', 'cls', 'collide_point', 'collide_widget', 'container', 'convert_distance_to_scroll', 'create_property', 'current_panel', 'current_uid', 'dec_disabled', 'disabled', 'dispatch', 'dispatch_children', 'dispatch_generic', 'do_scroll', 'do_scroll_x', 'do_scroll_y', 'effect_cls', 'effect_x', 'effect_y', 'events', 'export_as_image', 'export_to_png', 'fbind', 'funbind', 'g_translate', 'get_center_x', 'get_center_y', 'get_disabled', 'get_parent_window', 'get_property_observers', 'get_right', 'get_root_window', 'get_top', 'get_window_matrix', 'getter', 'hbar', 'height', 'id', 'ids', 'inc_disabled', 'is_event_type', 'on__viewport', 'on_current_uid', 'on_effect_cls', 'on_effect_x', 'on_effect_y', 'on_kv_post', 'on_opacity', 'on_scroll_move', 'on_scroll_start', 'on_scroll_stop', 'on_touch_down', 'on_touch_move', 'on_touch_up', 'opacity', 'panels', 'parent', 'pos', 'pos_hint', 'properties', 'property', 'proxy_ref', 'register_event_type', 'remove_widget', 'right', 'scroll_distance', 'scroll_timeout', 'scroll_to', 'scroll_type', 'scroll_wheel_distance', 'scroll_x', 'scroll_y', 'set_center_x', 'set_center_y', 'set_disabled', 'set_right', 'set_top', 'setter', 'simulate_touch_down', 'size', 'size_hint', 'size_hint_max', 'size_hint_max_x', 'size_hint_max_y', 'size_hint_min', 'size_hint_min_x', 'size_hint_min_y', 'size_hint_x', 'size_hint_y', 'smooth_scroll_end', 'to_local', 'to_parent', 'to_widget', 'to_window', 'top', 'uid', 'unbind', 'unbind_uid', 'unregister_event_types', 'update_from_scroll', 'vbar', 'viewport_size', 'walk', 'walk_reverse', 'width', 'x', 'y']|

With that, I printed a bunch of info about the current_title object that's referenced

                        print( "s.interface.current_panel:|" +str(s.interface.cu
rrent_panel)+ "|" )
                        print( "s.interface.current_panel.children:|" +str(s.int
erface.current_panel.children)+ "|" )
                        print( "s.interface.current_panel.walk():|" +str([widget
 for widget in s.interface.current_panel.walk()])+ "|" )
                        print( "dir(s.interface.current_panel):|" +str(dir(s.int
erface.current_panel))+ "|\n" )

Output

s.interface.current_panel:|<kivy.uix.settings.SettingsPanel object at 0x7778e4821750>|
s.interface.current_panel.children:|[<kivy.uix.settings.SettingOptions object at 0x7778e4834b40>, <kivy.uix.label.Label object at 0x7778e4826440>]|
s.interface.current_panel.walk():|[<kivy.uix.settings.SettingsPanel object at 0x7778e4821750>, <kivy.uix.label.Label object at 0x7778e4826440>, <kivy.uix.settings.SettingOptions object at 0x7778e4834b40>, <kivy.uix.boxlayout.BoxLayout object at 0x7778e483c980>, <kivy.uix.label.Label object at 0x7778e48449f0>, <kivy.uix.boxlayout.BoxLayout object at 0x7778e484f910>, <kivy.uix.label.Label object at 0x7778e47d5b40>]|
dir(s.interface.current_panel):|['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__events__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__metaclass__', '__module__', '__ne__', '__new__', '__proxy_getter', '__proxy_setter', '__pyx_vtable__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '_apply_transform', '_cols', '_context', '_disabled_count', '_disabled_value', '_fill_rows_cols_sizes', '_finalize_rows_cols_sizes', '_init_rows_cols_sizes', '_iterate_layout', '_kwargs_applied_init', '_proxy_ref', '_rows', '_trigger_layout', '_update_minimum_size', '_walk', '_walk_reverse', 'add_widget', 'apply_class_lang_rules', 'apply_property', 'bind', 'canvas', 'center', 'center_x', 'center_y', 'children', 'clear_widgets', 'cls', 'col_default_width', 'col_force_default', 'collide_point', 'collide_widget', 'cols', 'cols_minimum', 'config', 'create_property', 'dec_disabled', 'disabled', 'dispatch', 'dispatch_children', 'dispatch_generic', 'do_layout', 'events', 'export_as_image', 'export_to_png', 'fbind', 'funbind', 'get_center_x', 'get_center_y', 'get_disabled', 'get_max_widgets', 'get_parent_window', 'get_property_observers', 'get_right', 'get_root_window', 'get_top', 'get_value', 'get_window_matrix', 'getter', 'height', 'id', 'ids', 'inc_disabled', 'is_event_type', 'layout_hint_with_bounds', 'minimum_height', 'minimum_size', 'minimum_width', 'on_children', 'on_config', 'on_kv_post', 'on_opacity', 'on_touch_down', 'on_touch_move', 'on_touch_up', 'opacity', 'padding', 'parent', 'pos', 'pos_hint', 'properties', 'property', 'proxy_ref', 'register_event_type', 'remove_widget', 'right', 'row_default_height', 'row_force_default', 'rows', 'rows_minimum', 'set_center_x', 'set_center_y', 'set_disabled', 'set_right', 'set_top', 'set_value', 'setter', 'settings', 'size', 'size_hint', 'size_hint_max', 'size_hint_max_x', 'size_hint_max_y', 'size_hint_min', 'size_hint_min_x', 'size_hint_min_y', 'size_hint_x', 'size_hint_y', 'spacing', 'title', 'to_local', 'to_parent', 'to_widget', 'to_window', 'top', 'uid', 'unbind', 'unbind_uid', 'unregister_event_types', 'walk', 'walk_reverse', 'width', 'x', 'y']|

I can see here that there's a title attribute for the panel.

https://github.com/kivy/kivy/blob/2efdc2646147e5604767b8a5c6a43e5808455bf2/kivy/uix/settings.py#L680-L688

@maltfield
Copy link
Member

maltfield commented Nov 16, 2022

If I try to set it to the empty string, it works -- but there is an empty label there taking up space. If I try to set it to None

                        s.interface.current_panel.title = None

..then I get an error

   File "/home/user/sandbox/buskill-app/src/buskill_gui.py", line 613, in on_pre_enter
     s.interface.current_panel.title = None
   File "kivy/properties.pyx", line 497, in kivy.properties.Property.__set__
   File "kivy/properties.pyx", line 541, in kivy.properties.Property.set
   File "kivy/properties.pyx", line 532, in kivy.properties.Property.set
   File "kivy/properties.pyx", line 695, in kivy.properties.StringProperty.check
   File "kivy/properties.pyx", line 570, in kivy.properties.Property.check
 ValueError: None is not allowed for SettingsPanel.title

However, I found I could do this more sanely as follows

                        for child in s.interface.current_panel.children:
                                if type(child) == Label:
                                        s.interface.current_panel.remove_widget(
child)

maltfield added a commit that referenced this issue Nov 16, 2022
…the `InterfaceWithNoMenu`'s `current_panel` instance field and looping through that panel's children to remove the Label.

See also:

 * #16 (comment)
@maltfield
Copy link
Member

Finally, I found the relevant .kv files!

user@buskill:~/sandbox/buskill-app$ grep -irl 'SettingsPanel' /tmp/kivy_appdir/
/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/uix/settings.py
/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/data/style.kv
user@buskill:~/sandbox/buskill-app$

This excerpt from the kivy/data/style.kv file explains how the title is actually added to the GUI

<SettingsPanel>:
    spacing: 5
    padding: 5
    size_hint_y: None
    height: self.minimum_height

    Label:
        size_hint_y: None
        text: root.title
        text_size: self.width - 32, None
        height: max(50, self.texture_size[1] + 20)
        color: (.5, .5, .5, 1)
        font_size: '15sp'

        canvas.after:
            Color:
                rgb: .2, .2, .2
            Rectangle:
                pos: self.x, self.y - 2
                size: self.width, 1

@maltfield
Copy link
Member

maltfield commented Nov 16, 2022

SettingOptions is defined, but it leaves much to wonder how it works

<SettingOptions>:
    Label:
        text: root.value or ''
        pos: root.pos
        font_size: '15sp'

...but when you check the code, you see that SettingOptions is a child of SettingItem

https://github.com/kivy/kivy/blob/2efdc2646147e5604767b8a5c6a43e5808455bf2/kivy/uix/settings.py#L612

<SettingItem>:
    size_hint: .25, None
    height: labellayout.texture_size[1] + dp(10)
    content: content
    canvas:
        Color:
            rgba: 47 / 255., 167 / 255., 212 / 255., self.selected_alpha
        Rectangle:
            pos: self.x, self.y + 1
            size: self.size
        Color:
            rgb: .2, .2, .2
        Rectangle:
            pos: self.x, self.y - 2
            size: self.width, 1

    BoxLayout:
        pos: root.pos

        Label:
            size_hint_x: .66
            id: labellayout
            markup: True
            text: u'{0}\n[size=13sp][color=999999]{1}[/color][/size]'.format(root.title or '', root.desc or '')
            font_size: '15sp'
            text_size: self.width - 32, None

        BoxLayout:
            id: content
            size_hint_x: .33

So if I want to define an icon for each item in this list, it looks like I'll at least need to override the SettingItem class and the kv code above

@maltfield
Copy link
Member

I finally figured out why overriding a kivy widget's kv-language only added-to (instead of replaced) the existing widget's layout.

As the above documentation describes, you need to change

<SomeWidget>

with this (you add a dash before it to clear it)

<-SomeWidget>

maltfield added a commit that referenced this issue Nov 16, 2022
This commit finally solved the puzzling issue where I kept trying to change the layout for a given widget that I'm overriding (in this case the SettingsPanel with BusKillSettingsPanel), but it somehow just added the layout on-top of the existing layout. For example, I couldn't get rid of the label at the top of SettingsPanel. Even if it was absent in BusKillSettingsPanel and I used only BusKillSettingsPanel.

Solution: If you want to completely override a given classes' layout in kv-language, you must prepend a dash (-) before the definition.

For example, don't use this:

  <BusKillSettingsPanel>:
      spacing: 5
      padding: 5
      size_hint_y: None
      height: self.minimum_height

Use this:

  <-BusKillSettingsPanel>:
      spacing: 5
      padding: 5
      size_hint_y: None
      height: self.minimum_height

See also:

 * https://stackoverflow.com/questions/74467933/how-to-override-built-in-kivy-widgets-and-their-kv-language-layouts
 * https://kivy.org/doc/stable/api-kivy.lang.html#redefining-a-widget-s-style
 * #16 (comment)
maltfield added a commit that referenced this issue Nov 17, 2022
This commit finally managed to get the 'icon' and 'key/value' widgets in one row of our ComplexOption setting (the "Trigger") on the Settings Screen's SettingsPanel to appear side-by-side such that each label took up only the width it needed, and that the text wrapped on the key/value widget inside a StackLayout.

See also:

 * https://stackoverflow.com/questions/74482717/why-is-declaring-size-x-and-size-y-different-from-delcaring-both-in-size-i
 * https://stackoverflow.com/questions/74480862/side-by-side-labels-in-stacklayout-why-is-second-label-missing-kivy-python/74482434#74482434
 * https://stackoverflow.com/questions/74480089/equivalent-of-browser-debuggers-box-model-inspector-in-kivy
 * #16
@maltfield
Copy link
Member

maltfield commented Nov 18, 2022

Yesterday I finished replacing the popup when choosing the value for a given key that's an option (equivalent of a radio button in html).

Instead of doing this in a very restrictive and poor-UX modal popup, we dynamically create a new screen where the user can have more information about each option in a scrollable view without it being cluttered in the modal. This includes:

  1. Each row will have a radio button (selected or unselected), a label that has a title for each option and a short description of what that option does, and an optional "gear" icon to further configure that option
  2. A "help" ActionItem at the top that, when clicked, presents a modal with a very detailed explanation of what this option does

The layout of rows of options will be similar to the layout of rows of settings on the previous screen, which is a tree as follows

Screen Manager:
 \_ Screen 'settings'
  \_ BoxLayout (mine; it contains the ActionBar)
   \_ BoxLayout (mine;  it's the main wrapper for the settings_content; could maybe be deleted)
    \_ BoxLayout (SettingsWithNoMenu)
     \_ ScrollView (InterfaceWithNoMenu/ContentPanel)
      \_ GridLayout (this is part of ContentPanel)
       \_ GridLayout (SettingsPanel)
        \_ FloatLayout (BusKillSettingComplexOptions/SettingOptions/SettingItem)
         \_ Label (mine; icon_label)
         \_ Label (layoutlabel)

maltfield added a commit that referenced this issue Nov 18, 2022
added a scrollview and grid layout to the BusKillSettingsSomplexOptionsScreen

added a hard-coded example of an option for a given setting (this is like a radio button for a set of values that can be chosen for a given setting)

TODO: actually populate the options dynamically, not hard-coded

 * #16 (comment)
@maltfield maltfield pinned this issue Nov 25, 2022
@maltfield
Copy link
Member

maltfield commented Jan 16, 2023

Yesterday I got the changes made in the GUI's Settings Window to actually write the changes to the now-combined buskill/kivy .ini file

TODO: Add the ActionItem at the top for help for the given ComplexOption's Screen

@maltfield
Copy link
Member

Today I finished with the UI for displaying the help message when a "help" button icon is pressed in the BusKillSettingsComplexOptionsScreen's ActionBar

TODO: implement the "reset" button to restore the defaults for all settings (after confirmation) on the main Settings screen

@maltfield
Copy link
Member

maltfield commented Jan 17, 2023

Today I finished with the logic to actually update the runtime BusKill object's trigger after the user changed it when leaving the Settings Screen, and to prompt them to disarm & arm again if they changed the trigger from what it is currently armed with.

Also I updated the "BusKill is currently armed" text on the MainWindow Screen to include which trigger it's armed-with. This should avoid ambiguity to the user.

Finally, I noticed that the nav drawer is open when exiting the Settings Screen.

TODO: fix nav-drawer-open bug
TODO: implement logic for the "reset" button to restore all buskill settings to their defaults

@maltfield
Copy link
Member

Note to self: if you see this error

 Traceback (most recent call last):
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/configparser.py", line 788, in get
     value = d[option]
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/collections/__init__.py", line 916, in __getitem__
     return self.__missing__(key)            # support subclasses that define __missing__
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/collections/__init__.py", line 908, in __missing__
     raise KeyError(key)
 KeyError: 'drive'
 
 During handling of the above exception, another exception occurred:
 
 Traceback (most recent call last):
   File "src/main.py", line 146, in <module>
     BusKillApp( bk ).run()
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/app.py", line 855, in run
     runTouchApp()
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/base.py", line 504, in runTouchApp
     EventLoop.window.mainloop()
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/core/window/window_sdl2.py", line 747, in mainloop
     self._mainloop()
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/core/window/window_sdl2.py", line 479, in _mainloop
     EventLoop.idle()
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/base.py", line 342, in idle
     self.dispatch_input()
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/base.py", line 327, in dispatch_input
     post_dispatch_input(*pop(0))
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/base.py", line 293, in post_dispatch_input
     wid.dispatch('on_touch_up', me)
   File "kivy/_event.pyx", line 707, in kivy._event.EventDispatcher.dispatch
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/uix/behaviors/button.py", line 179, in on_touch_up
     self.dispatch('on_release')
   File "kivy/_event.pyx", line 703, in kivy._event.EventDispatcher.dispatch
   File "kivy/_event.pyx", line 1214, in kivy._event.EventObservers.dispatch
   File "kivy/_event.pyx", line 1098, in kivy._event.EventObservers._dispatch
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/lang/builder.py", line 64, in custom_callback
     exec(__kvlang__.co_value, idmap)
   File "/home/user/sandbox/buskill-app/src/buskill.kv", line 108, in <module>
     root.manager.current = 'settings'
   File "kivy/properties.pyx", line 497, in kivy.properties.Property.__set__
   File "kivy/properties.pyx", line 544, in kivy.properties.Property.set
   File "kivy/properties.pyx", line 599, in kivy.properties.Property.dispatch
   File "kivy/_event.pyx", line 1214, in kivy._event.EventObservers.dispatch
   File "kivy/_event.pyx", line 1120, in kivy._event.EventObservers._dispatch
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/uix/screenmanager.py", line 1049, in on_current
     self.transition.start(self)
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/uix/screenmanager.py", line 377, in start
     self.screen_in.dispatch('on_pre_enter')
   File "kivy/_event.pyx", line 707, in kivy._event.EventDispatcher.dispatch
   File "/home/user/sandbox/buskill-app/src/buskill_gui.py", line 1032, in on_pre_enter
     s.add_json_panel( 'buskill', Config, os.path.join(self.bk.SRC_DIR, 'packages', 'buskill', 'settings_buskill.json') )
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/uix/settings.py", line 975, in add_json_panel
     panel = self.create_json_panel(title, config, filename, data)
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/uix/settings.py", line 1015, in create_json_panel
     instance = cls(panel=panel, **str_settings)
   File "/home/user/sandbox/buskill-app/src/buskill_gui.py", line 751, in __init__
     super(BusKillSettingComplexOptions, self).__init__(**kwargs)
   File "/home/user/sandbox/buskill-app/src/buskill_gui.py", line 680, in __init__
     super(BusKillSettingItem, self).__init__(**kwargs)
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/uix/settings.py", line 289, in __init__
     self.value = self.panel.get_value(self.section, self.key)
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/uix/settings.py", line 654, in get_value
     return config.get(section, key)
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/site-packages/kivy/config.py", line 501, in get
     value = PythonConfigParser.get(self, section, option, **kwargs)
   File "/tmp/kivy_appdir/opt/python3.7/lib/python3.7/configparser.py", line 791, in get
     raise NoOptionError(option, section)
 configparser.NoOptionError: No option 'drive' in section: 'buskill'
 [INFO/MainProcess] process shutting down
 [DEBUG/MainProcess] running all "atexit" finalizers with priority >= 0
 [DEBUG/MainProcess] running the remaining "atexit" finalizers

..then it means you need to add a key/value pair to the defaults defined by Config.setdefaults() in BusKillApp()'s build_config() in buskill_gui.py

        def build_config(self, config):

                Config.read( self.bk.CONF_FILE )
                Config.setdefaults('buskill', {
                 'trigger': 'lock-screen',
                })      
                Config.set('kivy', 'exit_on_escape', '0')
                Config.set('input', 'mouse', 'mouse,multitouch_on_demand')
                Config.write()

@maltfield
Copy link
Member

I fixed a logic bug where I was attempting to re-arm whenever leaving the Settings screen -- but it really should only happen when leaving the Settings Screen and going back to the Main Screen. It should not attempt to re-arm when leaving the Settings Screen and going into a ComplexOptions Screen.

TODO: fix a lot of bugs where the value is correct in the config file after reset, but the value is not correct on the Settings Screen or the wrong option is selected in the ComplexOption Screen

@maltfield
Copy link
Member

I'm a bit confused as to why my BusKillOptionItem() instances have the values set to title and the value is hardcoded to replaceme. Currently the only way to get the title is by going up the tree to the screen and getting its name.

For example for the "trigger" option, I'd think that the title should be trigger and the value either lock-screen or soft-shutdown. But the title is currently lock-screen or soft-shutdown and the value is replaceme.

I'd like to change this, but it may break things and take a while to fix :(

maltfield added a commit that referenced this issue Jun 9, 2023
…usKillOptionItem in the next commit. Wish me luck.

 * #16 (comment)
maltfield added a commit that referenced this issue Jun 9, 2023
…"value" instead of "title" on the BusKillOptionItem objects

 * #16
@maltfield
Copy link
Member

maltfield commented Jun 9, 2023

I fixed the title / value thing above, and I also finished implementing the "Reset to Defaults" button functionality. I'm now focusing on two bugs:

  1. The nav bar drawer is open if you leave the Settings screen. This only happens if you went to a ComplexOption sub-screen first
  2. I'm getting these weird warning messages about having multiple copies of the ComplexOption sub-screens
[WARNING] [Multiple screens named "setting_trigger"] [<Screen name='setting_trigger'>, <Screen name='setting_trigger'>, <Screen name='setting_trigger'>]

The above message does not show-up the first time I click on the "trigger" option on the Settings Screen. It does show-up if I go back and then click on "trigger" again. The thrid time I click on it, I get this

[WARNING] [Multiple screens named "setting_trigger"] [<Screen name='setting_trigger'>, <Screen name='setting_trigger'>, <Screen name='setting_trigger'>, <Screen name='setting_trigger'>, <Screen name='setting_trigger'>]

And the fourth

[WARNING] [Multiple screens named "setting_trigger"] [<Screen name='setting_trigger'>, <Screen name='setting_trigger'>, <Screen name='setting_trigger'>, <Screen name='setting_trigger'>, <Screen name='setting_trigger'>, <Screen name='setting_trigger'>, <Screen name='setting_trigger'>]

The first message lists 4x setting_trigger screens. The second lists 6x. The third is 8x. So it appears that I'm creating two screens every time?

@maltfield
Copy link
Member

ok, I fixed the infinite screen creation

@maltfield
Copy link
Member

maltfield commented Jun 10, 2023

I also fixed the navbar drawer staying open.

As I cleanup the code, I went ahead and added some test options for all the other kivy types

While these new options generally "just work" -- the reset button does not work for them.

TODO: see if clearing the widgets from the Settings screen and re-running the init() (as we were doing before) now works for those. I think maybe the "multiple screens" bug may have caused that to not work? Otherwise I'll just have to iterate into each of those settings and update them to match the config file, not too hard.

@maltfield
Copy link
Member

I fixed the reset button to reset the built-in kivy types too.

For future reference (when we will add more options that my be of the built-in kivy types), here's what that looks like:

{
"type": "bool",
"title": "On or Off?",
"desc": "Test of boolean types",
"section": "buskill",
"key": "flag"
},
{
"type": "numeric",
"title": "How many?",
"desc": "Test of numeric type",
"section": "buskill",
"key": "count"
},
{
"type": "options",
"title": "Which one?",
"desc": "Test of built-in kivy 'options' type",
"section": "buskill",
"key": "which",
"options": ["short","medium","tall"]
},
{
"type": "string",
"title": "What's your name?",
"desc": "Test of string type",
"section": "buskill",
"key": "name"
},
{
"type": "path",
"title": "Where is it?",
"desc": "Test of path type",
"section": "buskill",
"key": "file"
}

buskill-app/src/buskill_gui.py

Lines 1163 to 1172 in fbfa7fa

Config.setdefaults('buskill', {
'trigger': 'lock-screen',
# TODO: remove these (just for testing future options & types)
'drive': 'all',
'flag': True,
'count': 42,
'which': "medium",
'name': "John Smith",
'file': "/tmp/some_file"
})

@maltfield
Copy link
Member

Today I did a lot more cleanup of the buskill_gui.py file and the buskill.kv file. I commented-out all the canvas bits for awkward colors that I used to see the edge of labels, and I removed all the other test SettingItems and the 'hard-shutdown' OptionItem (as we haven't implemented that trigger yet -- I was just testing the the GUI for the future).

At this point, I think this feature might be complete? I think I should just test the build on all three platforms. If all is well, then maybe I can merge it into dev!

@maltfield
Copy link
Member

As far as I can tell, this feature is done. See testing in #14 for more info.

I'm merging this into dev 🎉

@maltfield maltfield unpinned this issue Jun 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants