Commit b0fd8032 authored by Andreas Traczyk's avatar Andreas Traczyk
Browse files

mainapplication: isolate window mode logic in a top-level component

Logic for fullscreen/windowed mode switching is scattered within
the main ApplicationWindow and other components. Ideally,
components that need to transition to fullscreen, can make a dumb
request to module in charge of making the necessary checks and
carrying out the mode change.

This patch introduces the LayoutManager used to isolate this logic.

Change-Id: I0e5b932617d2b88eda1533f25a5d55fc1c66c438
parent 0a7a6643
......@@ -177,5 +177,6 @@
<file>src/mainview/components/KeyboardShortcutTable.qml</file>
<file>src/mainview/components/KeyboardShortcutKeyDelegate.qml</file>
<file>src/mainview/components/KeyboardShortcutTabButton.qml</file>
<file>src/LayoutManager.qml</file>
</qresource>
</RCC>
/*
* Copyright (C) 2022 Savoir-faire Linux Inc.
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* 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 net.jami.Adapters 1.1
import "mainview/components"
QtObject {
id: root
// A window-sized container for reparenting components.
required property Item appContainer
// True if the main window is fullscreen.
readonly property bool isFullScreen: visibility === Window.FullScreen
// Both the Hidden and Minimized states combined for convenience.
readonly property bool isHidden: visibility === Window.Hidden ||
visibility === Window.Minimized
// Used to store if a OngoingCallPage component is fullscreened.
property bool isCallFullscreen: false
// Restore a visible windowed mode.
function restoreApp() {
if (isHidden) {
if (priv.windowedVisibility === Window.Hidden
|| priv.windowedVisibility === Window.Minimized) {
showNormal()
return
}
visibility = priv.windowedVisibility
}
}
// Adds an item to the fullscreen item stack. Automatically puts
// the main window in fullscreen mode if needed. Callbacks should be used
// to perform component-specific tasks upon successful transitions.
function pushFullScreenItem(item, originalParent, pushedCb, removedCb) {
if (item === null || item === undefined
|| priv.fullScreenItems.length >= 3) {
return
}
// Make sure our window is in fullscreen mode.
priv.requestWindowModeChange(true)
// Add the item to our list and reparent it to appContainer.
priv.fullScreenItems.push({
"item": item,
"originalParent": originalParent,
"removedCb": removedCb
})
item.parent = appContainer
if (pushedCb) {
pushedCb()
}
// Reevaluate isCallFullscreen.
priv.fullScreenItemsChanged()
}
// Remove an item if specified, or by default, the top item. Automatically
// resets the main window to windowed mode if no items remain in the stack.
function popFullScreenItem(obj=null) {
// Remove the item and reparent it to its original parent.
if (obj === null) {
obj = priv.fullScreenItems.pop()
} else {
const index = priv.fullScreenItems.indexOf(obj);
if (index > -1) {
priv.fullScreenItems.splice(index, 1);
}
}
if (obj !== undefined) {
if (obj.item !== appWindow) {
obj.item.parent = obj.originalParent
if (obj.removedCb) {
obj.removedCb()
}
}
// Reevaluate isCallFullscreen.
priv.fullScreenItemsChanged()
}
// Only leave fullscreen mode if our window isn't in fullscreen
// mode already.
if (priv.fullScreenItems.length === 0) {
// Simply recall the last visibility state.
visibility = priv.windowedVisibility
}
}
// Used to filter removal for a specific item.
function removeFullScreenItem(item) {
priv.fullScreenItems.forEach(o => {
if (o.item === item) {
popFullScreenItem(o)
return
}
});
}
// Toggle the application window in fullscreen mode.
function toggleWindowFullScreen() {
priv.requestWindowModeChange(!isFullScreen)
// If we succeeded, place a dummy item onto the stack as
// a state indicator to prevent returning to windowed mode
// when popping an item on top. The corresponding pop will
// be made within requestWindowModeChange.
if (isFullScreen) {
priv.fullScreenItems.push({ "item": appWindow })
}
}
property var data: QtObject {
id: priv
// Used to store the last windowed mode visibility.
property int windowedVisibility
// An stack of items that are fullscreened.
property variant fullScreenItems: []
// When fullScreenItems is changed, we can recompute isCallFullscreen.
onFullScreenItemsChanged: {
isCallFullscreen = fullScreenItems
.filter(o => o.item instanceof OngoingCallPage)
.length
}
// Listen for a hangup combined with a fullscreen call state and
// remove the OngoingCallPage component.
property var data: Connections {
target: CallAdapter
function onHasCallChanged() {
if (!CallAdapter.hasCall && isCallFullscreen) {
priv.fullScreenItems.forEach(o => {
if (o.item instanceof OngoingCallPage) {
popFullScreenItem(o)
return
}
});
}
}
}
// Used internally to switch modes.
function requestWindowModeChange(fullScreen) {
if (fullScreen) {
if (!isFullScreen) {
// Save the previous visibility state.
windowedVisibility = visibility
showFullScreen()
}
} else {
// Clear the stack.
while (fullScreenItems.length) {
popFullScreenItem()
}
}
}
}
}
......@@ -33,6 +33,7 @@ import net.jami.Helpers 1.1
import net.jami.Constants 1.1
import "mainview"
import "mainview/components"
import "wizardview"
import "commoncomponents"
......@@ -46,15 +47,9 @@ ApplicationWindow {
None
}
property ApplicationWindow appWindow : root
property bool isFullscreen: visibility === Window.FullScreen
function toggleFullScreen() {
if (isFullscreen) {
showNormal()
} else {
showFullScreen()
}
property ApplicationWindow appWindow: root
property LayoutManager layoutManager: LayoutManager {
appContainer: appContainer
}
function checkLoadedSource() {
......@@ -165,17 +160,13 @@ ApplicationWindow {
function onRestoreAppRequested() {
requestActivate()
if (visibility === Window.Hidden || visibility === Window.Minimized) {
showNormal()
}
layoutManager.restoreApp()
}
function onNotificationClicked() {
requestActivate()
raise()
if (visibility === Window.Hidden || visibility === Window.Minimized) {
showNormal()
}
layoutManager.restoreApp()
}
}
......
......@@ -35,7 +35,6 @@ Loader {
property bool showTime: false
property int seq: MsgSeq.single
property string author: Author
property bool changeWindowVisibility: false
width: ListView.view ? ListView.view.width : 0
......@@ -279,7 +278,7 @@ Loader {
settings.fullScreenSupportEnabled: mediaInfo.isVideo
settings.javascriptCanOpenWindows: false
Component.onCompleted: loadHtml(mediaInfo.html, 'file://')
layer.enabled: parent !== appContainer && !appWindow.isFullscreen
layer.enabled: !isFullScreen
layer.effect: OpacityMask {
maskSource: MessageBubble {
out: isOutgoing
......@@ -289,43 +288,20 @@ Loader {
radius: msgRadius
}
}
function leaveFullScreen() {
parent = localMediaCompLoader
if (root.changeWindowVisibility) {
root.changeWindowVisibility = false
appWindow.showNormal()
}
}
onFullScreenRequested: function(request) {
if (JamiQmlUtils.callIsFullscreen)
return
if (request.toggleOn) {
parent = appContainer
if (!appWindow.isFullscreen) {
root.changeWindowVisibility = true
appWindow.showFullScreen()
}
} else {
leaveFullScreen()
layoutManager.pushFullScreenItem(
this,
localMediaCompLoader,
null,
function() { wev.fullScreenCancelled() })
} else if (!request.toggleOn) {
layoutManager.removeFullScreenItem(this)
}
request.accept()
}
Connections {
target: appWindow
function onVisibilityChanged() {
if (wev.isFullScreen && !appWindow.isFullScreen) {
wev.fullScreenCancelled()
leaveFullScreen()
}
}
}
}
}
Component {
id: animatedImageComp
AnimatedImage {
......
......@@ -33,9 +33,6 @@ Item {
property var mainApplicationScreen: ""
property bool callIsFullscreen: false
signal fullScreenCallEnded
property var accountCreationInputParaObject: ({})
function setUpAccountCreationInputPara(inputPara) {
......@@ -71,15 +68,6 @@ Item {
}
}
Connections {
target: CallAdapter
function onHasCallChanged() {
if (!CallAdapter.hasCall && callIsFullscreen)
fullScreenCallEnded()
}
}
Text {
id: globalTextMetrics
}
......
......@@ -528,13 +528,13 @@ Rectangle {
Shortcut {
sequence: "F11"
context: Qt.ApplicationShortcut
onActivated: {
// Don't toggle fullscreen mode when we're already
// in a fullscreen call.
if (JamiQmlUtils.callIsFullscreen)
return
appWindow.toggleFullScreen()
}
onActivated: layoutManager.toggleWindowFullScreen()
}
Shortcut {
sequence: "Escape"
context: Qt.ApplicationShortcut
onActivated: layoutManager.popFullScreenItem()
}
Shortcut {
......@@ -556,17 +556,6 @@ Rectangle {
onActivated: startWizard()
}
Shortcut {
sequence: "Escape"
context: Qt.ApplicationShortcut
onActivated: {
if (JamiQmlUtils.callIsFullscreen)
callStackView.toggleFullScreen()
else if (appWindow.visibility === Window.FullScreen)
appWindow.toggleFullScreen()
}
}
Shortcut {
sequence: StandardKey.Quit
context: Qt.ApplicationShortcut
......
......@@ -29,7 +29,6 @@ Rectangle {
id: root
property bool isAudioOnly: false
property bool changeWindowVisibility: false
property var sipKeys: [
"1", "2", "3", "A",
"4", "5", "6", "B",
......@@ -111,36 +110,22 @@ Rectangle {
}
function toggleFullScreen() {
var callPage = callStackMainView.currentItem
if (!callPage)
return
// manual toggle here because of our fake fullscreen mode (F11)
// TODO: handle and save window states, not just a boolean isFullScreen
if (!appWindow.isFullscreen && !JamiQmlUtils.callIsFullscreen) {
root.changeWindowVisibility = true
appWindow.showFullScreen()
} else if (JamiQmlUtils.callIsFullscreen && root.changeWindowVisibility) {
root.changeWindowVisibility = false
appWindow.showNormal()
}
JamiQmlUtils.callIsFullscreen = !JamiQmlUtils.callIsFullscreen
callPage.parent = JamiQmlUtils.callIsFullscreen ?
appContainer :
callStackMainView
if (!root.isAudioOnly) {
ongoingCallPage.handleParticipantsInfo(CallAdapter.getConferencesInfos())
const transitionCb = function() {
if (!root.isAudioOnly) {
ongoingCallPage.handleParticipantsInfo(
CallAdapter.getConferencesInfos())
}
}
}
Connections {
target: JamiQmlUtils
function onFullScreenCallEnded() {
if (appWindow.isFullscreen) {
toggleFullScreen()
}
if (!layoutManager.isCallFullscreen) {
layoutManager.pushFullScreenItem(
callStackMainView.currentItem,
callStackMainView,
transitionCb,
transitionCb)
} else {
layoutManager.removeFullScreenItem(
callStackMainView.currentItem)
}
}
......
......@@ -88,9 +88,10 @@ ContextMenuAutoLoader {
GeneralMenuItem {
id: fullScreen
itemName: JamiQmlUtils.callIsFullscreen ?
JamiStrings.exitFullScreen : JamiStrings.fullScreen
iconSource: JamiQmlUtils.callIsFullscreen ?
itemName: layoutManager.callIsFullscreen ?
JamiStrings.exitFullScreen :
JamiStrings.fullScreen
iconSource: layoutManager.callIsFullscreen ?
JamiResources.close_fullscreen_24dp_svg :
JamiResources.open_in_full_24dp_svg
onClicked: {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment