-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathqt-utils.lisp
362 lines (309 loc) · 12.9 KB
/
qt-utils.lisp
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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
(in-package #:simple-gui)
(defvar *qt-modules* '(:qtcore
:qtgui
:qtopengl
:qtsvg
:qtwebkit)
"Qt Modules to load and generate GUI description macros.")
;;;; Some meta utils
(defun all-qclasses ()
"List all qt classes, each is represented by a number."
(let ((all-qclasses nil))
(qt::map-classes #'(lambda (qclass) (push qclass all-qclasses)))
(nreverse all-qclasses)))
(memof all-qclasses)
(defun all-qsubclasses-of (qclass)
"List all subclasses of given QCLASS. QCLASS is a number."
(remove-if-not #'(lambda (qsubclass)
(qt::qsubclassp qsubclass qclass))
(all-qclasses)))
(memof all-qsubclasses-of)
(defun qclass-names (qclass-list)
"All qt class names of QCLASS-LIST contains a list of numbers."
(mapcar #'qt::qclass-name qclass-list))
(defun all-qmethods ()
"List all qt methods, each is represented by a number."
(let ((all-qmethods nil))
(qt::map-methods #'(lambda (qmethod)
(unless (or (qt::qmethod-ctor-p qmethod)
(qt::qmethod-dtor-p qmethod)
(qt::qmethod-enum-p qmethod))
(push qmethod all-qmethods))))
all-qmethods))
(memof all-qmethods)
(defun all-qenums ()
"List all qt enums, each is represented by a number."
(let ((all-qenums nil))
(qt::map-methods #'(lambda (qmethod)
(if (qt::qmethod-enum-p qmethod)
(push qmethod all-qenums))))
all-qenums))
(memof all-qenums)
(defvar *except-qmethod-names* '("drop_action" "setDrop_action" "default_action"
"setDefault_action" "set_widget" "_deviceType"
"set_deviceType" "_touchPointStates"
"set_touchPointStates" "_touchPoints"
"set_touchPoints" "_widget")
"Excepted qt method names, which are actually not in qt doc but for internal use.")
(defun except-qmethod-name-p (qmethod-name)
"Return T if QMETHOD-NAME is an except method name, otherwise return NIL"
(find qmethod-name
*except-qmethod-names*
:test #'string=))
(defun all-qmethod-names ()
"List all qt method names, duplicate name but different method are only count once."
(let ((names (make-hash-table :test #'equal)))
(mapcar #'(lambda (qmethod)
(let ((qmethod-name (qt::qmethod-name qmethod)))
(unless (except-qmethod-name-p qmethod-name)
(setf (gethash qmethod-name names)
t))))
(all-qmethods))
(hash-table-keys names)))
(memof all-qmethod-names)
(defvar *except-enum-names* '("Q_COMPLEX_TYPE" "Q_PRIMITIVE_TYPE" "Q_STATIC_TYPE"
"Q_MOVABLE_TYPE" "Q_DUMMY_TYPE" "LicensedGui" "LicensedXml"
"LicensedQt3SupportLight" "LicensedScript" "LicensedOpenVG"
"LicensedDBus" "LicensedTest" "LicensedActiveQt"
"LicensedScriptTools" "LicensedSvg" "LicensedDeclarative"
"LicensedSql" "LicensedOpenGL" "LicensedCore" "QtDebugMsg"
"QtWarningMsg" "QtCriticalMsg" "QtFatalMsg" "QtSystemMsg"
"LicensedHelp" "LicensedMultimedia" "LicensedQt3Support"
"LicensedXmlPatterns" "LicensedNetwork")
"Except qt enum names, which are actually not implemented by CommonQt.")
(defun except-qenum-name-p (enum-class-and-name)
"Return T if ENUM-CLASS-AND-NAME is an except qt enum name, otherwise return NIL."
(and (string= (car enum-class-and-name) "QGlobalSpace")
(find (cdr enum-class-and-name)
*except-enum-names*
:test #'string=)))
(defun all-qenum-names ()
"List all qt enum names, each of name is in a form (QCLASS-NAME . QENUM-NAME)."
(let ((names nil))
(mapcar #'(lambda (qmethod)
(let ((enum-class-and-name
(cons (qt::qclass-name (qt::qmethod-class qmethod))
(qt::qmethod-name qmethod))))
(unless (except-qenum-name-p enum-class-and-name)
(push enum-class-and-name names))))
(all-qenums))
names))
(memof all-qenum-names)
;;;; Name conversions
(defun convert-to-qt-class-name (s)
"Convert a string foo-bar to string FooBar"
(let ((camel-case (convert-lisp-name-to-camel-case s)))
(setf (aref camel-case 0) (char-upcase (aref camel-case 0)))
camel-case))
(defun convert-to-lisp-name (s)
"Convert a string QPushButton or qPush_button to string Q-PUSH-BUTTON."
(let ((temp-name
(ppcre:regex-replace-all "--"
(ppcre:regex-replace-all
"_" (string-upcase
(ppcre:regex-replace-all "[A-Z]" s "-\\\&")) "-")
"-")))
(if (eql (aref temp-name 0) #\-)
(subseq temp-name 1)
temp-name)))
(defvar *convert-special-cases*
'(("OPERATOR--" . "OPERATOR--")
("APPEND" . "QAPPEND") ("SUBSTITUTE" . "QSUBSTITUTE") ("SPACE" . "QSPACE")
("FIND" . "QFIND") ("POSITION" . "QPOSITION") ("LAST" . "QLAST")
("READLINE" . "QREAD-LINE") ("NUMBER" . "QNUMBER") ("WARNING" . "QWARNING")
("OPEN" . "QOPEN") ("OPTIMIZE" . "QOPTIMIZE") ("STRING" . "QSTRING")
("CONJUGATE" . "QCONJUGATE") ("LOAD" . "QLOAD") ("TYPE" . "QTYPE")
("WRITE" . "QWRITE") ("READ" . "QREAD") ("SECOND" . "QSECOND")
("CLASSNAME" . "Q-CLASS-NAME") ("CHECKTYPE" . "QCHECK-TYPE")
("FIRST" . "QFIRST") ("CLOSE" . "QCLOSE") ("MERGE" . "QMERGE")
("ABORT" . "QABORT") ("SEQUENCE" . "QSEQUENCE") ("SPEED" . "QSPEED")
("IGNORE" . "QIGNORE") ("REPLACE" . "QREPLACE") ("METHOD" . "Q-METHOD")
("SORT" . "QSORT") ("ERROR" . "QERROR") ("REMOVE" . "QREMOVE")
("SIGNAL" . "Q-SIGNAL") ("PUSH" . "QPUSH") ("MAP" . "QMAP")
("RESTART" . "QRESTART") ("DIRECTORY" . "QDIRECTORY") ("FILL" . "QFILL")
("T" . "Q-T") ("LOG" . "QLOG") ("PRINT" . "QPRINT") ("FORMAT" . "QFORMAT")
("LENGTH" . "QLENGTH") ("TIME" . "QTIME") ("BLOCK" . "QBLOCK")
("TRUNCATE" . "QTRUNCATE") ("VECTOR" . "QVECTOR") ("COUNT" . "QCOUNT")
("SHADOW" . "QSHADOW") ("PROFILE" . "QPROFILE") ("RESET" . "QRESET")
("TIMEOUT" . "QTIMEOUT") ("ExIT" . "QEXIT") ("QUIT" . "QQuit")
("DEREF" . "QDEREF") ("CAST" . "QCAST"))
"Special cases where name conversions to lisp is manually specified.")
(defun convert-qt-method-name-to-lisp-name (s)
"Do the conversion by CONVERT-TO-LISP-NAME, but with some special-case."
(let ((special (assoc s *convert-special-cases* :test #'string-equal)))
(if special
(cdr special)
(convert-to-lisp-name s))))
(defun convert-to-qt-method-name (s)
"Convert a string foo-bar to string fooBar"
(convert-lisp-name-to-camel-case s))
(defun convert-lisp-name-to-camel-case (s)
"Convert a string designator foo-bar to string fooBar"
(let ((s (string-downcase (string s))))
(do ((i 0 (1+ i)))
((>= i (length s)))
(when (eql (aref s i) #\-)
(incf i)
(when (< i (length s))
(setf (aref s i) (char-upcase (aref s i))))))
(delete #\- s)))
(defun convert-to-cpp-basic-type (s)
"Convert a symbol or string S to the string indicating a C++ basic type."
(string-downcase (string s)))
(defun convert-to-cpp-basic-type-or-qt-class (s)
"Convert a symbol or string s to the string indicating a C++ basic type or a qt class."
(if (find #\- (string s))
(convert-to-qt-class-name s)
(convert-to-cpp-basic-type s)))
(defun convert-to-qt-signal-name (sig &rest args)
"Convert a list like (:clicked :int :q-push-button) to corresponding qt signal name."
(mkstr (convert-to-qt-method-name sig)
"("
(join-str "," (mapcar #'convert-to-cpp-basic-type-or-qt-class args))
")"))
;;;; Translation for simple descriptive GUI definition.
(defun expand-qt-method-call (method-name &rest args)
"Expand to a CommonQt method call."
`(optimized-call t ,(car args) ,method-name ,@(cdr args)))
(defun translate-set (obj prop)
"Translate a set specification to a qt setter method."
(let ((setter (car prop))
(vals (cdr prop)))
(apply #'expand-qt-method-call
(convert-to-qt-method-name (mkstr "set-" setter))
obj
vals)))
(defun translate-add (obj to-add)
"Translate an add specification to the q-add wrapper."
`(q-add ,obj ,to-add))
(defun q-add (parent child)
"A wrapper that will determine to call addWidget or addLayout depends on type of child."
(let ((child-class (slot-value child 'class)))
(cond ((qt::qsubclassp child-class (qt::find-qclass "QWidget"))
(optimized-call t parent "addWidget" child))
((qt::qsubclassp child-class (qt::find-qclass "QLayout"))
(optimized-call t parent "addLayout" child))
(t (error "Illegal object ~a to add." child)))))
(defun translate-connect (sender sig-and-fn)
"Translate a qt signal connect specification."
(let ((sig (butlast sig-and-fn))
(fn (lastcar sig-and-fn)))
(expand-qt-connect-signal sender sig fn)))
(defun translate-connect* (sender sig-fn-data)
"Translate a more detailed connect specification, which can pass an additional
data argument to the connected function."
(let* ((sig-fn (butlast sig-fn-data))
(sig (butlast sig-fn))
(fn (lastcar sig-fn))
(data (lastcar sig-fn-data))
(gargs (gensym)))
(expand-qt-connect-signal sender sig `#'(lambda (&rest ,gargs)
(apply ,fn ,data ,gargs)))))
(defun expand-qt-connect-signal (sender sig fn)
"Common parts for expand connect and connect* specification."
`(connect ,sender ,(apply #'convert-to-qt-signal-name sig) ,fn))
(defun translate-spec (spec-opr obj spec)
"Translate a specification according to the SPEC-OPR."
(ecase spec-opr
(:add (translate-add obj spec))
(:set (translate-set obj spec))
((:con :connect) (translate-connect obj spec))
((:con* :connect*) (translate-connect* obj spec))))
(defun translate-specs (name specs)
"Translate a group of specifications of same type."
(if (keywordp (car specs))
(mapcar #'(lambda (spec)
(translate-spec (car specs)
name
spec))
(cdr specs))
(mapcar #'(lambda (spec)
(translate-set name spec))
specs)))
(defun translate-body (name body)
"Translate a GUI element body, contains groups of specifications."
(mapcan #'(lambda (specs)
(translate-specs name specs)) body))
;;;; Generate macros for GUI element description.
(defmacro generate-all-qt-class-macros ()
"Generate all macros for GUI element description."
`(progn
,@(mapcar #'expand-qt-class-macro
(qclass-names
(remove-duplicates
(append
(all-qsubclasses-of (qt::find-qclass "QWidget"))
(all-qsubclasses-of (qt::find-qclass "QLayout"))))))))
(defun expand-qt-class-macro (qclass-name)
"Gernerate a single GUI element description."
(let* ((qclass-lisp-name (convert-to-lisp-name qclass-name))
(qclass-symbol (intern qclass-lisp-name :simple-gui)))
`(progn
(export ',qclass-symbol :simple-gui)
(defmacro ,qclass-symbol (name &body body)
(if (or (null name) (string= (symbol-name name)
"-"))
(setf name (gensym)))
`(let* ((,name (optimized-new ,,qclass-name))
(it ,name))
(declare (ignorable it))
(setf (gethash ',name *q-objects*) ,name)
,@ (translate-body name body)
,name)))))
(eval-when (:load-toplevel :compile-toplevel :execute)
(defun load-qt-stage1 ()
"Load qt modules and switch to CommonQt's readtable."
(named-readtables:in-readtable :qt)
(mapcar #'ensure-smoke *qt-modules*)))
(load-qt-stage1)
(defmacro gui (name)
"Macro for access gui elements."
`(gethash ',name *q-objects*))
;;;; Generate all qt methods in a lisp like way to reprensent method names.
(defvar *qt-method-package* (or (find-package "Q-")
(make-package "Q-" :use ()))
"The package that will contains all qt methods (including enums).")
(defmacro generate-all-qt-methods ()
"Generate all qt methods, each different method name converts to only one macro defination,
which method will be invoked is determined by smoke, not in cl."
`(progn
,@(mapcar #'expand-qt-method-macro
(all-qmethod-names))))
(defmacro generate-all-qt-enums ()
"Generate all qt enums."
`(progn
,@ (mapcar #'expand-qt-enum-macro
(all-qenum-names))))
(defun expand-qt-method-macro (qmethod-name)
"Generate a macro for the call of QMETHOD-NAME."
(let* ((qmethod-symbol-name (convert-qt-method-name-to-lisp-name qmethod-name))
(qmethod-symbol (intern qmethod-symbol-name *qt-method-package*)))
`(progn
(export ',qmethod-symbol ,(package-name (symbol-package qmethod-symbol)))
(defmacro ,qmethod-symbol (&rest args)
`(let ((class-or-obj ,(car args)))
(optimized-call t
(if (symbolp class-or-obj)
(convert-to-qt-class-name class-or-obj)
class-or-obj)
,,qmethod-name ,@(cdr args)))))))
(defun expand-qt-enum-macro (enum-class-and-name)
"Generate a symbol macro for the enum in the format Q-CLASS/Q-ENUM."
(let* ((qenum-symbol-name
(mkstr
(convert-to-lisp-name (car enum-class-and-name))
"/"
(convert-qt-method-name-to-lisp-name (cdr enum-class-and-name))))
(qenum-symbol (intern qenum-symbol-name *qt-method-package*)))
`(progn
(export ',qenum-symbol ,(package-name (symbol-package qenum-symbol)))
(define-symbol-macro ,qenum-symbol
(optimized-call t ,(car enum-class-and-name)
,(cdr enum-class-and-name))))))
(defmacro q-new (qclass &rest args)
"Make new qt non GUI element, maybe useful sometimes."
`(optimized-new ,(convert-to-qt-class-name qclass) ,@args))
(defmacro q-connect (obj &rest spec)
(translate-connect obj spec))
(defmacro q-connect* (obj &rest spec)
(translate-connect* obj spec))