1 /***************************************************************************
2 * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
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. *
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. *
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 ***************************************************************************/
21 #include "collapsiblegroup.h"
25 #include <QVBoxLayout>
26 #include <QInputDialog>
27 #include <QDragEnterEvent>
29 #include <QMutexLocker>
32 #include <KGlobalSettings>
34 #include <KMessageBox>
35 #include <KStandardDirs>
36 #include <KFileDialog>
37 #include <KUrlRequester>
38 #include <KColorScheme>
40 MyEditableLabel::MyEditableLabel(QWidget * parent):
45 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
48 void MyEditableLabel::mouseDoubleClickEvent ( QMouseEvent * e )
56 CollapsibleGroup::CollapsibleGroup(int ix, bool firstGroup, bool lastGroup, const EffectInfo &info, QWidget * parent) :
57 AbstractCollapsibleWidget(parent)
59 m_info.groupIndex = ix;
60 m_subWidgets = QList <CollapsibleEffect *> ();
61 setFont(KGlobalSettings::smallestReadableFont());
62 frame->setObjectName("framegroup");
63 decoframe->setObjectName("decoframegroup");
64 QHBoxLayout *l = static_cast <QHBoxLayout *>(frame->layout());
65 m_title = new MyEditableLabel(this);
66 l->insertWidget(2, m_title);
67 m_title->setText(info.groupName.isEmpty() ? i18n("Effect Group") : info.groupName);
68 m_info.groupName = m_title->text();
69 connect(m_title, SIGNAL(editingFinished()), this, SLOT(slotRenameGroup()));
70 buttonUp->setIcon(KIcon("kdenlive-up"));
71 buttonUp->setToolTip(i18n("Move effect up"));
72 buttonDown->setIcon(KIcon("kdenlive-down"));
73 buttonDown->setToolTip(i18n("Move effect down"));
75 buttonDel->setIcon(KIcon("kdenlive-deleffect"));
76 buttonDel->setToolTip(i18n("Delete effect"));
77 if (firstGroup) buttonUp->setVisible(false);
78 if (lastGroup) buttonDown->setVisible(false);
80 m_menu->addAction(KIcon("view-refresh"), i18n("Reset Group"), this, SLOT(slotResetGroup()));
81 m_menu->addAction(KIcon("document-save"), i18n("Save Group"), this, SLOT(slotSaveGroup()));
83 m_menu->addAction(KIcon("list-remove"), i18n("Ungroup"), this, SLOT(slotUnGroup()));
85 menuButton->setIcon(KIcon("kdenlive-menu"));
86 menuButton->setMenu(m_menu);
88 enabledButton->setChecked(false);
89 enabledButton->setIcon(KIcon("visible"));
91 if (info.groupIsCollapsed) {
95 connect(collapseButton, SIGNAL(clicked()), this, SLOT(slotSwitch()));
96 connect(enabledButton, SIGNAL(toggled(bool)), this, SLOT(slotEnable(bool)));
97 connect(buttonUp, SIGNAL(clicked()), this, SLOT(slotEffectUp()));
98 connect(buttonDown, SIGNAL(clicked()), this, SLOT(slotEffectDown()));
99 connect(buttonDel, SIGNAL(clicked()), this, SLOT(slotDeleteGroup()));
103 CollapsibleGroup::~CollapsibleGroup()
108 void CollapsibleGroup::slotUnGroup()
113 bool CollapsibleGroup::isActive() const
115 return decoframe->property("active").toBool();
118 void CollapsibleGroup::setActive(bool activate)
120 decoframe->setProperty("active", activate);
121 decoframe->setStyleSheet(decoframe->styleSheet());
124 void CollapsibleGroup::mouseDoubleClickEvent ( QMouseEvent * event )
126 if (frame->underMouse() && collapseButton->isEnabled()) slotSwitch();
127 QWidget::mouseDoubleClickEvent(event);
131 void CollapsibleGroup::slotEnable(bool disable, bool emitInfo)
133 m_title->setEnabled(!disable);
134 enabledButton->blockSignals(true);
135 enabledButton->setChecked(disable);
136 enabledButton->setIcon(disable ? KIcon("novisible") : KIcon("visible"));
137 enabledButton->blockSignals(false);
138 for (int i = 0; i < m_subWidgets.count(); ++i)
139 m_subWidgets.at(i)->slotDisable(disable, emitInfo);
142 void CollapsibleGroup::slotDeleteGroup()
145 // delete effects from the last one to the first, otherwise each deletion would trigger an update
146 // in other effects's kdenlive_ix index.
147 for (int i = m_subWidgets.count() - 1; i >= 0; --i)
148 doc.appendChild(doc.importNode(m_subWidgets.at(i)->effect(), true));
149 emit deleteGroup(doc);
152 void CollapsibleGroup::slotEffectUp()
155 for (int i = 0; i < m_subWidgets.count(); ++i)
156 indexes << m_subWidgets.at(i)->effectIndex();
157 emit changeEffectPosition(indexes, true);
160 void CollapsibleGroup::slotEffectDown()
163 for (int i = 0; i < m_subWidgets.count(); ++i)
164 indexes << m_subWidgets.at(i)->effectIndex();
165 emit changeEffectPosition(indexes, false);
168 void CollapsibleGroup::slotSaveGroup()
170 QString name = QInputDialog::getText(this, i18n("Save Group"), i18n("Name for saved group: "), QLineEdit::Normal, m_title->text());
171 if (name.isEmpty()) return;
172 QString path = KStandardDirs::locateLocal("appdata", "effects/", true);
173 path = path + name + ".xml";
174 if (QFile::exists(path)) if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", path)) == KMessageBox::No) return;
176 QDomDocument doc = effectsData();
177 QDomElement base = doc.documentElement();
178 QDomNodeList effects = base.elementsByTagName("effect");
179 for (int i = 0; i < effects.count(); ++i) {
180 QDomElement eff = effects.at(i).toElement();
181 eff.removeAttribute("kdenlive_ix");
183 info.fromString(eff.attribute("kdenlive_info"));
184 // Make sure all effects have the correct new group name
185 info.groupName = name;
186 // Saved effect group should have a group index of -1
187 info.groupIndex = -1;
188 eff.setAttribute("kdenlive_info", info.toString());
192 base.setAttribute("name", name);
193 base.setAttribute("id", name);
194 base.setAttribute("type", "custom");
197 if (file.open(QFile::WriteOnly | QFile::Truncate)) {
198 QTextStream out(&file);
199 out << doc.toString();
202 emit reloadEffects();
205 void CollapsibleGroup::slotResetGroup()
207 QMutexLocker lock(&m_mutex);
208 for (int i = 0; i < m_subWidgets.count(); ++i)
209 m_subWidgets.at(i)->slotResetEffect();
212 void CollapsibleGroup::slotSwitch()
214 bool enable = !widgetFrame->isVisible();
218 void CollapsibleGroup::slotShow(bool show)
220 widgetFrame->setVisible(show);
222 collapseButton->setArrowType(Qt::DownArrow);
223 m_info.groupIsCollapsed = false;
226 collapseButton->setArrowType(Qt::RightArrow);
227 m_info.groupIsCollapsed = true;
229 if (!m_subWidgets.isEmpty()) m_subWidgets.at(0)->groupStateChanged(m_info.groupIsCollapsed);
232 QWidget *CollapsibleGroup::title() const
237 void CollapsibleGroup::addGroupEffect(CollapsibleEffect *effect)
239 QMutexLocker lock(&m_mutex);
240 QVBoxLayout *vbox = static_cast<QVBoxLayout *>(widgetFrame->layout());
242 vbox = new QVBoxLayout();
243 vbox->setContentsMargins(0, 0, 0, 0);
245 widgetFrame->setLayout(vbox);
247 effect->setGroupIndex(groupIndex());
248 effect->setGroupName(m_title->text());
249 effect->decoframe->setObjectName("decoframesub");
250 m_subWidgets.append(effect);
251 vbox->addWidget(effect);
254 QString CollapsibleGroup::infoString() const
256 return m_info.toString();
259 void CollapsibleGroup::removeGroup(int ix, QVBoxLayout *layout)
261 QMutexLocker lock(&m_mutex);
262 QVBoxLayout *vbox = static_cast<QVBoxLayout *>(widgetFrame->layout());
263 if (vbox == NULL) return;
264 for (int i = m_subWidgets.count() - 1; i >= 0 ; --i) {
265 vbox->removeWidget(m_subWidgets.at(i));
266 layout->insertWidget(ix, m_subWidgets.at(i));
267 m_subWidgets.at(i)->decoframe->setObjectName("decoframe");
268 m_subWidgets.at(i)->removeFromGroup();
270 m_subWidgets.clear();
273 int CollapsibleGroup::groupIndex() const
275 return m_info.groupIndex;
278 bool CollapsibleGroup::isGroup() const
283 void CollapsibleGroup::updateTimecodeFormat()
285 QVBoxLayout *vbox = static_cast<QVBoxLayout *>(widgetFrame->layout());
286 if (vbox == NULL) return;
287 for (int j = vbox->count() - 1; j >= 0; j--) {
288 CollapsibleEffect *e = static_cast<CollapsibleEffect *>(vbox->itemAt(j)->widget());
289 if (e) e->updateTimecodeFormat();
293 void CollapsibleGroup::dragEnterEvent(QDragEnterEvent *event)
295 if (event->mimeData()->hasFormat("kdenlive/effectslist")) {
296 frame->setProperty("target", true);
297 frame->setStyleSheet(frame->styleSheet());
298 event->acceptProposedAction();
302 void CollapsibleGroup::dragLeaveEvent(QDragLeaveEvent */*event*/)
304 frame->setProperty("target", false);
305 frame->setStyleSheet(frame->styleSheet());
308 void CollapsibleGroup::dropEvent(QDropEvent *event)
310 frame->setProperty("target", false);
311 frame->setStyleSheet(frame->styleSheet());
312 const QString effects = QString::fromUtf8(event->mimeData()->data("kdenlive/effectslist"));
313 //event->acceptProposedAction();
315 doc.setContent(effects, true);
316 QDomElement e = doc.documentElement();
317 int ix = e.attribute("kdenlive_ix").toInt();
318 if (ix == 0 || e.tagName() == "effectgroup") {
319 if (e.tagName() == "effectgroup") {
320 // dropped a group on another group
321 QDomNodeList pastedEffects = e.elementsByTagName("effect");
322 if (pastedEffects.isEmpty() || m_subWidgets.isEmpty()) {
323 // Buggy groups, should not happen
327 QList <int> pastedEffectIndexes;
328 QList <int> currentEffectIndexes;
329 EffectInfo pasteInfo;
330 pasteInfo.fromString(pastedEffects.at(0).toElement().attribute("kdenlive_info"));
331 if (pasteInfo.groupIndex == -1) {
332 // Group dropped from effects list, add effect
333 e.setAttribute("kdenlive_ix", m_subWidgets.last()->effectIndex());
335 event->setDropAction(Qt::CopyAction);
340 for (int i = 0; i < pastedEffects.count(); ++i) {
341 pastedEffectIndexes << pastedEffects.at(i).toElement().attribute("kdenlive_ix").toInt();
343 for (int i = 0; i < m_subWidgets.count(); ++i) {
344 currentEffectIndexes << m_subWidgets.at(i)->effectIndex();
346 kDebug()<<"PASTING: "<<pastedEffectIndexes<<" TO "<<currentEffectIndexes;
347 if (pastedEffectIndexes.at(0) < currentEffectIndexes.at(0)) {
348 // Pasting group after current one:
349 emit moveEffect(pastedEffectIndexes, currentEffectIndexes.last(), pasteInfo.groupIndex, pasteInfo.groupName);
352 // Group moved before current one
353 emit moveEffect(pastedEffectIndexes, currentEffectIndexes.first(), pasteInfo.groupIndex, pasteInfo.groupName);
355 event->setDropAction(Qt::MoveAction);
359 // effect dropped from effects list, add it
360 e.setAttribute("kdenlive_info", m_info.toString());
361 if (!m_subWidgets.isEmpty()) {
362 e.setAttribute("kdenlive_ix", m_subWidgets.at(0)->effectIndex());
365 event->setDropAction(Qt::CopyAction);
369 if (m_subWidgets.isEmpty()) return;
370 int new_index = m_subWidgets.last()->effectIndex();
371 emit moveEffect(QList <int> () <<ix, new_index, m_info.groupIndex, m_title->text());
372 event->setDropAction(Qt::MoveAction);
376 void CollapsibleGroup::slotRenameGroup()
378 m_title->setReadOnly(true);
379 if (m_title->text().isEmpty()) m_title->setText(i18n("Effect Group"));
380 for (int j = 0; j < m_subWidgets.count(); j++) {
381 m_subWidgets.at(j)->setGroupName(m_title->text());
383 m_info.groupName = m_title->text();
384 emit groupRenamed(this);
387 QList <CollapsibleEffect*> CollapsibleGroup::effects()
389 QMutexLocker lock(&m_mutex);
393 QDomDocument CollapsibleGroup::effectsData()
395 QMutexLocker lock(&m_mutex);
397 QDomElement list = doc.createElement("effectgroup");
398 list.setAttribute("name", m_title->text());
399 doc.appendChild(list);
400 for (int j = 0; j < m_subWidgets.count(); j++) {
401 list.appendChild(doc.importNode(m_subWidgets.at(j)->effect(), true));
406 void CollapsibleGroup::adjustEffects()
408 for (int i = 0; i < m_subWidgets.count(); ++i) {
409 m_subWidgets.at(i)->adjustButtons(i, m_subWidgets.count());
414 #include "collapsiblegroup.moc"