summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMahmoud Badri <mahmoud.badri@qt.io>2022-01-28 16:12:23 +0200
committerMahmoud Badri <mahmoud.badri@qt.io>2022-01-31 14:57:22 +0000
commit25fa5540ea8601cdf5fe1e841d940aff8483ad3f (patch)
tree442b4ea667bcf2be93ab3ef3876fdade6eda76cd
parent01f33fe4f1b83797845cbacacf2762fc4b63ce71 (diff)
downloadqt-creator-25fa5540ea8601cdf5fe1e841d940aff8483ad3f.tar.gz
QmlDesigner: Implement adding and removing asset folders
Also corrected assets view margins and few relevants tweaks. Task-number: QDS-5795 Change-Id: Ieeb68584bcb261422f48ec1a865f510a00c251f5 Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
-rw-r--r--share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml237
-rw-r--r--share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml2
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.cpp4
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.h3
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp50
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h5
-rw-r--r--src/plugins/qmldesigner/documentmanager.cpp6
7 files changed, 288 insertions, 19 deletions
diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml
index 8444a2c88f..ab403d203b 100644
--- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml
+++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml
@@ -36,7 +36,9 @@ Item {
property var selectedAssets: ({})
property int allExpandedState: 0
- property string delFilePath: ""
+ property string contextFilePath: ""
+ property var contextDir: undefined
+ property bool isDirContextMenu: false
DropArea {
id: dropArea
@@ -67,6 +69,19 @@ Item {
}
}
+ MouseArea { // right clicking the empty area of the view
+ anchors.fill: parent
+ acceptedButtons: Qt.RightButton
+ onClicked: {
+ if (!assetsModel.isEmpty) {
+ contextFilePath = ""
+ contextDir = assetsModel.rootDir()
+ isDirContextMenu = false
+ contextMenu.popup()
+ }
+ }
+ }
+
// called from C++ to close context menu on focus out
function handleViewFocusOut()
{
@@ -75,9 +90,139 @@ Item {
selectedAssetsChanged()
}
+ Dialog {
+ id: newFolderDialog
+
+ title: qsTr("Create new folder")
+ anchors.centerIn: parent
+ closePolicy: Popup.CloseOnEscape
+ modal: true
+
+ contentItem: Column {
+ spacing: 2
+
+ Row {
+ Text {
+ text: qsTr("Folder Name: ")
+ anchors.verticalCenter: parent.verticalCenter
+ color: StudioTheme.Values.themeTextColor
+ }
+
+ StudioControls.TextField {
+ id: folderName
+
+ actionIndicator.visible: false
+ translationIndicator.visible: false
+
+ Keys.onEnterPressed: btnCreate.onClicked()
+ Keys.onReturnPressed: btnCreate.onClicked()
+ }
+ }
+
+ Text {
+ text: qsTr("Folder Name cannot be empty.")
+ color: "#ff0000"
+ anchors.right: parent.right
+ visible: folderName.text === ""
+ }
+
+ Item { // spacer
+ width: 1
+ height: 20
+ }
+
+ Row {
+ anchors.right: parent.right
+
+ Button {
+ id: btnCreate
+
+ text: qsTr("Create")
+ enabled: folderName.text !== ""
+ onClicked: {
+ assetsModel.addNewFolder(contextDir.dirPath + '/' + folderName.text)
+ newFolderDialog.accept()
+ }
+ }
+
+ Button {
+ text: qsTr("Cancel")
+ onClicked: newFolderDialog.reject()
+ }
+ }
+ }
+
+ onOpened: {
+ folderName.text = "New folder"
+ folderName.selectAll()
+ folderName.forceActiveFocus()
+ }
+ }
+
+ Dialog {
+ id: confirmDeleteFolderDialog
+
+ title: qsTr("Folder not empty")
+ anchors.centerIn: parent
+ closePolicy: Popup.CloseOnEscape
+ implicitWidth: 300
+ modal: true
+
+ contentItem: Column {
+ spacing: 20
+ width: parent.width
+
+ Text {
+ id: folderNotEmpty
+
+ text: qsTr("Folder '%1' is not empty. Are you sure you want to delete it?")
+ .arg(contextDir ? contextDir.dirName : "")
+ color: StudioTheme.Values.themeTextColor
+ wrapMode: Text.WordWrap
+ width: confirmDeleteFolderDialog.width
+ leftPadding: 10
+ rightPadding: 10
+
+ Keys.onEnterPressed: btnDelete.onClicked()
+ Keys.onReturnPressed: btnDelete.onClicked()
+ }
+
+ Text {
+ text: qsTr("If the folder has assets in use, deleting it might cause the project to not work correctly.")
+ color: StudioTheme.Values.themeTextColor
+ wrapMode: Text.WordWrap
+ width: confirmDeleteFolderDialog.width
+ leftPadding: 10
+ rightPadding: 10
+ }
+
+ Row {
+ anchors.right: parent.right
+ Button {
+ id: btnDelete
+
+ text: qsTr("Delete")
+
+ onClicked: {
+ assetsModel.deleteFolder(contextDir.dirPath)
+ confirmDeleteFolderDialog.accept()
+ }
+ }
+
+ Button {
+ text: qsTr("Cancel")
+ onClicked: confirmDeleteFolderDialog.reject()
+ }
+ }
+ }
+
+ onOpened: folderNotEmpty.forceActiveFocus()
+ }
+
ScrollView { // TODO: experiment using ListView instead of ScrollView + Column
id: assetsView
anchors.fill: parent
+ interactive: assetsView.verticalScrollBarVisible
Item {
StudioControls.Menu {
@@ -86,7 +231,7 @@ Item {
StudioControls.MenuItem {
text: qsTr("Expand All")
enabled: allExpandedState !== 1
- visible: !delFilePath
+ visible: isDirContextMenu
height: visible ? implicitHeight : 0
onTriggered: assetsModel.toggleExpandAll(true)
}
@@ -94,22 +239,51 @@ Item {
StudioControls.MenuItem {
text: qsTr("Collapse All")
enabled: allExpandedState !== 2
- visible: !delFilePath
+ visible: isDirContextMenu
height: visible ? implicitHeight : 0
onTriggered: assetsModel.toggleExpandAll(false)
}
+ StudioControls.MenuSeparator {
+ visible: isDirContextMenu
+ height: visible ? StudioTheme.Values.border : 0
+ }
+
StudioControls.MenuItem {
text: qsTr("Delete File")
- visible: delFilePath
+ visible: contextFilePath
+ height: visible ? implicitHeight : 0
+ onTriggered: assetsModel.deleteFile(contextFilePath)
+ }
+
+ StudioControls.MenuSeparator {
+ visible: contextFilePath
+ height: visible ? StudioTheme.Values.border : 0
+ }
+
+ StudioControls.MenuItem {
+ text: qsTr("New Folder")
+ onTriggered: newFolderDialog.open()
+ }
+
+ StudioControls.MenuItem {
+ text: qsTr("Delete Folder")
+ visible: isDirContextMenu
height: visible ? implicitHeight : 0
- onTriggered: assetsModel.removeFile(delFilePath)
+ onTriggered: {
+ var dirEmpty = !(contextDir.dirsModel && contextDir.dirsModel.rowCount() > 0)
+ && !(contextDir.filesModel && contextDir.filesModel.rowCount() > 0);
+
+ if (dirEmpty)
+ assetsModel.deleteFolder(contextDir.dirPath)
+ else
+ confirmDeleteFolderDialog.open()
+ }
}
}
}
Column {
- spacing: 2
Repeater {
model: assetsModel // context property
delegate: dirSection
@@ -120,31 +294,35 @@ Item {
Section {
width: assetsView.width -
- (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0)
+ (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - 5
caption: dirName
sectionHeight: 30
sectionFontSize: 15
- levelShift: 20
leftPadding: 0
+ topPadding: dirDepth > 0 ? 5 : 0
+ bottomPadding: 0
hideHeader: dirDepth === 0
- showLeftBorder: true
+ showLeftBorder: dirDepth > 0
expanded: dirExpanded
- visible: dirVisible
+ visible: !assetsModel.isEmpty && dirVisible
expandOnClick: false
useDefaulContextMenu: false
onToggleExpand: {
dirExpanded = !dirExpanded
}
+
onShowContextMenu: {
- delFilePath = ""
+ contextFilePath = ""
+ contextDir = model
+ isDirContextMenu = true
allExpandedState = assetsModel.getAllExpandedState()
contextMenu.popup()
}
Column {
spacing: 5
- leftPadding: 15
+ leftPadding: 5
Repeater {
model: dirsModel
@@ -155,6 +333,25 @@ Item {
model: filesModel
delegate: fileSection
}
+
+ Text {
+ text: qsTr("Empty folder")
+ color: StudioTheme.Values.themeTextColorDisabled
+ font.pixelSize: 12
+ visible: !(dirsModel && dirsModel.rowCount() > 0)
+ && !(filesModel && filesModel.rowCount() > 0)
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.RightButton
+ onClicked: {
+ contextFilePath = ""
+ contextDir = model
+ isDirContextMenu = true
+ contextMenu.popup()
+ }
+ }
+ }
}
}
}
@@ -222,8 +419,11 @@ Item {
if (currFileSelected)
rootView.startDragAsset(selectedAssetsArr, mapToGlobal(mouse.x, mouse.y))
} else {
- delFilePath = filePath
+ contextFilePath = filePath
+ contextDir = model.fileDir
+
tooltipBackend.hideTooltip()
+ isDirContextMenu = false
contextMenu.popup()
}
}
@@ -263,7 +463,7 @@ Item {
// Placeholder when the assets panel is empty
Column {
id: colNoAssets
- visible: assetsModel.isEmpty
+ visible: assetsModel.isEmpty && !rootView.searchActive
spacing: 20
x: 20
@@ -307,4 +507,13 @@ Item {
wrapMode: Text.WordWrap
}
}
+
+ Text {
+ text: qsTr("No match found.")
+ x: 20
+ y: 10
+ color: StudioTheme.Values.themeTextColor
+ font.pixelSize: 12
+ visible: assetsModel.isEmpty && rootView.searchActive
+ }
}
diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml
index 66f05462c3..3f8a331189 100644
--- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml
+++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml
@@ -173,7 +173,7 @@ Item {
id: leftBorder
visible: false
width: 1
- height: parent.height - 15
+ height: parent.height - bottomPadding
color: header.color
}
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.cpp
index 1140ba43d7..271f330fd2 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.cpp
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.cpp
@@ -34,6 +34,7 @@ ItemLibraryAssetsFilesModel::ItemLibraryAssetsFilesModel(QObject *parent)
// add roles
m_roleNames.insert(FileNameRole, "fileName");
m_roleNames.insert(FilePathRole, "filePath");
+ m_roleNames.insert(FileDirRole, "fileDir");
}
QVariant ItemLibraryAssetsFilesModel::data(const QModelIndex &index, int role) const
@@ -49,6 +50,9 @@ QVariant ItemLibraryAssetsFilesModel::data(const QModelIndex &index, int role) c
if (role == FilePathRole)
return m_files[index.row()];
+ if (role == FileDirRole)
+ return QVariant::fromValue(parent());
+
qWarning() << Q_FUNC_INFO << "Invalid role requested: " << QString::number(role);
return {};
}
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.h
index 25577dce51..5b44878eef 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.h
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.h
@@ -44,7 +44,8 @@ public:
private:
enum Roles {FileNameRole = Qt::UserRole + 1,
- FilePathRole};
+ FilePathRole,
+ FileDirRole};
QStringList m_files;
QHash<int, QByteArray> m_roleNames;
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp
index a09391570d..be9b3b9f25 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp
@@ -99,7 +99,7 @@ void ItemLibraryAssetsModel::toggleExpandAll(bool expand)
endResetModel();
}
-void ItemLibraryAssetsModel::removeFile(const QString &filePath)
+void ItemLibraryAssetsModel::deleteFile(const QString &filePath)
{
bool askBeforeDelete = DesignerSettings::getValue(
DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET).toBool();
@@ -134,6 +134,52 @@ void ItemLibraryAssetsModel::removeFile(const QString &filePath)
}
}
+void ItemLibraryAssetsModel::addNewFolder(const QString &folderPath)
+{
+ QString iterPath = folderPath;
+ QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
+ QDir dir{folderPath};
+
+ while (dir.exists()) {
+ // if the folder name ends with a number, increment it
+ QRegularExpressionMatch match = rgx.match(iterPath);
+ if (match.hasMatch()) { // ends with a number
+ QString numStr = match.captured(0);
+ int num = match.captured(0).toInt();
+
+ // get number of padding zeros, ex: for "005" = 2
+ int nPaddingZeros = 0;
+ for (; nPaddingZeros < numStr.size() && numStr[nPaddingZeros] == '0'; ++nPaddingZeros);
+
+ ++num;
+
+ // if the incremented number's digits increased, decrease the padding zeros
+ if (std::fmod(std::log10(num), 1.0) == 0)
+ --nPaddingZeros;
+
+ iterPath = folderPath.mid(0, match.capturedStart())
+ + QString('0').repeated(nPaddingZeros)
+ + QString::number(num);
+ } else {
+ iterPath = folderPath + '1';
+ }
+
+ dir.setPath(iterPath);
+ }
+
+ dir.mkpath(iterPath);
+}
+
+void ItemLibraryAssetsModel::deleteFolder(const QString &folderPath)
+{
+ QDir{folderPath}.removeRecursively();
+}
+
+QObject *ItemLibraryAssetsModel::rootDir() const
+{
+ return m_assetsDir;
+}
+
const QStringList &ItemLibraryAssetsModel::supportedImageSuffixes()
{
static QStringList retList;
@@ -270,7 +316,7 @@ void ItemLibraryAssetsModel::setRootPath(const QString &path)
isEmpty &= parseDirRecursive(assetsDir, currDepth + 1);
}
- if (isEmpty)
+ if (!m_searchText.isEmpty() && isEmpty)
currAssetsDir->setDirVisible(false);
return isEmpty;
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h
index f762156349..e429605b9a 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h
@@ -83,7 +83,10 @@ public:
Q_INVOKABLE void toggleExpandAll(bool expand);
Q_INVOKABLE DirExpandState getAllExpandedState() const;
- Q_INVOKABLE void removeFile(const QString &filePath);
+ Q_INVOKABLE void deleteFile(const QString &filePath);
+ Q_INVOKABLE void addNewFolder(const QString &folderPath);
+ Q_INVOKABLE void deleteFolder(const QString &folderPath);
+ Q_INVOKABLE QObject *rootDir() const;
signals:
void isEmptyChanged();
diff --git a/src/plugins/qmldesigner/documentmanager.cpp b/src/plugins/qmldesigner/documentmanager.cpp
index 84e1416eb0..d510193d0a 100644
--- a/src/plugins/qmldesigner/documentmanager.cpp
+++ b/src/plugins/qmldesigner/documentmanager.cpp
@@ -504,8 +504,14 @@ bool DocumentManager::belongsToQmakeProject()
Utils::FilePath DocumentManager::currentResourcePath()
{
Utils::FilePath resourcePath = currentProjectDirPath();
+
if (resourcePath.isEmpty())
return currentFilePath().absolutePath();
+
+ FilePath contentFilePath = resourcePath.pathAppended("content");
+ if (contentFilePath.exists())
+ return contentFilePath;
+
return resourcePath;
}