VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/VBoxVMLogViewer.cpp@ 35740

Last change on this file since 35740 was 30356, checked in by vboxsync, 14 years ago

FE/Qt4: added a new segmented button type; code cleanup

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 19.9 KB
Line 
1/* $Id: VBoxVMLogViewer.cpp 30356 2010-06-22 08:42:22Z vboxsync $ */
2/** @file
3 *
4 * VBox frontends: Qt4 GUI ("VirtualBox"):
5 * VBoxVMLogViewer class implementation
6 */
7
8/*
9 * Copyright (C) 2006-2008 Oracle Corporation
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 */
19
20#ifdef VBOX_WITH_PRECOMPILED_HEADERS
21# include "precomp.h"
22#else /* !VBOX_WITH_PRECOMPILED_HEADERS */
23#include "QITabWidget.h"
24#include "UIIconPool.h"
25#include "UISpecialControls.h"
26#include "VBoxGlobal.h"
27#include "VBoxProblemReporter.h"
28#include "VBoxUtils.h"
29#include "VBoxVMLogViewer.h"
30
31/* Qt includes */
32#include <QCheckBox>
33#include <QDateTime>
34#include <QDir>
35#include <QFileDialog>
36#include <QKeyEvent>
37#include <QLabel>
38#include <QLineEdit>
39#include <QPushButton>
40#include <QScrollBar>
41#include <QStyle>
42#include <QTextEdit>
43#endif /* !VBOX_WITH_PRECOMPILED_HEADERS */
44
45VBoxVMLogViewer::LogViewersMap VBoxVMLogViewer::mSelfArray = LogViewersMap();
46
47void VBoxVMLogViewer::createLogViewer (QWidget *aCenterWidget, CMachine &aMachine)
48{
49 if (!mSelfArray.contains (aMachine.GetName()))
50 {
51 /* Creating new log viewer if there is no one existing */
52#ifdef Q_WS_MAC
53 VBoxVMLogViewer *lv = new VBoxVMLogViewer (aCenterWidget, Qt::Window, aMachine);
54#else /* Q_WS_MAC */
55 VBoxVMLogViewer *lv = new VBoxVMLogViewer (NULL, Qt::Window, aMachine);
56#endif /* Q_WS_MAC */
57
58 lv->centerAccording (aCenterWidget);
59 connect (vboxGlobal().mainWindow(), SIGNAL (closing()), lv, SLOT (close()));
60 lv->setAttribute (Qt::WA_DeleteOnClose);
61 mSelfArray [aMachine.GetName()] = lv;
62 }
63
64 VBoxVMLogViewer *viewer = mSelfArray [aMachine.GetName()];
65 viewer->show();
66 viewer->raise();
67 viewer->setWindowState (viewer->windowState() & ~Qt::WindowMinimized);
68 viewer->activateWindow();
69}
70
71
72VBoxVMLogViewer::VBoxVMLogViewer (QWidget *aParent,
73 Qt::WindowFlags aFlags,
74 const CMachine &aMachine)
75 : QIWithRetranslateUI2<QIMainDialog> (aParent, aFlags)
76 , mIsPolished (false)
77 , mFirstRun (true)
78 , mMachine (aMachine)
79{
80 /* Apply UI decorations */
81 Ui::VBoxVMLogViewer::setupUi (this);
82
83 /* Apply window icons */
84 setWindowIcon(UIIconPool::iconSetFull(QSize (32, 32), QSize (16, 16),
85 ":/vm_show_logs_32px.png", ":/show_logs_16px.png"));
86
87 /* Enable size grip without using a status bar. */
88 setSizeGripEnabled (true);
89
90 /* Logs list creation */
91 mLogList = new QITabWidget (mLogsFrame);
92 QVBoxLayout *logsFrameLayout = new QVBoxLayout (mLogsFrame);
93 logsFrameLayout->setContentsMargins (0, 0, 0, 0);
94 logsFrameLayout->addWidget (mLogList);
95
96 connect (mLogList, SIGNAL (currentChanged (int)),
97 this, SLOT (currentLogPageChanged (int)));
98
99 /* Search panel creation */
100 mSearchPanel = new VBoxLogSearchPanel (mLogsFrame, this);
101 logsFrameLayout->addWidget (mSearchPanel);
102 mSearchPanel->hide();
103
104 /* Add missing buttons & retrieve standard buttons */
105 mBtnHelp = mButtonBox->button (QDialogButtonBox::Help);
106 mBtnFind = mButtonBox->addButton (QString::null, QDialogButtonBox::ActionRole);
107 mBtnSave = mButtonBox->button (QDialogButtonBox::Save);
108 mBtnRefresh = mButtonBox->addButton (QString::null, QDialogButtonBox::ActionRole);
109 mBtnClose = mButtonBox->button (QDialogButtonBox::Close);
110
111 /* Setup connections */
112 connect (mButtonBox, SIGNAL (helpRequested()),
113 &vboxProblem(), SLOT (showHelpHelpDialog()));
114 connect (mBtnFind, SIGNAL (clicked()), this, SLOT (search()));
115 connect (mBtnSave, SIGNAL (clicked()), this, SLOT (save()));
116 connect (mBtnRefresh, SIGNAL (clicked()), this, SLOT (refresh()));
117
118 /* Reading log files */
119 refresh();
120 /* Set the focus to the initial default button */
121 defaultButton()->setDefault (true);
122 defaultButton()->setFocus();
123#ifdef Q_WS_MAC
124 /* We have to force this to get the default button L&F on the mac. */
125 defaultButton()->setEnabled (true);
126# ifdef VBOX_DARWIN_USE_NATIVE_CONTROLS
127 logsFrameLayout->setSpacing (4);
128# endif /* VBOX_DARWIN_USE_NATIVE_CONTROLS */
129#endif /* Q_WS_MAC */
130 /* Loading language constants */
131 retranslateUi();
132}
133
134VBoxVMLogViewer::~VBoxVMLogViewer()
135{
136 if (!mMachine.isNull())
137 mSelfArray.remove (mMachine.GetName());
138}
139
140QTextEdit* VBoxVMLogViewer::currentLogPage()
141{
142 if (mLogList->isEnabled())
143 {
144 QWidget *container = mLogList->currentWidget();
145 QTextEdit *browser = container->findChild<QTextEdit*>();
146 Assert (browser);
147 return browser ? browser : 0;
148 }
149 else
150 return 0;
151}
152
153
154bool VBoxVMLogViewer::close()
155{
156 mSearchPanel->hide();
157 return QIMainDialog::close();
158}
159
160void VBoxVMLogViewer::refresh()
161{
162 /* Clearing old data if any */
163 mLogFiles.clear();
164 mLogList->setEnabled (true);
165 while (mLogList->count())
166 {
167 QWidget *firstPage = mLogList->widget (0);
168 mLogList->removeTab (0);
169 delete firstPage;
170 }
171
172 bool isAnyLogPresent = false;
173
174 const CSystemProperties &sys = vboxGlobal().virtualBox().GetSystemProperties();
175 int cMaxLogs = sys.GetLogHistoryCount();
176 for (int i=0; i <= cMaxLogs; ++i)
177 {
178 /* Query the log file name for index i */
179 QString file = mMachine.QueryLogFilename(i);
180 if (!file.isEmpty())
181 {
182 /* Try to read the log file with the index i */
183 ULONG uOffset = 0;
184 QString text;
185 while (true)
186 {
187 QVector<BYTE> data = mMachine.ReadLog(i, uOffset, _1M);
188 if (data.size() == 0)
189 break;
190 text.append(QString::fromUtf8((char*)data.data(), data.size()));
191 uOffset += data.size();
192 }
193 /* Anything read at all? */
194 if (uOffset > 0)
195 {
196 /* Create a log viewer page and append the read text to it */
197 QTextEdit *logViewer = createLogPage(QFileInfo(file).fileName());
198 logViewer->setPlainText(text);
199 /* Add the actual file name and the QTextEdit containing the
200 content to a list. */
201 mLogFiles << qMakePair(file, logViewer);
202 isAnyLogPresent = true;
203 }
204 }
205 }
206
207 /* Create an empty log page if there are no logs at all */
208 if (!isAnyLogPresent)
209 {
210 QTextEdit *dummyLog = createLogPage ("VBox.log");
211 dummyLog->setWordWrapMode (QTextOption::WordWrap);
212 dummyLog->setHtml (tr ("<p>No log files found. Press the "
213 "<b>Refresh</b> button to rescan the log folder "
214 "<nobr><b>%1</b></nobr>.</p>")
215 .arg (mMachine.GetLogFolder()));
216 /* We don't want it to remain white */
217 QPalette pal = dummyLog->palette();
218 pal.setColor (QPalette::Base, pal.color (QPalette::Window));
219 dummyLog->setPalette (pal);
220 }
221
222 /* Show the first tab widget's page after the refresh */
223 mLogList->setCurrentIndex (0);
224 currentLogPageChanged (0);
225
226 /* Enable/Disable save button & tab widget according log presence */
227 mBtnFind->setEnabled (isAnyLogPresent);
228 mBtnSave->setEnabled (isAnyLogPresent);
229 mLogList->setEnabled (isAnyLogPresent);
230 /* Default to the save button if there are any log files otherwise to the
231 * close button. The initial automatic of the main dialog has to be
232 * overwritten */
233 setDefaultButton (isAnyLogPresent ? mBtnSave:mBtnClose);
234}
235
236void VBoxVMLogViewer::save()
237{
238 /* Prepare "save as" dialog */
239 QFileInfo fileInfo (mLogFiles.at(mLogList->currentIndex()).first);
240 QDateTime dtInfo = fileInfo.lastModified();
241 QString dtString = dtInfo.toString ("yyyy-MM-dd-hh-mm-ss");
242 QString defaultFileName = QString ("%1-%2.log")
243 .arg (mMachine.GetName()).arg (dtString);
244 QString defaultFullName = QDir::toNativeSeparators (
245 QDir::home().absolutePath() + "/" + defaultFileName);
246 QString newFileName = QFileDialog::getSaveFileName (this,
247 tr ("Save VirtualBox Log As"), defaultFullName);
248
249 /* Copy log into the file */
250 if (!newFileName.isEmpty())
251 QFile::copy(mMachine.QueryLogFilename(mLogList->currentIndex()), newFileName);
252}
253
254void VBoxVMLogViewer::search()
255{
256 mSearchPanel->isHidden() ? mSearchPanel->show() : mSearchPanel->hide();
257}
258
259void VBoxVMLogViewer::currentLogPageChanged (int aIndex)
260{
261 if (aIndex >= 0 &&
262 aIndex < mLogFiles.count())
263 setFileForProxyIcon(mLogFiles.at(aIndex).first);
264}
265
266void VBoxVMLogViewer::retranslateUi()
267{
268 /* Translate uic generated strings */
269 Ui::VBoxVMLogViewer::retranslateUi (this);
270
271 /* Setup a dialog caption */
272 if (!mMachine.isNull())
273 setWindowTitle (tr ("%1 - VirtualBox Log Viewer").arg (mMachine.GetName()));
274
275 mBtnFind->setText (tr ("&Find"));
276 mBtnRefresh->setText (tr ("&Refresh"));
277 mBtnSave->setText (tr ("&Save"));
278 mBtnClose->setText (tr ("Close"));
279}
280
281void VBoxVMLogViewer::showEvent (QShowEvent *aEvent)
282{
283 QIMainDialog::showEvent (aEvent);
284
285 /* One may think that QWidget::polish() is the right place to do things
286 * below, but apparently, by the time when QWidget::polish() is called,
287 * the widget style & layout are not fully done, at least the minimum
288 * size hint is not properly calculated. Since this is sometimes necessary,
289 * we provide our own "polish" implementation. */
290
291 if (mIsPolished)
292 return;
293
294 mIsPolished = true;
295
296 if (mFirstRun)
297 {
298 /* Resize the whole log-viewer to fit 80 symbols in
299 * text-browser for the first time started */
300 QTextEdit *firstPage = currentLogPage();
301 if (firstPage)
302 {
303 int fullWidth =
304 firstPage->fontMetrics().width (QChar ('x')) * 80 +
305 firstPage->verticalScrollBar()->width() +
306 firstPage->frameWidth() * 2 +
307 /* mLogList margin */ 10 * 2 +
308 /* CentralWidget margin */ 10 * 2;
309 resize (fullWidth, height());
310 mFirstRun = false;
311 }
312 }
313
314 /* Make sure the log view widget has the focus */
315 QWidget *w = currentLogPage();
316 if (w)
317 w->setFocus();
318}
319
320QTextEdit* VBoxVMLogViewer::createLogPage (const QString &aName)
321{
322 QWidget *pageContainer = new QWidget();
323 QVBoxLayout *pageLayout = new QVBoxLayout (pageContainer);
324 QTextEdit *logViewer = new QTextEdit (pageContainer);
325 pageLayout->addWidget (logViewer);
326 pageLayout->setContentsMargins (10, 10, 10, 10);
327
328 QFont font = logViewer->currentFont();
329 font.setFamily ("Courier New,courier");
330 logViewer->setFont (font);
331 logViewer->setWordWrapMode (QTextOption::NoWrap);
332 logViewer->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
333 logViewer->setReadOnly (true);
334
335 mLogList->addTab (pageContainer, aName);
336 return logViewer;
337}
338
339
340VBoxLogSearchPanel::VBoxLogSearchPanel (QWidget *aParent,
341 VBoxVMLogViewer *aViewer)
342 : QIWithRetranslateUI<QWidget> (aParent)
343 , mViewer (aViewer)
344 , mButtonClose (0)
345 , mSearchName (0), mSearchString (0)
346 , mButtonsNextPrev (0)
347 , mCaseSensitive (0)
348 , mWarningSpacer (0), mWarningIcon (0), mWarningString (0)
349{
350 mButtonClose = new UIMiniCancelButton (this);
351 connect (mButtonClose, SIGNAL (clicked()), this, SLOT (hide()));
352
353 mSearchName = new QLabel (this);
354 mSearchString = new UISearchField (this);
355 mSearchString->setSizePolicy (QSizePolicy::Preferred,
356 QSizePolicy::Fixed);
357 connect (mSearchString, SIGNAL (textChanged (const QString &)),
358 this, SLOT (findCurrent (const QString &)));
359
360 mButtonsNextPrev = new UIRoundRectSegmentedButton(2, this);
361 mButtonsNextPrev->setEnabled (0, false);
362 mButtonsNextPrev->setEnabled (1, false);
363#ifndef Q_WS_MAC
364 /* No icons on the Mac */
365 mButtonsNextPrev->setIcon(0, UIIconPool::defaultIcon(UIIconPool::ArrowBackIcon, this));
366 mButtonsNextPrev->setIcon(1, UIIconPool::defaultIcon(UIIconPool::ArrowForwardIcon, this));
367#endif /* !Q_WS_MAC */
368 connect (mButtonsNextPrev, SIGNAL (clicked (int)), this, SLOT (find (int)));
369
370 mCaseSensitive = new QCheckBox (this);
371
372 mWarningSpacer = new QSpacerItem (0, 0, QSizePolicy::Fixed,
373 QSizePolicy::Minimum);
374 mWarningIcon = new QLabel (this);
375 mWarningIcon->hide();
376
377 QIcon icon = UIIconPool::defaultIcon(UIIconPool::MessageBoxWarningIcon, this);
378 if (!icon.isNull())
379 mWarningIcon->setPixmap (icon.pixmap (16, 16));
380 mWarningString = new QLabel (this);
381 mWarningString->hide();
382
383 QSpacerItem *spacer = new QSpacerItem (0, 0, QSizePolicy::Expanding,
384 QSizePolicy::Minimum);
385
386#ifdef VBOX_DARWIN_USE_NATIVE_CONTROLS
387 QFont font = mSearchName->font();
388 font.setPointSize (::darwinSmallFontSize());
389 mSearchName->setFont (font);
390 mCaseSensitive->setFont (font);
391 mWarningString->setFont (font);
392#endif /* VBOX_DARWIN_USE_NATIVE_CONTROLS */
393
394 QHBoxLayout *mainLayout = new QHBoxLayout (this);
395 mainLayout->setSpacing (5);
396 mainLayout->setContentsMargins (0, 0, 0, 0);
397 mainLayout->addWidget (mButtonClose);
398 mainLayout->addWidget (mSearchName);
399 mainLayout->addWidget (mSearchString);
400 mainLayout->addWidget (mButtonsNextPrev);
401 mainLayout->addWidget (mCaseSensitive);
402 mainLayout->addItem (mWarningSpacer);
403 mainLayout->addWidget (mWarningIcon);
404 mainLayout->addWidget (mWarningString);
405 mainLayout->addItem (spacer);
406
407 setFocusProxy (mCaseSensitive);
408 qApp->installEventFilter (this);
409
410 retranslateUi();
411}
412
413void VBoxLogSearchPanel::retranslateUi()
414{
415 mButtonClose->setToolTip (tr ("Close the search panel"));
416
417 mSearchName->setText (tr ("Find "));
418 mSearchString->setToolTip (tr ("Enter a search string here"));
419
420 mButtonsNextPrev->setTitle (0, tr ("&Previous"));
421 mButtonsNextPrev->setToolTip (0, tr ("Search for the previous occurrence "
422 "of the string"));
423
424 mButtonsNextPrev->setTitle (1, tr ("&Next"));
425 mButtonsNextPrev->setToolTip (1, tr ("Search for the next occurrence of "
426 "the string"));
427
428 mCaseSensitive->setText (tr ("C&ase Sensitive"));
429 mCaseSensitive->setToolTip (tr ("Perform case sensitive search "
430 "(when checked)"));
431
432 mWarningString->setText (tr ("String not found"));
433}
434
435void VBoxLogSearchPanel::findCurrent (const QString &aSearchString)
436{
437 mButtonsNextPrev->setEnabled (0, aSearchString.length());
438 mButtonsNextPrev->setEnabled (1, aSearchString.length());
439 toggleWarning (!aSearchString.length());
440 if (aSearchString.length())
441 search (true, true);
442 else
443 {
444 QTextEdit *browser = mViewer->currentLogPage();
445 if (browser && browser->textCursor().hasSelection())
446 {
447 QTextCursor cursor = browser->textCursor();
448 cursor.setPosition (cursor.anchor());
449 browser->setTextCursor (cursor);
450 }
451 }
452}
453
454void VBoxLogSearchPanel::search (bool aForward,
455 bool aStartCurrent)
456{
457 QTextEdit *browser = mViewer->currentLogPage();
458 if (!browser) return;
459
460 QTextCursor cursor = browser->textCursor();
461 int pos = cursor.position();
462 int anc = cursor.anchor();
463
464 QString text = browser->toPlainText();
465 int diff = aStartCurrent ? 0 : 1;
466
467 int res = -1;
468 if (aForward && (aStartCurrent || pos < text.size() - 1))
469 res = text.indexOf (mSearchString->text(),
470 anc + diff,
471 mCaseSensitive->isChecked() ?
472 Qt::CaseSensitive : Qt::CaseInsensitive);
473 else if (!aForward && anc > 0)
474 res = text.lastIndexOf (mSearchString->text(), anc - 1,
475 mCaseSensitive->isChecked() ?
476 Qt::CaseSensitive : Qt::CaseInsensitive);
477
478 if (res != -1)
479 {
480 cursor.movePosition (QTextCursor::Start,
481 QTextCursor::MoveAnchor);
482 cursor.movePosition (QTextCursor::NextCharacter,
483 QTextCursor::MoveAnchor, res);
484 cursor.movePosition (QTextCursor::NextCharacter,
485 QTextCursor::KeepAnchor,
486 mSearchString->text().size());
487 browser->setTextCursor (cursor);
488 }
489
490 toggleWarning (res != -1);
491}
492
493bool VBoxLogSearchPanel::eventFilter (QObject *aObject, QEvent *aEvent)
494{
495 /* Check that the object is a child of the parent of the search panel. If
496 * not do not proceed, cause we get all key events from all windows here. */
497 QObject *pp = aObject;
498 while(pp && pp != parentWidget()) { pp = pp->parent(); };
499 if (!pp)
500 return false;
501 switch (aEvent->type())
502 {
503 case QEvent::KeyPress:
504 {
505 QKeyEvent *e = static_cast<QKeyEvent*> (aEvent);
506
507 /* handle the Enter keypress for mSearchString
508 * widget as a search next string action */
509 if (aObject == mSearchString &&
510 (e->QInputEvent::modifiers() == 0 ||
511 e->QInputEvent::modifiers() & Qt::KeypadModifier) &&
512 (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return))
513 {
514 mButtonsNextPrev->animateClick (1);
515 return true;
516 }
517 /* handle other search next/previous shortcuts */
518 else if (e->key() == Qt::Key_F3)
519 {
520 if (e->QInputEvent::modifiers() == 0)
521 mButtonsNextPrev->animateClick (1);
522 else if (e->QInputEvent::modifiers() == Qt::ShiftModifier)
523 mButtonsNextPrev->animateClick (0);
524 return true;
525 }
526 /* handle ctrl-f key combination as a shortcut to
527 * move to the search field */
528 else if (e->QInputEvent::modifiers() == Qt::ControlModifier &&
529 e->key() == Qt::Key_F)
530 {
531 if (mViewer->currentLogPage())
532 {
533 if (isHidden()) show();
534 mSearchString->setFocus();
535 return true;
536 }
537 }
538 /* handle alpha-numeric keys to implement the
539 * "find as you type" feature */
540 else if ((e->QInputEvent::modifiers() & ~Qt::ShiftModifier) == 0 &&
541 e->key() >= Qt::Key_Exclam &&
542 e->key() <= Qt::Key_AsciiTilde)
543 {
544 if (mViewer->currentLogPage())
545 {
546 if (isHidden()) show();
547 mSearchString->setFocus();
548 mSearchString->insert (e->text());
549 return true;
550 }
551 }
552 break;
553 }
554 default:
555 break;
556 }
557 return false;
558}
559
560void VBoxLogSearchPanel::showEvent (QShowEvent *aEvent)
561{
562 QWidget::showEvent (aEvent);
563 mSearchString->setFocus();
564 mSearchString->selectAll();
565}
566
567void VBoxLogSearchPanel::hideEvent (QHideEvent *aEvent)
568{
569 QWidget *focus = QApplication::focusWidget();
570 if (focus &&
571 focus->parent() == this)
572 focusNextPrevChild (true);
573 QWidget::hideEvent (aEvent);
574}
575
576void VBoxLogSearchPanel::toggleWarning (bool aHide)
577{
578 mWarningSpacer->changeSize (aHide ? 0 : 16, 0, QSizePolicy::Fixed,
579 QSizePolicy::Minimum);
580 if (!aHide)
581 mSearchString->markError();
582 else
583 mSearchString->unmarkError();
584 mWarningIcon->setHidden (aHide);
585 mWarningString->setHidden (aHide);
586}
587
588void VBoxLogSearchPanel::find (int aButton)
589{
590 if (aButton == 0)
591 findBack();
592 else
593 findNext();
594}
595
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use