diff --git a/CMakeLists.txt b/CMakeLists.txt
index ca6b079..28175a6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,67 +1,67 @@
cmake_minimum_required(VERSION 3.0)
set(NOTA_VERSION 1.0.0)
project(nota VERSION ${NOTA_VERSION})
find_package(ECM 1.7.0 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${ECM_MODULE_PATH})
find_package(MauiKit REQUIRED)
-find_package(Qt5 REQUIRED NO_MODULE COMPONENTS Qml Quick Sql Svg QuickControls2 Widgets Xml)
+find_package(Qt5 REQUIRED NO_MODULE COMPONENTS Qml Sql Svg)
find_package(KF5 ${KF5_VERSION} REQUIRED COMPONENTS I18n Notifications Config KIO Attica SyntaxHighlighting)
include(KDEInstallDirs)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(KDECMakeSettings)
include(ECMInstallIcons)
include(ECMAddAppIcon)
include(ECMSetupVersion)
include(FeatureSummary)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTORCC ON)
set(nota_SRCS
src/main.cpp
src/models/documentsmodel.cpp
src/models/editormodel.cpp
)
set(nota_HDRS
src/models/documentsmodel.h
src/models/editormodel.h
)
set(nota_ASSETS
src/qml.qrc
src/assets/img_assets.qrc
)
add_executable(nota
${nota_SRCS}
${nota_HDRS}
${nota_ASSETS}
)
ecm_setup_version(${NOTA_VERSION}
VARIABLE_PREFIX NOTA
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/nota_version.h"
)
if (ANDROID)
find_package(Qt5 REQUIRED COMPONENTS AndroidExtras WebView)
target_link_libraries(nota MauiKit Qt5::AndroidExtras)
else()
find_package(Qt5 REQUIRED COMPONENTS WebEngine)
endif()
-target_link_libraries(nota MauiKit Qt5::Sql Qt5::Qml Qt5::Widgets Qt5::Svg Qt5::QuickControls2 KF5::ConfigCore KF5::Notifications KF5::KIOCore KF5::I18n KF5::Attica)
+target_link_libraries(nota MauiKit Qt5::Sql Qt5::Qml Qt5::Svg KF5::ConfigCore KF5::Notifications KF5::KIOCore KF5::I18n KF5::Attica)
install(TARGETS nota ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES org.kde.nota.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
#TODO: port to ecm_install_icons()
# install(FILES assets/pix.svg DESTINATION ${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps)
# install(FILES org.kde.pix.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/src/Editor.qml b/src/Editor.qml
index 1720ccd..86b18b7 100644
--- a/src/Editor.qml
+++ b/src/Editor.qml
@@ -1,45 +1,45 @@
import QtQuick 2.9
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
import org.kde.mauikit 1.0 as Maui
import org.kde.kirigami 2.7 as Kirigami
Maui.Editor
{
height: _editorListView.height
width: _editorListView.width
footBar.visible: false
headBar.rightContent: [
ToolButton
{
id: saveBtn
icon.name: "document-save"
- onClicked: saveFile(document.fileUrl)
+ onClicked: saveFile(document.fileUrl, _tabBar.currentIndex)
},
ToolButton
{
icon.name: "document-save-as"
text: qsTr("Save as...")
- onClicked: saveFile()
+ onClicked: saveFile("", _tabBar.currentIndex)
}
]
- function saveFile(path)
+ function saveFile(path, index)
{
if (path && Maui.FM.fileExists(path))
{
document.saveAs(path);
} else
{
fileDialog.mode = fileDialog.modes.SAVE;
fileDialog.settings.singleSelection = true
fileDialog.show(function (paths)
{
document.saveAs(paths[0]);
- _editorList.update(_tabBar.currentIndex, paths[0]);
+ _editorList.update(index, paths[0]);
});
}
}
}
diff --git a/src/main.qml b/src/main.qml
index 8dc3614..3fc6964 100644
--- a/src/main.qml
+++ b/src/main.qml
@@ -1,505 +1,545 @@
import QtQuick 2.9
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.7 as Kirigami
import org.kde.mauikit 1.0 as Maui
import org.maui.nota 1.0 as Nota
import QtQuick.Window 2.0
-
+import QtQml.Models 2.3
import "views"
Maui.ApplicationWindow
{
id: root
title: qsTr("Nota")
// property bool terminalVisible: Maui.FM.loadSettings("TERMINAL", "MAINVIEW", false) == "true"
// property alias terminal : terminalLoader.item
property var views : ({editor: 0, documents: 1, recent: 2})
Maui.App.iconName: "qrc:/img/nota.svg"
Maui.App.description: qsTr("Nota is a simple text editor for Plasma Mobile, GNU/Linux distros and Android")
rightIcon.visible: false
// mainMenu: [
// MenuItem
// {
// text: qsTr("Show terminal")
// checkable: true
// checked: terminal.visible
// onTriggered:
// {
// terminalVisible = !terminalVisible
// Maui.FM.saveSettings("TERMINAL",terminalVisible, "MAINVIEW")
// }
// }
// ]
+ ObjectModel
+ {
+ id: _documentModel
+ }
+
onClosing:
{
- var files = []
+ _unsavedFilesModel.clear()
for(var i = 0; i<_editorListView.count; i++)
{
- const doc = _editorListView.itemAtIndex(i)
+ const doc = _documentModel.get(i)
if(doc.document.modified)
- files.push({'file': _editorModel.get(i), 'index': i})
+ _unsavedFilesModel.append({'file': _editorModel.get(i), 'documentIndex': i})
}
- if(files.length > 0 && !_unsavedDialog.discard)
+ if(_unsavedFilesModel.count > 0 && !_unsavedDialog.discard)
{
close.accepted = false
- _unsavedDialog.files = files
_unsavedDialog.open()
}else close.accepted = true
}
Maui.Dialog
{
id: _unsavedDialog
- property var files : []
property bool discard : false
acceptButton.visible: false
page.title: qsTr("Un saved files")
-
+ headBar.visible: true
maxHeight: 500
maxWidth: 400
page.padding: Maui.Style.space.big
ListView
{
id: _unsavedFilesListView
anchors.fill: parent
spacing: Maui.Style.space.medium
- model: _unsavedDialog.files
+ model: ListModel
+ {
+ id: _unsavedFilesModel
+ }
+
+ onCountChanged: if(count === 0) _unsavedDialog.close()
+
clip: true
delegate : Maui.ItemDelegate
{
+ id: _unsavedFileDelegate
+ property int index_ : index
width: parent.width
height: Maui.Style.rowHeight * 1.2
RowLayout
{
anchors.fill: parent
Maui.ListItemTemplate
{
Layout.fillHeight: true
Layout.fillWidth: true
- label1.text: modelData.file.label
- label2.text: modelData.file.path
- iconSource: modelData.file.icon
+ label1.text: model.file.label
+ label2.text: model.file.path
+ iconSource: model.file.icon
iconSizeHint: Maui.Style.iconSizes.big
}
Row
{
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.preferredWidth: implicitWidth
Button
{
text: qsTr("Save")
- onClicked: Object.call(_editorListView.itemAtIndex(modelData.index).saveFile(modelData.file.path))
+ onClicked:
+ {
+ _documentModel.get(model.documentIndex).saveFile(model.file.path, model.documentIndex)
+// closeTab(model.index)
+ _unsavedFilesModel.remove(_unsavedFileDelegate.index_)
+ }
+ }
+
+ Button
+ {
+ text: qsTr("Discard")
+ onClicked:
+ {
+ closeTab(model.documentIndex)
+ _unsavedFilesModel.remove(_unsavedFileDelegate.index_)
+ }
}
}
}
}
}
rejectButton.text: qsTr("Discard")
onRejected:
{
discard = true
root.close()
}
}
Maui.FileDialog
{
id: fileDialog
settings.onlyDirs: false
settings.filterType: Maui.FMList.TEXT
settings.sortBy: Maui.FMList.MODIFIED
mode: modes.OPEN
}
Maui.FloatingButton
{
id: _overlayButton
z: 999
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Maui.Style.toolBarHeight
anchors.bottomMargin: Maui.Style.toolBarHeight
height: Maui.Style.toolBarHeight
width: height
icon.name: "document-new"
icon.color: Kirigami.Theme.highlightedTextColor
onClicked: openTab("")
Maui.Badge
{
iconName: "list-add"
anchors
{
horizontalCenter: parent.right
verticalCenter: parent.top
}
onClicked: _newDocumentMenu.open()
}
Maui.Dialog
{
id: _newDocumentMenu
maxHeight: 300
maxWidth: 400
defaultButtons: false
footBar.middleContent: Button
{
text: qsTr("Add new template file")
}
ColumnLayout
{
anchors.fill: parent
anchors.margins: Maui.Style.space.big
spacing: Maui.Style.space.big
Maui.ItemDelegate
{
Layout.fillWidth: true
Layout.fillHeight: true
Maui.ListItemTemplate
{
anchors.fill:parent
iconSizeHint: Math.min(height, Maui.Style.iconSizes.big)
iconSource: "text-x-generic"
label1.text: qsTr("Text file")
label2.text: qsTr("Simple text file with syntax highlighting")
}
onClicked:
{
openTab("")
_newDocumentMenu.close()
}
}
Maui.ItemDelegate
{
Layout.fillWidth: true
Layout.fillHeight: true
Maui.ListItemTemplate
{
anchors.fill:parent
iconSizeHint: Math.min(height, Maui.Style.iconSizes.big)
iconSource: "text-enriched"
label1.text: qsTr("Rich text file")
label2.text: qsTr("With support for basic text format editing")
}
}
Maui.ItemDelegate
{
Layout.fillWidth: true
Layout.fillHeight: true
Maui.ListItemTemplate
{
anchors.fill:parent
iconSizeHint: Math.min(height, Maui.Style.iconSizes.big)
iconSource: "text-html"
label1.text: qsTr("HTML text file")
label2.text: qsTr("Text file with HTML markup support")
}
}
}
}
}
headBar.rightContent: [
ToolButton
{
icon.name: "document-open"
onClicked:
{
fileDialog.mode = fileDialog.modes.OPEN
fileDialog.settings.onlyDirs = false
fileDialog.settings.singleSelection = false
fileDialog.show(function (paths)
{
for(var i in paths)
openTab(paths[i])
});
}
}
]
headBar.middleContent: Maui.ActionGroup
{
id: _actionGroup
currentIndex: _swipeView.currentIndex
Layout.fillHeight: true
width: implicitWidth
Action
{
text: qsTr("Editor")
icon.name: "document-edit"
}
Action
{
text: qsTr("Documents")
icon.name: "view-pim-journal" // to do
}
Action
{
text: qsTr("Recent")
icon.name: "view-media-recent" // to do
}
}
sideBar: Maui.AbstractSideBar
{
id : _drawer
focus: true
- width: visible ? Math.min(Kirigami.Units.gridUnit * (Kirigami.Settings.isMobile? 14 : 18), root.width) : 0
+ width: visible ? Math.min(Kirigami.Units.gridUnit * (Kirigami.Settings.isMobile? 14 : 16), root.width) : 0
modal: !isWide
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
dragMargin: Maui.Style.space.big
Maui.Page
{
anchors.fill: parent
headBar.middleContent: ComboBox
{
Layout.fillWidth: true
z : _drawer.z + 9999
model: Maui.BaseModel
{
list: Maui.PlacesList
{
groups: [
Maui.FMList.PLACES_PATH,
Maui.FMList.DRIVES_PATH,
Maui.FMList.TAGS_PATH]
}
}
textRole: "label"
onActivated:
{
currentIndex = index
browserView.openFolder(model.list.get(index).path)
}
}
Maui.FileBrowser
{
id: browserView
anchors.fill: parent
headBar.position: ToolBar.Footer
headBar.visible: true
viewType : Maui.FMList.LIST_VIEW
settings.filterType: Maui.FMList.TEXT
headBar.rightLayout.visible: false
headBar.rightLayout.width: 0
onItemClicked:
{
var item = currentFMList.get(index)
if(item.isdir == "true")
openFolder(item.path)
else
root.openTab(item.path)
}
}
}
}
Maui.BaseModel
{
id: _editorModel
list: Nota.Editor
{
id: _editorList
}
}
SwipeView
{
id: _swipeView
anchors.fill: parent
currentIndex: _actionGroup.currentIndex
onCurrentItemChanged: currentItem.forceActiveFocus()
onCurrentIndexChanged: _actionGroup.currentIndex = currentIndex
ColumnLayout
{
id: editorView
spacing: 0
Maui.TabBar
{
id: _tabBar
visible: _editorListView.count > 1
Layout.fillWidth: true
Layout.preferredHeight: _tabBar.implicitHeight
position: TabBar.Header
currentIndex : _editorListView.currentIndex
// Keys.onPressed:
// {
// if(event.key == Qt.Key_Return)
// {
// _browserList.currentIndex = currentIndex
// control.currentPath = tabsObjectModel.get(currentIndex).path
// }
// }
Repeater
{
id: _repeater
model: _editorModel
Maui.TabButton
{
id: _tabButton
+ readonly property int index_ : index
implicitHeight: _tabBar.implicitHeight
implicitWidth: Math.max(_tabBar.width / _repeater.count, 120)
checked: index === _tabBar.currentIndex
text: model.label
onClicked: _editorListView.currentIndex = index
onCloseClicked:
{
- if( _editorListView.itemAtIndex(_tabButton.index).document.modified)
+ console.log("CLOSING EDITOR AT", _tabButton.index_)
+ if( _documentModel.get(_tabButton.index_).document.modified)
_saveDialog.open()
else
- _editorList.remove(index)
+ closeTab(_tabButton.index_)
}
Maui.Dialog
{
id: _saveDialog
page.padding: Maui.Style.space.huge
- title: _editorModel.get(_tabButton.index).path
- message: qsTr("This file has been modified, you can save now your changes or discard them")
+ title: qsTr("Save file")
+ message: qsTr(String("This file has been modified, you can save your changes now or discard them.\n")) + _editorModel.get(_tabButton.index).path
acceptButton.text: qsTr("Save")
rejectButton.text: qsTr("Discard")
onAccepted:
{
- _editorListView.itemAtIndex(_tabButton.index).saveFile(_editorModel.get(_tabButton.index).path)
+ _documentModel.get(_tabButton.index_).saveFile(_editorModel.get(_tabButton.index_).path, _tabButton.index_)
_saveDialog.close()
}
onRejected:
{
- _editorList.remove(_tabButton.index)
_saveDialog.close()
+ _editorList.remove(_tabButton.index_)
}
}
}
}
}
// Kirigami.Separator
// {
// color: Qt.tint(Kirigami.Theme.textColor, Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.7))
// Layout.fillWidth: true
// Layout.preferredHeight: 1
// visible: _tabBar.visible
// }
ListView
{
id: _editorListView
Layout.fillHeight: true
Layout.fillWidth: true
orientation: ListView.Horizontal
- model: _editorModel
+ model: _documentModel
snapMode: ListView.SnapOneItem
spacing: 0
interactive: Kirigami.Settings.isMobile && count > 1
highlightFollowsCurrentItem: true
highlightMoveDuration: 0
onMovementEnded: currentIndex = indexAt(contentX, contentY)
-
+ cacheBuffer: count
Maui.Holder
{
id: _holder
visible: !_editorListView.count
emoji: "qrc:/img/document-edit.svg"
emojiSize: Maui.Style.iconSizes.huge
isMask: true
onActionTriggered: openTab("")
title: qsTr("Create a new document")
body: qsTr("You can create a new document by clicking the New File button, or here.
Alternative you can open existing files from the left places sidebar or by clicking the Open button")
}
- delegate: Editor
- {
- Component.onCompleted: fileUrl = model.path
- }
+// delegate: Editor
+// {
+// Component.onCompleted: fileUrl = model.path
+// }
}
// Loader
// {
// id: terminalLoader
// visible: terminalVisible
// focus: true
// Layout.fillWidth: true
// Layout.alignment: Qt.AlignBottom
// Layout.minimumHeight: 100
// Layout.maximumHeight: 200
// // anchors.bottom: parent.bottom
// // anchors.top: handle.bottom
// source: !isMobile ? "Terminal.qml" : undefined
// }
}
DocumentsView
{
id: _documentsView
}
RecentView
{
id:_recentView
}
}
-
function openTab(path)
{
if(!_editorList.append(path))
return ;
- _editorListView.currentIndex = _editorListView.count - 1
+ var component = Qt.createComponent("Editor.qml");
+ if (component.status === Component.Ready)
+ {
+ _documentModel.append(component.createObject(_documentModel));
+
+ _editorListView.currentIndex = _documentModel.count - 1
+ _documentModel.get(_documentModel.count - 1).fileUrl = path
+
+ if(path && Maui.FM.fileExists(path))
+ browserView.openFolder(Maui.FM.fileDir(path))
+ }
+ }
- if(path && Maui.FM.fileExists(path))
- browserView.openFolder(Maui.FM.fileDir(path))
+ function closeTab(index)
+ {
+ _documentModel.remove(index)
+ _editorList.remove(index)
}
}
diff --git a/src/models/documentsmodel.cpp b/src/models/documentsmodel.cpp
index bd34c4a..d61ccd3 100644
--- a/src/models/documentsmodel.cpp
+++ b/src/models/documentsmodel.cpp
@@ -1,70 +1,70 @@
#include "documentsmodel.h"
#include
Q_DECLARE_METATYPE (FMH::MODEL_LIST)
Q_DECLARE_METATYPE (FMH::MODEL)
DocumentsModel::DocumentsModel(QObject * parent) : MauiList (parent)
{
qRegisterMetaType("MODEL_LIST");
qRegisterMetaType("MODEL");
- FileLoader *loader = new FileLoader;
+ FilesFetcher *loader = new FilesFetcher;
loader->moveToThread(&m_worker);
connect(&m_worker, &QThread::finished, loader, &QObject::deleteLater);
- connect(this, &DocumentsModel::start, loader, &FileLoader::fetch);
- connect(loader, &FileLoader::itemReady, this, &DocumentsModel::append);
+ connect(this, &DocumentsModel::start, loader, &FilesFetcher::fetch);
+ connect(loader, &FilesFetcher::itemReady, this, &DocumentsModel::append);
m_worker.start();
}
DocumentsModel::~DocumentsModel()
{
m_worker.quit();
m_worker.wait();
}
void DocumentsModel::setList(const FMH::MODEL_LIST &list)
{
emit this->preListChanged ();
this->m_list = list;
emit this->postListChanged ();
}
void DocumentsModel::append(const FMH::MODEL &item)
{
emit this->preItemAppended ();
this->m_list << item;
emit this->postItemAppended ();
}
-void FileLoader::fetch(const QList & urls)
+void FilesFetcher::fetch(const QList & urls)
{
FMH::MODEL_LIST res;
for(const auto &url : urls)
{
QDirIterator it(url.toLocalFile(), FMH::FILTER_LIST[FMH::FILTER_TYPE::TEXT], QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks, QDirIterator::Subdirectories);
while (it.hasNext())
{
const auto item = FMH::getFileInfoModel (QUrl::fromLocalFile (it.next ()));
res << item;
emit this->itemReady (item);
}
}
// emit this->resultReady(res);
}
FMH::MODEL_LIST DocumentsModel::items() const
{
return this->m_list;
}
void DocumentsModel::componentComplete()
{
emit this->start({FMH::DocumentsPath, FMH::DownloadsPath, FMH::DesktopPath});
}
diff --git a/src/models/documentsmodel.h b/src/models/documentsmodel.h
index c149a63..390d1ea 100644
--- a/src/models/documentsmodel.h
+++ b/src/models/documentsmodel.h
@@ -1,50 +1,50 @@
#ifndef DOCUMENTSMODEL_H
#define DOCUMENTSMODEL_H
#include
#include
#ifdef STATIC_MAUIKIT
#include "fmh.h"
#include "mauilist.h"
#else
#include
#include
#endif
-class FileLoader : public QObject
+class FilesFetcher : public QObject
{
Q_OBJECT
public slots:
void fetch(const QList &urls);
signals:
void resultReady(FMH::MODEL_LIST items);
void itemReady(FMH::MODEL item);
};
class DocumentsModel : public MauiList
{
Q_OBJECT
QThread m_worker;
public:
DocumentsModel(QObject *parent = nullptr);
~DocumentsModel() override;
FMH::MODEL_LIST items() const override final;
void componentComplete() override final;
private:
void setList(const FMH::MODEL_LIST &list);
void append(const FMH::MODEL &item);
FMH::MODEL_LIST m_list;
signals:
void start(QList urls);
};
#endif // DOCUMENTSMODEL_H
diff --git a/src/views/DocumentsView.qml b/src/views/DocumentsView.qml
index 92aeb54..7aa4ee0 100644
--- a/src/views/DocumentsView.qml
+++ b/src/views/DocumentsView.qml
@@ -1,51 +1,59 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import org.kde.kirigami 2.6 as Kirigami
import org.kde.mauikit 1.0 as Maui
import org.maui.nota 1.0 as Nota
Maui.Page
{
id: control
property alias model : _documentsModel
property alias list : _documentsList
headBar.middleContent: Maui.TextField
{
Layout.fillWidth: true
placeholderText: qsTr("Filter...")
- onAccepted: _gridView.model.filter = text
- onCleared: _gridView.model.filter = text
+ onAccepted: _listView.model.filter = text
+ onCleared: _listView.model.filter = text
}
Maui.ListBrowser
{
- id: _gridView
+ id: _listView
anchors.fill: parent
model: Maui.BaseModel
{
id: _documentsModel
list: Nota.Documents
{
id: _documentsList
}
}
- delegate: Maui.ListBrowserDelegate
+ delegate: Maui.ItemDelegate
{
height: Maui.Style.rowHeight *2
- width: parent.width
- label1.text: model.label
- label2.text: model.path
+ width: _listView.width
padding: Maui.Style.space.medium
+
+ Maui.ListItemTemplate
+ {
+ anchors.fill: parent
+ label1.text: model.label
+ label2.text: model.path
+ iconSource: model.icon
+ iconSizeHint: Maui.Style.iconSizes.big
+ }
+
onClicked:
{
- root.openTab(_gridView.model.get(index).path)
+ root.openTab(_listView.model.get(index).path)
_actionGroup.currentIndex = views.editor
}
}
}
}