/src/pages/TabPage.qml

https://github.com/sletta/sailfish-browser · QML · 422 lines · 322 code · 63 blank · 37 comment · 37 complexity · 35ab20ea3744cd1d8cf584d05e7ec1c8 MD5 · raw file

  1. /****************************************************************************
  2. **
  3. ** Copyright (C) 2013 Jolla Ltd.
  4. ** Contact: Vesa-Matti Hartikainen <vesa-matti.hartikainen@jollamobile.com>
  5. **
  6. ****************************************************************************/
  7. /* This Source Code Form is subject to the terms of the Mozilla Public
  8. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  9. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  10. import QtQuick 2.0
  11. import QtGraphicalEffects 1.0
  12. import Sailfish.Browser 1.0
  13. import Sailfish.Silica 1.0
  14. import Sailfish.Silica.private 1.0
  15. import "components" as Browser
  16. Page {
  17. id: page
  18. property BrowserPage browserPage
  19. // focus to input field on opening
  20. property bool initialSearchFocus
  21. property bool newTab
  22. property bool historyVisible: initialSearchFocus
  23. property Item historyHeader
  24. property Item favoriteHeader
  25. property bool initialized
  26. property string _search
  27. function load(url, title) {
  28. if (page.newTab) {
  29. browserPage.tabs.newTab(url, title)
  30. } else {
  31. browserPage.load(url, title)
  32. }
  33. pageStack.pop(browserPage)
  34. }
  35. function activateTab(index) {
  36. browserPage.tabs.activateTab(index)
  37. pageStack.pop(browserPage)
  38. }
  39. onStatusChanged: {
  40. if (status === PageStatus.Active) {
  41. initialized = true
  42. }
  43. }
  44. backNavigation: browserPage.tabs.count > 0 && browserPage.url != ""
  45. states: [
  46. State {
  47. when: page.status < PageStatus.Active && !initialized
  48. },
  49. State {
  50. name: "historylist"
  51. when: historyVisible
  52. },
  53. State {
  54. name: "favoritelist"
  55. when: !historyVisible
  56. }
  57. ]
  58. transitions: [
  59. Transition {
  60. to: "favoritelist"
  61. ParallelAnimation {
  62. SequentialAnimation {
  63. PropertyAction { target: commonHeader; property: "parent"; value: page }
  64. FadeAnimation { target: favoriteList; to: 1.0 }
  65. PropertyAction { target: commonHeader; property: "parent"; value: page.favoriteHeader }
  66. ScriptAction { script: {
  67. if (!favoriteList.model) {
  68. favoriteList.model = browserPage.favorites
  69. }
  70. favoriteList.positionViewAtBeginning()
  71. }
  72. }
  73. }
  74. FadeAnimation { target: historyList; to: 0.0 }
  75. }
  76. },
  77. Transition {
  78. to: "historylist"
  79. ParallelAnimation {
  80. SequentialAnimation {
  81. PropertyAction { target: commonHeader; property: "parent"; value: page }
  82. FadeAnimation { target: historyList; to: 1.0 }
  83. PropertyAction { target: commonHeader; property: "parent"; value: page.historyHeader }
  84. PropertyAction { target: page.historyHeader; property: "opacity"; value: 1.0 }
  85. ScriptAction { script: {
  86. if (!historyList.model) {
  87. historyList.model = browserPage.history
  88. }
  89. historyList.positionViewAtBeginning()
  90. focusTimer.restart()
  91. }
  92. }
  93. }
  94. FadeAnimation { target: favoriteList; to: 0.0 }
  95. }
  96. }
  97. ]
  98. Browser.HistoryList {
  99. id: historyList
  100. visible: opacity > 0.0
  101. header: Item {
  102. id: historyHeader
  103. width: commonHeader.width
  104. height: commonHeader.height + historySectionHeader.height
  105. opacity: 0.0
  106. Behavior on opacity { FadeAnimation {} }
  107. SectionHeader {
  108. id: historySectionHeader
  109. //: Section header for history items
  110. //% "History"
  111. text: qsTrId("sailfish_browser-he-history")
  112. anchors.bottom: historyHeader.bottom
  113. }
  114. Component.onCompleted: page.historyHeader = historyHeader
  115. }
  116. search: _search
  117. onLoad: page.load(url, title)
  118. Browser.TabPageMenu {
  119. active: initialized
  120. visible: browserPage.tabs.count > 0 && !page.newTab
  121. shareEnabled: browserPage.url == _search
  122. browserPage: page.browserPage
  123. flickable: historyList
  124. }
  125. }
  126. Browser.FavoriteList {
  127. id: favoriteList
  128. visible: opacity > 0.0
  129. opacity: initialSearchFocus ? 0.0 : 1.0
  130. header: Item {
  131. id: favoriteHeader
  132. width: commonHeader.width
  133. height: (page.newTab ? commonHeader.height : commonHeader.height + tabsGrid.height) + favoriteSectionHeader.height
  134. VisibilityCull {
  135. target: tabsGrid
  136. }
  137. Grid {
  138. id: tabsGrid
  139. // Fill initial view immediately
  140. property int loadIndex: columns * Math.round(page.height / (page.width / columns))
  141. visible: !page.newTab
  142. columns: page.isPortrait ? 2 : 4
  143. rows: Math.ceil((browserPage.tabs.count - 1) / columns)
  144. height: rows > 0 ? rows * (page.width / tabsGrid.columns) : 0
  145. anchors.bottom: favoriteSectionHeader.top
  146. move: Transition {
  147. NumberAnimation { properties: "x,y"; easing.type: Easing.InOutQuad; duration: 200 }
  148. }
  149. add: Transition {
  150. AddAnimation {}
  151. }
  152. Repeater {
  153. model: browserPage.tabs
  154. Browser.TabItem {
  155. width: page.width/tabsGrid.columns
  156. height: width
  157. active: index < tabsGrid.loadIndex || initialized
  158. asynchronous: index >= tabsGrid.loadIndex
  159. // activateTab doesn't work inside delagate because this tab is removed (deleted)
  160. // from the model and old active tab pushed to first.
  161. onClicked: activateTab(model.index)
  162. }
  163. }
  164. Behavior on height {
  165. NumberAnimation { easing.type: Easing.InOutQuad; duration: 200 }
  166. }
  167. }
  168. SectionHeader {
  169. id: favoriteSectionHeader
  170. //: Section header for favorites
  171. //% "Favorites"
  172. text: qsTrId("sailfish_browser-he-favorites")
  173. anchors.bottom: favoriteHeader.bottom
  174. }
  175. Component.onCompleted: page.favoriteHeader = favoriteHeader
  176. }
  177. hasContextMenu: !page.newTab
  178. onLoad: {
  179. if (newTab) page.newTab = true
  180. page.load(url, title)
  181. }
  182. onRemoveBookmark: browserPage.favorites.removeBookmark(url)
  183. Browser.TabPageMenu {
  184. active: initialized
  185. visible: browserPage.tabs.count > 0 && !page.newTab
  186. shareEnabled: browserPage.url == _search
  187. browserPage: page.browserPage
  188. flickable: favoriteList
  189. }
  190. }
  191. Column {
  192. id: commonHeader
  193. width: page.width
  194. PageHeader {
  195. id: pageHeader
  196. //% "New tab"
  197. title: (browserPage.tabs.count == 0 || newTab) ? qsTrId("sailfish_browser-la-new_tab") :
  198. //% "Search"
  199. (historyVisible ? qsTrId("sailfish_browser-la-search")
  200. //: Header at the Tab page
  201. //% "Tabs"
  202. : qsTrId("sailfish_browser-he-tabs"))
  203. visible: page.isPortrait
  204. height: Theme.itemSizeLarge
  205. }
  206. Rectangle {
  207. id: headerContent
  208. width: parent.width
  209. height: Screen.width / 2
  210. color: Theme.rgba(Theme.highlightColor, 0.1)
  211. Image {
  212. id: headerThumbnail
  213. width: height
  214. height: parent.height
  215. sourceSize.height: height
  216. anchors.right: parent.right
  217. asynchronous: true
  218. source: browserPage.thumbnailPath
  219. cache: false
  220. visible: status !== Image.Error && source !== "" && !page.newTab
  221. }
  222. OpacityRampEffect {
  223. opacity: 0.6
  224. sourceItem: headerThumbnail
  225. slope: 1.0
  226. offset: 0.0
  227. direction: OpacityRamp.RightToLeft
  228. }
  229. Column {
  230. id: urlColumn
  231. property int indicatorWidth: window.indicatorParentItem.childrenRect.width
  232. x: page.isPortrait ? 0 : indicatorWidth + Theme.paddingLarge
  233. anchors.topMargin: Theme.itemSizeLarge / 2 - titleLabel.height / 2
  234. states: [
  235. State {
  236. when: page.isLandscape
  237. AnchorChanges {
  238. target: urlColumn
  239. anchors.bottom: undefined
  240. anchors.top: headerContent.top
  241. }
  242. },
  243. State {
  244. when: page.isPortrait
  245. AnchorChanges {
  246. target: urlColumn
  247. anchors.bottom: headerContent.bottom
  248. anchors.top: undefined
  249. }
  250. }
  251. ]
  252. Label {
  253. id: titleLabel
  254. x: Theme.paddingLarge
  255. // Reuse new tab label (la-new_tab)
  256. text: (browserPage.tabs.count == 0 || newTab) ? qsTrId("sailfish_browser-la-new_tab") : (browserPage.url == _search ? browserPage.title : "")
  257. color: Theme.highlightColor
  258. font.pixelSize: Theme.fontSizeSmall
  259. width: textFieldLoader.width - x - Theme.paddingMedium
  260. truncationMode: TruncationMode.Fade
  261. opacity: 0.0
  262. Behavior on opacity { FadeAnimation {} }
  263. }
  264. Loader {
  265. id: textFieldLoader
  266. property bool searchFieldFocused: item ? item.focus : false
  267. property bool focusOnceLoaded
  268. asynchronous: true
  269. width: page.isPortrait ? page.width - (closeActiveTabButton.visible ? closeActiveTabButton.width : 0)
  270. : page.width - urlColumn.x - Theme.paddingMedium
  271. height: Theme.itemSizeMedium
  272. active: initialized
  273. sourceComponent: TextField {
  274. id: searchField
  275. function focusAndSelect() {
  276. forceActiveFocus()
  277. selectAll()
  278. _updateFlickables()
  279. historyList.currentIndex = -1
  280. }
  281. width: parent.width
  282. // Handle initially newTab state. Currently newTab initially
  283. // true when triggering new tab cover action.
  284. text: newTab ? "" : browserPage.url
  285. //: Placeholder for the search/address field
  286. //% "Search or Address"
  287. placeholderText: qsTrId("sailfish_browser-ph-search_or_url")
  288. color: searchField.focus ? Theme.highlightColor : Theme.primaryColor
  289. focusOutBehavior: FocusBehavior.KeepFocus
  290. inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase | Qt.ImhUrlCharactersOnly
  291. opacity: 0.0
  292. label: {
  293. if (text.length === 0) return ""
  294. if (searchField.activeFocus || text !== browserPage.url) {
  295. // Reuse search label
  296. return qsTrId("sailfish_browser-la-search")
  297. }
  298. //: Active browser tab.
  299. //% "Active Tab"
  300. var activeTab = qsTrId("sailfish_browser-la-active-tab")
  301. if (text === browserPage.url && browserPage.viewLoading) {
  302. //: Current browser page loading.
  303. //% "Loading"
  304. return activeTab + " • " + qsTrId("sailfish_browser-la-loading")
  305. } else {
  306. //: Current browser page loaded.
  307. //% "Done"
  308. return activeTab + " • " + qsTrId("sailfish_browser-la-done")
  309. }
  310. }
  311. EnterKey.iconSource: "image://theme/icon-m-enter-accept"
  312. EnterKey.onClicked: {
  313. Qt.inputMethod.hide()
  314. // let gecko figure out how to handle malformed URLs
  315. page.load(searchField.text)
  316. }
  317. onTextChanged: if (text != browserPage.url) browserPage.history.search(text)
  318. onFocusChanged: historyVisible = focus
  319. Behavior on opacity { FadeAnimation {} }
  320. Binding { target: page; property: "_search"; value: searchField.text }
  321. }
  322. // These steps cannot be done in TextField's Component.onCompleted as
  323. // item of the Loader might be still null.
  324. onLoaded: {
  325. item.opacity = 1.0
  326. titleLabel.opacity = 1.0
  327. if (textFieldLoader.focusOnceLoaded) {
  328. focusTimer.restart()
  329. }
  330. }
  331. Timer {
  332. id: focusTimer
  333. interval: 1
  334. onTriggered: {
  335. if (textFieldLoader.item) {
  336. textFieldLoader.item.focusAndSelect()
  337. } else {
  338. textFieldLoader.focusOnceLoaded = true
  339. }
  340. }
  341. }
  342. }
  343. Connections {
  344. target: page
  345. onStatusChanged: {
  346. // break binding when pushed to stick with proper value for this depth of pagestack
  347. if (status === PageStatus.Active && window.indicatorParentItem.childrenRect.width == urlColumn.indicatorWidth)
  348. urlColumn.indicatorWidth = window.indicatorParentItem.childrenRect.width
  349. }
  350. }
  351. }
  352. Browser.CloseTabButton {
  353. id: closeActiveTabButton
  354. visible: browserPage.tabs.count > 0 && !page.newTab && !textFieldLoader.searchFieldFocus
  355. closeActiveTab: true
  356. }
  357. }
  358. }
  359. }