Cleanup effect stack (part 1)

parent d1dc39f4
......@@ -6,5 +6,8 @@
<parameter type="url" name="resource" default="">
<name>Url</name>
</parameter>
<parameter type="geometry" name="composite.geometry" default="0%,0%:100%x100%" fixed="1" opacity="false">
<name>Pan and Zoom</name>
</parameter>
<parameter type="fixed" name="filter_only" min="1" max="1" default="1" />
</effect>
......@@ -74,7 +74,7 @@ CornersWidget::~CornersWidget()
MonitorEditWidget *edit = m_monitor->getEffectEdit();
edit->showVisibilityButton(false);
edit->removeCustomControls();
m_monitor->slotEffectScene(false);
m_monitor->slotShowEffectScene(false);
}
}
......@@ -178,7 +178,7 @@ void CornersWidget::slotShowScene(bool show)
{
m_showScene = show;
if (!m_showScene)
m_monitor->slotEffectScene(false);
m_monitor->slotShowEffectScene(false);
else
slotCheckMonitorPosition(m_monitor->render->seekFramePosition());
}
......
set(kdenlive_SRCS
${kdenlive_SRCS}
effectstack/parametercontainer.cpp
effectstack/abstractcollapsiblewidget.cpp
effectstack/collapsibleeffect.cpp
effectstack/collapsiblegroup.cpp
......
This diff is collapsed.
......@@ -21,9 +21,10 @@
#ifndef COLLAPSIBLEEFFECT_H
#define COLLAPSIBLEEFFECT_H
#include "parametercontainer.h"
#include "abstractcollapsiblewidget.h"
#include "timecode.h"
#include "keyframeedit.h"
#include <QDomElement>
#include <QToolButton>
......@@ -31,84 +32,6 @@
class QFrame;
class QLabel;
class Monitor;
class GeometryWidget;
struct EffectMetaInfo {
MltVideoProfile profile;
Timecode timecode;
Monitor *monitor;
QPoint frameSize;
bool trackMode;
};
enum WIPE_DIRECTON { UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3, CENTER = 4 };
struct wipeInfo {
WIPE_DIRECTON start;
WIPE_DIRECTON end;
int startTransparency;
int endTransparency;
};
class MySpinBox : public QSpinBox
{
Q_OBJECT
public:
MySpinBox(QWidget * parent = 0);
protected:
virtual void focusInEvent(QFocusEvent*);
virtual void focusOutEvent(QFocusEvent*);
};
class ParameterContainer : public QObject
{
Q_OBJECT
public:
ParameterContainer(QDomElement effect, ItemInfo info, EffectMetaInfo *metaInfo, QWidget * parent = 0);
~ParameterContainer();
void updateTimecodeFormat();
void updateProjectFormat(MltVideoProfile profile, Timecode t);
void updateParameter(const QString &key, const QString &value);
private slots:
void slotCollectAllParameters();
void slotStartFilterJobAction();
private:
/** @brief Updates parameter @param name according to new value of dependency.
* @param name Name of the parameter which will be updated
* @param type Type of the parameter which will be updated
* @param value Value of the dependency parameter */
void meetDependency(const QString& name, QString type, QString value);
wipeInfo getWipeInfo(QString value);
QString getWipeString(wipeInfo info);
int m_in;
int m_out;
QList<QWidget*> m_uiItems;
QMap<QString, QWidget*> m_valueItems;
Timecode m_timecode;
KeyframeEdit *m_keyframeEditor;
GeometryWidget *m_geometryWidget;
EffectMetaInfo *m_metaInfo;
QDomElement m_effect;
QVBoxLayout *m_vbox;
signals:
void parameterChanged(const QDomElement, const QDomElement, int);
void syncEffectsPos(int);
void effectStateChanged(bool);
void checkMonitorPosition(int);
void seekTimeline(int);
void showComments(bool);
/** @brief Start an MLT filter job on this clip. */
void startFilterJob(QString filterName, QString filterParams, QString finalFilterName, QString consumer, QString consumerParams, QString properties);
};
/**)
* @class CollapsibleEffect
......@@ -123,7 +46,6 @@ class CollapsibleEffect : public AbstractCollapsibleWidget
public:
CollapsibleEffect(QDomElement effect, QDomElement original_effect, ItemInfo info, EffectMetaInfo *metaInfo, bool lastEffect, QWidget * parent = 0);
~CollapsibleEffect();
static QMap<QString, QImage> iconCache;
QLabel *title;
void setupWidget(ItemInfo info, EffectMetaInfo *metaInfo);
......@@ -145,12 +67,12 @@ public:
bool isActive() const;
/** @brief Should the wheel event be sent to parent widget for scrolling. */
bool filterWheelEvent;
/** @brief Return the stylesheet required for effect parameters. */
static const QString getStyleSheet();
/** @brief Parent group was collapsed, update. */
void groupStateChanged(bool collapsed);
/** @brief Show / hide up / down buttons. */
void adjustButtons(int ix, int max);
/** @brief Returns true of this effect requires an on monitor adjustable effect scene. */
bool needsMonitorEffectScene();
public slots:
void slotSyncEffectsPos(int pos);
......
......@@ -35,6 +35,7 @@
#include <KStandardDirs>
#include <KFileDialog>
#include <KColorScheme>
#include <KColorUtils>
#include <QMenu>
#include <QTextStream>
......@@ -69,7 +70,7 @@ EffectStackView2::EffectStackView2(Monitor *monitor, QWidget *parent) :
setEnabled(false);
setStyleSheet(CollapsibleEffect::getStyleSheet());
setStyleSheet(getStyleSheet());
}
EffectStackView2::~EffectStackView2()
......@@ -78,7 +79,7 @@ EffectStackView2::~EffectStackView2()
void EffectStackView2::updatePalette()
{
setStyleSheet(CollapsibleEffect::getStyleSheet());
setStyleSheet(getStyleSheet());
}
void EffectStackView2::slotRenderPos(int pos)
......@@ -406,10 +407,11 @@ void EffectStackView2::slotSeekTimeline(int pos)
void EffectStackView2::slotCheckMonitorPosition(int renderPos)
{
if (m_effectMetaInfo.trackMode || (m_clipref && renderPos >= m_clipref->startPos().frames(KdenliveSettings::project_fps()) && renderPos <= m_clipref->endPos().frames(KdenliveSettings::project_fps()))) {
if (!m_effectMetaInfo.monitor->getEffectEdit()->getScene()->views().at(0)->isVisible())
m_effectMetaInfo.monitor->slotEffectScene(true);
if (!m_effectMetaInfo.monitor->effectSceneDisplayed())
//if (!m_effectMetaInfo.monitor->getEffectEdit()->getScene()->views().at(0)->isVisible())
m_effectMetaInfo.monitor->slotShowEffectScene(true);
} else {
m_effectMetaInfo.monitor->slotEffectScene(false);
m_effectMetaInfo.monitor->slotShowEffectScene(false);
}
}
......@@ -521,6 +523,7 @@ void EffectStackView2::slotSetCurrentEffect(int ix)
for (int i = 0; i < m_effects.count(); i++) {
if (m_effects.at(i)->effectIndex() == ix) {
m_effects.at(i)->setActive(true);
m_effectMetaInfo.monitor->slotShowEffectScene(m_effects.at(i)->needsMonitorEffectScene());
m_ui.labelComment->setText(i18n(m_effects.at(i)->effect().firstChildElement("description").firstChildElement("full").text().toUtf8().data()));
m_ui.labelComment->setHidden(!m_ui.buttonShowComments->isChecked() || m_ui.labelComment->text().isEmpty());
}
......@@ -866,4 +869,46 @@ void EffectStackView2::dropEvent(QDropEvent *event)
processDroppedEffect(doc.documentElement(), event);
}
//static
const QString EffectStackView2::getStyleSheet()
{
KColorScheme scheme(QApplication::palette().currentColorGroup(), KColorScheme::View, KSharedConfig::openConfig(KdenliveSettings::colortheme()));
QColor selected_bg = scheme.decoration(KColorScheme::FocusColor).color();
QColor hgh = KColorUtils::mix(QApplication::palette().window().color(), selected_bg, 0.2);
QColor hover_bg = scheme.decoration(KColorScheme::HoverColor).color();
QColor light_bg = scheme.shade(KColorScheme::LightShade);
QColor alt_bg = scheme.background(KColorScheme::NormalBackground).color();
QString stylesheet;
// effect background
stylesheet.append(QString("QFrame#decoframe {border-top-left-radius:5px;border-top-right-radius:5px;border-bottom:2px solid palette(mid);border-top:1px solid palette(light);} QFrame#decoframe[active=\"true\"] {background: %1;}").arg(hgh.name()));
// effect in group background
stylesheet.append(QString("QFrame#decoframesub {border-top:1px solid palette(light);} QFrame#decoframesub[active=\"true\"] {background: %1;}").arg(hgh.name()));
// group background
stylesheet.append(QString("QFrame#decoframegroup {border-top-left-radius:5px;border-top-right-radius:5px;border:2px solid palette(dark);margin:0px;margin-top:2px;} "));
// effect title bar
stylesheet.append(QString("QFrame#frame {margin-bottom:2px;border-top-left-radius:5px;border-top-right-radius:5px;} QFrame#frame[target=\"true\"] {background: palette(highlight);}"));
// group effect title bar
stylesheet.append(QString("QFrame#framegroup {border-top-left-radius:2px;border-top-right-radius:2px;background: palette(dark);} QFrame#framegroup[target=\"true\"] {background: palette(highlight);} "));
// draggable effect bar content
stylesheet.append(QString("QProgressBar::chunk:horizontal {background: palette(button);border-top-left-radius: 4px;border-bottom-left-radius: 4px;} QProgressBar::chunk:horizontal#dragOnly {background: %1;border-top-left-radius: 4px;border-bottom-left-radius: 4px;} QProgressBar::chunk:horizontal:hover {background: %2;}").arg(alt_bg.name()).arg(selected_bg.name()));
// draggable effect bar
stylesheet.append(QString("QProgressBar:horizontal {border: 1px solid palette(dark);border-top-left-radius: 4px;border-bottom-left-radius: 4px;border-right:0px;background:%3;padding: 0px;text-align:left center} QProgressBar:horizontal:disabled {border: 1px solid palette(button)} QProgressBar:horizontal#dragOnly {background: %3} QProgressBar:horizontal[inTimeline=\"true\"] { border: 1px solid %1;border-right: 0px;background: %2;padding: 0px;text-align:left center } QProgressBar::chunk:horizontal[inTimeline=\"true\"] {background: %1;}").arg(hover_bg.name()).arg(light_bg.name()).arg(alt_bg.name()));
// spin box for draggable widget
stylesheet.append(QString("QAbstractSpinBox#dragBox {border: 1px solid palette(dark);border-top-right-radius: 4px;border-bottom-right-radius: 4px;padding-right:0px;} QAbstractSpinBox::down-button#dragBox {width:0px;padding:0px;} QAbstractSpinBox:disabled#dragBox {border: 1px solid palette(button);} QAbstractSpinBox::up-button#dragBox {width:0px;padding:0px;} QAbstractSpinBox[inTimeline=\"true\"]#dragBox { border: 1px solid %1;} QAbstractSpinBox:hover#dragBox {border: 1px solid %2;} ").arg(hover_bg.name()).arg(selected_bg.name()));
// group editable labels
stylesheet.append(QString("MyEditableLabel { background-color: transparent; color: palette(bright-text); border-radius: 2px;border: 1px solid transparent;} MyEditableLabel:hover {border: 1px solid palette(highlight);} "));
return stylesheet;
}
#include "effectstackview2.moc"
......@@ -25,7 +25,6 @@
#define EFFECTSTACKVIEW2_H
#include "ui_effectstack2_ui.h"
#include "effectstackedit.h"
#include "collapsibleeffect.h"
#include "collapsiblegroup.h"
......@@ -72,6 +71,9 @@ public:
/** @brief Process dropped xml effect. */
void processDroppedEffect(QDomElement e, QDropEvent *event);
/** @brief Return the stylesheet required for effect parameters. */
static const QString getStyleSheet();
protected:
virtual void mouseMoveEvent(QMouseEvent * event);
......
This diff is collapsed.
/***************************************************************************
* Copyright (C) 2012 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#ifndef PARAMETERCONTAINER_H
#define PARAMETERCONTAINER_H
#include "keyframeedit.h"
class GeometryWidget;
class Monitor;
struct EffectMetaInfo {
MltVideoProfile profile;
Timecode timecode;
Monitor *monitor;
QPoint frameSize;
bool trackMode;
};
enum WIPE_DIRECTON { UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3, CENTER = 4 };
struct wipeInfo {
WIPE_DIRECTON start;
WIPE_DIRECTON end;
int startTransparency;
int endTransparency;
};
class MySpinBox : public QSpinBox
{
Q_OBJECT
public:
MySpinBox(QWidget * parent = 0);
protected:
virtual void focusInEvent(QFocusEvent*);
virtual void focusOutEvent(QFocusEvent*);
};
class ParameterContainer : public QObject
{
Q_OBJECT
public:
ParameterContainer(QDomElement effect, ItemInfo info, EffectMetaInfo *metaInfo, QWidget * parent = 0);
~ParameterContainer();
void updateTimecodeFormat();
void updateProjectFormat(MltVideoProfile profile, Timecode t);
void updateParameter(const QString &key, const QString &value);
/** @brief Returns true of this effect requires an on monitor adjustable effect scene. */
bool needsMonitorEffectScene();
private slots:
void slotCollectAllParameters();
void slotStartFilterJobAction();
private:
/** @brief Updates parameter @param name according to new value of dependency.
* @param name Name of the parameter which will be updated
* @param type Type of the parameter which will be updated
* @param value Value of the dependency parameter */
void meetDependency(const QString& name, QString type, QString value);
wipeInfo getWipeInfo(QString value);
QString getWipeString(wipeInfo info);
/** @brief Delete all child widgets */
void clearLayout(QLayout *layout);
int m_in;
int m_out;
QList<QWidget*> m_uiItems;
QMap<QString, QWidget*> m_valueItems;
Timecode m_timecode;
KeyframeEdit *m_keyframeEditor;
GeometryWidget *m_geometryWidget;
EffectMetaInfo *m_metaInfo;
QDomElement m_effect;
QVBoxLayout *m_vbox;
signals:
void parameterChanged(const QDomElement, const QDomElement, int);
void syncEffectsPos(int);
void effectStateChanged(bool);
void checkMonitorPosition(int);
void seekTimeline(int);
void showComments(bool);
/** @brief Start an MLT filter job on this clip. */
void startFilterJob(QString filterName, QString filterParams, QString finalFilterName, QString consumer, QString consumerParams, QString properties);
};
#endif
This diff is collapsed.
......@@ -21,7 +21,7 @@
#include "definitions.h"
#include "timecode.h"
#include "keyframeedit.h"
#include "effectstack/collapsibleeffect.h"
#include "effectstack/parametercontainer.h"
#include <QWidget>
#include <QDomElement>
......@@ -56,22 +56,9 @@ public:
virtual bool eventFilter( QObject * o, QEvent * e );
private:
/** @brief Deletes all parameter widgets. */
void clearAllItems();
/** @brief Updates parameter @param name according to new value of dependency.
* @param name Name of the parameter which will be updated
* @param type Type of the parameter which will be updated
* @param value Value of the dependency parameter */
void meetDependency(const QString& name, QString type, QString value);
QVBoxLayout *m_vbox;
QList<QWidget*> m_uiItems;
QMap<QString, QWidget*> m_valueItems;
int m_in;
int m_out;
KeyframeEdit *m_keyframeEditor;
Monitor *m_monitor;
GeometryWidget *m_geometryWidget;
EffectMetaInfo m_metaInfo;
QWidget *m_baseWidget;
ParameterContainer *m_paramWidget;
......@@ -80,11 +67,6 @@ public slots:
/** @brief Called when an effect is selected, builds the UI for this effect. */
void transferParamDesc(const QDomElement &d, ItemInfo info, bool isEffect = true);
/** @brief Called whenever(?) some parameter is changed in the gui.
*
* Transfers all Dynamic gui parameter settings into m_params(??) */
void collectAllParameters();
/** @brief Pass position changes of the timeline cursor to the effects to keep their local timelines in sync. */
void slotSyncEffectsPos(int pos);
......
......@@ -512,9 +512,9 @@ void EffectStackView::slotCheckMonitorPosition(int renderPos)
{
if (m_trackMode || (m_clipref && renderPos >= m_clipref->startPos().frames(KdenliveSettings::project_fps()) && renderPos <= m_clipref->endPos().frames(KdenliveSettings::project_fps()))) {
if (!m_monitor->getEffectEdit()->getScene()->views().at(0)->isVisible())
m_monitor->slotEffectScene(true);
m_monitor->slotShowEffectScene(true);
} else {
m_monitor->slotEffectScene(false);
m_monitor->slotShowEffectScene(false);
}
}
......
......@@ -100,16 +100,16 @@ GeometryWidget::GeometryWidget(Monitor* monitor, Timecode timecode, int clipPos,
connect(m_ui.buttonSync, SIGNAL(toggled(bool)), this, SLOT(slotSetSynchronize(bool)));
m_spinX = new DragValue(i18nc("x axis position", "X"), 0, 0, -99000, 99000, -1, QString(), false, this);
m_ui.horizontalLayout->addWidget(m_spinX);
m_ui.horizontalLayout->addWidget(m_spinX, 0, 0);
m_spinY = new DragValue(i18nc("y axis position", "Y"), 0, 0, -99000, 99000, -1, QString(), false, this);
m_ui.horizontalLayout->addWidget(m_spinY);
m_ui.horizontalLayout->addWidget(m_spinY, 0, 1);
m_spinWidth = new DragValue(i18nc("Frame width", "W"), m_monitor->render->frameRenderWidth(), 0, 1, 99000, -1, QString(), false, this);
m_ui.horizontalLayout->addWidget(m_spinWidth);
m_ui.horizontalLayout->addWidget(m_spinWidth, 0, 2);
m_spinHeight = new DragValue(i18nc("Frame height", "H"), m_monitor->render->renderHeight(), 0, 1, 99000, -1, QString(), false, this);
m_ui.horizontalLayout->addWidget(m_spinHeight);
m_ui.horizontalLayout->addWidget(m_spinHeight, 0, 3);
QMenu *menu = new QMenu(this);
QAction *adjustSize = new QAction(i18n("Adjust to original size"), this);
......@@ -174,9 +174,10 @@ GeometryWidget::GeometryWidget(Monitor* monitor, Timecode timecode, int clipPos,
alignButton->setDefaultAction(alignbottom);
alignButton->setAutoRaise(true);
alignLayout->addWidget(alignButton);
alignLayout->addStretch(10);
m_ui.horizontalLayout->addLayout(alignLayout);
m_ui.horizontalLayout->addStretch(10);
m_ui.horizontalLayout->addLayout(alignLayout, 1, 0, 1, 4);
//m_ui.horizontalLayout->addStretch(10);
m_spinSize = new DragValue(i18n("Size"), 100, 2, 1, 99000, -1, i18n("%"), false, this);
m_ui.horizontalLayout2->addWidget(m_spinSize);
......@@ -254,7 +255,7 @@ GeometryWidget::~GeometryWidget()
}
if (m_monitor) {
m_monitor->getEffectEdit()->showVisibilityButton(false);
m_monitor->slotEffectScene(false);
m_monitor->slotShowEffectScene(false);
}
}
......@@ -538,9 +539,9 @@ void GeometryWidget::slotCheckMonitorPosition(int renderPos)
} else {
if (renderPos >= m_clipPos && renderPos <= m_clipPos + m_outPoint - m_inPoint) {
if (!m_scene->views().at(0)->isVisible())
m_monitor->slotEffectScene(true);
m_monitor->slotShowEffectScene(true);
} else {
m_monitor->slotEffectScene(false);
m_monitor->slotShowEffectScene(false);
}
}
}
......@@ -707,7 +708,7 @@ void GeometryWidget::slotShowScene(bool show)
{
m_showScene = show;
if (!m_showScene)
m_monitor->slotEffectScene(false);
m_monitor->slotShowEffectScene(false);
else
slotCheckMonitorPosition(m_monitor->render->seekFramePosition());
}
......
......@@ -20,7 +20,6 @@
#include "initeffects.h"
#include "kdenlivesettings.h"
#include "effectslist.h"
#include "effectstackedit.h"
#include "mainwindow.h"
#include <KDebug>
......@@ -49,10 +48,10 @@ void initEffectsThumbnailer::run()
foreach(const QString & entry, m_list) {
kDebug() << entry;
if (!entry.isEmpty() && (entry.endsWith(".png") || entry.endsWith(".pgm"))) {
if (!EffectStackEdit::iconCache.contains(entry)) {
if (!MainWindow::m_lumacache.contains(entry)) {
QImage pix(entry);
//if (!pix.isNull())
EffectStackEdit::iconCache[entry] = pix.scaled(30, 30);
MainWindow::m_lumacache.insert(entry, pix.scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
kDebug() << "stored";
}
}
......
......@@ -140,6 +140,8 @@ EffectsList MainWindow::audioEffects;
EffectsList MainWindow::customEffects;
EffectsList MainWindow::transitions;
QMap <QString,QImage> MainWindow::m_lumacache;
MainWindow::MainWindow(const QString &MltPath, const KUrl & Url, const QString & clipsToLoad, QWidget *parent) :
KXmlGuiWindow(parent),
m_activeDocument(NULL),
......@@ -207,7 +209,7 @@ MainWindow::MainWindow(const QString &MltPath, const KUrl & Url, const QString &
// FIXME: the next call returns a newly allocated object, which leaks
initEffects::parseEffectFiles();
//initEffects::parseCustomEffectsFile();
m_monitorManager = new MonitorManager();
m_shortcutRemoveFocus = new QShortcut(QKeySequence("Esc"), this);
......
......@@ -28,6 +28,9 @@
#include <QEvent>
#include <QTimer>
#include <QShortcut>
#include <QMap>
#include <QString>
#include <QImage>
#include <KXmlGuiWindow>
#include <KTextEdit>
......@@ -38,6 +41,7 @@
#include <KComboBox>
#include <kautosavefile.h>
#include <KActionCategory>
#include <KImageCache>
#include "effectslist.h"
#include "gentime.h"
......@@ -109,6 +113,10 @@ public:
static EffectsList audioEffects;
static EffectsList customEffects;
static EffectsList transitions;
/** @brief Cache for luma files thumbnails. */
static QMap <QString,QImage> m_lumacache;
protected:
/** @brief Closes the window.
......
......@@ -986,7 +986,7 @@ void Monitor::slotSetSelectedClip(Transition* item)
}
void Monitor::slotEffectScene(bool show)
void Monitor::slotShowEffectScene(bool show)
{
if (m_id == Kdenlive::projectMonitor) {
if (videoSurface) {
......
......@@ -200,7 +200,8 @@ public slots:
void adjustRulerSize(int length);
void setTimePos(const QString &pos);
QStringList getZoneInfo() const;
void slotEffectScene(bool show = true);
/** @brief Display the on monitor effect scene (to adjust geometry over monitor). */
void slotShowEffectScene(bool show = true);
bool effectSceneDisplayed();
/** @brief Sets m_selectedClip to @param item. Used for looping it. */
......
......@@ -91,7 +91,7 @@ RotoWidget::~RotoWidget()
MonitorEditWidget *edit = m_monitor->getEffectEdit();
edit->showVisibilityButton(false);
edit->removeCustomControls();
m_monitor->slotEffectScene(false);
m_monitor->slotShowEffectScene(false);
}
}
......@@ -112,7 +112,7 @@ void RotoWidget::slotShowScene(bool show)
{
m_showScene = show;
if (!m_showScene)
m_monitor->slotEffectScene(false);
m_monitor->slotShowEffectScene(false);
else
slotCheckMonitorPosition(m_monitor->render->seekFramePosition());
}
......
......@@ -224,10 +224,11 @@ void TransitionSettings::slotSeekTimeline(int pos)