Search code examples
pythonkivykivy-language

How to update two keys in a dictionary with info from one Popup window in Kivy?


I'm trying to update dictionary collected_info key 'Liners' keys '4x20' and '8x20' with info from a ColorsPopup popup, but I'm having issues doing it correctly.

The idea is, that when either of the two buttons is pressed, a popup appears, that contains multiple coloured buttons inside. After you toggle buttons inside the popup, it should add those colours as a list to the corresponding key (either '4x20' or '8x20').

The problem is, I cannot implement all the required functionality.

My current issue is that I can't separate list of liners for each of the collected_info keys - 4x20 and 8x10.

Here's MRE python code:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, BooleanProperty, ListProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.togglebutton import ToggleButton


class ProtocolInfoPage(Screen):
    big = ObjectProperty(None)
    small = ObjectProperty(None)
    collected_info = {'Liners': {'4x20': list(), '8x10': list()}}

    def open_popup(self, liner):
        popup = ColorsPopup()
        if liner == '4x20' and self.big.state == 'down':
            popup.popupWindow.open()
            self.collected_info['Liners']['4x20'] = popup.liners
        elif liner == '4x20' and self.big.state == 'normal':
            self.collected_info['Liners']['4x20'] = []
        if liner == '8x10' and self.small.state == 'down':
            popup.popupWindow.open()
            self.collected_info['Liners']['8x10'] = popup.liners
        elif liner == '8x10' and self.small.state == 'normal':
            self.collected_info['Liners']['8x10'] = []


class ColorsPopup(Screen):
    liners = list()
    colors = ['GB', 'BL', 'GR', 'RT', 'SW', 'BR', 'TR']

    def __init__(self, **kwargs):
        super(ColorsPopup, self).__init__(**kwargs)
        main_layout = BoxLayout(orientation='vertical')
        layout = GridLayout(cols=3, size_hint=(.7, .7), pos_hint={'center_x': .5})
        self.popupWindow = Popup(title='Flatliner Colors', content=main_layout, size_hint=(1, .5), auto_dismiss=False)
        close_btn = Button(text='Choose Colors', size_hint=(.7, .3), pos_hint={'center_x': .5})
        close_btn.bind(on_press=self.popupWindow.dismiss)
        for color in self.colors:
            color_btn = ToggleButton(text=color)
            color_btn.bind(state=self.adding_removing_colors)
            if color == 'GB':
                color_btn.background_color = (1, 1, 0, 1)
            elif color == 'BL':
                color_btn.background_color = (0, 0, 1, 1)
            elif color == 'GR':
                color_btn.background_color = (0, 1, 0, 1)
            elif color == 'RT':
                color_btn.background_color = (1, 0, 0, 1)
            elif color == 'SW':
                color_btn.background_color = (0, 0, 0, 1)
            elif color == 'BR':
                color_btn.background_color = (.5, .5, .3, 1)
            layout.add_widget(color_btn)
        main_layout.add_widget(layout)
        main_layout.add_widget(close_btn)

    def adding_removing_colors(self, color, state):
        if state == 'down':
            self.liners.append(color.text)
        elif state == 'normal':
            self.liners.remove(color.text)
        print(self.liners)


kv = Builder.load_file("kivymd.kv")


class MyApp(App):
    def build(self):
        return kv


if __name__ == '__main__':
    MyApp().run()

print(ProtocolInfoPage.collected_info)

And here's kv:

ProtocolInfoPage:
    name: 'second'
    big: big_liners
    small: small_liners

    BoxLayout:
        orientation: 'vertical'
        GridLayout:
            cols:2
            Label:
                text: 'Liners'
            GridLayout:
                cols:2
                ToggleButton:
                    id: big_liners
                    text: '4x20'
                    on_release:
                        root.open_popup(self.text)
                ToggleButton:
                    id: small_liners
                    text: '8x10'
                    on_release:
                        root.open_popup(self.text)

I have reworked how ColorsPopup work, and I feel like this implementation is better than just writing it in kv file, but I still cant figure out a way to do it correctly.


Solution

  • The problem with your logic is you are modifying collected_info with the value of popup.liners during instantiation of popup which is empty by default. Hence you never can access it when it changes inside the popup. This can be solved in various ways.

    One simple way is to observe the changes of popup.liners and update your collected_info accordingly. Now that's exactly what bind does. For that, create a suitable (kivy) property and bind a callback method/function whenever or wherever you need.

    The following is the implementation of this concept. I have added an extra Label to reflect the changes in real time. Rest have been clarified in comments.

    Modified .kv,

    ProtocolInfoPage:
        name: 'second'
    #    big: big_liners
    #    small: small_liners
    
        BoxLayout:
            orientation: 'vertical'
            Label: # To visualize the change. Optional.
                id: info
                size_hint_y: 0.25
            GridLayout:
                cols:2
                Label:
                    text: 'Liners'
                GridLayout:
                    cols:2
                    ToggleButton:
    #                    id: big_liners
                        text: '4x20'
                        on_state: root.select_option(self) # Pass the instance.
    #                    on_release:
    #                        root.open_popup(self.text)
                    ToggleButton:
    #                    id: small_liners
                        text: '8x10'
                        on_state: root.select_option(self)
    #                    on_release:
    #                        root.open_popup(self.text)
    

    Modified .py,

    class ProtocolInfoPage(Screen):
        big = ObjectProperty(None)
        small = ObjectProperty(None)
        collected_info = {'Liners': {'4x20': list(), '8x10': list()}}
    
        def select_option(self, tbtn):
            # Create an instance and save it to self.
            self.popup = ColorsPopup()
            # Now bind a callback function to it in order to listen to any change in its prop. 'liners'.
            # Pass the toggle button also for usage purpose.
            self.popup.bind(liners = lambda *args : self.update_collected_info(tbtn, *args))
            # Using partial.
    #       self.popup.bind(liners = partial(self.update_collected_info, tbtn))
            # Set logic.
            if tbtn.state == "normal":
                self.collected_info['Liners'][tbtn.text] = []
            else: # i.e. when tbtn.state is "down", open the popup.
                self.popup.popupWindow.open()
            # Update the info. Optional.
            self.ids.info.text = str(self.collected_info)
    
    
        def update_collected_info(self, tbtn, instance, value):
            """This method will be triggered whenever the prop. 'liners' of ColorsPopup changes."""
            self.collected_info['Liners'][tbtn.text] = value
            # Update the info. Optional.
            self.ids.info.text = str(self.collected_info)
    
    
    class ColorsPopup(Screen):
        liners = ListProperty([ ]) # Make it a kivy property in order to listen to its changes automatically.
        .
        .
        .