2009-11-07 7 views
11

Ich habe keine Lösung gefunden, die ClojureREPL mit Qt im Web zu verwenden. Grundsätzlich ist das Problem, dass die REPL hängt, sobald Sie QApplication/exec aufrufen, um die Benutzeroberfläche anzuzeigen. Du kannst nicht C-c C-c zurück in die REPL, und das Schließen des aktiven Qt-Fensters scheint den gesamten Clojure-Prozess zu beenden.Wie kann ich die Clojure REPL zusammen mit Qt Jambi verwenden?

Der einfache Aufruf von QApplication/processEvents aus einem Agenten heraus ist nicht möglich, es sei denn, der Agent wird in genau demselben Thread ausgeführt, in dem Sie die Qt-Widgets erstellt haben. Ich habe zwei Tage gebraucht, um das herauszufinden und ich habe gesehen, dass andere das gleiche Problem haben, aber ohne eine Lösung. hier also ist mein, 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#))) 
    [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)) 

Grundsätzlich verwendet es die ScheduledThreadPoolExecutor Klasse, um alle Qt-Code auszuführen. Sie können das With-GUI-Thread-Makro verwenden, um das Aufrufen von Funktionen innerhalb des Threads zu erleichtern. Dies ermöglicht es, die Qt UI im laufenden Betrieb ohne Neukompilierung zu ändern.

+0

Ja, ich musste das gleiche tun. – levand

+0

Ich weiß nichts über QT. Aber warum willst du das machen? Clojure hat Zugang zu Swing, einem sehr leistungsfähigen und vielseitigen GUI-Framework. Verbinden Sie sich mit einer bereits vorhandenen QT-GUI? –

+0

QT ist in vielerlei Hinsicht besser als Swing, einschließlich Performance und nativem Look-and-Feel. – levand

Antwort

5

Wenn Sie sich mit Qt Widgets von der REPL, QApplication/invokeLater oder QApplication/invokeAndWait befassen wollen, sind wahrscheinlich, was Sie wollen. Sie können sie in Verbindung mit Agenten verwenden. Vor diesem Hintergrund:

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

Dann von einem 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 
+0

Sehr schön. Ich mag es. Vielen Dank! – MHOOO

3

Ich habe darüber geschrieben, wie dies zu tun mit SLIME on my blog (deutsch) sowie on the Clojure mailing-list. Der Trick besteht darin, entsprechende Funktionen auf der Emacs-Seite zu definieren und SLIME zu sagen, dass sie diese verwenden sollen, wenn sie Anfragen stellen. Wichtig dabei ist, dass Sie beim Aufruf von Qt-Code keine besonderen Beschwörungsformeln mehr benötigen.

Zitiert mich:

Da wir Lisp hier sprechen, sowieso, schien die Lösung offensichtlich zu sein: Hack SLIME! So ist das, was ich tat. Der Code unten, in Ihre .emacs (an einem Punkt, bei dem SLIME ist bereits voll geladen), drei neue Emacs-Lisp Funktionen für die interaktive Nutzung abgelegt. Sie können sie binden, was auch immer Schlüssel Sie mögen, oder Sie können sogar festlegen, nur die Schleim-send-Durch QApplication Variable t nach Ihrer Anwendung begonnen hat und schon gar nicht über Schlüssel Bindungen sorgen. Entweder sollte Ihre REPL-Übermittlungen und C-M-x-style interaktive Bewertungen indirekte über QCoreApplication/invokeAndWait machen.

Viel Spaß!

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