简体   繁体   English

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

[英]How can I use the Clojure REPL together with Qt Jambi?

I have not found a solution to use the Clojure REPL with Qt on the web. 我还没有找到在网上使用Clojure REPL和Qt的解决方案。 Basically the problem is that the REPL hangs as soon as you call QApplication/exec in order to get the UI to display. 基本上问题是,只要您调用QApplication / exec以便显示UI,REPL就会挂起。 You cannot Cc Cc back into the REPL, and closing the active Qt window seems to kill the whole Clojure process. 你不能将Cc Cc重新放回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. 现在只能在代理中调用QApplication / processEvents是不可能的,除非代理运行在您创建Qt小部件的完全相同的线程中。 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: 所以这是我的,在代码中:

(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. 基本上它使用ScheduledThreadPoolExecutor类来执行所有Qt代码。 You can use the with-gui-thread macro to make it easier to call functions from within the thread. 您可以使用with-gui-thread宏来更容易地从线程内调用函数。 This makes it possible to change the Qt UI on-the-fly, without recompiling. 这使得可以在不重新编译的情况下即时更改Qt UI。

If you want to mess with Qt widgets from the REPL, QApplication/invokeLater or QApplication/invokeAndWait are probably what you want. 如果你想搞乱REPL中的Qt小部件, QApplication/invokeLaterQApplication/invokeAndWait可能就是你想要的。 You can use them in conjunction with agents. 您可以将它们与代理结合使用。 Given this: 鉴于这种:

(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

I've written about how to do this with SLIME on my blog (German) as well as on the Clojure mailing-list . 我已经在我的博客 (德语)以及Clojure邮件列表上写过如何使用SLIME执行此操作。 The trick is to define appropriate functions on the Emacs side and tell SLIME to use those when making requests. 诀窍是在Emacs端定义适当的功能,并告诉SLIME在发出请求时使用它们。 Importantly, this frees you from having to do special incantations when invoking Qt code. 重要的是,这使您无需在调用Qt代码时执行特殊的咒语。

Quoting myself: 引用自己:

Given that we're talking Lisp here, anyway, the solution seemed to be obvious: Hack SLIME! 鉴于我们在这里谈论Lisp,无论如何,解决方案似乎显而易见: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. 下面的代码,当放入.emacs(在SLIME已经完全加载的位置)时,会注册三个新的Emacs-Lisp函数以供交互使用。 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. 您可以将它们绑定到您喜欢的任何键,或者您甚至可以在应用程序启动后将slime-send-through-qapplication变量设置为t,而不必担心键绑定。 Either should make your REPL submissions and CMx-style interactive evaluations indirect through QCoreApplication/invokeAndWait. 要么通过QCoreApplication / invokeAndWait间接进行REPL提交和CMx风格的交互式评估。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM