Welcome, guest | Sign In | My Account | Store | Cart

PYQT TOUCH INPUT WIDGET

Python, 286 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
from PyQt4 import QtGui, QtCore
from decimal import Decimal

# applicationle widgets
SIP_WIDGETS = [QtGui.QLineEdit]

class MyFlatPushButton(QtGui.QPushButton):
    def __init__(self, caption, min_size=(50, 50)):
        self.MIN_SIZE = min_size
        QtGui.QPushButton.__init__(self, caption)
        self.setFocusPolicy(QtCore.Qt.NoFocus)

    def sizeHint(self):
        return QtCore.QSize(self.MIN_SIZE[0], self.MIN_SIZE[1])


class SoftInputWidget(QtGui.QDialog):
    def __init__(self, parent_object, keyboard_type='default'):
        QtGui.QDialog.__init__(self)
        self.setWindowFlags(QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint)
        self.INPUT_WIDGET = None
        self.PARENT_OBJECT = parent_object
        self.signalMapper = QtCore.QSignalMapper(self)

        self.NO_ORD_KEY_LIST = list()
        self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Left)
        self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Up)
        self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Right)
        self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Down)
        self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Backspace)
        self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Enter)
        self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Tab)
        self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Escape)

        self.do_layout(keyboard_type)

        self.signalMapper.mapped[int].connect(self.buttonClicked)

    def do_layout(self, keyboard_type='default'):
        """
        @param   keyboard_type:
        @return:
        """
        gl = QtGui.QVBoxLayout()
        self.setFont(self.PARENT_OBJECT.font())
        number_widget_list = []
        sym_list = range(0, 10)
        for sym in sym_list:
            button = MyFlatPushButton(str(sym))
            button.KEY_CHAR = ord(str(sym))
            number_widget_list.append(button)

        button = MyFlatPushButton('*')
        button.KEY_CHAR = ord('*')
        number_widget_list.append(button)

        # alphabets
        alpha_widget_list = []
        sym_list    = ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',
                       'new_row',
                       'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',
                       'new_row',
                       'z', 'x', 'c', 'v', 'b', 'n', 'm']
        for sym in sym_list:
            if sym == 'new_row':
                alpha_widget_list.append('new_row')
            else:
                button = MyFlatPushButton(sym)
                button.KEY_CHAR = ord(sym)
                alpha_widget_list.append(button)

        # back space
        control_widget_list = []
        button = MyFlatPushButton('<B')
        button.setToolTip('Backspace')
        button.KEY_CHAR = QtCore.Qt.Key_Backspace
        control_widget_list.append(button)
        control_widget_list.append('sep')

        # tab
        button = MyFlatPushButton('>T')
        button.KEY_CHAR = QtCore.Qt.Key_Tab
        control_widget_list.append(button)
        control_widget_list.append('sep')

        # space
        button = MyFlatPushButton('SPC')
        button.KEY_CHAR = QtCore.Qt.Key_Space
        control_widget_list.append(button)
        control_widget_list.append('sep')

        # close
        button = MyFlatPushButton('X')
        button.KEY_CHAR = QtCore.Qt.Key_Escape
        control_widget_list.append(button)
        control_widget_list.append('sep')

        # enter
        button = MyFlatPushButton('ENT', min_size=(110, 60))
        button.KEY_CHAR = QtCore.Qt.Key_Enter
        control_widget_list.append(button)
        control_widget_list.append('sep')

        alist = list()
        alist.append((QtCore.Qt.Key_Left,  'L-A'))
        alist.append((QtCore.Qt.Key_Right, 'R-A'))
        alist.append((QtCore.Qt.Key_Up,    'U-A'))
        alist.append((QtCore.Qt.Key_Down,  'D-A'))
        for key in alist:
            button = MyFlatPushButton(key[1])
            button.KEY_CHAR = key[0]
            control_widget_list.append(button)

        MAX_COL = 10
        col     = 0
        tlist   = list()
        if keyboard_type == 'numeric':
            widget_list = number_widget_list
        elif keyboard_type == 'alpha':
            widget_list = alpha_widget_list
        else:
            widget_list = list()
            widget_list.extend(number_widget_list)
            widget_list.append('new_row')
            widget_list.extend(alpha_widget_list)

        widget_list.append('new_row')
        widget_list.extend(control_widget_list)

        for widget in widget_list:
            if widget == 'new_row':
                col = MAX_COL
            elif widget == 'sep':
                tlist.append(self.get_vline())
                continue
            else:
                tlist.append(widget)
                widget.clicked.connect(self.signalMapper.map)
                self.signalMapper.setMapping(widget, widget.KEY_CHAR)

            if col == MAX_COL:
                col = 0
                v = QtGui.QHBoxLayout()
                v.addStretch()
                v.setSpacing(5)
                map(v.addWidget, tlist)
                v.addStretch()
                gl.addLayout(v)
                tlist = []
            else:
                col += 1

        v = QtGui.QHBoxLayout()
        v.setSpacing(5)
        v.addStretch()
        map(v.addWidget, tlist)
        v.addStretch()
        gl.addLayout(v)
        gl.setContentsMargins(0, 0, 0, 0)
        gl.setSpacing(5)
        gl.setSizeConstraint(gl.SetFixedSize)

        self.setLayout(gl)

    def reject(self):
        self.buttonClicked(QtCore.Qt.Key_Escape)

    def buttonClicked(self, char_ord):
        w = self.INPUT_WIDGET
        if char_ord in self.NO_ORD_KEY_LIST:
            keyPress = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, char_ord, QtCore.Qt.NoModifier, '')
        else:
            keyPress = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, char_ord, QtCore.Qt.NoModifier, chr(char_ord))

        # send keypress event to widget
        QtGui.QApplication.sendEvent(w, keyPress)

        # line edit returnPressed event is triggering twise for press and release both
        # that is why do not send release event for special key
        if char_ord not in self.NO_ORD_KEY_LIST:
            keyRelease = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, char_ord, QtCore.Qt.NoModifier, '')
            QtGui.QApplication.sendEvent(w, keyRelease)

        # hide on enter or esc button click
        if char_ord in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Escape):
            self.hide()

    def show_input_panel(self, widget):
        self.INPUT_WIDGET = widget
        self.show()
        self.update_panel_position()

    def update_panel_position(self):
        widget = self.INPUT_WIDGET
        if not widget: return

        widget_rect         = widget.rect()
        widget_bottom       = widget.mapToGlobal(QtCore.QPoint(widget.frameGeometry().x(), widget.frameGeometry().y())).y()
        screen_height       = QtGui.qApp.desktop().availableGeometry().height()
        input_panel_height  = self.geometry().height() + 5

        if (screen_height - widget_bottom) > input_panel_height:
            # display input panel at bottom of widget
            panelPos = QtCore.QPoint(widget_rect.left(), widget_rect.bottom() + 2)
        else:
            # display input panel at top of widget
            panelPos = QtCore.QPoint(widget_rect.left(), widget_rect.top() - input_panel_height)

        panelPos = widget.mapToGlobal(panelPos)
        self.move(panelPos)

    def _get_line(self, vertical=True):
        line = QtGui.QFrame()
        line.setContentsMargins(0, 0, 0, 0)
        if vertical is True:
            line.setFrameShape(line.VLine)
        else:
            line.setFrameShape(line.HLine)
        line.setFrameShadow(line.Sunken)
        return line

    def get_hline(self):
        return self._get_line(vertical=False)

    def get_vline(self):
        return self._get_line()


class TouchInterface(QtCore.QObject):
    def __init__(self, PARENT_WIDGET):
        QtCore.QObject.__init__(self)
        self._PARENT_WIDGET        = PARENT_WIDGET
        self._input_panel_all      = SoftInputWidget(PARENT_WIDGET, 'default')
        self._input_panel_numeric  = SoftInputWidget(PARENT_WIDGET, 'numeric')

    def childEvent(self, event):
        if event.type() == QtCore.QEvent.ChildAdded:
            if isinstance(event.child(), *SIP_WIDGETS):
                event.child().installEventFilter(self)

    def eventFilter(self, widget, event):
        if self._PARENT_WIDGET.focusWidget() == widget and event.type() == QtCore.QEvent.MouseButtonPress:
            if hasattr(widget, 'keyboard_type'):
                if widget.keyboard_type == 'default':
                    self._input_panel_all.show_input_panel(widget)
                elif widget.keyboard_type == 'numeric':
                    self._input_panel_numeric.show_input_panel(widget)

        return False


class TouchInputWidget(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.touch_interface = TouchInterface(self)

    def childEvent(self, event):
        self.touch_interface.childEvent(event)

    def eventFilter(self, widget, event):
        return self.touch_interface.eventFilter(widget, event)


class ExampleWidget(TouchInputWidget):
    def __init__(self):
        TouchInputWidget.__init__(self)

        self.txtNumeric    = QtGui.QLineEdit()
        # actiate touch input
        self.txtNumeric.keyboard_type = 'numeric'

        self.txtText = QtGui.QLineEdit()
        # activate touch input
        self.txtText.keyboard_type = 'default'

        gl = QtGui.QVBoxLayout()
        gl.addWidget(self.txtNumeric)
        gl.addWidget(self.txtText)

        self.setWindowTitle('Touch Input Demo')
        self.setLayout(gl)

if __name__ == '__main__':
    app = QtGui.QApplication([])
    ExampleWidget().show()
    app.exec_()

10 comments

Jacky 8 years, 5 months ago  # | flag

I run this recipe, but it seems something wrong with it. It do not generate the soft keyboard and it can not input and output. As I am a beginner of Python. Can somebody kindly give some instruction about this recipe? regards

jshah (author) 8 years, 5 months ago  # | flag

Click on input box to activate the soft keyboard

A 7 years, 7 months ago  # | flag

Hello, mate.

First of all I want to say this keyboard works really well, awesome.

I want to try it into my app and I don't know how to bind my QLineEdit focus/click event (MainWindow and widgets and stuff were created with QtDesigner) to trigger out your keyboard focusing my QLineEdit, if you could explain how to do it/how it works that would be awesome cause I really want to learn about python/PyQt.

My "MainWindow" class looks like:

First, .ui import

form_class = uic.loadUiType("example.ui")[0]

My class

class MyWindow(QtGui.QMainWindow, form_class): def __init__ (self, parent=None): QtGui.QMainWindow.__init__(self, parent)

when I press a button, I just want your Keyboard to be the input,

instead of the 'physical' keyboard

Thanks for your work!!

A 7 years, 7 months ago  # | flag

Well, markdown syntax didn't worked as expected, hope you understand my code and thanks again for uploading yours.

jshah (author) 7 years, 7 months ago  # | flag

You Need to subclass the TouchInputWidget and add the qlineedit and set the attribute keyboard_type I dont know how you can do this in qtdesigner

A 7 years, 7 months ago  # | flag

I'll paste you my code: 'link' <- Here

I've pasted your code, deleted your "ExampleWidget" class and subclass "TouchInputWidget", I've done something wrong cause it doesn't seem to work.

My app is a very simple test, is just a converter.

My "main" class is MyWindowClass.

inputDialog is a QLineEdit, I've added keyboard_type.

I'm still pretty new to OOP, I really want to learn! I would be realy grateful if you help me a bit (:

Thanks again!

jshah (author) 7 years, 7 months ago  # | flag

I am not familiar with qt designer I suggest you write your UI in plain python only and qt framework is very nice you dont need any designer to create a UI.

A 7 years, 7 months ago  # | flag

'THIS' is exactly the same but withoutQtDesigner. QtDesigner only places the widgets and set their geometry. Now I simply set a main window with a QLineEdit, exactly as it is in your code. May you tell me what I'm doing wrong?

A 7 years, 7 months ago  # | flag

Jshah, may you help me?

jshah (author) 7 years, 7 months ago  # | flag
Created by jshah on Mon, 26 Oct 2015 (MIT)
Python recipes (4591)
jshah's recipes (1)

Required Modules

Other Information and Tasks