]> git.sesse.net Git - kdenlive/blob - src/effectstack/collapsiblegroup.cpp
c4824e9fb1f1c3b217e7805723cc8aa52fd14216
[kdenlive] / src / effectstack / collapsiblegroup.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 "collapsiblegroup.h"
22
23
24 #include <QMenu>
25 #include <QVBoxLayout>
26 #include <QInputDialog>
27 #include <QDragEnterEvent>
28 #include <QDropEvent>
29 #include <QMutexLocker>
30
31 #include <KDebug>
32 #include <KGlobalSettings>
33 #include <KLocale>
34 #include <KMessageBox>
35 #include <KStandardDirs>
36 #include <KFileDialog>
37 #include <KUrlRequester>
38 #include <KColorScheme>
39
40 MyEditableLabel::MyEditableLabel(QWidget * parent):
41     QLineEdit(parent)
42 {
43     setFrame(false);
44     setReadOnly(true);
45     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
46 }
47
48 void MyEditableLabel::mouseDoubleClickEvent ( QMouseEvent * e )
49 {
50     setReadOnly(false);
51     selectAll();
52     e->accept();
53 }
54
55
56 CollapsibleGroup::CollapsibleGroup(int ix, bool firstGroup, bool lastGroup, const EffectInfo &info, QWidget * parent) :
57         AbstractCollapsibleWidget(parent)
58 {
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"));
74
75     buttonDel->setIcon(KIcon("kdenlive-deleffect"));
76     buttonDel->setToolTip(i18n("Delete effect"));
77     if (firstGroup) buttonUp->setVisible(false);
78     if (lastGroup) buttonDown->setVisible(false);
79     m_menu = new QMenu;
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()));
82     
83     m_menu->addAction(KIcon("list-remove"), i18n("Ungroup"), this, SLOT(slotUnGroup()));
84     setAcceptDrops(true);
85     menuButton->setIcon(KIcon("kdenlive-menu"));
86     menuButton->setMenu(m_menu);
87     
88     enabledButton->setChecked(false);
89     enabledButton->setIcon(KIcon("visible"));
90     
91     if (info.groupIsCollapsed) {
92         slotShow(false);
93     }
94
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()));
100
101 }
102
103 CollapsibleGroup::~CollapsibleGroup()
104 {
105     delete m_menu;
106 }
107
108 void CollapsibleGroup::slotUnGroup()
109 {
110     emit unGroup(this);
111 }
112
113 bool CollapsibleGroup::isActive() const
114 {
115     return decoframe->property("active").toBool();
116 }
117
118 void CollapsibleGroup::setActive(bool activate)
119 {
120     decoframe->setProperty("active", activate);
121     decoframe->setStyleSheet(decoframe->styleSheet());
122 }
123
124 void CollapsibleGroup::mouseDoubleClickEvent ( QMouseEvent * event )
125 {
126     if (frame->underMouse() && collapseButton->isEnabled()) slotSwitch();
127     QWidget::mouseDoubleClickEvent(event);
128 }
129
130
131 void CollapsibleGroup::slotEnable(bool disable, bool emitInfo)
132 {
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);
140 }
141
142 void CollapsibleGroup::slotDeleteGroup()
143 {
144     QDomDocument doc;
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);
150 }
151
152 void CollapsibleGroup::slotEffectUp()
153 {
154     QList <int> indexes;
155     for (int i = 0; i < m_subWidgets.count(); ++i)
156         indexes << m_subWidgets.at(i)->effectIndex();
157     emit changeEffectPosition(indexes, true);
158 }
159
160 void CollapsibleGroup::slotEffectDown()
161 {
162     QList <int> indexes;
163     for (int i = 0; i < m_subWidgets.count(); ++i)
164         indexes << m_subWidgets.at(i)->effectIndex();
165     emit changeEffectPosition(indexes, false);
166 }
167
168 void CollapsibleGroup::slotSaveGroup()
169 {
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;
175
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");
182         EffectInfo info;
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());
189
190     }
191     
192     base.setAttribute("name", name);
193     base.setAttribute("id", name);
194     base.setAttribute("type", "custom");  
195
196     QFile file(path);
197     if (file.open(QFile::WriteOnly | QFile::Truncate)) {
198         QTextStream out(&file);
199         out << doc.toString();
200     }
201     file.close();
202     emit reloadEffects();
203 }
204
205 void CollapsibleGroup::slotResetGroup()
206 {
207     QMutexLocker lock(&m_mutex);
208     for (int i = 0; i < m_subWidgets.count(); ++i)
209         m_subWidgets.at(i)->slotResetEffect();
210 }
211
212 void CollapsibleGroup::slotSwitch()
213 {
214     bool enable = !widgetFrame->isVisible();
215     slotShow(enable);
216 }
217
218 void CollapsibleGroup::slotShow(bool show)
219 {
220     widgetFrame->setVisible(show);
221     if (show) {
222         collapseButton->setArrowType(Qt::DownArrow);
223         m_info.groupIsCollapsed = false;
224     }
225     else {
226         collapseButton->setArrowType(Qt::RightArrow);
227         m_info.groupIsCollapsed = true;
228     }
229     if (!m_subWidgets.isEmpty()) m_subWidgets.at(0)->groupStateChanged(m_info.groupIsCollapsed);
230 }
231
232 QWidget *CollapsibleGroup::title() const
233 {
234     return m_title;
235 }
236
237 void CollapsibleGroup::addGroupEffect(CollapsibleEffect *effect)
238 {
239     QMutexLocker lock(&m_mutex);
240     QVBoxLayout *vbox = static_cast<QVBoxLayout *>(widgetFrame->layout());
241     if (vbox == NULL) {
242         vbox = new QVBoxLayout();
243         vbox->setContentsMargins(0, 0, 0, 0);
244         vbox->setSpacing(2);
245         widgetFrame->setLayout(vbox);
246     }
247     effect->setGroupIndex(groupIndex());
248     effect->setGroupName(m_title->text());
249     effect->decoframe->setObjectName("decoframesub");
250     m_subWidgets.append(effect);
251     vbox->addWidget(effect);
252 }
253
254 QString CollapsibleGroup::infoString() const
255 {
256     return m_info.toString();
257 }
258
259 void CollapsibleGroup::removeGroup(int ix, QVBoxLayout *layout)
260 {
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();
269     }
270     m_subWidgets.clear();
271 }
272
273 int CollapsibleGroup::groupIndex() const
274 {
275     return m_info.groupIndex;
276 }
277
278 bool CollapsibleGroup::isGroup() const
279 {
280     return true;
281 }
282
283 void CollapsibleGroup::updateTimecodeFormat()
284 {
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();
290     }
291 }
292
293 void CollapsibleGroup::dragEnterEvent(QDragEnterEvent *event)
294 {
295     if (event->mimeData()->hasFormat("kdenlive/effectslist")) {
296         frame->setProperty("target", true);
297         frame->setStyleSheet(frame->styleSheet());
298         event->acceptProposedAction();
299     }
300 }
301
302 void CollapsibleGroup::dragLeaveEvent(QDragLeaveEvent */*event*/)
303 {
304     frame->setProperty("target", false);
305     frame->setStyleSheet(frame->styleSheet());
306 }
307
308 void CollapsibleGroup::dropEvent(QDropEvent *event)
309 {
310     frame->setProperty("target", false);
311     frame->setStyleSheet(frame->styleSheet());
312     const QString effects = QString::fromUtf8(event->mimeData()->data("kdenlive/effectslist"));
313     //event->acceptProposedAction();
314     QDomDocument doc;
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
324                 event->ignore();
325                 return;
326             }
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());
334                 emit addEffect(e);
335                 event->setDropAction(Qt::CopyAction);
336                 event->accept();
337                 return;
338             }
339             // Moving group
340             for (int i = 0; i < pastedEffects.count(); ++i) {
341                 pastedEffectIndexes << pastedEffects.at(i).toElement().attribute("kdenlive_ix").toInt();
342             }
343             for (int i = 0; i < m_subWidgets.count(); ++i) {
344                 currentEffectIndexes << m_subWidgets.at(i)->effectIndex();
345             }
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);
350             }
351             else {
352                 // Group moved before current one
353                 emit moveEffect(pastedEffectIndexes, currentEffectIndexes.first(), pasteInfo.groupIndex, pasteInfo.groupName);
354             }
355             event->setDropAction(Qt::MoveAction);
356             event->accept();
357             return;
358         }
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());
363         }
364         emit addEffect(e);
365         event->setDropAction(Qt::CopyAction);
366         event->accept();
367         return;
368     }
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);
373     event->accept();
374 }
375
376 void CollapsibleGroup::slotRenameGroup()
377 {
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());
382     }
383     m_info.groupName = m_title->text();
384     emit groupRenamed(this);
385 }
386
387 QList <CollapsibleEffect*> CollapsibleGroup::effects()
388 {
389     QMutexLocker lock(&m_mutex);
390     return m_subWidgets;
391 }
392
393 QDomDocument CollapsibleGroup::effectsData()
394 {
395     QMutexLocker lock(&m_mutex);
396     QDomDocument doc;
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));
402     }
403     return doc;
404 }
405
406 void CollapsibleGroup::adjustEffects()
407 {
408     for (int i = 0; i < m_subWidgets.count(); ++i) {
409         m_subWidgets.at(i)->adjustButtons(i, m_subWidgets.count());
410     }
411 }
412
413
414 #include "collapsiblegroup.moc"