Warning: That file was not part of the compilation database. It may have many parsing errors.
1 | /**************************************************************************** |
---|---|
2 | ** |
3 | ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the tools applications of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and Digia. For licensing terms and |
14 | ** conditions see http://qt.digia.com/licensing. For further information |
15 | ** use the contact form at http://qt.digia.com/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 2.1 requirements |
23 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
24 | ** |
25 | ** In addition, as a special exception, Digia gives you certain additional |
26 | ** rights. These rights are described in the Digia Qt LGPL Exception |
27 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
28 | ** |
29 | ** GNU General Public License Usage |
30 | ** Alternatively, this file may be used under the terms of the GNU |
31 | ** General Public License version 3.0 as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL included in the |
33 | ** packaging of this file. Please review the following information to |
34 | ** ensure the GNU General Public License version 3.0 requirements will be |
35 | ** met: http://www.gnu.org/copyleft/gpl.html. |
36 | ** |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | #include "qvfb.h" |
43 | #include "qvfbview.h" |
44 | #include "qvfbhdr.h" |
45 | #ifdef Q_WS_X11 |
46 | #include "qvfbx11view.h" |
47 | #endif |
48 | #include "qvfbratedlg.h" |
49 | #include "ui_config.h" |
50 | #include "qanimationwriter.h" |
51 | |
52 | #include <deviceskin.h> |
53 | |
54 | #include <QMenuBar> |
55 | #include <QMenu> |
56 | #include <QApplication> |
57 | #include <QMessageBox> |
58 | #include <QComboBox> |
59 | #include <QLabel> |
60 | #include <QFileDialog> |
61 | #include <QSlider> |
62 | #include <QSpinBox> |
63 | #include <QLayout> |
64 | #include <QRadioButton> |
65 | #include <QImage> |
66 | #include <QPixmap> |
67 | #include <QCheckBox> |
68 | #include <QCursor> |
69 | #include <QTime> |
70 | #include <QScrollArea> |
71 | #include <QProgressBar> |
72 | #include <QPushButton> |
73 | #include <QTextStream> |
74 | #include <QFile> |
75 | #include <QFileInfo> |
76 | #include <QDebug> |
77 | |
78 | #include <unistd.h> |
79 | #include <stdlib.h> |
80 | #include <sys/types.h> |
81 | |
82 | QT_BEGIN_NAMESPACE |
83 | |
84 | // ===================================================================== |
85 | |
86 | static const char *red_on_led_xpm[] = { |
87 | "11 11 10 1", |
88 | " c None", |
89 | ". c #FF0000", |
90 | "+ c #FF4C4C", |
91 | "@ c #FF7A7A", |
92 | "# c #D30000", |
93 | "$ c #FF9393", |
94 | "% c #BA0000", |
95 | "& c #FFFFFF", |
96 | "* c #7F0000", |
97 | "= c #000000", |
98 | " ", |
99 | " .++@@ ", |
100 | " .....+@ ", |
101 | " ##...$.+@ ", |
102 | " %#..$&$.+ ", |
103 | " *#...$..+ ", |
104 | " *%#...... ", |
105 | " =*%#..... ", |
106 | " =*%###. ", |
107 | " ===*. ", |
108 | " "}; |
109 | |
110 | static const char *red_off_led_xpm[] = { |
111 | "11 11 12 1", |
112 | " c None", |
113 | ". c #CDB7B4", |
114 | "+ c #D2BFBD", |
115 | "@ c #DBCBCA", |
116 | "# c #E5D9D8", |
117 | "$ c #BC9E9B", |
118 | "% c #E2D6D5", |
119 | "& c #AD8986", |
120 | "* c #FFFFFF", |
121 | "= c #A8817D", |
122 | "- c #B2908D", |
123 | "; c #6F4D4A", |
124 | " ", |
125 | " .++@# ", |
126 | " .....@# ", |
127 | " $$...%.@# ", |
128 | " &$..%*%.@ ", |
129 | " =-...%..+ ", |
130 | " =&-...... ", |
131 | " ;==-..... ", |
132 | " ;=&-$$. ", |
133 | " ;==&$ ", |
134 | " "}; |
135 | |
136 | static bool copyButtonConfiguration(const QString &prefix, int displayId) |
137 | { |
138 | const QString destDir = QT_VFB_DATADIR(displayId).append("/"); |
139 | const QFileInfo src(prefix + QLatin1String("defaultbuttons.conf")); |
140 | const QFileInfo dst(destDir + QLatin1String("defaultbuttons.conf")); |
141 | unlink(dst.absoluteFilePath().toLatin1().constData()); |
142 | if (!src.exists()) |
143 | return false; |
144 | const bool rc = QFile::copy(src.absoluteFilePath(), dst.absoluteFilePath()); |
145 | if (!rc) |
146 | qWarning() << "Failed to copy the button configuration file "<< src.absoluteFilePath() << " to "<< dst.absoluteFilePath() << |
147 | return rc; |
148 | } |
149 | |
150 | // ===================================================================== |
151 | |
152 | class AnimationSaveWidget : public QWidget { |
153 | Q_OBJECT |
154 | public: |
155 | AnimationSaveWidget(QVFbAbstractView *v); |
156 | ~AnimationSaveWidget(); |
157 | bool detectPpmtoMpegCommand(); |
158 | void timerEvent(QTimerEvent *te); |
159 | void convertToMpeg(QString filename); |
160 | void removeTemporaryFiles(); |
161 | protected slots: |
162 | void toggleRecord(); |
163 | void reset(); |
164 | void save(); |
165 | private: |
166 | QVFbAbstractView *view; |
167 | QProgressBar *progressBar; |
168 | QLabel *statusText; |
169 | bool haveMpeg, savingAsMpeg, recording; |
170 | QCheckBox *mpegSave; |
171 | QAnimationWriter *animation; |
172 | QPushButton *recBt, *resetBt, *saveBt; |
173 | QLabel *timeDpy, *recLED; |
174 | int timerId, progressTimerId; |
175 | QPixmap recOn, recOff; |
176 | QTime tm; |
177 | int elapsed, imageNum; |
178 | }; |
179 | |
180 | // ===================================================================== |
181 | |
182 | Zoomer::Zoomer(QVFb* target) : |
183 | qvfb(target) |
184 | { |
185 | QVBoxLayout *layout = new QVBoxLayout(this); |
186 | QSlider *sl = new QSlider(Qt::Horizontal); |
187 | sl->setMinimum(10); |
188 | sl->setMaximum(64); |
189 | sl->setPageStep(1); |
190 | sl->setValue(32); |
191 | layout->addWidget(sl); |
192 | connect(sl,SIGNAL(valueChanged(int)),this,SLOT(zoom(int))); |
193 | label = new QLabel(); |
194 | layout->addWidget(label); |
195 | } |
196 | |
197 | void Zoomer::zoom(int z) |
198 | { |
199 | double d = (double)z/32.0; |
200 | qvfb->setZoom(d); |
201 | label->setText(QString::number(d, |
202 | } |
203 | |
204 | // ===================================================================== |
205 | |
206 | QVFb::QVFb( int display_id, int w, int h, int d, int r, const QString &skin, DisplayType displayType, QWidget *parent, Qt::WindowFlags flags ) |
207 | : QMainWindow( parent, flags ) |
208 | { |
209 | this->displayType = displayType; |
210 | view = 0; |
211 | secondaryView = 0; |
212 | scroller = 0; |
213 | this->skin = 0; |
214 | currentSkinIndex = -1; |
215 | findSkins(skin); |
216 | zoomer = 0; |
217 | QPixmap pix(":/res/images/logo.png"); |
218 | setWindowIcon( pix ); |
219 | rateDlg = 0; |
220 | refreshRate = 30; |
221 | // Create the menu first to avoid scroll bars in the main window |
222 | createMenu( menuBar() ); |
223 | init( display_id, w, h, d, r, skin ); |
224 | enableCursor( true ); |
225 | } |
226 | |
227 | QVFb::~QVFb() |
228 | { |
229 | } |
230 | |
231 | void QVFb::popupMenu() |
232 | { |
233 | QMenu *pm = new QMenu( this ); |
234 | createMenu( pm ); |
235 | pm->exec(QCursor::pos()); |
236 | } |
237 | |
238 | void QVFb::init( int display_id, int pw, int ph, int d, int r, const QString& skin_name ) |
239 | { |
240 | delete view; |
241 | view = 0; |
242 | delete secondaryView; |
243 | secondaryView = 0; |
244 | delete scroller; |
245 | scroller = 0; |
246 | delete skin; |
247 | skin = 0; |
248 | |
249 | skinscaleH = skinscaleV = 1.0; |
250 | QVFbView::Rotation rot = ((r == 90) ? QVFbView::Rot90 : |
251 | ((r == 180) ? QVFbView::Rot180 : |
252 | ((r == 270) ? QVFbView::Rot270 : |
253 | QVFbView::Rot0 ))); |
254 | if ( !skin_name.isEmpty() ) { |
255 | const bool vis = isVisible(); |
256 | DeviceSkinParameters parameters; |
257 | QString readError; |
258 | if (parameters.read(skin_name,DeviceSkinParameters::ReadAll, &readError)) { |
259 | skin = new DeviceSkin(parameters, this); |
260 | connect(skin, SIGNAL(popupMenu()), this, SLOT(popupMenu())); |
261 | const int sw = parameters.screenSize().width(); |
262 | const int sh = parameters.screenSize().height(); |
263 | const int sd = parameters.screenDepth; |
264 | if (!pw) pw = sw; |
265 | if (!ph) ph = sh; |
266 | if (d < 0) { |
267 | if (sd) |
268 | d = sd; |
269 | else |
270 | d = -d; |
271 | } |
272 | if (vis) |
273 | hide(); |
274 | menuBar()->hide(); |
275 | scroller = 0; |
276 | #ifdef Q_WS_X11 |
277 | if (displayType == X11) |
278 | view = new QVFbX11View( display_id, pw, ph, d, rot, skin ); |
279 | else |
280 | #endif |
281 | view = new QVFbView( display_id, pw, ph, d, rot, skin ); |
282 | skin->setView( view ); |
283 | view->setContentsMargins( 0, 0, 0, 0 ); |
284 | view->setTouchscreenEmulation(!parameters.hasMouseHover); |
285 | connect(skin, SIGNAL(skinKeyPressEvent(int,QString,bool)), view, SLOT(skinKeyPressEvent(int,QString,bool))); |
286 | connect(skin, SIGNAL(skinKeyReleaseEvent(int,QString,bool)), view, SLOT(skinKeyReleaseEvent(int,QString,bool))); |
287 | |
288 | copyButtonConfiguration(skin->prefix(), view->displayId()); |
289 | |
290 | setCentralWidget( skin ); |
291 | adjustSize(); |
292 | skinscaleH = (double)sw/pw; |
293 | skinscaleV = (double)sh/ph; |
294 | if ( skinscaleH != 1.0 || skinscaleH != 1.0 ) |
295 | setZoom(skinscaleH); |
296 | view->show(); |
297 | |
298 | if (parameters.hasSecondaryScreen()) { |
299 | const QSize ssize = parameters.secondaryScreenSize(); |
300 | // assumes same depth and rotation |
301 | #ifdef Q_WS_X11 |
302 | if (displayType == X11) |
303 | secondaryView = new QVFbX11View( display_id+1, ssize.width(), ssize.height(), d, rot, skin ); |
304 | else |
305 | #endif |
306 | secondaryView = new QVFbView( display_id+1, ssize.width(), ssize.height(), d, rot, skin ); |
307 | skin->setSecondaryView(secondaryView); |
308 | secondaryView->show(); |
309 | } |
310 | |
311 | if ( vis ) show(); |
312 | } else { |
313 | qWarning("%s", qPrintable(readError)); |
314 | } |
315 | } |
316 | |
317 | // If we failed to get a skin or we were not supplied |
318 | // with one then fallback to a framebuffer without |
319 | // a skin |
320 | if (!skin){ |
321 | // Default values |
322 | if (!pw) |
323 | pw = 240; |
324 | if (!ph) |
325 | ph = 320; |
326 | if (!d) |
327 | d = 32; |
328 | else if (d < 0) |
329 | d = -d; |
330 | |
331 | if (currentSkinIndex != -1) { |
332 | clearMask(); |
333 | setParent( 0, 0 ); |
334 | move( pos() ); |
335 | show(); |
336 | //unset fixed size: |
337 | setMinimumSize(0,0); |
338 | setMaximumSize(QWIDGETSIZE_MAX,QWIDGETSIZE_MAX); |
339 | } |
340 | menuBar()->show(); |
341 | scroller = new QScrollArea(this); |
342 | scroller->setFocusPolicy(Qt::NoFocus); // don't steal key events from the embedded app |
343 | #ifdef Q_WS_X11 |
344 | if (displayType == X11) |
345 | view = new QVFbX11View( display_id, pw, ph, d, rot, scroller ); |
346 | else |
347 | #endif |
348 | view = new QVFbView( display_id, pw, ph, d, rot, scroller ); |
349 | scroller->setWidget(view); |
350 | view->setContentsMargins( 0, 0, 0, 0 ); |
351 | setCentralWidget(scroller); |
352 | ph += 2; // avoid scrollbar |
353 | scroller->show(); |
354 | // delete defaultbuttons.conf if it was left behind... |
355 | unlink(QFileInfo(QT_VFB_DATADIR(view->displayId()).append("/defaultbuttons.conf")).absoluteFilePath().toLatin1().constData()); |
356 | if (secondaryView) |
357 | unlink(QFileInfo(QT_VFB_DATADIR(view->displayId() + 1).append("/defaultbuttons.conf")).absoluteFilePath().toLatin1().constData()); |
358 | } |
359 | view->setRate(refreshRate); |
360 | if (secondaryView) { |
361 | secondaryView->setRate(refreshRate); |
362 | } |
363 | // Resize QVFb to the new size |
364 | QSize newSize = view->sizeHint(); |
365 | |
366 | // ... fudge factor |
367 | newSize += QSize(20, 35); |
368 | |
369 | resize(newSize); |
370 | |
371 | setWindowTitle(QString("Virtual framebuffer %1x%2 %3bpp Display :%4 Rotate %5") |
372 | .arg(view->displayWidth()).arg(view->displayHeight()) |
373 | .arg(d).arg(display_id).arg(r)); |
374 | } |
375 | |
376 | void QVFb::enableCursor( bool e ) |
377 | { |
378 | if ( skin && skin->hasCursor() ) { |
379 | view->setCursor( Qt::BlankCursor ); |
380 | if (secondaryView) |
381 | secondaryView->setCursor( Qt::BlankCursor ); |
382 | } else { |
383 | view->setCursor( e ? Qt::ArrowCursor : Qt::BlankCursor ); |
384 | if (secondaryView) |
385 | secondaryView->setCursor( e ? Qt::ArrowCursor : Qt::BlankCursor ); |
386 | } |
387 | cursorAction->setChecked( e ); |
388 | } |
389 | |
390 | template <typename T> |
391 | void QVFb::createMenu(T *menu) |
392 | { |
393 | menu->addMenu( createFileMenu() ); |
394 | menu->addMenu( createViewMenu() ); |
395 | menu->addSeparator(); |
396 | menu->addMenu( createHelpMenu() ); |
397 | } |
398 | |
399 | QMenu* QVFb::createFileMenu() |
400 | { |
401 | QMenu *file = new QMenu( tr("&File"), this ); |
402 | file->addAction( tr("&Configure..."), this, SLOT(configure()), QKeySequence::Preferences ); |
403 | file->addSeparator(); |
404 | file->addAction( tr("&Save image..."), this, SLOT(saveImage()), QKeySequence::Save ); |
405 | file->addAction( tr("&Animation..."), this, SLOT(toggleAnimation()), 0 ); |
406 | file->addSeparator(); |
407 | file->addAction( tr("&Quit"), qApp, SLOT(quit()), QKeySequence::Quit ); |
408 | return file; |
409 | } |
410 | |
411 | QMenu* QVFb::createViewMenu() |
412 | { |
413 | viewMenu = new QMenu( tr("&View"), this ); |
414 | cursorAction = viewMenu->addAction( tr("Show &Cursor"), this, |
415 | SLOT(toggleCursor()) ); |
416 | cursorAction->setCheckable(true); |
417 | if ( view ) |
418 | enableCursor(true); |
419 | viewMenu->addAction( tr("&Refresh Rate..."), this, SLOT(changeRate()) ); |
420 | viewMenu->addSeparator(); |
421 | viewMenu->addAction( tr("&No rotation"), this, SLOT(setRot0()) ); |
422 | viewMenu->addAction( tr("&90\260 rotation"), this, SLOT(setRot90()) ); |
423 | viewMenu->addAction( tr("1&80\260 rotation"), this, SLOT(setRot180()) ); |
424 | viewMenu->addAction( tr("2&70\260 rotation"), this, SLOT(setRot270()) ); |
425 | viewMenu->addSeparator(); |
426 | viewMenu->addAction( tr("Zoom scale &0.5"), this, SLOT(setZoomHalf()) ); |
427 | viewMenu->addAction( tr("Zoom scale 0.7&5"), this, SLOT(setZoom075()) ); |
428 | viewMenu->addAction( tr("Zoom scale &1"), this, SLOT(setZoom1()) ); |
429 | viewMenu->addAction( tr("Zoom scale &2"), this, SLOT(setZoom2()) ); |
430 | viewMenu->addAction( tr("Zoom scale &3"), this, SLOT(setZoom3()) ); |
431 | viewMenu->addAction( tr("Zoom scale &4"), this, SLOT(setZoom4()) ); |
432 | viewMenu->addSeparator(); |
433 | viewMenu->addAction( tr("Zoom &scale..."), this, SLOT(setZoom()) ); |
434 | return viewMenu; |
435 | } |
436 | |
437 | |
438 | QMenu* QVFb::createHelpMenu() |
439 | { |
440 | QMenu *help = new QMenu( tr("&Help"), this ); |
441 | help->addAction(tr("&About..."), this, SLOT(about())); |
442 | return help; |
443 | } |
444 | |
445 | void QVFb::setZoom(double z) |
446 | { |
447 | view->setZoom(z,z*skinscaleV/skinscaleH); |
448 | if (secondaryView) |
449 | secondaryView->setZoom(z,z*skinscaleV/skinscaleH); |
450 | |
451 | if (skin) { |
452 | skin->setTransform(QMatrix().scale(z/skinscaleH,z/skinscaleV).rotate(90*view->displayRotation())); |
453 | if (secondaryView) |
454 | secondaryView->setFixedSize( |
455 | int(secondaryView->displayWidth()*z), |
456 | int(secondaryView->displayHeight()*z*skinscaleV/skinscaleH)); |
457 | } |
458 | } |
459 | |
460 | void QVFb::setRotation(QVFbView::Rotation r) |
461 | { |
462 | view->setRotation(r); |
463 | if (secondaryView) |
464 | secondaryView->setRotation(r); |
465 | setZoom(view->zoomH()); |
466 | } |
467 | |
468 | void QVFb::setRot0() |
469 | { |
470 | setRotation(QVFbView::Rot0); |
471 | } |
472 | |
473 | void QVFb::setRot90() |
474 | { |
475 | setRotation(QVFbView::Rot90); |
476 | } |
477 | |
478 | void QVFb::setRot180() |
479 | { |
480 | setRotation(QVFbView::Rot180); |
481 | } |
482 | |
483 | void QVFb::setRot270() |
484 | { |
485 | setRotation(QVFbView::Rot270); |
486 | } |
487 | |
488 | void QVFb::setZoomHalf() |
489 | { |
490 | setZoom(0.5); |
491 | } |
492 | |
493 | void QVFb::setZoom075() |
494 | { |
495 | setZoom(0.75); |
496 | } |
497 | |
498 | void QVFb::setZoom1() |
499 | { |
500 | setZoom(1); |
501 | } |
502 | |
503 | void QVFb::setZoom() |
504 | { |
505 | if ( !zoomer ) |
506 | zoomer = new Zoomer(this); |
507 | zoomer->show(); |
508 | } |
509 | |
510 | void QVFb::setZoom2() |
511 | { |
512 | setZoom(2); |
513 | } |
514 | |
515 | void QVFb::setZoom3() |
516 | { |
517 | setZoom(3); |
518 | } |
519 | |
520 | void QVFb::setZoom4() |
521 | { |
522 | setZoom(4); |
523 | } |
524 | |
525 | void QVFb::saveImage() |
526 | { |
527 | QImage img = view->image(); |
528 | QString filename = QFileDialog::getSaveFileName(this, tr("Save Main Screen image"), tr( "snapshot.png"), tr( "Portable Network Graphics (*.png)")); |
529 | if (!filename.isEmpty()){ |
530 | if(!img.save(filename,"PNG")) |
531 | QMessageBox::critical(this, tr("Save Main Screen Image"), tr( "Save failed. Check that you have permission to write to the target directory.")); |
532 | } |
533 | if (secondaryView) { |
534 | QImage img = view->image(); |
535 | QString filename = QFileDialog::getSaveFileName(this, tr("Save Second Screen image"), tr( "snapshot.png"), tr( "Portable Network Graphics (*.png)")); |
536 | if (!filename.isEmpty()) { |
537 | if(!img.save(filename,"PNG")) |
538 | QMessageBox::critical(this, tr("Save Second Screen Image"), tr( "Save failed. Check that you have permission to write to the target directory.")); |
539 | } |
540 | } |
541 | } |
542 | |
543 | void QVFb::toggleAnimation() |
544 | { |
545 | static AnimationSaveWidget *animWidget = 0; |
546 | if ( !animWidget ) |
547 | animWidget = new AnimationSaveWidget(view); |
548 | if ( animWidget->isVisible() ) |
549 | animWidget->hide(); |
550 | else |
551 | animWidget->show(); |
552 | } |
553 | |
554 | void QVFb::toggleCursor() |
555 | { |
556 | enableCursor(cursorAction->isChecked()); |
557 | } |
558 | |
559 | void QVFb::changeRate() |
560 | { |
561 | if ( !rateDlg ) { |
562 | rateDlg = new QVFbRateDialog( refreshRate, this ); |
563 | connect( rateDlg, SIGNAL(updateRate(int)), this, SLOT(setRate(int)) ); |
564 | } |
565 | |
566 | rateDlg->show(); |
567 | } |
568 | |
569 | void QVFb::setRate(int i) |
570 | { |
571 | refreshRate = i; |
572 | view->setRate(i); |
573 | if (secondaryView) |
574 | secondaryView->setRate(i); |
575 | } |
576 | |
577 | |
578 | void QVFb::about() |
579 | { |
580 | QMessageBox::about(this, tr("About QVFB"), tr( |
581 | "<h2>The Qt for Embedded Linux Virtual X11 Framebuffer</h2>" |
582 | "<p>This application runs under Qt for X11, emulating a simple framebuffer, " |
583 | "which the Qt for Embedded Linux server and clients can attach to just as if " |
584 | "it was a hardware Linux framebuffer. " |
585 | "<p>With the aid of this development tool, you can develop Qt for Embedded " |
586 | "Linux applications under X11 without having to switch to a virtual console. " |
587 | "This means you can comfortably use your other development tools such " |
588 | "as GUI profilers and debuggers." |
589 | )); |
590 | } |
591 | |
592 | void QVFb::findSkins(const QString ¤tSkin) |
593 | { |
594 | skinnames.clear(); |
595 | skinfiles.clear(); |
596 | QDir dir(":/skins/", "*.skin"); |
597 | const QFileInfoList l = dir.entryInfoList(); |
598 | int i = 1; // "None" is already in list at index 0 |
599 | for (QFileInfoList::const_iterator it = l.begin(); it != l.end(); ++it) { |
600 | skinnames.append((*it).baseName()); // should perhaps be in file |
601 | skinfiles.append((*it).filePath()); |
602 | if (((*it).baseName() + ".skin") == currentSkin) |
603 | currentSkinIndex = i; |
604 | i++; |
605 | } |
606 | } |
607 | |
608 | class Config : public QDialog, public Ui::Config |
609 | { |
610 | public: |
611 | Config(QWidget *parent) |
612 | : QDialog(parent) |
613 | { |
614 | setupUi(this); |
615 | setModal(true); |
616 | |
617 | connect(buttonOk, SIGNAL(clicked()), this, SLOT(accept())); |
618 | connect(buttonCancel, SIGNAL(clicked()), this, SLOT(reject())); |
619 | } |
620 | }; |
621 | |
622 | void QVFb::configure() |
623 | { |
624 | config = new Config(this); |
625 | |
626 | int w = view->displayWidth(); |
627 | int h = view->displayHeight(); |
628 | |
629 | // Need to block signals, because we connect to animateClick(), |
630 | // since QCheckBox doesn't have setChecked(bool) in 2.x. |
631 | chooseSize(QSize(w,h)); |
632 | config->skin->insertItems(config->skin->count(), skinnames); |
633 | if (currentSkinIndex > 0) |
634 | config->skin->setCurrentIndex(currentSkinIndex); |
635 | config->skin->addItem(tr("Browse...")); |
636 | config->touchScreen->setChecked(view->touchScreenEmulation()); |
637 | config->lcdScreen->setChecked(view->lcdScreenEmulation()); |
638 | chooseDepth(view->displayDepth(), view->displayFormat()); |
639 | config->rgbSwapped->setChecked(view->rgbSwapped()); |
640 | connect(config->skin, SIGNAL(activated(int)), this, SLOT(skinConfigChosen(int))); |
641 | if ( view->gammaRed() == view->gammaGreen() && view->gammaGreen() == view->gammaBlue() ) { |
642 | config->gammaslider->setValue(int(view->gammaRed()*400)); |
643 | config->rslider->setValue(100); |
644 | config->gslider->setValue(100); |
645 | config->bslider->setValue(100); |
646 | } else { |
647 | config->gammaslider->setValue(100); |
648 | config->rslider->setValue(int(view->gammaRed()*400)); |
649 | config->gslider->setValue(int(view->gammaGreen()*400)); |
650 | config->bslider->setValue(int(view->gammaBlue()*400)); |
651 | } |
652 | connect(config->gammaslider, SIGNAL(valueChanged(int)), this, SLOT(setGamma400(int))); |
653 | connect(config->rslider, SIGNAL(valueChanged(int)), this, SLOT(setR400(int))); |
654 | connect(config->gslider, SIGNAL(valueChanged(int)), this, SLOT(setG400(int))); |
655 | connect(config->bslider, SIGNAL(valueChanged(int)), this, SLOT(setB400(int))); |
656 | updateGammaLabels(); |
657 | |
658 | double ogr=view->gammaRed(), ogg=view->gammaGreen(), ogb=view->gammaBlue(); |
659 | qApp->setQuitOnLastWindowClosed(false); |
660 | |
661 | hide(); |
662 | if ( config->exec() ) { |
663 | int id = view->displayId(); // not settable yet |
664 | if ( config->size_176_220->isChecked() ) { |
665 | w=176; h=220; |
666 | } else if ( config->size_240_320->isChecked() ) { |
667 | w=240; h=320; |
668 | } else if ( config->size_320_240->isChecked() ) { |
669 | w=320; h=240; |
670 | } else if ( config->size_640_480->isChecked() ) { |
671 | w=640; h=480; |
672 | } else if ( config->size_800_480->isChecked() ) { |
673 | w=800; h=480; |
674 | } else if ( config->size_800_600->isChecked() ) { |
675 | w=800; h=600; |
676 | } else if ( config->size_1024_768->isChecked() ) { |
677 | w=1024; h=768; |
678 | } else { |
679 | w=config->size_width->value(); |
680 | h=config->size_height->value(); |
681 | } |
682 | int d; |
683 | if ( config->depth_1->isChecked() ) |
684 | d=1; |
685 | else if ( config->depth_2gray->isChecked() ) |
686 | d=2; |
687 | else if ( config->depth_4gray->isChecked() ) |
688 | d=4; |
689 | else if ( config->depth_8->isChecked() ) |
690 | d=8; |
691 | else if ( config->depth_12->isChecked() ) |
692 | d=12; |
693 | else if ( config->depth_15->isChecked() ) |
694 | d = 15; |
695 | else if ( config->depth_16->isChecked() ) |
696 | d=16; |
697 | else if ( config->depth_18->isChecked() ) |
698 | d=18; |
699 | else if ( config->depth_24->isChecked() ) |
700 | d=24; |
701 | else |
702 | d=32; |
703 | QVFbView::PixelFormat displayFormat = config->depth_32_argb->isChecked() |
704 | ? QVFbView::ARGBFormat : QVFbView::DefaultFormat; |
705 | int skinIndex = config->skin->currentIndex(); |
706 | if ( w != view->displayWidth() || h != view->displayHeight() |
707 | || d != view->displayDepth() || skinIndex != currentSkinIndex ) { |
708 | QVFbView::Rotation rot = view->displayRotation(); |
709 | int r = ((rot == QVFbView::Rot90) ? 90 : |
710 | ((rot == QVFbView::Rot180) ? 180 : |
711 | ((rot == QVFbView::Rot270) ? 270 : 0 ))); |
712 | currentSkinIndex = skinIndex; |
713 | init( id, w, h, d, r, skinIndex > 0 ? skinfiles[skinIndex-1] : QString::null ); |
714 | } |
715 | view->setViewFormat(displayFormat); |
716 | view->setTouchscreenEmulation( config->touchScreen->isChecked() ); |
717 | if (view->rgbSwapped() != config->rgbSwapped->isChecked()) { |
718 | //### the windowTitle logic is inside init(), and init isn't always invoked |
719 | QString caption = windowTitle(); |
720 | if (!config->rgbSwapped->isChecked()) |
721 | caption.replace(QLatin1String(" BGR"), QString()); |
722 | else |
723 | caption.append(QLatin1String(" BGR")); |
724 | setWindowTitle(caption); |
725 | view->setRgbSwapped(config->rgbSwapped->isChecked()); |
726 | } |
727 | bool lcdEmulation = config->lcdScreen->isChecked(); |
728 | view->setLcdScreenEmulation( lcdEmulation ); |
729 | if ( lcdEmulation ) |
730 | setZoom3(); |
731 | } else { |
732 | view->setGamma(ogr, ogg, ogb); |
733 | } |
734 | show(); |
735 | qApp->setQuitOnLastWindowClosed(true); |
736 | delete config; |
737 | config=0; |
738 | } |
739 | |
740 | void QVFb::chooseSize(const QSize& sz) |
741 | { |
742 | config->size_width->blockSignals(true); |
743 | config->size_height->blockSignals(true); |
744 | config->size_width->setValue(sz.width()); |
745 | config->size_height->setValue(sz.height()); |
746 | config->size_width->blockSignals(false); |
747 | config->size_height->blockSignals(false); |
748 | config->size_custom->setChecked(true); // unless changed by settings below |
749 | config->size_176_220->setChecked(sz == QSize(176,220)); |
750 | config->size_240_320->setChecked(sz == QSize(240,320)); |
751 | config->size_320_240->setChecked(sz == QSize(320,240)); |
752 | config->size_640_480->setChecked(sz == QSize(640,480)); |
753 | config->size_800_480->setChecked(sz == QSize(800,480)); |
754 | config->size_800_600->setChecked(sz == QSize(800,600)); |
755 | config->size_1024_768->setChecked(sz == QSize(1024,768)); |
756 | } |
757 | |
758 | void QVFb::chooseDepth(int depth, QVFbView::PixelFormat displayFormat) |
759 | { |
760 | config->depth_1->setChecked(depth==1); |
761 | config->depth_2gray->setChecked(depth==2); |
762 | config->depth_4gray->setChecked(depth==4); |
763 | config->depth_8->setChecked(depth==8); |
764 | config->depth_12->setChecked(depth==12); |
765 | config->depth_15->setChecked(depth==15); |
766 | config->depth_16->setChecked(depth==16); |
767 | config->depth_18->setChecked(depth==18); |
768 | config->depth_24->setChecked(depth==24); |
769 | config->depth_32->setChecked(depth==32 && displayFormat != QVFbView::ARGBFormat); |
770 | config->depth_32_argb->setChecked(depth==32 && displayFormat == QVFbView::ARGBFormat); |
771 | } |
772 | |
773 | void QVFb::skinConfigChosen(int i) |
774 | { |
775 | if (i == config->skin->count() - 1) { // Browse... ? |
776 | QFileDialog dlg(this); |
777 | dlg.setFileMode(QFileDialog::DirectoryOnly); |
778 | dlg.setWindowTitle(tr("Load Custom Skin...")); |
779 | dlg.setFilter(tr("All QVFB Skins (*.skin)")); |
780 | dlg.setDirectory(QDir::current()); |
781 | if (dlg.exec() && dlg.selectedFiles().count() == 1) { |
782 | skinfiles.append(dlg.selectedFiles().first()); |
783 | i = skinfiles.count(); |
784 | config->skin->insertItem(i, QFileInfo(skinfiles.last()).baseName()); |
785 | config->skin->setCurrentIndex(i); |
786 | } else { |
787 | i = 0; |
788 | } |
789 | } |
790 | if ( i ) { |
791 | DeviceSkinParameters parameters; |
792 | QString readError; |
793 | if (parameters.read(skinfiles[i-1], DeviceSkinParameters::ReadSizeOnly, &readError)) { |
794 | chooseSize(parameters.screenSize()); |
795 | if (parameters.screenDepth) |
796 | chooseDepth(parameters.screenDepth,QVFbView::ARGBFormat); |
797 | config->touchScreen->setChecked(!parameters.hasMouseHover); |
798 | } else { |
799 | qWarning("%s", qPrintable(readError)); |
800 | } |
801 | } |
802 | } |
803 | |
804 | void QVFb::setGamma400(int n) |
805 | { |
806 | double g = n/100.0; |
807 | view->setGamma(config->rslider->value()/100.0*g, |
808 | config->gslider->value()/100.0*g, |
809 | config->bslider->value()/100.0*g); |
810 | updateGammaLabels(); |
811 | } |
812 | |
813 | void QVFb::setR400(int n) |
814 | { |
815 | double g = n/100.0; |
816 | view->setGamma(config->rslider->value()/100.0*g, |
817 | view->gammaGreen(), |
818 | view->gammaBlue()); |
819 | updateGammaLabels(); |
820 | } |
821 | |
822 | void QVFb::setG400(int n) |
823 | { |
824 | double g = n/100.0; |
825 | view->setGamma(view->gammaRed(), |
826 | config->gslider->value()/100.0*g, |
827 | view->gammaBlue()); |
828 | updateGammaLabels(); |
829 | } |
830 | |
831 | void QVFb::setB400(int n) |
832 | { |
833 | double g = n/100.0; |
834 | view->setGamma(view->gammaRed(), |
835 | view->gammaGreen(), |
836 | config->bslider->value()/100.0*g); |
837 | updateGammaLabels(); |
838 | } |
839 | |
840 | void QVFb::updateGammaLabels() |
841 | { |
842 | config->rlabel->setText(QString::number(view->gammaRed(), |
843 | config->glabel->setText(QString::number(view->gammaGreen(), |
844 | config->blabel->setText(QString::number(view->gammaBlue(), |
845 | } |
846 | |
847 | QSize QVFb::sizeHint() const |
848 | { |
849 | return QSize(int(view->displayWidth()*view->zoomH()), |
850 | int(menuBar()->height()+view->displayHeight()*view->zoomV())); |
851 | } |
852 | |
853 | // ===================================================================== |
854 | |
855 | AnimationSaveWidget::AnimationSaveWidget(QVFbAbstractView *v) : |
856 | QWidget((QWidget*)0,0), |
857 | view(v), recording(false), animation(0), |
858 | timerId(-1), progressTimerId(-1), |
859 | recOn(red_on_led_xpm), recOff(red_off_led_xpm), |
860 | imageNum(0) |
861 | { |
862 | // Create the animation record UI dialog |
863 | QVBoxLayout *vlayout = new QVBoxLayout( this ); |
864 | |
865 | QWidget *hbox = new QWidget( this ); |
866 | vlayout->addWidget(hbox); |
867 | QHBoxLayout *hlayout = new QHBoxLayout(hbox); |
868 | recBt = new QPushButton( tr("Record"), hbox ); |
869 | hlayout->addWidget(recBt); |
870 | resetBt = new QPushButton( tr("Reset"), hbox ); |
871 | hlayout->addWidget(resetBt); |
872 | saveBt = new QPushButton( tr("Save"), hbox ); |
873 | hlayout->addWidget(saveBt); |
874 | recBt->setFixedWidth( 100 ); |
875 | resetBt->setFixedWidth( 100 ); |
876 | saveBt->setFixedWidth( 100 ); |
877 | timeDpy = new QLabel( "00:00", hbox ); |
878 | hlayout->addWidget(timeDpy); |
879 | recLED = new QLabel( hbox ); |
880 | hlayout->addWidget(recLED); |
881 | recLED->setPixmap( recOff ); |
882 | timeDpy->setMargin( 5 ); |
883 | connect( recBt, SIGNAL(clicked()), this, SLOT(toggleRecord()) ); |
884 | connect( resetBt, SIGNAL(clicked()), this, SLOT(reset()) ); |
885 | connect( saveBt, SIGNAL(clicked()), this, SLOT(save()) ); |
886 | elapsed = 0; |
887 | vlayout->setMargin( 5 ); |
888 | vlayout->setSpacing( 5 ); |
889 | haveMpeg = detectPpmtoMpegCommand(); |
890 | mpegSave = new QCheckBox( tr("Save in MPEG format (requires netpbm package installed)"), this ); |
891 | vlayout->addWidget(mpegSave); |
892 | mpegSave->setChecked( haveMpeg ); |
893 | mpegSave->setEnabled( haveMpeg ); |
894 | savingAsMpeg = haveMpeg; |
895 | QWidget *hbox2 = new QWidget( this ); |
896 | vlayout->addWidget(hbox2); |
897 | QHBoxLayout *hlayout2 = new QHBoxLayout( hbox2 ); |
898 | statusText = new QLabel( tr("Click record to begin recording."), hbox2 ); |
899 | hlayout2->addWidget(statusText); |
900 | progressBar = new QProgressBar( hbox2 ); |
901 | progressBar->setValue( 0 ); |
902 | hlayout2->addWidget(progressBar); |
903 | progressBar->hide(); |
904 | } |
905 | |
906 | AnimationSaveWidget::~AnimationSaveWidget() |
907 | { |
908 | // clean up |
909 | removeTemporaryFiles(); |
910 | delete animation; |
911 | } |
912 | |
913 | // returns true if we have ppmtompeg command, else returns false |
914 | bool AnimationSaveWidget::detectPpmtoMpegCommand() |
915 | { |
916 | // search the PATH for the ppmtompeg command to test we can record to mpeg |
917 | QStringList paths = QString(::getenv("PATH")).split( ":"); |
918 | for ( int i = 0; i < paths.count(); i++ ) |
919 | if ( QFile::exists( paths[i] + "/"+ "ppmtompeg") ) |
920 | return true; |
921 | return false; |
922 | } |
923 | |
924 | void AnimationSaveWidget::timerEvent( QTimerEvent *te ) |
925 | { |
926 | QString str; |
927 | |
928 | // Recording timer |
929 | if ( te->timerId() == timerId ) { |
930 | |
931 | // Add a frame to the animation |
932 | if ( savingAsMpeg && view ) |
933 | view->image().save( str.sprintf("/tmp/qvfb_tmp_image_%04d.ppm", imageNum), "PPM"); |
934 | else if ( animation && view ) |
935 | animation->appendFrame(view->image());//QPoint(0,0)); |
936 | imageNum++; |
937 | |
938 | // Update the display of number of seconds that have been recorded. |
939 | int tmMsec = tm.elapsed(); |
940 | timeDpy->setText( str.sprintf("%02d:%02d", tmMsec/60000, (tmMsec%60000)/1000) ); |
941 | QObject::timerEvent( te ); |
942 | |
943 | // Make the recording LED blink |
944 | static int tick = 0; |
945 | static bool on = false; |
946 | if ( tick > 10 ) { |
947 | tick = 0; |
948 | if ( on ) |
949 | recLED->setPixmap( recOff ); |
950 | else |
951 | recLED->setPixmap( recOn ); |
952 | on = !on; |
953 | } |
954 | tick++; |
955 | } |
956 | |
957 | // Saving progress timer |
958 | if ( te->timerId() == progressTimerId ) { |
959 | // Parse output log file to work out the encoding progress. |
960 | QFile f("/tmp/qvfb_tmp_output.log"); |
961 | f.open(QIODevice::ReadOnly); |
962 | int largestNum = 0; |
963 | bool done = false; |
964 | char buffer[1024]; |
965 | while ( !f.atEnd() ) { |
966 | // example of the output log entries |
967 | // During each frame: |
968 | // "FRAME 764 (B): I BLOCKS: 0...... |
969 | // When complete: |
970 | // "======FRAMES READ: 766" |
971 | f.readLine(buffer, 1024); |
972 | str = QString(buffer); |
973 | if ( str.left(6) == "FRAME ") { |
974 | int num = str.mid(6, str.indexOf(QChar( |
975 | if ( num > largestNum ) |
976 | largestNum = num; |
977 | } else if ( str.left(18) == "======FRAMES READ:") { |
978 | done = true; |
979 | } |
980 | } |
981 | f.close(); |
982 | |
983 | // Update the progress bar with the frame we are up to |
984 | progressBar->setValue( largestNum ); |
985 | |
986 | // Finished saving |
987 | if ( done ) { |
988 | progressBar->hide(); |
989 | statusText->setText( tr("Finished saving.")); |
990 | removeTemporaryFiles(); |
991 | killTimer( progressTimerId ); |
992 | progressTimerId = -1; |
993 | reset(); |
994 | } |
995 | } |
996 | } |
997 | |
998 | // Takes the saved ppm files and converts them to a mpeg file named filename |
999 | void AnimationSaveWidget::convertToMpeg(QString filename) |
1000 | { |
1001 | recLED->setPixmap( recOff ); |
1002 | killTimer( timerId ); |
1003 | |
1004 | progressBar->show(); |
1005 | progressBar->setRange( 0, imageNum ); |
1006 | progressBar->setValue( 0 ); |
1007 | |
1008 | // Build parameter file required by ppmtompeg |
1009 | QFile file("/tmp/qvfb_tmp_ppmtompeg.params"); |
1010 | if ( file.open( QIODevice::WriteOnly ) ) { |
1011 | QTextStream t( &file ); |
1012 | t << "PATTERN IBBPBBPBBPBBPBB\n"; |
1013 | t << "OUTPUT "<< filename << "\n"; |
1014 | t << "INPUT_DIR /tmp\n"; |
1015 | t << "INPUT\n"; |
1016 | QString str; |
1017 | str = str.sprintf("%04d", imageNum - 1); |
1018 | t << "qvfb_tmp_image_*.ppm [0000-"<< str << "]\n"; |
1019 | t << "END_INPUT\n"; |
1020 | t << "BASE_FILE_FORMAT PPM\n"; |
1021 | t << "INPUT_CONVERT *\n"; |
1022 | t << "GOP_SIZE 15\n"; |
1023 | t << "SLICES_PER_FRAME 1\n"; |
1024 | t << "PIXEL HALF\n"; |
1025 | t << "RANGE 5\n"; |
1026 | t << "PSEARCH_ALG LOGARITHMIC\n"; |
1027 | t << "BSEARCH_ALG SIMPLE\n"; |
1028 | t << "IQSCALE 1\n"; |
1029 | t << "PQSCALE 1\n"; |
1030 | t << "BQSCALE 1\n"; |
1031 | t << "REFERENCE_FRAME DECODED\n"; |
1032 | t << "ASPECT_RATIO 1\n"; |
1033 | t << "FRAME_RATE 24\n"; |
1034 | t << "BIT_RATE 64000\n"; // Quality |
1035 | t << "BUFFER_SIZE 2048\n"; |
1036 | } |
1037 | file.close(); |
1038 | |
1039 | // ### can't use QProcess, not in Qt 2.3 |
1040 | // ### but it's certainly in Qt 4! use it? |
1041 | // Execute the ppmtompeg command as a separate process to do the encoding |
1042 | pid_t pid = ::fork(); |
1043 | if ( !pid ) { |
1044 | // Child process |
1045 | // redirect stdout to log file |
1046 | freopen("/tmp/qvfb_tmp_output.log", "w", stdout); |
1047 | // ppmtompeg tool is from the netpbm package |
1048 | ::execlp("ppmtompeg", "ppmtompeg", "/tmp/qvfb_tmp_ppmtompeg.params", (void *)0); |
1049 | exit(0); |
1050 | } |
1051 | |
1052 | // Update the saving progress bar every 200ms |
1053 | progressTimerId = startTimer( 200 ); |
1054 | } |
1055 | |
1056 | // Cleanup temporary files created during creating a mpeg file |
1057 | void AnimationSaveWidget::removeTemporaryFiles() |
1058 | { |
1059 | QString str; |
1060 | for ( int i = 0; i < imageNum; i++ ) |
1061 | QFile::remove( str.sprintf("/tmp/qvfb_tmp_image_%04d.ppm", i) ); |
1062 | QFile::remove("/tmp/qvfb_tmp_ppmtompeg.params"); |
1063 | QFile::remove("/tmp/qvfb_tmp_output.log"); |
1064 | imageNum = 0; |
1065 | } |
1066 | |
1067 | // toggles between recording and paused (usually when record button clicked) |
1068 | void AnimationSaveWidget::toggleRecord() |
1069 | { |
1070 | if ( recording ) { |
1071 | recLED->setPixmap( recOff ); |
1072 | recBt->setText( tr("Record") ); |
1073 | statusText->setText( tr("Paused. Click record to resume, or save if done.")); |
1074 | killTimer( timerId ); |
1075 | timerId = -1; |
1076 | elapsed = tm.elapsed(); |
1077 | } else { |
1078 | recLED->setPixmap( recOn ); |
1079 | recBt->setText( tr("Pause") ); |
1080 | statusText->setText( tr("Recording...")); |
1081 | tm.start(); |
1082 | if ( elapsed == 0 ) { |
1083 | savingAsMpeg = mpegSave->isChecked(); |
1084 | if ( !savingAsMpeg ) { |
1085 | delete animation; |
1086 | animation = new QAnimationWriter("/tmp/qvfb_tmp_animation.mng", "MNG"); |
1087 | animation->setFrameRate(24); |
1088 | if ( view ) |
1089 | animation->appendFrame(view->image()); |
1090 | } |
1091 | } |
1092 | tm = tm.addMSecs(-elapsed); |
1093 | elapsed = 0; |
1094 | timerId = startTimer(1000 / 24); |
1095 | } |
1096 | recording = !recording; |
1097 | } |
1098 | |
1099 | // Reset everything to initial state of not recording |
1100 | void AnimationSaveWidget::reset() |
1101 | { |
1102 | if ( recording ) { |
1103 | toggleRecord(); |
1104 | statusText->setText( tr("Click record to begin recording.")); |
1105 | removeTemporaryFiles(); |
1106 | } |
1107 | progressBar->setValue( 0 ); |
1108 | timeDpy->setText( "00:00"); |
1109 | elapsed = 0; |
1110 | imageNum = 0; |
1111 | delete animation; |
1112 | animation = 0; |
1113 | } |
1114 | |
1115 | // Prompt for filename to save to and put animation in that file |
1116 | void AnimationSaveWidget::save() |
1117 | { |
1118 | if ( recording ) |
1119 | toggleRecord(); // pauses |
1120 | statusText->setText( tr("Saving... ")); |
1121 | |
1122 | QString filename; |
1123 | if ( savingAsMpeg ) { |
1124 | filename = QFileDialog::getSaveFileName(this, tr("Save animation..."), "", "*.mpg"); |
1125 | if ( !filename.isNull() ) |
1126 | convertToMpeg(filename); |
1127 | } else { |
1128 | filename = QFileDialog::getSaveFileName(this, tr("Save animation..."), "", "*.mng"); |
1129 | if (filename.isNull()) { |
1130 | statusText->setText(tr("Save canceled.")); |
1131 | } else { |
1132 | QFile::remove(filename); |
1133 | bool success = QFile::rename(QLatin1String("/tmp/qvfb_tmp_animation.mng"), |
1134 | filename); |
1135 | if (success) { |
1136 | statusText->setText(tr("Finished saving.")); |
1137 | reset(); |
1138 | } else { |
1139 | statusText->setText(tr("Save failed!")); |
1140 | } |
1141 | } |
1142 | } |
1143 | } |
1144 | |
1145 | QT_END_NAMESPACE |
1146 | |
1147 | #include "qvfb.moc" |
1148 |
Warning: That file was not part of the compilation database. It may have many parsing errors.