]> git.sesse.net Git - kdenlive/blob - src/effectstack/collapsiblegroup.cpp
Remember effect group state (collapsed)
[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::Maximum, 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, EffectInfo info, QWidget * parent) :
57         AbstractCollapsibleWidget(parent),
58         m_index(ix)
59 {
60     setupUi(this);
61     
62     m_subWidgets = QList <CollapsibleEffect *> ();
63     setFont(KGlobalSettings::smallestReadableFont());
64     QHBoxLayout *l = static_cast <QHBoxLayout *>(framegroup->layout());
65     m_title = new MyEditableLabel(this);
66     l->insertWidget(3, m_title);
67     m_title->setText(info.groupName.isEmpty() ? i18n("Effect Group") : info.groupName);
68     connect(m_title, SIGNAL(editingFinished()), this, SLOT(slotRenameGroup()));
69     buttonUp->setIcon(KIcon("kdenlive-up"));
70     buttonUp->setToolTip(i18n("Move effect up"));
71     buttonDown->setIcon(KIcon("kdenlive-down"));
72     buttonDown->setToolTip(i18n("Move effect down"));
73
74     buttonDel->setIcon(KIcon("kdenlive-deleffect"));
75     buttonDel->setToolTip(i18n("Delete effect"));
76     if (firstGroup) buttonUp->setVisible(false);
77     if (lastGroup) buttonDown->setVisible(false);
78     m_menu = new QMenu;
79     m_menu->addAction(KIcon("view-refresh"), i18n("Reset Group"), this, SLOT(slotResetGroup()));
80     m_menu->addAction(KIcon("document-save"), i18n("Save Group"), this, SLOT(slotSaveGroup()));
81     
82     m_menu->addAction(KIcon("list-remove"), i18n("Ungroup"), this, SLOT(slotUnGroup()));
83     setAcceptDrops(true);
84     menuButton->setIcon(KIcon("kdenlive-menu"));
85     menuButton->setMenu(m_menu);
86     
87     enabledButton->setChecked(false);
88     enabledButton->setIcon(KIcon("visible"));
89     
90     if (info.groupIsCollapsed) {
91         slotShow(false);
92     }
93
94     connect(collapseButton, SIGNAL(clicked()), this, SLOT(slotSwitch()));
95     connect(enabledButton, SIGNAL(toggled(bool)), this, SLOT(slotEnable(bool)));
96     connect(buttonUp, SIGNAL(clicked()), this, SLOT(slotEffectUp()));
97     connect(buttonDown, SIGNAL(clicked()), this, SLOT(slotEffectDown()));
98     connect(buttonDel, SIGNAL(clicked()), this, SLOT(slotDeleteGroup()));
99
100 }
101
102 CollapsibleGroup::~CollapsibleGroup()
103 {
104     delete m_menu;
105 }
106
107 void CollapsibleGroup::slotUnGroup()
108 {
109     emit unGroup(this);
110 }
111
112 bool CollapsibleGroup::isActive() const
113 {
114     return decoframegroup->property("active").toBool();
115 }
116
117 void CollapsibleGroup::setActive(bool activate)
118 {
119     decoframegroup->setProperty("active", activate);
120     decoframegroup->setStyleSheet(decoframegroup->styleSheet());
121 }
122
123 void CollapsibleGroup::mouseDoubleClickEvent ( QMouseEvent * event )
124 {
125     if (framegroup->underMouse() && collapseButton->isEnabled()) slotSwitch();
126     QWidget::mouseDoubleClickEvent(event);
127 }
128
129
130 void CollapsibleGroup::slotEnable(bool disable)
131 {
132     m_title->setEnabled(!disable);
133     enabledButton->blockSignals(true);
134     enabledButton->setChecked(disable);
135     enabledButton->setIcon(disable ? KIcon("novisible") : KIcon("visible"));
136     enabledButton->blockSignals(false);
137     for (int i = 0; i < m_subWidgets.count(); i++)
138         m_subWidgets.at(i)->slotEnable(disable);
139 }
140
141 void CollapsibleGroup::slotDeleteGroup()
142 {
143     QDomDocument doc;
144     // delete effects from the last one to the first, otherwise each deletion would trigger an update
145     // in other effects's kdenlive_ix index.
146     for (int i = m_subWidgets.count() - 1; i >= 0; i--)
147         doc.appendChild(doc.importNode(m_subWidgets.at(i)->effect(), true));
148     emit deleteGroup(doc);
149 }
150
151 void CollapsibleGroup::slotEffectUp()
152 {
153     emit changeGroupPosition(groupIndex(), true);
154 }
155
156 void CollapsibleGroup::slotEffectDown()
157 {
158     emit changeGroupPosition(groupIndex(), false);
159 }
160
161 void CollapsibleGroup::slotSaveGroup()
162 {
163     QString name = QInputDialog::getText(this, i18n("Save Group"), i18n("Name for saved group: "), QLineEdit::Normal, m_title->text());
164     if (name.isEmpty()) return;
165     QString path = KStandardDirs::locateLocal("appdata", "effects/", true);
166     path = path + name + ".xml";
167     if (QFile::exists(path)) if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", path)) == KMessageBox::No) return;
168
169     QDomDocument doc = effectsData();
170     QDomElement base = doc.documentElement();
171     QDomNodeList effects = base.elementsByTagName("effect");
172     for (int i = 0; i < effects.count(); i++) {
173         QDomElement eff = effects.at(i).toElement();
174         eff.removeAttribute("kdenlive_ix");
175         EffectInfo info;
176         info.fromString(eff.attribute("kdenlive_info"));
177         // Make sure all effects have the correct new group name
178         info.groupName = name;
179         // Saved effect group should have a group index of -1
180         info.groupIndex = -1;
181         eff.setAttribute("kdenlive_info", info.toString());
182
183     }
184     
185     base.setAttribute("name", name);
186     base.setAttribute("id", name);
187     base.setAttribute("type", "custom");  
188
189     QFile file(path);
190     if (file.open(QFile::WriteOnly | QFile::Truncate)) {
191         QTextStream out(&file);
192         out << doc.toString();
193     }
194     file.close();
195     emit reloadEffects();
196 }
197
198 void CollapsibleGroup::slotResetGroup()
199 {
200     QMutexLocker lock(&m_mutex);
201     for (int i = 0; i < m_subWidgets.count(); i++)
202         m_subWidgets.at(i)->slotResetEffect();
203 }
204
205 void CollapsibleGroup::slotSwitch()
206 {
207     bool enable = !widgetFrame->isVisible();
208     slotShow(enable);
209 }
210
211 void CollapsibleGroup::slotShow(bool show)
212 {
213     widgetFrame->setVisible(show);
214     if (show) {
215         collapseButton->setArrowType(Qt::DownArrow);
216         m_info.groupIsCollapsed = false;
217     }
218     else {
219         collapseButton->setArrowType(Qt::RightArrow);
220         m_info.groupIsCollapsed = true;
221     }
222     if (!m_subWidgets.isEmpty()) m_subWidgets.at(0)->groupStateChanged(m_info.groupIsCollapsed);
223 }
224
225 QWidget *CollapsibleGroup::title() const
226 {
227     return m_title;
228 }
229
230 void CollapsibleGroup::addGroupEffect(CollapsibleEffect *effect)
231 {
232     QMutexLocker lock(&m_mutex);
233     QVBoxLayout *vbox = static_cast<QVBoxLayout *>(widgetFrame->layout());
234     if (vbox == NULL) {
235         vbox = new QVBoxLayout();
236         vbox->setContentsMargins(0, 0, 0, 0);
237         vbox->setSpacing(2);
238         widgetFrame->setLayout(vbox);
239     }
240     effect->setGroupIndex(groupIndex());
241     effect->setGroupName(m_title->text());
242     effect->decoframe->setObjectName("decoframesub");
243     m_subWidgets.append(effect);
244     vbox->addWidget(effect);
245 }
246
247 QString CollapsibleGroup::infoString() const
248 {
249     return m_info.toString();
250 }
251
252 void CollapsibleGroup::removeGroup(int ix, QVBoxLayout *layout)
253 {
254     QMutexLocker lock(&m_mutex);
255     QVBoxLayout *vbox = static_cast<QVBoxLayout *>(widgetFrame->layout());
256     if (vbox == NULL) return;
257     for (int i = m_subWidgets.count() - 1; i >= 0 ; i--) {
258         vbox->removeWidget(m_subWidgets.at(i));
259         layout->insertWidget(ix, m_subWidgets.at(i));
260         m_subWidgets.at(i)->decoframe->setObjectName("decoframe");
261         m_subWidgets.at(i)->removeFromGroup();
262     }
263     m_subWidgets.clear();
264 }
265
266 int CollapsibleGroup::groupIndex() const
267 {
268     return m_index;
269 }
270
271 bool CollapsibleGroup::isGroup() const
272 {
273     return true;
274 }
275
276 void CollapsibleGroup::updateTimecodeFormat()
277 {
278     QVBoxLayout *vbox = static_cast<QVBoxLayout *>(widgetFrame->layout());
279     if (vbox == NULL) return;
280     for (int j = vbox->count() - 1; j >= 0; j--) {
281         CollapsibleEffect *e = static_cast<CollapsibleEffect *>(vbox->itemAt(j)->widget());
282         if (e) e->updateTimecodeFormat();
283     }
284 }
285
286 void CollapsibleGroup::dragEnterEvent(QDragEnterEvent *event)
287 {
288     if (event->mimeData()->hasFormat("kdenlive/effectslist")) {
289         framegroup->setProperty("target", true);
290         framegroup->setStyleSheet(framegroup->styleSheet());
291         event->acceptProposedAction();
292     }
293 }
294
295 void CollapsibleGroup::dragLeaveEvent(QDragLeaveEvent */*event*/)
296 {
297     framegroup->setProperty("target", false);
298     framegroup->setStyleSheet(framegroup->styleSheet());
299 }
300
301 void CollapsibleGroup::dropEvent(QDropEvent *event)
302 {
303     framegroup->setProperty("target", false);
304     framegroup->setStyleSheet(framegroup->styleSheet());
305     const QString effects = QString::fromUtf8(event->mimeData()->data("kdenlive/effectslist"));
306     //event->acceptProposedAction();
307     QDomDocument doc;
308     doc.setContent(effects, true);
309     QDomElement e = doc.documentElement();
310     int ix = e.attribute("kdenlive_ix").toInt();
311     if (ix == 0) {
312         // effect dropped from effects list, add it
313         e.setAttribute("kdenlive_ix", ix);
314         event->setDropAction(Qt::CopyAction);
315         event->accept();
316         emit addEffect(e);
317         return;
318     }
319     if (m_subWidgets.isEmpty()) return;
320     int new_index = m_subWidgets.last()->effectIndex();
321     emit moveEffect(ix, new_index, m_index, m_title->text());
322     event->setDropAction(Qt::MoveAction);
323     event->accept();
324 }
325
326 void CollapsibleGroup::slotRenameGroup()
327 {
328     m_title->setReadOnly(true);
329     if (m_title->text().isEmpty()) m_title->setText(i18n("Effect Group"));
330     for (int j = 0; j < m_subWidgets.count(); j++) {
331         m_subWidgets.at(j)->setGroupName(m_title->text());
332     }
333     emit groupRenamed(this);
334 }
335
336 QList <CollapsibleEffect*> CollapsibleGroup::effects()
337 {
338     QMutexLocker lock(&m_mutex);
339     return m_subWidgets;
340 }
341
342 QDomDocument CollapsibleGroup::effectsData()
343 {
344     QMutexLocker lock(&m_mutex);
345     QDomDocument doc;
346     QDomElement list = doc.createElement("effectgroup");
347     list.setAttribute("name", m_title->text());
348     doc.appendChild(list);
349     for (int j = 0; j < m_subWidgets.count(); j++) {
350         list.appendChild(doc.importNode(m_subWidgets.at(j)->effect(), true));
351     }
352     return doc;
353 }
354