]> git.sesse.net Git - kdenlive/blob - src/vectorscope.cpp
Allow to loop selected timeline item
[kdenlive] / src / vectorscope.cpp
1 /***************************************************************************
2  *   Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com)      *
3  *   This file is part of kdenlive. See www.kdenlive.org.                  *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  ***************************************************************************/
10
11
12 #include <QAction>
13 #include <QColor>
14 #include <QDebug>
15 #include <QMouseEvent>
16 #include <QMenu>
17 #include <QPainter>
18
19 #include <QTime>
20
21 #include "colorplaneexport.h"
22 #include "colortools.h"
23 #include "renderer.h"
24 #include "vectorscope.h"
25 #include "vectorscopegenerator.h"
26
27 const float P75 = .75;
28 const unsigned char DEFAULT_Y = 255;
29
30 const QPointF YUV_R(-.147,  .615);
31 const QPointF YUV_G(-.289, -.515);
32 const QPointF YUV_B(.437, -.100);
33 const QPointF YUV_Cy(.147, -.615);
34 const QPointF YUV_Mg(.289,  .515);
35 const QPointF YUV_Yl(-.437,  .100);
36
37 const QPointF YPbPr_R(-.169, .5);
38 const QPointF YPbPr_G(-.331, -.419);
39 const QPointF YPbPr_B(.5, -.081);
40 const QPointF YPbPr_Cy(.169, -.5);
41 const QPointF YPbPr_Mg(.331, .419);
42 const QPointF YPbPr_Yl(-.5, .081);
43
44
45 Vectorscope::Vectorscope(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent) :
46     AbstractScopeWidget(projMonitor, clipMonitor, true, parent),
47     m_gain(1)
48 {
49     ui = new Ui::Vectorscope_UI();
50     ui->setupUi(this);
51
52     m_colorTools = new ColorTools();
53     m_colorPlaneExport = new ColorPlaneExport(this);
54     m_vectorscopeGenerator = new VectorscopeGenerator();
55
56     ui->paintMode->addItem(i18n("Green 2"), QVariant(VectorscopeGenerator::PaintMode_Green2));
57     ui->paintMode->addItem(i18n("Green"), QVariant(VectorscopeGenerator::PaintMode_Green));
58     ui->paintMode->addItem(i18n("Black"), QVariant(VectorscopeGenerator::PaintMode_Black));
59     ui->paintMode->addItem(i18n("Modified YUV (Chroma)"), QVariant(VectorscopeGenerator::PaintMode_Chroma));
60     ui->paintMode->addItem(i18n("YUV"), QVariant(VectorscopeGenerator::PaintMode_YUV));
61     ui->paintMode->addItem(i18n("Original Color"), QVariant(VectorscopeGenerator::PaintMode_Original));
62
63     ui->backgroundMode->addItem(i18n("None"), QVariant(BG_NONE));
64     ui->backgroundMode->addItem(i18n("YUV"), QVariant(BG_YUV));
65     ui->backgroundMode->addItem(i18n("Modified YUV (Chroma)"), QVariant(BG_CHROMA));
66     ui->backgroundMode->addItem(i18n("YPbPr"), QVariant(BG_YPbPr));
67
68     ui->sliderGain->setMinimum(0);
69     ui->sliderGain->setMaximum(40);
70
71     bool b = true;
72     b &= connect(ui->backgroundMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotBackgroundChanged()));
73     b &= connect(ui->sliderGain, SIGNAL(valueChanged(int)), this, SLOT(slotGainChanged(int)));
74     b &= connect(ui->paintMode, SIGNAL(currentIndexChanged(int)), this, SLOT(forceUpdateScope()));
75     b &= connect(this, SIGNAL(signalMousePositionChanged()), this, SLOT(forceUpdateHUD()));
76     ui->sliderGain->setValue(0);
77
78
79     ///// Build context menu /////
80
81     m_menu->addSeparator();
82
83     m_aExportBackground = new QAction(i18n("Export background"), this);
84     m_menu->addAction(m_aExportBackground);
85     b &= connect(m_aExportBackground, SIGNAL(triggered()), this, SLOT(slotExportBackground()));
86
87     m_a75PBox = new QAction(i18n("75% box"), this);
88     m_a75PBox->setCheckable(true);
89     m_menu->addAction(m_a75PBox);
90     b &= connect(m_a75PBox, SIGNAL(changed()), this, SLOT(forceUpdateBackground()));
91
92     m_aAxisEnabled = new QAction(i18n("Draw axis"), this);
93     m_aAxisEnabled->setCheckable(true);
94     m_menu->addAction(m_aAxisEnabled);
95     b &= connect(m_aAxisEnabled, SIGNAL(changed()), this, SLOT(forceUpdateBackground()));
96
97     m_menu->addSeparator()->setText(i18n("Color Space"));
98     m_aColorSpace_YPbPr = new QAction(i18n("YPbPr"), this);
99     m_aColorSpace_YPbPr->setCheckable(true);
100     m_aColorSpace_YUV = new QAction(i18n("YUV"), this);
101     m_aColorSpace_YUV->setCheckable(true);
102     m_agColorSpace = new QActionGroup(this);
103     m_agColorSpace->addAction(m_aColorSpace_YPbPr);
104     m_agColorSpace->addAction(m_aColorSpace_YUV);
105     m_menu->addAction(m_aColorSpace_YPbPr);
106     m_menu->addAction(m_aColorSpace_YUV);
107     b &= connect(m_aColorSpace_YPbPr, SIGNAL(toggled(bool)), this, SLOT(slotColorSpaceChanged()));
108     b &= connect(m_aColorSpace_YUV, SIGNAL(toggled(bool)), this, SLOT(slotColorSpaceChanged()));
109
110     Q_ASSERT(b);
111
112     // To make the 1.0x text show
113     slotGainChanged(ui->sliderGain->value());
114
115     init();
116 }
117
118 Vectorscope::~Vectorscope()
119 {
120     writeConfig();
121
122     delete m_colorTools;
123     delete m_colorPlaneExport;
124     delete m_vectorscopeGenerator;
125
126     delete m_aColorSpace_YPbPr;
127     delete m_aColorSpace_YUV;
128     delete m_aExportBackground;
129     delete m_aAxisEnabled;
130     delete m_a75PBox;
131     delete m_agColorSpace;
132 }
133
134 QString Vectorscope::widgetName() const { return QString("Vectorscope"); }
135
136 void Vectorscope::readConfig()
137 {
138     AbstractScopeWidget::readConfig();
139
140     KSharedConfigPtr config = KGlobal::config();
141     KConfigGroup scopeConfig(config, configName());
142     m_a75PBox->setChecked(scopeConfig.readEntry("75PBox", false));
143     m_aAxisEnabled->setChecked(scopeConfig.readEntry("axis", false));
144     ui->backgroundMode->setCurrentIndex(scopeConfig.readEntry("backgroundmode").toInt());
145     ui->paintMode->setCurrentIndex(scopeConfig.readEntry("paintmode").toInt());
146     ui->sliderGain->setValue(scopeConfig.readEntry("gain", 1));
147     m_aColorSpace_YPbPr->setChecked(scopeConfig.readEntry("colorspace_ypbpr", false));
148     m_aColorSpace_YUV->setChecked(!m_aColorSpace_YPbPr->isChecked());
149 }
150
151 void Vectorscope::writeConfig()
152 {
153     KSharedConfigPtr config = KGlobal::config();
154     KConfigGroup scopeConfig(config, configName());
155     scopeConfig.writeEntry("75PBox", m_a75PBox->isChecked());
156     scopeConfig.writeEntry("axis", m_aAxisEnabled->isChecked());
157     scopeConfig.writeEntry("backgroundmode", ui->backgroundMode->currentIndex());
158     scopeConfig.writeEntry("paintmode", ui->paintMode->currentIndex());
159     scopeConfig.writeEntry("gain", ui->sliderGain->value());
160     scopeConfig.writeEntry("colorspace_ypbpr", m_aColorSpace_YPbPr->isChecked());
161     scopeConfig.sync();
162 }
163
164 QRect Vectorscope::scopeRect()
165 {
166     // Distance from top/left/right
167     int offset = 6;
168
169     // We want to paint below the controls area. The line is the lowest element.
170     QPoint topleft(offset, ui->verticalSpacer->geometry().y()+offset);
171     QPoint bottomright(ui->horizontalSpacer->geometry().right()-offset, this->size().height()-offset);
172
173     QRect scopeRect(topleft, bottomright);
174
175     // Circle Width: min of width and height
176     cw = (scopeRect.height() < scopeRect.width()) ? scopeRect.height() : scopeRect.width();
177     scopeRect.setWidth(cw);
178     scopeRect.setHeight(cw);
179
180
181     m_centerPoint = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), QPointF(0,0));
182     pR75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_R);
183     pG75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_G);
184     pB75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_B);
185     pCy75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_Cy);
186     pMg75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_Mg);
187     pYl75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_Yl);
188     qR75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YPbPr_R);
189     qG75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YPbPr_G);
190     qB75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YPbPr_B);
191     qCy75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YPbPr_Cy);
192     qMg75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YPbPr_Mg);
193     qYl75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YPbPr_Yl);
194
195     return scopeRect;
196 }
197
198 bool Vectorscope::isHUDDependingOnInput() const { return false; }
199 bool Vectorscope::isScopeDependingOnInput() const { return true; }
200 bool Vectorscope::isBackgroundDependingOnInput() const { return false; }
201
202 QImage Vectorscope::renderHUD(uint)
203 {
204
205     QImage hud;
206
207     if (m_mouseWithinWidget) {
208         // Mouse moved: Draw a circle over the scope
209
210         hud = QImage(m_scopeRect.size(), QImage::Format_ARGB32);
211         hud.fill(qRgba(0, 0, 0, 0));
212
213         QPainter davinci(&hud);
214         QPoint widgetCenterPoint = m_scopeRect.topLeft() + m_centerPoint;
215
216         int dx = -widgetCenterPoint.x()+m_mousePos.x();
217         int dy =  widgetCenterPoint.y()-m_mousePos.y();
218
219         QPoint reference = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), QPointF(1,0));
220
221         float r = sqrt(dx*dx + dy*dy);
222         float percent = (float) 100*r/VectorscopeGenerator::scaling/m_gain/(reference.x() - widgetCenterPoint.x());
223
224         switch (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt()) {
225         case BG_NONE:
226             davinci.setPen(penLight);
227             break;
228         default:
229             if (r > cw/2) {
230                 davinci.setPen(penLight);
231             } else {
232                 davinci.setPen(penDark);
233             }
234             break;
235         }
236         davinci.drawEllipse(m_centerPoint, (int)r, (int)r);
237         davinci.setPen(penThin);
238         davinci.drawText(QPoint(m_scopeRect.width()-40, m_scopeRect.height()), i18n("%1 \%", QString::number(percent, 'f', 0)));
239
240         float angle = copysign(acos(dx/r)*180/M_PI, dy);
241         davinci.drawText(QPoint(10, m_scopeRect.height()), i18n("%1°", QString::number(angle, 'f', 1)));
242
243 //        m_circleEnabled = false;
244     } else {
245         hud = QImage(0, 0, QImage::Format_ARGB32);
246     }
247     emit signalHUDRenderingFinished(0, 1);
248     return hud;
249 }
250
251 QImage Vectorscope::renderScope(uint accelerationFactor, const QImage qimage)
252 {
253     QTime start = QTime::currentTime();
254     QImage scope;
255
256     if (cw <= 0) {
257         qDebug() << "Scope size not known yet. Aborting.";
258     } else {
259
260         VectorscopeGenerator::ColorSpace colorSpace = m_aColorSpace_YPbPr->isChecked() ?
261                                                       VectorscopeGenerator::ColorSpace_YPbPr : VectorscopeGenerator::ColorSpace_YUV;
262         VectorscopeGenerator::PaintMode paintMode = (VectorscopeGenerator::PaintMode) ui->paintMode->itemData(ui->paintMode->currentIndex()).toInt();
263         scope = m_vectorscopeGenerator->calculateVectorscope(m_scopeRect.size(),
264                                                              qimage,
265                                                              m_gain, paintMode, colorSpace,
266                                                              m_aAxisEnabled->isChecked(), accelerationFactor);
267
268     }
269
270     unsigned int mseconds = start.msecsTo(QTime::currentTime());
271     emit signalScopeRenderingFinished(mseconds, accelerationFactor);
272     return scope;
273 }
274
275 QImage Vectorscope::renderBackground(uint)
276 {
277     QTime start = QTime::currentTime();
278     start.start();
279
280     QImage bg;
281     switch (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt()) {
282     case BG_YUV:
283         qDebug() << "YUV background.";
284         bg = m_colorTools->yuvColorWheel(m_scopeRect.size(), (unsigned char) 128, 1/VectorscopeGenerator::scaling, false, true);
285         break;
286     case BG_CHROMA:
287         bg = m_colorTools->yuvColorWheel(m_scopeRect.size(), (unsigned char) 255, 1/VectorscopeGenerator::scaling, true, true);
288         break;
289     case BG_YPbPr:
290         bg = m_colorTools->yPbPrColorWheel(m_scopeRect.size(), (unsigned char) 128, 1/VectorscopeGenerator::scaling, true);
291         break;
292     default:
293         qDebug() << "No background.";
294         bg = QImage(cw, cw, QImage::Format_ARGB32);
295         bg.fill(qRgba(0,0,0,0));
296         break;
297     }
298
299
300     // Draw the vectorscope circle
301     QPainter davinci(&bg);
302     QPoint vinciPoint;
303
304
305     davinci.setRenderHint(QPainter::Antialiasing, true);
306
307     davinci.setPen(penThick);
308     davinci.drawEllipse(0, 0, cw, cw);
309
310     // Draw RGB/CMY points with 100% chroma
311     if (m_aColorSpace_YUV->isChecked()) {
312         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_R);
313         davinci.drawEllipse(vinciPoint, 4,4);
314         davinci.drawText(vinciPoint-QPoint(20, -10), "R");
315
316         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_G);
317         davinci.drawEllipse(vinciPoint, 4,4);
318         davinci.drawText(vinciPoint-QPoint(20, 0), "G");
319
320         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_B);
321         davinci.drawEllipse(vinciPoint, 4,4);
322         davinci.drawText(vinciPoint+QPoint(15, 10), "B");
323
324         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Cy);
325         davinci.drawEllipse(vinciPoint, 4,4);
326         davinci.drawText(vinciPoint+QPoint(15, -5), "Cy");
327
328         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Mg);
329         davinci.drawEllipse(vinciPoint, 4,4);
330         davinci.drawText(vinciPoint+QPoint(15, 10), "Mg");
331
332         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Yl);
333         davinci.drawEllipse(vinciPoint, 4,4);
334         davinci.drawText(vinciPoint-QPoint(25, 0), "Yl");
335     } else {
336         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_R);
337         davinci.drawEllipse(vinciPoint, 4,4);
338         davinci.drawText(vinciPoint-QPoint(20, -10), "R");
339
340         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_G);
341         davinci.drawEllipse(vinciPoint, 4,4);
342         davinci.drawText(vinciPoint-QPoint(20, 0), "G");
343
344         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_B);
345         davinci.drawEllipse(vinciPoint, 4,4);
346         davinci.drawText(vinciPoint+QPoint(15, 10), "B");
347
348         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_Cy);
349         davinci.drawEllipse(vinciPoint, 4,4);
350         davinci.drawText(vinciPoint+QPoint(15, -5), "Cy");
351
352         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_Mg);
353         davinci.drawEllipse(vinciPoint, 4,4);
354         davinci.drawText(vinciPoint+QPoint(15, 10), "Mg");
355
356         vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_Yl);
357         davinci.drawEllipse(vinciPoint, 4,4);
358         davinci.drawText(vinciPoint-QPoint(25, 0), "Yl");
359     }
360
361     switch (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt()) {
362     case BG_NONE:
363         davinci.setPen(penLight);
364         break;
365     default:
366         davinci.setPen(penDark);
367         break;
368     }
369
370     // Draw axis
371     if (m_aAxisEnabled->isChecked()) {
372         davinci.drawLine(m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), QPointF(0,-.9)), m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), QPointF(0,.9)));
373         davinci.drawLine(m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), QPointF(-.9,0)), m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), QPointF(.9,0)));
374     }
375
376     // Draw center point
377     switch (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt()) {
378     case BG_CHROMA:
379         davinci.setPen(penDark);
380         break;
381     default:
382         davinci.setPen(penThin);
383         break;
384     }
385     davinci.drawEllipse(m_centerPoint, 5,5);
386
387
388     // Draw 75% box
389     if (m_a75PBox->isChecked()) {
390         if (m_aColorSpace_YUV->isChecked()) {
391             davinci.drawLine(pR75, pYl75);
392             davinci.drawLine(pYl75, pG75);
393             davinci.drawLine(pG75, pCy75);
394             davinci.drawLine(pCy75, pB75);
395             davinci.drawLine(pB75, pMg75);
396             davinci.drawLine(pMg75, pR75);
397         } else {
398             davinci.drawLine(qR75, qYl75);
399             davinci.drawLine(qYl75, qG75);
400             davinci.drawLine(qG75, qCy75);
401             davinci.drawLine(qCy75, qB75);
402             davinci.drawLine(qB75, qMg75);
403             davinci.drawLine(qMg75, qR75);
404         }
405     }
406
407     // Draw RGB/CMY points with 75% chroma (for NTSC)
408     davinci.setPen(penThin);
409     if (m_aColorSpace_YUV->isChecked()) {
410         davinci.drawEllipse(pR75, 3,3);
411         davinci.drawEllipse(pG75, 3,3);
412         davinci.drawEllipse(pB75, 3,3);
413         davinci.drawEllipse(pCy75, 3,3);
414         davinci.drawEllipse(pMg75, 3,3);
415         davinci.drawEllipse(pYl75, 3,3);
416     } else {
417         davinci.drawEllipse(qR75, 3,3);
418         davinci.drawEllipse(qG75, 3,3);
419         davinci.drawEllipse(qB75, 3,3);
420         davinci.drawEllipse(qCy75, 3,3);
421         davinci.drawEllipse(qMg75, 3,3);
422         davinci.drawEllipse(qYl75, 3,3);
423     }
424
425     // Draw realtime factor (number of skipped pixels)
426     if (m_aRealtime->isChecked()) {
427         davinci.setPen(penThin);
428         davinci.drawText(QPoint(m_scopeRect.width()-40, m_scopeRect.height()-15), QVariant(m_accelFactorScope).toString().append("x"));
429     }
430
431     emit signalBackgroundRenderingFinished(start.elapsed(), 1);
432     return bg;
433 }
434
435
436
437
438 ///// Slots /////
439
440 void Vectorscope::slotGainChanged(int newval)
441 {
442     m_gain = 1 + (float)newval/10;
443     ui->lblGain->setText(QString::number(m_gain, 'f', 1) + "x");
444     forceUpdateScope();
445 }
446
447 void Vectorscope::slotExportBackground()
448 {
449     qDebug() << "Exporting background";
450     m_colorPlaneExport->show();
451
452 }
453
454 void Vectorscope::slotBackgroundChanged()
455 {
456     // Background changed, switch to a suitable color mode now
457     int index;
458     switch (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt()) {
459     case BG_YUV:
460         index = ui->paintMode->findData(QVariant(VectorscopeGenerator::PaintMode_Black));
461         if (index >= 0) {
462             ui->paintMode->setCurrentIndex(index);
463         }
464         break;
465
466     case BG_NONE:
467         if (ui->paintMode->itemData(ui->paintMode->currentIndex()).toInt() == VectorscopeGenerator::PaintMode_Black) {
468             index = ui->paintMode->findData(QVariant(VectorscopeGenerator::PaintMode_Green2));
469             ui->paintMode->setCurrentIndex(index);
470         }
471         break;
472     }
473     forceUpdateBackground();
474 }
475
476 void Vectorscope::slotColorSpaceChanged()
477 {
478     int index;
479     if (m_aColorSpace_YPbPr->isChecked()) {
480         if (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt() == BG_YUV) {
481             index = ui->backgroundMode->findData(QVariant(BG_YPbPr));
482             if (index >= 0) {
483                 ui->backgroundMode->setCurrentIndex(index);
484             }
485         }
486     } else {
487         if (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt() == BG_YPbPr) {
488             index = ui->backgroundMode->findData(QVariant(BG_YUV));
489             if (index >= 0) {
490                 ui->backgroundMode->setCurrentIndex(index);
491             }
492         }
493     }
494     forceUpdate();
495 }