diff options
author | Dennis Oberst <[email protected]> | 2023-06-27 15:54:20 +0200 |
---|---|---|
committer | Dennis Oberst <[email protected]> | 2023-08-21 14:10:04 +0200 |
commit | 959f88e4b5371edff0b344e5f768c70aa5202402 (patch) | |
tree | 4c634d55d552af545eed705a98b1916f62ea0ed9 /examples/quickcontrols/filesystemexplorer | |
parent | 4f9687927da504c344a6e1805fe32f0b2d650d8e (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
Pick-to: 6.6
Change-Id: Ib816a95f843b3f4b84b11db893facda0ffc6f482
Reviewed-by: Mitch Curtis <[email protected]>
Reviewed-by: Qt CI Bot <[email protected]>
Diffstat (limited to 'examples/quickcontrols/filesystemexplorer')
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 Binary files differdeleted file mode 100644 index 628b5368d0..0000000000 --- a/examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.jpg +++ /dev/null diff --git a/examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.webp b/examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.webp Binary files differnew file mode 100644 index 0000000000..10ad0d26e7 --- /dev/null +++ b/examples/quickcontrols/filesystemexplorer/doc/images/qtquickcontrols-filesystemexplorer.webp 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 |