mainview: make all context menus generated at run time with the same style

By giving a base context menu, all context menus are generated at run time
and kept the same style. Some issues are fixed along with the patch.

Gitlab: #8
Gitlab: #35
Change-Id: Ieb812420fcb44c33d161a62c8574f6705dc5e1a9
diff --git a/src/mainview/components/CallOverlayButtonGroup.qml b/src/mainview/components/CallOverlayButtonGroup.qml
index 107ab9e..88a097f 100644
--- a/src/mainview/components/CallOverlayButtonGroup.qml
+++ b/src/mainview/components/CallOverlayButtonGroup.qml
@@ -230,7 +230,7 @@
 
             onClicked: {
                 var rectPos = mapToItem(callStackViewWindow, optionsButton.x, optionsButton.y)
-                callViewContextMenu.activate()
+                callViewContextMenu.openMenu()
                 callViewContextMenu.x = rectPos.x + optionsButton.width/2 - callViewContextMenu.width/2
                 callViewContextMenu.y = rectPos.y - 12 - callViewContextMenu.height
             }
diff --git a/src/mainview/components/CallViewContextMenu.qml b/src/mainview/components/CallViewContextMenu.qml
index 2deeeaa..5b2272a 100644
--- a/src/mainview/components/CallViewContextMenu.qml
+++ b/src/mainview/components/CallViewContextMenu.qml
@@ -17,6 +17,7 @@
  * 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.12
@@ -24,61 +25,125 @@
 
 import "../../commoncomponents"
 
+import "../../commoncomponents/js/contextmenugenerator.js" as ContextMenuGenerator
 import "../js/videodevicecontextmenuitemcreation.js" as VideoDeviceContextMenuItemCreation
 import "../js/selectscreenwindowcreation.js" as SelectScreenWindowCreation
+import "../js/screenrubberbandcreation.js" as ScreenRubberBandCreation
 
-Menu {
+Item {
     id: root
 
-    property int generalMenuSeparatorCount: 0
-    property int commonBorderWidth: 1
-
-    signal pluginItemClicked
-
-    font.pointSize: JamiTheme.textFontSize+3
-
     property bool isSIP: false
     property bool isPaused: false
     property bool isAudioOnly: false
     property bool isRecording: false
 
+    signal pluginItemClicked
     signal transferCallButtonClicked
 
-    function activate() {
+    function openMenu(){
+        if (isSIP){
+            ContextMenuGenerator.addMenuItem(isPaused ? qsTr("Resume call") : qsTr("Hold call"),
+                                             isPaused ?
+                                                 "qrc:/images/icons/play_circle_outline-24px.svg" :
+                                                 "qrc:/images/icons/pause_circle_outline-24px.svg",
+                                             function (){
+                                                 CallAdapter.holdThisCallToggle()
+                                             })
+            ContextMenuGenerator.addMenuItem(qsTr("Sip Input Panel"),
+                                             "qrc:/images/icons/ic_keypad.svg",
+                                             function (){
+                                                 sipInputPanel.open()
+                                             })
+            ContextMenuGenerator.addMenuItem(qsTr("Transfer call"),
+                                             "qrc:/images/icons/phone_forwarded-24px.svg",
+                                             function (){
+                                                 root.transferCallButtonClicked()
+                                             })
+
+            ContextMenuGenerator.addMenuSeparator()
+        }
+
+        if (!isAudioOnly) {
+            ContextMenuGenerator.addMenuItem(isRecording ? qsTr("Stop recording") :
+                                                           qsTr("Start recording"),
+                                             "qrc:/images/icons/ic_video_call_24px.svg",
+                                             function (){
+                                                  CallAdapter.recordThisCallToggle()
+                                             })
+            ContextMenuGenerator.addMenuItem(videoCallPage.isFullscreen ? qsTr("Exit full screen") :
+                                                                          qsTr("Full screen mode"),
+                                             videoCallPage.isFullscreen ?
+                                                 "qrc:/images/icons/close_fullscreen-24px.svg" :
+                                                 "qrc:/images/icons/open_in_full-24px.svg",
+                                             function (){
+                                                  videoCallPageRect.needToShowInFullScreen()
+                                             })
+
+            ContextMenuGenerator.addMenuSeparator()
+
+            generateDeviceMenuItem()
+
+            ContextMenuGenerator.addMenuSeparator()
+
+            ContextMenuGenerator.addMenuItem(qsTr("Share entire screen"),
+                                             "qrc:/images/icons/screen_share-24px.svg",
+                                             function (){
+                                                 if (Qt.application.screens.length === 1) {
+                                                     AvAdapter.shareEntireScreen(0)
+                                                 } else {
+                                                     SelectScreenWindowCreation.createSelectScreenWindowObject()
+                                                     SelectScreenWindowCreation.showSelectScreenWindow()
+                                                 }
+                                             })
+            ContextMenuGenerator.addMenuItem(qsTr("Share screen area"),
+                                             "qrc:/images/icons/screen_share-24px.svg",
+                                             function (){
+                                                 if (Qt.application.screens.length === 1) {
+                                                     ScreenRubberBandCreation.createScreenRubberBandWindowObject(
+                                                                 null, 0)
+                                                     ScreenRubberBandCreation.showScreenRubberBandWindow()
+                                                 } else {
+                                                     SelectScreenWindowCreation.createSelectScreenWindowObject(true)
+                                                     SelectScreenWindowCreation.showSelectScreenWindow()
+                                                 }
+                                             })
+            ContextMenuGenerator.addMenuItem(qsTr("Share file"),
+                                             "qrc:/images/icons/insert_photo-24px.svg",
+                                             function (){
+                                                  jamiFileDialog.open()
+                                             })
+        }
+
+        ContextMenuGenerator.addMenuItem(qsTr("Toggle plugin"),
+                                         "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"]
 
-        var count = 2
+        if (mapSize === 0)
+            VideoDeviceContextMenuItemCreation.createVideoDeviceContextMenuItemObjects(
+                        qsTr("No video device"), false)
+
         for (var deviceName in deviceContextMenuInfoMap) {
-            if (deviceName === "size" || root.isAudioOnly)
+            if (deviceName === "size")
                 continue
-            if (videoDeviceItem.itemName === "No video device") {
-                videoDeviceItem.checkable = true
-                videoDeviceItem.itemName = deviceName
-                videoDeviceItem.checked = deviceContextMenuInfoMap[deviceName]
-                if (count === mapSize)
-                    root.open()
-            } else {
-                VideoDeviceContextMenuItemCreation.createVideoDeviceContextMenuItemObjects(
-                            deviceName, deviceContextMenuInfoMap[deviceName],
-                            count === mapSize)
-            }
-            count++
+            VideoDeviceContextMenuItemCreation.createVideoDeviceContextMenuItemObjects(
+                        deviceName, deviceContextMenuInfoMap[deviceName])
         }
-        root.open()
-    }
-
-    Component.onCompleted: {
-        VideoDeviceContextMenuItemCreation.setVideoContextMenuObject(root)
-    }
-
-
-    onClosed: {
-        videoDeviceItem.itemName = "No video device"
-        VideoDeviceContextMenuItemCreation.removeCreatedItems()
     }
 
     JamiFileDialog {
@@ -92,187 +157,13 @@
         }
     }
 
-    /*
-     * All GeneralMenuItems should remain the same width / height.
-     */
-    GeneralMenuItem {
-        id: holdCallButton
+    Component.onCompleted: {
+        ContextMenuGenerator.createBaseContextMenuObjects(root)
+        VideoDeviceContextMenuItemCreation.setVideoContextMenuObject(ContextMenuGenerator.getMenu())
 
-        visible: isSIP
-        height: isSIP? undefined : 0
-
-        itemName: isPaused? qsTr("Resume call") : qsTr("Hold call")
-        iconSource: isPaused? "qrc:/images/icons/play_circle_outline-24px.svg" : "qrc:/images/icons/pause_circle_outline-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            CallAdapter.holdThisCallToggle()
-            root.close()
-        }
-    }
-
-    GeneralMenuItem {
-        id: showSipInputPanelButton
-
-        visible: isSIP
-        height: isSIP? undefined : 0
-
-        itemName: qsTr("Sip Input Panel")
-        iconSource: "qrc:/images/icons/ic_keypad.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            root.close()
-            sipInputPanel.open()
-        }
-    }
-
-    GeneralMenuItem {
-        id: transferCallButton
-
-        visible: isSIP
-        height: isSIP? undefined : 0
-
-        itemName: qsTr("Transfer call")
-        iconSource: "qrc:/images/icons/phone_forwarded-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            root.transferCallButtonClicked()
-            root.close()
-        }
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startRecordingItem.preferredWidth
-        preferredHeight: commonBorderWidth
-
-        visible: isSIP
-        height: isSIP? undefined : 0
-
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    GeneralMenuItem {
-        id: startRecordingItem
-
-        itemName: isRecording? qsTr("Stop recording") : qsTr("Start recording")
-        iconSource: "qrc:/images/icons/ic_video_call_24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            root.close()
-            CallAdapter.recordThisCallToggle()
-        }
-    }
-
-    GeneralMenuItem {
-        id: fullScreenItem
-
-        itemName: videoCallPage.isFullscreen ? qsTr("Exit full screen") : qsTr(
-                                     "Full screen mode")
-        iconSource: videoCallPage.isFullscreen ? "qrc:/images/icons/close_fullscreen-24px.svg" : "qrc:/images/icons/open_in_full-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            root.close()
-            videoCallPageRect.needToShowInFullScreen()
-        }
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startRecordingItem.preferredWidth
-        preferredHeight: commonBorderWidth
-
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    VideoCallPageContextMenuDeviceItem {
-        id: videoDeviceItem
-        visible: !isAudioOnly
-        height: !isAudioOnly? undefined : 0
-
-        contextMenuPreferredWidth: root.implicitWidth
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startRecordingItem.preferredWidth
-        preferredHeight: commonBorderWidth
-        visible: !isAudioOnly
-        height: !isAudioOnly? undefined : 0
-
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    GeneralMenuItem {
-        id: shareEntireScreenItem
-
-        itemName: qsTr("Share entire screen")
-        iconSource: "qrc:/images/icons/screen_share-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-        visible: !isAudioOnly
-        height: !isAudioOnly? undefined : 0
-
-        onClicked: {
-            root.close()
-            if (Qt.application.screens.length === 1) {
-                AvAdapter.shareEntireScreen(0)
-            } else {
-                SelectScreenWindowCreation.createSelectScreenWindowObject()
-                SelectScreenWindowCreation.showSelectScreenWindow()
-            }
-        }
-    }
-
-    GeneralMenuItem {
-        id: shareScreenAreaItem
-
-        itemName: qsTr("Share screen area")
-        iconSource: "qrc:/images/icons/screen_share-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-        visible: !isAudioOnly
-        height: !isAudioOnly? undefined : 0
-
-        onClicked: {
-            root.close()
-            if (Qt.application.screens.length === 1) {
-                ScreenRubberBandCreation.createScreenRubberBandWindowObject(
-                            null, 0)
-                ScreenRubberBandCreation.showScreenRubberBandWindow()
-            } else {
-                SelectScreenWindowCreation.createSelectScreenWindowObject(true)
-                SelectScreenWindowCreation.showSelectScreenWindow()
-            }
-        }
-    }
-
-    GeneralMenuItem {
-        id: shareFileItem
-
-        itemName: qsTr("Share file")
-        iconSource: "qrc:/images/icons/insert_photo-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-        visible: !isAudioOnly
-        height: !isAudioOnly? undefined : 0
-
-        onClicked: {
-            root.close()
-            jamiFileDialog.open()
-        }
+        ContextMenuGenerator.getMenu().closed.connect(function (){
+            VideoDeviceContextMenuItemCreation.removeCreatedItems()
+        })
     }
 
     /* TODO: In the future we want to implement this
@@ -289,31 +180,5 @@
             root.close()
         }
     }*/
-
-    GeneralMenuItem {
-        id: pluginItem
-
-        itemName: qsTr("Toggle plugin")
-        iconSource: "qrc:/images/icons/extension_24dp.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            root.pluginItemClicked()
-            root.close()
-        }
-    }
-
-    background: Rectangle {
-        implicitWidth: startRecordingItem.preferredWidth
-        implicitHeight: startRecordingItem.preferredHeight
-                        * (root.count
-                          - (isSIP? 0 : 2)
-                          - (isAudioOnly? 6 : 0)
-                          - generalMenuSeparatorCount)
-
-        border.width: commonBorderWidth
-        border.color: JamiTheme.tabbarBorderColor
-    }
 }
 
diff --git a/src/mainview/components/ConversationSmartListContextMenu.qml b/src/mainview/components/ConversationSmartListContextMenu.qml
index 26c5406..c5aa471 100644
--- a/src/mainview/components/ConversationSmartListContextMenu.qml
+++ b/src/mainview/components/ConversationSmartListContextMenu.qml
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (C) 2020 by Savoir-faire Linux
  * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
@@ -16,6 +15,7 @@
  * 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.12
@@ -23,157 +23,88 @@
 
 import "../../commoncomponents"
 
-Menu {
-    id: contextMenu
+import "../../commoncomponents/js/contextmenugenerator.js" as ContextMenuGenerator
+
+Item {
+    id: root
+
     property string responsibleAccountId: ""
     property string responsibleConvUid: ""
-
-    property int generalMenuSeparatorCount: 0
-    property int commonBorderWidth: 1
-    font.pointSize: JamiTheme.menuFontSize
+    property int contactType: Profile.Type.INVALID
 
     function openMenu(){
-        visible = true
-        visible = false
-        visible = true
-    }
+        ContextMenuGenerator.addMenuItem(qsTr("Start video call"),
+                                         "qrc:/images/icons/ic_video_call_24px.svg",
+                                         function (){
+                                             ConversationsAdapter.selectConversation(
+                                                         responsibleAccountId,
+                                                         responsibleConvUid, false)
+                                             CallAdapter.placeCall()
+                                         })
+        ContextMenuGenerator.addMenuItem(qsTr("Start audio call"),
+                                         "qrc:/images/icons/ic_phone_24px.svg",
+                                         function (){
+                                             ConversationsAdapter.selectConversation(
+                                                         responsibleAccountId,
+                                                         responsibleConvUid, false)
+                                             CallAdapter.placeAudioOnlyCall()
+                                         })
+        ContextMenuGenerator.addMenuItem(qsTr("Clear conversation"),
+                                         "qrc:/images/icons/ic_clear_24px.svg",
+                                         function (){
+                                             ClientWrapper.utilsAdaptor.clearConversationHistory(
+                                                         responsibleAccountId,
+                                                         responsibleConvUid)
+                                         })
 
-    GeneralMenuSeparator {
-        preferredWidth: startVideoCallItem.preferredWidth
-        preferredHeight: 8
-        separatorColor: "transparent"
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
+        if (contactType === Profile.Type.RING || contactType === Profile.Type.SIP) {
+            ContextMenuGenerator.addMenuItem(qsTr("Remove contact"),
+                                             "qrc:/images/icons/round-remove_circle-24px.svg",
+                                             function (){
+                                                 ClientWrapper.utilsAdaptor.removeConversation(
+                                                             responsibleAccountId,
+                                                             responsibleConvUid)
+                                             })
         }
-    }
 
-    /*
-     * All GeneralMenuItems should remain the same width / height.
-     */
-    GeneralMenuItem {
-        id: startVideoCallItem
+        if (contactType === Profile.Type.RING || contactType === Profile.Type.PENDING) {
+            ContextMenuGenerator.addMenuSeparator()
 
-        itemName: qsTr("Start video call")
-        iconSource: "qrc:/images/icons/ic_video_call_24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
+            if (contactType === Profile.Type.PENDING) {
+                ContextMenuGenerator.addMenuItem(qsTr("Accept request"),
+                                                 "qrc:/images/icons/person_add-24px.svg",
+                                                 function (){
+                                                     MessagesAdapter.acceptInvitation(
+                                                                 responsibleConvUid)
+                                                 })
+                ContextMenuGenerator.addMenuItem(qsTr("Decline request"),
+                                                 "qrc:/images/icons/round-close-24px.svg",
+                                                 function (){
+                                                     MessagesAdapter.refuseInvitation(
+                                                                 responsibleConvUid)
+                                                 })
+            }
+            ContextMenuGenerator.addMenuItem(qsTr("Block contact"),
+                                             "qrc:/images/icons/ic_block_24px.svg",
+                                             function (){
+                                                 MessagesAdapter.blockConversation(
+                                                             responsibleConvUid)
+                                             })
 
-        onClicked: {
-            contextMenu.close()
-            ConversationsAdapter.selectConversation(responsibleAccountId,
-                                                    responsibleConvUid, false)
-            CallAdapter.placeCall()
+            ContextMenuGenerator.addMenuSeparator()
+            ContextMenuGenerator.addMenuItem(qsTr("Profile"),
+                                             "qrc:/images/icons/person-24px.svg",
+                                             function (){
+                                                 userProfile.open()
+                                             })
         }
+
+        root.height = ContextMenuGenerator.getMenu().height
+        root.width = ContextMenuGenerator.getMenu().width
+        ContextMenuGenerator.getMenu().open()
     }
 
-    GeneralMenuItem {
-        id: startAudioCallItem
-
-        itemName: qsTr("Start audio call")
-        iconSource: "qrc:/images/icons/ic_phone_24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            contextMenu.close()
-            ConversationsAdapter.selectConversation(responsibleAccountId,
-                                                    responsibleConvUid, false)
-            CallAdapter.placeAudioOnlyCall()
-        }
-    }
-
-    GeneralMenuItem {
-        id: clearConversationItem
-
-        itemName: qsTr("Clear conversation")
-        iconSource: "qrc:/images/icons/ic_clear_24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            contextMenu.close()
-            ClientWrapper.utilsAdaptor.clearConversationHistory(responsibleAccountId,
-                                                  responsibleConvUid)
-        }
-    }
-
-    GeneralMenuItem {
-        id: removeContactItem
-
-        itemName: qsTr("Remove contact")
-        iconSource: "qrc:/images/icons/round-remove_circle-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            contextMenu.close()
-            ClientWrapper.utilsAdaptor.removeConversation(responsibleAccountId,
-                                            responsibleConvUid)
-        }
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startVideoCallItem.preferredWidth
-        preferredHeight: commonBorderWidth
-
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    GeneralMenuItem {
-        id: blockContactItem
-
-        itemName: qsTr("Block contact")
-        iconSource: "qrc:/images/icons/ic_block_24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            contextMenu.close()
-            ClientWrapper.utilsAdaptor.removeConversation(responsibleAccountId,
-                                            responsibleConvUid, true)
-        }
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startVideoCallItem.preferredWidth
-        preferredHeight: commonBorderWidth
-
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    GeneralMenuItem {
-        id: profileItem
-
-        itemName: qsTr("Profile")
-        iconSource: "qrc:/images/icons/person-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            contextMenu.close()
-            userProfile.open()
-        }
-    }
-
-    GeneralMenuSeparator {
-        preferredWidth: startVideoCallItem.preferredWidth
-        preferredHeight: 8
-        separatorColor: "transparent"
-        Component.onCompleted: {
-            generalMenuSeparatorCount++
-        }
-    }
-
-    background: Rectangle {
-        implicitWidth: startVideoCallItem.preferredWidth
-        implicitHeight: startVideoCallItem.preferredHeight
-                        * (contextMenu.count - generalMenuSeparatorCount)
-
-        border.width: commonBorderWidth
-        border.color: JamiTheme.tabbarBorderColor
+    Component.onCompleted: {
+        ContextMenuGenerator.createBaseContextMenuObjects(root)
     }
 }
diff --git a/src/mainview/components/ConversationSmartListView.qml b/src/mainview/components/ConversationSmartListView.qml
index ee787f7..d16b96d 100644
--- a/src/mainview/components/ConversationSmartListView.qml
+++ b/src/mainview/components/ConversationSmartListView.qml
@@ -82,6 +82,10 @@
         conversationSmartListView.model.setAccount(accountId)
     }
 
+    ConversationSmartListContextMenu {
+        id: smartListContextMenu
+    }
+
     Connections {
         target: CallAdapter
 
diff --git a/src/mainview/components/ConversationSmartListViewItemDelegate.qml b/src/mainview/components/ConversationSmartListViewItemDelegate.qml
index 7b564a8..74d07db 100644
--- a/src/mainview/components/ConversationSmartListViewItemDelegate.qml
+++ b/src/mainview/components/ConversationSmartListViewItemDelegate.qml
@@ -180,7 +180,7 @@
                 itemSmartListBackground.color = JamiTheme.releaseColor
             }
             if (mouse.button === Qt.RightButton) {
-
+                smartListContextMenu.parent = mouseAreaSmartListItemDelegate
 
                 /*
                  * Make menu pos at mouse.
@@ -191,6 +191,7 @@
                 smartListContextMenu.y = relativeMousePos.y
                 smartListContextMenu.responsibleAccountId = ClientWrapper.utilsAdaptor.getCurrAccId()
                 smartListContextMenu.responsibleConvUid = UID
+                smartListContextMenu.contactType = ContactType
                 userProfile.responsibleConvUid = UID
                 userProfile.aliasText = DisplayName
                 userProfile.registeredNameText = DisplayID
@@ -225,8 +226,4 @@
             }
         }
     }
-
-    ConversationSmartListContextMenu {
-        id: smartListContextMenu
-    }
 }
diff --git a/src/mainview/components/ParticipantContextMenu.qml b/src/mainview/components/ParticipantContextMenu.qml
index 9397701..87d883a 100644
--- a/src/mainview/components/ParticipantContextMenu.qml
+++ b/src/mainview/components/ParticipantContextMenu.qml
@@ -1,7 +1,7 @@
-
 /*
  * Copyright (C) 2020 by Savoir-faire Linux
  * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ * 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
@@ -16,6 +16,7 @@
  * 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.12
@@ -23,105 +24,46 @@
 
 import "../../commoncomponents"
 
-import "../js/videodevicecontextmenuitemcreation.js" as VideoDeviceContextMenuItemCreation
-import "../js/selectscreenwindowcreation.js" as SelectScreenWindowCreation
+import "../../commoncomponents/js/contextmenugenerator.js" as ContextMenuGenerator
 
-Menu {
+Item {
     id: root
 
-    property int generalMenuSeparatorCount: 0
-    property int commonBorderWidth: 1
-    font.pointSize: JamiTheme.textFontSize + 3
     property var uri: ""
     property var maximized: true
     property var active: true
+    property var showHangup: false
+    property var showMaximize: false
+    property var showMinimize: false
 
-    function showHangup(show) {
-        if (show) {
-            hangupItem.visible = true
-            hangupItem.height = hangupItem.preferredHeight
-        } else {
-            hangupItem.visible = false
-            hangupItem.height = 0
-        }
+    function openMenu(){
+        if (showHangup)
+            ContextMenuGenerator.addMenuItem(qsTr("Hang up"),
+                                             "qrc:/images/icons/ic_call_end_white_24px.svg",
+                                             function (){
+                                                 CallAdapter.hangupCall(uri)
+                                             })
+
+        if (showMaximize)
+            ContextMenuGenerator.addMenuItem(qsTr("Maximize participant"),
+                                             "qrc:/images/icons/open_in_full-24px.svg",
+                                             function (){
+                                                  CallAdapter.maximizeParticipant(uri, active)
+                                             })
+        if (showMinimize)
+            ContextMenuGenerator.addMenuItem(qsTr("Minimize participant"),
+                                             "qrc:/images/icons/close_fullscreen-24px.svg",
+                                             function (){
+                                                  CallAdapter.minimizeParticipant()
+                                             })
+
+        root.height = ContextMenuGenerator.getMenu().height
+        root.width = ContextMenuGenerator.getMenu().width
+        ContextMenuGenerator.getMenu().open()
     }
 
-    function showMaximize(show) {
-        if (show) {
-            maximizeItem.visible = true
-            maximizeItem.height = hangupItem.preferredHeight
-        } else {
-            maximizeItem.visible = false
-            maximizeItem.height = 0
-        }
-    }
-
-    function showMinimize(show) {
-        if (show) {
-            minimizeItem.visible = true
-            minimizeItem.height = hangupItem.preferredHeight
-        } else {
-            minimizeItem.visible = false
-            minimizeItem.height = 0
-        }
-    }
-
-    function setHeight(visibleItems) {
-        root.height = hangupItem.preferredHeight * visibleItems;
-    }
-
-    /*
-     * All GeneralMenuItems should remain the same width / height.
-     */
-    GeneralMenuItem {
-        id: hangupItem
-
-        itemName: qsTr("Hangup")
-        iconSource: "qrc:/images/icons/ic_call_end_white_24px.svg"
-        icon.color: "black"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-
-        onClicked: {
-            CallAdapter.hangupCall(uri)
-            root.close()
-        }
-    }
-    GeneralMenuItem {
-        id: maximizeItem
-
-        itemName: qsTr("Maximize participant")
-        iconSource: "qrc:/images/icons/open_in_full-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-        visible: !maximized
-
-        onClicked: {
-            CallAdapter.maximizeParticipant(uri, active)
-            root.close()
-        }
-    }
-    GeneralMenuItem {
-        id: minimizeItem
-
-        itemName: qsTr("Minimize participant")
-        iconSource: "qrc:/images/icons/close_fullscreen-24px.svg"
-        leftBorderWidth: commonBorderWidth
-        rightBorderWidth: commonBorderWidth
-        visible: maximized
-
-        onClicked: {
-            CallAdapter.minimizeParticipant()
-            root.close()
-        }
-    }
-
-    background: Rectangle {
-        implicitWidth: hangupItem.preferredWidth
-        implicitHeight: hangupItem.preferredHeight * 3
-
-        border.width: commonBorderWidth
-        border.color: JamiTheme.tabbarBorderColor
+    Component.onCompleted: {
+        ContextMenuGenerator.createBaseContextMenuObjects(root)
     }
 }
 
diff --git a/src/mainview/components/ParticipantOverlay.qml b/src/mainview/components/ParticipantOverlay.qml
index fb49a46..2f17b08 100644
--- a/src/mainview/components/ParticipantOverlay.qml
+++ b/src/mainview/components/ParticipantOverlay.qml
@@ -112,18 +112,14 @@
                         var layout = CallAdapter.getCurrentLayoutType()
                         var showMaximized = layout !== 2
                         var showMinimized = !(layout === 0 || (layout === 1 && !active))
-                        injectedContextMenu.showHangup(!root.isLocal)
-                        injectedContextMenu.showMaximize(showMaximized)
-                        injectedContextMenu.showMinimize(showMinimized)
-                        injectedContextMenu.setHeight(
-                            (root.isLocal ? 0 : 1)
-                            + (showMaximized ? 1 : 0)
-                            + (showMinimized ? 1 : 0))
+                        injectedContextMenu.showHangup = !root.isLocal
+                        injectedContextMenu.showMaximize = showMaximized
+                        injectedContextMenu.showMinimize = showMinimized
                         injectedContextMenu.uri = uri
                         injectedContextMenu.active = active
                         injectedContextMenu.x = mousePos.x
                         injectedContextMenu.y = mousePos.y - injectedContextMenu.height
-                        injectedContextMenu.open()
+                        injectedContextMenu.openMenu()
                     }
                 }
             }
@@ -166,4 +162,4 @@
             duration: 500
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml b/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml
index 73e692b..a6a4f80 100644
--- a/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml
+++ b/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (C) 2020 by Savoir-faire Linux
  * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
@@ -16,24 +15,21 @@
  * 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 net.jami.Models 1.0
 
 import "../../commoncomponents"
 
-
 /*
- * Take advantage of child can access parent's item (ex: contextMenu, commonBorderWidth).
+ * Menu item wrapper for video device checkable item.
  */
 GeneralMenuItem {
     id: videoCallPageContextMenuDeviceItem
 
     property int contextMenuPreferredWidth: 250
 
-    leftBorderWidth: commonBorderWidth
-    rightBorderWidth: commonBorderWidth
-
     TextMetrics {
         id: textMetrics
         elide: Text.ElideMiddle
@@ -54,7 +50,6 @@
 
     onClicked: {
         var deviceName = videoCallPageContextMenuDeviceItem.itemName
-        contextMenu.close()
         AvAdapter.onVideoContextMenuDeviceItemClicked(deviceName)
     }
 }