1 
2 /******************************************************************************
3  * MODULE     : qt_gui.cpp
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  ******************************************************************************/
11 
12 #include <locale.h>
13 
14 #include "convert.hpp"
15 #include "iterator.hpp"
16 #include "dictionary.hpp"
17 #include "file.hpp" // added for copy_as_graphics
18 #include "analyze.hpp"
19 #include "language.hpp"
20 #include "message.hpp"
21 #include "scheme.hpp"
22 #include "tm_window.hpp"
23 #include "new_window.hpp"
24 
25 #include "qt_gui.hpp"
26 #include "qt_utilities.hpp"
27 #include "qt_renderer.hpp" // for the_qt_renderer
28 #include "qt_simple_widget.hpp"
29 #include "qt_window_widget.hpp"
30 
31 #include <QDesktopWidget>
32 #include <QClipboard>
33 #include <QBuffer>
34 #include <QFileOpenEvent>
35 #include <QStackedLayout>
36 #include <QLabel>
37 #include <QSocketNotifier>
38 #include <QSetIterator>
39 #include <QTranslator>
40 #include <QLocale>
41 #include <QMimeData>
42 #include <QByteArray>
43 #include <QCoreApplication>
44 #include <QLibraryInfo>
45 #include <QImage>
46 #include <QUrl>
47 
48 #include "QTMGuiHelper.hpp"
49 #include "QTMWidget.hpp"
50 #include "QTMWindow.hpp"
51 #include "QTMApplication.hpp"
52 
53 #ifdef MACOSX_EXTENSIONS
54 #include "MacOS/mac_utilities.h"
55 #endif
56 
57 qt_gui_rep* the_gui = NULL;
58 int nr_windows = 0; // FIXME: fake variable, referenced in tm_server
59 
60 /******************************************************************************
61 * FIXME: temporary hack by Joris
62 * Additional wait mechanism to keep CPU usage down
63 ******************************************************************************/
64 
65 #ifdef QT_CPU_FIX
66 #include <unistd.h>
67 
68 static double tm_count= 0.0;
69 static double tm_delay= 1.0;
70 
71 void
tm_wake_up()72 tm_wake_up () {
73   tm_delay= 1.0;
74 }
75 
76 void
tm_sleep()77 tm_sleep () {
78   //tm_count += 1.0;
79   tm_delay= 1.0001 * tm_delay;
80   if (tm_delay > 250000.0) tm_delay= 250000;
81   //cout << tm_count << ", " << tm_delay << "\r";
82   //cout.flush ();
83   usleep ((int) floor (tm_delay));
84 }
85 #endif
86 
87 /******************************************************************************
88 * Constructor and geometry
89 ******************************************************************************/
90 
qt_gui_rep(int & argc,char ** argv)91 qt_gui_rep::qt_gui_rep (int &argc, char **argv):
92 interrupted (false), waitWindow (NULL), popup_wid_time (0), q_translator (0),
93 time_credit (100), do_check_events (false), updating (false),
94 needing_update (false)
95 {
96   (void) argc; (void) argv;
97     // argc = argc2;
98     // argv = argv2;
99 
100   interrupted  = false;
101   time_credit  = 100;
102   timeout_time = texmacs_time () + time_credit;
103 
104     //waitDialog = NULL;
105 
106   gui_helper = new QTMGuiHelper (this);
107   qApp->installEventFilter (gui_helper);
108 
109 #ifdef QT_MAC_USE_COCOA
110     //HACK: this filter is needed to overcome a bug in Qt/Cocoa
111   extern void mac_install_filter(); // defined in src/Plugins/MacOS/mac_app.mm
112   mac_install_filter();
113 #endif
114 
115   set_output_language (get_locale_language ());
116   refresh_language();
117 
118   updatetimer = new QTimer (gui_helper);
119   updatetimer->setSingleShot (true);
120   QObject::connect (updatetimer, SIGNAL (timeout()),
121                     gui_helper, SLOT (doUpdate()));
122     //  (void) default_font ();
123 }
124 
125 /* important routines */
126 void
get_extents(SI & width,SI & height)127 qt_gui_rep::get_extents (SI& width, SI& height) {
128   coord2 size = from_qsize (QApplication::desktop()->size());
129   width  = size.x1;
130   height = size.x2;
131 }
132 
133 void
get_max_size(SI & width,SI & height)134 qt_gui_rep::get_max_size (SI& width, SI& height) {
135   width = 8000 * PIXEL;
136   height = 6000 * PIXEL;
137 }
138 
~qt_gui_rep()139 qt_gui_rep::~qt_gui_rep()  {
140   delete gui_helper;
141 
142   while (waitDialogs.count()) {
143     waitDialogs.last()->deleteLater();
144     waitDialogs.removeLast();
145   }
146   if (waitWindow) delete waitWindow;
147 
148     // delete updatetimer; we do not need this given that gui_helper is the
149     // parent of updatetimer
150 }
151 
152 
153 /******************************************************************************
154  * interclient communication
155  ******************************************************************************/
156 
157 bool
get_selection(string key,tree & t,string & s,string format)158 qt_gui_rep::get_selection (string key, tree& t, string& s, string format) {
159   QClipboard *cb = QApplication::clipboard ();
160   QClipboard::Mode mode = QClipboard::Clipboard;
161   if (key == "primary" || (key == "mouse" && cb->supportsSelection ()))
162     if (key == "mouse") mode = QClipboard::Selection;
163 
164   QString originalText = cb->text (mode);
165   const QMimeData *md = cb->mimeData (mode);
166   QByteArray buf;
167   string input_format;
168 
169   s = "";
170   t = "none";
171     // Knowing when we owns (or not) the content is not clear
172   bool owns = (format != "temp" && format != "wrapbuf" && key != "primary") &&
173   !(key == "mouse" && cb->supportsSelection ());
174 
175   if (!owns && md->hasFormat ("application/x-texmacs-pid")) {
176     buf = md->data ("application/x-texmacs-pid");
177     if (!(buf.isEmpty())) {
178       owns = string (buf.constData(), buf.size())
179       == as_string (QCoreApplication::applicationPid ());
180     }
181   }
182 
183   if (owns) {
184     if (!selection_t->contains (key)) return false;
185     t = copy (selection_t [key]);
186     s = copy (selection_s [key]);
187     return true;
188   }
189 
190   if (format == "default") {
191     if (md->hasFormat ("application/x-texmacs-clipboard")) {
192       buf = md->data ("application/x-texmacs-clipboard");
193       input_format = "texmacs-snippet";
194     }
195     else if (md->hasImage ()) {
196       if (md->hasUrls ()) {
197         QList<QUrl> l= md->urls ();
198         if (l.size () == 1) {
199           s= from_qstring (l[0].toString ());
200           input_format = "linked-picture";
201         }
202       }
203       else {
204         QBuffer qbuf(&buf);
205         QImage image= qvariant_cast<QImage> (md->imageData());
206         qbuf.open (QIODevice::WriteOnly);
207         image.save (&qbuf, "PNG");
208         input_format = "picture";
209       }
210     }
211     else if (md->hasHtml ()) {
212       buf = md->html().toUtf8 ();
213       input_format = "html-snippet";
214     }
215     else if (md->hasFormat ("text/plain;charset=utf8")) {
216       buf = md->data ("text/plain;charset=utf8");
217       input_format = "verbatim-snippet";
218     }
219     else {
220       buf = md->text().toUtf8 ();
221       input_format = "verbatim-snippet";
222     }
223   }
224   else if (format == "verbatim"
225            && (get_preference ("verbatim->texmacs:encoding") == "utf-8" ||
226                get_preference ("verbatim->texmacs:encoding") == "auto"  ))
227     buf = md->text().toUtf8 ();
228   else {
229     if (md->hasFormat ("plain/text")) buf = md->data ("plain/text").data();
230     else buf = md->text().toUtf8 ();
231   }
232   if (!(buf.isEmpty())) s << string (buf.constData(), buf.size());
233   if (input_format == "html-snippet" && seems_buggy_html_paste (s))
234     s = correct_buggy_html_paste (s);
235   if (input_format != "picture" && seems_buggy_paste (s))
236     s = correct_buggy_paste (s);
237   if (input_format != "" &&
238       input_format != "picture" &&
239       input_format != "linked-picture")
240     s = as_string (call ("convert", s, input_format, "texmacs-snippet"));
241   if (input_format == "html-snippet") {
242     tree t = as_tree (call ("convert", s, "texmacs-snippet", "texmacs-tree"));
243     t = default_with_simplify (t);
244     s = as_string (call ("convert", t, "texmacs-tree", "texmacs-snippet"));
245   }
246   if (input_format == "picture") {
247     tree t (IMAGE);
248     QSize size= qvariant_cast<QImage>(md->imageData()).size ();
249     string w= as_string (size.width ()) * "px";
250     string h= as_string (size.height ()) * "px";
251     t << tuple (tree (RAW_DATA, s), "png") << w << h << "" << "";
252     s= as_string (call ("convert", t, "texmacs-tree", "texmacs-snippet"));
253   }
254   if (input_format == "linked-picture") {
255     tree im (IMAGE, s, "", "", "", "");
256     s= as_string (call ("convert", im, "texmacs-tree", "texmacs-snippet"));
257   }
258   t = tuple ("extern", s);
259   return true;
260 }
261 
262 bool
set_selection(string key,tree t,string s,string sv,string sh,string format)263 qt_gui_rep::set_selection (string key, tree t,
264                            string s, string sv, string sh, string format) {
265   selection_t (key)= copy (t);
266   selection_s (key)= copy (s);
267 
268   QClipboard *cb = QApplication::clipboard ();
269   QClipboard::Mode mode = QClipboard::Clipboard;
270   if (key == "primary");
271   else if (key == "mouse" && cb->supportsSelection())
272     mode = QClipboard::Selection;
273   else return true;
274   cb->clear (mode);
275 
276   c_string selection (s);
277   cb->setText (QString::fromAscii (selection), mode);
278   QMimeData *md = new QMimeData;
279 
280   if (format == "verbatim" || format == "default") {
281     if (format == "default") {
282       md->setData ("application/x-texmacs-clipboard", (char*)selection);
283 
284       QString pid_str;
285       pid_str.setNum (QCoreApplication::applicationPid ());
286       md->setData ("application/x-texmacs-pid", pid_str.toAscii());
287 
288       (void) sh;
289         //selection = c_string (sh);
290         //md->setHtml (selection);
291         //tm_delete_array (selection);
292 
293       selection = c_string (sv);
294     }
295 
296     string enc = get_preference ("texmacs->verbatim:encoding");
297     if (enc == "auto")
298       enc = get_locale_charset ();
299 
300     if (enc == "utf-8" || enc == "UTF-8")
301       md->setText (QString::fromUtf8 (selection));
302     else if (enc == "iso-8859-1" || enc == "ISO-8859-1")
303       md->setText (QString::fromLatin1 (selection));
304     else
305       md->setText (QString::fromAscii (selection));
306   }
307   else
308     md->setText (QString::fromAscii (selection));
309   cb->setMimeData (md, mode);
310     // according to the docs, ownership of mimedata is transferred to clipboard
311     // so no memory leak here
312   return true;
313 }
314 
315 void
clear_selection(string key)316 qt_gui_rep::clear_selection (string key) {
317   selection_t->reset (key);
318   selection_s->reset (key);
319 
320   QClipboard *cb = QApplication::clipboard();
321   QClipboard::Mode mode = QClipboard::Clipboard;
322   if (key == "primary");
323   else if (key == "mouse" && cb->supportsSelection())
324     mode = QClipboard::Selection;
325   else return;
326 
327   bool owns = false;
328   const QMimeData *md = cb->mimeData (mode);
329   if (md) owns = md->hasFormat ("application/x-texmacs-clipboard");
330   if (owns) cb->clear (mode);
331 }
332 
333 /******************************************************************************
334  * Miscellaneous
335  ******************************************************************************/
336 
set_mouse_pointer(string name)337 void qt_gui_rep::set_mouse_pointer (string name) { (void) name; }
338   // FIXME: implement this function
set_mouse_pointer(string curs_name,string mask_name)339 void qt_gui_rep::set_mouse_pointer (string curs_name, string mask_name)
340 { (void) curs_name; (void) mask_name; } ;
341 
342 /******************************************************************************
343  * Main loop
344  ******************************************************************************/
345 
346 void
show_wait_indicator(widget w,string message,string arg)347 qt_gui_rep::show_wait_indicator (widget w, string message, string arg)  {
348   if (DEBUG_QT)
349     debug_qt << "show_wait_indicator \"" << message << "\"\"" << arg << "\"\n";
350 
351   qt_window_widget_rep* wid = static_cast<qt_window_widget_rep*> (w.rep);
352 
353     // we move the texmacs window during an operation.
354     // We need to disable updates of the window to avoid erasure of the canvas
355     // area
356     //  wid->wid->setUpdatesEnabled (false);
357 
358     //FIXME: we must center the wait widget wrt the current active window
359 
360   if (!waitWindow) {
361     waitWindow = new QWidget (wid->qwid->window());
362     waitWindow->setWindowFlags (Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
363     QStackedLayout *layout = new QStackedLayout();
364     layout->setSizeConstraint (QLayout::SetFixedSize);
365     waitWindow->setLayout (layout);
366   }
367 
368   if (waitDialogs.count()) {
369     waitWindow->layout()->removeWidget (waitDialogs.last());
370   }
371 
372   if (N(message)) {
373       // push a new wait message in the list
374 
375     if (arg != "") message = message * " " * arg * "...";
376 
377     QLabel* lab = new  QLabel();
378     lab->setFocusPolicy (Qt::NoFocus);
379     lab->setMargin (15);
380     lab->setText (to_qstring (message));
381     waitDialogs << lab;
382   } else {
383       // pop the next wait message from the list
384     if (waitDialogs.count()) {
385       waitDialogs.last()->deleteLater();
386       waitDialogs.removeLast();
387     }
388   }
389 
390   if (waitDialogs.count()) {
391     waitWindow->layout()->addWidget (waitDialogs.last());
392     waitWindow->updateGeometry();
393     {
394       QSize sz = waitWindow->geometry().size();
395       QRect rect = QRect (QPoint (0,0),sz);
396         //HACK:
397         // processEvents is needed to let Qt update windows coordinates in the case
398       qApp->processEvents (QEventLoop::ExcludeUserInputEvents);
399         //ENDHACK
400       QPoint pt = wid->qwid->window()->geometry().center();
401       rect.moveCenter (pt);
402       waitWindow->move (rect.topLeft());
403 
404     }
405     waitWindow->show();
406     qApp->processEvents (QEventLoop::ExcludeUserInputEvents);
407     waitWindow->repaint();
408   } else {
409     waitWindow->close();
410   }
411   qApp->processEvents();
412   QApplication::flush();
413 
414   wid->qwid->activateWindow ();
415   send_keyboard_focus (wid);
416     // next time we do update the dialog will disappear
417   need_update();
418 }
419 
420 void (*the_interpose_handler) (void) = NULL;
421 
gui_interpose(void (* r)(void))422 void gui_interpose (void (*r) (void)) { the_interpose_handler = r; }
423 
424 void
event_loop()425 qt_gui_rep::event_loop () {
426   QTMApplication* app = static_cast<QTMApplication*>(QApplication::instance());
427   update();
428     //need_update();
429   app->exec();
430 }
431 
432 
433 /******************************************************************************
434  * Main routines
435  ******************************************************************************/
436 
437 void
gui_open(int & argc,char ** argv)438 gui_open (int& argc, char** argv) {
439     // start the gui
440     // new QApplication (argc,argv); now in texmacs.cpp
441   the_gui = tm_new<qt_gui_rep> (argc, argv);
442 
443 #ifdef MACOSX_EXTENSIONS
444   mac_begin_remote();
445 #endif
446 }
447 
448 void
gui_start_loop()449 gui_start_loop () {
450     // start the main loop
451   the_gui->event_loop ();
452 }
453 
454 void
gui_close()455 gui_close () {
456     // cleanly close the gui
457   ASSERT (the_gui != NULL, "gui not yet open");
458   tm_delete (the_gui);
459   the_gui = NULL;
460 
461 #ifdef MACOSX_EXTENSIONS
462   mac_end_remote();
463 #endif
464 }
465 
466 void
gui_root_extents(SI & width,SI & height)467 gui_root_extents (SI& width, SI& height) {
468     // get the screen size
469   the_gui->get_extents (width, height);
470 }
471 
472 void
gui_maximal_extents(SI & width,SI & height)473 gui_maximal_extents (SI& width, SI& height) {
474     // get the maximal size of a window (can be larger than the screen size)
475   the_gui->get_max_size (width, height);
476 }
477 
478 void
gui_refresh()479 gui_refresh () {
480   the_gui->refresh_language();
481 }
482 
483 
484 /******************************************************************************
485  * Queued processing
486  ******************************************************************************/
487 
488 /*!
489  We process a maximum of max events. There are two kind of events: those
490  which need a pass on interpose_handler just after and the others. We count
491  only the first kind of events. In update() we call this function with
492  max = 1 so that only one of these "sensible" events is handled. Otherwise
493  updating the internal TeXmacs structure becomes very slow. This can be
494  considered a limitation of the current implementation of interpose_handler
495  Likewise this function is just a hack to get things working properly.
496  */
497 
498 void
process_queued_events(int max)499 qt_gui_rep::process_queued_events (int max) {
500   int count = 0;
501   while (max < 0 || count < max)  {
502     const queued_event& ev = waiting_events.next();
503     if (ev.x1 == qp_type::QP_NULL) break;
504 #ifdef QT_CPU_FIX
505     if (ev.x1 != qp_type::QP_NULL &&
506 	ev.x1 != qp_type::QP_SOCKET_NOTIFICATION &&
507 	ev.x1 != qp_type::QP_DELAYED_COMMANDS)
508       tm_wake_up ();
509 #endif
510     switch (ev.x1) {
511       case qp_type::QP_NULL :
512         break;
513       case qp_type::QP_KEYPRESS :
514       {
515         typedef triple<widget, string, time_t > T;
516         T x = open_box <T> (ev.x2);
517         if (!is_nil (x.x1))
518           concrete_simple_widget (x.x1)->handle_keypress (x.x2, x.x3);
519       }
520         break;
521       case qp_type::QP_KEYBOARD_FOCUS :
522       {
523         typedef triple<widget, bool, time_t > T;
524         T x = open_box <T> (ev.x2);
525         if (!is_nil (x.x1))
526           concrete_simple_widget (x.x1)->handle_keyboard_focus (x.x2, x.x3);
527       }
528         break;
529       case qp_type::QP_MOUSE :
530       {
531         typedef quintuple<string, SI, SI, int, time_t > T1;
532         typedef pair<widget, T1> T;
533         T x = open_box <T> (ev.x2);
534         if (!is_nil (x.x1))
535           concrete_simple_widget (x.x1)->handle_mouse (x.x2.x1, x.x2.x2,
536                                                        x.x2.x3, x.x2.x4, x.x2.x5);
537       }
538         break;
539       case qp_type::QP_RESIZE :
540       {
541         typedef triple<widget, SI, SI > T;
542         T x = open_box <T> (ev.x2);
543         if (!is_nil (x.x1))
544           concrete_simple_widget (x.x1)->handle_notify_resize (x.x2, x.x3) ;
545       }
546         break;
547       case qp_type::QP_COMMAND :
548       {
549         command cmd = open_box <command> (ev.x2) ;
550         cmd->apply();
551       }
552         break;
553       case qp_type::QP_COMMAND_ARGS :
554       {
555         typedef pair<command, object> T;
556         T x = open_box <T> (ev.x2);
557         x.x1->apply (x.x2);
558       }
559         break;
560       case qp_type::QP_DELAYED_COMMANDS :
561       {
562         delayed_commands.exec_pending();
563       }
564         break;
565 
566       default:
567         FAILED ("Unexpected queued event");
568     }
569     switch (ev.x1) {
570       case qp_type::QP_COMMAND:
571       case qp_type::QP_COMMAND_ARGS:
572       case qp_type::QP_RESIZE:
573       case qp_type::QP_DELAYED_COMMANDS:
574         break;
575       default:
576         count++;
577         break;
578     }
579   }
580 }
581 
582 void
process_keypress(qt_simple_widget_rep * wid,string key,time_t t)583 qt_gui_rep::process_keypress (qt_simple_widget_rep *wid, string key, time_t t) {
584   typedef triple<widget, string, time_t > T;
585   add_event (queued_event (qp_type::QP_KEYPRESS,
586                            close_box<T> (T (wid, key, t))));
587 }
588 
589 void
process_keyboard_focus(qt_simple_widget_rep * wid,bool has_focus,time_t t)590 qt_gui_rep::process_keyboard_focus (qt_simple_widget_rep *wid, bool has_focus,
591                                     time_t t ) {
592   typedef triple<widget, bool, time_t > T;
593   add_event (queued_event (qp_type::QP_KEYBOARD_FOCUS,
594                            close_box<T> (T (wid, has_focus, t))));
595 }
596 
597 void
process_mouse(qt_simple_widget_rep * wid,string kind,SI x,SI y,int mods,time_t t)598 qt_gui_rep::process_mouse (qt_simple_widget_rep *wid, string kind, SI x, SI y,
599                            int mods, time_t t ) {
600   typedef quintuple<string, SI, SI, int, time_t > T1;
601   typedef pair<widget, T1> T;
602   add_event (queued_event (qp_type::QP_MOUSE,
603                            close_box<T> ( T (wid, T1 (kind, x, y, mods, t)))));
604 }
605 
606 void
process_resize(qt_simple_widget_rep * wid,SI x,SI y)607 qt_gui_rep::process_resize (qt_simple_widget_rep *wid, SI x, SI y ) {
608   typedef triple<widget, SI, SI > T;
609   add_event (queued_event (qp_type::QP_RESIZE, close_box<T> (T (wid, x, y))));
610 }
611 
612 void
process_command(command _cmd)613 qt_gui_rep::process_command (command _cmd) {
614   add_event (queued_event (qp_type::QP_COMMAND, close_box<command> (_cmd)));
615 }
616 
617 void
process_command(command _cmd,object _args)618 qt_gui_rep::process_command (command _cmd, object _args) {
619   typedef pair<command, object > T;
620   add_event (queued_event (qp_type::QP_COMMAND_ARGS,
621                            close_box<T> (T (_cmd,_args))));
622 }
623 
624 void
process_delayed_commands()625 qt_gui_rep::process_delayed_commands () {
626   add_event (queued_event (qp_type::QP_DELAYED_COMMANDS, blackbox()));
627 }
628 
629 /*!
630   FIXME: add more types and refine, compare with X11 version.
631  */
632 bool
check_event(int type)633 qt_gui_rep::check_event (int type) {
634     // do not interrupt if not updating (e.g. while painting the icons in menus)
635   if (!updating || !do_check_events) return false;
636 
637   switch (type) {
638     case INTERRUPT_EVENT:
639       if (interrupted) return true;
640       else {
641         time_t now = texmacs_time ();
642         if (now - timeout_time < 0) return false;
643         timeout_time = now + time_credit;
644         interrupted  = !waiting_events.is_empty();
645         return interrupted;
646       }
647     case INTERRUPTED_EVENT:
648       return interrupted;
649     default:
650       return false;
651   }
652 }
653 
654 void
set_check_events(bool enable_check)655 qt_gui_rep::set_check_events (bool enable_check) {
656   do_check_events = enable_check;
657 }
658 
659 void
add_event(const queued_event & ev)660 qt_gui_rep::add_event (const queued_event& ev) {
661   waiting_events.append (ev);
662   if (updating) {
663     needing_update = true;
664   } else {
665     need_update();
666       // NOTE: we cannot update now since sometimes this seems to give problems
667       // to the update of the window size after a resize. In that situation
668       // sometimes when the window receives focus again, update will be called
669       // for the focus_in event and interpose_handler is run which sends a
670       // slot_extent message to the widget causing a wrong resize of the window.
671       // This seems to cure the problem.
672   }
673 }
674 
675 
676 /*!
677  This is called by doUpdate(), which in turn is fired by a timer activated in
678  needs_update(), and ensuring that interpose_handler() is run during a pass in
679  the event loop after we reactivate the timer with a pause (see FIXME below).
680  */
681 
682 void
update()683 qt_gui_rep::update () {
684 #ifdef QT_CPU_FIX
685   int std_delay= 1;
686   tm_sleep ();
687 #else
688   int std_delay= 1000 / 6;
689 #endif
690 
691   if (updating) {
692     cout << "NESTED UPDATING: This should not happen" << LF;
693     need_update();
694     return;
695   }
696 
697     // cout << "<" << texmacs_time() << " " << N(delayed_queue) << " ";
698 
699   updatetimer->stop();
700   updating = true;
701 
702   static int count_events    = 0;
703   static int max_proc_events = 10;
704 
705   time_t     now = texmacs_time();
706   needing_update = false;
707   time_credit    = 100 / (waiting_events.size() + 1);
708 
709     // 1.
710     // Check if a wait dialog is active and in that case remove it.
711     // If we are here then the long operation has finished.
712 
713   if (waitDialogs.count()) {
714     waitWindow->layout()->removeWidget (waitDialogs.last());
715     waitWindow->close();
716     while (waitDialogs.count()) {
717       waitDialogs.last()->deleteLater();
718       waitDialogs.removeLast();
719     }
720   }
721 
722   if (popup_wid_time > 0 && now > popup_wid_time) {
723     popup_wid_time = 0;
724     _popup_wid->send (SLOT_VISIBILITY, close_box<bool> (true));
725   }
726 
727     // 2.
728     // Manage delayed commands
729 
730   if (delayed_commands.must_wait (now))
731     process_delayed_commands();
732 
733     // 3.
734     // If there are pending events in the private queue process them until the
735     // limit in processed events is reached.
736     // If there are no events or the limit is reached then proceed to a redraw.
737 
738   if (waiting_events.size() == 0) {
739       // If there are no waiting events call the interpose handler at least once
740     if (the_interpose_handler) the_interpose_handler();
741   } else while (waiting_events.size() > 0 && count_events < max_proc_events) {
742     process_queued_events (1);
743     count_events++;
744     if (the_interpose_handler) the_interpose_handler();
745   }
746     // Repaint invalid regions and redraw
747   count_events = 0;
748 
749   interrupted  = false;
750   timeout_time = texmacs_time() + time_credit;
751 
752   QSetIterator<QTMWidget*> i (QTMWidget::all_widgets);
753   while (i.hasNext()) {
754     QTMWidget* w = i.next();
755     if (w->isVisible()) w->repaint_invalid_regions();
756   }
757 
758   if (waiting_events.size() > 0) needing_update = true;
759   if (interrupted)               needing_update = true;
760   if (nr_windows == 0)           qApp->quit();
761 
762   time_t delay = delayed_commands.lapse - texmacs_time();
763   if (needing_update) delay = 0;
764   else                delay = max (0, min (std_delay, delay));
765 
766   updatetimer->start (delay);
767   updating = false;
768 
769     // FIXME: we need to ensure that the interpose_handler is run at regular
770     //        intervals (1/6th of sec) so that informations on the footbar are
771     //        updated. (this should be better handled by promoting code in
772     //        tm_editor::apply_changes (which is activated only after idle
773     //        periods) at the level of delayed commands in the gui.
774     //        The interval cannot be too small to keep CPU usage low in idle state
775 }
776 
777 void
force_update()778 qt_gui_rep::force_update() {
779   if (updating) needing_update = true;
780   else          update();
781 }
782 
783 void
need_update()784 qt_gui_rep::need_update () {
785   if (updating) needing_update = true;
786   else          updatetimer->start (0);
787     // 0 ms - call immediately when all other events have been processed
788 }
789 
needs_update()790 void needs_update () {
791   the_gui->need_update();
792 }
793 
794 /*! Called upon change of output language.
795 
796  We currently emit a signal which forces every QTMAction to change his text
797  according to the new language, but the preferred Qt way seems to use
798  LanguageChange events (these are triggered upon installation of QTranslators)
799  */
800 void
refresh_language()801 qt_gui_rep::refresh_language() {
802   /* FIXME: why is this here? We don't use QTranslators...
803   QTranslator* qtr = new QTranslator();
804   if (qtr->load ("qt_" +
805                  QLocale (to_qstring
806                            (language_to_locale (get_output_language()))).name(),
807                  QLibraryInfo::location (QLibraryInfo::TranslationsPath))) {
808     if (q_translator)
809       qApp->removeTranslator (q_translator);
810     qApp->installTranslator (qtr);
811     q_translator = qtr;
812   } else {
813     delete qtr;
814   }
815    */
816   gui_helper->doRefresh();
817 }
818 
819 /*! Display a popup help balloon (i.e. a tooltip) at window coordinates x, y
820 
821  We use a dedicated wrapper QWidget which handles mouse events: as soon as the
822  mouse is moved out of we hide it.
823  Problem: the widget need not appear below the mouse pointer, thus making it
824  impossible to access links or widgets inside it.
825  Solution?? as soon as the mouse moves (out of the widget), start a timer,
826  giving enough time to the user to move (back) into the widget, then abort the
827  close operation if he gets there.
828  */
829 void
show_help_balloon(widget wid,SI x,SI y)830 qt_gui_rep::show_help_balloon (widget wid, SI x, SI y) {
831   if (popup_wid_time > 0) return;
832 
833   _popup_wid = popup_window_widget (wid, "Balloon");
834   SI winx, winy;
835   // HACK around wrong? reporting of window widget for embedded texmacs-inputs:
836   // call get_window on the current window (concrete_window()->win is set to
837   // the texmacs-input widget whenever there is one)
838   get_position (get_window (concrete_window()->win), winx, winy);
839   set_position (_popup_wid, x+winx, y+winy);
840   popup_wid_time = texmacs_time() + 666;
841     // update() will eventually show the widget
842 }
843 
844 
845 /******************************************************************************
846  * Font support
847  ******************************************************************************/
848 
849 /*! Sets the name of the default font.
850  @note This is ignored since Qt handles fonts for the widgets.
851  */
852 void
set_default_font(string name)853 set_default_font (string name) {
854   (void) name;
855 }
856 
857 /*! Gets the default font or monospaced font (if tt is true).
858  @return A null font since this function is not called in the Qt port.
859  */
860 font
get_default_font(bool tt,bool mini,bool bold)861 get_default_font (bool tt, bool mini, bool bold) {
862   (void) tt; (void) mini; (void) bold;
863   if (DEBUG_QT) debug_qt << "get_default_font(): SHOULD NOT BE CALLED\n";
864   return NULL;  //return tex_font (this, "ecrm", 10, 300, 0);
865 }
866 
867 /*! Loads the metric and glyphs of a system font.
868  You are not forced to provide any system fonts.
869  */
870 void
load_system_font(string fam,int sz,int dpi,font_metric & fnm,font_glyphs & fng)871 load_system_font (string fam, int sz, int dpi, font_metric& fnm, font_glyphs& fng)
872 {
873   (void) fam; (void) sz; (void) dpi; (void) fnm; (void) fng;
874   if (DEBUG_QT) debug_qt << "load_system_font(): SHOULD NOT BE CALLED\n";
875 }
876 
877 
878 /******************************************************************************
879  * Clipboard support
880  ******************************************************************************/
881 
882 bool
set_selection(string key,tree t,string s,string sv,string sh,string format)883 set_selection (string key, tree t,
884                string s, string sv, string sh, string format) {
885     // Copy a selection 't' with string equivalent 's' to the clipboard 'cb'
886     // and possibly the variants 'sv' and 'sh' for verbatim and html
887     // Returns true on success
888   return the_gui->set_selection (key, t, s, sv, sh, format);
889 }
890 
891 bool
get_selection(string key,tree & t,string & s,string format)892 get_selection (string key, tree& t, string& s, string format) {
893     // Retrieve the selection 't' with string equivalent 's' from clipboard 'cb'
894     // Returns true on success; sets t to (extern s) for external selections
895   return the_gui->get_selection (key, t, s, format);
896 }
897 
898 void
clear_selection(string key)899 clear_selection (string key) {
900     // Clear the selection on clipboard 'cb'
901   the_gui->clear_selection (key);
902 }
903 
904 bool
put_graphics_on_clipboard(url file)905 qt_gui_rep::put_graphics_on_clipboard (url file) {
906   string extension = suffix (file) ;
907 
908     // for bitmaps this works :
909   if ((extension == "bmp") || (extension == "png") ||
910       (extension == "jpg") || (extension == "jpeg")) {
911     QClipboard *clipboard = QApplication::clipboard();
912     c_string tmp (concretize (file));
913     clipboard->setImage (QImage (tmp));
914   }
915   else {
916       // vector formats
917       // Are there applications receiving eps, pdf,... through the clipboard?
918       // I have not experimented with EMF/WMF (windows) or SVM (Ooo)
919     QString mime ="image/*"; // generic image format;
920     if (extension == "eps") mime = "application/postscript";
921     if (extension == "pdf") mime = "application/pdf";
922     if (extension == "svg") mime = "image/svg+xml"; //this works with Inskcape version >= 0.47
923 
924     string filecontent;
925     load_string (file, filecontent, true);
926 
927     c_string tmp (filecontent);
928     QByteArray rawdata (tmp);
929 
930     QMimeData *mymimeData = new QMimeData;
931     mymimeData->setData (mime, rawdata);
932 
933     QClipboard *clipboard = QApplication::clipboard();
934     clipboard->setMimeData (mymimeData);// default mode = QClipboard::Clipboard
935   }
936   return true;
937 }
938 
939 /******************************************************************************
940  * Miscellaneous
941  ******************************************************************************/
942 int char_clip = 0;
943 
944 void
beep()945 beep () {
946     // Issue a beep
947   QApplication::beep();
948 }
949 
950 bool
check_event(int type)951 check_event (int type) {
952     // Check whether an event of one of the above types has occurred;
953     // we check for keyboard events while repainting windows
954   return the_gui->check_event (type);
955 }
956 
957 void
show_help_balloon(widget balloon,SI x,SI y)958 show_help_balloon (widget balloon, SI x, SI y) {
959     // Display a help balloon at position (x, y); the help balloon should
960     // disappear as soon as the user presses a key or moves the mouse
961   the_gui->show_help_balloon (balloon, x, y);
962 }
963 
964 void
show_wait_indicator(widget base,string message,string argument)965 show_wait_indicator (widget base, string message, string argument) {
966     // Display a wait indicator with a message and an optional argument
967     // The indicator might for instance be displayed at the center of
968     // the base widget which triggered the lengthy operation;
969     // the indicator should be removed if the message is empty
970   the_gui->show_wait_indicator (base, message, argument);
971 }
972 
973 void
external_event(string type,time_t t)974 external_event (string type, time_t t) {
975     // External events, such as pushing a button of a remote infrared commander
976   QTMWidget *tm_focus = qobject_cast<QTMWidget*>(qApp->focusWidget());
977   if (tm_focus) {
978     simple_widget_rep* wid = tm_focus->tm_widget();
979     if (wid) the_gui -> process_keypress (wid, type, t);
980   }
981 }
982 
983 
984 /******************************************************************************
985  * Delayed commands
986  ******************************************************************************/
987 
command_queue()988 command_queue::command_queue() : lapse (0), wait (true) { }
~command_queue()989 command_queue::~command_queue() { clear_pending(); /* implicit */ }
990 
991 void
exec(object cmd)992 command_queue::exec (object cmd) {
993   q << cmd;
994   start_times << (((time_t) texmacs_time ()) - 1000000000);
995   lapse = texmacs_time();
996   the_gui->need_update();
997 }
998 
999 void
exec_pause(object cmd)1000 command_queue::exec_pause (object cmd) {
1001   q << cmd;
1002   start_times << ((time_t) texmacs_time ());
1003   lapse = texmacs_time();
1004   the_gui->need_update();
1005 }
1006 
1007 void
exec_pending()1008 command_queue::exec_pending () {
1009   array<object> a = q;
1010   array<time_t> b = start_times;
1011   q = array<object> (0);
1012   start_times = array<time_t> (0);
1013   int i, n = N(a);
1014   for (i = 0; i<n; i++) {
1015     time_t now =  texmacs_time ();
1016     if ((now - b[i]) >= 0) {
1017       object obj = call (a[i]);
1018       if (is_int (obj) && (now - b[i] < 1000000000)) {
1019         time_t pause = as_int (obj);
1020           //cout << "pause = " << obj << "\n";
1021         q << a[i];
1022         start_times << (now + pause);
1023       }
1024     }
1025     else {
1026       q << a[i];
1027       start_times << b[i];
1028     }
1029   }
1030   if (N(q) > 0) {
1031     wait = true;  // wait_for_delayed_commands
1032     lapse = start_times[0];
1033     int n = N(start_times);
1034     for (i = 1; i<n; i++) {
1035       if (lapse > start_times[i]) lapse = start_times[i];
1036     }
1037   } else
1038     wait = false;
1039 }
1040 
1041 void
clear_pending()1042 command_queue::clear_pending () {
1043   q = array<object> (0);
1044   start_times = array<time_t> (0);
1045   wait = false;
1046 }
1047 
1048 bool
must_wait(time_t now) const1049 command_queue::must_wait (time_t now) const {
1050   return wait && (lapse <= now);
1051 }
1052 
1053 
1054 /******************************************************************************
1055  * Delayed commands interface
1056  ******************************************************************************/
1057 
exec_delayed(object cmd)1058 void exec_delayed (object cmd) {
1059   the_gui->delayed_commands.exec(cmd);
1060 }
exec_delayed_pause(object cmd)1061 void exec_delayed_pause (object cmd) {
1062   the_gui->delayed_commands.exec_pause(cmd);
1063 }
clear_pending_commands()1064 void clear_pending_commands () {
1065   the_gui->delayed_commands.clear_pending();
1066 }
1067 
1068 
1069 /******************************************************************************
1070  * Queued events
1071  ******************************************************************************/
1072 
event_queue()1073 event_queue::event_queue() : n(0) { }
1074 
1075 void
append(const queued_event & ev)1076 event_queue::append (const queued_event& ev) {
1077   q << ev;
1078   ++n;
1079 }
1080 
1081 queued_event
next()1082 event_queue::next () {
1083   if (is_nil(q))
1084     return queued_event();
1085   queued_event ev = q->item;
1086   q = q->next;
1087   --n;
1088   return ev;
1089 }
1090 
1091 bool
is_empty() const1092 event_queue::is_empty() const {
1093   ASSERT (!(n!=0 && is_nil(q)), "WTF?");
1094   return n == 0;
1095 }
1096 
1097 int
size() const1098 event_queue::size() const {
1099   return n;
1100 }
1101 
1102 
1103