2 /******************************************************************************
4 * DESCRIPTION: QT display class
5 * COPYRIGHT : (C) 2008 Massimiliano Gubinelli
6 *******************************************************************************
7 * This software falls under the GNU general public license version 3 or later.
8 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
9 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
10 ******************************************************************************/
12 #include "iterator.hpp"
13 #include "dictionary.hpp"
15 #include "analyze.hpp"
17 #include "language.hpp"
18 #include "message.hpp"
19 #include <QDesktopWidget>
21 #include <QFileOpenEvent>
22 #include <QSocketNotifier>
23 #include <QSetIterator>
24 #include "QTMGuiHelper.hpp"
25 #include "QTMWidget.hpp"
26 #include "qt_renderer.hpp" // for the_qt_renderer
28 #include "tm_link.hpp" // for number_of_servers
30 #include "Scheme/object.hpp"
31 //#include "TeXmacs/server.hpp" // for get_server
33 #include "qt_simple_widget.hpp"
35 extern window (*get_current_window
) (void);
37 qt_gui_rep
* the_gui
= NULL
;
38 int nr_windows
= 0; // FIXME: fake variable, referenced in tm_server
40 time_t time_credit
; // interval to interrupt long redrawings
41 time_t timeout_time
; // new redraw interruption
42 time_t lapse
= 0; // optimization for delayed commands
44 // marshalling flags between update, needs_update and check_event.
45 bool updating
= false;
46 bool needing_update
= false;
48 /******************************************************************************
49 * Constructor and geometry
50 ******************************************************************************/
52 qt_gui_rep::qt_gui_rep(int &argc
, char **argv
):
55 (void) argc
; (void) argv
;
61 timeout_time
= texmacs_time () + time_credit
;
63 set_output_language (get_locale_language ());
64 gui_helper
= new QTMGuiHelper (this);
65 qApp
-> installEventFilter (gui_helper
);
66 updatetimer
= new QTimer (gui_helper
);
67 updatetimer
->setSingleShot (true);
68 QObject::connect ( updatetimer
, SIGNAL(timeout()),
69 gui_helper
, SLOT(doUpdate()));
70 // (void) default_font ();
73 /* important routines */
75 qt_gui_rep::get_extents (SI
& width
, SI
& height
) {
76 QDesktopWidget
* d
= QApplication::desktop();
77 int w
= d
->width(); // returns desktop width
78 int h
= d
->height(); // returns desktop height
79 width
= ((SI
) w
) * PIXEL
;
80 height
= ((SI
) h
) * PIXEL
;
84 qt_gui_rep::get_max_size (SI
& width
, SI
& height
) {
90 qt_gui_rep::~qt_gui_rep() {
92 // delete updatetimer; we do not need this given that gui_helper is the
93 // parent of updatetimer
96 /******************************************************************************
97 * interclient communication
98 ******************************************************************************/
101 qt_gui_rep::get_selection (string key
, tree
& t
, string
& s
) {
102 QClipboard
*cb
= QApplication::clipboard();
104 QClipboard::Mode mode
;
105 if (key
== "primary") {
106 owns
= cb
->ownsClipboard();
107 mode
= QClipboard::Clipboard
;
108 } else if (key
== "mouse" && cb
->supportsSelection()) {
109 owns
= cb
->ownsSelection();
110 mode
= QClipboard::Selection
;
116 if (selection_t
->contains (key
)) {
117 t
= copy (selection_t
[key
]);
118 s
= copy (selection_s
[key
]);
124 QString originalText
= cb
->text(mode
);
125 QByteArray buf
= originalText
.toAscii();
126 if (!(buf
.isEmpty())) {
127 s
<< string(buf
.constData(), buf
.size());
130 t
= tuple ("extern", s
);
135 qt_gui_rep::set_selection (string key
, tree t
, string s
) {
136 selection_t (key
)= copy (t
);
137 selection_s (key
)= copy (s
);
140 QClipboard
*cb
= QApplication::clipboard();
141 QClipboard::Mode mode
;
142 if (key
== "primary") {
143 mode
= QClipboard::Clipboard
;
144 } else if (key
== "mouse" && cb
->supportsSelection()) {
145 mode
= QClipboard::Selection
;
150 char *selection
= as_charp (s
);
151 cb
->setText(selection
,mode
);
152 tm_delete_array (selection
);
157 qt_gui_rep::clear_selection (string key
) {
158 selection_t
->reset (key
);
159 selection_s
->reset (key
);
162 /******************************************************************************
164 ******************************************************************************/
166 void qt_gui_rep::image_gc (string name
) { (void) name
; }
167 // FIXME: remove this unused function
168 void qt_gui_rep::set_mouse_pointer (string name
) { (void) name
; }
169 // FIXME: implement this function
170 void qt_gui_rep::set_mouse_pointer (string curs_name
, string mask_name
)
171 { (void) curs_name
; (void) mask_name
; } ;
173 /******************************************************************************
175 ******************************************************************************/
178 qt_gui_rep::show_wait_indicator (widget w
, string message
, string arg
) {
179 (void) w
; (void) message
; (void) arg
;
182 void (*the_interpose_handler
) (void) = NULL
;
184 void gui_interpose (void (*r
) (void)) { the_interpose_handler
= r
; }
187 qt_gui_rep::event_loop () {
188 QApplication
*app
= (QApplication
*) QApplication::instance();
195 /******************************************************************************
196 * Sockets notifications
197 ******************************************************************************/
199 static hashmap
<socket_notifier
,pointer
> read_notifiers
;
200 static hashmap
<socket_notifier
,pointer
> write_notifiers
;
203 qt_gui_rep::add_notifier (socket_notifier sn
)
205 QSocketNotifier
*qsn
;
207 if (DEBUG_QT
) cout
<< "ADD NOTIFIER " << sn
->fd
<< LF
;
209 // replace any already present notifier
211 remove_notifier (sn
);
213 // installs both a read and a write notifier
214 // (the texmacs interface does not specify enough its needs)
216 qsn
= new QSocketNotifier(sn
->fd
, QSocketNotifier::Read
, gui_helper
);
217 read_notifiers (sn
) = (pointer
) (qsn
);
218 QObject::connect( qsn
, SIGNAL(activated(int)),
219 gui_helper
, SLOT(doReadSocketNotification(int)) );
221 qsn
= new QSocketNotifier(sn
->fd
, QSocketNotifier::Write
, gui_helper
);
222 write_notifiers (sn
) = (pointer
) (qsn
);
223 QObject::connect( qsn
, SIGNAL(activated(int)),
224 gui_helper
, SLOT(doWriteSocketNotification(int)) );
228 qt_gui_rep::remove_notifier (socket_notifier sn
)
230 QSocketNotifier
*qsn
;
232 if (DEBUG_QT
) cout
<< "REMOVE NOTIFIER" << LF
;
234 // disable the (r/w) notifiers to prevent them to fire past this point
235 // and schedule them for deletion at the end of the current runloop
237 qsn
= (QSocketNotifier
*)read_notifiers
[sn
];
239 qsn
->setEnabled (false);
242 read_notifiers
->reset (sn
);
244 qsn
= (QSocketNotifier
*)write_notifiers (sn
);
246 qsn
->setEnabled (false);
249 write_notifiers
->reset (sn
);
253 qt_gui_rep::enable_notifier (socket_notifier sn
, bool flag
)
255 QSocketNotifier
*qsn
;
256 qsn
= (QSocketNotifier
*)read_notifiers (sn
);
257 if (qsn
) qsn
->setEnabled (flag
);
258 qsn
= (QSocketNotifier
*)write_notifiers (sn
);
259 if (qsn
) qsn
->setEnabled (flag
);
264 /******************************************************************************
266 ******************************************************************************/
268 //FIXME: the code below is almost a copy of the code in Guile/Scheme/object.cpp
269 // maybe some refactoring would be necessary
271 static array
<object
> delayed_queue
;
272 static array
<time_t> start_queue
;
275 exec_delayed (object cmd
) {
276 delayed_queue
<< cmd
;
277 start_queue
<< (((time_t) texmacs_time ()) - 1000000000);
278 lapse
= texmacs_time();
283 exec_delayed_pause (object cmd
) {
284 delayed_queue
<< cmd
;
285 start_queue
<< ((time_t) texmacs_time ());
286 lapse
= texmacs_time();
291 exec_pending_commands () {
292 array
<object
> a
= delayed_queue
;
293 array
<time_t> b
= start_queue
;
294 delayed_queue
= array
<object
> (0);
295 start_queue
= array
<time_t> (0);
297 for (i
=0; i
<n
; i
++) {
298 time_t now
= texmacs_time ();
299 if ((now
- b
[i
]) >= 0) {
300 object obj
= call (a
[i
]);
301 if (is_int (obj
) && (now
- b
[i
] < 1000000000)) {
302 time_t pause
= as_int (obj
);
303 //cout << "pause= " << obj << "\n";
304 delayed_queue
<< a
[i
];
305 start_queue
<< (now
+ pause
);
309 delayed_queue
<< a
[i
];
313 if (N(delayed_queue
)>0) {
314 lapse
= start_queue
[0];
315 int n
= N(start_queue
);
316 for (i
=1; i
<n
; i
++) {
317 if (lapse
> start_queue
[i
]) lapse
= start_queue
[i
];
323 clear_pending_commands () {
324 delayed_queue
= array
<object
> (0);
325 start_queue
= array
<time_t> (0);
328 /******************************************************************************
330 ******************************************************************************/
334 gui_open (int& argc
, char** argv
) {
336 // new QApplication (argc,argv); now in texmacs.cpp
337 the_gui
= tm_new
<qt_gui_rep
> (argc
, argv
);
342 // start the main loop
343 the_gui
->event_loop ();
348 // cleanly close the gui
349 ASSERT (the_gui
!= NULL
, "gui not yet open");
355 gui_root_extents (SI
& width
, SI
& height
) {
356 // get the screen size
357 the_gui
->get_extents (width
, height
);
359 cout
<< "gui_root_extents (" << width
<< "," << height
<< ")" << LF
;
363 gui_maximal_extents (SI
& width
, SI
& height
) {
364 // get the maximal size of a window (can be larger than the screen size)
365 the_gui
->get_max_size (width
, height
);
367 cout
<< "gui_maximal_extents (" << width
<< "," << height
<< ")" << LF
;
372 // update and redraw all windows (e.g. on change of output language)
373 // FIXME: add suitable code
377 /******************************************************************************
378 * QTMGuiHelper methods
379 ******************************************************************************/
382 QTMGuiHelper::doUpdate () {
383 // cout << "UPDATE " << texmacs_time () << LF;
388 QTMGuiHelper::eventFilter (QObject
*obj
, QEvent
*event
) {
389 if (event
->type() == QEvent::FileOpen
) {
390 QFileOpenEvent
*openEvent
= static_cast<QFileOpenEvent
*>(event
);
391 const char *s
= openEvent
->file().toAscii().constData();
392 //qDebug ("File Open Event %s", s);
393 call ( "texmacs-load-buffer", object(url_system (s
)),
394 object("generic"), object(1), object(false));
397 // standard event processing
398 return QObject::eventFilter(obj
, event
);
403 QTMGuiHelper::doWriteSocketNotification (int socket
) {
404 //cout << "WRITE SOCKET NOTIFICATION " << socket << " "<< texmacs_time () << LF;
405 iterator
<socket_notifier
> it
= iterate (write_notifiers
);
406 while (it
->busy ()) {
407 socket_notifier sn
= it
->next ();
408 if (sn
->fd
== socket
) {
410 the_gui
->process_socket_notification (sn
);
411 the_gui
->enable_notifier (sn
, false);
417 QTMGuiHelper::doReadSocketNotification (int socket
) {
418 //cout << "READ SOCKET NOTIFICATION " << socket << " "<< texmacs_time () << LF;
419 iterator
<socket_notifier
> it
= iterate (read_notifiers
);
420 while (it
->busy ()) {
421 socket_notifier sn
= it
->next ();
422 if (sn
->fd
== socket
) {
424 the_gui
->process_socket_notification (sn
);
425 the_gui
->enable_notifier (sn
, false);
431 /******************************************************************************
433 ******************************************************************************/
441 QP_SOCKET_NOTIFICATION
447 inline qp_type (qp_type_id sid2
= QP_NULL
): sid (sid2
) {}
448 inline qp_type (const qp_type
& s
): sid (s
.sid
) {}
449 inline qp_type
& operator = (qp_type s
) { sid
= s
.sid
; return *this; }
450 inline operator qp_type_id () { return sid
; }
451 inline bool operator == (qp_type_id sid2
) { return sid
== sid2
; }
452 inline bool operator != (qp_type_id sid2
) { return sid
!= sid2
; }
453 inline bool operator == (qp_type s
) { return sid
== s
.sid
; }
454 inline bool operator != (qp_type s
) { return sid
!= s
.sid
; }
455 inline friend ostream
& operator << (ostream
& out
, qp_type s
)
456 { return out
<< s
.sid
; }
459 class queued_event
: public pair
<qp_type
, blackbox
>
462 queued_event(qp_type _type
= qp_type(), blackbox _bb
= blackbox())
463 : pair
<qp_type
, blackbox
>(_type
, _bb
) {};
467 static array
<queued_event
> waiting_events
;
470 process_event (queued_event ev
) {
471 switch ((qp_type_id
) ev
.x1
) {
476 typedef triple
<simple_widget_rep
*, string
, time_t > T
;
477 T x
= open_box
<T
> (ev
.x2
) ;
478 x
.x1
-> handle_keypress (x
.x2
, x
.x3
) ;
481 case QP_KEYBOARD_FOCUS
:
483 typedef triple
<simple_widget_rep
*, bool, time_t > T
;
484 T x
= open_box
<T
> (ev
.x2
) ;
485 x
.x1
-> handle_keyboard_focus (x
.x2
, x
.x3
) ;
486 send_invalidate_all(x
.x1
);
487 //FIXME: the above invalidate is due to incorrect repainting when
488 // the widget loose the focus. I should investigate better
494 typedef quintuple
<string
, SI
, SI
, int, time_t > T1
;
495 typedef pair
<simple_widget_rep
*, T1
> T
;
496 T x
= open_box
<T
> (ev
.x2
) ;
497 x
.x1
-> handle_mouse (x
.x2
.x1
, x
.x2
.x2
, x
.x2
.x3
, x
.x2
.x4
, x
.x2
.x5
) ;
502 typedef triple
<simple_widget_rep
*, SI
, SI
> T
;
503 T x
= open_box
<T
> (ev
.x2
) ;
504 x
.x1
-> handle_notify_resize (x
.x2
, x
.x3
) ;
507 case QP_SOCKET_NOTIFICATION
:
509 socket_notifier sn
= open_box
<socket_notifier
> (ev
.x2
) ;
510 // cout << "QP_SOCKET_NOTIFICATION " << sn->fd << LF;
512 the_gui
->enable_notifier (sn
, true);
516 FAILED("Unexpected queued event");
522 qt_gui_rep::process_queued_events (int max
) {
524 array
<queued_event
> a
= waiting_events
, b
;
525 waiting_events
= array
<queued_event
> ();
529 //cout << "(" << n << " events)";
530 for (i
=0; i
<n
; i
++) {
531 //cout << (int)a[i].x1 << ",";
532 if ((max
< 0) || (i
<max
))
543 add_event(const queued_event
&ev
)
546 waiting_events
<< ev
;
547 needing_update
= true;
556 qt_gui_rep::process_keypress (simple_widget_rep
*wid
, string key
, time_t t
) {
557 typedef triple
<simple_widget_rep
*, string
, time_t > T
;
558 add_event( queued_event ( QP_KEYPRESS
, close_box
<T
> (T(wid
, key
, t
))));
559 // wid -> handle_keypress (key, t);
564 qt_gui_rep::process_keyboard_focus ( simple_widget_rep
*wid
, bool has_focus
,
566 typedef triple
<simple_widget_rep
*, bool, time_t > T
;
568 queued_event ( QP_KEYBOARD_FOCUS
, close_box
<T
> (T(wid
, has_focus
, t
))));
569 // wid -> handle_keyboard_focus (has_focus, t);
574 qt_gui_rep::process_mouse ( simple_widget_rep
*wid
, string kind
, SI x
, SI y
,
575 int mods
, time_t t
) {
576 typedef quintuple
<string
, SI
, SI
, int, time_t > T1
;
577 typedef pair
<simple_widget_rep
*, T1
> T
;
579 queued_event ( QP_MOUSE
, close_box
<T
> ( T (wid
, T1 (kind
, x
, y
, mods
, t
)))));
580 // wid -> handle_mouse (kind, x, y, mods, t);
585 qt_gui_rep::process_resize ( simple_widget_rep
*wid
, SI x
, SI y
) {
586 typedef triple
<simple_widget_rep
*, SI
, SI
> T
;
587 add_event( queued_event ( QP_RESIZE
, close_box
<T
> (T(wid
, x
, y
))));
588 // wid -> handle_notify_resize (x, y);
593 qt_gui_rep::process_socket_notification ( socket_notifier sn
) {
595 queued_event (QP_SOCKET_NOTIFICATION
, close_box
< socket_notifier
> (sn
)));
597 // enable_notifier (sn, true);
604 if (N(waiting_events
) == 0) {
605 qApp
->processEvents(QEventLoop::ExcludeSocketNotifiers
, 0);
606 // if (N(waiting_events) > 0) cout << "addins some events" << LF;
611 qt_gui_rep::check_event (int type
) {
612 //FIXME: add more types and refine, compare with X11 version.
614 // cout << "."; cout.flush();
617 case INTERRUPT_EVENT
:
618 if (interrupted
) return true;
620 time_t now
= texmacs_time ();
621 if (now
- timeout_time
< 0) return false;
623 timeout_time
= now
+ (100 / (N(waiting_events
) + 1));
624 interrupted
= (N(waiting_events
) > 0);
625 //if (interrupted) cout << "INTERRUPT "
626 // << now << "------------------" << LF;
629 case INTERRUPTED_EVENT
:
638 qt_gui_rep::update () {
639 // this is called by doUpdate, which in turns is fired by a timer activated in
640 // needs_update, and ensuring that interpose_handler is run during a pass in
641 // the eventloop afterwards we reactivate the timer with a pause
648 int count_events
= 0;
649 int max_proc_events
= 2;
652 time_t now
= texmacs_time();
653 needing_update
= false;
657 if (N(waiting_events
) > 0) {
658 //cout << "PROCESS QUEUED EVENTS START..."; cout.flush();
659 process_queued_events (1);
660 //cout << "AND END" << LF;
663 //cout << "TYPESET START..."; cout.flush();
664 if (lapse
<= now
) exec_pending_commands();
665 if (the_interpose_handler
) the_interpose_handler();
666 //cout << "AND END" << LF;
670 if ((count_events
> max_proc_events
) || (N(waiting_events
) == 0)) {
671 //if (count_events > max_proc_events) cout << "PARTIAL REDRAW" << LF;
674 // repaint invalid regions
676 timeout_time
= texmacs_time() + time_credit
/(N(waiting_events
)+1);
678 //cout << "REPAINT START..."; cout.flush();
680 QSetIterator
<QTMWidget
*> i(QTMWidget::all_widgets
);
681 while (i
.hasNext()) {
682 QTMWidget
*w
= i
.next();
683 w
->repaint_invalid_regions();
686 if (interrupted
) needing_update
= true;
687 //cout << "AND END" << LF;
690 } while ((N(waiting_events
)>0) || needing_update
);
696 if (nr_windows
== 0) {
701 time_t delay
= lapse
- texmacs_time();
702 if (delay
> 1000/6) delay
= 1000/6;
703 if (delay
< 0) delay
= 0;
704 if (needing_update
|| interrupted
) delay
= 0;
705 updatetimer
->start (delay
);
707 // FIXME: we need to ensure that the interpose_handler is run at regular
708 // intervals (1/6th of sec) so that informations on the footbar are
709 // updated. (this should be better handled by promoting code in
710 // tm_editor::apply_changes (which is activated only after idle
711 // periods) at the level of delayed commands in the gui.
712 // The interval cannot be too small to keep CPU usage low in idle state
717 /******************************************************************************
719 ******************************************************************************/
722 set_default_font (string name
) {
724 // set the name of the default font
725 // this is ignored since Qt handles fonts for the widgets
729 get_default_font (bool tt
) {
731 // get the default font or monospaced font (if tt is true)
733 // return a null font since this function is not called in the Qt port.
734 if (DEBUG_QT
) cout
<< "get_default_font(): SHOULD NOT BE CALLED\n";
736 //return tex_font (this, "ecrm", 10, 300, 0);
739 // load the metric and glyphs of a system font
740 // you are not obliged to provide any system fonts
743 load_system_font (string family
, int size
, int dpi
,
744 font_metric
& fnm
, font_glyphs
& fng
)
746 (void) family
; (void) size
; (void) dpi
; (void) fnm
; (void) fng
;
747 if (DEBUG_QT
) cout
<< "load_system_font(): SHOULD NOT BE CALLED\n";
750 /******************************************************************************
752 ******************************************************************************/
755 set_selection (string key
, tree t
, string s
) {
756 // Copy a selection 't' with string equivalent 's' to the clipboard 'cb'
757 // Returns true on success
758 return the_gui
->set_selection (key
, t
, s
);
762 get_selection (string key
, tree
& t
, string
& s
) {
763 // Retrieve the selection 't' with string equivalent 's' from clipboard 'cb'
764 // Returns true on success; sets t to (extern s) for external selections
765 return the_gui
->get_selection (key
, t
, s
);
769 clear_selection (string key
) {
770 // Clear the selection on clipboard 'cb'
771 the_gui
->clear_selection (key
);
774 /******************************************************************************
776 ******************************************************************************/
782 QApplication::beep();
787 // 0 ms - call immediately when all other events have
789 //cout << "needs_update" << LF;
791 needing_update
= true;
794 the_gui
->updatetimer
->start (0);
799 check_event (int type
) {
800 // Check whether an event of one of the above types has occurred;
801 // we check for keyboard events while repainting windows
802 return the_gui
->check_event(type
);
805 void image_gc (string name
) {
806 // Garbage collect images of a given name (may use wildcards)
807 // This routine only needs to be implemented if you use your own image cache
808 the_qt_renderer()->image_gc(name
);
812 show_help_balloon (widget balloon
, SI x
, SI y
) {
813 // Display a help balloon at position (x, y); the help balloon should
814 // disappear as soon as the user presses a key or moves the mouse
815 (void) balloon
; (void) x
; (void) y
;
819 show_wait_indicator (widget base
, string message
, string argument
) {
820 // Display a wait indicator with a message and an optional argument
821 // The indicator might for instance be displayed at the center of
822 // the base widget which triggered the lengthy operation;
823 // the indicator should be removed if the message is empty
824 the_gui
->show_wait_indicator(base
,message
,argument
);
827 font
x_font (string family
, int size
, int dpi
)
829 (void) family
; (void) size
; (void) dpi
;
830 if (DEBUG_QT
) cout
<< "x_font(): SHOULD NOT BE CALLED\n";