2023-01-06 14:07:33 -05:00
|
|
|
/*
|
2025-02-06 21:47:26 -04:00
|
|
|
* Copyright (C) 2024-2025 Savoir-faire Linux Inc.
|
2023-01-06 14:07:33 -05:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
import QtQuick
|
|
|
|
import QtQuick.Controls
|
|
|
|
import QtQuick.Layouts
|
|
|
|
import net.jami.Constants 1.1
|
|
|
|
import net.jami.Models 1.1
|
2023-02-27 18:08:06 -05:00
|
|
|
import "commoncomponents"
|
|
|
|
|
2023-01-06 14:07:33 -05:00
|
|
|
QtObject {
|
|
|
|
id: root
|
|
|
|
|
|
|
|
// A map of view names to file paths for QML files that define each view.
|
2023-02-24 19:05:26 -05:00
|
|
|
property variant resources: {
|
|
|
|
"SidePanel": "mainview/components/SidePanel.qml",
|
2023-02-27 18:08:06 -05:00
|
|
|
"WelcomePage": "mainview/components/WelcomePage.qml",
|
2023-02-24 19:05:26 -05:00
|
|
|
"ConversationView": "mainview/ConversationView.qml",
|
|
|
|
"NewSwarmPage": "mainview/components/NewSwarmPage.qml",
|
|
|
|
"WizardView": "wizardview/WizardView.qml",
|
2023-04-06 14:27:46 -04:00
|
|
|
"AccountMigrationView": "AccountMigrationView.qml",
|
2023-02-24 19:05:26 -05:00
|
|
|
"SettingsView": "settingsview/SettingsView.qml",
|
2023-04-13 16:18:26 -04:00
|
|
|
"SettingsSidePanel": "settingsview/SettingsSidePanel.qml"
|
2023-02-24 19:05:26 -05:00
|
|
|
}
|
2023-01-06 14:07:33 -05:00
|
|
|
|
|
|
|
// The `main` view of the application window.
|
2023-02-27 18:08:06 -05:00
|
|
|
property StackView rootView
|
2023-01-06 14:07:33 -05:00
|
|
|
|
2023-12-21 14:37:32 -05:00
|
|
|
readonly property Item currentView: rootView && rootView.currentItem || null
|
|
|
|
readonly property var currentViewName: currentView && currentView.objectName || null
|
|
|
|
readonly property bool isDualPane: currentView && currentView instanceof DualPaneView
|
|
|
|
readonly property bool isInSinglePaneMode: !isDualPane || currentView.isSinglePane
|
|
|
|
readonly property bool isRTL: Qt.application.layoutDirection === Qt.RightToLeft
|
|
|
|
// A list of the current visible views. This could be a single view or two views in
|
|
|
|
// dual pane mode. The list is ordered [minor, major] where major is the view on the
|
|
|
|
// right side when not in RTL and should represent the main or content-type view.
|
|
|
|
readonly property var visibleViews: {
|
|
|
|
if (!currentView)
|
2025-02-18 16:12:23 -05:00
|
|
|
return [];
|
2023-12-21 14:37:32 -05:00
|
|
|
if (isDualPane) {
|
|
|
|
if (isInSinglePaneMode)
|
2025-02-18 16:12:23 -05:00
|
|
|
return [currentView.rightPaneItem];
|
|
|
|
return [currentView.leftPaneItem, currentView.rightPaneItem];
|
2023-12-21 14:37:32 -05:00
|
|
|
}
|
2025-02-18 16:12:23 -05:00
|
|
|
return [currentView];
|
2023-12-21 14:37:32 -05:00
|
|
|
}
|
|
|
|
// Aggregate this info and expose it as a single string for convenience.
|
|
|
|
// JSON indented by 2 spaces.
|
|
|
|
readonly property string currentViewInfo: {
|
|
|
|
var info = {
|
|
|
|
currentViewName: currentViewName,
|
|
|
|
isDualPane: isDualPane,
|
|
|
|
isInSinglePaneMode: isInSinglePaneMode,
|
2025-02-18 16:12:23 -05:00
|
|
|
visibleViews: visibleViews.map(function (view) {
|
|
|
|
return view && view.objectName || null;
|
|
|
|
}),
|
|
|
|
visibleViewWidths: visibleViews.map(function (view) {
|
|
|
|
return view && view.width || null;
|
|
|
|
})
|
2023-12-21 14:37:32 -05:00
|
|
|
};
|
|
|
|
return JSON.stringify(info, null, 2);
|
|
|
|
}
|
2023-01-06 14:07:33 -05:00
|
|
|
|
2023-03-22 13:19:21 -04:00
|
|
|
function init(mainStackView) {
|
2023-02-27 18:08:06 -05:00
|
|
|
rootView = Qt.createQmlObject(`import QtQuick; import QtQuick.Controls
|
2023-04-13 16:18:26 -04:00
|
|
|
StackView { anchors.fill: parent }`, mainStackView);
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
|
|
|
|
2023-02-27 18:08:06 -05:00
|
|
|
function deinit() {
|
2023-04-13 16:18:26 -04:00
|
|
|
viewManager.destroyAllViews();
|
|
|
|
if (rootView)
|
|
|
|
rootView.destroy();
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
|
|
|
|
2023-02-27 18:08:06 -05:00
|
|
|
// Finds a view and gets its index within the StackView it's in.
|
|
|
|
function getStackIndex(viewName) {
|
|
|
|
for (const [key, value] of Object.entries(viewManager.views)) {
|
|
|
|
if (value.objectName === viewName) {
|
2023-04-13 16:18:26 -04:00
|
|
|
return value.StackView.index;
|
2023-02-27 18:08:06 -05:00
|
|
|
}
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
2023-04-13 16:18:26 -04:00
|
|
|
return -1;
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create, present, and return a dialog object.
|
2025-02-18 16:12:23 -05:00
|
|
|
function presentDialog(parent, path, props = {}, singleInstance = false) {
|
2023-01-06 14:07:33 -05:00
|
|
|
// Open the dialog once the object is created
|
2025-02-18 16:12:23 -05:00
|
|
|
let createFunc = singleInstance ? viewManager.createView : viewManager.createUniqueView;
|
|
|
|
return createFunc(path, parent, function (obj) {
|
2023-04-13 16:18:26 -04:00
|
|
|
const doneCb = function () {
|
|
|
|
viewManager.destroyView(path);
|
|
|
|
};
|
|
|
|
if (obj.closed !== undefined) {
|
|
|
|
obj.closed.connect(doneCb);
|
|
|
|
} else {
|
|
|
|
if (obj.accepted !== undefined) {
|
|
|
|
obj.accepted.connect(doneCb);
|
|
|
|
}
|
|
|
|
if (obj.rejected !== undefined) {
|
|
|
|
obj.rejected.connect(doneCb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
obj.open();
|
|
|
|
}, props);
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
|
|
|
|
2023-02-27 18:08:06 -05:00
|
|
|
// Present a view by name.
|
|
|
|
function present(viewName, props) {
|
2023-04-13 16:18:26 -04:00
|
|
|
const path = resources[viewName];
|
2023-01-06 14:07:33 -05:00
|
|
|
|
2023-02-27 18:08:06 -05:00
|
|
|
// Check if the current view should inhibit the presentation of this view.
|
|
|
|
if (rootView.currentItem && rootView.currentItem.inhibits.includes(viewName)) {
|
2023-04-13 16:18:26 -04:00
|
|
|
print("inhibiting view:", viewName);
|
|
|
|
return;
|
2023-02-24 19:05:26 -05:00
|
|
|
}
|
|
|
|
|
2023-01-06 14:07:33 -05:00
|
|
|
// If the view already exists in the StackView, the function will attempt
|
|
|
|
// to navigate to its StackView position by dismissing elevated views.
|
2023-04-13 16:18:26 -04:00
|
|
|
if (rootView.find(function (item) {
|
|
|
|
return item.objectName === viewName;
|
|
|
|
}, StackView.DontLoad)) {
|
|
|
|
const viewIndex = getStackIndex(viewName);
|
2023-02-27 18:08:06 -05:00
|
|
|
for (var i = (rootView.depth - 1); i > viewIndex; i--) {
|
2023-04-13 16:18:26 -04:00
|
|
|
dismissObj(rootView.get(i, StackView.DontLoad));
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
2023-04-13 16:18:26 -04:00
|
|
|
return;
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
2023-04-13 16:18:26 -04:00
|
|
|
if (!viewManager.createView(path, rootView, function (view) {
|
|
|
|
// push the view onto the stack if it's not already there
|
|
|
|
if (rootView.currentItem !== view) {
|
|
|
|
rootView.push(view, StackView.Immediate);
|
|
|
|
}
|
|
|
|
if (!view.managed)
|
|
|
|
view.presented();
|
|
|
|
}, props)) {
|
2024-10-03 15:32:31 -04:00
|
|
|
print("An error occurred while creating view:", viewName);
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dismiss by object.
|
2023-02-27 18:08:06 -05:00
|
|
|
function dismissObj(obj) {
|
2023-03-22 13:19:21 -04:00
|
|
|
// Check if it makes sense to remove this view at all.
|
|
|
|
if (obj.StackView.view !== rootView || !viewManager.viewCount()) {
|
2023-04-13 16:18:26 -04:00
|
|
|
return;
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// If we are dismissing a view that is not at the top of the stack,
|
|
|
|
// we need to store each of the views on top into a temporary stack
|
|
|
|
// and then restore them after the view is dismissed.
|
2023-04-13 16:18:26 -04:00
|
|
|
const viewIndex = obj.StackView.index;
|
|
|
|
var tempStack = [];
|
2023-02-27 18:08:06 -05:00
|
|
|
for (var i = (rootView.depth - 1); i > viewIndex; i--) {
|
2023-04-13 16:18:26 -04:00
|
|
|
var item = rootView.pop(StackView.Immediate);
|
|
|
|
tempStack.push(item);
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
|
|
|
// And we define a function to restore and resolve the views.
|
|
|
|
var resolveStack = () => {
|
|
|
|
for (var i = 0; i < tempStack.length; i++) {
|
2023-04-13 16:18:26 -04:00
|
|
|
rootView.push(tempStack[i], StackView.Immediate);
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
2023-04-13 16:18:26 -04:00
|
|
|
if (rootView.depth > 0)
|
|
|
|
rootView.currentItem.presented();
|
|
|
|
};
|
2023-01-06 14:07:33 -05:00
|
|
|
|
|
|
|
// Now we can dismiss the view at the top of the stack.
|
2023-04-13 16:18:26 -04:00
|
|
|
const depth = rootView.depth;
|
2023-02-27 18:08:06 -05:00
|
|
|
if (obj === rootView.get(depth - 1, StackView.DontLoad)) {
|
2023-04-13 16:18:26 -04:00
|
|
|
var view;
|
2023-02-27 18:08:06 -05:00
|
|
|
if (rootView.depth === 1) {
|
2023-04-13 16:18:26 -04:00
|
|
|
view = rootView.currentItem;
|
|
|
|
rootView.clear();
|
|
|
|
} else
|
|
|
|
view = rootView.pop(StackView.Immediate);
|
2023-01-06 14:07:33 -05:00
|
|
|
if (!view) {
|
2024-10-03 15:32:31 -04:00
|
|
|
print("An error occurred while attempting to pop view:", obj.objectName);
|
2023-04-13 16:18:26 -04:00
|
|
|
resolveStack();
|
|
|
|
return;
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the view is managed, we can destroy it, otherwise, it can
|
|
|
|
// be reused and destroyed by it's parent.
|
|
|
|
if (view.managed) {
|
2023-04-13 16:18:26 -04:00
|
|
|
var objectName = view ? view.objectName : obj.objectName;
|
2023-01-06 14:07:33 -05:00
|
|
|
if (!viewManager.destroyView(resources[objectName])) {
|
2024-10-03 15:32:31 -04:00
|
|
|
print("An error occurred while attempting to destroy view:", objectName);
|
2023-12-21 14:37:32 -05:00
|
|
|
} else {
|
|
|
|
print("destroyed view:", objectName);
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
2023-04-13 16:18:26 -04:00
|
|
|
} else
|
|
|
|
view.dismissed();
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
2023-04-13 16:18:26 -04:00
|
|
|
resolveStack();
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
|
|
|
|
2023-02-27 18:08:06 -05:00
|
|
|
// Dismiss a view by name or the top view if unspecified.
|
2023-04-13 16:18:26 -04:00
|
|
|
function dismiss(viewName = undefined) {
|
|
|
|
if (!rootView || rootView.depth === 0)
|
|
|
|
return;
|
2023-02-27 18:08:06 -05:00
|
|
|
if (viewName !== undefined) {
|
2023-04-13 16:18:26 -04:00
|
|
|
const depth = rootView.depth;
|
2023-02-27 18:08:06 -05:00
|
|
|
for (var i = (depth - 1); i >= 0; i--) {
|
2023-04-13 16:18:26 -04:00
|
|
|
const view = rootView.get(i, StackView.DontLoad);
|
2023-02-27 18:08:06 -05:00
|
|
|
if (view.objectName === viewName) {
|
2023-04-13 16:18:26 -04:00
|
|
|
dismissObj(view);
|
|
|
|
return;
|
2023-02-27 18:08:06 -05:00
|
|
|
}
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
2023-04-13 16:18:26 -04:00
|
|
|
return;
|
2023-02-27 18:08:06 -05:00
|
|
|
} else {
|
2023-04-13 16:18:26 -04:00
|
|
|
dismissObj(rootView.currentItem);
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-21 14:37:32 -05:00
|
|
|
function getView(viewName, forceCreate = false) {
|
|
|
|
// If the view is already loaded, return it.
|
|
|
|
var view = viewManager.getView(viewName);
|
|
|
|
if (view)
|
|
|
|
return view;
|
|
|
|
if (!forceCreate)
|
|
|
|
return null;
|
|
|
|
// Otherwise, create it.
|
|
|
|
view = viewManager.createView(resources[viewName], null);
|
|
|
|
if (!view) {
|
|
|
|
console.log("Failed to load view: " + viewName);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return view;
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|
|
|
|
|
2023-04-13 16:18:26 -04:00
|
|
|
// Load a view without presenting it.
|
|
|
|
function preload(viewName) {
|
|
|
|
if (!viewManager.createView(resources[viewName], null)) {
|
|
|
|
console.log("Failed to load view: " + viewName);
|
|
|
|
}
|
|
|
|
}
|
2023-01-06 14:07:33 -05:00
|
|
|
}
|