Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Multimedia
Kdenlive
Commits
5cc52f51
Commit
5cc52f51
authored
Oct 10, 2020
by
Vivek Yadav
Committed by
Jean-Baptiste Mardelle
Oct 18, 2020
Browse files
improved last commit
parent
d8c42f4a
Changes
13
Hide whitespace changes
Inline
Side-by-side
src/assets/assetlist/model/assettreemodel.cpp
View file @
5cc52f51
...
...
@@ -90,79 +90,6 @@ QString AssetTreeModel::getDescription(bool isEffect, const QModelIndex &index)
return
QString
();
}
QString
AssetTreeModel
::
editCustomEffectInfo
(
const
QString
newName
,
const
QString
newDescription
,
const
QModelIndex
&
index
)
{
std
::
shared_ptr
<
TreeItem
>
item
=
getItemById
((
int
)
index
.
internalId
());
QString
currentName
=
item
->
dataColumn
(
AssetTreeModel
::
nameCol
).
toString
();
QDomDocument
doc
;
QDomElement
effect
=
EffectsRepository
::
get
()
->
getXml
(
currentName
);
QDir
dir
(
QStandardPaths
::
writableLocation
(
QStandardPaths
::
AppDataLocation
)
+
QStringLiteral
(
"/effects/"
));
QString
oldpath
=
dir
.
absoluteFilePath
(
currentName
+
QStringLiteral
(
".xml"
));
doc
.
appendChild
(
doc
.
importNode
(
effect
,
true
));
if
(
!
newDescription
.
trimmed
().
isEmpty
()){
QDomElement
root
=
doc
.
documentElement
();
QDomElement
nodelist
=
root
.
firstChildElement
(
"description"
);
QDomElement
newNodeTag
=
doc
.
createElement
(
QString
(
"description"
));
QDomText
text
=
doc
.
createTextNode
(
newDescription
);
newNodeTag
.
appendChild
(
text
);
root
.
replaceChild
(
newNodeTag
,
nodelist
);
}
if
(
!
newName
.
trimmed
().
isEmpty
())
{
QDir
dir
(
QStandardPaths
::
writableLocation
(
QStandardPaths
::
AppDataLocation
)
+
QStringLiteral
(
"/effects/"
));
if
(
!
dir
.
exists
())
{
dir
.
mkpath
(
QStringLiteral
(
"."
));
}
if
(
dir
.
exists
(
newName
+
QStringLiteral
(
".xml"
))){
QMessageBox
message
;
message
.
critical
(
0
,
i18n
(
"Error"
),
i18n
(
"Effect name %1 already exists.
\n
Try another name?"
,
newName
));
message
.
setFixedSize
(
400
,
200
);
return
oldpath
;
}
QFile
file
(
dir
.
absoluteFilePath
(
newName
+
QStringLiteral
(
".xml"
)));
QDomElement
root
=
doc
.
documentElement
();
QDomElement
nodelist
=
root
.
firstChildElement
(
"name"
);
QDomElement
newNodeTag
=
doc
.
createElement
(
QString
(
"name"
));
QDomText
text
=
doc
.
createTextNode
(
newName
);
newNodeTag
.
appendChild
(
text
);
root
.
replaceChild
(
newNodeTag
,
nodelist
);
QDomElement
e
=
doc
.
documentElement
();
e
.
setAttribute
(
"id"
,
newName
);
if
(
file
.
open
(
QFile
::
WriteOnly
|
QFile
::
Truncate
))
{
QTextStream
out
(
&
file
);
out
<<
doc
.
toString
();
}
file
.
close
();
deleteEffect
(
index
);
QString
path
=
dir
.
absoluteFilePath
(
newName
+
QStringLiteral
(
".xml"
));
return
path
;
}
else
{
QFile
file
(
dir
.
absoluteFilePath
(
currentName
+
QStringLiteral
(
".xml"
)));
if
(
file
.
open
(
QFile
::
WriteOnly
|
QFile
::
Truncate
))
{
QTextStream
out
(
&
file
);
out
<<
doc
.
toString
();
}
file
.
close
();
return
oldpath
;
}
}
QVariant
AssetTreeModel
::
data
(
const
QModelIndex
&
index
,
int
role
)
const
{
...
...
src/assets/assetlist/model/assettreemodel.hpp
View file @
5cc52f51
...
...
@@ -44,12 +44,12 @@ public:
QString
getDescription
(
bool
isEffect
,
const
QModelIndex
&
index
)
const
;
// Helper function to retrieve if an effect is categorized as favorite
bool
isFavorite
(
const
QModelIndex
&
index
)
const
;
QString
editCustomEffectInfo
(
const
QString
newName
,
const
QString
newDescription
,
const
QModelIndex
&
index
);
QHash
<
int
,
QByteArray
>
roleNames
()
const
override
;
QVariant
data
(
const
QModelIndex
&
index
,
int
role
)
const
override
;
virtual
void
reloadAssetMenu
(
QMenu
*
effectsMenu
,
KActionCategory
*
effectActions
)
=
0
;
virtual
void
setFavorite
(
const
QModelIndex
&
index
,
bool
favorite
,
bool
isEffect
)
=
0
;
virtual
void
deleteEffect
(
const
QModelIndex
&
index
)
=
0
;
virtual
void
editCustomAsset
(
const
QString
newName
,
const
QString
newDescription
,
const
QModelIndex
&
index
)
=
0
;
// for convenience, we store the column of each data field
static
int
nameCol
,
idCol
,
favCol
,
typeCol
,
preferredCol
;
...
...
src/assets/assetlist/view/assetlistwidget.cpp
View file @
5cc52f51
...
...
@@ -29,11 +29,6 @@
#include <QQuickItem>
#include <QStandardPaths>
#include <kdeclarative_version.h>
#include <QFormLayout>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLineEdit>
#include <QTextEdit>
AssetListWidget
::
AssetListWidget
(
QWidget
*
parent
)
:
QQuickWidget
(
parent
)
...
...
@@ -89,32 +84,6 @@ QString AssetListWidget::getDescription(bool isEffect, const QModelIndex &index)
return
m_model
->
getDescription
(
isEffect
,
m_proxyModel
->
mapToSource
(
index
));
}
QString
AssetListWidget
::
editCustomEffectInfo
(
const
QModelIndex
&
index
)
{
QDialog
dialog
(
this
);
QFormLayout
form
(
&
dialog
);
QLineEdit
*
effectName
=
new
QLineEdit
(
&
dialog
);
QTextEdit
*
descriptionBox
=
new
QTextEdit
(
&
dialog
);
QString
label_Name
=
QString
(
"Name : "
);
form
.
addRow
(
label_Name
,
effectName
);
QString
label
=
QString
(
"Comments : "
);
form
.
addRow
(
label
,
descriptionBox
);
QDialogButtonBox
buttonBox
(
QDialogButtonBox
::
Ok
|
QDialogButtonBox
::
Cancel
,
Qt
::
Horizontal
,
&
dialog
);
form
.
addRow
(
&
buttonBox
);
QObject
::
connect
(
&
buttonBox
,
SIGNAL
(
accepted
()),
&
dialog
,
SLOT
(
accept
()));
QObject
::
connect
(
&
buttonBox
,
SIGNAL
(
rejected
()),
&
dialog
,
SLOT
(
reject
()));
if
(
dialog
.
exec
()
==
QDialog
::
Accepted
)
{
QString
name
=
effectName
->
text
();
QString
enteredDescription
=
descriptionBox
->
toPlainText
();
if
(
name
.
trimmed
().
isEmpty
()
&&
enteredDescription
.
trimmed
().
isEmpty
())
{
return
index
.
data
().
toString
();
}
return
m_model
->
editCustomEffectInfo
(
name
,
enteredDescription
,
m_proxyModel
->
mapToSource
(
index
));
}
return
index
.
data
().
toString
();
}
void
AssetListWidget
::
setFilterName
(
const
QString
&
pattern
)
{
m_proxyModel
->
setFilterName
(
!
pattern
.
isEmpty
(),
pattern
);
...
...
src/assets/assetlist/view/assetlistwidget.hpp
View file @
5cc52f51
...
...
@@ -55,7 +55,7 @@ public:
/* @brief Delete a custom effect */
void
deleteCustomEffect
(
const
QModelIndex
&
index
);
virtual
void
reloadCustomEffectIx
(
const
QModelIndex
&
index
)
=
0
;
QString
editCustom
EffectInfo
(
const
QModelIndex
&
index
);
virtual
void
editCustom
Asset
(
const
QModelIndex
&
index
)
=
0
;
/* @brief Returns the description of the asset given its model index */
QString
getDescription
(
bool
isEffect
,
const
QModelIndex
&
index
)
const
;
...
...
src/effects/effectlist/model/effecttreemodel.cpp
View file @
5cc52f51
...
...
@@ -32,6 +32,7 @@
#include <KActionCategory>
#include <QDebug>
#include <QMenu>
#include <QMessageBox>
EffectTreeModel
::
EffectTreeModel
(
QObject
*
parent
)
:
AssetTreeModel
(
parent
)
...
...
@@ -196,3 +197,76 @@ void EffectTreeModel::setFavorite(const QModelIndex &index, bool favorite, bool
KdenliveSettings
::
setFavorite_effects
(
favs
);
}
void
EffectTreeModel
::
editCustomAsset
(
const
QString
newName
,
const
QString
newDescription
,
const
QModelIndex
&
index
)
{
std
::
shared_ptr
<
TreeItem
>
item
=
getItemById
((
int
)
index
.
internalId
());
QString
currentName
=
item
->
dataColumn
(
AssetTreeModel
::
nameCol
).
toString
();
QDomDocument
doc
;
QDomElement
effect
=
EffectsRepository
::
get
()
->
getXml
(
currentName
);
QDir
dir
(
QStandardPaths
::
writableLocation
(
QStandardPaths
::
AppDataLocation
)
+
QStringLiteral
(
"/effects/"
));
QString
oldpath
=
dir
.
absoluteFilePath
(
currentName
+
QStringLiteral
(
".xml"
));
doc
.
appendChild
(
doc
.
importNode
(
effect
,
true
));
if
(
!
newDescription
.
trimmed
().
isEmpty
()){
QDomElement
root
=
doc
.
documentElement
();
QDomElement
nodelist
=
root
.
firstChildElement
(
"description"
);
QDomElement
newNodeTag
=
doc
.
createElement
(
QString
(
"description"
));
QDomText
text
=
doc
.
createTextNode
(
newDescription
);
newNodeTag
.
appendChild
(
text
);
root
.
replaceChild
(
newNodeTag
,
nodelist
);
}
if
(
!
newName
.
trimmed
().
isEmpty
())
{
QDir
dir
(
QStandardPaths
::
writableLocation
(
QStandardPaths
::
AppDataLocation
)
+
QStringLiteral
(
"/effects/"
));
if
(
!
dir
.
exists
())
{
dir
.
mkpath
(
QStringLiteral
(
"."
));
}
if
(
dir
.
exists
(
newName
+
QStringLiteral
(
".xml"
))){
QMessageBox
message
;
message
.
critical
(
0
,
i18n
(
"Error"
),
i18n
(
"Effect name %1 already exists.
\n
Try another name?"
,
newName
));
message
.
setFixedSize
(
400
,
200
);
return
;
}
QFile
file
(
dir
.
absoluteFilePath
(
newName
+
QStringLiteral
(
".xml"
)));
QDomElement
root
=
doc
.
documentElement
();
QDomElement
nodelist
=
root
.
firstChildElement
(
"name"
);
QDomElement
newNodeTag
=
doc
.
createElement
(
QString
(
"name"
));
QDomText
text
=
doc
.
createTextNode
(
newName
);
newNodeTag
.
appendChild
(
text
);
root
.
replaceChild
(
newNodeTag
,
nodelist
);
QDomElement
e
=
doc
.
documentElement
();
e
.
setAttribute
(
"id"
,
newName
);
if
(
file
.
open
(
QFile
::
WriteOnly
|
QFile
::
Truncate
))
{
QTextStream
out
(
&
file
);
out
<<
doc
.
toString
();
}
file
.
close
();
deleteEffect
(
index
);
reloadEffect
(
dir
.
absoluteFilePath
(
newName
+
QStringLiteral
(
".xml"
)));
}
else
{
QFile
file
(
dir
.
absoluteFilePath
(
currentName
+
QStringLiteral
(
".xml"
)));
if
(
file
.
open
(
QFile
::
WriteOnly
|
QFile
::
Truncate
))
{
QTextStream
out
(
&
file
);
out
<<
doc
.
toString
();
}
file
.
close
();
reloadEffect
(
oldpath
);
}
}
src/effects/effectlist/model/effecttreemodel.hpp
View file @
5cc52f51
...
...
@@ -42,6 +42,7 @@ public:
void
reloadAssetMenu
(
QMenu
*
effectsMenu
,
KActionCategory
*
effectActions
)
override
;
void
setFavorite
(
const
QModelIndex
&
index
,
bool
favorite
,
bool
isEffect
)
override
;
void
deleteEffect
(
const
QModelIndex
&
index
)
override
;
void
editCustomAsset
(
const
QString
newName
,
const
QString
newDescription
,
const
QModelIndex
&
index
)
override
;
protected:
std
::
shared_ptr
<
TreeItem
>
m_customCategory
;
...
...
src/effects/effectlist/view/effectlistwidget.cpp
View file @
5cc52f51
...
...
@@ -29,6 +29,12 @@
#include <QQmlContext>
#include <QStandardPaths>
#include <memory>
#include <QFormLayout>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLineEdit>
#include <QTextEdit>
EffectListWidget
::
EffectListWidget
(
QWidget
*
parent
)
:
AssetListWidget
(
parent
)
{
...
...
@@ -101,3 +107,28 @@ void EffectListWidget::reloadEffectMenu(QMenu *effectsMenu, KActionCategory *eff
{
m_model
->
reloadAssetMenu
(
effectsMenu
,
effectActions
);
}
void
EffectListWidget
::
editCustomAsset
(
const
QModelIndex
&
index
)
{
QDialog
dialog
(
this
);
QFormLayout
form
(
&
dialog
);
QLineEdit
*
effectName
=
new
QLineEdit
(
&
dialog
);
QTextEdit
*
descriptionBox
=
new
QTextEdit
(
&
dialog
);
QString
label_Name
=
QString
(
"Name : "
);
form
.
addRow
(
label_Name
,
effectName
);
QString
label
=
QString
(
"Comments : "
);
form
.
addRow
(
label
,
descriptionBox
);
QDialogButtonBox
buttonBox
(
QDialogButtonBox
::
Ok
|
QDialogButtonBox
::
Cancel
,
Qt
::
Horizontal
,
&
dialog
);
form
.
addRow
(
&
buttonBox
);
QObject
::
connect
(
&
buttonBox
,
SIGNAL
(
accepted
()),
&
dialog
,
SLOT
(
accept
()));
QObject
::
connect
(
&
buttonBox
,
SIGNAL
(
rejected
()),
&
dialog
,
SLOT
(
reject
()));
if
(
dialog
.
exec
()
==
QDialog
::
Accepted
)
{
QString
name
=
effectName
->
text
();
QString
enteredDescription
=
descriptionBox
->
toPlainText
();
if
(
name
.
trimmed
().
isEmpty
()
&&
enteredDescription
.
trimmed
().
isEmpty
())
{
return
;
}
m_model
->
editCustomAsset
(
name
,
enteredDescription
,
m_proxyModel
->
mapToSource
(
index
));
}
}
src/effects/effectlist/view/effectlistwidget.cpp.autosave
0 → 100644
View file @
5cc52f51
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive. See www.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) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* 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, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include "effectlistwidget.hpp"
#include "../model/effectfilter.hpp"
#include "../model/effecttreemodel.hpp"
#include "assets/assetlist/view/qmltypes/asseticonprovider.hpp"
#include <KActionCategory>
#include <QMenu>
#include <QQmlContext>
#include <QStandardPaths>
#include <memory>
#include <QFormLayout>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLineEdit>
#include <QTextEdit>
EffectListWidget::EffectListWidget(QWidget *parent)
: AssetListWidget(parent)
{
QString effectCategory = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdenliveeffectscategory.rc"));
m_model = EffectTreeModel::construct(effectCategory, this);
m_proxyModel = std::make_unique<EffectFilter>(this);
m_proxyModel->setSourceModel(m_model.get());
m_proxyModel->setSortRole(EffectTreeModel::NameRole);
m_proxyModel->sort(0, Qt::AscendingOrder);
m_proxy = new EffectListWidgetProxy(this);
rootContext()->setContextProperty("assetlist", m_proxy);
rootContext()->setContextProperty("assetListModel", m_proxyModel.get());
rootContext()->setContextProperty("isEffectList", true);
m_assetIconProvider = new AssetIconProvider(true);
setup();
// Activate "Main effects" filter
setFilterType("");
}
void EffectListWidget::updateFavorite(const QModelIndex &index)
{
emit m_proxyModel->dataChanged(index, index, QVector<int>());
m_proxyModel->reloadFilterOnFavorite();
emit reloadFavorites();
}
EffectListWidget::~EffectListWidget()
{
qDebug() << " - - -Deleting effect list widget";
}
void EffectListWidget::setFilterType(const QString &type)
{
if (type == "video") {
static_cast<EffectFilter *>(m_proxyModel.get())->setFilterType(true, AssetListType::AssetType::Video);
} else if (type == "audio") {
static_cast<EffectFilter *>(m_proxyModel.get())->setFilterType(true, AssetListType::AssetType::Audio);
} else if (type == "custom") {
static_cast<EffectFilter *>(m_proxyModel.get())->setFilterType(true, AssetListType::AssetType::Custom);
} else if (type == "favorites") {
static_cast<EffectFilter *>(m_proxyModel.get())->setFilterType(true, AssetListType::AssetType::Favorites);
} else {
static_cast<EffectFilter *>(m_proxyModel.get())->setFilterType(true, AssetListType::AssetType::Preferred);
}
}
QString EffectListWidget::getMimeType(const QString &assetId) const
{
Q_UNUSED(assetId);
return QStringLiteral("kdenlive/effect");
}
void EffectListWidget::reloadCustomEffectIx(const QModelIndex &index)
{
static_cast<EffectTreeModel *>(m_model.get())->reloadEffectFromIndex(m_proxyModel->mapToSource(index));
m_proxyModel->sort(0, Qt::AscendingOrder);
}
void EffectListWidget::reloadCustomEffect(const QString &path)
{
static_cast<EffectTreeModel *>(m_model.get())->reloadEffect(path);
m_proxyModel->sort(0, Qt::AscendingOrder);
}
void EffectListWidget::reloadEffectMenu(QMenu *effectsMenu, KActionCategory *effectActions)
{
m_model->reloadAssetMenu(effectsMenu, effectActions);
}
void EffectListWidget::editCustomAsset(const QModelIndex &index)
{
QDialog dialog(this);
QFormLayout form(&dialog);
QLineEdit *effectName = new QLineEdit(&dialog);
QTextEdit *descriptionBox = new QTextEdit(&dialog);
QString label_Name = QString("Name : ");
form.addRow(label_Name, effectName);
QString label = QString("Comments : ");
form.addRow(label, descriptionBox);
QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog);
form.addRow(&buttonBox);
QObject::connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
QObject::connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
if(dialog.exec() == QDialog::Accepted) {
QString name = effectName->text();
QString enteredDescription = descriptionBox->toPlainText();
if (name.trimmed().isEmpty() && enteredDescription.trimmed().isEmpty()) {
return;
}
m_model->editCustomAsset(name, enteredDescription, m_proxyModel->mapToSource(index));
}
}
src/effects/effectlist/view/effectlistwidget.hpp
View file @
5cc52f51
...
...
@@ -48,7 +48,7 @@ public:
void
updateFavorite
(
const
QModelIndex
&
index
);
void
reloadEffectMenu
(
QMenu
*
effectsMenu
,
KActionCategory
*
effectActions
);
void
reloadCustomEffectIx
(
const
QModelIndex
&
index
)
override
;
void
editCustomAsset
(
const
QModelIndex
&
index
)
override
;
public
slots
:
void
reloadCustomEffect
(
const
QString
&
path
);
...
...
@@ -81,14 +81,7 @@ public:
}
Q_INVOKABLE
void
deleteCustomEffect
(
const
QModelIndex
&
index
)
{
q
->
deleteCustomEffect
(
index
);
}
Q_INVOKABLE
QString
getDescription
(
const
QModelIndex
&
index
)
const
{
return
q
->
getDescription
(
true
,
index
);
}
Q_INVOKABLE
void
editCustomEffectInfo
(
const
QModelIndex
&
index
)
{
QString
pathToUpdate
=
q
->
editCustomEffectInfo
(
index
);
if
(
index
.
data
().
toString
()
!=
pathToUpdate
)
{
q
->
reloadCustomEffect
(
pathToUpdate
);
}
}
Q_INVOKABLE
void
editCustomEffectInfo
(
const
QModelIndex
&
index
){
q
->
editCustomAsset
(
index
);
}
Q_INVOKABLE
QVariantMap
getMimeData
(
const
QString
&
assetId
)
const
{
return
q
->
getMimeData
(
assetId
);
}
Q_INVOKABLE
void
activate
(
const
QModelIndex
&
ix
)
{
q
->
activate
(
ix
);
}
...
...
src/transitions/transitionlist/model/transitiontreemodel.cpp
View file @
5cc52f51
...
...
@@ -111,3 +111,6 @@ void TransitionTreeModel::deleteEffect(const QModelIndex &)
{
}
void
TransitionTreeModel
::
editCustomAsset
(
const
QString
newName
,
const
QString
newDescription
,
const
QModelIndex
&
index
)
{
}
src/transitions/transitionlist/model/transitiontreemodel.hpp
View file @
5cc52f51
...
...
@@ -40,7 +40,7 @@ public:
void
reloadAssetMenu
(
QMenu
*
effectsMenu
,
KActionCategory
*
effectActions
)
override
;
void
setFavorite
(
const
QModelIndex
&
index
,
bool
favorite
,
bool
isEffect
)
override
;
void
deleteEffect
(
const
QModelIndex
&
index
)
override
;
void
editCustomAsset
(
const
QString
newName
,
const
QString
newDescription
,
const
QModelIndex
&
index
)
override
;
protected:
};
...
...
src/transitions/transitionlist/view/transitionlistwidget.cpp
View file @
5cc52f51
...
...
@@ -107,3 +107,8 @@ void TransitionListWidget::downloadNewLumas()
void
TransitionListWidget
::
reloadCustomEffectIx
(
const
QModelIndex
&
path
)
{
}
void
TransitionListWidget
::
editCustomAsset
(
const
QModelIndex
&
index
)
{
}
src/transitions/transitionlist/view/transitionlistwidget.hpp
View file @
5cc52f51
...
...
@@ -44,7 +44,7 @@ public:
void
updateFavorite
(
const
QModelIndex
&
index
);
void
downloadNewLumas
();
void
reloadCustomEffectIx
(
const
QModelIndex
&
path
)
override
;
void
editCustomAsset
(
const
QModelIndex
&
index
)
override
;
private:
TransitionListWidgetProxy
*
m_proxy
;
int
getNewStuff
(
const
QString
&
configFile
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment