Problem
I've run into rather weird problem: QLineEdit
objects in the interface cannot be accessed by mouse clicking on them, but using Tab
's they receive the focus and respond to keyboard input. But even when they have the focus, they do not respond to clicks. What makes things weirder is that the last added QLineEdit
does respond to clicks.
The extra problem: mouse-inaccessible line-edits do not show tool-tips.
I am using Common Lisp's qtools to construct the interface, so I will have to do some explaining how it works. In short, I'm adding line-edits one-by-one in the loop, all of them created in the same manner and added to QGridLayout
.
Updated: See below for a possible source of the problem
Parameter widget
Parameter widget is used to represent one parameter input. Each parameter has a name, value and units (of measure). The name and units are shown as labels, the value is editable and is represented by QLineEdit
(define-widget parameter-widget (QWidget)
((parameter :initarg :parameter)))
(define-subwidget (parameter-widget label) (q+:make-qlabel parameter-widget)
(setf (q+:text label) (parameter-base-name parameter)))
(define-subwidget (parameter-widget entry) (q+:make-qlineedit parameter-widget)
(setf (q+:alignment entry) +align-right+)
(setf (q+:text entry) (format nil "~A" (parameter-value parameter)))
(setf (q+:tool-tip entry) (parameter-base-description parameter)))
(define-subwidget (parameter-widget units) (q+:make-qlabel parameter-widget)
(setf (q+:text units) (parameter-units parameter)))
To indicate that parameter value has been changed, parameter-widget
will emmit the new signal parameter-changed
(it is translated to a signal "parameterChanged" for Qt):
(define-signal (parameter-widget parameter-changed) ())
The slot for the line edit tries to convert the text to a number, and if successful, updates the underlying parameter and emits parameter-changed
:
(define-slot (parameter-widget parameter-changed) ((new-text string))
(declare (connected entry (text-changed string)))
(handler-case
(let ((new-number (parse-number new-text)))
(setf (parameter-value parameter) new-number)
(signal! parameter-widget (parameter-changed)))
(error nil)))
This method is just to automatically construct the widget for the object of type parameter
(factory method):
(defmethod make-parameter-ui ((object parameter))
(make-instance 'parameter-widget :parameter object))
This method is required to align labels for multiple single parameters in parameter-container
(see further-below):
(defmethod add-to-grid ((widget parameter-widget) grid row column)
(with-slots (label entry units) widget
(q+:add-widget grid label row column 1 1)
(q+:add-widget grid entry row (1+ column) 1 1)
(q+:add-widget grid units row (+ column 2) 1 1)))
Parameter container
So, if I use parameter-widget
on its own - everything is OK. But most of the time I need multiple parameters in the form of parameter-container
:
(define-widget parameter-container-widget (QWidget)
((parameter-container :initarg :parameter-container)))
This slot captures the signal parameter-changed
from all children parameter
's and re-emits it for the container
(define-slot (parameter-container-widget parameter-changed) ()
(format t "~&Re-emitting the signal...~%")
(signal! parameter-container-widget (parameter-changed)))
layout
is QGridLayout
to put all individual parameter-widget
's for all parameter
-children of parameter-container
. All it does:
- it loops over all individual parameters,
- constructs
parameter-widget
for each - adds to the
layout
row-by-row - and connects the signal
parameter-changed
to the above-defined slot.
Code:
(define-subwidget (parameter-container-widget layout)
(q+:make-qgridlayout parameter-container-widget)
(loop for p in (parameter-container-children parameter-container)
for row from 0
do (let ((parameter-widget (make-parameter-ui p)))
(setf (q+:parent parameter-widget) parameter-container-widget)
(add-to-grid parameter-widget layout row 0)
(connect! parameter-widget (parameter-changed)
parameter-container-widget
(parameter-changed)))))
This method is for uniform treatment to parameter-widget
and parameter-container
:
(defmethod add-to-grid ((widget parameter-container-widget) grid row column)
(q+:add-widget grid widget row column 1 1))
And generic instantiator (factory method):
(defmethod make-parameter-ui ((object parameter-container))
(make-instance 'parameter-container-widget :parameter-container object))
System information
Qt4.8, Common Lisp: SBCL 1.3.7, qtools from Quicklisp, OS: Ubuntu 16.04
Update
The same problem on Windows 10
Update 2: Full minimal example
I apologise, the code above uses many definitions from other packages: putting it all would make it really long. The smallest example boils down to this:
(ql:quickload '(:qtools :qtcore :qtgui))
(defpackage :qtexample
(:use #:cl+qt))
(in-package qtexample)
(in-readtable :qtools)
(defclass parameter ()
((name :initarg :name :accessor parameter-name)
(value :initarg :value :accessor parameter-value)
(units :initarg :units :accessor parameter-units)))
(defun parameter (name value units)
(make-instance 'parameter
:name name
:value value
:units units))
(defvar *mass* (parameter "mass" 1d0 "kg"))
(defvar *velocity* (parameter "velocity" 0.5d0 "m/s"))
(defvar *temperature* (parameter "temperature" 300d0 "K"))
(defvar *pressure* (parameter "pressure" 1d5 "Pa"))
(define-widget parameter-widget (QWidget)
((parameter
:initarg :parameter
:accessor parameter-widget-parameter)))
(define-subwidget (parameter-widget label)
(q+:make-qlabel parameter-widget)
(setf (q+:text label) (parameter-name parameter)))
(defmethod make-ui ((object parameter))
(make-instance 'parameter-widget :parameter object))
(defconstant +align-right+ 2)
(define-subwidget (parameter-widget entry)
(q+:make-qlineedit parameter-widget)
(setf (q+:alignment entry) +align-right+)
(setf (q+:text entry) (format nil "~A" (parameter-value parameter))))
(define-subwidget (parameter-widget units)
(q+:make-qlabel parameter-widget)
(setf (q+:text units) (parameter-units parameter)))
(define-signal (parameter-widget parameter-changed) ())
(define-slot (parameter-widget entry) ((new-text string))
(declare (connected entry (text-changed string)))
(format t "~&Parameter has changed~%")
(handler-case
(let ((new-number (parse-number:parse-number new-text)))
(setf (parameter-value parameter) new-number)
(signal! parameter-widget (parameter-changed)))
(error nil)))
(defmethod add-to-grid ((widget parameter-widget) grid row column)
(with-slots (label entry units) widget
(q+:add-widget grid label row column 1 1)
(q+:add-widget grid entry row (1+ column) 1 1)
(q+:add-widget grid units row (+ column 2) 1 1)
(list (1+ row) (+ column 3))))
(define-widget parameter-widget-window (QWidget)
((parameter :initarg :parameter)))
(define-subwidget (parameter-widget-window parameter-widget)
(make-ui parameter))
(define-subwidget (parameter-widget-window grid)
(q+:make-qgridlayout parameter-widget-window)
(add-to-grid parameter-widget grid 0 0))
(defun parameter-example (parameter)
(with-main-window
(window (make-instance 'parameter-widget-window
:parameter parameter))))
(define-widget parameter-container-widget (QWidget)
((parameter-container
:initarg :parameter-container
:accessor parameter-container-widget-parameter-container)))
(defmethod make-ui ((object list))
(make-instance 'parameter-container-widget :parameter-container object))
(define-slot (parameter-container-widget parameter-changed) ()
(format t "~&Re-emitting the signal...~%")
(signal! parameter-container-widget (parameter-changed)))
(define-subwidget (parameter-container-widget layout)
(q+:make-qgridlayout parameter-container-widget)
(let* ((parameter-widgets (loop for p in parameter-container
collect (make-ui p))))
(loop for p in parameter-widgets
for row from 0
do (progn
(setf (q+:parent p) parameter-container-widget)
(add-to-grid p layout row 0)
(connect! p
(parameter-changed)
parameter-container-widget
(parameter-changed))))))
(define-widget parameter-container-widget-window (QWidget)
((parameter-container :initarg :parameter-container)))
(define-subwidget (parameter-container-widget-window container-widget)
(make-ui parameter-container)
(setf (q+:parent container-widget) parameter-container-widget-window))
(define-slot (parameter-container-widget-window parameter-changed) ()
(declare (connected container-widget (parameter-changed)))
(format t "~&Got parameter changed~%"))
(defmethod add-to-grid ((widget parameter-container-widget) grid row column)
(q+:add-widget grid widget row column))
(defun example-parameter-container (parameter-container)
(with-main-window
(window (make-instance 'parameter-container-widget-window
:parameter-container parameter-container))))
;; to run:
(example-parameter-container (list *mass* *velocity*))
While working on it, I stumbled upon a possible solution. This line
(setf (q+:parent p) parameter-container-widget)
sets the parent for a sub-widget p
(in the list of sub-widgets) to be parameter-container-widget
. If this line is commented, everything works fine.
p
's sub-widgets (which include entry
, an instance of QLineEdit
) are later added into the grid, but not p
itself! In a way, parameter-widget
is not a proper widget: it's just a collection of other widgets with the rules of how to add them into the container. But it needs to act as widget in a sense of being able to receive and send signals.
parameter-widget
not aparameter-object
- aQObject
, notQWidget
? – Kuba hasn't forgotten Monicadefine-object
, which is just a new name fordefine-widget
. The truth is that even if you usedefine-widget
, the automaticwidget
superclass inherits fromQObject
, notQWidget
. As such, in order to define aQObject
subclass you can simply do(define-widget foo (QObject) ())
. – Shinmera