From e1fbb3a7d4148622b7d9cb56cc0cf157be905b90 Mon Sep 17 00:00:00 2001
From: Ming Rui Zhang <mingrui.zhang@savoirfairelinux.com>
Date: Mon, 31 May 2021 14:04:38 -0400
Subject: [PATCH] misc: refinement for various context menus and share actions

Change-Id: I4e6fff2c74ce6ace1464fa6a4569e4b3fbfae68b
---
 qml.qrc                                       |   9 +-
 src/avadapter.cpp                             |  29 +-
 src/avadapter.h                               |   7 +-
 src/commoncomponents/BaseContextMenu.qml      |  67 -----
 src/commoncomponents/LineEditContextMenu.qml  |  88 +++---
 src/commoncomponents/MaterialLineEdit.qml     |   4 +-
 .../contextmenu/BaseContextMenu.qml           | 116 +++++++
 .../contextmenu/ContextMenuAutoLoader.qml     |  67 +++++
 .../{ => contextmenu}/GeneralMenuItem.qml     |  51 ++--
 .../GeneralMenuSeparator.qml                  |   8 +-
 .../js/contextmenugenerator.js                | 144 ---------
 src/constant/JamiStrings.qml                  |  15 +-
 src/constant/JamiTheme.qml                    |  10 +
 src/mainview/components/CallActionBar.qml     |   8 +-
 src/mainview/components/CallOverlay.qml       |   7 +-
 .../components/CallViewContextMenu.qml        | 282 ++++++++----------
 src/mainview/components/ContactSearchBar.qml  |   4 +-
 .../ConversationSmartListContextMenu.qml      | 199 ++++++------
 .../js/videodevicecontextmenuitemcreation.js  |  89 ------
 19 files changed, 542 insertions(+), 662 deletions(-)
 delete mode 100644 src/commoncomponents/BaseContextMenu.qml
 create mode 100644 src/commoncomponents/contextmenu/BaseContextMenu.qml
 create mode 100644 src/commoncomponents/contextmenu/ContextMenuAutoLoader.qml
 rename src/commoncomponents/{ => contextmenu}/GeneralMenuItem.qml (71%)
 rename src/commoncomponents/{ => contextmenu}/GeneralMenuSeparator.qml (83%)
 delete mode 100644 src/commoncomponents/js/contextmenugenerator.js
 delete mode 100644 src/mainview/js/videodevicecontextmenuitemcreation.js

diff --git a/qml.qrc b/qml.qrc
index 68a24f053..e65945ede 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -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>
diff --git a/src/avadapter.cpp b/src/avadapter.cpp
index fb7bfb730..c24a1e5c8 100644
--- a/src/avadapter.cpp
+++ b/src/avadapter.cpp
@@ -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())
diff --git a/src/avadapter.h b/src/avadapter.h
index 9f169df59..fc4b699be 100644
--- a/src/avadapter.h
+++ b/src/avadapter.h
@@ -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);
diff --git a/src/commoncomponents/BaseContextMenu.qml b/src/commoncomponents/BaseContextMenu.qml
deleted file mode 100644
index 636fc74e2..000000000
--- a/src/commoncomponents/BaseContextMenu.qml
+++ /dev/null
@@ -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
-        }
-    }
-}
diff --git a/src/commoncomponents/LineEditContextMenu.qml b/src/commoncomponents/LineEditContextMenu.qml
index c4e0d780a..1a1f6ff74 100644
--- a/src/commoncomponents/LineEditContextMenu.qml
+++ b/src/commoncomponents/LineEditContextMenu.qml
@@ -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()
-                                             })
-        }
+    // lineEdit (TextEdit) selection will be lost when menu is opened
+    property var lineEditObj
+    property var selectionStart
+    property var selectionEnd
+
+    property list<GeneralMenuItem> menuItems: [
+        GeneralMenuItem {
+            id: copy
+
+            canTrigger: lineEditObj.selectedText.length
+            itemName: JamiStrings.copy
+            onClicked: {
+                lineEditObj.copy()
+            }
+        },
+        GeneralMenuItem {
+            id: cut
 
-        ContextMenuGenerator.addMenuItem(qsTr("Paste"),
-                                         "",
-                                         function (){
-                                             lineEditObj.paste()
-                                         })
+            canTrigger: lineEditObj.selectedText.length
+            itemName: JamiStrings.cut
 
-        root.height = ContextMenuGenerator.getMenu().height
-        root.width = ContextMenuGenerator.getMenu().width
-        ContextMenuGenerator.getMenu().x = mouseEvent.x
-        ContextMenuGenerator.getMenu().y = mouseEvent.y
+            onClicked: {
+                lineEditObj.cut()
+            }
+        },
+        GeneralMenuItem {
+            id: paste
+
+            itemName: JamiStrings.paste
+            onClicked: {
+                lineEditObj.paste()
+            }
+        }
+    ]
 
-        // lineEdit (TextEdit) selection will be lost when menu is opened
-        var selectionStartTemp = lineEditObj.selectionStart
-        var selectionEndTemp = lineEditObj.selectionEnd
+    function openMenuAt(mouseEvent) {
+        x = mouseEvent.x
+        y = mouseEvent.y
 
-        ContextMenuGenerator.getMenu().open()
+        selectionStart = lineEditObj.selectionStart
+        selectionEnd = lineEditObj.selectionEnd
 
-        lineEditObj.select(selectionStartTemp, selectionEndTemp)
+        root.openMenu()
+
+        lineEditObj.select(selectionStart, selectionEnd)
     }
 
-    Component.onCompleted: {
-        ContextMenuGenerator.createBaseContextMenuObjects(root)
+    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
 }
diff --git a/src/commoncomponents/MaterialLineEdit.qml b/src/commoncomponents/MaterialLineEdit.qml
index 9a9c4e446..2b482d345 100644
--- a/src/commoncomponents/MaterialLineEdit.qml
+++ b/src/commoncomponents/MaterialLineEdit.qml
@@ -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)
     }
 }
diff --git a/src/commoncomponents/contextmenu/BaseContextMenu.qml b/src/commoncomponents/contextmenu/BaseContextMenu.qml
new file mode 100644
index 000000000..79c4e5508
--- /dev/null
+++ b/src/commoncomponents/contextmenu/BaseContextMenu.qml
@@ -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()
+        }
+    }
+}
diff --git a/src/commoncomponents/contextmenu/ContextMenuAutoLoader.qml b/src/commoncomponents/contextmenu/ContextMenuAutoLoader.qml
new file mode 100644
index 000000000..711842327
--- /dev/null
+++ b/src/commoncomponents/contextmenu/ContextMenuAutoLoader.qml
@@ -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)
+            }
+        }
+    }
+}
diff --git a/src/commoncomponents/GeneralMenuItem.qml b/src/commoncomponents/contextmenu/GeneralMenuItem.qml
similarity index 71%
rename from src/commoncomponents/GeneralMenuItem.qml
rename to src/commoncomponents/contextmenu/GeneralMenuItem.qml
index ac588ae34..173835590 100644
--- a/src/commoncomponents/GeneralMenuItem.qml
+++ b/src/commoncomponents/contextmenu/GeneralMenuItem.qml
@@ -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
+            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
 
diff --git a/src/commoncomponents/GeneralMenuSeparator.qml b/src/commoncomponents/contextmenu/GeneralMenuSeparator.qml
similarity index 83%
rename from src/commoncomponents/GeneralMenuSeparator.qml
rename to src/commoncomponents/contextmenu/GeneralMenuSeparator.qml
index 6ccac22e0..49f0e90f7 100644
--- a/src/commoncomponents/GeneralMenuSeparator.qml
+++ b/src/commoncomponents/contextmenu/GeneralMenuSeparator.qml
@@ -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
     }
 }
diff --git a/src/commoncomponents/js/contextmenugenerator.js b/src/commoncomponents/js/contextmenugenerator.js
deleted file mode 100644
index 4f5848e75..000000000
--- a/src/commoncomponents/js/contextmenugenerator.js
+++ /dev/null
@@ -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
-}
diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml
index 82ffef41f..6e29cec22 100644
--- a/src/constant/JamiStrings.qml
+++ b/src/constant/JamiStrings.qml
@@ -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")
diff --git a/src/constant/JamiTheme.qml b/src/constant/JamiTheme.qml
index 536c8b505..78d5f8255 100644
--- a/src/constant/JamiTheme.qml
+++ b/src/constant/JamiTheme.qml
@@ -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
diff --git a/src/mainview/components/CallActionBar.qml b/src/mainview/components/CallActionBar.qml
index a140ce95e..e5e18e712 100644
--- a/src/mainview/components/CallActionBar.qml
+++ b/src/mainview/components/CallActionBar.qml
@@ -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))
diff --git a/src/mainview/components/CallOverlay.qml b/src/mainview/components/CallOverlay.qml
index e4d68709e..3718d8546 100644
--- a/src/mainview/components/CallOverlay.qml
+++ b/src/mainview/components/CallOverlay.qml
@@ -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() }
         }
diff --git a/src/mainview/components/CallViewContextMenu.qml b/src/mainview/components/CallViewContextMenu.qml
index 351813b29..9de9ec41a 100644
--- a/src/mainview/components/CallViewContextMenu.qml
+++ b/src/mainview/components/CallViewContextMenu.qml
@@ -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()
-    }
-
-    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 () {
-                            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()
-        }
+    property list<GeneralMenuItem> menuItems: [
+        GeneralMenuItem {
+            id: resumePauseCall
 
-        ContextMenuGenerator.addMenuItem(
-                    localIsRecording ? JamiStrings.stopRec : JamiStrings.startRec,
-                    "qrc:/images/icons/av_icons/fiber_manual_record-24px.svg",
-                    function () {
-                        CallAdapter.recordThisCallToggle()
-                        localIsRecording = CallAdapter.isRecordingThisCall()
-                    }, JamiTheme.recordIconColor)
-
-        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()
-                        })
-
-        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()
-                        })
-
-            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 () {
-                                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 () {
-                                if (Qt.platform.os !== "windows") {
-                                    AvAdapter.shareScreenArea(0, 0, 0, 0)
-                                } else {
-                                    ScreenRubberBandCreation.createScreenRubberBandWindowObject()
-                                    ScreenRubberBandCreation.showScreenRubberBandWindow()
-                                }
-                            })
+            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()
+            }
+        },
+        GeneralMenuItem {
+            id: inputPanelSIP
+
+            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()
+            }
+        },
+        GeneralMenuItem {
+            id: fullScreen
+
+            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
+
+            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
+
+            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()
+                }
+            }
+        },
+        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
+
+            canTrigger: !isAudioOnly && !isSIP
+            itemName: JamiStrings.shareFile
+            iconSource: "qrc:/images/icons/insert_photo-24px.svg"
+            onClicked: {
+                jamiFileDialog.open()
+            }
+        },
+        GeneralMenuItem {
+            id: viewPlugin
+
+            canTrigger: UtilsAdapter.checkShowPluginsButton(true)
+            itemName: JamiStrings.viewPlugin
+            iconSource: "qrc:/images/icons/extension_24dp.svg"
+            onClicked: {
+                root.pluginItemClicked()
             }
-
-            ContextMenuGenerator.addMenuItem(
-                        JamiStrings.shareFile,
-                        "qrc:/images/icons/insert_photo-24px.svg", function () {
-                            jamiFileDialog.open()
-                        })
-        }
-
-        if (UtilsAdapter.checkShowPluginsButton(true)) {
-            ContextMenuGenerator.addMenuItem(
-                        JamiStrings.viewPlugin,
-                        "qrc:/images/icons/extension_24dp.svg", function () {
-                            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
 }
diff --git a/src/mainview/components/ContactSearchBar.qml b/src/mainview/components/ContactSearchBar.qml
index 1f9dbc754..ef817b4bb 100644
--- a/src/mainview/components/ContactSearchBar.qml
+++ b/src/mainview/components/ContactSearchBar.qml
@@ -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)
         }
     }
 
diff --git a/src/mainview/components/ConversationSmartListContextMenu.qml b/src/mainview/components/ConversationSmartListContextMenu.qml
index c4a3bce54..119eb76b8 100644
--- a/src/mainview/components/ConversationSmartListContextMenu.qml
+++ b/src/mainview/components/ConversationSmartListContextMenu.qml
@@ -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
+    property bool hasCall: {
+        if (responsibleAccountId && responsibleConvUid)
+            return UtilsAdapter.getCallId(responsibleAccountId,
+                                          responsibleConvUid) !== ""
+        return false
+    }
+
+    property list<GeneralMenuItem> menuItems: [
+        GeneralMenuItem {
+            id: startVideoCallItem
 
-    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)
-                                                 })
+            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
 
-        } else {
-            ContextMenuGenerator.addMenuItem(JamiStrings.hangup,
-                                             "qrc:/images/icons/ic_call_end_white_24px.svg",
-                                             function (){
-                                                 CallAdapter.hangUpACall(responsibleAccountId,
-                                                                         responsibleConvUid)
-                                             })
-        }
+            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
 
-        if ((contactType === Profile.Type.RING || contactType === Profile.Type.PENDING
-             || contactType === Profile.Type.TEMPORARY)) {
-            if (contactType === Profile.Type.PENDING || !hasCall) {
-                ContextMenuGenerator.addMenuSeparator()
+            canTrigger: !hasCall
+            itemName: JamiStrings.clearConversation
+            iconSource: "qrc:/images/icons/ic_clear_24px.svg"
+            onClicked: {
+                MessagesAdapter.clearConversationHistory(responsibleAccountId,
+                                                         responsibleConvUid)
             }
+        },
+        GeneralMenuItem {
+            id: removeContact
 
-            if (contactType === Profile.Type.PENDING) {
-                ContextMenuGenerator.addMenuItem(JamiStrings.acceptContactRequest,
-                                                 "qrc:/images/icons/add_people-24px.svg",
-                                                 function (){
-                                                     MessagesAdapter.acceptInvitation(
-                                                                 responsibleConvUid)
-                                                     communicationPageMessageWebView.
-                                                     setSendContactRequestButtonVisible(false)
-                                                 })
-
-                ContextMenuGenerator.addMenuItem(JamiStrings.declineContactRequest,
-                                                 "qrc:/images/icons/round-close-24px.svg",
-                                                 function (){
-                                                     MessagesAdapter.refuseInvitation(
-                                                                 responsibleConvUid)
-                                                 })
+            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)
             }
-            if (!hasCall) {
-                ContextMenuGenerator.addMenuItem(qsTr("Block contact"),
-                                                 "qrc:/images/icons/ic_block_24px.svg",
-                                                 function (){
-                                                     MessagesAdapter.blockConversation(
-                                                                 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)
             }
-            ContextMenuGenerator.addMenuSeparator()
-            ContextMenuGenerator.addMenuItem(qsTr("Contact details"),
-                                             "qrc:/images/icons/person-24px.svg",
-                                             function (){
-                                                 userProfile.open()
-                                             })
-        }
+        },
+        GeneralMenuItem {
+            id: acceptContactRequest
 
-        root.height = ContextMenuGenerator.getMenu().height
-        root.width = ContextMenuGenerator.getMenu().width
-        ContextMenuGenerator.getMenu().open()
-    }
+            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
 
-    Component.onCompleted: {
-        ContextMenuGenerator.createBaseContextMenuObjects(root)
-    }
+            canTrigger: contactType === Profile.Type.PENDING
+            itemName: JamiStrings.declineContactRequest
+            iconSource: "qrc:/images/icons/round-close-24px.svg"
+            onClicked: {
+                MessagesAdapter.refuseInvitation(responsibleConvUid)
+            }
+        },
+        GeneralMenuItem {
+            id: blockContact
+
+            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)
+            }
+        },
+        GeneralMenuItem {
+            id: contactDetails
+
+            canTrigger: contactType !== Profile.Type.SIP
+            itemName: JamiStrings.contactDetails
+            iconSource: "qrc:/images/icons/person-24px.svg"
+            onClicked: {
+                userProfile.open()
+            }
+        }
+    ]
+
+    Component.onCompleted: menuItemsToLoad = menuItems
 }
diff --git a/src/mainview/js/videodevicecontextmenuitemcreation.js b/src/mainview/js/videodevicecontextmenuitemcreation.js
deleted file mode 100644
index ca17fc7cf..000000000
--- a/src/mainview/js/videodevicecontextmenuitemcreation.js
+++ /dev/null
@@ -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 = []
-}
-- 
GitLab