【问题标题】:How can I use the Clojure REPL together with Qt Jambi?如何将 Clojure REPL 与 Qt Jambi 一起使用?
【发布时间】:2023-12-27 14:22:01
【问题描述】:

我还没有找到在网络上将Clojure REPL 与 Qt 一起使用的解决方案。 基本上问题是,只要您调用 QApplication/exec 以显示 UI,REPL 就会挂起。你不能 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#)))
     ~@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))

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

【问题讨论】:

  • 是的,我必须做同样的事情。
  • 我对QT一无所知。但是你为什么要这样做呢? Clojure 可以访问 Swing,这是一个非常强大且通用的 GUI 框架。您是否连接到已经存在的 QT GUI?
  • QT 在很多方面都可以说比 Swing 更好,包括性能和原生外观。
  • 在我看来,Qt 也有一个更干净的 API。 (很难想出比 Swing 更丑的 API。)Qt 应用与 Linux 桌面环境的集成也比 Swing 应用好得多。

标签: qt lisp clojure read-eval-print-loop qt-jambi


【解决方案1】:

如果你想弄乱 REPL 中的 Qt 小部件,QApplication/invokeLaterQApplication/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)
#<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】:

我已经写过如何使用 SLIME on my blog(德语)和 on the Clojure mailing-list 来做到这一点。诀窍是在 Emacs 端定义适当的函数,并告诉 SLIME 在发出请求时使用这些函数。重要的是,这使您在调用 Qt 代码时不必执行特殊的咒语。

引用我自己的话:

鉴于我们在这里讨论的是 Lisp, 无论如何,解决方案似乎是 显而易见:破解 SLIME!所以这就是我 做过。下面的代码,当被丢弃 进入你的 .emacs (在某个点 SLIME 已经完全加载), 注册三个新的 Emacs-Lisp 交互使用的功能。你 可以将它们绑定到您的任何键 喜欢,或者你甚至可以设置 slime-send-through-qapplication 申请后可变为 t 已经开始,不用担心钥匙 完全绑定。要么应该使 你的 REPL 提交和 C-M-x 风格 交互式评价间接 通过 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))) 

【讨论】:

    最近更新 更多