]> git.sesse.net Git - kdenlive/blob - src/renderwidget.cpp
01069f5e8182a69ca50a216801c8275c6d8d0036
[kdenlive] / src / renderwidget.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, write to the                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
18  ***************************************************************************/
19
20
21 #include <QDomDocument>
22
23 #include <KStandardDirs>
24 #include <KDebug>
25 #include <KMessageBox>
26 #include <KComboBox>
27
28 #include "kdenlivesettings.h"
29 #include "renderwidget.h"
30 #include "ui_saveprofile_ui.h"
31
32 const int GroupRole = Qt::UserRole;
33 const int ExtensionRole = GroupRole + 1;
34 const int StandardRole = GroupRole + 2;
35 const int RenderRole = GroupRole + 3;
36 const int ParamsRole = GroupRole + 4;
37 const int EditableRole = GroupRole + 5;
38
39 RenderWidget::RenderWidget(QWidget * parent): QDialog(parent) {
40     m_view.setupUi(this);
41     m_view.buttonDelete->setIcon(KIcon("trash-empty"));
42     m_view.buttonDelete->setToolTip(i18n("Delete profile"));
43     m_view.buttonDelete->setEnabled(false);
44
45     m_view.buttonEdit->setIcon(KIcon("document-properties"));
46     m_view.buttonEdit->setToolTip(i18n("Edit profile"));
47     m_view.buttonEdit->setEnabled(false);
48
49     m_view.buttonSave->setIcon(KIcon("document-new"));
50     m_view.buttonSave->setToolTip(i18n("Create new profile"));
51
52     m_view.buttonInfo->setIcon(KIcon("help-about"));
53
54     if (KdenliveSettings::showrenderparams()) {
55         m_view.buttonInfo->setDown(true);
56     } else m_view.advanced_params->hide();
57
58     m_view.experimentalrender->setChecked(KdenliveSettings::experimentalrender());
59
60     m_view.experimentalrender->setToolTip(i18n("Changing the size of video when rendering\nis not fully supported, you may have problems\nwith some effects or title clips, so the export\nprofiles that resize your video are marked as\nexperimental"));
61
62     connect(m_view.buttonInfo, SIGNAL(clicked()), this, SLOT(showInfoPanel()));
63
64     connect(m_view.buttonSave, SIGNAL(clicked()), this, SLOT(slotSaveProfile()));
65     connect(m_view.buttonEdit, SIGNAL(clicked()), this, SLOT(slotEditProfile()));
66     connect(m_view.buttonDelete, SIGNAL(clicked()), this, SLOT(slotDeleteProfile()));
67     connect(m_view.buttonStart, SIGNAL(clicked()), this, SLOT(slotExport()));
68     connect(m_view.out_file, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButtons()));
69     connect(m_view.format_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshView()));
70     connect(m_view.size_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshParams()));
71
72     connect(m_view.render_guide, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
73     connect(m_view.render_zone, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
74     connect(m_view.render_full, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
75
76     connect(m_view.guide_end, SIGNAL(activated(int)), this, SLOT(slotCheckStartGuidePosition()));
77     connect(m_view.guide_start, SIGNAL(activated(int)), this, SLOT(slotCheckEndGuidePosition()));
78
79     connect(m_view.format_selection, SIGNAL(activated(int)), this, SLOT(refreshView()));
80     connect(m_view.experimentalrender, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateExperimentalRendering()));
81
82     m_view.buttonStart->setEnabled(false);
83     m_view.guides_box->setVisible(false);
84     parseProfiles();
85     m_view.splitter->setStretchFactor(1, 5);
86     m_view.splitter->setStretchFactor(0, 2);
87
88     focusFirstVisibleItem();
89 }
90
91 void RenderWidget::slotUpdateExperimentalRendering() {
92     KdenliveSettings::setExperimentalrender(m_view.experimentalrender->isChecked());
93     refreshView();
94 }
95
96
97 void RenderWidget::showInfoPanel() {
98     if (m_view.advanced_params->isVisible()) {
99         m_view.advanced_params->setVisible(false);
100         m_view.buttonInfo->setDown(false);
101         KdenliveSettings::setShowrenderparams(false);
102     } else {
103         m_view.advanced_params->setVisible(true);
104         m_view.buttonInfo->setDown(true);
105         KdenliveSettings::setShowrenderparams(true);
106     }
107 }
108
109 void RenderWidget::slotUpdateGuideBox() {
110     m_view.guides_box->setVisible(m_view.render_guide->isChecked());
111 }
112
113 void RenderWidget::slotCheckStartGuidePosition() {
114     if (m_view.guide_start->currentIndex() > m_view.guide_end->currentIndex())
115         m_view.guide_start->setCurrentIndex(m_view.guide_end->currentIndex());
116 }
117
118 void RenderWidget::slotCheckEndGuidePosition() {
119     if (m_view.guide_end->currentIndex() < m_view.guide_start->currentIndex())
120         m_view.guide_end->setCurrentIndex(m_view.guide_start->currentIndex());
121 }
122
123 void RenderWidget::setGuides(QDomElement guidesxml, double duration) {
124     m_view.guide_start->clear();
125     m_view.guide_end->clear();
126     QDomNodeList nodes = guidesxml.elementsByTagName("guide");
127     if (nodes.count() > 0) {
128         m_view.guide_start->addItem(i18n("Render"), "0");
129         m_view.render_guide->setEnabled(true);
130     } else m_view.render_guide->setEnabled(false);
131     for (int i = 0; i < nodes.count(); i++) {
132         QDomElement e = nodes.item(i).toElement();
133         if (!e.isNull()) {
134             m_view.guide_start->addItem(e.attribute("comment"), e.attribute("time").toDouble());
135             m_view.guide_end->addItem(e.attribute("comment"), e.attribute("time").toDouble());
136         }
137     }
138     if (nodes.count() > 0)
139         m_view.guide_end->addItem(i18n("End"), QString::number(duration));
140 }
141
142 void RenderWidget::slotUpdateButtons() {
143     if (m_view.out_file->url().isEmpty()) m_view.buttonStart->setEnabled(false);
144     else m_view.buttonStart->setEnabled(true);
145 }
146
147 void RenderWidget::slotSaveProfile() {
148     Ui::SaveProfile_UI ui;
149     QDialog *d = new QDialog(this);
150     ui.setupUi(d);
151     QString customGroup = i18n("Custom");
152     QStringList groupNames;
153     for (int i = 0; i < m_view.format_list->count(); i++)
154         groupNames.append(m_view.format_list->item(i)->text());
155     if (!groupNames.contains(customGroup)) groupNames.prepend(customGroup);
156     ui.group_name->addItems(groupNames);
157     int pos = ui.group_name->findText(customGroup);
158     ui.group_name->setCurrentIndex(pos);
159
160     ui.parameters->setText(m_view.advanced_params->toPlainText());
161     ui.extension->setText(m_view.size_list->currentItem()->data(ExtensionRole).toString());
162     ui.profile_name->setFocus();
163     if (d->exec() == QDialog::Accepted && !ui.profile_name->text().simplified().isEmpty()) {
164         QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
165         QDomDocument doc;
166         QFile file(exportFile);
167         doc.setContent(&file, false);
168         file.close();
169
170         QDomElement documentElement;
171         bool groupExists = false;
172         QString groupName;
173         QString newProfileName = ui.profile_name->text().simplified();
174         QString newGroupName = ui.group_name->currentText();
175         QDomNodeList groups = doc.elementsByTagName("group");
176         int i = 0;
177         if (groups.count() == 0) {
178             QDomElement profiles = doc.createElement("profiles");
179             doc.appendChild(profiles);
180         } else while (!groups.item(i).isNull()) {
181                 documentElement = groups.item(i).toElement();
182                 groupName = documentElement.attribute("name");
183                 kDebug() << "// SAVE, PARSING FROUP: " << i << ", name: " << groupName << ", LOOK FR: " << newGroupName;
184                 if (groupName == newGroupName) {
185                     groupExists = true;
186                     break;
187                 }
188                 i++;
189             }
190         if (!groupExists) {
191             documentElement = doc.createElement("group");
192             documentElement.setAttribute("name", ui.group_name->currentText());
193             documentElement.setAttribute("renderer", "avformat");
194             doc.documentElement().appendChild(documentElement);
195         }
196         QDomElement profileElement = doc.createElement("profile");
197         profileElement.setAttribute("name", newProfileName);
198         profileElement.setAttribute("extension", ui.extension->text().simplified());
199         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
200         documentElement.appendChild(profileElement);
201
202         //QCString save = doc.toString().utf8();
203
204         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
205             KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
206             delete d;
207             return;
208         }
209         QTextStream out(&file);
210         out << doc.toString();
211         file.close();
212         parseProfiles(newGroupName, newProfileName);
213     }
214     delete d;
215 }
216
217 void RenderWidget::slotEditProfile() {
218     QListWidgetItem *item = m_view.size_list->currentItem();
219     if (!item) return;
220     QString currentGroup = m_view.format_list->currentItem()->text();
221
222     QString params = item->data(ParamsRole).toString();
223     QString extension = item->data(ExtensionRole).toString();
224     QString currentProfile = item->text();
225
226     Ui::SaveProfile_UI ui;
227     QDialog *d = new QDialog(this);
228     ui.setupUi(d);
229     QStringList groupNames;
230     for (int i = 0; i < m_view.format_list->count(); i++)
231         groupNames.append(m_view.format_list->item(i)->text());
232     if (!groupNames.contains(currentGroup)) groupNames.prepend(currentGroup);
233     ui.group_name->addItems(groupNames);
234     int pos = ui.group_name->findText(currentGroup);
235     ui.group_name->setCurrentIndex(pos);
236     ui.profile_name->setText(currentProfile);
237     ui.extension->setText(extension);
238     ui.parameters->setText(params);
239     ui.profile_name->setFocus();
240
241     if (d->exec() == QDialog::Accepted) {
242         slotDeleteProfile();
243         QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
244         QDomDocument doc;
245         QFile file(exportFile);
246         doc.setContent(&file, false);
247         file.close();
248
249         QDomElement documentElement;
250         bool groupExists = false;
251         QString groupName;
252         QString newProfileName = ui.profile_name->text();
253         QString newGroupName = ui.group_name->currentText();
254         QDomNodeList groups = doc.elementsByTagName("group");
255         int i = 0;
256         if (groups.count() == 0) {
257             QDomElement profiles = doc.createElement("profiles");
258             doc.appendChild(profiles);
259         } else while (!groups.item(i).isNull()) {
260                 documentElement = groups.item(i).toElement();
261                 groupName = documentElement.attribute("name");
262                 kDebug() << "// SAVE, PARSING FROUP: " << i << ", name: " << groupName << ", LOOK FR: " << newGroupName;
263                 if (groupName == newGroupName) {
264                     groupExists = true;
265                     break;
266                 }
267                 i++;
268             }
269         if (!groupExists) {
270             documentElement = doc.createElement("group");
271             documentElement.setAttribute("name", ui.group_name->currentText());
272             documentElement.setAttribute("renderer", "avformat");
273             doc.documentElement().appendChild(documentElement);
274         }
275         QDomElement profileElement = doc.createElement("profile");
276         profileElement.setAttribute("name", newProfileName);
277         profileElement.setAttribute("extension", ui.extension->text().simplified());
278         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
279         documentElement.appendChild(profileElement);
280
281         //QCString save = doc.toString().utf8();
282
283         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
284             KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
285             delete d;
286             return;
287         }
288         QTextStream out(&file);
289         out << doc.toString();
290         file.close();
291         parseProfiles(newGroupName, newProfileName);
292     }
293     delete d;
294 }
295
296 void RenderWidget::slotDeleteProfile() {
297     QString currentGroup = m_view.format_list->currentItem()->text();
298     QString currentProfile = m_view.size_list->currentItem()->text();
299
300     QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
301     QDomDocument doc;
302     QFile file(exportFile);
303     doc.setContent(&file, false);
304     file.close();
305
306     QDomElement documentElement;
307     bool groupExists = false;
308     QString groupName;
309     QDomNodeList groups = doc.elementsByTagName("group");
310     int i = 0;
311
312     while (!groups.item(i).isNull()) {
313         documentElement = groups.item(i).toElement();
314         groupName = documentElement.attribute("name");
315         if (groupName == currentGroup) {
316             QDomNodeList children = documentElement.childNodes();
317             for (int j = 0; j < children.count(); j++) {
318                 QDomElement pro = children.at(j).toElement();
319                 if (pro.attribute("name") == currentProfile) {
320                     groups.item(i).removeChild(children.at(j));
321                     if (groups.item(i).childNodes().isEmpty())
322                         doc.documentElement().removeChild(groups.item(i));
323                     break;
324                 }
325             }
326             break;
327         }
328         i++;
329     }
330
331     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
332         KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
333         return;
334     }
335     QTextStream out(&file);
336     out << doc.toString();
337     file.close();
338     parseProfiles(currentGroup);
339     focusFirstVisibleItem();
340 }
341
342 void RenderWidget::updateButtons() {
343     if (!m_view.size_list->currentItem() || m_view.size_list->currentItem()->isHidden()) {
344         m_view.buttonSave->setEnabled(false);
345         m_view.buttonDelete->setEnabled(false);
346         m_view.buttonEdit->setEnabled(false);
347     } else {
348         m_view.buttonSave->setEnabled(true);
349         if (m_view.size_list->currentItem()->data(EditableRole).toString().isEmpty()) {
350             m_view.buttonDelete->setEnabled(false);
351             m_view.buttonEdit->setEnabled(false);
352         } else {
353             m_view.buttonDelete->setEnabled(true);
354             m_view.buttonEdit->setEnabled(true);
355         }
356     }
357 }
358
359
360 void RenderWidget::focusFirstVisibleItem() {
361     if (m_view.size_list->currentItem() && !m_view.size_list->currentItem()->isHidden()) {
362         updateButtons();
363         return;
364     }
365     for (uint ix = 0; ix < m_view.size_list->count(); ix++) {
366         QListWidgetItem *item = m_view.size_list->item(ix);
367         if (item && !item->isHidden()) {
368             m_view.size_list->setCurrentRow(ix);
369             break;
370         }
371     }
372     if (!m_view.size_list->currentItem()) m_view.size_list->setCurrentRow(0);
373     updateButtons();
374 }
375
376 void RenderWidget::slotExport() {
377     QListWidgetItem *item = m_view.size_list->currentItem();
378     if (!item) return;
379     QFile f(m_view.out_file->url().path());
380     if (f.exists()) {
381         if (KMessageBox::warningYesNo(this, i18n("File already exists. Do you want to overwrite it ?")) != KMessageBox::Yes)
382             return;
383     }
384     QStringList overlayargs;
385     if (m_view.tc_overlay->isChecked()) {
386         QString filterFile = KStandardDirs::locate("appdata", "metadata.properties");
387         overlayargs << "meta.attr.timecode=1" << "meta.attr.timecode.markup=#timecode";
388         overlayargs << "-attach" << "data_feed:attr_check" << "-attach";
389         overlayargs << "data_show:" + filterFile << "_fezzik=1" << "dynamic=1";
390     }
391     double startPos = -1;
392     double endPos = -1;
393     if (m_view.render_guide->isChecked()) {
394         startPos = m_view.guide_start->itemData(m_view.guide_start->currentIndex()).toDouble();
395         endPos = m_view.guide_end->itemData(m_view.guide_end->currentIndex()).toDouble();
396     }
397     QString renderArgs = m_view.advanced_params->toPlainText();
398     renderArgs.replace("%width", QString::number(m_profile.width));
399     renderArgs.replace("%height", QString::number(m_profile.height));
400     renderArgs.replace("%dar", "@" + QString::number(m_profile.display_aspect_num) + "/" + QString::number(m_profile.display_aspect_den));
401     if (m_view.force_progressive->checkState() == Qt::Checked) renderArgs.append(" progressive=1");
402     else if (m_view.force_progressive->checkState() == Qt::Unchecked) renderArgs.append(" progressive=0");
403     emit doRender(m_view.out_file->url().path(), item->data(RenderRole).toString(), overlayargs, renderArgs.simplified().split(' '), m_view.render_zone->isChecked(), m_view.play_after->isChecked(), startPos, endPos);
404 }
405
406 void RenderWidget::setProfile(MltVideoProfile profile) {
407     m_profile = profile;
408     //WARNING: this way to tell the video standard is a bit hackish...
409     if (m_profile.description.contains("pal", Qt::CaseInsensitive) || m_profile.description.contains("25", Qt::CaseInsensitive) || m_profile.description.contains("50", Qt::CaseInsensitive)) m_view.format_selection->setCurrentIndex(0);
410     else m_view.format_selection->setCurrentIndex(1);
411     m_view.force_progressive->setCheckState(Qt::PartiallyChecked);
412     refreshView();
413 }
414
415 void RenderWidget::refreshView() {
416     m_view.size_list->blockSignals(true);
417     QListWidgetItem *item = m_view.format_list->currentItem();
418     if (!item) {
419         m_view.format_list->setCurrentRow(0);
420         item = m_view.format_list->currentItem();
421     }
422     if (!item) return;
423     QString std;
424     QString group = item->text();
425     QListWidgetItem *sizeItem;
426     bool firstSelected = false;
427     for (int i = 0; i < m_view.size_list->count(); i++) {
428         sizeItem = m_view.size_list->item(i);
429         if (sizeItem->data(GroupRole) == group) {
430             std = sizeItem->data(StandardRole).toString();
431             if (!std.isEmpty()) {
432                 if (std.contains("PAL", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 0);
433                 else if (std.contains("NTSC", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 1);
434             } else {
435                 sizeItem->setHidden(false);
436                 if (!firstSelected) m_view.size_list->setCurrentItem(sizeItem);
437                 firstSelected = true;
438             }
439             if (!KdenliveSettings::experimentalrender() && !sizeItem->isHidden()) {
440                 // hide experimental codecs (which do resize the video)
441                 std = sizeItem->data(ParamsRole).toString();
442                 if (std.contains(" s=")) {
443                     QString subsize = std.section(" s=", 1, 1);
444                     subsize = subsize.section(' ', 0, 0).toLower();
445                     if (subsize != "%widthx%height") {
446                         const QString currentSize = QString::number(m_profile.width) + 'x' + QString::number(m_profile.height);
447                         if (subsize != currentSize) sizeItem->setHidden(true);
448                     }
449                 }
450             }
451         } else sizeItem->setHidden(true);
452     }
453     focusFirstVisibleItem();
454     m_view.size_list->blockSignals(false);
455     refreshParams();
456 }
457
458 void RenderWidget::refreshParams() {
459     QListWidgetItem *item = m_view.size_list->currentItem();
460     if (!item || item->isHidden()) {
461         m_view.advanced_params->clear();
462         m_view.buttonStart->setEnabled(false);
463         return;
464     }
465     QString params = item->data(ParamsRole).toString();
466     QString extension = item->data(ExtensionRole).toString();
467     m_view.advanced_params->setPlainText(params);
468     m_view.advanced_params->setToolTip(params);
469     KUrl url = m_view.out_file->url();
470     if (!url.isEmpty()) {
471         QString path = url.path();
472         int pos = path.lastIndexOf('.') + 1;
473         if (pos == 0) path.append('.') + extension;
474         else path = path.left(pos) + extension;
475         m_view.out_file->setUrl(KUrl(path));
476     } else {
477         m_view.out_file->setUrl(KUrl(QDir::homePath() + "/untitled." + extension));
478     }
479
480     if (item->data(EditableRole).toString().isEmpty()) {
481         m_view.buttonDelete->setEnabled(false);
482         m_view.buttonEdit->setEnabled(false);
483     } else {
484         m_view.buttonDelete->setEnabled(true);
485         m_view.buttonEdit->setEnabled(true);
486     }
487     m_view.buttonStart->setEnabled(true);
488 }
489
490 void RenderWidget::parseProfiles(QString group, QString profile) {
491     m_view.size_list->clear();
492     m_view.format_list->clear();
493     QString exportFile = KStandardDirs::locate("appdata", "export/profiles.xml");
494     parseFile(exportFile, false);
495     exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
496     if (QFile::exists(exportFile)) parseFile(exportFile, true);
497     refreshView();
498     QList<QListWidgetItem *> child;
499     child = m_view.format_list->findItems(group, Qt::MatchExactly);
500     if (!child.isEmpty()) m_view.format_list->setCurrentItem(child.at(0));
501     child = m_view.size_list->findItems(profile, Qt::MatchExactly);
502     if (!child.isEmpty()) m_view.size_list->setCurrentItem(child.at(0));
503 }
504
505 void RenderWidget::parseFile(QString exportFile, bool editable) {
506     QDomDocument doc;
507     QFile file(exportFile);
508     doc.setContent(&file, false);
509     file.close();
510     QDomElement documentElement;
511     QDomElement profileElement;
512     QDomNodeList groups = doc.elementsByTagName("group");
513
514     if (groups.count() == 0) {
515         kDebug() << "// Export file: " << exportFile << " IS BROKEN";
516         return;
517     }
518
519     int i = 0;
520     QString groupName;
521     QString profileName;
522     QString extension;
523     QString prof_extension;
524     QString renderer;
525     QString params;
526     QString standard;
527     QListWidgetItem *item;
528     while (!groups.item(i).isNull()) {
529         documentElement = groups.item(i).toElement();
530         groupName = documentElement.attribute("name", QString::null);
531         extension = documentElement.attribute("extension", QString::null);
532         renderer = documentElement.attribute("renderer", QString::null);
533         if (m_view.format_list->findItems(groupName, Qt::MatchExactly).isEmpty())
534             new QListWidgetItem(groupName, m_view.format_list);
535
536         QDomNode n = groups.item(i).firstChild();
537         while (!n.isNull()) {
538             profileElement = n.toElement();
539             profileName = profileElement.attribute("name");
540             standard = profileElement.attribute("standard");
541             params = profileElement.attribute("args");
542             prof_extension = profileElement.attribute("extension");
543             if (!prof_extension.isEmpty()) extension = prof_extension;
544             item = new QListWidgetItem(profileName, m_view.size_list);
545             item->setData(GroupRole, groupName);
546             item->setData(ExtensionRole, extension);
547             item->setData(RenderRole, renderer);
548             item->setData(StandardRole, standard);
549             item->setData(ParamsRole, params);
550             if (editable) item->setData(EditableRole, "true");
551             n = n.nextSibling();
552         }
553
554         i++;
555     }
556 }
557
558
559
560 #include "renderwidget.moc"
561
562