aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDennis Oberst <dennis.oberst@qt.io>2023-06-27 15:54:20 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2023-08-23 11:48:19 +0000
commit44fc775d59fd82c35c3782cf8d1ee011021ad845 (patch)
tree8f7ad6f6aca43d03ade8bd656c406828bac08e16
parent30b04091a7a7840f9400862ec20e7a906dcce40e (diff)
Filesystem Explorer Example: Introduce version 2
This updated version addresses several bugs and misbehaviors that were identified in the previous version. I have rewritten and improved various aspects of the application to provide a more stable and reliable user experience. Here are some of the key changes and enhancements in version 2: - Fix qmllint warnings. - Reduce the number of redundant items - Apply the custom window decorations inside MyMenuBar to the contentItem instead of the background to scale properly. - Fix additional scaling and UI misbehaviors - Add an application icon - Add an editor with line numbers - Add command line options to specify an initial directory - Since rootIndex is exposed inside TreeView since 6.6 this is an excellent opportunity to make use of it - Crosslink the python version of this example in the docs Change-Id: Ib816a95f843b3f4b84b11db893facda0ffc6f482 Reviewed-by: Mitch Curtis <mitch.curtis@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> (cherry picked from commit 959f88e4b5371edff0b344e5f768c70aa5202402) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--examples/quickcontrols/filesystemexplorer/CMakeLists.txt18
-rw-r--r--examples/quickcontrols/filesystemexplorer/Main.qml223
-rw-r--r--examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.jpgbin48862 -> 0 bytes
-rw-r--r--examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.webpbin0 -> 47416 bytes
-rw-r--r--examples/quickcontrols/filesystemexplorer/doc/src/qtquickcontrols-filesystemexplorer.qdoc118
-rw-r--r--examples/quickcontrols/filesystemexplorer/filesystemexplorer.pro9
-rw-r--r--examples/quickcontrols/filesystemexplorer/filesystemmodel.cpp57
-rw-r--r--examples/quickcontrols/filesystemexplorer/filesystemmodel.h24
-rw-r--r--examples/quickcontrols/filesystemexplorer/icons/app_icon.svg2
-rw-r--r--examples/quickcontrols/filesystemexplorer/linenumbermodel.cpp58
-rw-r--r--examples/quickcontrols/filesystemexplorer/linenumbermodel.h32
-rw-r--r--examples/quickcontrols/filesystemexplorer/main.cpp26
-rw-r--r--examples/quickcontrols/filesystemexplorer/qml/About.qml44
-rw-r--r--examples/quickcontrols/filesystemexplorer/qml/Colors.qml29
-rw-r--r--examples/quickcontrols/filesystemexplorer/qml/Editor.qml160
-rw-r--r--examples/quickcontrols/filesystemexplorer/qml/FileSystemView.qml110
-rw-r--r--examples/quickcontrols/filesystemexplorer/qml/Icon.qml44
-rw-r--r--examples/quickcontrols/filesystemexplorer/qml/MyMenu.qml19
-rw-r--r--examples/quickcontrols/filesystemexplorer/qml/MyMenuBar.qml184
-rw-r--r--examples/quickcontrols/filesystemexplorer/qml/ResizeButton.qml10
-rw-r--r--examples/quickcontrols/filesystemexplorer/qml/Sidebar.qml147
-rw-r--r--examples/quickcontrols/filesystemexplorer/qmldir2
22 files changed, 882 insertions, 434 deletions
diff --git a/examples/quickcontrols/filesystemexplorer/CMakeLists.txt b/examples/quickcontrols/filesystemexplorer/CMakeLists.txt
index 1127d0f93d..53eeb7b4e6 100644
--- a/examples/quickcontrols/filesystemexplorer/CMakeLists.txt
+++ b/examples/quickcontrols/filesystemexplorer/CMakeLists.txt
@@ -10,14 +10,14 @@ endif ()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quickcontrols/filesystemexplorer")
-find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick QuickControls2)
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick QuickControls2 Svg)
qt_standard_project_setup(REQUIRES 6.5)
-qt_add_executable(filesystemexplorerapp
+qt_add_executable(filesystemexplorer
main.cpp
)
-set_target_properties(filesystemexplorerapp
+set_target_properties(filesystemexplorer
PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
@@ -28,15 +28,15 @@ set_source_files_properties(qml/Colors.qml
QT_QML_SINGLETON_TYPE TRUE
)
-qt_add_qml_module(filesystemexplorerapp
+qt_add_qml_module(filesystemexplorer
URI FileSystemModule
VERSION 1.0
QML_FILES
"Main.qml"
"qml/About.qml"
"qml/Colors.qml"
+ "qml/Editor.qml"
"qml/FileSystemView.qml"
- "qml/Icon.qml"
"qml/MyMenu.qml"
"qml/MyMenuBar.qml"
"qml/Sidebar.qml"
@@ -52,20 +52,24 @@ qt_add_qml_module(filesystemexplorerapp
"icons/read.svg"
"icons/resize.svg"
"icons/qt_logo.svg"
+ "icons/app_icon.svg"
SOURCES
filesystemmodel.cpp
filesystemmodel.h
+ linenumbermodel.cpp
+ linenumbermodel.h
)
-target_link_libraries(filesystemexplorerapp
+target_link_libraries(filesystemexplorer
PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Quick
Qt6::QuickControls2
+ Qt6::Svg
)
-install(TARGETS filesystemexplorerapp
+install(TARGETS filesystemexplorer
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
diff --git a/examples/quickcontrols/filesystemexplorer/Main.qml b/examples/quickcontrols/filesystemexplorer/Main.qml
index 429eae9bdf..233817de7f 100644
--- a/examples/quickcontrols/filesystemexplorer/Main.qml
+++ b/examples/quickcontrols/filesystemexplorer/Main.qml
@@ -1,47 +1,65 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
import QtQuick
import QtQuick.Controls.Basic
import QtQuick.Layouts
import FileSystemModule
+pragma ComponentBehavior: Bound
+
ApplicationWindow {
id: root
+
+ property bool expandPath: false
+ property bool showLineNumbers: true
+ property string currentFilePath: ""
+
width: 1100
height: 600
visible: true
+ color: Colors.background
flags: Qt.Window | Qt.FramelessWindowHint
- title: qsTr("Qt Quick Controls 2 - File System Explorer")
+ title: qsTr("File System Explorer Example")
- property string currentFilePath: ""
- property bool expandPath: false
+ function getInfoText() : string {
+ let out = root.currentFilePath
+ if (!out)
+ return qsTr("File System Explorer")
+ return root.expandPath ? out : out.substring(out.lastIndexOf("/") + 1, out.length)
+ }
menuBar: MyMenuBar {
- rootWindow: root
-
- infoText: currentFilePath
- ? (expandPath ? currentFilePath
- : currentFilePath.substring(currentFilePath.lastIndexOf("/") + 1, currentFilePath.length))
- : "File System Explorer"
-
+ dragWindow: root
+ infoText: root.getInfoText()
MyMenu {
title: qsTr("File")
Action {
text: qsTr("Increase Font")
shortcut: "Ctrl++"
- onTriggered: textArea.font.pixelSize += 1
+ onTriggered: editor.text.font.pixelSize += 1
}
Action {
text: qsTr("Decrease Font")
shortcut: "Ctrl+-"
- onTriggered: textArea.font.pixelSize -= 1
+ onTriggered: editor.text.font.pixelSize -= 1
+ }
+ Action {
+ text: root.showLineNumbers ? qsTr("Toggle Line Numbers OFF")
+ : qsTr("Toggle Line Numbers ON")
+ shortcut: "Ctrl+L"
+ onTriggered: root.showLineNumbers = !root.showLineNumbers
+ }
+ Action {
+ text: root.expandPath ? qsTr("Toggle Short Path")
+ : qsTr("Toggle Expand Path")
+ enabled: root.currentFilePath
+ onTriggered: root.expandPath = !root.expandPath
}
Action {
- text: expandPath ? qsTr("Toggle Short Path") : qsTr("Toggle Expand Path")
- enabled: currentFilePath
- onTriggered: expandPath = !expandPath
+ text: qsTr("Reset Filesystem")
+ enabled: sidebar.currentTabIndex === 1
+ onTriggered: fileSystemView.rootIndex = undefined
}
Action {
text: qsTr("Exit")
@@ -55,134 +73,109 @@ ApplicationWindow {
Action {
text: qsTr("Cut")
shortcut: StandardKey.Cut
- enabled: textArea.selectedText.length > 0
- onTriggered: textArea.cut()
+ enabled: editor.text.selectedText.length > 0
+ onTriggered: editor.text.cut()
}
Action {
text: qsTr("Copy")
shortcut: StandardKey.Copy
- enabled: textArea.selectedText.length > 0
- onTriggered: textArea.copy()
+ enabled: editor.text.selectedText.length > 0
+ onTriggered: editor.text.copy()
}
Action {
text: qsTr("Paste")
shortcut: StandardKey.Paste
- enabled: textArea.canPaste
- onTriggered: textArea.paste()
+ enabled: editor.text.canPaste
+ onTriggered: editor.text.paste()
}
Action {
text: qsTr("Select All")
shortcut: StandardKey.SelectAll
- enabled: textArea.length > 0
- onTriggered: textArea.selectAll()
+ enabled: editor.text.length > 0
+ onTriggered: editor.text.selectAll()
}
Action {
text: qsTr("Undo")
shortcut: StandardKey.Undo
- enabled: textArea.canUndo
- onTriggered: textArea.undo()
+ enabled: editor.text.canUndo
+ onTriggered: editor.text.undo()
}
}
}
-
- Rectangle {
+ // Set up the layout of the main components in a row:
+ // [ Sidebar, Navigation, Editor ]
+ RowLayout {
anchors.fill: parent
- color: Colors.background
-
- RowLayout {
- anchors.fill: parent
- spacing: 0
-
- // Stores the buttons that navigate the application.
- Sidebar {
- id: sidebar
- rootWindow: root
-
- Layout.preferredWidth: 60
- Layout.fillHeight: true
- }
+ spacing: 0
+
+ // Stores the buttons that navigate the application.
+ Sidebar {
+ id: sidebar
+ dragWindow: root
+ Layout.preferredWidth: 50
+ Layout.fillHeight: true
+ }
- // Allows resizing parts of the UI.
- SplitView {
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- handle: Rectangle {
- implicitWidth: 10
- color: SplitHandle.pressed ? Colors.color2 : Colors.background
- border.color: Colors.color2
- opacity: SplitHandle.hovered || SplitHandle.pressed ? 1.0 : 0.0
-
- Behavior on opacity {
- OpacityAnimator {
- duration: 900
- }
+ // Allows resizing parts of the UI.
+ SplitView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ // Customized handle to drag between the Navigation and the Editor.
+ handle: Rectangle {
+ implicitWidth: 10
+ color: SplitHandle.pressed ? Colors.color2 : Colors.background
+ border.color: SplitHandle.hovered ? Colors.color2 : Colors.background
+ opacity: SplitHandle.hovered || navigationView.width < 15 ? 1.0 : 0.0
+
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: 1400
}
}
+ }
- // We use an inline component to make a reusable TextArea component.
- // This is convenient when the component is only used in one file.
- component MyTextArea: TextArea {
- antialiasing: true
- color: Colors.textFile
- selectedTextColor: Colors.textFile
- selectionColor: Colors.selection
- renderType: Text.QtRendering
- textFormat: TextEdit.PlainText
-
- background: null
- }
-
- Rectangle {
- color: Colors.surface1
-
- SplitView.preferredWidth: 250
- SplitView.fillHeight: true
-
- StackLayout {
- currentIndex: sidebar.currentTabIndex
-
- anchors.fill: parent
-
- // Shows the help text.
- MyTextArea {
- readOnly: true
- text: qsTr("This example shows how to use and visualize the file system.\n\n"
- + "Customized Qt Quick Components have been used to achieve this look.\n\n"
- + "You can edit the files but they won't be changed on the file system.\n\n"
- + "Click on the folder icon to the left to get started.")
- wrapMode: TextArea.Wrap
- }
-
- // Shows the files on the file system.
- FileSystemView {
- id: fileSystemView
- color: Colors.surface1
-
- onFileClicked: (path) => root.currentFilePath = path
- }
+ Rectangle {
+ id: navigationView
+ color: Colors.surface1
+ SplitView.preferredWidth: 250
+ SplitView.fillHeight: true
+ // The stack-layout provides different views, based on the
+ // selected buttons inside the sidebar.
+ StackLayout {
+ anchors.fill: parent
+ currentIndex: sidebar.currentTabIndex
+
+ // Shows the help text.
+ Text {
+ text: qsTr("This example shows how to use and visualize the file system.\n\n"
+ + "Customized Qt Quick Components have been used to achieve this look.\n\n"
+ + "You can edit the files but they won't be changed on the file system.\n\n"
+ + "Click on the folder icon to the left to get started.")
+ wrapMode: TextArea.Wrap
+ color: Colors.text
}
- }
-
- // The ScrollView that contains the TextArea which shows the file's content.
- ScrollView {
- leftPadding: 20
- topPadding: 20
- bottomPadding: 20
- clip: true
-
- SplitView.fillWidth: true
- SplitView.fillHeight: true
-
- property alias textArea: textArea
- MyTextArea {
- id: textArea
- text: FileSystemModel.readFile(root.currentFilePath)
+ // Shows the files on the file system.
+ FileSystemView {
+ id: fileSystemView
+ color: Colors.surface1
+ onFileClicked: path => root.currentFilePath = path
}
}
}
+
+ // The main view that contains the editor.
+ Editor {
+ id: editor
+ showLineNumbers: root.showLineNumbers
+ currentFilePath: root.currentFilePath
+ SplitView.fillWidth: true
+ SplitView.fillHeight: true
+ }
}
- ResizeButton {}
+ }
+
+ ResizeButton {
+ resizeWindow: root
}
}
diff --git a/examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.jpg b/examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.jpg
deleted file mode 100644
index 628b5368d0..0000000000
--- a/examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.jpg
+++ /dev/null
Binary files differ
diff --git a/examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.webp b/examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.webp
new file mode 100644
index 0000000000..10ad0d26e7
--- /dev/null
+++ b/examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.webp
Binary files differ
diff --git a/examples/quickcontrols/filesystemexplorer/doc/src/qtquickcontrols-filesystemexplorer.qdoc b/examples/quickcontrols/filesystemexplorer/doc/src/qtquickcontrols-filesystemexplorer.qdoc
index 23e109a9b8..8c950a67c7 100644
--- a/examples/quickcontrols/filesystemexplorer/doc/src/qtquickcontrols-filesystemexplorer.qdoc
+++ b/examples/quickcontrols/filesystemexplorer/doc/src/qtquickcontrols-filesystemexplorer.qdoc
@@ -4,70 +4,87 @@
\example filesystemexplorer
\examplecategory {Application Examples}
\meta tags {quickcontrols, layout, styling, treeview}
- \title Modern File System Explorer
+ \title File System Explorer
\ingroup qtquickcontrols-examples
- \brief A QML app utilizing customized Qt Quick Controls to display text files from a filesystem.
-
- In this example, a modern layout is used that consists of three major components. There is an
- icon-based \e {Sidebar} to the left, followed by a resizable TreeView displaying the file system
- from a QFileSystemModel, and finally the TextArea displaying the selected text files.
- There is a common look and feel across all operating systems. We accomplish this by using
- customized quick controls and frameless windows, with our own window decorations.
-
- \image qtquickcontrols-filesystemexplorer.jpg
+ \brief A QML app utilizing customized Qt Quick Controls to display text
+ files from a filesystem.
+
+ In this example, a modern layout is used that consists of three major
+ components. There is an icon-based \e {Sidebar} to the left, followed by a
+ resizable TreeView displaying the file system from a QFileSystemModel, and
+ finally the TextArea displaying the selected text files. There is a common
+ look and feel across all operating systems. We accomplish this by using
+ customized quick controls and frameless windows, with our own window
+ decorations. When launching this application from the command-line, you
+ have the option to provide an initial directory as a parameter. This
+ initial directory will be used by the TreeView to set the starting point
+ for displaying the directory structure.
+
+ \image qtquickcontrols-filesystemexplorer.webp
\include examples-run.qdocinc
- \section1 Modern layout and colors
+ \section1 Modern layout and structure
- To begin with, we are providing the colors throughout a singleton QML object. In this way,
- we can provide more structured control over the appearance of the application.
+ To begin with, we are providing the colors throughout a singleton QML
+ object. In this way, we can provide more structured control over the
+ appearance of the application.
\quotefromfile filesystemexplorer/qml/Colors.qml
\skipto pragma
\printuntil }
- Since we do not want to rely on the operating system's window decoration and instead want to
- provide our own, we use the \c FramelessWindowHint flag inside the ApplicationWindow.
- In order to achieve an equivalent interaction with the window, we override the \c background
- property of our customized MenuBar and display some information text as well as interaction
- possibilities for dragging or closing the application. \l {Inline Components} have been used
- to simplify this process.
+ Since we do not want to rely on the operating system's window decoration
+ and instead want to provide our own, we use the \c FramelessWindowHint flag
+ inside the ApplicationWindow. In order to achieve an equivalent interaction
+ with the window, we override the \c contentItem property of our customized
+ MenuBar and display some information text as well as interaction
+ possibilities for dragging or closing the application. \l {Inline
+ Components} have been used to simplify this process.
\quotefromfile filesystemexplorer/qml/MyMenuBar.qml
- \skipto /^\s{4}background: Rectangle {$/
- \printto InteractionButton {
+ \skipto component InteractionButton
+ \printuntil id: maximize
+ \dots
- The \e {Sidebar} on the left includes checkable navigation buttons on top and one-shot buttons on
- the bottom. A ButtonGroup and a Container are used to ensure that only one entry is active at
- any given time. It is then possible to provide different views using a property alias for the
- current position, along with a StackLayout.
+ The \e {Sidebar} on the left includes checkable navigation buttons on top
+ and one-shot buttons on the bottom. A ButtonGroup and a Container are used
+ to ensure that only one entry is active at any given time. It is then
+ possible to provide different views using a property alias for the current
+ position, along with a StackLayout.
- This technique allows us to simply extend the functionality by adding another button and the
- corresponding element inside the StackLayout.
+ This technique allows us to simply extend the functionality by adding
+ another button and the corresponding element inside the StackLayout.
\quotefromfile filesystemexplorer/Main.qml
\skipto StackLayout {
- \printuntil /^\s{20}\}$/
+ \printuntil /^\s{16}\}$/
- The StackLayout includes, besides some simple information text, the \e {FileSystemView}. This
- custom component displays files and folders and populates it with data from a
- \l {Using C++ Models with Qt Quick Views}{C++ model}. We can then select the files and
- read them accordingly.
+ The StackLayout includes, besides some information text, the \e
+ {FileSystemView}. This custom component displays files and folders and
+ populates it with data from a \l {Using C++ Models with Qt Quick Views}{C++
+ model}. We can then select the files and read them accordingly.
\quotefromfile filesystemexplorer/filesystemmodel.cpp
\skipto readFile
\printuntil /^\s{0}\}$/
- By using a SplitView, we are able to dynamically share the space between the StackLayout and
- the ScrollView. Our ScrollView contains an embedded TextArea that displays the opened file. Using
- this method, we can resize the view vertically and enable scrolling within the TextArea.
+ By right-clicking on a folder in the TreeView, a popup Menu is opened,
+ which allows control over the \c rootIndex property of the TreeView.
+
+ \quotefromfile filesystemexplorer/qml/FileSystemView.qml
+ \skipto MyMenu
+ \printuntil /^\s{8}\}$/
+
+ By using a SplitView, we are able to dynamically share the space between
+ the StackLayout and the Editor. Our Editor contains the TextArea that
+ displays the opened file and provides us with all the functionality needed
+ to edit text files. Additionally, we provide a visualization of the line
+ numbers, which can be toggled on and off in the Menu.
\quotefromfile filesystemexplorer/Main.qml
- \skipuntil SplitView
- \dots
- \skipto ScrollView {
- \printuntil /^\s{16}\}$/
+ \skipto Editor {
+ \printuntil /^\s{12}\}$/
\section1 Custom components
@@ -75,17 +92,32 @@
\l {Customizing a Control} {this} article first. We are using reusable and
customized components throughout this example.
- For example, the \e {MyMenu} component customizes Menu's \c background property
- as well as its delegates' \c contentItem and \c background properties.
+ For instance, the \e {MyMenu} component customizes Menu's \c background
+ property as well as its delegates' \c contentItem and \c background
+ properties.
\quotefile filesystemexplorer/qml/MyMenu.qml
- Another example is the customization of the ScrollIndicator inside
- the \e {FileSystemView}, which additionally uses customized animations. Here we
+ Another example is the customization of the ScrollIndicator inside the \e
+ {FileSystemView}, which additionally uses customized animations. Here we
also override the \c contentItem.
\quotefromfile filesystemexplorer/qml/FileSystemView.qml
\skipto ScrollIndicator.vertical
\printuntil /^\s{8}\}$/
+ \section1 Python version
+
+ If you're interested in the Python version of this example, you can find it
+ \l{https://doc.qt.io/qtforpython-6/examples/example_quickcontrols_filesystemexplorer.html}
+ {here}. This showcases the usage of Qt for Python and demonstrates how it
+ can be used to create the same application.
+
+ Additionally, there is a detailed
+ \l {https://doc.qt.io/qtforpython-6/tutorials/extendedexplorer/extendedexplorer.html}
+ {tutorial} available that provides step-by-step instructions on how to
+ extend this example with additional features. This tutorial can be helpful
+ if you want to explore and learn more about building upon the existing
+ functionality of the filesystem explorer.
*/
+
diff --git a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pro b/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pro
index 125f2c1a56..3748d9577b 100644
--- a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pro
+++ b/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pro
@@ -1,7 +1,7 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-QT += quick quickcontrols2
+QT += quick
CONFIG += qmltypes
@@ -15,16 +15,18 @@ TEMPLATE = app
SOURCES += \
main.cpp \
filesystemmodel.cpp \
+ linenumbermodel.cpp \
HEADERS += \
- filesystemmodel.h
+ filesystemmodel.h \
+ linenumbermodel.h \
qml_resources.files = \
qmldir \
Main.qml \
- qml/Icon.qml \
qml/About.qml \
qml/Colors.qml \
+ qml/Editor.qml \
qml/MyMenu.qml \
qml/Sidebar.qml \
qml/MyMenuBar.qml \
@@ -44,6 +46,7 @@ theme_resources.files = \
icons/read.svg \
icons/resize.svg \
icons/qt_logo.svg \
+ icons/app_icon.svg
theme_resources.prefix = /qt/qml/FileSystemModule
diff --git a/examples/quickcontrols/filesystemexplorer/filesystemmodel.cpp b/examples/quickcontrols/filesystemexplorer/filesystemmodel.cpp
index e383a0ea29..b580bced05 100644
--- a/examples/quickcontrols/filesystemexplorer/filesystemmodel.cpp
+++ b/examples/quickcontrols/filesystemexplorer/filesystemmodel.cpp
@@ -3,18 +3,15 @@
#include "filesystemmodel.h"
-#include <QMimeDatabase>
#include <QStandardPaths>
+#include <QMimeDatabase>
+#include <QTextDocument>
+#include <QTextObject>
FileSystemModel::FileSystemModel(QObject *parent) : QFileSystemModel(parent)
{
- setRootPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
-}
-
-int FileSystemModel::columnCount(const QModelIndex &parent) const
-{
- Q_UNUSED(parent);
- return 1;
+ setFilter(QDir::AllEntries | QDir::Hidden | QDir::NoDotAndDotDot);
+ setInitialDirectory();
}
QString FileSystemModel::readFile(const QString &filePath)
@@ -46,3 +43,47 @@ QString FileSystemModel::readFile(const QString &filePath)
}
return tr("Filetype not supported!");
}
+
+// This function gets called from Editor.qml
+int FileSystemModel::currentLineNumber(QQuickTextDocument *textDocument, int cursorPosition)
+{
+ if (QTextDocument *td = textDocument->textDocument()) {
+ QTextBlock tb = td->findBlock(cursorPosition);
+ return tb.blockNumber();
+ }
+ return -1;
+}
+
+int FileSystemModel::columnCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent)
+ return 1;
+}
+
+QModelIndex FileSystemModel::rootIndex() const
+{
+ return m_rootIndex;
+}
+
+void FileSystemModel::setRootIndex(const QModelIndex index)
+{
+ if (index == m_rootIndex)
+ return;
+ m_rootIndex = index;
+ emit rootIndexChanged();
+}
+
+void FileSystemModel::setInitialDirectory(const QString &path)
+{
+ QDir dir(path);
+ if (dir.makeAbsolute())
+ setRootPath(dir.path());
+ else
+ setRootPath(getDefaultRootDir());
+ setRootIndex(QFileSystemModel::index(dir.path(), 0));
+}
+
+QString FileSystemModel::getDefaultRootDir()
+{
+ return QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
+}
diff --git a/examples/quickcontrols/filesystemexplorer/filesystemmodel.h b/examples/quickcontrols/filesystemexplorer/filesystemmodel.h
index 24f3d658b2..c79af4aa82 100644
--- a/examples/quickcontrols/filesystemexplorer/filesystemmodel.h
+++ b/examples/quickcontrols/filesystemexplorer/filesystemmodel.h
@@ -5,18 +5,36 @@
#define FILESYSTEMMODEL_H
#include <QFileSystemModel>
-#include <QtQml/qqml.h>
+#include <QQuickTextDocument>
class FileSystemModel : public QFileSystemModel
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
-
+ Q_PROPERTY(QModelIndex rootIndex READ rootIndex WRITE setRootIndex NOTIFY rootIndexChanged)
public:
explicit FileSystemModel(QObject *parent = nullptr);
- int columnCount(const QModelIndex &parent) const override;
+
+ // Functions invokable from QML
Q_INVOKABLE QString readFile(const QString &filePath);
+ Q_INVOKABLE int currentLineNumber(QQuickTextDocument *textDocument, int cursorPosition);
+
+ // Overridden functions
+ int columnCount(const QModelIndex &parent) const override;
+
+ // Member functions from here
+ QModelIndex rootIndex() const;
+ void setRootIndex(const QModelIndex index);
+ void setInitialDirectory(const QString &path = getDefaultRootDir());
+
+ static QString getDefaultRootDir();
+
+signals:
+ void rootIndexChanged();
+
+private:
+ QModelIndex m_rootIndex;
};
#endif
diff --git a/examples/quickcontrols/filesystemexplorer/icons/app_icon.svg b/examples/quickcontrols/filesystemexplorer/icons/app_icon.svg
new file mode 100644
index 0000000000..5aae4221f4
--- /dev/null
+++ b/examples/quickcontrols/filesystemexplorer/icons/app_icon.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#EBDBB2" d="M13.25 8.5a.75.75 0 1 1-.75-.75.75.75 0 0 1 .75.75zM9.911 21.35l.816.578C10.819 21.798 13 18.666 13 13h-1a15.503 15.503 0 0 1-2.089 8.35zM4 6.703V10a2.002 2.002 0 0 1-2 2v1a2.002 2.002 0 0 1 2 2v3.297A3.707 3.707 0 0 0 7.703 22H9v-1H7.703A2.706 2.706 0 0 1 5 18.297V15a2.999 2.999 0 0 0-1.344-2.5A2.999 2.999 0 0 0 5 10V6.703A2.706 2.706 0 0 1 7.703 4H9V3H7.703A3.707 3.707 0 0 0 4 6.703zM20 10V6.703A3.707 3.707 0 0 0 16.297 3H15v1h1.297A2.706 2.706 0 0 1 19 6.703V10a2.999 2.999 0 0 0 1.344 2.5A2.999 2.999 0 0 0 19 15v3.297A2.706 2.706 0 0 1 16.297 21H15v1h1.297A3.707 3.707 0 0 0 20 18.297V15a2.002 2.002 0 0 1 2-2v-1a2.002 2.002 0 0 1-2-2z"/><path fill="none" d="M0 0h24v24H0z"/></svg>
diff --git a/examples/quickcontrols/filesystemexplorer/linenumbermodel.cpp b/examples/quickcontrols/filesystemexplorer/linenumbermodel.cpp
new file mode 100644
index 0000000000..5a7982dca9
--- /dev/null
+++ b/examples/quickcontrols/filesystemexplorer/linenumbermodel.cpp
@@ -0,0 +1,58 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "linenumbermodel.h"
+
+#include <QQmlInfo>
+
+/*!
+ When using an integer model based on the line count of the editor,
+ any changes in that line count cause all delegates to be destroyed
+ and recreated. That's inefficient, so instead, we add/remove model
+ items as necessary ourselves, based on the lineCount property.
+*/
+LineNumberModel::LineNumberModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+}
+
+int LineNumberModel::lineCount() const
+{
+ return m_lineCount;
+}
+
+void LineNumberModel::setLineCount(int lineCount)
+{
+ if (lineCount < 0) {
+ qmlWarning(this) << "lineCount must be greater than zero";
+ return;
+ }
+
+ if (m_lineCount == lineCount)
+ return;
+
+ if (m_lineCount < lineCount) {
+ beginInsertRows(QModelIndex(), m_lineCount, lineCount - 1);
+ m_lineCount = lineCount;
+ endInsertRows();
+ } else if (m_lineCount > lineCount) {
+ beginRemoveRows(QModelIndex(), lineCount, m_lineCount - 1);
+ m_lineCount = lineCount;
+ endRemoveRows();
+ }
+
+ emit lineCountChanged();
+}
+
+int LineNumberModel::rowCount(const QModelIndex &) const
+{
+ return m_lineCount;
+}
+
+QVariant LineNumberModel::data(const QModelIndex &index, int role) const
+{
+ if (!checkIndex(index) || role != Qt::DisplayRole)
+ return QVariant();
+
+ return index.row();
+}
diff --git a/examples/quickcontrols/filesystemexplorer/linenumbermodel.h b/examples/quickcontrols/filesystemexplorer/linenumbermodel.h
new file mode 100644
index 0000000000..1ec800ffd8
--- /dev/null
+++ b/examples/quickcontrols/filesystemexplorer/linenumbermodel.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef LINENUMBERMODEL_H
+#define LINENUMBERMODEL_H
+
+#include <QAbstractItemModel>
+#include <QQmlEngine>
+
+class LineNumberModel : public QAbstractListModel
+{
+ Q_OBJECT
+ QML_ELEMENT
+ Q_PROPERTY(int lineCount READ lineCount WRITE setLineCount NOTIFY lineCountChanged)
+
+public:
+ explicit LineNumberModel(QObject *parent = nullptr);
+
+ int lineCount() const;
+ void setLineCount(int lineCount);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+signals:
+ void lineCountChanged();
+
+private:
+ int m_lineCount = 0;
+};
+
+#endif // LINENUMBERMODEL_H
diff --git a/examples/quickcontrols/filesystemexplorer/main.cpp b/examples/quickcontrols/filesystemexplorer/main.cpp
index e815704f83..9a43fa9d6d 100644
--- a/examples/quickcontrols/filesystemexplorer/main.cpp
+++ b/examples/quickcontrols/filesystemexplorer/main.cpp
@@ -1,19 +1,43 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+#include "filesystemmodel.h"
+
#include <QGuiApplication>
+#include <QCommandLineParser>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
+ // Initialize the static application object.
QGuiApplication app(argc, argv);
QGuiApplication::setOrganizationName("QtProject");
QGuiApplication::setApplicationName("File System Explorer");
+ QGuiApplication::setApplicationVersion(QT_VERSION_STR);
+ QGuiApplication::setWindowIcon(QIcon(":/qt/qml/FileSystemModule/icons/app_icon.svg"));
+
+ // Setup the parser and parse the command-line arguments.
+ QCommandLineParser parser;
+ parser.setApplicationDescription("Qt Filesystemexplorer Example");
+ parser.addHelpOption();
+ parser.addVersionOption();
+ parser.addPositionalArgument("", QGuiApplication::translate(
+ "main", "Initial directory"),"[path]");
+ parser.process(app);
+ const auto args = parser.positionalArguments();
+ // Load the QML entry point.
QQmlApplicationEngine engine;
engine.loadFromModule("FileSystemModule", "Main");
if (engine.rootObjects().isEmpty())
return -1;
- return app.exec();
+ // Set the initial directory if provided
+ if (args.length() == 1) {
+ auto *fileSystemModel = engine.singletonInstance<FileSystemModel*>(
+ "FileSystemModule","FileSystemModel");
+ fileSystemModel->setInitialDirectory(args[0]);
+ }
+
+ return QGuiApplication::exec(); // Start the event loop.
}
diff --git a/examples/quickcontrols/filesystemexplorer/qml/About.qml b/examples/quickcontrols/filesystemexplorer/qml/About.qml
index b7bc0ac6f3..f247cb20d9 100644
--- a/examples/quickcontrols/filesystemexplorer/qml/About.qml
+++ b/examples/quickcontrols/filesystemexplorer/qml/About.qml
@@ -14,20 +14,25 @@ ApplicationWindow {
menuBar: MyMenuBar {
id: menuBar
- implicitHeight: 20
- rootWindow: root
+
+ dragWindow: root
+ implicitHeight: 30
infoText: "About Qt"
}
Image {
id: logo
+
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 20
+
source: "../icons/qt_logo.svg"
- sourceSize: Qt.size(80, 80)
+ sourceSize.width: 80
+ sourceSize.height: 80
fillMode: Image.PreserveAspectFit
+
smooth: true
antialiasing: true
asynchronous: true
@@ -39,21 +44,26 @@ ApplicationWindow {
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 20
- antialiasing: true
- wrapMode: Text.WrapAnywhere
- color: Colors.textFile
+
+ selectedTextColor: Colors.textFile
+ selectionColor: Colors.selection
horizontalAlignment: Text.AlignHCenter
+ text: qsTr("Qt Group (Nasdaq Helsinki: QTCOM) is a global software company with a strong"
+ + "presence in more than 70 industries and is the leading independent technology"
+ + "behind 1+ billion devices and applications. Qt is used by major global"
+ + "companiesand developers worldwide, and the technology enables its customers to"
+ + "deliver exceptional user experiences and advance their digital transformation"
+ + "initiatives. Qt achieves this through its cross-platform software framework for"
+ + "the development of apps and devices, under both commercial and"
+ + "open-source licenses.")
+ color: Colors.textFile
+ wrapMode: Text.WrapAnywhere
readOnly: true
- selectionColor: Colors.selection
- text: qsTr("Qt Group (Nasdaq Helsinki: QTCOM) is a global software company with a strong \
-presence in more than 70 industries and is the leading independent technology behind 1+ billion \
-devices and applications. Qt is used by major global companies and developers worldwide, and the \
-technology enables its customers to deliver exceptional user experiences and advance their digital \
-transformation initiatives. Qt achieves this through its cross-platform software framework for the \
-development of apps and devices, under both commercial and open-source licenses.")
- background: Rectangle {
- color: "transparent"
- }
+ antialiasing: true
+ background: null
+ }
+
+ ResizeButton {
+ resizeWindow: root
}
- ResizeButton {}
}
diff --git a/examples/quickcontrols/filesystemexplorer/qml/Colors.qml b/examples/quickcontrols/filesystemexplorer/qml/Colors.qml
index 280f892864..2856677738 100644
--- a/examples/quickcontrols/filesystemexplorer/qml/Colors.qml
+++ b/examples/quickcontrols/filesystemexplorer/qml/Colors.qml
@@ -1,22 +1,23 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-pragma Singleton
import QtQuick
+pragma Singleton
+
QtObject {
- readonly property color background: "#23272E"
- readonly property color surface1: "#1E2227"
+ readonly property color background: "#292828"
+ readonly property color surface1: "#171819"
readonly property color surface2: "#090A0C"
- readonly property color text: "#ABB2BF"
- readonly property color textFile: "#C5CAD3"
- readonly property color disabledText: "#454D5F"
- readonly property color selection: "#2C313A"
- readonly property color active: "#23272E"
- readonly property color inactive: "#3E4452"
- readonly property color folder: "#3D4451"
- readonly property color icon: "#3D4451"
- readonly property color iconIndicator: "#E5C07B"
- readonly property color color1: "#E06B74"
- readonly property color color2: "#62AEEF"
+ readonly property color text: "#D4BE98"
+ readonly property color textFile: "#E1D2B7"
+ readonly property color disabledText: "#2C313A"
+ readonly property color selection: "#4B4A4A"
+ readonly property color active: "#292828"
+ readonly property color inactive: "#383737"
+ readonly property color folder: "#383737"
+ readonly property color icon: "#383737"
+ readonly property color iconIndicator: "#D5B35D"
+ readonly property color color1: "#A7B464"
+ readonly property color color2: "#D3869B"
}
diff --git a/examples/quickcontrols/filesystemexplorer/qml/Editor.qml b/examples/quickcontrols/filesystemexplorer/qml/Editor.qml
new file mode 100644
index 0000000000..80f7c04c57
--- /dev/null
+++ b/examples/quickcontrols/filesystemexplorer/qml/Editor.qml
@@ -0,0 +1,160 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import FileSystemModule
+
+pragma ComponentBehavior: Bound
+
+// This is the text editor that displays the currently open file, including
+// their corresponding line numbers.
+Rectangle {
+ id: root
+
+ required property string currentFilePath
+ required property bool showLineNumbers
+ property alias text: textArea
+ property int currentLineNumber: -1
+ property int rowHeight: Math.ceil(fontMetrics.lineSpacing)
+
+ color: Colors.background
+
+ onWidthChanged: textArea.update()
+ onHeightChanged: textArea.update()
+
+ RowLayout {
+ anchors.fill: parent
+ // We use a flickable to synchronize the position of the editor and
+ // the line numbers. This is necessary because the line numbers can
+ // extend the available height.
+ Flickable {
+ id: lineNumbers
+
+ // Calculate the width based on the logarithmic scale.
+ Layout.preferredWidth: fontMetrics.averageCharacterWidth
+ * (Math.floor(Math.log10(textArea.lineCount)) + 1) + 10
+ Layout.fillHeight: true
+
+ interactive: false
+ contentY: editorFlickable.contentY
+ visible: textArea.text !== "" && root.showLineNumbers
+
+ Column {
+ anchors.fill: parent
+ Repeater {
+ id: repeatedLineNumbers
+
+ model: LineNumberModel {
+ lineCount: textArea.text !== "" ? textArea.lineCount : 0
+ }
+
+ delegate: Item {
+ required property int index
+
+ width: parent.width
+ height: root.rowHeight
+ Label {
+ id: numbers
+
+ text: parent.index + 1
+
+ width: parent.width
+ height: parent.height
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+
+ color: (root.currentLineNumber === parent.index)
+ ? Colors.iconIndicator : Qt.darker(Colors.text, 2)
+ font: textArea.font
+ }
+ Rectangle {
+ id: indicator
+
+ anchors.left: numbers.right
+ width: 1
+ height: parent.height
+ color: Qt.darker(Colors.text, 3)
+ }
+ }
+ }
+ }
+ }
+
+ Flickable {
+ id: editorFlickable
+
+ property alias textArea: textArea
+
+ // We use an inline component to customize the horizontal and vertical
+ // scroll-bars. This is convenient when the component is only used in one file.
+ component MyScrollBar: ScrollBar {
+ id: scrollBar
+ background: Rectangle {
+ implicitWidth: scrollBar.interactive ? 8 : 4
+ implicitHeight: scrollBar.interactive ? 8 : 4
+
+ opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0
+ color: Colors.background
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: 500
+ }
+ }
+ }
+ contentItem: Rectangle {
+ implicitWidth: scrollBar.interactive ? 8 : 4
+ implicitHeight: scrollBar.interactive ? 8 : 4
+ opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0
+ color: Colors.color1
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: 1000
+ }
+ }
+ }
+ }
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ ScrollBar.horizontal: MyScrollBar {}
+ ScrollBar.vertical: MyScrollBar {}
+
+ boundsBehavior: Flickable.StopAtBounds
+
+ TextArea.flickable: TextArea {
+ id: textArea
+ anchors.fill: parent
+
+ focus: false
+ topPadding: 0
+ leftPadding: 10
+
+ text: FileSystemModel.readFile(root.currentFilePath)
+ tabStopDistance: fontMetrics.averageCharacterWidth * 4
+
+ // Grab the current line number from the C++ interface.
+ onCursorPositionChanged: {
+ root.currentLineNumber = FileSystemModel.currentLineNumber(
+ textArea.textDocument, textArea.cursorPosition)
+ }
+
+ color: Colors.textFile
+ selectedTextColor: Colors.textFile
+ selectionColor: Colors.selection
+
+ textFormat: TextEdit.PlainText
+ renderType: Text.QtRendering
+ selectByMouse: true
+ antialiasing: true
+ background: null
+ }
+
+ FontMetrics {
+ id: fontMetrics
+ font: textArea.font
+ }
+ }
+ }
+}
diff --git a/examples/quickcontrols/filesystemexplorer/qml/FileSystemView.qml b/examples/quickcontrols/filesystemexplorer/qml/FileSystemView.qml
index ade2e48c1f..c25d518b4e 100644
--- a/examples/quickcontrols/filesystemexplorer/qml/FileSystemView.qml
+++ b/examples/quickcontrols/filesystemexplorer/qml/FileSystemView.qml
@@ -2,26 +2,31 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
-import QtQuick.Layouts
+import QtQuick.Effects
import QtQuick.Controls.Basic
import FileSystemModule
+pragma ComponentBehavior: Bound
+
// This is the file system view which gets populated by the C++ model.
Rectangle {
id: root
signal fileClicked(string filePath)
+ property alias rootIndex: fileSystemTreeView.rootIndex
TreeView {
id: fileSystemTreeView
+
+ property int lastIndex: -1
+
anchors.fill: parent
model: FileSystemModel
+ rootIndex: FileSystemModel.rootIndex
boundsBehavior: Flickable.StopAtBounds
boundsMovement: Flickable.StopAtBounds
clip: true
- property int lastIndex: -1
-
Component.onCompleted: fileSystemTreeView.toggleExpanded(0)
// The delegate represents a single entry in the filesystem.
@@ -31,50 +36,92 @@ Rectangle {
implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250
implicitHeight: 25
+ // Since we have the 'ComponentBehavior Bound' pragma, we need to
+ // require these properties from our model. This is a convenient way
+ // to bind the properties provided by the model's role names.
required property int index
required property url filePath
+ required property string fileName
- indicator: null
-
- contentItem: Item {
- anchors.fill: parent
+ indicator: Image {
+ id: directoryIcon
- Icon {
- id: directoryIcon
- x: leftMargin + (depth * indentation)
- anchors.verticalCenter: parent.verticalCenter
- path: treeDelegate.hasChildren
- ? (treeDelegate.expanded ? "../icons/folder_open.svg" : "../icons/folder_closed.svg")
+ x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation)
+ anchors.verticalCenter: parent.verticalCenter
+ source: treeDelegate.hasChildren ? (treeDelegate.expanded
+ ? "../icons/folder_open.svg" : "../icons/folder_closed.svg")
: "../icons/generic_file.svg"
- iconColor: (treeDelegate.expanded && treeDelegate.hasChildren) ? Colors.color2 : Colors.folder
- }
- Text {
- anchors.left: directoryIcon.right
- anchors.verticalCenter: parent.verticalCenter
- width: parent.width
- text: model.fileName
- color: Colors.text
- }
+ sourceSize.width: 20
+ sourceSize.height: 20
+ fillMode: Image.PreserveAspectFit
+
+ smooth: true
+ antialiasing: true
+ asynchronous: true
+ }
+
+ contentItem: Text {
+ text: treeDelegate.fileName
+ color: Colors.text
}
background: Rectangle {
- color: treeDelegate.index === fileSystemTreeView.lastIndex
+ color: (treeDelegate.index === fileSystemTreeView.lastIndex)
? Colors.selection
: (hoverHandler.hovered ? Colors.active : "transparent")
}
- TapHandler {
- onSingleTapped: {
- fileSystemTreeView.toggleExpanded(row)
- fileSystemTreeView.lastIndex = index
- // If this model item doesn't have children, it means it's representing a file.
- if (!treeDelegate.hasChildren)
- root.fileClicked(filePath)
- }
+ // We color the directory icons with this MultiEffect, where we overlay
+ // the colorization color ontop of the SVG icons.
+ MultiEffect {
+ id: iconOverlay
+
+ anchors.fill: directoryIcon
+ source: directoryIcon
+
+ colorizationColor: (treeDelegate.expanded && treeDelegate.hasChildren)
+ ? Colors.color2 : Colors.folder
+ colorization: 1.0
+ brightness: 1.0
}
+
HoverHandler {
id: hoverHandler
}
+
+ TapHandler {
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onSingleTapped: (eventPoint, button) => {
+ switch (button) {
+ case Qt.LeftButton:
+ fileSystemTreeView.toggleExpanded(treeDelegate.row)
+ fileSystemTreeView.lastIndex = treeDelegate.index
+ // If this model item doesn't have children, it means it's
+ // representing a file.
+ if (!treeDelegate.hasChildren)
+ root.fileClicked(treeDelegate.filePath)
+ break;
+ case Qt.RightButton:
+ if (treeDelegate.hasChildren)
+ contextMenu.popup();
+ break;
+ }
+ }
+ }
+
+ MyMenu {
+ id: contextMenu
+ Action {
+ text: qsTr("Set as root index")
+ onTriggered: {
+ fileSystemTreeView.rootIndex = fileSystemTreeView.index(treeDelegate.row, 0)
+ }
+ }
+ Action {
+ text: qsTr("Reset root index")
+ onTriggered: fileSystemTreeView.rootIndex = undefined
+ }
+ }
}
// Provide our own custom ScrollIndicator for the TreeView.
@@ -85,6 +132,7 @@ Rectangle {
contentItem: Rectangle {
implicitWidth: 6
implicitHeight: 6
+
color: Colors.color1
opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0
diff --git a/examples/quickcontrols/filesystemexplorer/qml/Icon.qml b/examples/quickcontrols/filesystemexplorer/qml/Icon.qml
deleted file mode 100644
index 25162d9d3f..0000000000
--- a/examples/quickcontrols/filesystemexplorer/qml/Icon.qml
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Effects
-
-// Custom Component for displaying Icons
-Item {
- id: root
-
- required property url path
- property real padding: 5
- property real size: 30
- property alias iconColor: overlay.colorizationColor
- property alias hovered: mouse.hovered
-
- width: size
- height: size
-
- Image {
- id: icon
- anchors.fill: root
- anchors.margins: padding
- source: path
- sourceSize: Qt.size(size, size)
- fillMode: Image.PreserveAspectFit
- smooth: true
- antialiasing: true
- asynchronous: true
- }
-
- MultiEffect {
- id: overlay
- anchors.fill: icon
- source: icon
- colorization: 1.0
- brightness: 1.0
- }
-
- HoverHandler {
- id: mouse
- acceptedDevices: PointerDevice.Mouse
- }
-}
diff --git a/examples/quickcontrols/filesystemexplorer/qml/MyMenu.qml b/examples/quickcontrols/filesystemexplorer/qml/MyMenu.qml
index 99795b5e53..1f1d30c566 100644
--- a/examples/quickcontrols/filesystemexplorer/qml/MyMenu.qml
+++ b/examples/quickcontrols/filesystemexplorer/qml/MyMenu.qml
@@ -8,35 +8,38 @@ import FileSystemModule
Menu {
id: root
- background: Rectangle {
- implicitWidth: 200
- implicitHeight: 40
- color: Colors.surface2
- }
-
delegate: MenuItem {
id: menuItem
- implicitWidth: 200
- implicitHeight: 40
contentItem: Item {
Text {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 5
+
text: menuItem.text
color: enabled ? Colors.text : Colors.disabledText
}
Rectangle {
+ id: indicator
+
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
width: 6
height: parent.height
+
visible: menuItem.highlighted
color: Colors.color2
}
}
background: Rectangle {
+ implicitWidth: 210
+ implicitHeight: 35
color: menuItem.highlighted ? Colors.active : "transparent"
}
}
+ background: Rectangle {
+ implicitWidth: 210
+ implicitHeight: 35
+ color: Colors.surface2
+ }
}
diff --git a/examples/quickcontrols/filesystemexplorer/qml/MyMenuBar.qml b/examples/quickcontrols/filesystemexplorer/qml/MyMenuBar.qml
index a2a3fea886..4874a2c03f 100644
--- a/examples/quickcontrols/filesystemexplorer/qml/MyMenuBar.qml
+++ b/examples/quickcontrols/filesystemexplorer/qml/MyMenuBar.qml
@@ -6,130 +6,172 @@ import QtQuick.Layouts
import QtQuick.Controls.Basic
import FileSystemModule
-// The MenuBar also serves as a controller for our Window as we don't use any decorations.
+// The MenuBar also serves as a controller for our window as we don't use any decorations.
MenuBar {
id: root
- required property ApplicationWindow rootWindow
+ required property ApplicationWindow dragWindow
property alias infoText: windowInfo.text
- implicitHeight: 25
-
- // The top level menus on the left side
+ // Customization of the top level menus inside the MenuBar
delegate: MenuBarItem {
id: menuBarItem
- implicitHeight: 25
contentItem: Text {
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
- color: menuBarItem.highlighted ? Colors.textFile : Colors.text
- opacity: enabled ? 1.0 : 0.3
+
text: menuBarItem.text
- elide: Text.ElideRight
font: menuBarItem.font
+ elide: Text.ElideRight
+ color: menuBarItem.highlighted ? Colors.textFile : Colors.text
+ opacity: enabled ? 1.0 : 0.3
}
background: Rectangle {
+ id: background
+
color: menuBarItem.highlighted ? Colors.selection : "transparent"
Rectangle {
id: indicator
+
width: 0; height: 3
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
- color: Colors.color1
+ color: Colors.color1
states: State {
- name: "active"; when: menuBarItem.highlighted
- PropertyChanges { target: indicator; width: parent.width }
+ name: "active"
+ when: menuBarItem.highlighted
+ PropertyChanges {
+ indicator.width: background.width - 2
+ }
}
-
transitions: Transition {
NumberAnimation {
properties: "width"
- duration: 300
+ duration: 175
}
}
-
}
}
}
+ // We use the contentItem property as a place to attach our window decorations. Beneath
+ // the usual menu entries within a MenuBar, it includes a centered information text, along
+ // with the minimize, maximize, and close buttons.
+ contentItem: RowLayout {
+ id: windowBar
- // The background property contains an information text in the middle as well as the
- // Minimize, Maximize and Close Buttons.
- background: Rectangle {
- color: Colors.surface2
- // Make the empty space drag the specified root window.
- WindowDragHandler { dragWindow: rootWindow }
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ spacing: root.spacing
+ Repeater {
+ id: menuBarItems
- Text {
- id: windowInfo
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
- color: Colors.text
+ Layout.alignment: Qt.AlignLeft
+ model: root.contentModel
}
- component InteractionButton: Rectangle {
- signal action;
- property alias hovered: hoverHandler.hovered
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Text {
+ id: windowInfo
+
+ width: parent.width; height: parent.height
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ leftPadding: windowActions.width
+ color: Colors.text
+ clip: true
+ }
+ }
- width: root.height
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- color: hovered ? Colors.background : "transparent"
+ RowLayout {
+ id: windowActions
- HoverHandler { id: hoverHandler }
- TapHandler { onTapped: action() }
- }
+ Layout.alignment: Qt.AlignRight
+ Layout.fillHeight: true
- InteractionButton {
- id: minimize
+ spacing: 0
- anchors.right: maximize.left
- onAction: rootWindow.showMinimized()
- Rectangle {
- width: parent.height - 10; height: 2
- anchors.centerIn: parent
- color: parent.hovered ? Colors.iconIndicator : Colors.icon
+ component InteractionButton: Rectangle {
+ id: interactionButton
+
+ signal action()
+ property alias hovered: hoverHandler.hovered
+
+ Layout.fillHeight: true
+ Layout.preferredWidth: height
+
+ color: hovered ? Colors.background : "transparent"
+ HoverHandler {
+ id: hoverHandler
+ }
+ TapHandler {
+ id: tapHandler
+ onTapped: interactionButton.action()
+ }
}
- }
- InteractionButton {
- id: maximize
+ InteractionButton {
+ id: minimize
- anchors.right: close.left
- onAction: rootWindow.showMaximized()
- Rectangle {
- anchors.fill: parent
- anchors.margins: 5
- border.width: 2
- color: "transparent"
- border.color: parent.hovered ? Colors.iconIndicator : Colors.icon
+ onAction: root.dragWindow.showMinimized()
+ Rectangle {
+ anchors.centerIn: parent
+ color: parent.hovered ? Colors.iconIndicator : Colors.icon
+ height: 2
+ width: parent.height - 14
+ }
}
- }
- InteractionButton {
- id: close
+ InteractionButton {
+ id: maximize
- color: hovered ? "#ec4143" : "transparent"
- anchors.right: parent.right
- onAction: rootWindow.close()
- Rectangle {
- width: parent.height - 8; height: 2
- anchors.centerIn: parent
- color: parent.hovered ? Colors.iconIndicator : Colors.icon
- rotation: 45
- transformOrigin: Item.Center
- antialiasing: true
+ onAction: root.dragWindow.showMaximized()
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: 7
+ border.color: parent.hovered ? Colors.iconIndicator : Colors.icon
+ border.width: 2
+ color: "transparent"
+ }
+ }
+
+ InteractionButton {
+ id: close
+
+ color: hovered ? "#ec4143" : "transparent"
+ onAction: root.dragWindow.close()
Rectangle {
- width: parent.height
- height: parent.width
anchors.centerIn: parent
- color: parent.color
+ width: parent.height - 8; height: 2
+
+ rotation: 45
antialiasing: true
+ transformOrigin: Item.Center
+ color: parent.hovered ? Colors.iconIndicator : Colors.icon
+
+ Rectangle {
+ anchors.centerIn: parent
+ width: parent.height
+ height: parent.width
+
+ antialiasing: true
+ color: parent.color
+ }
}
}
}
}
+ background: Rectangle {
+ color: Colors.surface2
+ // Make the empty space drag the specified root window.
+ WindowDragHandler {
+ dragWindow: root.dragWindow
+ }
+ }
}
diff --git a/examples/quickcontrols/filesystemexplorer/qml/ResizeButton.qml b/examples/quickcontrols/filesystemexplorer/qml/ResizeButton.qml
index eb2e5bc027..0df65bf82f 100644
--- a/examples/quickcontrols/filesystemexplorer/qml/ResizeButton.qml
+++ b/examples/quickcontrols/filesystemexplorer/qml/ResizeButton.qml
@@ -5,6 +5,8 @@ import QtQuick.Controls
import FileSystemModule
Button {
+ required property ApplicationWindow resizeWindow
+
icon.width: 20; icon.height: 20
anchors.right: parent.right
anchors.bottom: parent.bottom
@@ -12,12 +14,10 @@ Button {
bottomPadding: 3
icon.source: "../icons/resize.svg"
- icon.color: down || checked ? Colors.iconIndicator : Colors.icon
+ icon.color: hovered ? Colors.iconIndicator : Colors.icon
+ background: null
checkable: false
display: AbstractButton.IconOnly
- background: null
- onPressed: {
- root.startSystemResize(Qt.BottomEdge | Qt.RightEdge)
- }
+ onPressed: resizeWindow.startSystemResize(Qt.BottomEdge | Qt.RightEdge)
}
diff --git a/examples/quickcontrols/filesystemexplorer/qml/Sidebar.qml b/examples/quickcontrols/filesystemexplorer/qml/Sidebar.qml
index 9d08562d97..aac5303942 100644
--- a/examples/quickcontrols/filesystemexplorer/qml/Sidebar.qml
+++ b/examples/quickcontrols/filesystemexplorer/qml/Sidebar.qml
@@ -8,77 +8,92 @@ import FileSystemModule
Rectangle {
id: root
+
+ property alias currentTabIndex: topBar.currentIndex
+ required property ApplicationWindow dragWindow
+ readonly property int tabBarSpacing: 10
+
color: Colors.surface2
- required property ApplicationWindow rootWindow
- property alias currentTabIndex: tabBar.currentIndex
+ component SidebarEntry: Button {
+ id: sidebarButton
- ColumnLayout {
- anchors.fill: root
- anchors.topMargin: 10
- anchors.bottomMargin: 10
- spacing: 10
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
- // TabBar is designed to be horizontal, whereas we need a vertical bar.
- // We can easily achieve that by using a Container.
- Container {
- id: tabBar
+ icon.color: down || checked ? Colors.iconIndicator : Colors.icon
+ icon.width: 27
+ icon.height: 27
- Layout.fillWidth: true
+ topPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ leftPadding: 0
+ background: null
- // ButtonGroup ensures that only one button can be checked at a time.
- ButtonGroup {
- buttons: tabBar.contentItem.children
- // We have to manage the currentIndex ourselves, which we do by setting it to the
- // index of the currently checked button.
- // We use setCurrentIndex instead of setting the currentIndex property to avoid breaking bindings.
- // See "Managing the Current Index" in Container's documentation for more information.
- onCheckedButtonChanged: tabBar.setCurrentIndex(Math.max(0, buttons.indexOf(checkedButton)))
- }
+ Rectangle {
+ id: indicator
- contentItem: ColumnLayout {
- spacing: tabBar.spacing
+ anchors.verticalCenter: parent.verticalCenter
+ x: 2
+ width: 4
+ height: sidebarButton.icon.height * 1.2
- Repeater {
- model: tabBar.contentModel
- }
- }
+ visible: sidebarButton.checked
+ color: Colors.color1
+ }
+ }
+
+ // TabBar is designed to be horizontal, whereas we need a vertical bar.
+ // We can easily achieve that by using a Container.
+ component TabBar: Container {
+ id: tabBarComponent
+
+ Layout.fillWidth: true
+ // ButtonGroup ensures that only one button can be checked at a time.
+ ButtonGroup {
+ buttons: tabBarComponent.contentChildren
+
+ // We have to manage the currentIndex ourselves, which we do by setting it to the index
+ // of the currently checked button. We use setCurrentIndex instead of setting the
+ // currentIndex property to avoid breaking bindings. See "Managing the Current Index"
+ // in Container's documentation for more information.
+ onCheckedButtonChanged: tabBarComponent.setCurrentIndex(
+ Math.max(0, buttons.indexOf(checkedButton)))
+ }
- component SidebarEntry: Button {
- id: sidebarButton
- icon.color: down || checked ? Colors.iconIndicator : Colors.icon
- icon.width: 35
- icon.height: 35
- leftPadding: 8 + indicator.width
-
- background: null
-
- Rectangle {
- id: indicator
- x: 4
- anchors.verticalCenter: parent.verticalCenter
- width: 4
- height: sidebarButton.icon.width
- color: Colors.color1
- visible: sidebarButton.checked
- }
+ contentItem: ColumnLayout {
+ spacing: tabBarComponent.spacing
+ Repeater {
+ model: tabBarComponent.contentModel
}
+ }
+ }
+ ColumnLayout {
+ anchors.fill: root
+ anchors.topMargin: root.tabBarSpacing
+ anchors.bottomMargin: root.tabBarSpacing
+
+ spacing: root.tabBarSpacing
+ TabBar {
+ id: topBar
+
+ spacing: root.tabBarSpacing
// Shows help text when clicked.
SidebarEntry {
+ id: infoTab
icon.source: "../icons/light_bulb.svg"
checkable: true
checked: true
-
- Layout.alignment: Qt.AlignHCenter
}
// Shows the file system when clicked.
SidebarEntry {
+ id: filesystemTab
+
icon.source: "../icons/read.svg"
checkable: true
-
- Layout.alignment: Qt.AlignHCenter
}
}
@@ -88,25 +103,31 @@ Rectangle {
Layout.fillWidth: true
// Make the empty space drag our main window.
- WindowDragHandler { dragWindow: rootWindow }
+ WindowDragHandler {
+ dragWindow: root.dragWindow
+ }
}
- // Opens the Qt website in the system's web browser.
- SidebarEntry {
- id: qtWebsiteButton
- icon.source: "../icons/globe.svg"
- checkable: false
+ TabBar {
+ id: bottomBar
- onClicked: Qt.openUrlExternally("https://www.qt.io/")
- }
+ spacing: root.tabBarSpacing
+ // Opens the Qt website in the system's web browser.
+ SidebarEntry {
+ id: qtWebsiteButton
+ icon.source: "../icons/globe.svg"
+ checkable: false
+ onClicked: Qt.openUrlExternally("https://www.qt.io/")
+ }
- // Opens the About Qt Window.
- SidebarEntry {
- id: aboutQtButton
- icon.source: "../icons/info_sign.svg"
- checkable: false
+ // Opens the About Qt Window.
+ SidebarEntry {
+ id: aboutQtButton
- onClicked: aboutQtWindow.visible = !aboutQtWindow.visible
+ icon.source: "../icons/info_sign.svg"
+ checkable: false
+ onClicked: aboutQtWindow.visible = !aboutQtWindow.visible
+ }
}
}
diff --git a/examples/quickcontrols/filesystemexplorer/qmldir b/examples/quickcontrols/filesystemexplorer/qmldir
index b89cf7d594..f94e68a8ac 100644
--- a/examples/quickcontrols/filesystemexplorer/qmldir
+++ b/examples/quickcontrols/filesystemexplorer/qmldir
@@ -1,9 +1,9 @@
module FileSystemModule
Main 1.0 Main.qml
-Icon 1.0 qml/Icon.qml
About 1.0 qml/About.qml
MyMenu 1.0 qml/MyMenu.qml
+Editor 1.0 qml/Editor.qml
Sidebar 1.0 qml/Sidebar.qml
MyMenuBar 1.0 qml/MyMenuBar.qml
singleton Colors 1.0 qml/Colors.qml