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 ****************************************************************************/
46 #include "qvfbx11view.h"
48 #include "qvfbratedlg.h"
49 #include "ui_config.h"
50 #include "qanimationwriter.h"
52 #include <deviceskin.h>
56 #include <QApplication>
57 #include <QMessageBox>
60 #include <QFileDialog>
64 #include <QRadioButton>
70 #include <QScrollArea>
71 #include <QProgressBar>
72 #include <QPushButton>
73 #include <QTextStream>
80 #include <sys/types.h>
84 // =====================================================================
86 static const char *red_on_led_xpm
[] = {
110 static const char *red_off_led_xpm
[] = {
136 static bool copyButtonConfiguration(const QString
&prefix
, int displayId
)
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());
144 const bool rc
= QFile::copy(src
.absoluteFilePath(), dst
.absoluteFilePath());
146 qWarning() << "Failed to copy the button configuration file " << src
.absoluteFilePath() << " to " << dst
.absoluteFilePath() << '.';
150 // =====================================================================
152 class AnimationSaveWidget
: public QWidget
{
155 AnimationSaveWidget(QVFbAbstractView
*v
);
156 ~AnimationSaveWidget();
157 bool detectPpmtoMpegCommand();
158 void timerEvent(QTimerEvent
*te
);
159 void convertToMpeg(QString filename
);
160 void removeTemporaryFiles();
166 QVFbAbstractView
*view
;
167 QProgressBar
*progressBar
;
169 bool haveMpeg
, savingAsMpeg
, recording
;
171 QAnimationWriter
*animation
;
172 QPushButton
*recBt
, *resetBt
, *saveBt
;
173 QLabel
*timeDpy
, *recLED
;
174 int timerId
, progressTimerId
;
175 QPixmap recOn
, recOff
;
177 int elapsed
, imageNum
;
180 // =====================================================================
182 Zoomer::Zoomer(QVFb
* target
) :
185 QVBoxLayout
*layout
= new QVBoxLayout(this);
186 QSlider
*sl
= new QSlider(Qt::Horizontal
);
191 layout
->addWidget(sl
);
192 connect(sl
,SIGNAL(valueChanged(int)),this,SLOT(zoom(int)));
193 label
= new QLabel();
194 layout
->addWidget(label
);
197 void Zoomer::zoom(int z
)
199 double d
= (double)z
/32.0;
201 label
->setText(QString::number(d
,'g',2));
204 // =====================================================================
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
)
209 this->displayType
= displayType
;
214 currentSkinIndex
= -1;
217 QPixmap
pix(":/res/images/logo.png");
218 setWindowIcon( pix
);
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 );
231 void QVFb::popupMenu()
233 QMenu
*pm
= new QMenu( this );
235 pm
->exec(QCursor::pos());
238 void QVFb::init( int display_id
, int pw
, int ph
, int d
, int r
, const QString
& skin_name
)
242 delete secondaryView
;
249 skinscaleH
= skinscaleV
= 1.0;
250 QVFbView::Rotation rot
= ((r
== 90) ? QVFbView::Rot90
:
251 ((r
== 180) ? QVFbView::Rot180
:
252 ((r
== 270) ? QVFbView::Rot270
:
254 if ( !skin_name
.isEmpty() ) {
255 const bool vis
= isVisible();
256 DeviceSkinParameters parameters
;
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
;
277 if (displayType
== X11
)
278 view
= new QVFbX11View( display_id
, pw
, ph
, d
, rot
, skin
);
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)));
288 copyButtonConfiguration(skin
->prefix(), view
->displayId());
290 setCentralWidget( skin
);
292 skinscaleH
= (double)sw
/pw
;
293 skinscaleV
= (double)sh
/ph
;
294 if ( skinscaleH
!= 1.0 || skinscaleH
!= 1.0 )
298 if (parameters
.hasSecondaryScreen()) {
299 const QSize ssize
= parameters
.secondaryScreenSize();
300 // assumes same depth and rotation
302 if (displayType
== X11
)
303 secondaryView
= new QVFbX11View( display_id
+1, ssize
.width(), ssize
.height(), d
, rot
, skin
);
306 secondaryView
= new QVFbView( display_id
+1, ssize
.width(), ssize
.height(), d
, rot
, skin
);
307 skin
->setSecondaryView(secondaryView
);
308 secondaryView
->show();
313 qWarning("%s", qPrintable(readError
));
317 // If we failed to get a skin or we were not supplied
318 // with one then fallback to a framebuffer without
331 if (currentSkinIndex
!= -1) {
338 setMaximumSize(QWIDGETSIZE_MAX
,QWIDGETSIZE_MAX
);
341 scroller
= new QScrollArea(this);
342 scroller
->setFocusPolicy(Qt::NoFocus
); // don't steal key events from the embedded app
344 if (displayType
== X11
)
345 view
= new QVFbX11View( display_id
, pw
, ph
, d
, rot
, scroller
);
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
354 // delete defaultbuttons.conf if it was left behind...
355 unlink(QFileInfo(QT_VFB_DATADIR(view
->displayId()).append("/defaultbuttons.conf")).absoluteFilePath().toLatin1().constData());
357 unlink(QFileInfo(QT_VFB_DATADIR(view
->displayId() + 1).append("/defaultbuttons.conf")).absoluteFilePath().toLatin1().constData());
359 view
->setRate(refreshRate
);
361 secondaryView
->setRate(refreshRate
);
363 // Resize QVFb to the new size
364 QSize newSize
= view
->sizeHint();
367 newSize
+= QSize(20, 35);
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
));
376 void QVFb::enableCursor( bool e
)
378 if ( skin
&& skin
->hasCursor() ) {
379 view
->setCursor( Qt::BlankCursor
);
381 secondaryView
->setCursor( Qt::BlankCursor
);
383 view
->setCursor( e
? Qt::ArrowCursor
: Qt::BlankCursor
);
385 secondaryView
->setCursor( e
? Qt::ArrowCursor
: Qt::BlankCursor
);
387 cursorAction
->setChecked( e
);
390 template <typename T
>
391 void QVFb::createMenu(T
*menu
)
393 menu
->addMenu( createFileMenu() );
394 menu
->addMenu( createViewMenu() );
395 menu
->addSeparator();
396 menu
->addMenu( createHelpMenu() );
399 QMenu
* QVFb::createFileMenu()
401 QMenu
*file
= new QMenu( "File", this );
402 file
->addAction( "Configure...", this, SLOT(configure()), 0 );
403 file
->addSeparator();
404 file
->addAction( "&Save image...", this, SLOT(saveImage()), 0 );
405 file
->addAction( "&Animation...", this, SLOT(toggleAnimation()), 0 );
406 file
->addSeparator();
407 file
->addAction( "&Quit", qApp
, SLOT(quit()) );
411 QMenu
* QVFb::createViewMenu()
413 viewMenu
= new QMenu( "View", this );
414 cursorAction
= viewMenu
->addAction( "Show &Cursor", this,
415 SLOT(toggleCursor()) );
416 cursorAction
->setCheckable(true);
419 viewMenu
->addAction( "&Refresh Rate...", this, SLOT(changeRate()) );
420 viewMenu
->addSeparator();
421 viewMenu
->addAction( "No rotation", this, SLOT(setRot0()) );
422 viewMenu
->addAction( "90\260 rotation", this, SLOT(setRot90()) );
423 viewMenu
->addAction( "180\260 rotation", this, SLOT(setRot180()) );
424 viewMenu
->addAction( "270\260 rotation", this, SLOT(setRot270()) );
425 viewMenu
->addSeparator();
426 viewMenu
->addAction( "Zoom scale &0.5", this, SLOT(setZoomHalf()) );
427 viewMenu
->addAction( "Zoom scale 0.75", this, SLOT(setZoom075()) );
428 viewMenu
->addAction( "Zoom scale &1", this, SLOT(setZoom1()) );
429 viewMenu
->addAction( "Zoom scale &2", this, SLOT(setZoom2()) );
430 viewMenu
->addAction( "Zoom scale &3", this, SLOT(setZoom3()) );
431 viewMenu
->addAction( "Zoom scale &4", this, SLOT(setZoom4()) );
432 viewMenu
->addSeparator();
433 viewMenu
->addAction( "Zoom scale...", this, SLOT(setZoom()) );
438 QMenu
* QVFb::createHelpMenu()
440 QMenu
*help
= new QMenu( "Help", this );
441 help
->addAction("About...", this, SLOT(about()));
445 void QVFb::setZoom(double z
)
447 view
->setZoom(z
,z
*skinscaleV
/skinscaleH
);
449 secondaryView
->setZoom(z
,z
*skinscaleV
/skinscaleH
);
452 skin
->setTransform(QMatrix().scale(z
/skinscaleH
,z
/skinscaleV
).rotate(90*view
->displayRotation()));
454 secondaryView
->setFixedSize(
455 int(secondaryView
->displayWidth()*z
),
456 int(secondaryView
->displayHeight()*z
*skinscaleV
/skinscaleH
));
460 void QVFb::setRotation(QVFbView::Rotation r
)
462 view
->setRotation(r
);
464 secondaryView
->setRotation(r
);
465 setZoom(view
->zoomH());
470 setRotation(QVFbView::Rot0
);
473 void QVFb::setRot90()
475 setRotation(QVFbView::Rot90
);
478 void QVFb::setRot180()
480 setRotation(QVFbView::Rot180
);
483 void QVFb::setRot270()
485 setRotation(QVFbView::Rot270
);
488 void QVFb::setZoomHalf()
493 void QVFb::setZoom075()
498 void QVFb::setZoom1()
506 zoomer
= new Zoomer(this);
510 void QVFb::setZoom2()
515 void QVFb::setZoom3()
520 void QVFb::setZoom4()
525 void QVFb::saveImage()
527 QImage img
= view
->image();
528 QString filename
= QFileDialog::getSaveFileName(this, "Save Main Screen image", "snapshot.png", "Portable Network Graphics (*.png)");
529 if (!filename
.isEmpty()){
530 if(!img
.save(filename
,"PNG"))
531 QMessageBox::critical(this, "Save Main Screen Image", "Save failed. Check that you have permission to write to the target directory.");
534 QImage img
= view
->image();
535 QString filename
= QFileDialog::getSaveFileName(this, "Save Second Screen image", "snapshot.png", "Portable Network Graphics (*.png)");
536 if (!filename
.isEmpty()) {
537 if(!img
.save(filename
,"PNG"))
538 QMessageBox::critical(this, "Save Second Screen Image", "Save failed. Check that you have permission to write to the target directory.");
543 void QVFb::toggleAnimation()
545 static AnimationSaveWidget
*animWidget
= 0;
547 animWidget
= new AnimationSaveWidget(view
);
548 if ( animWidget
->isVisible() )
554 void QVFb::toggleCursor()
556 enableCursor(cursorAction
->isChecked());
559 void QVFb::changeRate()
562 rateDlg
= new QVFbRateDialog( refreshRate
, this );
563 connect( rateDlg
, SIGNAL(updateRate(int)), this, SLOT(setRate(int)) );
569 void QVFb::setRate(int i
)
574 secondaryView
->setRate(i
);
580 QMessageBox::about(this, "About QVFB",
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."
592 void QVFb::findSkins(const QString
¤tSkin
)
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
;
608 class Config
: public QDialog
, public Ui::Config
611 Config(QWidget
*parent
)
617 connect(buttonOk
, SIGNAL(clicked()), this, SLOT(accept()));
618 connect(buttonCancel
, SIGNAL(clicked()), this, SLOT(reject()));
622 void QVFb::configure()
624 config
= new Config(this);
626 int w
= view
->displayWidth();
627 int h
= view
->displayHeight();
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);
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));
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)));
658 double ogr
=view
->gammaRed(), ogg
=view
->gammaGreen(), ogb
=view
->gammaBlue();
659 qApp
->setQuitOnLastWindowClosed(false);
662 if ( config
->exec() ) {
663 int id
= view
->displayId(); // not settable yet
664 if ( config
->size_176_220
->isChecked() ) {
666 } else if ( config
->size_240_320
->isChecked() ) {
668 } else if ( config
->size_320_240
->isChecked() ) {
670 } else if ( config
->size_640_480
->isChecked() ) {
672 } else if ( config
->size_800_600
->isChecked() ) {
674 } else if ( config
->size_1024_768
->isChecked() ) {
677 w
=config
->size_width
->value();
678 h
=config
->size_height
->value();
681 if ( config
->depth_1
->isChecked() )
683 else if ( config
->depth_2gray
->isChecked() )
685 else if ( config
->depth_4gray
->isChecked() )
687 else if ( config
->depth_8
->isChecked() )
689 else if ( config
->depth_12
->isChecked() )
691 else if ( config
->depth_15
->isChecked() )
693 else if ( config
->depth_16
->isChecked() )
695 else if ( config
->depth_18
->isChecked() )
697 else if ( config
->depth_24
->isChecked() )
701 QVFbView::PixelFormat displayFormat
= config
->depth_32_argb
->isChecked()
702 ? QVFbView::ARGBFormat
: QVFbView::DefaultFormat
;
703 int skinIndex
= config
->skin
->currentIndex();
704 if ( w
!= view
->displayWidth() || h
!= view
->displayHeight()
705 || d
!= view
->displayDepth() || skinIndex
!= currentSkinIndex
) {
706 QVFbView::Rotation rot
= view
->displayRotation();
707 int r
= ((rot
== QVFbView::Rot90
) ? 90 :
708 ((rot
== QVFbView::Rot180
) ? 180 :
709 ((rot
== QVFbView::Rot270
) ? 270 : 0 )));
710 currentSkinIndex
= skinIndex
;
711 init( id
, w
, h
, d
, r
, skinIndex
> 0 ? skinfiles
[skinIndex
-1] : QString::null
);
713 view
->setViewFormat(displayFormat
);
714 view
->setTouchscreenEmulation( config
->touchScreen
->isChecked() );
715 if (view
->rgbSwapped() != config
->rgbSwapped
->isChecked()) {
716 //### the windowTitle logic is inside init(), and init isn't always invoked
717 QString caption
= windowTitle();
718 if (!config
->rgbSwapped
->isChecked())
719 caption
.replace(QLatin1String(" BGR"), QString());
721 caption
.append(QLatin1String(" BGR"));
722 setWindowTitle(caption
);
723 view
->setRgbSwapped(config
->rgbSwapped
->isChecked());
725 bool lcdEmulation
= config
->lcdScreen
->isChecked();
726 view
->setLcdScreenEmulation( lcdEmulation
);
730 view
->setGamma(ogr
, ogg
, ogb
);
733 qApp
->setQuitOnLastWindowClosed(true);
738 void QVFb::chooseSize(const QSize
& sz
)
740 config
->size_width
->blockSignals(true);
741 config
->size_height
->blockSignals(true);
742 config
->size_width
->setValue(sz
.width());
743 config
->size_height
->setValue(sz
.height());
744 config
->size_width
->blockSignals(false);
745 config
->size_height
->blockSignals(false);
746 config
->size_custom
->setChecked(true); // unless changed by settings below
747 config
->size_176_220
->setChecked(sz
== QSize(176,220));
748 config
->size_240_320
->setChecked(sz
== QSize(240,320));
749 config
->size_320_240
->setChecked(sz
== QSize(320,240));
750 config
->size_640_480
->setChecked(sz
== QSize(640,480));
751 config
->size_800_600
->setChecked(sz
== QSize(800,600));
752 config
->size_1024_768
->setChecked(sz
== QSize(1024,768));
755 void QVFb::chooseDepth(int depth
, QVFbView::PixelFormat displayFormat
)
757 config
->depth_1
->setChecked(depth
==1);
758 config
->depth_2gray
->setChecked(depth
==2);
759 config
->depth_4gray
->setChecked(depth
==4);
760 config
->depth_8
->setChecked(depth
==8);
761 config
->depth_12
->setChecked(depth
==12);
762 config
->depth_15
->setChecked(depth
==15);
763 config
->depth_16
->setChecked(depth
==16);
764 config
->depth_18
->setChecked(depth
==18);
765 config
->depth_24
->setChecked(depth
==24);
766 config
->depth_32
->setChecked(depth
==32 && displayFormat
!= QVFbView::ARGBFormat
);
767 config
->depth_32_argb
->setChecked(depth
==32 && displayFormat
== QVFbView::ARGBFormat
);
770 void QVFb::skinConfigChosen(int i
)
772 if (i
== config
->skin
->count() - 1) { // Browse... ?
773 QFileDialog
dlg(this);
774 dlg
.setFileMode(QFileDialog::DirectoryOnly
);
775 dlg
.setWindowTitle(tr("Load Custom Skin..."));
776 dlg
.setFilter(tr("All QVFB Skins (*.skin)"));
777 dlg
.setDirectory(QDir::current());
778 if (dlg
.exec() && dlg
.selectedFiles().count() == 1) {
779 skinfiles
.append(dlg
.selectedFiles().first());
780 i
= skinfiles
.count();
781 config
->skin
->insertItem(i
, QFileInfo(skinfiles
.last()).baseName());
782 config
->skin
->setCurrentIndex(i
);
788 DeviceSkinParameters parameters
;
790 if (parameters
.read(skinfiles
[i
-1], DeviceSkinParameters::ReadSizeOnly
, &readError
)) {
791 chooseSize(parameters
.screenSize());
792 if (parameters
.screenDepth
)
793 chooseDepth(parameters
.screenDepth
,QVFbView::ARGBFormat
);
794 config
->touchScreen
->setChecked(!parameters
.hasMouseHover
);
796 qWarning("%s", qPrintable(readError
));
801 void QVFb::setGamma400(int n
)
804 view
->setGamma(config
->rslider
->value()/100.0*g
,
805 config
->gslider
->value()/100.0*g
,
806 config
->bslider
->value()/100.0*g
);
810 void QVFb::setR400(int n
)
813 view
->setGamma(config
->rslider
->value()/100.0*g
,
819 void QVFb::setG400(int n
)
822 view
->setGamma(view
->gammaRed(),
823 config
->gslider
->value()/100.0*g
,
828 void QVFb::setB400(int n
)
831 view
->setGamma(view
->gammaRed(),
833 config
->bslider
->value()/100.0*g
);
837 void QVFb::updateGammaLabels()
839 config
->rlabel
->setText(QString::number(view
->gammaRed(),'g',2));
840 config
->glabel
->setText(QString::number(view
->gammaGreen(),'g',2));
841 config
->blabel
->setText(QString::number(view
->gammaBlue(),'g',2));
844 QSize
QVFb::sizeHint() const
846 return QSize(int(view
->displayWidth()*view
->zoomH()),
847 int(menuBar()->height()+view
->displayHeight()*view
->zoomV()));
850 // =====================================================================
852 AnimationSaveWidget::AnimationSaveWidget(QVFbAbstractView
*v
) :
853 QWidget((QWidget
*)0,0),
854 view(v
), recording(false), animation(0),
855 timerId(-1), progressTimerId(-1),
856 recOn(red_on_led_xpm
), recOff(red_off_led_xpm
),
859 // Create the animation record UI dialog
860 QVBoxLayout
*vlayout
= new QVBoxLayout( this );
862 QWidget
*hbox
= new QWidget( this );
863 vlayout
->addWidget(hbox
);
864 QHBoxLayout
*hlayout
= new QHBoxLayout(hbox
);
865 recBt
= new QPushButton( tr("Record"), hbox
);
866 hlayout
->addWidget(recBt
);
867 resetBt
= new QPushButton( tr("Reset"), hbox
);
868 hlayout
->addWidget(resetBt
);
869 saveBt
= new QPushButton( tr("Save"), hbox
);
870 hlayout
->addWidget(saveBt
);
871 recBt
->setFixedWidth( 100 );
872 resetBt
->setFixedWidth( 100 );
873 saveBt
->setFixedWidth( 100 );
874 timeDpy
= new QLabel( "00:00", hbox
);
875 hlayout
->addWidget(timeDpy
);
876 recLED
= new QLabel( hbox
);
877 hlayout
->addWidget(recLED
);
878 recLED
->setPixmap( recOff
);
879 timeDpy
->setMargin( 5 );
880 connect( recBt
, SIGNAL(clicked()), this, SLOT(toggleRecord()) );
881 connect( resetBt
, SIGNAL(clicked()), this, SLOT(reset()) );
882 connect( saveBt
, SIGNAL(clicked()), this, SLOT(save()) );
884 vlayout
->setMargin( 5 );
885 vlayout
->setSpacing( 5 );
886 haveMpeg
= detectPpmtoMpegCommand();
887 mpegSave
= new QCheckBox( tr("Save in MPEG format (requires netpbm package installed)"), this );
888 vlayout
->addWidget(mpegSave
);
889 mpegSave
->setChecked( haveMpeg
);
890 mpegSave
->setEnabled( haveMpeg
);
891 savingAsMpeg
= haveMpeg
;
892 QWidget
*hbox2
= new QWidget( this );
893 vlayout
->addWidget(hbox2
);
894 QHBoxLayout
*hlayout2
= new QHBoxLayout( hbox2
);
895 statusText
= new QLabel( tr("Click record to begin recording."), hbox2
);
896 hlayout2
->addWidget(statusText
);
897 progressBar
= new QProgressBar( hbox2
);
898 progressBar
->setValue( 0 );
899 hlayout2
->addWidget(progressBar
);
903 AnimationSaveWidget::~AnimationSaveWidget()
906 removeTemporaryFiles();
910 // returns true if we have ppmtompeg command, else returns false
911 bool AnimationSaveWidget::detectPpmtoMpegCommand()
913 // search the PATH for the ppmtompeg command to test we can record to mpeg
914 QStringList paths
= QString(::getenv("PATH")).split(":");
915 for ( int i
= 0; i
< paths
.count(); i
++ )
916 if ( QFile::exists( paths
[i
] + "/" + "ppmtompeg" ) )
921 void AnimationSaveWidget::timerEvent( QTimerEvent
*te
)
926 if ( te
->timerId() == timerId
) {
928 // Add a frame to the animation
929 if ( savingAsMpeg
&& view
)
930 view
->image().save( str
.sprintf("/tmp/qvfb_tmp_image_%04d.ppm", imageNum
), "PPM");
931 else if ( animation
&& view
)
932 animation
->appendFrame(view
->image());//QPoint(0,0));
935 // Update the display of number of seconds that have been recorded.
936 int tmMsec
= tm
.elapsed();
937 timeDpy
->setText( str
.sprintf("%02d:%02d", tmMsec
/60000, (tmMsec
%60000)/1000) );
938 QObject::timerEvent( te
);
940 // Make the recording LED blink
942 static bool on
= false;
946 recLED
->setPixmap( recOff
);
948 recLED
->setPixmap( recOn
);
954 // Saving progress timer
955 if ( te
->timerId() == progressTimerId
) {
956 // Parse output log file to work out the encoding progress.
957 QFile
f("/tmp/qvfb_tmp_output.log");
958 f
.open(QIODevice::ReadOnly
);
962 while ( !f
.atEnd() ) {
963 // example of the output log entries
964 // During each frame:
965 // "FRAME 764 (B): I BLOCKS: 0......
967 // "======FRAMES READ: 766"
968 f
.readLine(buffer
, 1024);
969 str
= QString(buffer
);
970 if ( str
.left(6) == "FRAME " ) {
971 int num
= str
.mid(6, str
.indexOf(QChar(' '), 6) - 6).toInt();
972 if ( num
> largestNum
)
974 } else if ( str
.left(18) == "======FRAMES READ:" ) {
980 // Update the progress bar with the frame we are up to
981 progressBar
->setValue( largestNum
);
986 statusText
->setText( tr("Finished saving."));
987 removeTemporaryFiles();
988 killTimer( progressTimerId
);
989 progressTimerId
= -1;
995 // Takes the saved ppm files and converts them to a mpeg file named filename
996 void AnimationSaveWidget::convertToMpeg(QString filename
)
998 recLED
->setPixmap( recOff
);
999 killTimer( timerId
);
1001 progressBar
->show();
1002 progressBar
->setRange( 0, imageNum
);
1003 progressBar
->setValue( 0 );
1005 // Build parameter file required by ppmtompeg
1006 QFile
file("/tmp/qvfb_tmp_ppmtompeg.params");
1007 if ( file
.open( QIODevice::WriteOnly
) ) {
1008 QTextStream
t( &file
);
1009 t
<< "PATTERN IBBPBBPBBPBBPBB\n";
1010 t
<< "OUTPUT " << filename
<< "\n";
1011 t
<< "INPUT_DIR /tmp\n";
1014 str
= str
.sprintf("%04d", imageNum
- 1);
1015 t
<< "qvfb_tmp_image_*.ppm [0000-" << str
<< "]\n";
1017 t
<< "BASE_FILE_FORMAT PPM\n";
1018 t
<< "INPUT_CONVERT *\n";
1019 t
<< "GOP_SIZE 15\n";
1020 t
<< "SLICES_PER_FRAME 1\n";
1021 t
<< "PIXEL HALF\n";
1023 t
<< "PSEARCH_ALG LOGARITHMIC\n";
1024 t
<< "BSEARCH_ALG SIMPLE\n";
1028 t
<< "REFERENCE_FRAME DECODED\n";
1029 t
<< "ASPECT_RATIO 1\n";
1030 t
<< "FRAME_RATE 24\n";
1031 t
<< "BIT_RATE 64000\n"; // Quality
1032 t
<< "BUFFER_SIZE 2048\n";
1036 // ### can't use QProcess, not in Qt 2.3
1037 // ### but it's certainly in Qt 4! use it?
1038 // Execute the ppmtompeg command as a seperate process to do the encoding
1039 pid_t pid
= ::fork();
1042 // redirect stdout to log file
1043 freopen("/tmp/qvfb_tmp_output.log", "w", stdout
);
1044 // ppmtompeg tool is from the netpbm package
1045 ::execlp("ppmtompeg", "ppmtompeg", "/tmp/qvfb_tmp_ppmtompeg.params", (void *)0);
1049 // Update the saving progress bar every 200ms
1050 progressTimerId
= startTimer( 200 );
1053 // Cleanup temporary files created during creating a mpeg file
1054 void AnimationSaveWidget::removeTemporaryFiles()
1057 for ( int i
= 0; i
< imageNum
; i
++ )
1058 QFile::remove( str
.sprintf("/tmp/qvfb_tmp_image_%04d.ppm", i
) );
1059 QFile::remove("/tmp/qvfb_tmp_ppmtompeg.params");
1060 QFile::remove("/tmp/qvfb_tmp_output.log");
1064 // toggles between recording and paused (usually when record button clicked)
1065 void AnimationSaveWidget::toggleRecord()
1068 recLED
->setPixmap( recOff
);
1069 recBt
->setText( tr("Record") );
1070 statusText
->setText( tr("Paused. Click record to resume, or save if done."));
1071 killTimer( timerId
);
1073 elapsed
= tm
.elapsed();
1075 recLED
->setPixmap( recOn
);
1076 recBt
->setText( tr("Pause") );
1077 statusText
->setText( tr("Recording..."));
1079 if ( elapsed
== 0 ) {
1080 savingAsMpeg
= mpegSave
->isChecked();
1081 if ( !savingAsMpeg
) {
1083 animation
= new QAnimationWriter("/tmp/qvfb_tmp_animation.mng","MNG");
1084 animation
->setFrameRate(24);
1086 animation
->appendFrame(view
->image());
1089 tm
= tm
.addMSecs(-elapsed
);
1091 timerId
= startTimer(1000 / 24);
1093 recording
= !recording
;
1096 // Reset everything to initial state of not recording
1097 void AnimationSaveWidget::reset()
1101 statusText
->setText( tr("Click record to begin recording."));
1102 removeTemporaryFiles();
1104 progressBar
->setValue( 0 );
1105 timeDpy
->setText( "00:00" );
1112 // Prompt for filename to save to and put animation in that file
1113 void AnimationSaveWidget::save()
1116 toggleRecord(); // pauses
1117 statusText
->setText( tr("Saving... "));
1120 if ( savingAsMpeg
) {
1121 filename
= QFileDialog::getSaveFileName(this, tr("Save animation..."), "", "*.mpg");
1122 if ( !filename
.isNull() )
1123 convertToMpeg(filename
);
1125 filename
= QFileDialog::getSaveFileName(this, tr("Save animation..."), "", "*.mng");
1126 if (filename
.isNull()) {
1127 statusText
->setText(tr("Save canceled."));
1129 QFile::remove(filename
);
1130 bool success
= QFile::rename(QLatin1String("/tmp/qvfb_tmp_animation.mng"),
1133 statusText
->setText(tr("Finished saving."));
1136 statusText
->setText(tr("Save failed!"));