1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-04-21 21:52:03 +02:00

misc: refinement for various context menus and share actions

Change-Id: I4e6fff2c74ce6ace1464fa6a4569e4b3fbfae68b
This commit is contained in:
Ming Rui Zhang 2021-05-31 14:04:38 -04:00 committed by Andreas Traczyk
parent 8325a6ef51
commit e1fbb3a7d4
19 changed files with 547 additions and 667 deletions

View file

@ -17,13 +17,9 @@
<file>src/commoncomponents/PushButton.qml</file>
<file>src/commoncomponents/JamiFileDialog.qml</file>
<file>src/commoncomponents/TintedButton.qml</file>
<file>src/commoncomponents/GeneralMenuItem.qml</file>
<file>src/commoncomponents/GeneralMenuSeparator.qml</file>
<file>src/commoncomponents/AccountMigrationDialog.qml</file>
<file>src/commoncomponents/MaterialButton.qml</file>
<file>src/commoncomponents/ElidedTextLabel.qml</file>
<file>src/commoncomponents/js/contextmenugenerator.js</file>
<file>src/commoncomponents/BaseContextMenu.qml</file>
<file>src/commoncomponents/SpinnerButton.qml</file>
<file>src/commoncomponents/UsernameLineEdit.qml</file>
<file>src/commoncomponents/Scaffold.qml</file>
@ -125,7 +121,6 @@
<file>src/mainview/components/RecordBox.qml</file>
<file>src/mainview/components/SipInputPanel.qml</file>
<file>src/mainview/components/ParticipantOverlayMenu.qml</file>
<file>src/mainview/js/videodevicecontextmenuitemcreation.js</file>
<file>src/mainview/js/selectscreenwindowcreation.js</file>
<file>src/mainview/js/screenrubberbandcreation.js</file>
<file>src/mainview/js/contactpickercreation.js</file>
@ -145,5 +140,9 @@
<file>src/mainview/components/ParticipantCallInStatusView.qml</file>
<file>src/settingsview/components/TroubleshootSettings.qml</file>
<file>src/settingsview/components/LogsView.qml</file>
<file>src/commoncomponents/contextmenu/ContextMenuAutoLoader.qml</file>
<file>src/commoncomponents/contextmenu/BaseContextMenu.qml</file>
<file>src/commoncomponents/contextmenu/GeneralMenuItem.qml</file>
<file>src/commoncomponents/contextmenu/GeneralMenuSeparator.qml</file>
</qresource>
</RCC>

View file

@ -46,33 +46,6 @@ AvAdapter::AvAdapter(LRCInstance* instance, QObject* parent)
});
}
QVariantMap
AvAdapter::populateVideoDeviceContextMenuItem()
{
auto activeDevice = lrcInstance_->avModel().getCurrentVideoCaptureDevice();
/*
* Create a list of video input devices.
*/
QVariantMap deciveContextMenuNeededInfo;
auto devices = lrcInstance_->avModel().getDevices();
for (int i = 0; i < devices.size(); i++) {
try {
auto settings = lrcInstance_->avModel().getDeviceSettings(devices[i]);
deciveContextMenuNeededInfo[settings.name] = QVariant(settings.id == activeDevice);
} catch (...) {
qDebug().noquote() << "Error in getting device settings";
}
}
/*
* Add size parameter into the map since in qml there is no way to get the size.
*/
deciveContextMenuNeededInfo["size"] = QVariant(deciveContextMenuNeededInfo.size());
return deciveContextMenuNeededInfo;
}
void
AvAdapter::selectVideoInputDeviceByName(const QString& deviceName)
{
@ -242,7 +215,7 @@ AvAdapter::shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned heig
}
void
AvAdapter::stopSharingScreen()
AvAdapter::stopSharing()
{
auto callId = getCurrentCallId();
if (!callId.isEmpty())

View file

@ -45,9 +45,6 @@ Q_SIGNALS:
protected:
void safeInit() override {};
// Return needed info for populating video device context menu item.
Q_INVOKABLE QVariantMap populateVideoDeviceContextMenuItem();
// switch preview video input by device name
Q_INVOKABLE void selectVideoInputDeviceByName(const QString& deviceName);
@ -72,8 +69,8 @@ protected:
// Select screen area to display (from all screens).
Q_INVOKABLE void shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned height);
// Stop sharing the screen
Q_INVOKABLE void stopSharingScreen();
// Stop sharing the screen or file
Q_INVOKABLE void stopSharing();
Q_INVOKABLE void startAudioMeter(bool async);
Q_INVOKABLE void stopAudioMeter(bool async);

View file

@ -1,67 +0,0 @@
/*
* Copyright (C) 2020 by Savoir-faire Linux
* Author: Mingrui Zhang <mingrui.zhang@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 2.14
import QtQuick.Controls 2.14
import QtGraphicalEffects 1.14
import net.jami.Constants 1.0
Menu {
id: root
property int menuItemsPreferredWidth: 220
property int menuItemsPreferredHeight: 48
property int generalMenuSeparatorCount: 0
property int commonBorderWidth: 1
font.pointSize: JamiTheme.menuFontSize
modal: true
Overlay.modal: Rectangle {
color: "transparent"
}
// TODO: investigate
function openMenu(){
visible = true
visible = false
visible = true
}
background: Rectangle {
id: container
implicitWidth: menuItemsPreferredWidth
implicitHeight: menuItemsPreferredHeight
* (root.count - generalMenuSeparatorCount)
border.width: commonBorderWidth
border.color: JamiTheme.tabbarBorderColor
color: JamiTheme.backgroundColor
layer.enabled: true
layer.effect: DropShadow {
z: -1
horizontalOffset: 3.0
verticalOffset: 3.0
radius: 16.0
samples: 16
color: JamiTheme.shadowColor
}
}
}

View file

@ -24,49 +24,69 @@ import net.jami.Models 1.0
import net.jami.Adapters 1.0
import net.jami.Constants 1.0
import "js/contextmenugenerator.js" as ContextMenuGenerator
import "contextmenu"
Item {
ContextMenuAutoLoader {
id: root
function openMenu(lineEditObj, mouseEvent) {
ContextMenuGenerator.initMenu(Qt.size(150, 25), 2)
if (lineEditObj.selectedText.length) {
ContextMenuGenerator.addMenuItem(qsTr("Copy"),
"",
function (){
lineEditObj.copy()
})
ContextMenuGenerator.addMenuItem(qsTr("Cut"),
"",
function (){
lineEditObj.cut()
})
}
ContextMenuGenerator.addMenuItem(qsTr("Paste"),
"",
function (){
lineEditObj.paste()
})
root.height = ContextMenuGenerator.getMenu().height
root.width = ContextMenuGenerator.getMenu().width
ContextMenuGenerator.getMenu().x = mouseEvent.x
ContextMenuGenerator.getMenu().y = mouseEvent.y
// lineEdit (TextEdit) selection will be lost when menu is opened
var selectionStartTemp = lineEditObj.selectionStart
var selectionEndTemp = lineEditObj.selectionEnd
property var lineEditObj
property var selectionStart
property var selectionEnd
ContextMenuGenerator.getMenu().open()
property list<GeneralMenuItem> menuItems: [
GeneralMenuItem {
id: copy
lineEditObj.select(selectionStartTemp, selectionEndTemp)
canTrigger: lineEditObj.selectedText.length
itemName: JamiStrings.copy
onClicked: {
lineEditObj.copy()
}
},
GeneralMenuItem {
id: cut
Component.onCompleted: {
ContextMenuGenerator.createBaseContextMenuObjects(root)
canTrigger: lineEditObj.selectedText.length
itemName: JamiStrings.cut
onClicked: {
lineEditObj.cut()
}
},
GeneralMenuItem {
id: paste
itemName: JamiStrings.paste
onClicked: {
lineEditObj.paste()
}
}
]
function openMenuAt(mouseEvent) {
x = mouseEvent.x
y = mouseEvent.y
selectionStart = lineEditObj.selectionStart
selectionEnd = lineEditObj.selectionEnd
root.openMenu()
lineEditObj.select(selectionStart, selectionEnd)
}
contextMenuItemPreferredHeight: JamiTheme.lineEditContextMenuItemsHeight
contextMenuItemPreferredWidth: JamiTheme.lineEditContextMenuItemsWidth
contextMenuSeparatorPreferredHeight: JamiTheme.lineEditContextMenuSeparatorsHeight
Connections {
target: root.item
enabled: root.status === Loader.Ready
function onOpened() {
lineEditObj.select(selectionStart, selectionEnd)
}
}
Component.onCompleted: menuItemsToLoad = menuItems
}

View file

@ -83,6 +83,8 @@ TextField {
LineEditContextMenu {
id: lineEditContextMenu
lineEditObj: root
}
Image {
@ -142,6 +144,6 @@ TextField {
onReleased: {
if (event.button == Qt.RightButton)
lineEditContextMenu.openMenu(root, event)
lineEditContextMenu.openMenuAt(event)
}
}

View file

@ -0,0 +1,116 @@
/*
* Copyright (C) 2020 by Savoir-faire Linux
* Author: Mingrui Zhang <mingrui.zhang@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 2.14
import QtQuick.Controls 2.14
import QtGraphicalEffects 1.14
import net.jami.Constants 1.0
Menu {
id: root
property int menuPreferredWidth: 0
property int menuItemsPreferredHeight: 0
property int menuSeparatorPreferredHeight: 0
property GeneralMenuSeparator menuTopBorder: GeneralMenuSeparator {
separatorPreferredWidth: menuPreferredWidth ?
menuPreferredWidth : JamiTheme.menuItemsPreferredWidth
separatorPreferredHeight: menuSeparatorPreferredHeight ?
menuSeparatorPreferredHeight : JamiTheme.menuBorderPreferredHeight
separatorColor: "transparent"
}
property GeneralMenuSeparator menuBottomBorder: GeneralMenuSeparator {
separatorPreferredWidth: menuPreferredWidth ?
menuPreferredWidth : JamiTheme.menuItemsPreferredWidth
separatorPreferredHeight: menuSeparatorPreferredHeight ?
menuSeparatorPreferredHeight : JamiTheme.menuBorderPreferredHeight
separatorColor: "transparent"
}
property var generalMenuSeparatorList: []
function loadMenuItems(menuItems) {
root.addItem(menuTopBorder)
for (var i = 0; i < menuItems.length; ++i) {
if (menuItems[i].canTrigger) {
menuItems[i].parentMenu = root
root.addItem(menuItems[i])
if (menuPreferredWidth)
menuItems[i].itemPreferredWidth = menuPreferredWidth
if (menuItemsPreferredHeight)
menuItems[i].itemPreferredHeight = menuItemsPreferredHeight
}
if (menuItems[i].addMenuSeparatorAfter) {
// If the QML file to be loaded is a local file,
// you could omit the finishCreation() function
var menuSeparatorComponent = Qt.createComponent(
"GeneralMenuSeparator.qml",
Component.PreferSynchronous, root)
var menuSeparatorComponentObj = menuSeparatorComponent.createObject()
generalMenuSeparatorList.push(menuSeparatorComponentObj)
root.addItem(menuSeparatorComponentObj)
}
}
root.addItem(menuBottomBorder)
root.open()
}
onVisibleChanged: {
if (!visible)
root.close()
}
modal: true
Overlay.modal: Rectangle {
color: "transparent"
}
font.pointSize: JamiTheme.menuFontSize
background: Rectangle {
id: container
implicitWidth: menuPreferredWidth ? menuPreferredWidth : JamiTheme.menuItemsPreferredWidth
border.width: JamiTheme.menuItemsCommonBorderWidth
border.color: JamiTheme.tabbarBorderColor
color: JamiTheme.backgroundColor
layer.enabled: true
layer.effect: DropShadow {
z: -1
horizontalOffset: 3.0
verticalOffset: 3.0
radius: 16.0
samples: 16
color: JamiTheme.shadowColor
}
}
Component.onDestruction: {
for (var i = 0; i < generalMenuSeparatorList.length; ++i) {
generalMenuSeparatorList[i].destroy()
}
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 by Savoir-faire Linux
* Author: Mingrui Zhang <mingrui.zhang@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 2.14
import QtQuick.Controls 2.14
import QtGraphicalEffects 1.14
import net.jami.Models 1.0
import net.jami.Adapters 1.0
import net.jami.Constants 1.0
import "../../commoncomponents"
import "../../commoncomponents/contextmenu"
Loader {
id: root
// Cannot have menuItemsToLoad directly assigned as list<GeneralMenuItem>
// https://stackoverflow.com/questions/26733011/how-to-declare-list-property-in-qml
property var menuItemsToLoad
property int contextMenuItemPreferredWidth: 0
property int contextMenuItemPreferredHeight: 0
property int contextMenuSeparatorPreferredHeight: 0
function openMenu() {
root.active = true
root.sourceComponent = menuComponent
}
Connections {
target: root.item
enabled: root.status === Loader.Ready
function onClosed() {
root.active = false
}
}
Component {
id: menuComponent
BaseContextMenu {
id: contextMenu
Component.onCompleted: {
contextMenu.menuPreferredWidth = contextMenuItemPreferredWidth
contextMenu.menuItemsPreferredHeight = contextMenuItemPreferredHeight
contextMenu.menuSeparatorPreferredHeight = contextMenuSeparatorPreferredHeight
contextMenu.loadMenuItems(menuItemsToLoad)
}
}
}
}

View file

@ -23,6 +23,8 @@ import QtGraphicalEffects 1.14
import net.jami.Constants 1.0
import "../../commoncomponents"
// General menu item.
// Can control top, bottom, left, right border width.
// Use onClicked slot to simulate item click event.
@ -31,13 +33,16 @@ MenuItem {
id: menuItem
property string itemName: ""
property string iconSource: ""
property alias iconSource: contextMenuItemImage.source
property string iconColor: ""
property int preferredWidth: 220
property int preferredHeight: 48
property bool canTrigger: true
property bool addMenuSeparatorAfter: false
property BaseContextMenu parentMenu
property int leftBorderWidth: 0
property int rightBorderWidth: 0
property int itemPreferredWidth: JamiTheme.menuItemsPreferredWidth
property int itemPreferredHeight: JamiTheme.menuItemsPreferredHeight
property int leftBorderWidth: JamiTheme.menuItemsCommonBorderWidth
property int rightBorderWidth: JamiTheme.menuItemsCommonBorderWidth
signal clicked
@ -57,29 +62,25 @@ MenuItem {
ResponsiveImage {
id: contextMenuItemImage
anchors.left: menuItemContentRect.left
anchors.leftMargin: (visible ? 24 : 0)
anchors.left: status === Image.Ready ? menuItemContentRect.left : undefined
anchors.leftMargin: (status === Image.Ready ? 24 : 0)
anchors.verticalCenter: menuItemContentRect.verticalCenter
width: (visible ? 24 : 0)
height: (visible ? 24 : 0)
color: iconColor !== "" ? iconColor : JamiTheme.textColor
visible: false
smooth: true
opacity: 0.7
}
Text {
id: contextMenuItemText
anchors.left: contextMenuItemImage.right
anchors.leftMargin: contextMenuItemImage.visible ? 20 : 5
anchors.left: contextMenuItemImage.status === Image.Ready ?
contextMenuItemImage.right : menuItemContentRect.left
anchors.leftMargin: contextMenuItemImage.status === Image.Ready ? 20 : 10
anchors.verticalCenter: menuItemContentRect.verticalCenter
width: contextMenuItemImage.visible ?
(preferredWidth - contextMenuItemImage.width - 58) :
preferredWidth - 24
height: 30
height: itemPreferredHeight
text: itemName
color: JamiTheme.textColor
@ -89,7 +90,10 @@ MenuItem {
verticalAlignment: Text.AlignVCenter
}
onReleased: menuItem.clicked()
onReleased: {
menuItem.clicked()
parentMenu.close()
}
states: [
State {
@ -103,13 +107,6 @@ MenuItem {
]
}
onIconSourceChanged: {
if (iconSource !== "") {
contextMenuItemImage.source = iconSource
contextMenuItemImage.visible = true
}
}
highlighted: true
background: Rectangle {
@ -119,8 +116,8 @@ MenuItem {
anchors.leftMargin: leftBorderWidth
anchors.rightMargin: rightBorderWidth
implicitWidth: preferredWidth
implicitHeight: preferredHeight
implicitWidth: itemPreferredWidth
implicitHeight: itemPreferredHeight
border.width: 0

View file

@ -24,16 +24,16 @@ import net.jami.Constants 1.0
MenuSeparator {
id: menuSeparator
property int preferredWidth: 10
property int preferredHeight: 1
property int separatorPreferredWidth: JamiTheme.menuItemsPreferredWidth
property int separatorPreferredHeight: 1
property string separatorColor: JamiTheme.tabbarBorderColor
padding: 0
topPadding: 1
bottomPadding: 1
contentItem: Rectangle {
implicitWidth: preferredWidth
implicitHeight: preferredHeight
implicitWidth: separatorPreferredWidth
implicitHeight: separatorPreferredHeight
color: separatorColor
}
}

View file

@ -1,144 +0,0 @@
/*
* Copyright (C) 2020 by Savoir-faire Linux
* Author: Mingrui Zhang <mingrui.zhang@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/>.
*/
// Global base context menu, object variable for creation.
var baseContextMenuComponent
var baseContextMenuObject
var menuItemList = []
var menuDefaultSeparatorHeight = 8
function createBaseContextMenuObjects(parent) {
// If already created, return, since object cannot be destroyed.
if (baseContextMenuObject)
return
baseContextMenuComponent = Qt.createComponent("../BaseContextMenu.qml")
if (baseContextMenuComponent.status === Component.Ready)
finishCreation(parent)
else if (baseContextMenuComponent.status === Component.Error)
console.log("Error loading component:",
baseContextMenuComponent.errorString())
}
function finishCreation(parent) {
baseContextMenuObject = baseContextMenuComponent.createObject(parent)
if (baseContextMenuObject === null) {
// Error Handling.
console.log("Error creating object for base context menu")
}
baseContextMenuObject.closed.connect(function () {
// Remove the menu items when hidden.
for (var i = 0; i < menuItemList.length; i++) {
baseContextMenuObject.removeItem(menuItemList[i])
}
})
baseContextMenuObject.aboutToShow.connect(function () {
// Add default separator at the bottom.
addMenuSeparator(menuDefaultSeparatorHeight, "transparent")
})
}
function initMenu(preferedMenuItemSize, defaultSeparatorHeight) {
// Clear any existing items in the menu.
for (var i = 0; i < menuItemList.length; i++) {
baseContextMenuObject.removeItem(menuItemList[i])
}
if (preferedMenuItemSize) {
baseContextMenuObject.menuItemsPreferredWidth = preferedMenuItemSize.width
baseContextMenuObject.menuItemsPreferredHeight = preferedMenuItemSize.height
}
if (defaultSeparatorHeight)
menuDefaultSeparatorHeight = defaultSeparatorHeight
}
function addMenuSeparator(separatorHeight, separatorColor) {
var menuSeparatorObject
var menuSeparatorComponent = Qt.createComponent(
"../GeneralMenuSeparator.qml")
if (menuSeparatorComponent.status === Component.Ready) {
baseContextMenuObject.generalMenuSeparatorCount++
menuSeparatorObject = menuSeparatorComponent.createObject(
null, {
"preferredWidth": baseContextMenuObject.menuItemsPreferredWidth,
"preferredHeight": separatorHeight ? separatorHeight : 1
})
if (separatorColor)
menuSeparatorObject.separatorColor = separatorColor
} else if (menuSeparatorComponent.status === Component.Error)
console.log("Error loading component:",
menuSeparatorComponent.errorString())
if (menuSeparatorObject !== null) {
baseContextMenuObject.addItem(menuSeparatorObject)
menuItemList.push(menuSeparatorObject)
} else {
// Error handling.
console.log("Error creating object")
}
}
function addMenuItem(itemName, iconSource, onClickedCallback, iconColor) {
if (!baseContextMenuObject.count) {
// Add default separator at the top.
addMenuSeparator(menuDefaultSeparatorHeight, "transparent")
}
var menuItemObject
var menuItemComponent = Qt.createComponent("../GeneralMenuItem.qml")
if (menuItemComponent.status === Component.Ready) {
menuItemObject = menuItemComponent.createObject(
null, {
"itemName": itemName,
"iconSource": iconSource,
"iconColor": iconColor,
"preferredWidth": baseContextMenuObject.menuItemsPreferredWidth,
"preferredHeight": baseContextMenuObject.menuItemsPreferredHeight,
"leftBorderWidth": baseContextMenuObject.commonBorderWidth,
"rightBorderWidth": baseContextMenuObject.commonBorderWidth
})
} else if (menuItemComponent.status === Component.Error)
console.log("Error loading component:", menuItemComponent.errorString())
if (menuItemObject !== null) {
menuItemObject.clicked.connect(function () {
var callback = function () {
onClickedCallback()
baseContextMenuObject.onVisibleChanged.disconnect(callback)
baseContextMenuObject.close()
}
baseContextMenuObject.onVisibleChanged.connect(callback)
baseContextMenuObject.visible = false
})
menuItemObject.icon.color = "green"
baseContextMenuObject.addItem(menuItemObject)
menuItemList.push(menuItemObject)
} else {
// Error handling.
console.log("Error creating object")
}
}
function getMenu() {
return baseContextMenuObject
}

View file

@ -199,6 +199,19 @@ Item {
property string moreOptions: qsTr("More options")
property string mosaic: qsTr("Mosaic")
// LineEditContextMenu
property string copy: qsTr("Copy")
property string cut: qsTr("Cut")
property string paste: qsTr("Paste")
// ConversationContextMenu
property string startVideoCall: qsTr("Start video call")
property string startAudioCall: qsTr("Start audio call")
property string clearConversation: qsTr("Clear conversation")
property string removeContact: qsTr("Remove contact")
property string blockContact: qsTr("Block contact")
property string contactDetails: qsTr("Contact details")
// CallViewContextMenu
property string hold: qsTr("Hold")
property string sipInputPanel: qsTr("Sip input panel")
@ -208,7 +221,7 @@ Item {
property string exitFullScreen: qsTr("Exit full screen")
property string fullScreen: qsTr("Full screen")
property string shareScreen: qsTr("Share screen")
property string stopSharingScreen: qsTr("Stop sharing screen")
property string stopSharing: qsTr("Stop sharing screen or file")
property string shareScreenArea: qsTr("Share screen area")
property string shareFile: qsTr("Share file")
property string viewPlugin: qsTr("View plugin")

View file

@ -34,6 +34,7 @@ Item {
// General
property color blackColor: "#000000"
property color redColor: "red"
property color whiteColor: "#ffffff"
property color darkGreyColor: "#272727"
property color darkGreyColorOpacity: "#be272727" // 77%
@ -214,6 +215,11 @@ Item {
property int mosaicButtonPreferredWidth: 70
property int mosaicButtonMaxWidth: 100
property int menuItemsPreferredWidth: 220
property int menuItemsPreferredHeight: 48
property int menuItemsCommonBorderWidth: 1
property int menuBorderPreferredHeight: 8
property real maximumWidthSettingsView: 600
property real settingsHeaderpreferredHeight: 64
property real preferredFieldWidth: 256
@ -225,6 +231,10 @@ Item {
property real pluginHandlersPopupViewHeight: 200
property real pluginHandlersPopupViewDelegateHeight: 50
property real lineEditContextMenuItemsHeight: 15
property real lineEditContextMenuItemsWidth: 100
property real lineEditContextMenuSeparatorsHeight: 2
// main application spec
property real mainViewMinWidth: 300
property real mainViewMinHeight: 400

View file

@ -39,7 +39,7 @@ Control {
signal addToConferenceClicked
signal transferClicked // TODO: bind this
signal shareScreenClicked
signal stopSharingScreenClicked
signal stopSharingClicked
signal shareScreenAreaClicked // TODO: bind this
signal pluginsClicked
@ -189,7 +189,7 @@ Control {
Action {
id: shareAction
onTriggered: AvAdapter.currentRenderingDeviceType === Video.DeviceType.DISPLAY ?
root.stopSharingScreenClicked() :
root.stopSharingClicked() :
root.shareScreenClicked()
icon.source: AvAdapter.currentRenderingDeviceType === Video.DeviceType.DISPLAY ?
"qrc:/images/icons/share_stop_black_24dp.svg" :
@ -197,7 +197,7 @@ Control {
icon.color: AvAdapter.currentRenderingDeviceType === Video.DeviceType.DISPLAY ?
"red" : "white"
text: AvAdapter.currentRenderingDeviceType === Video.DeviceType.DISPLAY ?
JamiStrings.stopSharingScreen :
JamiStrings.stopSharing :
JamiStrings.shareScreen
property real size: 34
},
@ -248,7 +248,7 @@ Control {
if (isModerator && !isSIP)
CallOverlayModel.addSecondaryControl(addPersonAction)
CallOverlayModel.addSecondaryControl(chatAction)
if (!isAudioOnly)
if (!isAudioOnly && !isSIP)
CallOverlayModel.addSecondaryControl(shareAction)
CallOverlayModel.addSecondaryControl(recordAction)
if (UtilsAdapter.checkShowPluginsButton(true))

View file

@ -54,11 +54,6 @@ Item {
signal chatButtonClicked
onVisibleChanged: {
if (!visible)
callViewContextMenu.close()
}
ParticipantsLayer {
id: __participantsLayer
anchors.fill: parent
@ -195,7 +190,7 @@ Item {
function onAddToConferenceClicked() { openContactPicker(ContactList.CONFERENCE) }
function onTransferClicked() { openContactPicker(ContactList.TRANSFER) }
function onShareScreenClicked() { openShareScreen() }
function onStopSharingScreenClicked() { AvAdapter.stopSharingScreen() }
function onStopSharingClicked() { AvAdapter.stopSharing() }
function onShareScreenAreaClicked() { openShareScreenArea() }
function onPluginsClicked() { openPluginsMenu() }
}

View file

@ -27,12 +27,11 @@ import net.jami.Adapters 1.0
import net.jami.Constants 1.0
import "../../commoncomponents"
import "../../commoncomponents/js/contextmenugenerator.js" as ContextMenuGenerator
import "../js/videodevicecontextmenuitemcreation.js" as VideoDeviceContextMenuItemCreation
import "../../commoncomponents/contextmenu"
import "../js/selectscreenwindowcreation.js" as SelectScreenWindowCreation
import "../js/screenrubberbandcreation.js" as ScreenRubberBandCreation
Item {
ContextMenuAutoLoader {
id: root
property bool isSIP: false
@ -44,140 +43,130 @@ Item {
signal pluginItemClicked
signal transferCallButtonClicked
function close() {
// leave this debug line is a reminder of a design failure
console.debug("call view context menu close")
const menu = ContextMenuGenerator.getMenu()
if (menu)
menu.close()
}
property list<GeneralMenuItem> menuItems: [
GeneralMenuItem {
id: resumePauseCall
function openMenu() {
ContextMenuGenerator.initMenu()
if (isSIP) {
ContextMenuGenerator.addMenuItem(
isPaused ? JamiStrings.resumeCall : JamiStrings.pauseCall,
isPaused ? "qrc:/images/icons/play_circle_outline-24px.svg" :
"qrc:/images/icons/pause_circle_outline-24px.svg",
function () {
canTrigger: isSIP
itemName: isPaused ? JamiStrings.resumeCall : JamiStrings.pauseCall
iconSource: isPaused ? "qrc:/images/icons/play_circle_outline-24px.svg" :
"qrc:/images/icons/pause_circle_outline-24px.svg"
onClicked: {
CallAdapter.holdThisCallToggle()
})
ContextMenuGenerator.addMenuItem(JamiStrings.sipInputPanel,
"qrc:/images/icons/ic_keypad.svg",
function () {
sipInputPanel.open()
})
ContextMenuGenerator.addMenuItem(
JamiStrings.transferCall,
"qrc:/images/icons/phone_forwarded-24px.svg",
function () {
root.transferCallButtonClicked()
})
ContextMenuGenerator.addMenuSeparator()
}
},
GeneralMenuItem {
id: inputPanelSIP
ContextMenuGenerator.addMenuItem(
localIsRecording ? JamiStrings.stopRec : JamiStrings.startRec,
"qrc:/images/icons/av_icons/fiber_manual_record-24px.svg",
function () {
canTrigger: isSIP
itemName: JamiStrings.sipInputPanel
iconSource: "qrc:/images/icons/ic_keypad.svg"
onClicked: {
sipInputPanel.open()
}
},
GeneralMenuItem {
id: callTransfer
canTrigger: isSIP
itemName: JamiStrings.transferCall
iconSource: "qrc:/images/icons/phone_forwarded-24px.svg"
addMenuSeparatorAfter: isSIP
onClicked: {
root.transferCallButtonClicked()
}
},
GeneralMenuItem {
id: localRecord
itemName: localIsRecording ? JamiStrings.stopRec : JamiStrings.startRec
iconSource: "qrc:/images/icons/av_icons/fiber_manual_record-24px.svg"
iconColor: JamiTheme.recordIconColor
onClicked: {
CallAdapter.recordThisCallToggle()
localIsRecording = CallAdapter.isRecordingThisCall()
}, JamiTheme.recordIconColor)
}
},
GeneralMenuItem {
id: fullScreen
if (isAudioOnly && !isPaused)
ContextMenuGenerator.addMenuItem(
JamiQmlUtils.callIsFullscreen ? JamiStrings.exitFullScreen : JamiStrings.fullScreen,
JamiQmlUtils.callIsFullscreen ? "qrc:/images/icons/close_fullscreen-24px.svg" :
"qrc:/images/icons/open_in_full-24px.svg",
function () {
itemName: JamiQmlUtils.callIsFullscreen ?
JamiStrings.exitFullScreen : JamiStrings.fullScreen
iconSource: JamiQmlUtils.callIsFullscreen ?
"qrc:/images/icons/close_fullscreen-24px.svg" :
"qrc:/images/icons/open_in_full-24px.svg"
onClicked: {
callStackView.toggleFullScreen()
})
}
},
GeneralMenuItem {
id: stopSharing
if (!isAudioOnly && !isPaused) {
ContextMenuGenerator.addMenuItem(
JamiQmlUtils.callIsFullscreen ? JamiStrings.exitFullScreen : JamiStrings.fullScreen,
JamiQmlUtils.callIsFullscreen ? "qrc:/images/icons/close_fullscreen-24px.svg" :
"qrc:/images/icons/open_in_full-24px.svg",
function () {
callStackView.toggleFullScreen()
})
canTrigger: !isAudioOnly
&& AvAdapter.currentRenderingDeviceType === Video.DeviceType.DISPLAY
&& !isSIP
itemName: JamiStrings.stopSharing
iconSource: "qrc:/images/icons/share_stop_black_24dp.svg"
iconColor: JamiTheme.redColor
onClicked: {
AvAdapter.stopSharing()
}
},
GeneralMenuItem {
id: shareScreen
ContextMenuGenerator.addMenuSeparator()
generateDeviceMenuItem()
ContextMenuGenerator.addMenuSeparator()
if (AvAdapter.currentRenderingDeviceType === Video.DeviceType.DISPLAY) {
ContextMenuGenerator.addMenuItem(
JamiStrings.stopSharingScreen,
"qrc:/images/icons/share_stop_black_24dp.svg",
function () {
AvAdapter.stopSharingScreen()
})
} else {
ContextMenuGenerator.addMenuItem(
JamiStrings.shareScreen,
"qrc:/images/icons/share_screen_black_24dp.svg",
function () {
canTrigger: !isAudioOnly
&& AvAdapter.currentRenderingDeviceType !== Video.DeviceType.DISPLAY
&& !isSIP
itemName: JamiStrings.shareScreen
iconSource: "qrc:/images/icons/share_screen_black_24dp.svg"
onClicked: {
if (Qt.application.screens.length === 1) {
AvAdapter.shareEntireScreen(0)
} else {
SelectScreenWindowCreation.createSelectScreenWindowObject()
SelectScreenWindowCreation.showSelectScreenWindow()
}
})
ContextMenuGenerator.addMenuItem(
JamiStrings.shareScreenArea,
"qrc:/images/icons/share_screen_black_24dp.svg",
function () {
}
},
GeneralMenuItem {
id: shareScreenArea
canTrigger: !isAudioOnly
&& AvAdapter.currentRenderingDeviceType !== Video.DeviceType.DISPLAY
&& !isSIP
itemName: JamiStrings.shareScreenArea
iconSource: "qrc:/images/icons/share_screen_black_24dp.svg"
onClicked: {
if (Qt.platform.os !== "windows") {
AvAdapter.shareScreenArea(0, 0, 0, 0)
} else {
ScreenRubberBandCreation.createScreenRubberBandWindowObject()
ScreenRubberBandCreation.showScreenRubberBandWindow()
}
})
}
},
GeneralMenuItem {
id: shareFile
ContextMenuGenerator.addMenuItem(
JamiStrings.shareFile,
"qrc:/images/icons/insert_photo-24px.svg", function () {
canTrigger: !isAudioOnly && !isSIP
itemName: JamiStrings.shareFile
iconSource: "qrc:/images/icons/insert_photo-24px.svg"
onClicked: {
jamiFileDialog.open()
})
}
},
GeneralMenuItem {
id: viewPlugin
if (UtilsAdapter.checkShowPluginsButton(true)) {
ContextMenuGenerator.addMenuItem(
JamiStrings.viewPlugin,
"qrc:/images/icons/extension_24dp.svg", function () {
canTrigger: UtilsAdapter.checkShowPluginsButton(true)
itemName: JamiStrings.viewPlugin
iconSource: "qrc:/images/icons/extension_24dp.svg"
onClicked: {
root.pluginItemClicked()
})
}
root.height = ContextMenuGenerator.getMenu().height
root.width = ContextMenuGenerator.getMenu().width
ContextMenuGenerator.getMenu().open()
}
function generateDeviceMenuItem() {
var deviceContextMenuInfoMap = AvAdapter.populateVideoDeviceContextMenuItem()
// Somehow, the map size is undefined, so use this instead.
var mapSize = deviceContextMenuInfoMap["size"]
if (mapSize === 0)
VideoDeviceContextMenuItemCreation.createVideoDeviceContextMenuItemObjects(
JamiStrings.noVideoDevice, false)
for (var deviceName in deviceContextMenuInfoMap) {
if (deviceName === "size")
continue
VideoDeviceContextMenuItemCreation.createVideoDeviceContextMenuItemObjects(
deviceName, deviceContextMenuInfoMap[deviceName])
}
}
]
JamiFileDialog {
id: jamiFileDialog
@ -187,28 +176,5 @@ Item {
onAccepted: AvAdapter.shareFile(jamiFileDialog.file)
}
Component.onCompleted: {
ContextMenuGenerator.createBaseContextMenuObjects(root)
VideoDeviceContextMenuItemCreation.setVideoContextMenuObject(
ContextMenuGenerator.getMenu())
ContextMenuGenerator.getMenu().closed.connect(function () {
VideoDeviceContextMenuItemCreation.removeCreatedItems()
})
}
// TODO: In the future we want to implement this
// GeneralMenuItem {
// id: advancedInfosItem
// itemName: qsTr("Advanced informations")
// iconSource: "qrc:/images/icons/info-24px.svg"
// leftBorderWidth: commonBorderWidth
// rightBorderWidth: commonBorderWidth
// onClicked: {
// root.close()
// }
// }
Component.onCompleted: menuItemsToLoad = menuItems
}

View file

@ -48,6 +48,8 @@ Rectangle {
LineEditContextMenu {
id: lineEditContextMenu
lineEditObj: contactSearchBar
}
ResponsiveImage {
@ -99,7 +101,7 @@ Rectangle {
onTextChanged: root.contactSearchBarTextChanged(contactSearchBar.text)
onReleased: {
if (event.button == Qt.RightButton)
lineEditContextMenu.openMenu(contactSearchBar, event)
lineEditContextMenu.openMenuAt(event)
}
}

View file

@ -25,108 +25,131 @@ import net.jami.Adapters 1.0
import net.jami.Constants 1.0
import "../../commoncomponents"
import "../../commoncomponents/js/contextmenugenerator.js" as ContextMenuGenerator
import "../../commoncomponents/contextmenu"
Item {
ContextMenuAutoLoader {
id: root
property string responsibleAccountId: ""
property string responsibleConvUid: ""
property int contactType: Profile.Type.INVALID
function isOpen() { return ContextMenuGenerator.getMenu().visible }
function openMenu() {
ContextMenuGenerator.initMenu()
var hasCall = UtilsAdapter.getCallId(responsibleAccountId, responsibleConvUid) !== ""
if (!hasCall) {
ContextMenuGenerator.addMenuItem(qsTr("Start video call"),
"qrc:/images/icons/videocam-24px.svg",
function (){
LRCInstance.selectConversation(responsibleConvUid, responsibleAccountId)
CallAdapter.placeCall()
communicationPageMessageWebView.setSendContactRequestButtonVisible(false)
})
ContextMenuGenerator.addMenuItem(qsTr("Start audio call"),
"qrc:/images/icons/place_audiocall-24px.svg",
function (){
LRCInstance.selectConversation(responsibleConvUid, responsibleAccountId)
CallAdapter.placeAudioOnlyCall()
communicationPageMessageWebView.setSendContactRequestButtonVisible(false)
})
ContextMenuGenerator.addMenuItem(qsTr("Clear conversation"),
"qrc:/images/icons/ic_clear_24px.svg",
function (){
MessagesAdapter.clearConversationHistory(
responsibleAccountId,
responsibleConvUid)
})
if (contactType === Profile.Type.RING || contactType === Profile.Type.SIP) {
ContextMenuGenerator.addMenuItem(qsTr("Remove contact"),
"qrc:/images/icons/ic_hangup_participant-24px.svg",
function (){
MessagesAdapter.removeConversation(
responsibleAccountId,
responsibleConvUid)
})
property bool hasCall: {
if (responsibleAccountId && responsibleConvUid)
return UtilsAdapter.getCallId(responsibleAccountId,
responsibleConvUid) !== ""
return false
}
} else {
ContextMenuGenerator.addMenuItem(JamiStrings.hangup,
"qrc:/images/icons/ic_call_end_white_24px.svg",
function (){
property list<GeneralMenuItem> menuItems: [
GeneralMenuItem {
id: startVideoCallItem
canTrigger: !hasCall
itemName: JamiStrings.startVideoCall
iconSource: "qrc:/images/icons/videocam-24px.svg"
onClicked: {
LRCInstance.selectConversation(responsibleConvUid,
responsibleAccountId)
CallAdapter.placeCall()
communicationPageMessageWebView.setSendContactRequestButtonVisible(
false)
}
},
GeneralMenuItem {
id: startAudioCall
canTrigger: !hasCall
itemName: JamiStrings.startAudioCall
iconSource: "qrc:/images/icons/place_audiocall-24px.svg"
onClicked: {
LRCInstance.selectConversation(responsibleConvUid,
responsibleAccountId)
CallAdapter.placeAudioOnlyCall()
communicationPageMessageWebView.setSendContactRequestButtonVisible(
false)
}
},
GeneralMenuItem {
id: clearConversation
canTrigger: !hasCall
itemName: JamiStrings.clearConversation
iconSource: "qrc:/images/icons/ic_clear_24px.svg"
onClicked: {
MessagesAdapter.clearConversationHistory(responsibleAccountId,
responsibleConvUid)
}
},
GeneralMenuItem {
id: removeContact
canTrigger: !hasCall && (contactType === Profile.Type.RING
|| contactType === Profile.Type.SIP)
itemName: JamiStrings.removeContact
iconSource: "qrc:/images/icons/ic_hangup_participant-24px.svg"
onClicked: {
MessagesAdapter.removeConversation(responsibleAccountId,
responsibleConvUid)
}
},
GeneralMenuItem {
id: hangup
canTrigger: hasCall
itemName: JamiStrings.hangup
iconSource: "qrc:/images/icons/ic_call_end_white_24px.svg"
addMenuSeparatorAfter: contactType !== Profile.Type.SIP
&& (contactType === Profile.Type.PENDING
|| !hasCall)
onClicked: {
CallAdapter.hangUpACall(responsibleAccountId,
responsibleConvUid)
})
}
},
GeneralMenuItem {
id: acceptContactRequest
if ((contactType === Profile.Type.RING || contactType === Profile.Type.PENDING
|| contactType === Profile.Type.TEMPORARY)) {
if (contactType === Profile.Type.PENDING || !hasCall) {
ContextMenuGenerator.addMenuSeparator()
canTrigger: contactType === Profile.Type.PENDING
itemName: JamiStrings.acceptContactRequest
iconSource: "qrc:/images/icons/add_people-24px.svg"
onClicked: {
MessagesAdapter.acceptInvitation(responsibleConvUid)
communicationPageMessageWebView.setSendContactRequestButtonVisible(
false)
}
},
GeneralMenuItem {
id: declineContactRequest
if (contactType === Profile.Type.PENDING) {
ContextMenuGenerator.addMenuItem(JamiStrings.acceptContactRequest,
"qrc:/images/icons/add_people-24px.svg",
function (){
MessagesAdapter.acceptInvitation(
responsibleConvUid)
communicationPageMessageWebView.
setSendContactRequestButtonVisible(false)
})
canTrigger: contactType === Profile.Type.PENDING
itemName: JamiStrings.declineContactRequest
iconSource: "qrc:/images/icons/round-close-24px.svg"
onClicked: {
MessagesAdapter.refuseInvitation(responsibleConvUid)
}
},
GeneralMenuItem {
id: blockContact
ContextMenuGenerator.addMenuItem(JamiStrings.declineContactRequest,
"qrc:/images/icons/round-close-24px.svg",
function (){
MessagesAdapter.refuseInvitation(
responsibleConvUid)
})
canTrigger: !hasCall && contactType !== Profile.Type.SIP
itemName: JamiStrings.blockContact
iconSource: "qrc:/images/icons/ic_block_24px.svg"
addMenuSeparatorAfter: contactType !== Profile.Type.SIP
onClicked: {
MessagesAdapter.blockConversation(responsibleConvUid)
}
if (!hasCall) {
ContextMenuGenerator.addMenuItem(qsTr("Block contact"),
"qrc:/images/icons/ic_block_24px.svg",
function (){
MessagesAdapter.blockConversation(
responsibleConvUid)
})
}
ContextMenuGenerator.addMenuSeparator()
ContextMenuGenerator.addMenuItem(qsTr("Contact details"),
"qrc:/images/icons/person-24px.svg",
function (){
},
GeneralMenuItem {
id: contactDetails
canTrigger: contactType !== Profile.Type.SIP
itemName: JamiStrings.contactDetails
iconSource: "qrc:/images/icons/person-24px.svg"
onClicked: {
userProfile.open()
})
}
}
]
root.height = ContextMenuGenerator.getMenu().height
root.width = ContextMenuGenerator.getMenu().width
ContextMenuGenerator.getMenu().open()
}
Component.onCompleted: {
ContextMenuGenerator.createBaseContextMenuObjects(root)
}
Component.onCompleted: menuItemsToLoad = menuItems
}

View file

@ -1,89 +0,0 @@
/*
* Copyright (C) 2020 by Savoir-faire Linux
* Author: Mingrui Zhang <mingrui.zhang@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/>.
*/
/*
* Global storage for created video device context menu item,
* will be cleared once context menu is closed.
*/
var itemArray = []
/*
* Global videoDeviceContextMenuItem component, object variable for creation.
*/
var videoContextMenuObject
var videoDeviceContextMenuItemComponent
var videoDeviceContextMenuItemObject
/*
* Init videoContextMenuObject.
*/
function setVideoContextMenuObject(obj) {
videoContextMenuObject = obj
}
function createVideoDeviceContextMenuItemObjects(deviceName, setChecked) {
videoDeviceContextMenuItemComponent = Qt.createComponent(
"../components/VideoCallPageContextMenuDeviceItem.qml")
if (videoDeviceContextMenuItemComponent.status === Component.Ready)
finishCreation(deviceName, setChecked)
else if (videoDeviceContextMenuItemComponent.status === Component.Error)
console.log("Error loading component:",
videoDeviceContextMenuItemComponent.errorString())
}
function finishCreation(deviceName, setChecked) {
videoDeviceContextMenuItemObject = videoDeviceContextMenuItemComponent.createObject()
if (videoDeviceContextMenuItemObject === null) {
// Error Handling.
console.log("Error creating video context menu object")
}
videoDeviceContextMenuItemObject.leftBorderWidth =
videoContextMenuObject.commonBorderWidth
videoDeviceContextMenuItemObject.rightBorderWidth =
videoContextMenuObject.commonBorderWidth
videoDeviceContextMenuItemObject.itemName = deviceName
videoDeviceContextMenuItemObject.checkable = true
videoDeviceContextMenuItemObject.checked = setChecked
videoDeviceContextMenuItemObject.contextMenuPreferredWidth = videoContextMenuObject.implicitWidth
/*
* Push into the storage array, and insert it into context menu.
*/
itemArray.push(videoDeviceContextMenuItemObject)
videoContextMenuObject.addItem(videoDeviceContextMenuItemObject)
videoDeviceContextMenuItemObject.clicked.connect(function () {
videoContextMenuObject.close()
})
}
function removeCreatedItems() {
var arrayLength = itemArray.length
for (var i = 0; i < arrayLength; i++) {
videoContextMenuObject.removeItem(itemArray[i])
itemArray[i].destroy()
}
itemArray = []
}