如何与Qt Jambi一起使用Clojure REPL?

时间:2022-03-01 22:50:04

I have not found a solution to use the Clojure REPL with Qt on the web. Basically the problem is that the REPL hangs as soon as you call QApplication/exec in order to get the UI to display. You cannot C-c C-c back into the REPL, and closing the active Qt window seems to kill the whole Clojure process.

我还没有找到在网上使用Clojure REPL和Qt的解决方案。基本上问题是,只要您调用QApplication / exec以便显示UI,REPL就会挂起。你不能将C-c C-c重新放回REPL,关闭活动的Qt窗口似乎会杀死整个Clojure进程。

Now simply calling QApplication/processEvents from within an agent is not possible, unless the agent runs in exactly the same thread in which you created your Qt widgets. It took me two days to figure this out and I have seen others have the same issue/problem but without a solution. So here is mine, in code:

现在只能在代理中调用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#)))
     ~@rest
     ))

(defmacro with-gui-thread [& body]
  `(.get (.schedule *gui-thread* (fn [] (do ~@body)) (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))

Basically it uses the ScheduledThreadPoolExecutor class in order to execute all Qt-code. You can use the with-gui-thread macro to make it easier to call functions from within the thread. This makes it possible to change the Qt UI on-the-fly, without recompiling.

基本上它使用ScheduledThreadPoolExecutor类来执行所有Qt代码。您可以使用with-gui-thread宏来更容易地从线程内调用函数。这使得可以即时更改Qt UI,而无需重新编译。

2 个解决方案

#1


5  

If you want to mess with Qt widgets from the REPL, QApplication/invokeLater or QApplication/invokeAndWait are probably what you want. You can use them in conjunction with agents. Given this:

如果你想搞乱REPL中的Qt小部件,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))

Then from a REPL:

然后从REPL:

qt4-demo=> (send-off *runner* hello-world)
#<Agent@38fff7: 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


3  

I've written about how to do this with SLIME on my blog (German) as well as on the Clojure mailing-list. The trick is to define appropriate functions on the Emacs side and tell SLIME to use those when making requests. Importantly, this frees you from having to do special incantations when invoking Qt code.

我已经在我的博客(德语)以及Clojure邮件列表上写过关于如何使用SLIME执行此操作。诀窍是在Emacs端定义适当的功能,并告诉SLIME在发出请求时使用它们。重要的是,这使您无需在调用Qt代码时执行特殊的咒语。

Quoting myself:

引用自己:

Given that we're talking Lisp here, anyway, the solution seemed to be obvious: Hack SLIME! So that's what I did. The code below, when dropped into your .emacs (at a point at which SLIME is already fully loaded), registers three new Emacs-Lisp functions for interactive use. You can bind them to whatever keys you like, or you may even just set the slime-send-through-qapplication variable to t after your application has started and not worry about key bindings at all. Either should make your REPL submissions and C-M-x-style interactive evaluations indirect through QCoreApplication/invokeAndWait.

鉴于我们在这里谈论Lisp,无论如何,解决方案似乎显而易见:Hack SLIME!这就是我做的。下面的代码,当放入.emacs(在SLIME已经完全加载的位置)时,会注册三个新的Emacs-Lisp函数以供交互使用。您可以将它们绑定到您喜欢的任何键,或者您甚至可以在应用程序启动后将slime-send-through-qapplication变量设置为t,而不必担心键绑定。要么通过QCoreApplication / invokeAndWait间接进行REPL提交和C-M-x风格的交互式评估。

Have fun!

玩的开心!

(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))) 

#1


5  

If you want to mess with Qt widgets from the REPL, QApplication/invokeLater or QApplication/invokeAndWait are probably what you want. You can use them in conjunction with agents. Given this:

如果你想搞乱REPL中的Qt小部件,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))

Then from a REPL:

然后从REPL:

qt4-demo=> (send-off *runner* hello-world)
#<Agent@38fff7: 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


3  

I've written about how to do this with SLIME on my blog (German) as well as on the Clojure mailing-list. The trick is to define appropriate functions on the Emacs side and tell SLIME to use those when making requests. Importantly, this frees you from having to do special incantations when invoking Qt code.

我已经在我的博客(德语)以及Clojure邮件列表上写过关于如何使用SLIME执行此操作。诀窍是在Emacs端定义适当的功能,并告诉SLIME在发出请求时使用它们。重要的是,这使您无需在调用Qt代码时执行特殊的咒语。

Quoting myself:

引用自己:

Given that we're talking Lisp here, anyway, the solution seemed to be obvious: Hack SLIME! So that's what I did. The code below, when dropped into your .emacs (at a point at which SLIME is already fully loaded), registers three new Emacs-Lisp functions for interactive use. You can bind them to whatever keys you like, or you may even just set the slime-send-through-qapplication variable to t after your application has started and not worry about key bindings at all. Either should make your REPL submissions and C-M-x-style interactive evaluations indirect through QCoreApplication/invokeAndWait.

鉴于我们在这里谈论Lisp,无论如何,解决方案似乎显而易见:Hack SLIME!这就是我做的。下面的代码,当放入.emacs(在SLIME已经完全加载的位置)时,会注册三个新的Emacs-Lisp函数以供交互使用。您可以将它们绑定到您喜欢的任何键,或者您甚至可以在应用程序启动后将slime-send-through-qapplication变量设置为t,而不必担心键绑定。要么通过QCoreApplication / invokeAndWait间接进行REPL提交和C-M-x风格的交互式评估。

Have fun!

玩的开心!

(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)))