]> git.lyx.org Git - features.git/blob - src/frontends/qt4/GuiView.cpp
rename LyXKeySym into KeySymbol
[features.git] / src / frontends / qt4 / GuiView.cpp
1 /**
2  * \file GuiView.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author John Levon
8  * \author Abdelrazak Younes
9  * \author Peter Kümmel
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "GuiView.h"
17
18 #include "GuiImplementation.h"
19 #include "GuiWorkArea.h"
20 #include "QKeySymbol.h"
21 #include "QLMenubar.h"
22 #include "QLToolbar.h"
23 #include "QCommandBuffer.h"
24 #include "qt_helpers.h"
25
26 #include "frontends/Application.h"
27 #include "frontends/Gui.h"
28 #include "frontends/WorkArea.h"
29
30 #include "support/filetools.h"
31 #include "support/convert.h"
32 #include "support/lstrings.h"
33
34 #include "BufferView.h"
35 #include "BufferList.h"
36 #include "debug.h"
37 #include "FuncRequest.h"
38 #include "lyx_cb.h"
39 #include "LyXRC.h"
40 #include "LyX.h"
41 #include "Session.h"
42 #include "LyXFunc.h"
43 #include "MenuBackend.h"
44 #include "Buffer.h"
45 #include "BufferList.h"
46
47 #include <QAction>
48 #include <QApplication>
49 #include <QCloseEvent>
50 #include <QPixmap>
51 #include <QStatusBar>
52 #include <QToolBar>
53 #include <QTabBar>
54 #include <QDesktopWidget>
55 #include <QVBoxLayout>
56
57 #include <boost/bind.hpp>
58 #include <boost/shared_ptr.hpp>
59
60 using std::endl;
61 using std::string;
62 using std::vector;
63
64 namespace lyx {
65
66 using support::FileName;
67 using support::libFileSearch;
68 using support::makeDisplayPath;
69
70 namespace frontend {
71
72 namespace {
73
74 int const statusbar_timer_value = 3000;
75
76 class TabWidget : public QWidget
77 {
78 public:
79         QTabBar* tabbar;
80
81         TabWidget(QWidget* w, bool topTabBar)
82         {
83                 tabbar = new QTabBar;
84                 QVBoxLayout* layout = new QVBoxLayout;
85                 if (topTabBar) {
86                         layout->addWidget(tabbar);
87                         layout->addWidget(w);
88                 } else {
89                         tabbar->setShape(QTabBar::RoundedSouth);
90                         layout->addWidget(w);
91                         layout->addWidget(tabbar);
92                 }
93                 layout->setMargin(0);
94                 setLayout(layout);
95         }
96
97         void clearTabbar()
98         {
99                 for (int i = tabbar->count() - 1; i >= 0; --i) 
100                         tabbar->removeTab(i);
101         }
102 };
103
104 } // namespace anon
105
106
107 struct GuiView::GuiViewPrivate
108 {
109         vector<string> tabnames;
110         string cur_title;
111
112         TabWidget* tabWidget;
113
114         int posx_offset;
115         int posy_offset;
116
117         GuiViewPrivate() : tabWidget(0), posx_offset(0), posy_offset(0)
118         {}
119
120         unsigned int smallIconSize;
121         unsigned int normalIconSize;
122         unsigned int bigIconSize;
123         // static needed by "New Window"
124         static unsigned int lastIconSize;
125
126         QMenu* toolBarPopup(GuiView *parent)
127         {
128                 // FIXME: translation 
129                 QMenu* menu = new QMenu(parent);
130                 QActionGroup *iconSizeGroup = new QActionGroup(parent);
131
132                 QAction *smallIcons = new QAction(iconSizeGroup);
133                 smallIcons->setText(qt_("Small-sized icons"));
134                 smallIcons->setCheckable(true);
135                 QObject::connect(smallIcons, SIGNAL(triggered()), parent, SLOT(smallSizedIcons()));
136                 menu->addAction(smallIcons);
137
138                 QAction *normalIcons = new QAction(iconSizeGroup);
139                 normalIcons->setText(qt_("Normal-sized icons"));
140                 normalIcons->setCheckable(true);
141                 QObject::connect(normalIcons, SIGNAL(triggered()), parent, SLOT(normalSizedIcons()));
142                 menu->addAction(normalIcons);
143
144
145                 QAction *bigIcons = new QAction(iconSizeGroup);
146                 bigIcons->setText(qt_("Big-sized icons"));
147                 bigIcons->setCheckable(true);
148                 QObject::connect(bigIcons, SIGNAL(triggered()), parent, SLOT(bigSizedIcons()));
149                 menu->addAction(bigIcons);
150
151                 unsigned int cur = parent->iconSize().width();
152                 if ( cur == parent->d.smallIconSize)
153                         smallIcons->setChecked(true);
154                 else if (cur == parent->d.normalIconSize)
155                         normalIcons->setChecked(true);
156                 else if (cur == parent->d.bigIconSize)
157                         bigIcons->setChecked(true);
158
159                 return menu;
160         }
161 };
162
163
164 unsigned int GuiView::GuiViewPrivate::lastIconSize = 0;
165
166
167 GuiView::GuiView(int id)
168         : QMainWindow(), LyXView(id), commandbuffer_(0), quitting_by_menu_(false),
169           d(*new GuiViewPrivate)
170 {
171         // Qt bug? signal lastWindowClosed does not work
172         setAttribute(Qt::WA_QuitOnClose, false);
173         setAttribute(Qt::WA_DeleteOnClose, true);
174
175         // hardcode here the platform specific icon size
176         d.smallIconSize = 14;   // scaling problems
177         d.normalIconSize = 20;  // ok, default
178         d.bigIconSize = 26;             // better for some math icons
179
180 #ifndef Q_WS_MACX
181         //  assign an icon to main form. We do not do it under Qt/Mac,
182         //  since the icon is provided in the application bundle.
183         FileName const iconname = libFileSearch("images", "lyx", "xpm");
184         if (!iconname.empty())
185                 setWindowIcon(QPixmap(toqstr(iconname.absFilename())));
186 #endif
187 }
188
189
190 GuiView::~GuiView()
191 {
192         menubar_.reset();
193         delete &d;
194 }
195
196
197 void GuiView::close()
198 {
199         quitting_by_menu_ = true;
200         QMainWindow::close();
201         quitting_by_menu_ = false;
202 }
203
204
205 void GuiView::setFocus()
206 {
207         BOOST_ASSERT(work_area_);
208         static_cast<GuiWorkArea *>(work_area_)->setFocus();
209 }
210
211
212 QMenu* GuiView::createPopupMenu()
213 {
214         return d.toolBarPopup(this);
215 }
216
217
218 void GuiView::init()
219 {
220         menubar_.reset(new QLMenubar(this, menubackend));
221         QObject::connect(menuBar(), SIGNAL(triggered(QAction *)),
222                 this, SLOT(updateMenu(QAction *)));
223
224         getToolbars().init();
225
226         statusBar()->setSizeGripEnabled(true);
227
228         QObject::connect(&statusbar_timer_, SIGNAL(timeout()),
229                 this, SLOT(update_view_state_qt()));
230
231         BOOST_ASSERT(work_area_);
232         if (!work_area_->bufferView().buffer() && !theBufferList().empty())
233                 setBuffer(theBufferList().first());
234
235         // make sure the buttons are disabled if needed
236         updateToolbars();
237         updateLayoutChoice();
238         updateMenubar();
239 }
240
241
242 void GuiView::closeEvent(QCloseEvent * close_event)
243 {
244         // we may have been called through the close window button
245         // which bypasses the LFUN machinery.
246         if (!quitting_by_menu_ && theApp()->gui().viewIds().size() == 1) {
247                 if (!theBufferList().quitWriteAll()) {
248                         close_event->ignore();
249                         return;
250                 }
251         }
252
253         theApp()->gui().unregisterView(id());
254         if (!theApp()->gui().viewIds().empty()) {
255                 // Just close the window and do nothing else if this is not the
256                 // last window.
257                 close_event->accept();
258                 return;
259         }
260
261         if (view()->buffer()) {
262                 // save cursor position for opened files to .lyx/session
263                 // only bottom (whole doc) level pit and pos is saved.
264                 LyX::ref().session().lastFilePos().save(
265                         FileName(buffer()->fileName()),
266                         boost::tie(view()->cursor().bottom().pit(),
267                         view()->cursor().bottom().pos()));
268         }
269
270         // this is the place where we leave the frontend.
271         // it is the only point at which we start quitting.
272         saveGeometry();
273         close_event->accept();
274         // quit the event loop
275         qApp->quit();
276 }
277
278
279 void GuiView::saveGeometry()
280 {
281         static bool done = false;
282         if (done)
283                 return;
284         else
285                 done = true;
286
287         // FIXME:
288         // change the ifdef to 'geometry = normalGeometry();' only
289         // when Trolltech has fixed the broken normalGeometry on X11:
290         // http://www.trolltech.com/developer/task-tracker/index_html?id=119684+&method=entry
291         // Then also the moveEvent, resizeEvent, and the
292         // code for floatingGeometry_ can be removed;
293         // adjust GuiView::setGeometry()
294         
295         QRect normal_geometry;
296         int maximized;
297 #ifdef Q_WS_WIN
298         normal_geometry = normalGeometry();
299         if (isMaximized()) {
300                 maximized = CompletelyMaximized;
301         } else {
302                 maximized = NotMaximized;
303         }
304 #else
305         normal_geometry = updateFloatingGeometry();
306
307         QDesktopWidget& dw = *qApp->desktop();
308         QRect desk = dw.availableGeometry(dw.primaryScreen());  
309         // Qt bug on Linux: load completely maximized, vert max. save-> frameGeometry().height() is wrong
310         if (isMaximized() && desk.width() <= frameGeometry().width() && desk.height() <= frameGeometry().height()) {
311                 maximized = CompletelyMaximized;
312                 // maximizing does not work when the window is allready hor. or vert. maximized
313                 // Tested only on KDE
314                 int dh = frameGeometry().height() - height(); 
315                 if (desk.height() <= normal_geometry.height() + dh) 
316                         normal_geometry.setHeight(normal_geometry.height() - 1);
317                 int dw = frameGeometry().width() - width();
318                 if (desk.width() <= normal_geometry.width() + dw) 
319                         normal_geometry.setWidth(normal_geometry.width() - 1);
320         } else if (desk.height() <= frameGeometry().height()) {
321                 maximized = VerticallyMaximized;                
322         } else if (desk.width() <= frameGeometry().width()) {
323                 maximized = HorizontallyMaximized;
324         } else {
325                 maximized = NotMaximized;
326         }
327
328
329 #endif
330         // save windows size and position
331         Session & session = LyX::ref().session();
332         session.sessionInfo().save("WindowWidth", convert<string>(normal_geometry.width()));
333         session.sessionInfo().save("WindowHeight", convert<string>(normal_geometry.height()));
334         session.sessionInfo().save("WindowMaximized", convert<string>(maximized));
335         session.sessionInfo().save("IconSizeXY", convert<string>(iconSize().width()));
336         if (lyxrc.geometry_xysaved) {
337                 session.sessionInfo().save("WindowPosX", convert<string>(normal_geometry.x() + d.posx_offset));
338                 session.sessionInfo().save("WindowPosY", convert<string>(normal_geometry.y() + d.posy_offset));
339         }
340         getToolbars().saveToolbarInfo();
341 }
342                                                   
343
344 void GuiView::setGeometry(unsigned int width,
345                           unsigned int height,
346                           int posx, int posy,
347                           int maximized,
348                           unsigned int iconSizeXY,
349                           const string & geometryArg)
350 {
351         // use last value (not at startup)
352         if (d.lastIconSize != 0)
353                 setIconSize(d.lastIconSize);
354         else if (iconSizeXY != 0)
355                 setIconSize(iconSizeXY);
356         else
357                 setIconSize(d.normalIconSize);
358
359         // only true when the -geometry option was NOT used
360         if (width != 0 && height != 0) {
361                 if (posx != -1 && posy != -1) {
362                         // if there are startup positioning problems:
363                         // http://doc.trolltech.com/4.2/qdesktopwidget.html 
364                         QDesktopWidget& dw = *qApp->desktop();
365                         if (dw.isVirtualDesktop()) {
366                                 if(!dw.geometry().contains(posx, posy)) {
367                                         posx = 50;
368                                         posy = 50;
369                                 }
370                         } else {
371                                 // Which system doesn't use a virtual desktop?
372                                 // TODO save also last screen number and check if it is still availabe.
373                         }
374 #ifdef Q_WS_WIN
375                         // FIXME: use setGeometry only when Trolltech has fixed the qt4/X11 bug
376                         QWidget::setGeometry(posx, posy, width, height);
377 #else
378                         resize(width, height);
379                         move(posx, posy);
380 #endif
381                 } else {
382                         resize(width, height);
383                 }
384
385                 // remember original size
386                 floatingGeometry_ = QRect(posx, posy, width, height);
387
388                 if (maximized != NotMaximized) {
389                         if (maximized == CompletelyMaximized) {
390                                 setWindowState(Qt::WindowMaximized);
391                         } else {
392 #ifndef Q_WS_WIN
393                                 // TODO How to set by the window manager?
394                                 //      setWindowState(Qt::WindowVerticallyMaximized); 
395                                 //      is not possible
396                                 QDesktopWidget& dw = *qApp->desktop();
397                                 QRect desk = dw.availableGeometry(dw.primaryScreen());
398                                 if (maximized == VerticallyMaximized) 
399                                         resize(width, desk.height());
400                                 if (maximized == HorizontallyMaximized)
401                                         resize(desk.width(), height);
402 #endif
403                         }
404                 }
405         }
406         else
407         {
408                 // FIXME: move this code into parse_geometry() (LyX.cpp)
409 #ifdef Q_WS_WIN
410                 int x, y;
411                 int w, h;
412                 QRegExp re( "[=]*(?:([0-9]+)[xX]([0-9]+)){0,1}[ ]*(?:([+-][0-9]*)([+-][0-9]*)){0,1}" );
413                 re.indexIn(toqstr(geometryArg.c_str()));
414                 w = re.cap(1).toInt();
415                 h = re.cap(2).toInt();
416                 x = re.cap(3).toInt();
417                 y = re.cap(4).toInt();
418                 QWidget::setGeometry( x, y, w, h );
419 #else
420                 // silence warning
421                 (void)geometryArg;
422 #endif
423         }
424
425         show();
426
427         // For an unknown reason, the Window title update is not effective for
428         // the second windows up until it is shown on screen (Qt bug?).
429         updateWindowTitle();
430
431         // after show geometry() has changed (Qt bug?)
432         // we compensate the drift when storing the position
433         d.posx_offset = 0;
434         d.posy_offset = 0;
435         if (width != 0 && height != 0) 
436                 if (posx != -1 && posy != -1) {
437 #ifdef Q_WS_WIN
438                         d.posx_offset = posx - normalGeometry().x();
439                         d.posy_offset = posy - normalGeometry().y();
440 #else
441 #ifndef Q_WS_MACX
442                         if (maximized == NotMaximized) {
443                                 d.posx_offset = posx - geometry().x();
444                                 d.posy_offset = posy - geometry().y();
445                         }
446 #endif
447 #endif
448                 }
449 }
450
451
452 void GuiView::updateMenu(QAction * /*action*/)
453 {
454         menubar_->update();
455 }
456
457
458 void GuiView::setWindowTitle(docstring const & t, docstring const & it)
459 {
460         QString title = windowTitle();
461         QString new_title = toqstr(t);
462         if (title != new_title) {
463                 QMainWindow::setWindowTitle(new_title);
464                 QMainWindow::setWindowIconText(toqstr(it));
465         }
466 }
467
468
469 void GuiView::addCommandBuffer(QToolBar * toolbar)
470 {
471         commandbuffer_ = new QCommandBuffer(this, *controlcommand_);
472         focus_command_buffer.connect(boost::bind(&GuiView::focus_command_widget, this));
473         toolbar->addWidget(commandbuffer_);
474 }
475
476
477 void GuiView::message(docstring const & str)
478 {
479         statusBar()->showMessage(toqstr(str));
480         statusbar_timer_.stop();
481         statusbar_timer_.start(statusbar_timer_value);
482 }
483
484
485 void GuiView::clearMessage()
486 {
487         update_view_state_qt();
488 }
489
490
491 void GuiView::setIconSize(unsigned int size)
492 {
493         d.lastIconSize = size;
494         QMainWindow::setIconSize(QSize(size, size));
495 }
496
497
498 void GuiView::smallSizedIcons()
499 {
500         setIconSize(d.smallIconSize);
501 }
502
503
504 void GuiView::normalSizedIcons()
505 {
506         setIconSize(d.normalIconSize);
507 }
508
509
510 void GuiView::bigSizedIcons()
511 {
512         setIconSize(d.bigIconSize);
513 }
514
515
516 void GuiView::focus_command_widget()
517 {
518         if (commandbuffer_)
519                 commandbuffer_->focus_command();
520 }
521
522
523 void GuiView::update_view_state_qt()
524 {
525         statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
526         statusbar_timer_.stop();
527 }
528
529
530 void GuiView::initTab(QWidget* workarea)
531 {
532         // construct the TabWidget with 'false' to have the tabbar at the bottom
533         d.tabWidget = new TabWidget(workarea, true);
534         setCentralWidget(d.tabWidget);
535         QObject::connect(d.tabWidget->tabbar, SIGNAL(currentChanged(int)),
536                         this, SLOT(currentTabChanged(int)));
537 }
538
539
540 void GuiView::updateTab()
541 {
542         std::vector<string> const & names = theBufferList().getFileNames();
543
544         string cur_title;
545         if (view()->buffer()) {
546                 cur_title = view()->buffer()->fileName();
547         }
548
549         // avoid unnecessary tabbar rebuild: 
550         // check if something has changed
551         if (d.tabnames == names && d.cur_title == cur_title) 
552                 return;
553         d.tabnames = names;
554         d.cur_title = cur_title;
555
556         QTabBar & tabbar = *d.tabWidget->tabbar;
557
558         // update when all is done
559         tabbar.blockSignals(true);
560
561         // remove all tab bars
562         d.tabWidget->clearTabbar();
563
564         // rebuild tabbar and function map from scratch
565         if (names.size() > 1) {
566                 for(size_t i = 0; i < names.size(); i++) {
567                         tabbar.addTab(toqstr(makeDisplayPath(names[i], 30))); 
568                         // set current tab
569                         if (names[i] == cur_title)
570                                 tabbar.setCurrentIndex(i);
571                 }
572         }
573         tabbar.blockSignals(false);
574 }
575
576
577 void GuiView::currentTabChanged(int i)
578 {
579         BOOST_ASSERT(i >= 0 && size_type(i) < d.tabnames.size());
580         dispatch(FuncRequest(LFUN_BUFFER_SWITCH, d.tabnames[i]));
581 }
582
583
584 void GuiView::updateStatusBar()
585 {
586         // let the user see the explicit message
587         if (statusbar_timer_.isActive())
588                 return;
589
590         statusBar()->showMessage(toqstr(theLyXFunc().viewStatusMessage()));
591 }
592
593
594 void GuiView::activated(FuncRequest const & func)
595 {
596         dispatch(func);
597 }
598
599
600 bool GuiView::hasFocus() const
601 {
602         return qApp->activeWindow() == this;
603 }
604
605
606 QRect  GuiView::updateFloatingGeometry()
607 {
608         QDesktopWidget& dw = *qApp->desktop();
609         QRect desk = dw.availableGeometry(dw.primaryScreen());
610         // remember only non-maximized sizes
611         if (!isMaximized() && desk.width() > frameGeometry().width() && desk.height() > frameGeometry().height()) {
612                 floatingGeometry_ = QRect(x(), y(), width(), height());
613         }
614         return floatingGeometry_;
615 }
616
617
618 void GuiView::resizeEvent(QResizeEvent *)
619 {
620         updateFloatingGeometry();
621 }
622
623
624 void GuiView::moveEvent(QMoveEvent *)
625 {
626         updateFloatingGeometry();
627 }
628
629
630 bool GuiView::event(QEvent * e)
631 {
632         // Useful debug code:
633         /*
634         switch (e->type())
635         {
636         case QEvent::WindowActivate:
637         case QEvent::ActivationChange:
638         case QEvent::WindowDeactivate:
639         case QEvent::Paint:
640         case QEvent::Enter:
641         case QEvent::Leave:
642         case QEvent::HoverEnter:
643         case QEvent::HoverLeave:
644         case QEvent::HoverMove:
645         case QEvent::StatusTip:
646                 break;
647         default:
648         */
649
650         if (e->type() == QEvent::ShortcutOverride) {
651                 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
652                 if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
653                         boost::shared_ptr<QKeySymbol> sym(new QKeySymbol);
654                         sym->set(ke);
655                         BOOST_ASSERT(work_area_);
656                         work_area_->processKeySym(sym, key_modifier::none);
657                         e->accept();
658                         return true;
659                 }
660         }
661         //} for the debug switch above.
662
663         return QMainWindow::event(e);
664 }
665
666
667 bool GuiView::focusNextPrevChild(bool /*next*/)
668 {
669         setFocus();
670         return true;
671 }
672
673
674 void GuiView::show()
675 {
676         QMainWindow::setWindowTitle(qt_("LyX"));
677         QMainWindow::show();
678         updateFloatingGeometry();
679 }
680
681
682 void GuiView::busy(bool yes)
683 {
684         BOOST_ASSERT(work_area_);
685         static_cast<GuiWorkArea *>(work_area_)->setUpdatesEnabled(!yes);
686
687         if (yes) {
688                 work_area_->stopBlinkingCursor();
689                 QApplication::setOverrideCursor(Qt::WaitCursor);
690         }
691         else {
692                 work_area_->startBlinkingCursor();
693                 QApplication::restoreOverrideCursor();
694         }
695 }
696
697
698 Toolbars::ToolbarPtr GuiView::makeToolbar(ToolbarInfo const & tbinfo, bool newline)
699 {
700         QLToolbar * Tb = new QLToolbar(tbinfo, *this);
701
702         if (tbinfo.flags & ToolbarInfo::TOP) {
703                 if (newline)
704                         addToolBarBreak(Qt::TopToolBarArea);
705                 addToolBar(Qt::TopToolBarArea, Tb);
706         }
707
708         if (tbinfo.flags & ToolbarInfo::BOTTOM) {
709 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
710 #if (QT_VERSION >= 0x040202)
711                 if (newline)
712                         addToolBarBreak(Qt::BottomToolBarArea);
713 #endif
714                 addToolBar(Qt::BottomToolBarArea, Tb);
715         }
716
717         if (tbinfo.flags & ToolbarInfo::LEFT) {
718 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
719 #if (QT_VERSION >= 0x040202)
720                 if (newline)
721                         addToolBarBreak(Qt::LeftToolBarArea);
722 #endif
723                 addToolBar(Qt::LeftToolBarArea, Tb);
724         }
725
726         if (tbinfo.flags & ToolbarInfo::RIGHT) {
727 // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock.
728 #if (QT_VERSION >= 0x040202)
729                 if (newline)
730                         addToolBarBreak(Qt::RightToolBarArea);
731 #endif
732                 addToolBar(Qt::RightToolBarArea, Tb);
733         }
734
735         // The following does not work so I cannot restore to exact toolbar location
736         /*
737         ToolbarSection::ToolbarInfo & tbinfo = LyX::ref().session().toolbars().load(tbinfo.name);
738         Tb->move(tbinfo.posx, tbinfo.posy);
739         */
740
741         return Toolbars::ToolbarPtr(Tb);
742 }
743
744 } // namespace frontend
745 } // namespace lyx
746
747 #include "GuiView_moc.cpp"