]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/QWorkArea.C
Added initial qt4 work by Abdelrazak Younes
[lyx.git] / src / frontends / qt4 / QWorkArea.C
1 /**
2  * \file QWorkArea.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Abdelrazak Younes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "QWorkArea.h"
15 #include "QLPainter.h"
16 #include "QLyXKeySym.h"
17
18 #include "lcolorcache.h"
19 #include "qt_helpers.h"
20
21 #include "debug.h"
22 #include "funcrequest.h"
23 #include "LColor.h"
24 #include "support/os.h"
25
26 #include <QApplication>
27 #include <QClipboard>
28 #include <QLayout>
29 #include <QMainWindow>
30 #include <Q3UriDrag>
31 #include <QDragEnterEvent>
32 #include <QPixmap>
33 #include <QPainter>
34 #include <QScrollBar>
35
36 #include <boost/bind.hpp>
37
38 #ifdef Q_WS_X11
39 #include <X11/Xlib.h>
40 #endif
41
42 #ifdef Q_WS_MACX
43 #include <Carbon/Carbon.h>
44 #endif
45
46 using std::endl;
47 using std::string;
48
49 namespace os = lyx::support::os;
50
51 namespace {
52 QWorkArea const * wa_ptr = 0;
53
54 /// return the LyX key state from Qt's
55 key_modifier::state q_key_state(Qt::ButtonState state)
56 {
57         key_modifier::state k = key_modifier::none;
58         if (state & Qt::ControlModifier)
59                 k |= key_modifier::ctrl;
60         if (state & Qt::ShiftModifier)
61                 k |= key_modifier::shift;
62         if (state & Qt::AltModifier)
63                 k |= key_modifier::alt;
64         return k;
65 }
66
67
68 /// return the LyX mouse button state from Qt's
69 mouse_button::state q_button_state(Qt::ButtonState button)
70 {
71         mouse_button::state b = mouse_button::none;
72         switch (button) {
73                 case Qt::LeftButton:
74                         b = mouse_button::button1;
75                         break;
76                 case Qt::MidButton:
77                         b = mouse_button::button2;
78                         break;
79                 case Qt::RightButton:
80                         b = mouse_button::button3;
81                         break;
82                 default:
83                         break;
84         }
85         return b;
86 }
87
88
89 /// return the LyX mouse button state from Qt's
90 mouse_button::state q_motion_state(Qt::ButtonState state)
91 {
92         mouse_button::state b = mouse_button::none;
93         if (state & Qt::LeftButton)
94                 b |= mouse_button::button1;
95         if (state & Qt::MidButton)
96                 b |= mouse_button::button2;
97         if (state & Qt::RightButton)
98                 b |= mouse_button::button3;
99         return b;
100 }
101
102 } // namespace anon
103
104 // This is a 'heartbeat' generating synthetic mouse move events when the
105 // cursor is at the top or bottom edge of the viewport. One scroll per 0.2 s
106 SyntheticMouseEvent::SyntheticMouseEvent()
107         : timeout(200), restart_timeout(true),
108           x_old(-1), y_old(-1), scrollbar_value_old(-1.0)
109 {}
110
111
112
113 Painter & QWorkArea::getPainter() { return painter_; }
114
115 //      QLPainter & QWorkArea::getQLPainter() { return painter_; }
116
117 /// get the content pane widget
118 QWidget * QWorkArea::getContent() const { return viewport(); }
119
120 QWorkArea::QWorkArea(LyXView &, int w, int h)
121         : WorkArea(), QAbstractScrollArea(qApp->mainWidget()), painter_(this)
122 {
123         setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
124         setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
125
126         (static_cast<QMainWindow*>(qApp->mainWidget()))->setCentralWidget(this);
127
128         setAcceptDrops(true);
129
130         setMinimumSize(100, 70);
131
132 //      setBackgroundRole(lcolorcache.get(LColor::background));
133 //      viewport()->setBackgroundRole(QPalette::Window);
134         viewport()->setAutoFillBackground(false);
135         viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
136
137         viewport()->setFocusPolicy(Qt::WheelFocus);
138         viewport()->setFocus();
139 //      viewport()->grabKeyboard();
140         setFocusPolicy(Qt::WheelFocus);
141         setFocusProxy(viewport());
142         viewport()->setCursor(Qt::IBeamCursor);
143
144         resize(w, h);
145         show();
146
147         scrolled_with_mouse_=false;
148         scrolled_with_keyboard_ = false;
149
150         synthetic_mouse_event_.timeout.timeout.connect(
151                 boost::bind(&QWorkArea::generateSyntheticMouseEvent,
152                             this));
153 /*
154         if ( !QObject::connect(&step_timer_, SIGNAL(timeout()),
155                 this, SLOT(keyeventTimeout())) )
156                         lyxerr[Debug::GUI] << "ERROR: keyeventTimeout cannot connect!" << endl;
157 */
158
159         if ( !QObject::connect(verticalScrollBar(), SIGNAL(actionTriggered(int)),
160                 this, SLOT(adjustViewWithScrollBar(int))) )
161                         lyxerr[Debug::GUI] << "ERROR: adjustViewWithScrollBar cannot connect!" << endl;
162         
163 #if USE_INPUT_METHODS
164         // to make qt-immodule work
165         setInputMethodEnabled(true);
166 #endif
167 #ifdef Q_WS_X11
168         // doubleClickInterval() is 400 ms on X11 witch is just too long.
169         // On Windows and Mac OS X, the operating system's value is used.
170         // On Microsoft Windows, calling this function sets the double
171         // click interval for all applications. So we don't!
172         QApplication::setDoubleClickInterval(300);
173 #endif
174
175         // Start the timer, one-shot.
176         step_timer_.start(50, true);
177
178         //viewport()->resize(w, h);
179         pixmap_.reset(new QPixmap(viewport()->width(), viewport()->height()));
180
181         this->workAreaResize();
182         
183         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
184                 << "\n Area width\t" << width()
185                 << "\n Area height\t" << height()
186                 << "\n viewport width\t" << viewport()->width()
187                 << "\n viewport height\t" << viewport()->height()
188                 << endl;
189
190 //      this->QWidget::resize(w,h);
191
192 #ifdef Q_WS_MACX
193         wa_ptr = this;
194 #endif
195 }
196
197 QWorkArea::~QWorkArea()
198 {
199 }
200
201 void QWorkArea::setScrollbarParams(int h, int scroll_pos, int scroll_line_step)
202 {
203         /*
204         if (scrolled_with_mouse_)
205         {
206                 scrolled_with_mouse_=false;
207                 return;
208         }
209         */
210
211         //if (scroll_pos_ == scroll_pos)
212         //{
213         //      verticalScrollBar()->triggerAction(QAbstractSlider::SliderMove);
214                 int scroll_pos_ = scroll_pos;
215         //}
216         int scroll_line_step_ = scroll_line_step;
217         int scroll_page_step_ = viewport()->height();
218
219         // do what cursor movement does (some grey)
220         h += height() / 4;
221         int scroll_max_ = std::max(0, h - height());
222
223         //scrolled_with_keyboard_=true;
224
225         verticalScrollBar()->setRange(0, scroll_max_);
226         verticalScrollBar()->setSliderPosition(scroll_pos_);
227         verticalScrollBar()->setLineStep(scroll_line_step_);
228         verticalScrollBar()->setPageStep(scroll_page_step_);
229 }
230
231 void QWorkArea::adjustViewWithScrollBar(int action)
232 {
233         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
234                 << " verticalScrollBar val=" << verticalScrollBar()->value()
235                 << " verticalScrollBar pos=" << verticalScrollBar()->sliderPosition()
236                 << " min=" << verticalScrollBar()->minimum()
237                 << " max=" << verticalScrollBar()->maximum()
238                 << " pagestep=" << verticalScrollBar()->pageStep()
239                 << " linestep=" << verticalScrollBar()->lineStep()
240                 << endl;
241
242         this->scrollDocView(verticalScrollBar()->sliderPosition());
243
244 //      scrolled_with_mouse_ = true;
245 }
246
247 #ifdef Q_WS_X11
248 bool lyxX11EventFilter(XEvent * xev)
249 {
250         switch (xev->type) {
251         case SelectionRequest:
252                 lyxerr[Debug::GUI] << "X requested selection." << endl;
253                 if (wa_ptr)
254                         wa_ptr->selectionRequested();
255                 break;
256         case SelectionClear:
257                 lyxerr[Debug::GUI] << "Lost selection." << endl;
258                 if (wa_ptr)
259                         wa_ptr->selectionLost();
260                 break;
261         }
262         return false;
263 }
264 #endif
265
266 #ifdef Q_WS_MACX
267 namespace{
268 OSErr checkAppleEventForMissingParams(const AppleEvent& theAppleEvent)
269  {
270         DescType returnedType;
271         Size actualSize;
272         OSErr err = AEGetAttributePtr(&theAppleEvent, keyMissedKeywordAttr,
273                                       typeWildCard, &returnedType, nil, 0,
274                                       &actualSize);
275         switch (err) {
276         case errAEDescNotFound:
277                 return noErr;
278         case noErr:
279                 return errAEEventNotHandled;
280         default:
281                 return err;
282         }
283  }
284 }
285
286 pascal OSErr handleOpenDocuments(const AppleEvent* inEvent,
287                                  AppleEvent* /*reply*/, long /*refCon*/)
288 {
289         QString s_arg;
290         AEDescList documentList;
291         OSErr err = AEGetParamDesc(inEvent, keyDirectObject, typeAEList,
292                                    &documentList);
293         if (err != noErr)
294                 return err;
295
296         err = checkAppleEventForMissingParams(*inEvent);
297         if (err == noErr) {
298                 long documentCount;
299                 err = AECountItems(&documentList, &documentCount);
300                 for (long documentIndex = 1;
301                      err == noErr && documentIndex <= documentCount;
302                      documentIndex++) {
303                         DescType returnedType;
304                         Size actualSize;
305                         AEKeyword keyword;
306                         FSRef ref;
307                         char qstr_buf[1024];
308                         err = AESizeOfNthItem(&documentList, documentIndex,
309                                               &returnedType, &actualSize);
310                         if (err == noErr) {
311                                 err = AEGetNthPtr(&documentList, documentIndex,
312                                                   typeFSRef, &keyword,
313                                                   &returnedType, (Ptr)&ref,
314                                                   sizeof(FSRef), &actualSize);
315                                 if (err == noErr) {
316                                         FSRefMakePath(&ref, (UInt8*)qstr_buf,
317                                                       1024);
318                                         s_arg=QString::fromUtf8(qstr_buf);
319                                         wa_ptr->dispatch(
320                                                 FuncRequest(LFUN_FILE_OPEN,
321                                                             fromqstr(s_arg)));
322                                         break;
323                                 }
324                         }
325                 } // for ...
326         }
327         AEDisposeDesc(&documentList);
328         return err;
329 }
330 #endif  // Q_WS_MACX
331
332 void QWorkArea::haveSelection(bool own) const
333 {
334         wa_ptr = this;
335
336         if (!QApplication::clipboard()->supportsSelection())
337                 return;
338
339         if (own) {
340                 QApplication::clipboard()->setText(QString(), QClipboard::Selection);
341         }
342         // We don't need to do anything if own = false, as this case is
343         // handled by QT.
344 }
345
346
347 string const QWorkArea::getClipboard() const
348 {
349         QString str = QApplication::clipboard()->text(QClipboard::Selection);
350         lyxerr[Debug::ACTION] << "getClipboard: " << (const char*) str << endl;
351         if (str.isNull())
352                 return string();
353         return fromqstr(str);
354 }
355
356
357 void QWorkArea::putClipboard(string const & str) const
358 {
359         QApplication::clipboard()->setText(toqstr(str), QClipboard::Selection);
360         lyxerr[Debug::ACTION] << "putClipboard: " << str << endl;
361 }
362
363
364 void QWorkArea::dragEnterEvent(QDragEnterEvent * event)
365 {
366         event->accept(Q3UriDrag::canDecode(event));
367
368         /// \todo Ask lyx-devel is this is enough:
369         /// if (event->mimeData()->hasFormat("text/plain"))
370         ///     event->acceptProposedAction();
371
372 }
373
374
375 void QWorkArea::dropEvent(QDropEvent* event)
376 {
377         QStringList files;
378
379         if (Q3UriDrag::decodeLocalFiles(event, files)) {
380                 lyxerr[Debug::GUI] << "QWorkArea::dropEvent: got URIs!"
381                                    << endl;
382                 for (QStringList::Iterator i = files.begin();
383                      i!=files.end(); ++i) {
384                         string const file = os::internal_path(fromqstr(*i));
385                         dispatch(FuncRequest(LFUN_FILE_OPEN, file));
386                 }
387         }
388 }
389 /*
390 void QWorkArea::scrollContentsBy(int dx, int dy)
391 {       
392         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
393                 << " scroll by dx=" << dx
394                 << " dy=" << dy
395                 << " verticalScrollBar val=" << verticalScrollBar()->value()
396                 << " min=" << verticalScrollBar()->minimum()
397                 << " max=" << verticalScrollBar()->maximum()
398                 << " pagestep=" << verticalScrollBar()->pageStep()
399                 << " linestep=" << verticalScrollBar()->lineStep()
400                 << endl;
401
402         if (scrolled_with_keyboard_)
403         {
404                 scrolled_with_keyboard_=false;
405                 return;
406         }
407
408         this->scrollDocView(verticalScrollBar()->value());
409         scrolled_with_mouse_ = true;
410 }
411 */
412
413 #if USE_INPUT_METHODS
414 // to make qt-immodule work
415
416 void QWorkArea::inputMethodEvent(QInputMethodEvent * e) 
417 {
418         QString const text = e->text();
419         if (!text.isEmpty()) {
420                 int key = 0;
421                 // needed to make math superscript work on some systems
422                 // ideally, such special coding should not be necessary
423                 if (text == "^")
424                         key = Qt::Key_AsciiCircum;
425                 QKeyEvent ev(QEvent::KeyPress, key, *text.ascii(), 0, text);
426                 keyPressEvent(&ev);
427         }
428         e->accept();
429 }
430 #endif
431
432 void QWorkArea::mousePressEvent(QMouseEvent * e)
433 {
434         if (dc_event_.active && dc_event_ == *e) {
435                 dc_event_.active = false;
436                 FuncRequest cmd(LFUN_MOUSE_TRIPLE,
437                         dc_event_.x, dc_event_.y,
438                         q_button_state(dc_event_.state));
439                 this->dispatch(cmd);
440                 return;
441         }
442
443         FuncRequest const cmd(LFUN_MOUSE_PRESS, e->x(), e->y(),
444                               q_button_state(e->button()));
445         this->dispatch(cmd);
446 }
447
448
449 void QWorkArea::mouseReleaseEvent(QMouseEvent * e)
450 {
451         if (synthetic_mouse_event_.timeout.running())
452                 synthetic_mouse_event_.timeout.stop();
453
454         FuncRequest const cmd(LFUN_MOUSE_RELEASE, e->x(), e->y(),
455                               q_button_state(e->button()));
456         this->dispatch(cmd);
457 }
458
459
460 void QWorkArea::mouseMoveEvent(QMouseEvent * e)
461 {
462         FuncRequest cmd(LFUN_MOUSE_MOTION, e->x(), e->y(),
463                               q_motion_state(e->state()));
464
465         // If we're above or below the work area...
466         if (e->y() <= 20 || e->y() >= viewport()->QWidget::height() - 20) {
467                 // Make sure only a synthetic event can cause a page scroll,
468                 // so they come at a steady rate:
469                 if (e->y() <= 20)
470                         // _Force_ a scroll up:
471                         cmd.y = -40;
472                 else
473                         cmd.y = viewport()->QWidget::height();
474                 // Store the event, to be handled when the timeout expires.
475                 synthetic_mouse_event_.cmd = cmd;
476
477                 if (synthetic_mouse_event_.timeout.running())
478                         // Discard the event. Note that it _may_ be handled
479                         // when the timeout expires if
480                         // synthetic_mouse_event_.cmd has not been overwritten.
481                         // Ie, when the timeout expires, we handle the
482                         // most recent event but discard all others that
483                         // occurred after the one used to start the timeout
484                         // in the first place.
485                         return;
486                 else {
487                         synthetic_mouse_event_.restart_timeout = true;
488                         synthetic_mouse_event_.timeout.start();
489                         // Fall through to handle this event...
490                 }
491
492         } else if (synthetic_mouse_event_.timeout.running()) {
493                 // Store the event, to be possibly handled when the timeout
494                 // expires.
495                 // Once the timeout has expired, normal control is returned
496                 // to mouseMoveEvent (restart_timeout = false).
497                 // This results in a much smoother 'feel' when moving the
498                 // mouse back into the work area.
499                 synthetic_mouse_event_.cmd = cmd;
500                 synthetic_mouse_event_.restart_timeout = false;
501                 return;
502         }
503
504         // Has anything changed on-screen since the last QMouseEvent
505         // was received?
506         double const scrollbar_value = verticalScrollBar()->value();
507         if (e->x() != synthetic_mouse_event_.x_old ||
508             e->y() != synthetic_mouse_event_.y_old ||
509             scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
510                 // Yes it has. Store the params used to check this.
511                 synthetic_mouse_event_.x_old = e->x();
512                 synthetic_mouse_event_.y_old = e->y();
513                 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
514
515                 // ... and dispatch the event to the LyX core.
516                 this->dispatch(cmd);
517         }
518 }
519
520
521 void QWorkArea::wheelEvent(QWheelEvent * e)
522 {
523 //      verticalScrollBar()->setValue(verticalScrollBar()->value() - e->delta());
524 }
525
526
527 void QWorkArea::keyPressEvent(QKeyEvent * e)
528 {
529         lyxerr[Debug::KEY] << BOOST_CURRENT_FUNCTION
530                 << " count=" << e->count()
531                 << " text=" << (const char *) e->text()
532                 << " isAutoRepeat=" << e->isAutoRepeat()
533                 << " key=" << e->key()
534                 << endl;
535
536         //viewport()->grabKeyboard();
537 //      keyeventQueue_.push(boost::shared_ptr<QKeyEvent>(new QKeyEvent(*e)));
538
539     boost::shared_ptr<QLyXKeySym> sym(new QLyXKeySym);
540     sym->set(e);
541     this->workAreaKeyPress(sym, q_key_state(e->state()));        
542  
543 }
544
545 void QWorkArea::generateSyntheticMouseEvent()
546 {
547         // Set things off to generate the _next_ 'pseudo' event.
548         if (synthetic_mouse_event_.restart_timeout)
549                 synthetic_mouse_event_.timeout.start();
550
551         // Has anything changed on-screen since the last timeout signal
552         // was received?
553         double const scrollbar_value = verticalScrollBar()->value();
554         if (scrollbar_value != synthetic_mouse_event_.scrollbar_value_old) {
555                 // Yes it has. Store the params used to check this.
556                 synthetic_mouse_event_.scrollbar_value_old = scrollbar_value;
557
558                 // ... and dispatch the event to the LyX core.
559                 this->dispatch(synthetic_mouse_event_.cmd);
560         }
561 }
562
563
564 void QWorkArea::keyeventTimeout()
565 {
566         bool handle_autos = true;
567
568         while (!keyeventQueue_.empty()) {
569                 boost::shared_ptr<QKeyEvent> ev = keyeventQueue_.front();
570
571                 // We never handle more than one auto repeated
572                 // char in a list of queued up events.
573                 if (!handle_autos && ev->isAutoRepeat()) {
574                         keyeventQueue_.pop();
575                         continue;
576                 }
577
578         boost::shared_ptr<QLyXKeySym> sym(new QLyXKeySym);
579                 sym->set(ev.get());
580
581         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
582                 << " count=" << ev->count()
583                 << " text=" << (const char *) ev->text()
584                 << " isAutoRepeat=" << ev->isAutoRepeat()
585                 << " key=" << ev->key()
586                 << endl;
587
588                 this->workAreaKeyPress(sym, q_key_state(ev->state()));
589                 keyeventQueue_.pop();
590
591                 handle_autos = false;
592         }
593
594         // Restart the timer.
595         step_timer_.start(25, true);
596 }
597
598
599 void QWorkArea::mouseDoubleClickEvent(QMouseEvent * e)
600 {
601         dc_event_ = double_click(e);
602
603         if (!dc_event_.active)
604                 return;
605
606         dc_event_.active = false;
607
608         FuncRequest cmd(LFUN_MOUSE_DOUBLE,
609                 dc_event_.x, dc_event_.y,
610                 q_button_state(dc_event_.state));
611         this->dispatch(cmd);
612 }
613
614
615 void QWorkArea::resizeEvent(QResizeEvent * resizeEvent)
616 {
617         pixmap_.reset(new QPixmap(viewport()->width(), viewport()->height()));
618
619         scrolled_with_mouse_=false;
620         scrolled_with_keyboard_=false;
621
622         this->workAreaResize();
623
624         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
625                 << "\n QWidget width\t" << viewport()->width()
626                 << "\n QWidget height\t" << viewport()->height()
627                 << "\n QResizeEvent rect left\t" << rect().left()
628                 << "\n QResizeEvent rect right\t" << rect().right()
629                 << endl;
630 }
631
632 void QWorkArea::paintEvent(QPaintEvent * e)
633 {
634 /*
635         lyxerr[Debug::GUI] << BOOST_CURRENT_FUNCTION
636                 << "\n QWidget width\t" << viewport()->width()
637                 << "\n QWidget height\t" << viewport()->height()
638                 << "\n QPaintEvent x\t" << e->rect().x()
639                 << "\n QPaintEvent y\t" << e->rect().x()
640                 << "\n QPaintEvent w\t" << e->rect().width()
641                 << "\n QPaintEvent h\t" << e->rect().height()
642                 << endl;
643 */
644         QPainter q(viewport());
645         q.drawPixmap(e->rect(), *pixmap_.get(), e->rect());
646
647 //      q.drawPixmap(QPoint(r.x(), r.y()),
648 //              *pixmap_.get(), r);
649 }