MenuBar.qml 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. /****************************************************************************
  2. **
  3. ** Copyright (C) 2016 The Qt Company Ltd.
  4. ** Contact: https://www.qt.io/licensing/
  5. **
  6. ** This file is part of the Qt Quick Controls module of the Qt Toolkit.
  7. **
  8. ** $QT_BEGIN_LICENSE:LGPL$
  9. ** Commercial License Usage
  10. ** Licensees holding valid commercial Qt licenses may use this file in
  11. ** accordance with the commercial license agreement provided with the
  12. ** Software or, alternatively, in accordance with the terms contained in
  13. ** a written agreement between you and The Qt Company. For licensing terms
  14. ** and conditions see https://www.qt.io/terms-conditions. For further
  15. ** information use the contact form at https://www.qt.io/contact-us.
  16. **
  17. ** GNU Lesser General Public License Usage
  18. ** Alternatively, this file may be used under the terms of the GNU Lesser
  19. ** General Public License version 3 as published by the Free Software
  20. ** Foundation and appearing in the file LICENSE.LGPL3 included in the
  21. ** packaging of this file. Please review the following information to
  22. ** ensure the GNU Lesser General Public License version 3 requirements
  23. ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
  24. **
  25. ** GNU General Public License Usage
  26. ** Alternatively, this file may be used under the terms of the GNU
  27. ** General Public License version 2.0 or (at your option) the GNU General
  28. ** Public license version 3 or any later version approved by the KDE Free
  29. ** Qt Foundation. The licenses are as published by the Free Software
  30. ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
  31. ** included in the packaging of this file. Please review the following
  32. ** information to ensure the GNU General Public License requirements will
  33. ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
  34. ** https://www.gnu.org/licenses/gpl-3.0.html.
  35. **
  36. ** $QT_END_LICENSE$
  37. **
  38. ****************************************************************************/
  39. import QtQml 2.14 as Qml
  40. import QtQuick 2.2
  41. import QtQuick.Controls 1.2
  42. import QtQuick.Controls.Styles 1.1
  43. import QtQuick.Controls.Private 1.0
  44. /*!
  45. \qmltype MenuBar
  46. \inqmlmodule QtQuick.Controls
  47. \since 5.1
  48. \ingroup applicationwindow
  49. \ingroup controls
  50. \brief Provides a horizontal menu bar.
  51. \image menubar.png
  52. MenuBar can be added to an \l ApplicationWindow, providing menu options
  53. to access additional functionality of the application.
  54. \code
  55. ApplicationWindow {
  56. ...
  57. menuBar: MenuBar {
  58. Menu {
  59. title: "File"
  60. MenuItem { text: "Open..." }
  61. MenuItem { text: "Close" }
  62. }
  63. Menu {
  64. title: "Edit"
  65. MenuItem { text: "Cut" }
  66. MenuItem { text: "Copy" }
  67. MenuItem { text: "Paste" }
  68. }
  69. }
  70. }
  71. \endcode
  72. \sa ApplicationWindow::menuBar
  73. */
  74. MenuBarPrivate {
  75. id: root
  76. /*! \qmlproperty Component MenuBar::style
  77. \since QtQuick.Controls.Styles 1.2
  78. The style Component for this control.
  79. \sa {MenuBarStyle}
  80. */
  81. property Component style: Settings.styleComponent(Settings.style, "MenuBarStyle.qml", root)
  82. /*! \internal */
  83. property QtObject __style: styleLoader.item
  84. __isNative: !__style.hasOwnProperty("__isNative") || __style.__isNative
  85. /*! \internal */
  86. __contentItem: Loader {
  87. id: topLoader
  88. sourceComponent: __menuBarComponent
  89. active: !root.__isNative
  90. focus: true
  91. Keys.forwardTo: [item]
  92. property real preferredWidth: parent && active ? parent.width : 0
  93. property bool altPressed: item ? item.__altPressed : false
  94. Loader {
  95. id: styleLoader
  96. property alias __control: topLoader.item
  97. sourceComponent: root.style
  98. onStatusChanged: {
  99. if (status === Loader.Error)
  100. console.error("Failed to load Style for", root)
  101. }
  102. }
  103. }
  104. /*! \internal */
  105. property Component __menuBarComponent: Loader {
  106. id: menuBarLoader
  107. Accessible.role: Accessible.MenuBar
  108. onStatusChanged: if (status === Loader.Error) console.error("Failed to load panel for", root)
  109. visible: status === Loader.Ready
  110. sourceComponent: d.style ? d.style.background : undefined
  111. width: implicitWidth || root.__contentItem.preferredWidth
  112. height: Math.max(row.height + d.heightPadding, item ? item.implicitHeight : 0)
  113. Qml.Binding {
  114. // Make sure the styled menu bar is in the background
  115. target: menuBarLoader.item
  116. property: "z"
  117. value: menuMouseArea.z - 1
  118. restoreMode: Binding.RestoreBinding
  119. }
  120. QtObject {
  121. id: d
  122. property Style style: __style
  123. property int openedMenuIndex: -1
  124. property bool preselectMenuItem: false
  125. property real heightPadding: style ? style.padding.top + style.padding.bottom : 0
  126. property bool altPressed: false
  127. property bool altPressedAgain: false
  128. property var mnemonicsMap: ({})
  129. function openMenuAtIndex(index) {
  130. if (openedMenuIndex === index)
  131. return;
  132. var oldIndex = openedMenuIndex
  133. openedMenuIndex = index
  134. if (oldIndex !== -1) {
  135. var menu = root.menus[oldIndex]
  136. if (menu.__popupVisible)
  137. menu.__dismissAndDestroy()
  138. }
  139. if (openedMenuIndex !== -1) {
  140. menu = root.menus[openedMenuIndex]
  141. if (menu.enabled) {
  142. if (menu.__usingDefaultStyle)
  143. menu.style = d.style.menuStyle
  144. var xPos = row.LayoutMirroring.enabled ? menuItemLoader.width : 0
  145. menu.__popup(Qt.rect(xPos, menuBarLoader.height - d.heightPadding, 0, 0), 0)
  146. if (preselectMenuItem)
  147. menu.__currentIndex = 0
  148. }
  149. }
  150. }
  151. function dismissActiveFocus(event, reason) {
  152. if (reason) {
  153. altPressedAgain = false
  154. altPressed = false
  155. openMenuAtIndex(-1)
  156. root.__contentItem.parent.forceActiveFocus()
  157. } else {
  158. event.accepted = false
  159. }
  160. }
  161. function maybeOpenFirstMenu(event) {
  162. if (altPressed && openedMenuIndex === -1) {
  163. preselectMenuItem = true
  164. openMenuAtIndex(0)
  165. } else {
  166. event.accepted = false
  167. }
  168. }
  169. }
  170. property alias __altPressed: d.altPressed // Needed for the menu contents
  171. focus: true
  172. Keys.onPressed: {
  173. var action = null
  174. if (event.key === Qt.Key_Alt) {
  175. if (!d.altPressed)
  176. d.altPressed = true
  177. else
  178. d.altPressedAgain = true
  179. } else if (d.altPressed && (action = d.mnemonicsMap[event.text.toUpperCase()])) {
  180. d.preselectMenuItem = true
  181. action.trigger()
  182. event.accepted = true
  183. }
  184. }
  185. Keys.onReleased: d.dismissActiveFocus(event, d.altPressedAgain && d.openedMenuIndex === -1)
  186. Keys.onEscapePressed: d.dismissActiveFocus(event, d.openedMenuIndex === -1)
  187. Keys.onUpPressed: d.maybeOpenFirstMenu(event)
  188. Keys.onDownPressed: d.maybeOpenFirstMenu(event)
  189. Keys.onLeftPressed: {
  190. if (d.openedMenuIndex > 0) {
  191. var idx = d.openedMenuIndex - 1
  192. while (idx >= 0 && !(root.menus[idx].enabled && root.menus[idx].visible))
  193. idx--
  194. if (idx >= 0) {
  195. d.preselectMenuItem = true
  196. d.openMenuAtIndex(idx)
  197. }
  198. } else {
  199. event.accepted = false;
  200. }
  201. }
  202. Keys.onRightPressed: {
  203. if (d.openedMenuIndex !== -1 && d.openedMenuIndex < root.menus.length - 1) {
  204. var idx = d.openedMenuIndex + 1
  205. while (idx < root.menus.length && !(root.menus[idx].enabled && root.menus[idx].visible))
  206. idx++
  207. if (idx < root.menus.length) {
  208. d.preselectMenuItem = true
  209. d.openMenuAtIndex(idx)
  210. }
  211. } else {
  212. event.accepted = false;
  213. }
  214. }
  215. Keys.forwardTo: d.openedMenuIndex !== -1 ? [root.menus[d.openedMenuIndex].__contentItem] : []
  216. Row {
  217. id: row
  218. x: d.style ? d.style.padding.left : 0
  219. y: d.style ? d.style.padding.top : 0
  220. width: parent.width - (d.style ? d.style.padding.left + d.style.padding.right : 0)
  221. LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
  222. Repeater {
  223. id: itemsRepeater
  224. model: root.menus
  225. Loader {
  226. id: menuItemLoader
  227. Accessible.role: Accessible.MenuItem
  228. Accessible.name: StyleHelpers.removeMnemonics(opts.text)
  229. Accessible.onPressAction: d.openMenuAtIndex(opts.index)
  230. property var styleData: QtObject {
  231. id: opts
  232. readonly property int index: __menuItemIndex
  233. readonly property string text: !!__menuItem && __menuItem.title
  234. readonly property bool enabled: !!__menuItem && __menuItem.enabled
  235. readonly property bool selected: menuMouseArea.hoveredItem === menuItemLoader
  236. readonly property bool open: !!__menuItem && __menuItem.__popupVisible || d.openedMenuIndex === index
  237. readonly property bool underlineMnemonic: d.altPressed
  238. }
  239. height: Math.max(menuBarLoader.height - d.heightPadding,
  240. menuItemLoader.item ? menuItemLoader.item.implicitHeight : 0)
  241. readonly property var __menuItem: modelData
  242. readonly property int __menuItemIndex: index
  243. sourceComponent: d.style ? d.style.itemDelegate : null
  244. visible: __menuItem.visible
  245. Connections {
  246. target: __menuItem
  247. function onAboutToHide() {
  248. if (d.openedMenuIndex === index) {
  249. d.openMenuAtIndex(-1)
  250. menuMouseArea.hoveredItem = null
  251. }
  252. }
  253. }
  254. Connections {
  255. target: __menuItem.__action
  256. function onTriggered() { d.openMenuAtIndex(__menuItemIndex) }
  257. }
  258. Component.onCompleted: {
  259. __menuItem.__visualItem = menuItemLoader
  260. var title = __menuItem.title
  261. var ampersandPos = title.indexOf("&")
  262. if (ampersandPos !== -1)
  263. d.mnemonicsMap[title[ampersandPos + 1].toUpperCase()] = __menuItem.__action
  264. }
  265. }
  266. }
  267. }
  268. MouseArea {
  269. id: menuMouseArea
  270. anchors.fill: parent
  271. hoverEnabled: Settings.hoverEnabled
  272. onPositionChanged: updateCurrentItem(mouse)
  273. onPressed: updateCurrentItem(mouse)
  274. onExited: hoveredItem = null
  275. property Item currentItem: null
  276. property Item hoveredItem: null
  277. function updateCurrentItem(mouse) {
  278. var pos = mapToItem(row, mouse.x, mouse.y)
  279. if (pressed || !hoveredItem
  280. || !hoveredItem.contains(Qt.point(pos.x - currentItem.x, pos.y - currentItem.y))) {
  281. hoveredItem = row.childAt(pos.x, pos.y)
  282. if (!hoveredItem)
  283. return false;
  284. currentItem = hoveredItem
  285. if (pressed || d.openedMenuIndex !== -1) {
  286. d.preselectMenuItem = false
  287. d.openMenuAtIndex(currentItem.__menuItemIndex)
  288. }
  289. }
  290. return true;
  291. }
  292. }
  293. }
  294. }