0

I am attempting to create a standard numeric keypad that will pop up when the user touches text-inputs on the screen, so that the user can enter number values without using a mouse and keyboard. I was following this question that allows for input into one text box, but when trying to use this to enter values into multiple text inputs I could not get the program to function correctly. I am limited to just using Python, not the Kivy language so I can appreciate that it is a bit more awkward to code.

My plan was to create a class for the Bubble (inputBubble), the Bubble Buttons (inputBubbleButtons) and the text input boxes (text_inputs), with the text_inputs widget calling a function from RHS() (one of my main layouts) that should display the bubble. I can't seem a way to replicate the app.root.text_input.text += self.text from the test.kv file, so my current error is "text_inputs object has no attribute 'bubblein'" and I can't think of a way to move past this.

import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.bubble import Bubble, BubbleButton

class inputBubble(Bubble):
        def __init__(self, **kwargs):
                super(inputBubble, self).__init__(**kwargs)
                inputGrid = GridLayout(cols = 3)
                keypad_numbers = ['7', '8', '9', '4', '5', '6', '1', '2', '3', 'CLR', '0', '.']
                for x in keypad_numbers:
                        inputGrid.add_widget = inputBubbleButtons(text = x)
                self.add_widget(inputGrid)

class inputBubbleButtons(BubbleButton):
        def __init__(self, **kwargs):
                super(inputBubbleButtons, self).__init__(**kwargs)
                self.on_release = self.buttonfunctions

        def buttonfunctions(self):
                if self.text != 'CLR':
                        text_input.text += self.text
                else:
                        text_input.text = '0'

class text_inputs(TextInput):
        def __init__(self, **kwargs):
                super(text_inputs, self).__init__(**kwargs)
                self.id = 'text_input'
                self.cursor_blink = False
                self.multiline = False
                self.on_focus = RHS.show_input(self)

class RHS(BoxLayout):
        def __init__(self, **kwargs):
                super(RHS, self).__init__(**kwargs)
                nangleRow = BoxLayout(orientation = 'horizontal')
                self.add_widget(nangleRow)
                nangleRow.add_widget(Label(text = 'New Angle'))
                nangleInput = text_inputs()
                nangleRow.add_widget(nangleInput)

        def show_input(self, *l):
                if not hasattr(self, 'bubblein'):
                        bubblein = inputBubble()
                        self.bubblein.arrow_pos = "bottom_mid"
                        self.add_widget(bubblein)

class Root(GridLayout):
        def __init__(self, **kwargs):
                super(Root, self).__init__(**kwargs)
                self.cols = 1
                self.add_widget(RHS(),0)

class MainWindow(App):
        def build(self):
                return Root()

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

I expect this to create a bubble when the focus is on nangleInput, but instead I get the error message "AttributeError: 'text_inputs' object has no attribute 'bubblein'".

Seabright22
  • 23
  • 1
  • 6

1 Answers1

1

Here is a version of your code that I think does what you want:

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.bubble import Bubble, BubbleButton

class inputBubble(Bubble):
        def __init__(self, **kwargs):
                super(inputBubble, self).__init__(**kwargs)
                inputGrid = GridLayout(cols = 3)
                keypad_numbers = ['7', '8', '9', '4', '5', '6', '1', '2', '3', 'CLR', '0', '.']
                for x in keypad_numbers:
                        inputGrid.add_widget(inputBubbleButtons(text = x))    # use add_widget to add each Button to the inputGrid
                self.add_widget(inputGrid)

class inputBubbleButtons(BubbleButton):
        def __init__(self, **kwargs):
                super(inputBubbleButtons, self).__init__(**kwargs)
                self.on_release = self.buttonfunctions

        def buttonfunctions(self):
                if self.text != 'CLR':
                        # complex path to the TextInput
                        App.get_running_app().root.RHS.nangleInput.text += self.text
                else:
                        App.get_running_app().root.RHS.nangleInput.text = '0'

class text_inputs(TextInput):
        def __init__(self, **kwargs):
                super(text_inputs, self).__init__(**kwargs)
                self.id = 'text_input'
                self.cursor_blink = False
                self.multiline = False

class RHS(BoxLayout):
        def __init__(self, **kwargs):
                super(RHS, self).__init__(**kwargs)
                nangleRow = BoxLayout(orientation = 'horizontal')
                self.add_widget(nangleRow)
                nangleRow.add_widget(Label(text = 'New Angle'))
                self.nangleInput = text_inputs()    # save a reference to text_inputs
                self.nangleInput.bind(focus=self.show_input)    # use bind to get method called when focus changes
                nangleRow.add_widget(self.nangleInput)

        def show_input(self, *l):
                if not hasattr(self, 'bubblein'):
                        self.bubblein = inputBubble()    # create attribute that is tested for in above line
                        self.bubblein.arrow_pos = "bottom_mid"
                        self.add_widget(self.bubblein)

class Root(GridLayout):
        def __init__(self, **kwargs):
                super(Root, self).__init__(**kwargs)
                self.cols = 1
                self.RHS = RHS()    # save  reference to RHS
                self.add_widget(self.RHS,0)

class MainWindow(App):
        def build(self):
                return Root()

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

The problems included:

  1. inputGrid.add_widget = inputBubbleButtons(text = x) should be inputGrid.add_widget(inputBubbleButtons(text = x)).
  2. In your buttonfunctions(), you reference text_input, but that has not been defined. I replaced that with a rather complex path to the actual text_inputs.
  3. self.on_focus = RHS.show_input(self) runs the show_input() method and assigns the return value (which is None) to self.on_focus. I have removed that line and put self.nangleInput.bind(focus=self.show_input) in the RHS class, which I think accomplishes your intent.
  4. In your show_inputs() method you are checking for the existence of an attribute named bubblein, but your code does not create one. I have changed the first line in that if block to self.bubblein = inputBubble(), which creates that attribute. Other changes also to access the new attribute.
  5. In the Root class, I saved a reference to the created RHS instance for use elsewhere.

If you intend to use several TextInput instances, you can adjust the target of the keypad to send the text to the different TextInputs. Here is another version of your code that does that:

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.bubble import Bubble, BubbleButton

class inputBubble(Bubble):
        def __init__(self, text_input, **kwargs):
                super(inputBubble, self).__init__(**kwargs)
                self.inputGrid = GridLayout(cols = 3)    # save a reference to the grid of inputBubbleButtons
                keypad_numbers = ['7', '8', '9', '4', '5', '6', '1', '2', '3', 'CLR', '0', '.']
                for x in keypad_numbers:
                        self.inputGrid.add_widget(inputBubbleButtons(text_input, text = x))    # use add_widget to add each Button to the inputGrid
                self.add_widget(self.inputGrid)

        # this method changes the target TextInput of the keypad
        def set_text_input(self, text_input):
                for butt in self.inputGrid.children:
                        butt.text_input = text_input

class inputBubbleButtons(BubbleButton):
        def __init__(self, text_input, **kwargs):
                self.text_input = text_input    # the target TextInput
                super(inputBubbleButtons, self).__init__(**kwargs)
                self.on_release = self.buttonfunctions

        def buttonfunctions(self):
                if self.text != 'CLR':
                        self.text_input.text += self.text
                else:
                        self.text_input.text = '0'

class text_inputs(TextInput):
        def __init__(self, **kwargs):
                super(text_inputs, self).__init__(**kwargs)
                self.id = 'text_input'
                self.cursor_blink = False
                self.multiline = False

class RHS(BoxLayout):
        def __init__(self, **kwargs):
                super(RHS, self).__init__(**kwargs)
                self.orientation = 'vertical'
                self.bubblein = None
                for i in range(5):
                        nangleRow = BoxLayout(orientation = 'horizontal')
                        self.add_widget(nangleRow)
                        nangleRow.add_widget(Label(text = 'New Angle ' + str(i)))
                        self.nangleInput = text_inputs()    # save a reference to text_inputs
                        self.nangleInput.bind(focus=self.show_input)    # use bind to get method called when focus changes
                        nangleRow.add_widget(self.nangleInput)

        def show_input(self, text_input, has_focus):
                if has_focus:
                        if self.bubblein is not None:
                                # already have a keypad, just change the target TextInput to receive the key strokes
                                self.bubblein.set_text_input(text_input)
                        else:
                                self.bubblein = inputBubble(text_input)
                                self.bubblein.arrow_pos = "bottom_mid"
                                self.add_widget(self.bubblein)

class Root(GridLayout):
        def __init__(self, **kwargs):
                super(Root, self).__init__(**kwargs)
                self.cols = 1
                self.RHS = RHS()    # save  reference to RHS
                self.add_widget(self.RHS,0)

class MainWindow(App):
        def build(self):
                return Root()

if __name__ == '__main__':
        MainWindow().run()
John Anderson
  • 35,991
  • 4
  • 13
  • 36
  • Thank you! If I wanted to go to different text inputs than nangleInput as well as having nangleInput (so App.get_running_app().root.RHS.nangleInput.text would read App.get_running_app().root.RHS.ndistInput.text) could I do this dynamically based on the text input I have selected or would I have to make a bubble class for each of them? Thanks again! – Seabright22 Nov 06 '19 at 15:56
  • Sorry to ask another one, is there a way to draw this so that it can be placed floating above the text inputs, and so that the bubble closes when I press a "Done" button I have added to it? If this should be a separate question let me know – Seabright22 Nov 07 '19 at 12:51
  • Yes, that should be a separate question. – John Anderson Nov 07 '19 at 13:43
  • It's okay I have managed to work it out, thanks again for your help! – Seabright22 Nov 07 '19 at 14:00