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