2009-11-07 17 views
11

Non ho trovato una soluzione per utilizzare ClojureREPL con Qt sul web. Fondamentalmente il problema è che il REPL si blocca non appena si chiama QApplication/exec per visualizzare l'interfaccia utente. Non puoi C-c C-c tornare in REPL, e chiudere la finestra Qt attiva sembra uccidere l'intero processo Clojure.Come posso utilizzare il REPL Clojure insieme a Qt Jambi?

Ora la semplice chiamata di QApplication/processEvents dall'interno di un agente non è possibile, a meno che l'agente non funzioni esattamente nello stesso thread in cui sono stati creati i widget Qt. Mi ci sono voluti due giorni per capirlo e ho visto che gli altri hanno lo stesso problema/problema ma senza una soluzione. Così qui è il mio, in codice:

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

Fondamentalmente si utilizza la classe ScheduledThreadPoolExecutor al fine di eseguire tutte le Qt-code. È possibile utilizzare la macro con gui-thread per rendere più semplice la chiamata di funzioni all'interno del thread. Ciò rende possibile cambiare l'interfaccia utente Qt al volo, senza ricompilare.

+0

Sì, ho dovuto fare la stessa cosa. – levand

+0

Non so nulla di QT. Ma perché vuoi farlo? Clojure ha accesso a Swing, una struttura GUI molto potente e versatile. Ti stai connettendo a una GUI QT già installata? –

+0

QT è decisamente migliore di Swing in molti modi, comprese le prestazioni e il look and feel nativo. – levand

risposta

5

Se si vuole scherzare con i widget Qt dal REPL, QApplication/invokeLater o QApplication/invokeAndWait sono probabilmente ciò che si desidera. Puoi usarli insieme agli agenti. Dato questo:

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

Poi da un 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

Molto bello. Mi piace. Grazie! – MHOOO

3

ho scritto su come fare questo con SLIME on my blog (tedesco), così come on the Clojure mailing-list. Il trucco è definire le funzioni appropriate sul lato Emacs e dire a SLIME di usarle quando si fanno richieste. È importante sottolineare che questo ti libera dal dover fare incantesimi speciali quando invochi il codice Qt.

Citando me stesso:

Dato che stiamo parlando Lisp qui, in ogni caso, la soluzione sembrava essere ovvio: Hack SLIME! Quindi è quello che ho fatto io . Il codice riportato di seguito, quando viene rimosso nel tuo .emacs (in un punto in cui lo SLAMM è già completamente caricato), registra tre nuove funzioni Emacs-Lisp per l'uso interattivo. È loro possibile associare a qualsiasi tasti piace, o si può anche solo impostare il melma-inviare-through-QApplication variabile t dopo l'applicazione ha iniziato e non preoccuparsi di chiave attacchi a tutti. O deve rendere le tue richieste REPL e le valutazioni interattive C-M-x indirette tramite QCoreApplication/invokeAndWait.

Buon divertimento!

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