Как я могу использовать Clojure REPL вместе с Qt Jambi?
Я не нашел решение использовать Clojure REPL с Qt в Интернете.
В основном проблема заключается в том, что REPL зависает, как только вы вызываете QApplication/exec, чтобы отобразить пользовательский интерфейс. Вы не можете C-c C-c вернуться в REPL, и закрытие активного окна Qt, кажется, убивает весь процесс Clojure.
Теперь просто вызов QApplication/processEvents из агента невозможен, если агент не работает точно в том же потоке, в котором вы создали свои виджеты Qt. Мне потребовалось два дня, чтобы понять это, и я видел, что у других есть одна проблема/проблема, но без решения.
Итак, вот мой, в коде:
(add-classpath "file:///usr/share/java/qtjambi.jar")
(ns qt4-demo
(:import (com.trolltech.qt.gui QApplication QPushButton QFont QFont$Weight)
(com.trolltech.qt.core QCoreApplication)
(java.util Timer TimerTask)
(java.util.concurrent ScheduledThreadPoolExecutor TimeUnit))
(:require swank.core))
(defn init []
(QApplication/initialize (make-array String 0)))
(def *gui-thread* (new java.util.concurrent.ScheduledThreadPoolExecutor 1))
(def *gui-update-task* nil)
(def *app* (ref nil))
(defn update-gui []
(println "Updating GUI")
(QApplication/processEvents))
(defn exec []
(.remove *gui-thread* update-gui)
(def *gui-update-task* (.scheduleAtFixedRate *gui-thread* update-gui 0 150 (. TimeUnit MILLISECONDS))))
(defn stop []
(.remove *gui-thread* update-gui)
(.cancel *gui-update-task*))
(defmacro qt4 [& rest]
`(do
(try (init) (catch RuntimeException e# (println e#)))
[email protected]
))
(defmacro with-gui-thread [& body]
`(.get (.schedule *gui-thread* (fn [] (do [email protected])) (long 0) (. TimeUnit MILLISECONDS))))
(defn hello-world []
(with-gui-thread
(qt4
(let [app (QCoreApplication/instance)
button (new QPushButton "Go Clojure Go")]
(dosync (ref-set *app* app))
(doto button
(.resize 250 100)
(.setFont (new QFont "Deja Vu Sans" 18 (.. QFont$Weight Bold value)))
(.setWindowTitle "Go Clojure Go")
(.show)))))
(exec))
В основном он использует класс ScheduledThreadPoolExecutor для выполнения всего Qt-кода. Вы можете использовать макрос с-gui-thread, чтобы упростить вызов функций из потока.
Это позволяет изменять Qt UI "на лету" без перекомпиляции.
Ответы
Ответ 1
Если вы хотите общаться с Qt-виджетами из REPL, QApplication/invokeLater
или QApplication/invokeAndWait
, вероятно, вы хотите. Вы можете использовать их вместе с агентами. Учитывая это:
(ns qt4-demo
(:import (com.trolltech.qt.gui QApplication QPushButton)
(com.trolltech.qt.core QCoreApplication)))
(def *app* (ref nil))
(def *button* (ref nil))
(def *runner* (agent nil))
(defn init [] (QApplication/initialize (make-array String 0)))
(defn exec [] (QApplication/exec))
(defn hello-world [a]
(init)
(let [app (QCoreApplication/instance)
button (doto (QPushButton. "Go Clojure Go") (.show))]
(dosync (ref-set *app* app)
(ref-set *button* button)))
(exec))
Затем из REPL:
qt4-demo=> (send-off *runner* hello-world)
#<[email protected]: nil>
;; This fails because we are not in the Qt main thread
qt4-demo=> (.setText @*button* "foo")
QObject used from outside its own thread, object=QPushButton(0x8d0f55f0) , objectThread=Thread[pool-2-thread-1,5,main], currentThread=Thread[main,5,main] (NO_SOURCE_FILE:0)
;; This should work though
qt4-demo=> (QApplication/invokeLater #(.setText @*button* "foo"))
nil
qt4-demo=> (QApplication/invokeAndWait #(.setText @*button* "bar"))
nil
Ответ 2
Я написал о том, как сделать это с помощью SLIME в моем блоге (немецкий), а также в списке рассылки Clojure. Трюк состоит в том, чтобы определить соответствующие функции на стороне Emacs и сообщить SLIME использовать их при выполнении запросов. Важно отметить, что это освобождает вас от необходимости делать специальные заклинания при вызове кода Qt.
Цитата:
Учитывая, что мы говорим Lisp здесь, во всяком случае, решение, казалось, было очевидно: Hack SLIME! Так что, что я сделал. Приведенный ниже код в ваш .emacs(в момент, когда SLIME уже полностью загружен), регистрирует три новых Emacs- Lispфункции для интерактивного использования. Вы может связывать их с любыми клавишами например, или вы даже можете установить шламовый посыл сквозного QApplication переменная до t после вашего приложения началось и не беспокоиться о ключе привязки вообще. Либо следует сделать ваши заявки REPL и C-M-x-style интерактивные оценки косвенные через QCoreApplication/invokeAndWait.
Удачи!
(defvar slime-send-through-qapplication nil)
(defvar slime-repl-send-string-fn (symbol-function 'slime-repl-send-
string))
(defvar slime-interactive-eval-fn (symbol-function 'slime-interactive-
eval))
(defun qt-appify-form (form)
(concatenate 'string ;'
"(let [return-ref (ref nil)] "
" (com.trolltech.qt.core.QCoreApplication/invokeAndWait "
" (fn [] "
" (let [return-value (do "
form
" )] "
" (dosync (ref-set return-ref return-value))))) "
" (deref return-ref))"))
(defun slime-interactive-eval (string)
(let ((string (if slime-send-through-qapplication
(qt-appify-form string)
string)))
(funcall slime-interactive-eval-fn string)))
(defun slime-repl-send-string (string &optional command-string)
(let ((string (if slime-send-through-qapplication
(qt-appify-form string)
string)))
(funcall slime-repl-send-string-fn string command-string)))
(defun slime-eval-defun-for-qt ()
(interactive)
(let ((slime-send-through-qapplication t))
(slime-eval-defun)))
(defun slime-repl-closing-return-for-qt ()
(interactive)
(let ((slime-send-through-qapplication t))
(slime-repl-closing-return)))
(defun slime-repl-return-for-qt (&optional end-of-input)
(interactive)
(let ((slime-send-through-qapplication t))
(slime-repl-return end-of-input)))