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