1 /****************************************************************************
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the tools applications of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
45 #include "qvfbx11view.h"
47 #include "qvfbratedlg.h"
48 #include "ui_config.h"
49 #include "qanimationwriter.h"
51 #include <deviceskin.h>
55 #include <QApplication>
56 #include <QMessageBox>
59 #include <QFileDialog>
63 #include <QRadioButton>
69 #include <QScrollArea>
70 #include <QProgressBar>
71 #include <QPushButton>
72 #include <QTextStream>
79 #include <sys/types.h>
83 // =====================================================================
85 static const char *red_on_led_xpm
[] = {
109 static const char *red_off_led_xpm
[] = {
135 static bool copyButtonConfiguration(const QString
&prefix
, int displayId
)
137 const QString destDir
= QString(QLatin1String("/tmp/qtembedded-%1/")).arg(displayId
);
138 const QFileInfo
src(prefix
+ QLatin1String("defaultbuttons.conf"));
139 const QFileInfo
dst(destDir
+ QLatin1String("defaultbuttons.conf"));
140 unlink(dst
.absoluteFilePath().toLatin1().constData());
143 const bool rc
= QFile::copy(src
.absoluteFilePath(), dst
.absoluteFilePath());
145 qWarning() << "Failed to copy the button configuration file " << src
.absoluteFilePath() << " to " << dst
.absoluteFilePath() << '.';
149 // =====================================================================
151 class AnimationSaveWidget
: public QWidget
{
154 AnimationSaveWidget(QVFbAbstractView
*v
);
155 ~AnimationSaveWidget();
156 bool detectPpmtoMpegCommand();
157 void timerEvent(QTimerEvent
*te
);
158 void convertToMpeg(QString filename
);
159 void removeTemporaryFiles();
165 QVFbAbstractView
*view
;
166 QProgressBar
*progressBar
;
168 bool haveMpeg
, savingAsMpeg
, recording
;
170 QAnimationWriter
*animation
;
171 QPushButton
*recBt
, *resetBt
, *saveBt
;
172 QLabel
*timeDpy
, *recLED
;
173 int timerId
, progressTimerId
;
174 QPixmap recOn
, recOff
;
176 int elapsed
, imageNum
;
179 // =====================================================================
181 Zoomer::Zoomer(QVFb
* target
) :
184 QVBoxLayout
*layout
= new QVBoxLayout(this);
185 QSlider
*sl
= new QSlider(Qt::Horizontal
);
190 layout
->addWidget(sl
);
191 connect(sl
,SIGNAL(valueChanged(int)),this,SLOT(zoom(int)));
192 label
= new QLabel();
193 layout
->addWidget(label
);
196 void Zoomer::zoom(int z
)
198 double d
= (double)z
/32.0;
200 label
->setText(QString::number(d
,'g',2));
203 // =====================================================================
205 QVFb::QVFb( int display_id
, int w
, int h
, int d
, int r
, const QString
&skin
, DisplayType displayType
, QWidget
*parent
, Qt::WindowFlags flags
)
206 : QMainWindow( parent
, flags
)
208 this->displayType
= displayType
;
213 currentSkinIndex
= -1;
216 QPixmap
pix(":/res/images/logo.png");
217 setWindowIcon( pix
);
220 // Create the menu first to avoid scroll bars in the main window
221 createMenu( menuBar() );
222 init( display_id
, w
, h
, d
, r
, skin
);
223 enableCursor( true );
230 void QVFb::popupMenu()
232 QMenu
*pm
= new QMenu( this );
234 pm
->exec(QCursor::pos());
237 void QVFb::init( int display_id
, int pw
, int ph
, int d
, int r
, const QString
& skin_name
)
241 delete secondaryView
;
248 skinscaleH
= skinscaleV
= 1.0;
249 QVFbView::Rotation rot
= ((r
== 90) ? QVFbView::Rot90
:
250 ((r
== 180) ? QVFbView::Rot180
:
251 ((r
== 270) ? QVFbView::Rot270
:
253 if ( !skin_name
.isEmpty() ) {
254 const bool vis
= isVisible();
255 DeviceSkinParameters parameters
;
257 if (parameters
.read(skin_name
,DeviceSkinParameters::ReadAll
, &readError
)) {
258 skin
= new DeviceSkin(parameters
, this);
259 connect(skin
, SIGNAL(popupMenu()), this, SLOT(popupMenu()));
260 const int sw
= parameters
.screenSize().width();
261 const int sh
= parameters
.screenSize().height();
262 const int sd
= parameters
.screenDepth
;
276 if (displayType
== X11
)
277 view
= new QVFbX11View( display_id
, pw
, ph
, d
, rot
, skin
);
280 view
= new QVFbView( display_id
, pw
, ph
, d
, rot
, skin
);
281 skin
->setView( view
);
282 view
->setContentsMargins( 0, 0, 0, 0 );
283 view
->setTouchscreenEmulation(!parameters
.hasMouseHover
);
284 connect(skin
, SIGNAL(skinKeyPressEvent(int,QString
,bool)), view
, SLOT(skinKeyPressEvent(int,QString
,bool)));
285 connect(skin
, SIGNAL(skinKeyReleaseEvent(int,QString
,bool)), view
, SLOT(skinKeyReleaseEvent(int,QString
,bool)));
287 copyButtonConfiguration(skin
->prefix(), view
->displayId());
289 setCentralWidget( skin
);
291 skinscaleH
= (double)sw
/pw
;
292 skinscaleV
= (double)sh
/ph
;
293 if ( skinscaleH
!= 1.0 || skinscaleH
!= 1.0 )
297 if (parameters
.hasSecondaryScreen()) {
298 const QSize ssize
= parameters
.secondaryScreenSize();
299 // assumes same depth and rotation
301 if (displayType
== X11
)
302 secondaryView
= new QVFbX11View( display_id
+1, ssize
.width(), ssize
.height(), d
, rot
, skin
);
305 secondaryView
= new QVFbView( display_id
+1, ssize
.width(), ssize
.height(), d
, rot
, skin
);
306 skin
->setSecondaryView(secondaryView
);
307 secondaryView
->show();
312 qWarning("%s", qPrintable(readError
));
316 // If we failed to get a skin or we were not supplied
317 // with one then fallback to a framebuffer without
330 if (currentSkinIndex
!= -1) {
337 setMaximumSize(QWIDGETSIZE_MAX
,QWIDGETSIZE_MAX
);
340 scroller
= new QScrollArea(this);
341 scroller
->setFocusPolicy(Qt::NoFocus
); // don't steal key events from the embedded app
343 if (displayType
== X11
)
344 view
= new QVFbX11View( display_id
, pw
, ph
, d
, rot
, scroller
);
347 view
= new QVFbView( display_id
, pw
, ph
, d
, rot
, scroller
);
348 scroller
->setWidget(view
);
349 view
->setContentsMargins( 0, 0, 0, 0 );
350 setCentralWidget(scroller
);
351 ph
+= 2; // avoid scrollbar
353 // delete defaultbuttons.conf if it was left behind...
354 unlink(QFileInfo(QString("/tmp/qtembedded-%1/defaultbuttons.conf").arg(view
->displayId())).absoluteFilePath().toLatin1().constData());
356 unlink(QFileInfo(QString("/tmp/qtembedded-%1/defaultbuttons.conf").arg(view
->displayId()+1)).absoluteFilePath().toLatin1().constData());
358 view
->setRate(refreshRate
);
360 secondaryView
->setRate(refreshRate
);
362 // Resize QVFb to the new size
363 QSize newSize
= view
->sizeHint();
366 newSize
+= QSize(20, 35);
370 setWindowTitle(QString("Virtual framebuffer %1x%2 %3bpp Display :%4 Rotate %5")
371 .arg(view
->displayWidth()).arg(view
->displayHeight())
372 .arg(d
).arg(display_id
).arg(r
));
375 void QVFb::enableCursor( bool e
)
377 if ( skin
&& skin
->hasCursor() ) {
378 view
->setCursor( Qt::BlankCursor
);
380 secondaryView
->setCursor( Qt::BlankCursor
);
382 view
->setCursor( e
? Qt::ArrowCursor
: Qt::BlankCursor
);
384 secondaryView
->setCursor( e
? Qt::ArrowCursor
: Qt::BlankCursor
);
386 cursorAction
->setChecked( e
);
389 template <typename T
>
390 void QVFb::createMenu(T
*menu
)
392 menu
->addMenu( createFileMenu() );
393 menu
->addMenu( createViewMenu() );
394 menu
->addSeparator();
395 menu
->addMenu( createHelpMenu() );
398 QMenu
* QVFb::createFileMenu()
400 QMenu
*file
= new QMenu( "File", this );
401 file
->addAction( "Configure...", this, SLOT(configure()), 0 );
402 file
->addSeparator();
403 file
->addAction( "&Save image...", this, SLOT(saveImage()), 0 );
404 file
->addAction( "&Animation...", this, SLOT(toggleAnimation()), 0 );
405 file
->addSeparator();
406 file
->addAction( "&Quit", qApp
, SLOT(quit()) );
410 QMenu
* QVFb::createViewMenu()
412 viewMenu
= new QMenu( "View", this );
413 cursorAction
= viewMenu
->addAction( "Show &Cursor", this,
414 SLOT(toggleCursor()) );
415 cursorAction
->setCheckable(true);
418 viewMenu
->addAction( "&Refresh Rate...", this, SLOT(changeRate()) );
419 viewMenu
->addSeparator();
420 viewMenu
->addAction( "No rotation", this, SLOT(setRot0()) );
421 viewMenu
->addAction( "90\260 rotation", this, SLOT(setRot90()) );
422 viewMenu
->addAction( "180\260 rotation", this, SLOT(setRot180()) );
423 viewMenu
->addAction( "270\260 rotation", this, SLOT(setRot270()) );
424 viewMenu
->addSeparator();
425 viewMenu
->addAction( "Zoom scale &0.5", this, SLOT(setZoomHalf()) );
426 viewMenu
->addAction( "Zoom scale 0.75", this, SLOT(setZoom075()) );
427 viewMenu
->addAction( "Zoom scale &1", this, SLOT(setZoom1()) );
428 viewMenu
->addAction( "Zoom scale &2", this, SLOT(setZoom2()) );
429 viewMenu
->addAction( "Zoom scale &3", this, SLOT(setZoom3()) );
430 viewMenu
->addAction( "Zoom scale &4", this, SLOT(setZoom4()) );
431 viewMenu
->addSeparator();
432 viewMenu
->addAction( "Zoom scale...", this, SLOT(setZoom()) );
437 QMenu
* QVFb::createHelpMenu()
439 QMenu
*help
= new QMenu( "Help", this );
440 help
->addAction("About...", this, SLOT(about()));
444 void QVFb::setZoom(double z
)
446 view
->setZoom(z
,z
*skinscaleV
/skinscaleH
);
448 secondaryView
->setZoom(z
,z
*skinscaleV
/skinscaleH
);
451 skin
->setTransform(QMatrix().scale(z
/skinscaleH
,z
/skinscaleV
).rotate(90*view
->displayRotation()));
453 secondaryView
->setFixedSize(
454 int(secondaryView
->displayWidth()*z
),
455 int(secondaryView
->displayHeight()*z
*skinscaleV
/skinscaleH
));
459 void QVFb::setRotation(QVFbView::Rotation r
)
461 view
->setRotation(r
);
463 secondaryView
->setRotation(r
);
464 setZoom(view
->zoomH());
469 setRotation(QVFbView::Rot0
);
472 void QVFb::setRot90()
474 setRotation(QVFbView::Rot90
);
477 void QVFb::setRot180()
479 setRotation(QVFbView::Rot180
);
482 void QVFb::setRot270()
484 setRotation(QVFbView::Rot270
);
487 void QVFb::setZoomHalf()
492 void QVFb::setZoom075()
497 void QVFb::setZoom1()
505 zoomer
= new Zoomer(this);
509 void QVFb::setZoom2()
514 void QVFb::setZoom3()
519 void QVFb::setZoom4()
524 void QVFb::saveImage()
526 QImage img
= view
->image();
527 QString filename
= QFileDialog::getSaveFileName(this, "Save Main Screen image", "snapshot.png", "Portable Network Graphics (*.png)");
528 if (!filename
.isEmpty()){
529 if(!img
.save(filename
,"PNG"))
530 QMessageBox::critical(this, "Save Main Screen Image", "Save failed. Check that you have permission to write to the target directory.");
533 QImage img
= view
->image();
534 QString filename
= QFileDialog::getSaveFileName(this, "Save Second Screen image", "snapshot.png", "Portable Network Graphics (*.png)");
535 if (!filename
.isEmpty()) {
536 if(!img
.save(filename
,"PNG"))
537 QMessageBox::critical(this, "Save Second Screen Image", "Save failed. Check that you have permission to write to the target directory.");
542 void QVFb::toggleAnimation()
544 static AnimationSaveWidget
*animWidget
= 0;
546 animWidget
= new AnimationSaveWidget(view
);
547 if ( animWidget
->isVisible() )
553 void QVFb::toggleCursor()
555 enableCursor(cursorAction
->isChecked());
558 void QVFb::changeRate()
561 rateDlg
= new QVFbRateDialog( refreshRate
, this );
562 connect( rateDlg
, SIGNAL(updateRate(int)), this, SLOT(setRate(int)) );
568 void QVFb::setRate(int i
)
573 secondaryView
->setRate(i
);
579 QMessageBox::about(this, "About QVFB",
580 "<h2>The Qt for Embedded Linux Virtual X11 Framebuffer</h2>"
581 "<p>This application runs under Qt for X11, emulating a simple framebuffer, "
582 "which the Qt for Embedded Linux server and clients can attach to just as if "
583 "it was a hardware Linux framebuffer. "
584 "<p>With the aid of this development tool, you can develop Qt for Embedded "
585 "Linux applications under X11 without having to switch to a virtual console. "
586 "This means you can comfortably use your other development tools such "
587 "as GUI profilers and debuggers."
591 void QVFb::findSkins(const QString
¤tSkin
)
595 QDir
dir(":/skins/","*.skin");
596 const QFileInfoList l
= dir
.entryInfoList();
597 int i
= 1; // "None" is already in list at index 0
598 for (QFileInfoList::const_iterator it
= l
.begin(); it
!= l
.end(); ++it
) {
599 skinnames
.append((*it
).baseName()); // should perhaps be in file
600 skinfiles
.append((*it
).filePath());
601 if (((*it
).baseName() + ".skin") == currentSkin
)
602 currentSkinIndex
= i
;
607 class Config
: public QDialog
, public Ui::Config
610 Config(QWidget
*parent
)
616 connect(buttonOk
, SIGNAL(clicked()), this, SLOT(accept()));
617 connect(buttonCancel
, SIGNAL(clicked()), this, SLOT(reject()));
621 void QVFb::configure()
623 config
= new Config(this);
625 int w
= view
->displayWidth();
626 int h
= view
->displayHeight();
628 // Need to block signals, because we connect to animateClick(),
629 // since QCheckBox doesn't have setChecked(bool) in 2.x.
630 chooseSize(QSize(w
,h
));
631 config
->skin
->insertItems(config
->skin
->count(), skinnames
);
632 if (currentSkinIndex
> 0)
633 config
->skin
->setCurrentIndex(currentSkinIndex
);
634 config
->skin
->addItem(tr("Browse..."));
635 config
->touchScreen
->setChecked(view
->touchScreenEmulation());
636 config
->lcdScreen
->setChecked(view
->lcdScreenEmulation());
637 chooseDepth(view
->displayDepth(), view
->displayFormat());
638 config
->rgbSwapped
->setChecked(view
->rgbSwapped());
639 connect(config
->skin
, SIGNAL(activated(int)), this, SLOT(skinConfigChosen(int)));
640 if ( view
->gammaRed() == view
->gammaGreen() && view
->gammaGreen() == view
->gammaBlue() ) {
641 config
->gammaslider
->setValue(int(view
->gammaRed()*400));
642 config
->rslider
->setValue(100);
643 config
->gslider
->setValue(100);
644 config
->bslider
->setValue(100);
646 config
->gammaslider
->setValue(100);
647 config
->rslider
->setValue(int(view
->gammaRed()*400));
648 config
->gslider
->setValue(int(view
->gammaGreen()*400));
649 config
->bslider
->setValue(int(view
->gammaBlue()*400));
651 connect(config
->gammaslider
, SIGNAL(valueChanged(int)), this, SLOT(setGamma400(int)));
652 connect(config
->rslider
, SIGNAL(valueChanged(int)), this, SLOT(setR400(int)));
653 connect(config
->gslider
, SIGNAL(valueChanged(int)), this, SLOT(setG400(int)));
654 connect(config
->bslider
, SIGNAL(valueChanged(int)), this, SLOT(setB400(int)));
657 double ogr
=view
->gammaRed(), ogg
=view
->gammaGreen(), ogb
=view
->gammaBlue();
658 qApp
->setQuitOnLastWindowClosed(false);
661 if ( config
->exec() ) {
662 int id
= view
->displayId(); // not settable yet
663 if ( config
->size_176_220
->isChecked() ) {
665 } else if ( config
->size_240_320
->isChecked() ) {
667 } else if ( config
->size_320_240
->isChecked() ) {
669 } else if ( config
->size_640_480
->isChecked() ) {
671 } else if ( config
->size_800_600
->isChecked() ) {
673 } else if ( config
->size_1024_768
->isChecked() ) {
676 w
=config
->size_width
->value();
677 h
=config
->size_height
->value();
680 if ( config
->depth_1
->isChecked() )
682 else if ( config
->depth_2gray
->isChecked() )
684 else if ( config
->depth_4gray
->isChecked() )
686 else if ( config
->depth_8
->isChecked() )
688 else if ( config
->depth_12
->isChecked() )
690 else if ( config
->depth_15
->isChecked() )
692 else if ( config
->depth_16
->isChecked() )
694 else if ( config
->depth_18
->isChecked() )
696 else if ( config
->depth_24
->isChecked() )
700 QVFbView::PixelFormat displayFormat
= config
->depth_32_argb
->isChecked()
701 ? QVFbView::ARGBFormat
: QVFbView::DefaultFormat
;
702 int skinIndex
= config
->skin
->currentIndex();
703 if ( w
!= view
->displayWidth() || h
!= view
->displayHeight()
704 || d
!= view
->displayDepth() || skinIndex
!= currentSkinIndex
) {
705 QVFbView::Rotation rot
= view
->displayRotation();
706 int r
= ((rot
== QVFbView::Rot90
) ? 90 :
707 ((rot
== QVFbView::Rot180
) ? 180 :
708 ((rot
== QVFbView::Rot270
) ? 270 : 0 )));
709 currentSkinIndex
= skinIndex
;
710 init( id
, w
, h
, d
, r
, skinIndex
> 0 ? skinfiles
[skinIndex
-1] : QString::null
);
712 view
->setViewFormat(displayFormat
);
713 view
->setTouchscreenEmulation( config
->touchScreen
->isChecked() );
714 if (view
->rgbSwapped() != config
->rgbSwapped
->isChecked()) {
715 //### the windowTitle logic is inside init(), and init isn't always invoked
716 QString caption
= windowTitle();
717 if (!config
->rgbSwapped
->isChecked())
718 caption
.replace(QLatin1String(" BGR"), QString());
720 caption
.append(QLatin1String(" BGR"));
721 setWindowTitle(caption
);
722 view
->setRgbSwapped(config
->rgbSwapped
->isChecked());
724 bool lcdEmulation
= config
->lcdScreen
->isChecked();
725 view
->setLcdScreenEmulation( lcdEmulation
);
729 view
->setGamma(ogr
, ogg
, ogb
);
732 qApp
->setQuitOnLastWindowClosed(true);
737 void QVFb::chooseSize(const QSize
& sz
)
739 config
->size_width
->blockSignals(true);
740 config
->size_height
->blockSignals(true);
741 config
->size_width
->setValue(sz
.width());
742 config
->size_height
->setValue(sz
.height());
743 config
->size_width
->blockSignals(false);
744 config
->size_height
->blockSignals(false);
745 config
->size_custom
->setChecked(true); // unless changed by settings below
746 config
->size_176_220
->setChecked(sz
== QSize(176,220));
747 config
->size_240_320
->setChecked(sz
== QSize(240,320));
748 config
->size_320_240
->setChecked(sz
== QSize(320,240));
749 config
->size_640_480
->setChecked(sz
== QSize(640,480));
750 config
->size_800_600
->setChecked(sz
== QSize(800,600));
751 config
->size_1024_768
->setChecked(sz
== QSize(1024,768));
754 void QVFb::chooseDepth(int depth
, QVFbView::PixelFormat displayFormat
)
756 config
->depth_1
->setChecked(depth
==1);
757 config
->depth_2gray
->setChecked(depth
==2);
758 config
->depth_4gray
->setChecked(depth
==4);
759 config
->depth_8
->setChecked(depth
==8);
760 config
->depth_12
->setChecked(depth
==12);
761 config
->depth_15
->setChecked(depth
==15);
762 config
->depth_16
->setChecked(depth
==16);
763 config
->depth_18
->setChecked(depth
==18);
764 config
->depth_24
->setChecked(depth
==24);
765 config
->depth_32
->setChecked(depth
==32 && displayFormat
!= QVFbView::ARGBFormat
);
766 config
->depth_32_argb
->setChecked(depth
==32 && displayFormat
== QVFbView::ARGBFormat
);
769 void QVFb::skinConfigChosen(int i
)
771 if (i
== config
->skin
->count() - 1) { // Browse... ?
772 QFileDialog
dlg(this);
773 dlg
.setFileMode(QFileDialog::DirectoryOnly
);
774 dlg
.setWindowTitle(tr("Load Custom Skin..."));
775 dlg
.setFilter(tr("All QVFB Skins (*.skin)"));
776 dlg
.setDirectory(QDir::current());
777 if (dlg
.exec() && dlg
.selectedFiles().count() == 1) {
778 skinfiles
.append(dlg
.selectedFiles().first());
779 i
= skinfiles
.count();
780 config
->skin
->insertItem(i
, QFileInfo(skinfiles
.last()).baseName());
781 config
->skin
->setCurrentIndex(i
);
787 DeviceSkinParameters parameters
;
789 if (parameters
.read(skinfiles
[i
-1], DeviceSkinParameters::ReadSizeOnly
, &readError
)) {
790 chooseSize(parameters
.screenSize());
791 if (parameters
.screenDepth
)
792 chooseDepth(parameters
.screenDepth
,QVFbView::ARGBFormat
);
793 config
->touchScreen
->setChecked(!parameters
.hasMouseHover
);
795 qWarning("%s", qPrintable(readError
));
800 void QVFb::setGamma400(int n
)
803 view
->setGamma(config
->rslider
->value()/100.0*g
,
804 config
->gslider
->value()/100.0*g
,
805 config
->bslider
->value()/100.0*g
);
809 void QVFb::setR400(int n
)
812 view
->setGamma(config
->rslider
->value()/100.0*g
,
818 void QVFb::setG400(int n
)
821 view
->setGamma(view
->gammaRed(),
822 config
->gslider
->value()/100.0*g
,
827 void QVFb::setB400(int n
)
830 view
->setGamma(view
->gammaRed(),
832 config
->bslider
->value()/100.0*g
);
836 void QVFb::updateGammaLabels()
838 config
->rlabel
->setText(QString::number(view
->gammaRed(),'g',2));
839 config
->glabel
->setText(QString::number(view
->gammaGreen(),'g',2));
840 config
->blabel
->setText(QString::number(view
->gammaBlue(),'g',2));
843 QSize
QVFb::sizeHint() const
845 return QSize(int(view
->displayWidth()*view
->zoomH()),
846 int(menuBar()->height()+view
->displayHeight()*view
->zoomV()));
849 // =====================================================================
851 AnimationSaveWidget::AnimationSaveWidget(QVFbAbstractView
*v
) :
852 QWidget((QWidget
*)0,0),
853 view(v
), recording(false), animation(0),
854 timerId(-1), progressTimerId(-1),
855 recOn(red_on_led_xpm
), recOff(red_off_led_xpm
),
858 // Create the animation record UI dialog
859 QVBoxLayout
*vlayout
= new QVBoxLayout( this );
861 QWidget
*hbox
= new QWidget( this );
862 vlayout
->addWidget(hbox
);
863 QHBoxLayout
*hlayout
= new QHBoxLayout(hbox
);
864 recBt
= new QPushButton( tr("Record"), hbox
);
865 hlayout
->addWidget(recBt
);
866 resetBt
= new QPushButton( tr("Reset"), hbox
);
867 hlayout
->addWidget(resetBt
);
868 saveBt
= new QPushButton( tr("Save"), hbox
);
869 hlayout
->addWidget(saveBt
);
870 recBt
->setFixedWidth( 100 );
871 resetBt
->setFixedWidth( 100 );
872 saveBt
->setFixedWidth( 100 );
873 timeDpy
= new QLabel( "00:00", hbox
);
874 hlayout
->addWidget(timeDpy
);
875 recLED
= new QLabel( hbox
);
876 hlayout
->addWidget(recLED
);
877 recLED
->setPixmap( recOff
);
878 timeDpy
->setMargin( 5 );
879 connect( recBt
, SIGNAL(clicked()), this, SLOT(toggleRecord()) );
880 connect( resetBt
, SIGNAL(clicked()), this, SLOT(reset()) );
881 connect( saveBt
, SIGNAL(clicked()), this, SLOT(save()) );
883 vlayout
->setMargin( 5 );
884 vlayout
->setSpacing( 5 );
885 haveMpeg
= detectPpmtoMpegCommand();
886 mpegSave
= new QCheckBox( tr("Save in MPEG format (requires netpbm package installed)"), this );
887 vlayout
->addWidget(mpegSave
);
888 mpegSave
->setChecked( haveMpeg
);
889 mpegSave
->setEnabled( haveMpeg
);
890 savingAsMpeg
= haveMpeg
;
891 QWidget
*hbox2
= new QWidget( this );
892 vlayout
->addWidget(hbox2
);
893 QHBoxLayout
*hlayout2
= new QHBoxLayout( hbox2
);
894 statusText
= new QLabel( tr("Click record to begin recording."), hbox2
);
895 hlayout2
->addWidget(statusText
);
896 progressBar
= new QProgressBar( hbox2
);
897 progressBar
->setValue( 0 );
898 hlayout2
->addWidget(progressBar
);
902 AnimationSaveWidget::~AnimationSaveWidget()
905 removeTemporaryFiles();
909 // returns true if we have ppmtompeg command, else returns false
910 bool AnimationSaveWidget::detectPpmtoMpegCommand()
912 // search the PATH for the ppmtompeg command to test we can record to mpeg
913 QStringList paths
= QString(::getenv("PATH")).split(":");
914 for ( int i
= 0; i
< paths
.count(); i
++ )
915 if ( QFile::exists( paths
[i
] + "/" + "ppmtompeg" ) )
920 void AnimationSaveWidget::timerEvent( QTimerEvent
*te
)
925 if ( te
->timerId() == timerId
) {
927 // Add a frame to the animation
928 if ( savingAsMpeg
&& view
)
929 view
->image().save( str
.sprintf("/tmp/qvfb_tmp_image_%04d.ppm", imageNum
), "PPM");
930 else if ( animation
&& view
)
931 animation
->appendFrame(view
->image());//QPoint(0,0));
934 // Update the display of number of seconds that have been recorded.
935 int tmMsec
= tm
.elapsed();
936 timeDpy
->setText( str
.sprintf("%02d:%02d", tmMsec
/60000, (tmMsec
%60000)/1000) );
937 QObject::timerEvent( te
);
939 // Make the recording LED blink
941 static bool on
= false;
945 recLED
->setPixmap( recOff
);
947 recLED
->setPixmap( recOn
);
953 // Saving progress timer
954 if ( te
->timerId() == progressTimerId
) {
955 // Parse output log file to work out the encoding progress.
956 QFile
f("/tmp/qvfb_tmp_output.log");
957 f
.open(QIODevice::ReadOnly
);
961 while ( !f
.atEnd() ) {
962 // example of the output log entries
963 // During each frame:
964 // "FRAME 764 (B): I BLOCKS: 0......
966 // "======FRAMES READ: 766"
967 f
.readLine(buffer
, 1024);
968 str
= QString(buffer
);
969 if ( str
.left(6) == "FRAME " ) {
970 int num
= str
.mid(6, str
.indexOf(QChar(' '), 6) - 6).toInt();
971 if ( num
> largestNum
)
973 } else if ( str
.left(18) == "======FRAMES READ:" ) {
979 // Update the progress bar with the frame we are up to
980 progressBar
->setValue( largestNum
);
985 statusText
->setText( tr("Finished saving."));
986 removeTemporaryFiles();
987 killTimer( progressTimerId
);
988 progressTimerId
= -1;
994 // Takes the saved ppm files and converts them to a mpeg file named filename
995 void AnimationSaveWidget::convertToMpeg(QString filename
)
997 recLED
->setPixmap( recOff
);
998 killTimer( timerId
);
1000 progressBar
->show();
1001 progressBar
->setRange( 0, imageNum
);
1002 progressBar
->setValue( 0 );
1004 // Build parameter file required by ppmtompeg
1005 QFile
file("/tmp/qvfb_tmp_ppmtompeg.params");
1006 if ( file
.open( QIODevice::WriteOnly
) ) {
1007 QTextStream
t( &file
);
1008 t
<< "PATTERN IBBPBBPBBPBBPBB\n";
1009 t
<< "OUTPUT " << filename
<< "\n";
1010 t
<< "INPUT_DIR /tmp\n";
1013 str
= str
.sprintf("%04d", imageNum
- 1);
1014 t
<< "qvfb_tmp_image_*.ppm [0000-" << str
<< "]\n";
1016 t
<< "BASE_FILE_FORMAT PPM\n";
1017 t
<< "INPUT_CONVERT *\n";
1018 t
<< "GOP_SIZE 15\n";
1019 t
<< "SLICES_PER_FRAME 1\n";
1020 t
<< "PIXEL HALF\n";
1022 t
<< "PSEARCH_ALG LOGARITHMIC\n";
1023 t
<< "BSEARCH_ALG SIMPLE\n";
1027 t
<< "REFERENCE_FRAME DECODED\n";
1028 t
<< "ASPECT_RATIO 1\n";
1029 t
<< "FRAME_RATE 24\n";
1030 t
<< "BIT_RATE 64000\n"; // Quality
1031 t
<< "BUFFER_SIZE 2048\n";
1035 // ### can't use QProcess, not in Qt 2.3
1036 // ### but it's certainly in Qt 4! use it?
1037 // Execute the ppmtompeg command as a seperate process to do the encoding
1038 pid_t pid
= ::fork();
1041 // redirect stdout to log file
1042 freopen("/tmp/qvfb_tmp_output.log", "w", stdout
);
1043 // ppmtompeg tool is from the netpbm package
1044 ::execlp("ppmtompeg", "ppmtompeg", "/tmp/qvfb_tmp_ppmtompeg.params", (void *)0);
1048 // Update the saving progress bar every 200ms
1049 progressTimerId
= startTimer( 200 );
1052 // Cleanup temporary files created during creating a mpeg file
1053 void AnimationSaveWidget::removeTemporaryFiles()
1056 for ( int i
= 0; i
< imageNum
; i
++ )
1057 QFile::remove( str
.sprintf("/tmp/qvfb_tmp_image_%04d.ppm", i
) );
1058 QFile::remove("/tmp/qvfb_tmp_ppmtompeg.params");
1059 QFile::remove("/tmp/qvfb_tmp_output.log");
1063 // toggles between recording and paused (usually when record button clicked)
1064 void AnimationSaveWidget::toggleRecord()
1067 recLED
->setPixmap( recOff
);
1068 recBt
->setText( tr("Record") );
1069 statusText
->setText( tr("Paused. Click record to resume, or save if done."));
1070 killTimer( timerId
);
1072 elapsed
= tm
.elapsed();
1074 recLED
->setPixmap( recOn
);
1075 recBt
->setText( tr("Pause") );
1076 statusText
->setText( tr("Recording..."));
1078 if ( elapsed
== 0 ) {
1079 savingAsMpeg
= mpegSave
->isChecked();
1080 if ( !savingAsMpeg
) {
1082 animation
= new QAnimationWriter("/tmp/qvfb_tmp_animation.mng","MNG");
1083 animation
->setFrameRate(24);
1085 animation
->appendFrame(view
->image());
1088 tm
= tm
.addMSecs(-elapsed
);
1090 timerId
= startTimer(1000 / 24);
1092 recording
= !recording
;
1095 // Reset everything to initial state of not recording
1096 void AnimationSaveWidget::reset()
1100 statusText
->setText( tr("Click record to begin recording."));
1101 removeTemporaryFiles();
1103 progressBar
->setValue( 0 );
1104 timeDpy
->setText( "00:00" );
1111 // Prompt for filename to save to and put animation in that file
1112 void AnimationSaveWidget::save()
1115 toggleRecord(); // pauses
1116 statusText
->setText( tr("Saving... "));
1119 if ( savingAsMpeg
) {
1120 filename
= QFileDialog::getSaveFileName(this, tr("Save animation..."), "", "*.mpg");
1121 if ( !filename
.isNull() )
1122 convertToMpeg(filename
);
1124 filename
= QFileDialog::getSaveFileName(this, tr("Save animation..."), "", "*.mng");
1125 if (filename
.isNull()) {
1126 statusText
->setText(tr("Save canceled."));
1128 QFile::remove(filename
);
1129 bool success
= QFile::rename(QLatin1String("/tmp/qvfb_tmp_animation.mng"),
1132 statusText
->setText(tr("Finished saving."));
1135 statusText
->setText(tr("Save failed!"));