Merge lp:~osomon/webbrowser-app/desktop-tabs-prototype into lp:webbrowser-app

Proposed by Olivier Tilloy on 2015-06-18
Status: Merged
Approved by: Olivier Tilloy on 2015-07-09
Approved revision: 1115
Merged at revision: 1089
Proposed branch: lp:~osomon/webbrowser-app/desktop-tabs-prototype
Merge into: lp:webbrowser-app
Diff against target: 3199 lines (+1524/-496)
31 files modified
src/app/BrowserView.qml (+4/-1)
src/app/BrowserWindow.qml (+2/-2)
src/app/ChromeBase.qml (+2/-0)
src/app/webbrowser/AddressBar.qml (+41/-10)
src/app/webbrowser/Browser.qml (+123/-45)
src/app/webbrowser/BrowserTab.qml (+3/-1)
src/app/webbrowser/CMakeLists.txt (+1/-1)
src/app/webbrowser/Chrome.qml (+50/-251)
src/app/webbrowser/NavigationBar.qml (+298/-0)
src/app/webbrowser/NewTabView.qml (+2/-2)
src/app/webbrowser/Suggestion.qml (+3/-2)
src/app/webbrowser/Suggestions.qml (+3/-3)
src/app/webbrowser/TabsBar.qml (+248/-0)
src/app/webbrowser/TabsList.qml (+1/-4)
src/app/webbrowser/assets/private-browsing-exit.svg (+174/-0)
src/app/webbrowser/tabs-model.cpp (+53/-23)
src/app/webbrowser/tabs-model.h (+8/-2)
tests/autopilot/webbrowser_app/emulators/browser.py (+29/-0)
tests/autopilot/webbrowser_app/tests/__init__.py (+24/-7)
tests/autopilot/webbrowser_app/tests/test_addressbar_bookmark.py (+28/-18)
tests/autopilot/webbrowser_app/tests/test_bookmark_options.py (+2/-1)
tests/autopilot/webbrowser_app/tests/test_keyboard.py (+22/-6)
tests/autopilot/webbrowser_app/tests/test_new_tab_view.py (+91/-73)
tests/autopilot/webbrowser_app/tests/test_private.py (+25/-14)
tests/autopilot/webbrowser_app/tests/test_suggestions.py (+1/-3)
tests/autopilot/webbrowser_app/tests/test_tabs.py (+28/-10)
tests/unittests/qml/CMakeLists.txt (+1/-0)
tests/unittests/qml/tst_AddressBar.qml (+2/-1)
tests/unittests/qml/tst_QmlTests.cpp (+3/-1)
tests/unittests/qml/tst_TabsBar.qml (+173/-0)
tests/unittests/tabs-model/tst_TabsModelTests.cpp (+79/-15)
To merge this branch: bzr merge lp:~osomon/webbrowser-app/desktop-tabs-prototype
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing on 2015-07-09
Ubuntu Phablet Team 2015-06-18 Pending
Review via email: mp+262358@code.launchpad.net

Commit Message

Desktop tabs for convergence.

Description of the Change

Desktop tabs for convergence.

To post a comment you must log in.
1067. By Olivier Tilloy on 2015-06-22

Merge the latest changes from trunk and resolve a few conflicts.

1068. By Olivier Tilloy on 2015-06-23

Add a new tab button to the right of all open tabs.

1069. By Olivier Tilloy on 2015-06-23

Always reserve space for the new tab button at the right.

1070. By Olivier Tilloy on 2015-06-23

Updated tab visuals.

1071. By Olivier Tilloy on 2015-06-23

Add asset for hovering over tabs.

1072. By Olivier Tilloy on 2015-06-23

Fix positioning of the new tab button.

1073. By Olivier Tilloy on 2015-06-23

Change current tab on mouse wheel events.

1074. By Olivier Tilloy on 2015-06-23

Change z-ordering of the new tab button to ensure it’s always below the open tabs.

1075. By Olivier Tilloy on 2015-06-25

Fix previous commit by changing the z-order implicitly.

1076. By Olivier Tilloy on 2015-06-25

Fix more z-ordering issues.

1077. By Olivier Tilloy on 2015-06-25

Reverse wheel action to match chromium’s behaviour.

1078. By Olivier Tilloy on 2015-06-25

Fix z-ordering of the horizontal separator.

1079. By Olivier Tilloy on 2015-06-25

Remove focus from the address bar when the current tab changes to ensure that its contents are updated.

1080. By Olivier Tilloy on 2015-06-25

Actually close the current tab when Ctrl+W or Alt+F4 is pressed.

1081. By Olivier Tilloy on 2015-06-25

Initial unit tests for the TabsBar component.

1082. By Olivier Tilloy on 2015-06-25

Use a real TabsModel in tests instead of a mock.

1083. By Olivier Tilloy on 2015-06-25

Merge the latest changes from trunk.

1084. By Olivier Tilloy on 2015-06-25

Revert changes to the translation template.

1085. By Olivier Tilloy on 2015-06-25

Add a unit test for tab reordering by drag’n’drop.

1086. By Olivier Tilloy on 2015-06-26

Display the tabs bar conditionally depending on the available width, not on the device type.

1087. By Olivier Tilloy on 2015-06-26

Close the tabs view when switching to wide mode.

1088. By Olivier Tilloy on 2015-06-26

When switching to narrow mode, move the current tab to the top of the stack.

1089. By Olivier Tilloy on 2015-06-26

Fix Ctrl+Tab shortcut to cycle through tabs, to make it work both in wide and narrow mode.

1090. By Olivier Tilloy on 2015-06-26

Wire up Ctrl+Shift+Tab keyboard shortcut.

1091. By Olivier Tilloy on 2015-06-26

Update and comment code that handles unloading webviews to free up memory on mobile.

1092. By Olivier Tilloy on 2015-07-02

Code simplification.

1093. By Olivier Tilloy on 2015-07-02

Fix the switchToTab function to focus the address bar only when really necessary.

1094. By Olivier Tilloy on 2015-07-02

Use grid units for the initial window size.

1095. By Olivier Tilloy on 2015-07-02

Fix existing autopilot tests to take into account the different UIs based on the width of the form factor.

1096. By Olivier Tilloy on 2015-07-02

Fix unit test.

1097. By Olivier Tilloy on 2015-07-03

Merge the latest changes from trunk and resolve conflicts.

1098. By Olivier Tilloy on 2015-07-06

In wide mode, do not show the favicon in the address bar (it’s already shown in the tabs bar).

1099. By Olivier Tilloy on 2015-07-07

Increase wait to (hopefully) make a unit test more reliable.

1100. By Olivier Tilloy on 2015-07-07

Merge the fix for bug #1472161.

1101. By Olivier Tilloy on 2015-07-07

Add a new 'editing' property on the AddressBar component, different from the 'canSimplifyText' property.

1102. By Olivier Tilloy on 2015-07-07

Do not focus the address bar when switching to a tab for which state is being restored.

1103. By Olivier Tilloy on 2015-07-07

Reset focus when closing the last private tab, to ensure the address bar is updated.

1104. By Olivier Tilloy on 2015-07-07

Fix navigation bar and icon colors depending on wide/narrow mode.

1105. By Olivier Tilloy on 2015-07-08

Add "exit private mode" icon provided by design.

1106. By Olivier Tilloy on 2015-07-08

Use dark grey consistently across the chrome UI elements, per design specification.

1107. By Olivier Tilloy on 2015-07-08

Update colors of the suggestions to match visual design spec.

1108. By Olivier Tilloy on 2015-07-08

Install missing asset.

1109. By Olivier Tilloy on 2015-07-09

Fix autopilot test expectation.

1110. By Olivier Tilloy on 2015-07-09

Do not create twice a temporary profile in the same test case.

1111. By Olivier Tilloy on 2015-07-09

Make the "new tab" button visible when incognito.

1112. By Olivier Tilloy on 2015-07-09

Fix broken autopilot emulator.

1113. By Olivier Tilloy on 2015-07-09

Also quit the app when closing the last open tab in wide mode.

1114. By Olivier Tilloy on 2015-07-09

Restore visibility of the progress bar.

1115. By Olivier Tilloy on 2015-07-09

Fix autopilot tests on wide form factors.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/app/BrowserView.qml'
2--- src/app/BrowserView.qml 2014-09-15 17:08:41 +0000
3+++ src/app/BrowserView.qml 2015-07-09 14:01:32 +0000
4@@ -1,5 +1,5 @@
5 /*
6- * Copyright 2013-2014 Canonical Ltd.
7+ * Copyright 2013-2015 Canonical Ltd.
8 *
9 * This file is part of webbrowser-app.
10 *
11@@ -32,6 +32,9 @@
12
13 property var osk: _osk
14
15+ // See http://design.canonical.com/2015/05/to-converge-onto-mobile-tablet-and-desktop-think-grid-units/
16+ readonly property bool wide: width >= units.gu(90)
17+
18 focus: true
19
20 property QtObject actionManager: UnityActions.ActionManager {
21
22=== modified file 'src/app/BrowserWindow.qml'
23--- src/app/BrowserWindow.qml 2015-06-17 11:11:43 +0000
24+++ src/app/BrowserWindow.qml 2015-07-09 14:01:32 +0000
25@@ -29,8 +29,8 @@
26
27 contentOrientation: Screen.orientation
28
29- width: 800
30- height: 600
31+ width: units.gu(100)
32+ height: units.gu(75)
33
34 QtObject {
35 id: internal
36
37=== modified file 'src/app/ChromeBase.qml'
38--- src/app/ChromeBase.qml 2015-04-27 20:02:45 +0000
39+++ src/app/ChromeBase.qml 2015-07-09 14:01:32 +0000
40@@ -58,5 +58,7 @@
41 right: parent.right
42 bottom: parent.bottom
43 }
44+
45+ z: 2
46 }
47 }
48
49=== modified file 'src/app/webbrowser/AddressBar.qml'
50--- src/app/webbrowser/AddressBar.qml 2015-07-07 10:14:03 +0000
51+++ src/app/webbrowser/AddressBar.qml 2015-07-09 14:01:32 +0000
52@@ -38,6 +38,8 @@
53 signal requestStop()
54 property string searchUrl
55 property bool canSimplifyText: true
56+ property bool editing: false
57+ property bool showFavicon: true
58
59 property var securityStatus: null
60
61@@ -51,7 +53,9 @@
62
63 height: textField.height
64
65- function selectAll() { textField.selectAll() }
66+ function selectAll() {
67+ textField.selectAll()
68+ }
69
70 TextField {
71 id: textField
72@@ -79,7 +83,7 @@
73 id: favicon
74 shouldCache: !addressbar.incognito
75 anchors.verticalCenter: parent.verticalCenter
76- visible: internal.idle && addressbar.actualUrl.toString() &&
77+ visible: showFavicon && internal.idle && addressbar.actualUrl.toString() &&
78 !internal.securityWarning && !internal.securityError
79 }
80
81@@ -89,7 +93,7 @@
82 height: parent.height
83 width: height
84
85- visible: addressbar.activeFocus || addressbar.loading || !addressbar.text || !canSimplifyText
86+ visible: addressbar.editing || addressbar.loading || !addressbar.text
87
88 enabled: addressbar.text
89 opacity: enabled ? 1.0 : 0.3
90@@ -101,6 +105,7 @@
91 name: addressbar.loading ? "stop" :
92 reload ? "reload" :
93 looksLikeAUrl ? "stock_website" : "search"
94+ color: UbuntuColors.darkGrey
95
96 MouseArea {
97 objectName: "actionButton"
98@@ -124,6 +129,7 @@
99
100 Icon {
101 name: "network-secure"
102+ color: UbuntuColors.darkGrey
103 height: parent.height
104 width: height
105 visible: internal.idle && internal.secureConnection
106@@ -138,6 +144,7 @@
107
108 Icon {
109 name: "security-alert"
110+ color: UbuntuColors.darkGrey
111 height: parent.height
112 width: height
113 visible: internal.idle && internal.securityWarning
114@@ -187,7 +194,7 @@
115 anchors.centerIn: parent
116
117 name: addressbar.bookmarked ? "starred" : "non-starred"
118- color: addressbar.bookmarked ? UbuntuColors.orange : keyColor
119+ color: addressbar.bookmarked ? UbuntuColors.orange : UbuntuColors.darkGrey
120 }
121
122 MouseArea {
123@@ -203,6 +210,7 @@
124 }
125
126 font.pixelSize: FontUtils.sizeToPixels("small")
127+ color: UbuntuColors.darkGrey
128 inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhUrlCharactersOnly
129
130 placeholderText: i18n.tr("search or enter an address")
131@@ -239,7 +247,7 @@
132 QtObject {
133 id: internal
134
135- readonly property bool idle: !addressbar.loading && !addressbar.activeFocus && addressbar.canSimplifyText
136+ readonly property bool idle: !addressbar.loading && !addressbar.editing
137
138 readonly property int securityLevel: addressbar.securityStatus ? addressbar.securityStatus.securityLevel : Oxide.SecurityStatus.SecurityLevelNone
139 readonly property bool secureConnection: addressbar.securityStatus ? (securityLevel == Oxide.SecurityStatus.SecurityLevelSecure || securityLevel == Oxide.SecurityStatus.SecurityLevelSecureEV || securityLevel == Oxide.SecurityStatus.SecurityLevelWarning) : false
140@@ -304,28 +312,51 @@
141 property bool simplified: false
142 }
143
144- onCanSimplifyTextChanged: {
145- if (canSimplifyText) {
146- if (!loading && actualUrl.toString()) {
147+ onEditingChanged: {
148+ if (editing && internal.simplified) {
149+ text = actualUrl
150+ internal.simplified = false
151+ } else if (!editing) {
152+ if (canSimplifyText && !loading && actualUrl.toString()) {
153 text = internal.simplifyUrl(actualUrl)
154 internal.simplified = true
155+ } else {
156+ text = actualUrl
157+ internal.simplified = false
158 }
159- } else if (internal.simplified) {
160+ }
161+ }
162+
163+ onCanSimplifyTextChanged: {
164+ if (editing) return
165+ if (canSimplifyText && !loading && actualUrl.toString()) {
166+ text = internal.simplifyUrl(actualUrl)
167+ internal.simplified = true
168+ } else if (!canSimplifyText && internal.simplified) {
169 text = actualUrl
170 internal.simplified = false
171 }
172 }
173
174 onActualUrlChanged: {
175- if (canSimplifyText || !actualUrl.toString()) {
176+ if (editing && actualUrl.toString()) return
177+ if (canSimplifyText) {
178 text = internal.simplifyUrl(actualUrl)
179 internal.simplified = true
180+ } else {
181+ text = actualUrl
182+ internal.simplified = false
183 }
184 }
185+
186 onRequestedUrlChanged: {
187+ if (editing) return
188 if (canSimplifyText) {
189 text = internal.simplifyUrl(requestedUrl)
190 internal.simplified = true
191+ } else {
192+ text = requestedUrl
193+ internal.simplified = false
194 }
195 }
196
197
198=== modified file 'src/app/webbrowser/Browser.qml'
199--- src/app/webbrowser/Browser.qml 2015-07-02 13:48:25 +0000
200+++ src/app/webbrowser/Browser.qml 2015-07-09 14:01:32 +0000
201@@ -62,6 +62,21 @@
202 }
203 }
204
205+ Connections {
206+ target: tabsModel
207+ onCurrentIndexChanged: {
208+ // Remove focus from the address bar when the current tab
209+ // changes to ensure that its contents are updated.
210+ tabContainer.forceActiveFocus()
211+
212+ // In narrow mode, the tabslist is a stack:
213+ // the current tab is always at the top.
214+ if (!browser.wide) {
215+ tabsModel.move(tabsModel.currentIndex, 0)
216+ }
217+ }
218+ }
219+
220 actions: [
221 Actions.GoTo {
222 onTriggered: currentWebview.url = value
223@@ -116,7 +131,7 @@
224 readonly property bool restoreSession: true
225 }
226
227- Item {
228+ FocusScope {
229 anchors.fill: parent
230 visible: !settingsContainer.visible && !historyViewContainer.visible
231
232@@ -221,13 +236,13 @@
233 onBookmarkClicked: {
234 chrome.requestedUrl = url
235 currentWebview.url = url
236- currentWebview.forceActiveFocus()
237+ tabContainer.forceActiveFocus()
238 }
239 onBookmarkRemoved: browser.bookmarksModel.remove(url)
240 onHistoryEntryClicked: {
241 chrome.requestedUrl = url
242 currentWebview.url = url
243- currentWebview.forceActiveFocus()
244+ tabContainer.forceActiveFocus()
245 }
246 }
247 }
248@@ -252,10 +267,14 @@
249 visible: !recentView.visible
250
251 webview: browser.currentWebview
252+ tabsModel: browser.tabsModel
253 searchUrl: currentSearchEngine.urlTemplate
254
255 incognito: browser.incognito
256
257+ showTabsBar: browser.wide
258+ showFaviconInAddressBar: !browser.wide
259+
260 y: webview ? webview.locationBarController.offset : 0
261
262 function isCurrentUrlBookmarked() {
263@@ -280,11 +299,12 @@
264 onRemoved: if (chrome.bookmarked && (url === chrome.webview.url)) chrome.bookmarked = false
265 }
266
267+ onRequestNewTab: browser.openUrlInNewTab("", true)
268+
269 anchors {
270 left: parent.left
271 right: parent.right
272 }
273- height: units.gu(6)
274
275 drawerActions: [
276 Action {
277@@ -308,18 +328,17 @@
278 objectName: "tabs"
279 text: i18n.tr("Open tabs")
280 iconName: "browser-tabs"
281- enabled: formFactor != "mobile"
282+ enabled: (formFactor != "mobile") && !browser.wide
283 onTriggered: {
284 recentView.state = "shown"
285 recentToolbar.state = "shown"
286- recentView.focus = true
287 }
288 },
289 Action {
290 objectName: "newtab"
291 text: i18n.tr("New tab")
292 iconName: browser.incognito ? "private-tab-new" : "tab-new"
293- enabled: formFactor != "mobile"
294+ enabled: (formFactor != "mobile") && !browser.wide
295 onTriggered: browser.openUrlInNewTab("", true)
296 },
297 Action {
298@@ -335,6 +354,7 @@
299 objectName: "privatemode"
300 text: browser.incognito ? i18n.tr("Leave Private Mode") : i18n.tr("Private Mode")
301 iconName: "private-browsing"
302+ iconSource: browser.incognito ? Qt.resolvedUrl("assets/private-browsing-exit.svg") : ""
303 onTriggered: {
304 if (browser.incognito) {
305 if (tabsModel.count > 1) {
306@@ -350,7 +370,9 @@
307 }
308 ]
309
310- addressBarCanSimplifyText: !(activeFocus || suggestionsList.activeFocus)
311+ canSimplifyText: !browser.wide
312+ editing: activeFocus || suggestionsList.activeFocus
313+
314 Keys.onDownPressed: if (suggestionsList.count) suggestionsList.focus = true
315 Keys.onEscapePressed: internal.resetFocus()
316 }
317@@ -428,7 +450,7 @@
318
319 onActivated: {
320 browser.currentWebview.url = url
321- browser.currentWebview.forceActiveFocus()
322+ tabContainer.forceActiveFocus()
323 chrome.requestedUrl = url
324 }
325 }
326@@ -589,6 +611,15 @@
327 }
328 }
329
330+ onWideChanged: {
331+ if (wide) {
332+ recentView.reset()
333+ } else {
334+ // In narrow mode, the tabslist is a stack: the current tab is always at the top.
335+ tabsModel.move(tabsModel.currentIndex, 0)
336+ }
337+ }
338+
339 BottomEdgeHandle {
340 id: bottomEdgeHandle
341
342@@ -782,6 +813,7 @@
343 BrowserTab {
344 anchors.fill: parent
345 current: tabsModel && tabsModel.currentTab === this
346+ focus: current
347
348 webviewComponent: WebViewImpl {
349 id: webviewimpl
350@@ -986,38 +1018,37 @@
351 function addTab(tab, setCurrent) {
352 var index = tabsModel.add(tab)
353 if (setCurrent) {
354- tabsModel.setCurrent(index)
355+ tabsModel.currentIndex = index
356 chrome.requestedUrl = tab.initialUrl
357 }
358 }
359
360 function switchToTab(index) {
361- var tab = tabsModel.get(index)
362+ tabsModel.currentIndex = index
363+ var tab = tabsModel.currentTab
364 if (tab) {
365- tab.load()
366- tabslist.model.setCurrent(index)
367- if (tab.initialUrl == "" && formFactor == "desktop") focusAddressBar()
368- else tab.forceActiveFocus()
369+ if (!tab.url.toString() && !tab.initialUrl.toString() &&
370+ (formFactor == "desktop")) {
371+ focusAddressBar()
372+ } else {
373+ tabContainer.forceActiveFocus()
374+ }
375 }
376 }
377
378 function closeCurrentTab() {
379 if (tabsModel.count > 0) {
380- var tab = tabsModel.remove(0)
381- if (tab) tab.close()
382-
383- if (tabsModel.count === 0) {
384- browser.openUrlInNewTab("", true)
385- } else {
386- internal.switchToTab(0)
387+ var tab = tabsModel.remove(tabsModel.currentIndex)
388+ if (tab) {
389+ tab.close()
390 }
391- }
392+ }
393 }
394
395 function focusAddressBar(selectContent) {
396 chrome.forceActiveFocus()
397 Qt.inputMethod.show() // work around http://pad.lv/1316057
398- if (selectContent) chrome.addressBarSelectAll()
399+ if (selectContent) chrome.selectAll()
400 }
401
402 function resetFocus() {
403@@ -1025,7 +1056,7 @@
404 if (!browser.currentWebview.url.toString() && (formFactor == "desktop")) {
405 internal.focusAddressBar()
406 } else {
407- browser.currentWebview.forceActiveFocus()
408+ tabContainer.forceActiveFocus()
409 }
410 }
411 }
412@@ -1104,7 +1135,7 @@
413 var tab = publicTabsModel.get(i)
414 tabs.push(serializeTabState(tab))
415 }
416- store(JSON.stringify({tabs: tabs}))
417+ store(JSON.stringify({tabs: tabs, currentIndex: publicTabsModel.currentIndex}))
418 }
419
420 function restore() {
421@@ -1125,6 +1156,9 @@
422 internal.addTab(tab, i == 0)
423 }
424 }
425+ if ('currentIndex' in state) {
426+ publicTabsModel.currentIndex = state.currentIndex
427+ }
428 }
429 }
430
431@@ -1134,6 +1168,7 @@
432 state.uniqueId = tab.uniqueId
433 state.url = tab.url.toString()
434 state.title = tab.title
435+ state.icon = tab.icon.toString()
436 state.preview = tab.preview.toString()
437 state.savedState = tab.webview ? tab.webview.currentState : tab.restoreState
438 return state
439@@ -1144,6 +1179,9 @@
440 if ('uniqueId' in state) {
441 properties["uniqueId"] = state.uniqueId
442 }
443+ if ('icon' in state) {
444+ properties["initialIcon"] = state.icon
445+ }
446 if ('preview' in state) {
447 properties["preview"] = state.preview
448 }
449@@ -1209,10 +1247,13 @@
450 }
451 }
452
453- // Ensure that at most n webviews are instantiated at all times,
454- // to reduce memory consumption (see http://pad.lv/1376418).
455 Connections {
456- target: tabsModel
457+ // On mobile, ensure that at most n webviews are instantiated at all
458+ // times, to reduce memory consumption (see http://pad.lv/1376418).
459+ // Note: this works only in narrow mode, where the list of tabs is a
460+ // stack. Switching from wide mode to narrow mode will result in
461+ // undefined behaviour (tabs previously loaded won’t be unloaded).
462+ target: ((formFactor == "mobile") && !browser.wide) ? tabsModel : null
463 onCurrentTabChanged: {
464 if (tabsModel.count > browser.maxLiveWebviews) {
465 tabsModel.get(browser.maxLiveWebviews).unload()
466@@ -1220,6 +1261,26 @@
467 }
468 }
469
470+ Connections {
471+ target: tabsModel
472+ onCurrentTabChanged: {
473+ var tab = tabsModel.currentTab
474+ if (tab) {
475+ tab.load()
476+ }
477+ }
478+ onCountChanged: {
479+ if (tabsModel.count == 0) {
480+ if (browser.incognito) {
481+ browser.incognito = false
482+ internal.resetFocus()
483+ } else if ((formFactor == "desktop") || browser.wide) {
484+ Qt.quit()
485+ }
486+ }
487+ }
488+ }
489+
490 Component {
491 id: leavePrivateModeDialog
492
493@@ -1250,20 +1311,37 @@
494 KeyboardShortcuts {
495 id: shortcuts
496
497- // Ctrl + Tab: pull the tab from the bottom of the stack to the
498- // top (i.e. make it current)
499+ // Ctrl+Tab: cycle through open tabs
500 KeyboardShortcut {
501 modifiers: Qt.ControlModifier
502 key: Qt.Key_Tab
503 enabled: chrome.visible || recentView.visible
504 onTriggered: {
505- internal.switchToTab(tabsModel.count - 1)
506- if (chrome.visible) recentView.reset()
507- else if (recentView.visible) recentView.focus = true
508- }
509- }
510-
511- // Ctrl + w or Ctrl+F4: Close the current tab
512+ if (browser.wide) {
513+ internal.switchToTab((tabsModel.currentIndex + 1) % tabsModel.count)
514+ } else {
515+ internal.switchToTab(tabsModel.count - 1)
516+ }
517+ if (recentView.visible) recentView.focus = true
518+ }
519+ }
520+
521+ // Ctrl+Shift+Tab: cycle through open tabs in reverse order
522+ KeyboardShortcut {
523+ modifiers: Qt.ControlModifier
524+ key: Qt.Key_Backtab
525+ enabled: chrome.visible || recentView.visible
526+ onTriggered: {
527+ if (browser.wide) {
528+ internal.switchToTab((tabsModel.currentIndex - 1 + tabsModel.count) % tabsModel.count)
529+ } else {
530+ internal.switchToTab(tabsModel.count - 1)
531+ }
532+ if (recentView.visible) recentView.focus = true
533+ }
534+ }
535+
536+ // Ctrl+W or Ctrl+F4: Close the current tab
537 KeyboardShortcut {
538 modifiers: Qt.ControlModifier
539 key: Qt.Key_W
540@@ -1277,7 +1355,7 @@
541 onTriggered: internal.closeCurrentTab()
542 }
543
544- // Ctrl + t: Open a new Tab
545+ // Ctrl+T: Open a new Tab
546 KeyboardShortcut {
547 modifiers: Qt.ControlModifier
548 key: Qt.Key_T
549@@ -1288,7 +1366,7 @@
550 }
551 }
552
553- // F6 or Ctrl + L or Alt + D: Select the content in the address bar
554+ // F6 or Ctrl+L or Alt+D: Select the content in the address bar
555 KeyboardShortcut {
556 modifiers: Qt.ControlModifier
557 key: Qt.Key_L
558@@ -1307,7 +1385,7 @@
559 onTriggered: internal.focusAddressBar(true)
560 }
561
562- // Ctrl + D: Toggle bookmarked state on current Tab
563+ // Ctrl+D: Toggle bookmarked state on current Tab
564 KeyboardShortcut {
565 modifiers: Qt.ControlModifier
566 key: Qt.Key_D
567@@ -1323,7 +1401,7 @@
568 }
569 }
570
571- // Ctrl + H: Show History
572+ // Ctrl+H: Show History
573 KeyboardShortcut {
574 modifiers: Qt.ControlModifier
575 key: Qt.Key_H
576@@ -1336,7 +1414,7 @@
577 }
578 }
579
580- // Alt + Left Arrow or Backspace: Goes to the previous page in history
581+ // Alt+← or Backspace: Goes to the previous page in history
582 KeyboardShortcut {
583 modifiers: Qt.AltModifier
584 key: Qt.Key_Left
585@@ -1349,7 +1427,7 @@
586 onTriggered: internal.historyGoBack()
587 }
588
589- // Alt + Right Arrow or Shift + Backspace: Goes to the next page in history
590+ // Alt+→ or Shift+Backspace: Goes to the next page in history
591 KeyboardShortcut {
592 modifiers: Qt.AltModifier
593 key: Qt.Key_Right
594@@ -1363,7 +1441,7 @@
595 onTriggered: internal.historyGoForward()
596 }
597
598- // F5 or Ctrl + R: Reload current Tab
599+ // F5 or Ctrl+R: Reload current Tab
600 KeyboardShortcut {
601 key: Qt.Key_F5
602 enabled: chrome.visible
603
604=== modified file 'src/app/webbrowser/BrowserTab.qml'
605--- src/app/webbrowser/BrowserTab.qml 2015-06-02 14:26:23 +0000
606+++ src/app/webbrowser/BrowserTab.qml 2015-07-09 14:01:32 +0000
607@@ -27,6 +27,7 @@
608 property string uniqueId: this.toString() + "-" + Date.now()
609 property url initialUrl
610 property string initialTitle
611+ property url initialIcon
612 property string restoreState
613 property int restoreType
614 property var request
615@@ -34,7 +35,7 @@
616 readonly property var webview: webviewContainer.webview
617 readonly property url url: webview ? webview.url : initialUrl
618 readonly property string title: webview ? webview.title : initialTitle
619- readonly property url icon: webview ? webview.icon : ""
620+ readonly property url icon: webview ? webview.icon : initialIcon
621 property url preview
622 property bool current: false
623 property bool incognito
624@@ -63,6 +64,7 @@
625 if (webview) {
626 initialUrl = webview.url
627 initialTitle = webview.title
628+ initialIcon = webview.icon
629 restoreState = webview.currentState
630 restoreType = Oxide.WebView.RestoreCurrentSession
631 webview.destroy()
632
633=== modified file 'src/app/webbrowser/CMakeLists.txt'
634--- src/app/webbrowser/CMakeLists.txt 2015-06-04 19:00:49 +0000
635+++ src/app/webbrowser/CMakeLists.txt 2015-07-09 14:01:32 +0000
636@@ -50,7 +50,7 @@
637
638 install(DIRECTORY assets
639 DESTINATION ${CMAKE_INSTALL_DATADIR}/webbrowser-app/webbrowser
640- FILES_MATCHING PATTERN *.png)
641+ FILES_MATCHING PATTERN *.png PATTERN *.svg)
642
643 install(DIRECTORY searchengines
644 DESTINATION ${CMAKE_INSTALL_DATADIR}/webbrowser-app/webbrowser
645
646=== modified file 'src/app/webbrowser/Chrome.qml'
647--- src/app/webbrowser/Chrome.qml 2015-06-19 17:32:38 +0000
648+++ src/app/webbrowser/Chrome.qml 2015-07-09 14:01:32 +0000
649@@ -23,273 +23,72 @@
650 ChromeBase {
651 id: chrome
652
653- property alias searchUrl: addressbar.searchUrl
654- readonly property string text: addressbar.text
655- property alias bookmarked: addressbar.bookmarked
656- property list<Action> drawerActions
657- readonly property bool drawerOpen: internal.openDrawer
658- property alias requestedUrl: addressbar.requestedUrl
659- property alias addressBarCanSimplifyText: addressbar.canSimplifyText
660- property alias incognito: addressbar.incognito
661+ property var tabsModel
662+ property alias searchUrl: navigationBar.searchUrl
663+ property alias text: navigationBar.text
664+ property alias bookmarked: navigationBar.bookmarked
665+ property alias drawerActions: navigationBar.drawerActions
666+ property alias drawerOpen: navigationBar.drawerOpen
667+ property alias requestedUrl: navigationBar.requestedUrl
668+ property alias canSimplifyText: navigationBar.canSimplifyText
669+ property alias editing: navigationBar.editing
670+ property alias incognito: navigationBar.incognito
671+ property alias showTabsBar: tabsBar.active
672+ property alias showFaviconInAddressBar: navigationBar.showFaviconInAddressBar
673+ readonly property alias bookmarkTogglePlaceHolder: navigationBar.bookmarkTogglePlaceHolder
674
675- readonly property alias bookmarkTogglePlaceHolder: addressbar.bookmarkTogglePlaceHolder
676+ signal requestNewTab()
677
678 backgroundColor: incognito ? UbuntuColors.darkGrey : Theme.palette.normal.background
679
680- function addressBarSelectAll() { addressbar.selectAll() }
681+ implicitHeight: tabsBar.height + navigationBar.height
682+
683+ function selectAll() {
684+ navigationBar.selectAll()
685+ }
686
687 FocusScope {
688- anchors {
689- fill: parent
690- margins: units.gu(1)
691- }
692+ anchors.fill: parent
693
694 focus: true
695
696- ChromeButton {
697- id: backButton
698- objectName: "backButton"
699-
700- iconName: "previous"
701- iconSize: 0.4 * height
702- iconColor: internal.iconColor
703-
704- height: chrome.height
705- width: height * 0.8
706+ Loader {
707+ id: tabsBar
708+
709+ sourceComponent: TabsBar {
710+ model: tabsModel
711+ incognito: chrome.incognito
712+ onRequestNewTab: chrome.requestNewTab()
713+ }
714
715 anchors {
716+ top: parent.top
717 left: parent.left
718- verticalCenter: parent.verticalCenter
719- }
720-
721- enabled: chrome.webview ? chrome.webview.canGoBack : false
722- onTriggered: chrome.webview.goBack()
723- }
724-
725- ChromeButton {
726- id: forwardButton
727- objectName: "forwardButton"
728-
729- iconName: "next"
730- iconSize: 0.4 * height
731- iconColor: internal.iconColor
732-
733- height: chrome.height
734- visible: enabled
735- width: visible ? height * 0.8 : 0
736-
737- anchors {
738- left: backButton.right
739- verticalCenter: parent.verticalCenter
740- }
741-
742- enabled: chrome.webview ? chrome.webview.canGoForward : false
743- onTriggered: chrome.webview.goForward()
744- }
745-
746- AddressBar {
747- id: addressbar
748+ right: parent.right
749+ }
750+ height: active ? units.gu(4) : 0
751+ }
752+
753+ Rectangle {
754+ anchors.fill: navigationBar
755+ color: (showTabsBar || !incognito) ? "#dedede" : UbuntuColors.darkGrey
756+ }
757+
758+ NavigationBar {
759+ id: navigationBar
760+
761+ iconColor: (incognito && !showTabsBar) ? "white" : UbuntuColors.darkGrey
762+
763+ webview: chrome.webview
764
765 focus: true
766
767 anchors {
768- left: forwardButton.right
769- leftMargin: units.gu(1)
770- right: drawerButton.left
771- rightMargin: units.gu(1)
772- verticalCenter: parent.verticalCenter
773- }
774-
775- icon: (chrome.webview && !chrome.webview.certificateError) ? chrome.webview.icon : ""
776-
777- loading: chrome.webview ? chrome.webview.loading : false
778-
779- onValidated: {
780- chrome.webview.forceActiveFocus()
781- chrome.webview.url = requestedUrl
782- }
783- onRequestReload: {
784- chrome.webview.forceActiveFocus()
785- chrome.webview.reload()
786- }
787- onRequestStop: chrome.webview.stop()
788-
789- Connections {
790- target: chrome.webview
791- onUrlChanged: {
792- // ensure that the URL actually changes so that the
793- // address bar is updated in case the user has entered a
794- // new address that redirects to where she previously was
795- // (https://bugs.launchpad.net/webbrowser-app/+bug/1306615)
796- addressbar.actualUrl = ""
797- addressbar.actualUrl = chrome.webview.url
798- }
799- }
800-
801- Connections {
802- target: chrome
803- onYChanged: addressbar.hideSecurityCertificateDetails()
804- }
805- }
806-
807- ChromeButton {
808- id: drawerButton
809- objectName: "drawerButton"
810-
811- iconName: "contextual-menu"
812- iconSize: 0.5 * height
813- iconColor: internal.iconColor
814-
815- height: chrome.height
816- width: height * 0.8
817-
818- anchors {
819- right: parent.right
820- verticalCenter: parent.verticalCenter
821- }
822-
823- onTriggered: {
824- if (!internal.openDrawer) {
825- internal.openDrawer = drawerComponent.createObject(chrome)
826- internal.openDrawer.opened = true
827- }
828- }
829- }
830- }
831-
832- QtObject {
833- id: internal
834- property var openDrawer: null
835- readonly property color iconColor: chrome.incognito ? "white" : "grey"
836- }
837-
838- onWebviewChanged: {
839- if (webview) {
840- addressbar.actualUrl = webview.url
841- addressbar.securityStatus = webview.securityStatus
842- } else {
843- addressbar.actualUrl = ""
844- addressbar.securityStatus = null
845- }
846- }
847-
848- Component {
849- id: drawerComponent
850-
851- Item {
852- id: drawer
853- objectName: "drawer"
854-
855- property bool opened: false
856- property bool closing: false
857- onOpenedChanged: {
858- if (!opened) {
859- closing = true
860- }
861- }
862-
863- anchors {
864- top: parent.bottom
865- right: parent.right
866- }
867- width: units.gu(22)
868- height: actionsColumn.height
869- clip: actionsColumn.y != 0
870-
871- InverseMouseArea {
872- enabled: drawer.opened
873- onPressed: drawer.opened = false
874- }
875-
876- Rectangle {
877- anchors.fill: actionsColumn
878- color: Theme.palette.normal.background
879-
880- Rectangle {
881- anchors {
882- top: parent.top
883- bottom: parent.bottom
884- left: parent.left
885- }
886- width: units.dp(1)
887- color: "#dedede"
888- }
889-
890- Rectangle {
891- anchors {
892- left: parent.left
893- right: parent.right
894- bottom: parent.bottom
895- }
896- height: units.dp(1)
897- color: "#dedede"
898- }
899- }
900-
901- Column {
902- id: actionsColumn
903-
904- anchors {
905- left: parent.left
906- right: parent.right
907- }
908-
909- y: drawer.opened ? 0 : -height
910- Behavior on y { UbuntuNumberAnimation {} }
911- onYChanged: {
912- if (drawer.closing && (y == -height)) {
913- drawer.destroy()
914- }
915- }
916-
917- Repeater {
918- model: chrome.drawerActions
919- delegate: AbstractButton {
920- objectName: action.objectName
921- anchors {
922- left: parent.left
923- right: parent.right
924- }
925- height: units.gu(6)
926- visible: action.enabled
927-
928- action: modelData
929- onClicked: drawer.opened = false
930-
931- Rectangle {
932- anchors.fill: parent
933- color: Theme.palette.selected.background
934- visible: parent.pressed
935- }
936-
937- Icon {
938- id: actionIcon
939- anchors {
940- left: parent.left
941- leftMargin: units.gu(2)
942- verticalCenter: parent.verticalCenter
943- }
944- width: units.gu(2)
945- height: width
946-
947- name: model.iconName
948- }
949-
950- Label {
951- anchors {
952- left: actionIcon.right
953- leftMargin: units.gu(2)
954- verticalCenter: parent.verticalCenter
955- right: parent.right
956- rightMargin: units.gu(1)
957- }
958- text: model.text
959- fontSize: "small"
960- elide: Text.ElideRight
961- }
962- }
963- }
964- }
965+ bottom: parent.bottom
966+ left: parent.left
967+ right: parent.right
968+ }
969+ height: units.gu(6)
970 }
971 }
972 }
973
974=== added file 'src/app/webbrowser/NavigationBar.qml'
975--- src/app/webbrowser/NavigationBar.qml 1970-01-01 00:00:00 +0000
976+++ src/app/webbrowser/NavigationBar.qml 2015-07-09 14:01:32 +0000
977@@ -0,0 +1,298 @@
978+/*
979+ * Copyright 2013-2015 Canonical Ltd.
980+ *
981+ * This file is part of webbrowser-app.
982+ *
983+ * webbrowser-app is free software; you can redistribute it and/or modify
984+ * it under the terms of the GNU General Public License as published by
985+ * the Free Software Foundation; version 3.
986+ *
987+ * webbrowser-app is distributed in the hope that it will be useful,
988+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
989+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
990+ * GNU General Public License for more details.
991+ *
992+ * You should have received a copy of the GNU General Public License
993+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
994+ */
995+
996+import QtQuick 2.0
997+import Ubuntu.Components 1.1
998+import ".."
999+
1000+FocusScope {
1001+ id: root
1002+
1003+ property var webview: null
1004+ property alias searchUrl: addressbar.searchUrl
1005+ readonly property string text: addressbar.text
1006+ property alias bookmarked: addressbar.bookmarked
1007+ property list<Action> drawerActions
1008+ readonly property bool drawerOpen: internal.openDrawer
1009+ property alias requestedUrl: addressbar.requestedUrl
1010+ property alias canSimplifyText: addressbar.canSimplifyText
1011+ property alias editing: addressbar.editing
1012+ property alias incognito: addressbar.incognito
1013+ property alias showFaviconInAddressBar: addressbar.showFavicon
1014+ readonly property alias bookmarkTogglePlaceHolder: addressbar.bookmarkTogglePlaceHolder
1015+ property color iconColor: UbuntuColors.darkGrey
1016+
1017+ function selectAll() {
1018+ addressbar.selectAll()
1019+ }
1020+
1021+ FocusScope {
1022+ anchors {
1023+ fill: parent
1024+ margins: units.gu(1)
1025+ }
1026+
1027+ focus: true
1028+
1029+ ChromeButton {
1030+ id: backButton
1031+ objectName: "backButton"
1032+
1033+ iconName: "previous"
1034+ iconSize: 0.4 * height
1035+ iconColor: root.iconColor
1036+
1037+ height: root.height
1038+ width: height * 0.8
1039+
1040+ anchors {
1041+ left: parent.left
1042+ verticalCenter: parent.verticalCenter
1043+ }
1044+
1045+ enabled: webview ? webview.canGoBack : false
1046+ onTriggered: webview.goBack()
1047+ }
1048+
1049+ ChromeButton {
1050+ id: forwardButton
1051+ objectName: "forwardButton"
1052+
1053+ iconName: "next"
1054+ iconSize: 0.4 * height
1055+ iconColor: root.iconColor
1056+
1057+ height: root.height
1058+ visible: enabled
1059+ width: visible ? height * 0.8 : 0
1060+
1061+ anchors {
1062+ left: backButton.right
1063+ verticalCenter: parent.verticalCenter
1064+ }
1065+
1066+ enabled: webview ? webview.canGoForward : false
1067+ onTriggered: webview.goForward()
1068+ }
1069+
1070+ AddressBar {
1071+ id: addressbar
1072+
1073+ focus: true
1074+
1075+ anchors {
1076+ left: forwardButton.right
1077+ leftMargin: units.gu(1)
1078+ right: drawerButton.left
1079+ rightMargin: units.gu(1)
1080+ verticalCenter: parent.verticalCenter
1081+ }
1082+
1083+ icon: (webview && !webview.certificateError) ? webview.icon : ""
1084+
1085+ loading: webview ? webview.loading : false
1086+
1087+ onValidated: {
1088+ webview.forceActiveFocus()
1089+ webview.url = requestedUrl
1090+ }
1091+ onRequestReload: {
1092+ webview.forceActiveFocus()
1093+ webview.reload()
1094+ }
1095+ onRequestStop: webview.stop()
1096+
1097+ Connections {
1098+ target: webview
1099+ onUrlChanged: {
1100+ // ensure that the URL actually changes so that the
1101+ // address bar is updated in case the user has entered a
1102+ // new address that redirects to where she previously was
1103+ // (https://launchpad.net/bugs/1306615)
1104+ addressbar.actualUrl = ""
1105+ addressbar.actualUrl = webview.url
1106+ }
1107+ }
1108+ }
1109+
1110+ ChromeButton {
1111+ id: drawerButton
1112+ objectName: "drawerButton"
1113+
1114+ iconName: "contextual-menu"
1115+ iconSize: 0.5 * height
1116+ iconColor: root.iconColor
1117+
1118+ height: root.height
1119+ width: height * 0.8
1120+
1121+ anchors {
1122+ right: parent.right
1123+ verticalCenter: parent.verticalCenter
1124+ }
1125+
1126+ onTriggered: {
1127+ if (!internal.openDrawer) {
1128+ internal.openDrawer = drawerComponent.createObject(chrome)
1129+ internal.openDrawer.opened = true
1130+ }
1131+ }
1132+ }
1133+ }
1134+
1135+ QtObject {
1136+ id: internal
1137+ property var openDrawer: null
1138+ }
1139+
1140+ onWebviewChanged: {
1141+ if (webview) {
1142+ addressbar.actualUrl = webview.url
1143+ addressbar.securityStatus = webview.securityStatus
1144+ } else {
1145+ addressbar.actualUrl = ""
1146+ addressbar.securityStatus = null
1147+ }
1148+ }
1149+
1150+ Component {
1151+ id: drawerComponent
1152+
1153+ Item {
1154+ id: drawer
1155+ objectName: "drawer"
1156+
1157+ property bool opened: false
1158+ property bool closing: false
1159+ onOpenedChanged: {
1160+ if (!opened) {
1161+ closing = true
1162+ }
1163+ }
1164+
1165+ anchors {
1166+ top: parent.bottom
1167+ right: parent.right
1168+ }
1169+ width: units.gu(22)
1170+ height: actionsColumn.height
1171+ clip: actionsColumn.y != 0
1172+
1173+ InverseMouseArea {
1174+ enabled: drawer.opened
1175+ onPressed: drawer.opened = false
1176+ }
1177+
1178+ Rectangle {
1179+ anchors.fill: actionsColumn
1180+ color: Theme.palette.normal.background
1181+
1182+ Rectangle {
1183+ anchors {
1184+ top: parent.top
1185+ bottom: parent.bottom
1186+ left: parent.left
1187+ }
1188+ width: units.dp(1)
1189+ color: "#dedede"
1190+ }
1191+
1192+ Rectangle {
1193+ anchors {
1194+ left: parent.left
1195+ right: parent.right
1196+ bottom: parent.bottom
1197+ }
1198+ height: units.dp(1)
1199+ color: "#dedede"
1200+ }
1201+ }
1202+
1203+ Column {
1204+ id: actionsColumn
1205+
1206+ anchors {
1207+ left: parent.left
1208+ right: parent.right
1209+ }
1210+
1211+ y: drawer.opened ? 0 : -height
1212+ Behavior on y { UbuntuNumberAnimation {} }
1213+ onYChanged: {
1214+ if (drawer.closing && (y == -height)) {
1215+ drawer.destroy()
1216+ }
1217+ }
1218+
1219+ Repeater {
1220+ model: drawerActions
1221+ delegate: AbstractButton {
1222+ objectName: action.objectName
1223+ anchors {
1224+ left: parent.left
1225+ right: parent.right
1226+ }
1227+ height: units.gu(6)
1228+ visible: action.enabled
1229+
1230+ action: modelData
1231+ onClicked: drawer.opened = false
1232+
1233+ Rectangle {
1234+ anchors.fill: parent
1235+ color: Theme.palette.selected.background
1236+ visible: parent.pressed
1237+ }
1238+
1239+ Icon {
1240+ id: actionIcon
1241+ anchors {
1242+ left: parent.left
1243+ leftMargin: units.gu(2)
1244+ verticalCenter: parent.verticalCenter
1245+ }
1246+ width: units.gu(2)
1247+ height: width
1248+
1249+ name: model.iconName
1250+ Binding on source {
1251+ when: model.iconSource.toString()
1252+ value: model.iconSource
1253+ }
1254+ color: UbuntuColors.darkGrey
1255+ }
1256+
1257+ Label {
1258+ anchors {
1259+ left: actionIcon.right
1260+ leftMargin: units.gu(2)
1261+ verticalCenter: parent.verticalCenter
1262+ right: parent.right
1263+ rightMargin: units.gu(1)
1264+ }
1265+ text: model.text
1266+ fontSize: "small"
1267+ color: UbuntuColors.darkGrey
1268+ elide: Text.ElideRight
1269+ }
1270+ }
1271+ }
1272+ }
1273+ }
1274+ }
1275+}
1276
1277=== modified file 'src/app/webbrowser/NewTabView.qml'
1278--- src/app/webbrowser/NewTabView.qml 2015-06-12 01:11:46 +0000
1279+++ src/app/webbrowser/NewTabView.qml 2015-07-09 14:01:32 +0000
1280@@ -116,7 +116,7 @@
1281
1282 anchors { top: parent.top; topMargin: units.gu(1) }
1283
1284- strokeColor: "#5d5d5d"
1285+ strokeColor: UbuntuColors.darkGrey
1286
1287 visible: internal.numberOfBookmarks > 4
1288
1289@@ -255,7 +255,7 @@
1290 verticalAlignment: Text.AlignVCenter
1291
1292 text: i18n.tr("You haven't visited any site yet")
1293- color: "#5d5d5d"
1294+ color: UbuntuColors.darkGrey
1295 }
1296
1297 UrlsList {
1298
1299=== modified file 'src/app/webbrowser/Suggestion.qml'
1300--- src/app/webbrowser/Suggestion.qml 2015-04-29 14:56:36 +0000
1301+++ src/app/webbrowser/Suggestion.qml 2015-07-09 14:01:32 +0000
1302@@ -51,6 +51,7 @@
1303 }
1304 width: units.gu(2)
1305 height: units.gu(2)
1306+ color: UbuntuColors.darkGrey
1307 }
1308
1309 Label {
1310@@ -62,7 +63,7 @@
1311 leftMargin: units.gu(2)
1312 right: parent.right
1313 }
1314-
1315+ color: selected ? "#DB4923" : UbuntuColors.darkGrey
1316 elide: Text.ElideRight
1317 }
1318
1319@@ -77,7 +78,7 @@
1320 fontSize: "small"
1321 elide: Text.ElideRight
1322 visible: text !== ""
1323- color: selected ? "#DB4923" : "black"
1324+ color: selected ? "#DB4923" : UbuntuColors.darkGrey
1325 }
1326 }
1327
1328
1329=== modified file 'src/app/webbrowser/Suggestions.qml'
1330--- src/app/webbrowser/Suggestions.qml 2015-05-25 17:28:36 +0000
1331+++ src/app/webbrowser/Suggestions.qml 2015-07-09 14:01:32 +0000
1332@@ -69,8 +69,8 @@
1333 width: suggestionsList.width
1334 showDivider: index < model.length - 1
1335
1336- title: highlightTerms(modelData.title)
1337- subtitle: modelData.displayUrl ? highlightTerms(modelData.url) : ""
1338+ title: selected ? modelData.title : highlightTerms(modelData.title)
1339+ subtitle: modelData.displayUrl ? (selected ? modelData.url : highlightTerms(modelData.url)) : ""
1340 icon: modelData.icon
1341 selected: suggestionsList.activeFocus && ListView.isCurrentItem
1342
1343@@ -98,7 +98,7 @@
1344 }
1345 var highlighted = text.toString()
1346 var count = searchTerms.length
1347- var highlight = '<b><font color="%1">$&</font></b>'.arg(UbuntuColors.orange)
1348+ var highlight = '<font color="%1">$&</font>'.arg("#752571")
1349 for (var i = 0; i < count; ++i) {
1350 var term = searchTerms[i]
1351 highlighted = highlighted.replace(escapeTerm(term), highlight)
1352
1353=== added file 'src/app/webbrowser/TabsBar.qml'
1354--- src/app/webbrowser/TabsBar.qml 1970-01-01 00:00:00 +0000
1355+++ src/app/webbrowser/TabsBar.qml 2015-07-09 14:01:32 +0000
1356@@ -0,0 +1,248 @@
1357+/*
1358+ * Copyright 2015 Canonical Ltd.
1359+ *
1360+ * This file is part of webbrowser-app.
1361+ *
1362+ * webbrowser-app is free software; you can redistribute it and/or modify
1363+ * it under the terms of the GNU General Public License as published by
1364+ * the Free Software Foundation; version 3.
1365+ *
1366+ * webbrowser-app is distributed in the hope that it will be useful,
1367+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1368+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1369+ * GNU General Public License for more details.
1370+ *
1371+ * You should have received a copy of the GNU General Public License
1372+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1373+ */
1374+
1375+import QtQuick 2.0
1376+import Ubuntu.Components 1.1
1377+import ".."
1378+
1379+Item {
1380+ id: root
1381+
1382+ property alias model: repeater.model
1383+
1384+ property real minTabWidth: 0 //units.gu(6)
1385+ property real maxTabWidth: units.gu(20)
1386+ property real tabWidth: model ? Math.max(Math.min(tabsContainer.maxWidth / model.count, maxTabWidth), minTabWidth) : 0
1387+
1388+ property bool incognito: false
1389+
1390+ signal requestNewTab()
1391+
1392+ MouseArea {
1393+ anchors.fill: parent
1394+ onWheel: {
1395+ var angle = (wheel.angleDelta.x != 0) ? wheel.angleDelta.x : wheel.angleDelta.y
1396+ if ((angle < 0) && (root.model.currentIndex < (root.model.count - 1))) {
1397+ root.model.currentIndex++
1398+ } else if ((angle > 0) && (root.model.currentIndex > 0)) {
1399+ root.model.currentIndex--
1400+ }
1401+ }
1402+ }
1403+
1404+ MouseArea {
1405+ id: newTabButton
1406+ objectName: "newTabButton"
1407+
1408+ anchors {
1409+ left: tabsContainer.right
1410+ leftMargin: units.gu(1)
1411+ top: parent.top
1412+ bottom: parent.bottom
1413+ }
1414+ width: height
1415+
1416+ visible: !repeater.reordering
1417+
1418+ Icon {
1419+ width: units.gu(2)
1420+ height: units.gu(2)
1421+ anchors.centerIn: parent
1422+ name: "add"
1423+ color: incognito ? "white" : UbuntuColors.darkGrey
1424+ }
1425+
1426+ onClicked: root.requestNewTab()
1427+ }
1428+
1429+ Item {
1430+ id: tabsContainer
1431+ objectName: "tabsContainer"
1432+
1433+ anchors {
1434+ top: parent.top
1435+ bottom: parent.bottom
1436+ left: parent.left
1437+ }
1438+ width: tabWidth * root.model.count
1439+ readonly property real maxWidth: root.width - newTabButton.width - units.gu(2)
1440+
1441+ Repeater {
1442+ id: repeater
1443+
1444+ property bool reordering: false
1445+
1446+ delegate: Item {
1447+ id: tabDelegate
1448+ objectName: "tabDelegate"
1449+ readonly property int tabIndex: index
1450+
1451+ anchors {
1452+ top: tabsContainer.top
1453+ bottom: tabsContainer.bottom
1454+ }
1455+ width: tabWidth
1456+
1457+ MouseArea {
1458+ id: mouseArea
1459+ anchors.fill: parent
1460+ acceptedButtons: Qt.LeftButton | Qt.MiddleButton
1461+ hoverEnabled: true
1462+ onPressed: {
1463+ if (mouse.button === Qt.LeftButton) {
1464+ root.model.currentIndex = index
1465+ }
1466+ }
1467+ onReleased: {
1468+ if (mouse.button === Qt.MiddleButton) {
1469+ internal.closeTab(index)
1470+ }
1471+ }
1472+ // XXX: should not start a drag when middle button was pressed
1473+ drag {
1474+ target: tabDelegate
1475+ axis: Drag.XAxis
1476+ minimumX: 0
1477+ maximumX: root.width - tabDelegate.width
1478+ }
1479+ }
1480+
1481+ Binding {
1482+ target: repeater
1483+ property: "reordering"
1484+ value: mouseArea.drag.active
1485+ }
1486+
1487+ readonly property string assetPrefix: (index == root.model.currentIndex) ? "assets/tab-active" : (mouseArea.containsMouse ? "assets/tab-hovered" : "assets/tab-inactive")
1488+
1489+ Item {
1490+ anchors.fill: parent
1491+
1492+ Image {
1493+ id: tabBackgroundLeft
1494+ anchors {
1495+ top: parent.top
1496+ bottom: parent.bottom
1497+ left: parent.left
1498+ }
1499+ source: "%1-left.png".arg(assetPrefix)
1500+ }
1501+
1502+ Image {
1503+ id: tabBackgroundRight
1504+ anchors {
1505+ top: parent.top
1506+ bottom: parent.bottom
1507+ right: parent.right
1508+ rightMargin: units.gu(-1.5)
1509+ }
1510+ source: "%1-right.png".arg(assetPrefix)
1511+ }
1512+
1513+ Image {
1514+ anchors {
1515+ top: parent.top
1516+ bottom: parent.bottom
1517+ left: tabBackgroundLeft.right
1518+ right: tabBackgroundRight.left
1519+ }
1520+ source: "%1-center.png".arg(assetPrefix)
1521+ fillMode: Image.TileHorizontally
1522+ }
1523+ }
1524+
1525+ Row {
1526+ anchors {
1527+ left: parent.left
1528+ right: parent.right
1529+ margins: units.gu(1.5)
1530+ verticalCenter: parent.verticalCenter
1531+ }
1532+ spacing: units.gu(1)
1533+
1534+ Favicon {
1535+ id: favicon
1536+ source: model.icon
1537+ shouldCache: !incognito
1538+ }
1539+
1540+ Label {
1541+ fontSize: "small"
1542+ color: UbuntuColors.darkGrey
1543+ text: model.title ? model.title : (model.url.toString() ? model.url : i18n.tr("New tab"))
1544+ elide: Text.ElideRight
1545+ width: parent.width - favicon.width - closeIcon.width - parent.spacing * 2
1546+ }
1547+
1548+ Icon {
1549+ id: closeIcon
1550+ objectName: "closeButton"
1551+ name: "close"
1552+ color: UbuntuColors.darkGrey
1553+ width: units.gu(1.5)
1554+ height: units.gu(1.5)
1555+ anchors.verticalCenter: parent.verticalCenter
1556+
1557+ MouseArea {
1558+ anchors.fill: parent
1559+ onClicked: internal.closeTab(index)
1560+ }
1561+ }
1562+ }
1563+
1564+ Binding on x {
1565+ when: !mouseArea.drag.active
1566+ value: index * width
1567+ }
1568+
1569+ Behavior on x { NumberAnimation { duration: 250 } }
1570+
1571+ onXChanged: {
1572+ if (!mouseArea.drag.active) return
1573+ if (x < (index * width - width / 2)) {
1574+ root.model.move(index, index - 1)
1575+ } else if ((x > (index * width + width / 2)) && (index < (root.model.count - 1))) {
1576+ root.model.move(index + 1, index)
1577+ }
1578+ }
1579+
1580+ z: (root.model.currentIndex == index) ? 3 : 1 - index / root.model.count
1581+ }
1582+ }
1583+
1584+ Rectangle {
1585+ anchors {
1586+ left: parent.left
1587+ bottom: parent.bottom
1588+ }
1589+ width: root.width
1590+ height: units.dp(1)
1591+ color: "#cacaca"
1592+ z: 2
1593+ }
1594+ }
1595+
1596+ QtObject {
1597+ id: internal
1598+
1599+ function closeTab(index) {
1600+ var tab = root.model.remove(index)
1601+ if (tab) tab.close()
1602+ }
1603+ }
1604+}
1605
1606=== modified file 'src/app/webbrowser/TabsList.qml'
1607--- src/app/webbrowser/TabsList.qml 2015-05-20 14:37:18 +0000
1608+++ src/app/webbrowser/TabsList.qml 2015-07-09 14:01:32 +0000
1609@@ -50,15 +50,12 @@
1610 Repeater {
1611 id: repeater
1612
1613- width: flickable.contentWidth
1614- height: flickable.contentHeight
1615-
1616 delegate: Loader {
1617 id: delegate
1618
1619 asynchronous: true
1620
1621- width: repeater.width
1622+ width: flickable.contentWidth
1623
1624 height: (index == (repeater.model.count - 1)) ? flickable.height : delegateHeight
1625 Behavior on height {
1626
1627=== added file 'src/app/webbrowser/assets/private-browsing-exit.svg'
1628--- src/app/webbrowser/assets/private-browsing-exit.svg 1970-01-01 00:00:00 +0000
1629+++ src/app/webbrowser/assets/private-browsing-exit.svg 2015-07-09 14:01:32 +0000
1630@@ -0,0 +1,174 @@
1631+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
1632+<!-- Created with Inkscape (http://www.inkscape.org/) -->
1633+
1634+<svg
1635+ xmlns:dc="http://purl.org/dc/elements/1.1/"
1636+ xmlns:cc="http://creativecommons.org/ns#"
1637+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
1638+ xmlns:svg="http://www.w3.org/2000/svg"
1639+ xmlns="http://www.w3.org/2000/svg"
1640+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
1641+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
1642+ width="96"
1643+ height="96"
1644+ id="svg4874"
1645+ version="1.1"
1646+ inkscape:version="0.91+devel r"
1647+ viewBox="0 0 96 96.000001"
1648+ sodipodi:docname="private-browsing-exit.svg">
1649+ <defs
1650+ id="defs4876" />
1651+ <sodipodi:namedview
1652+ id="base"
1653+ pagecolor="#ffffff"
1654+ bordercolor="#666666"
1655+ borderopacity="1.0"
1656+ inkscape:pageopacity="0.0"
1657+ inkscape:pageshadow="2"
1658+ inkscape:zoom="5.6199993"
1659+ inkscape:cx="-3.2295429"
1660+ inkscape:cy="67.517779"
1661+ inkscape:document-units="px"
1662+ inkscape:current-layer="g4780"
1663+ showgrid="false"
1664+ showborder="true"
1665+ fit-margin-top="0"
1666+ fit-margin-left="0"
1667+ fit-margin-right="0"
1668+ fit-margin-bottom="0"
1669+ inkscape:snap-bbox="true"
1670+ inkscape:bbox-paths="true"
1671+ inkscape:bbox-nodes="true"
1672+ inkscape:snap-bbox-edge-midpoints="true"
1673+ inkscape:snap-bbox-midpoints="true"
1674+ inkscape:object-paths="true"
1675+ inkscape:snap-intersection-paths="true"
1676+ inkscape:object-nodes="true"
1677+ inkscape:snap-smooth-nodes="true"
1678+ inkscape:snap-midpoints="true"
1679+ inkscape:snap-object-midpoints="true"
1680+ inkscape:snap-center="true"
1681+ showguides="false"
1682+ inkscape:guide-bbox="true"
1683+ inkscape:snap-global="true">
1684+ <inkscape:grid
1685+ type="xygrid"
1686+ id="grid5451"
1687+ empspacing="8" />
1688+ <sodipodi:guide
1689+ orientation="1,0"
1690+ position="8,-8.0000001"
1691+ id="guide4063" />
1692+ <sodipodi:guide
1693+ orientation="1,0"
1694+ position="4,-8.0000001"
1695+ id="guide4065" />
1696+ <sodipodi:guide
1697+ orientation="0,1"
1698+ position="-8,88.000001"
1699+ id="guide4067" />
1700+ <sodipodi:guide
1701+ orientation="0,1"
1702+ position="-8,92.000001"
1703+ id="guide4069" />
1704+ <sodipodi:guide
1705+ orientation="0,1"
1706+ position="104,4"
1707+ id="guide4071" />
1708+ <sodipodi:guide
1709+ orientation="0,1"
1710+ position="-5,8.0000001"
1711+ id="guide4073" />
1712+ <sodipodi:guide
1713+ orientation="1,0"
1714+ position="88,-8.0000001"
1715+ id="guide4077" />
1716+ <sodipodi:guide
1717+ orientation="0,1"
1718+ position="-8,84.000001"
1719+ id="guide4074" />
1720+ <sodipodi:guide
1721+ orientation="1,0"
1722+ position="12,-8.0000001"
1723+ id="guide4076" />
1724+ <sodipodi:guide
1725+ orientation="1,0"
1726+ position="84,-8.0000001"
1727+ id="guide4080" />
1728+ <sodipodi:guide
1729+ position="48,-8.0000001"
1730+ orientation="1,0"
1731+ id="guide4170" />
1732+ <sodipodi:guide
1733+ position="-8,48"
1734+ orientation="0,1"
1735+ id="guide4172" />
1736+ <sodipodi:guide
1737+ position="92,-8.0000001"
1738+ orientation="1,0"
1739+ id="guide4760" />
1740+ </sodipodi:namedview>
1741+ <metadata
1742+ id="metadata4879">
1743+ <rdf:RDF>
1744+ <cc:Work
1745+ rdf:about="">
1746+ <dc:format>image/svg+xml</dc:format>
1747+ <dc:type
1748+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
1749+ <dc:title></dc:title>
1750+ </cc:Work>
1751+ </rdf:RDF>
1752+ </metadata>
1753+ <g
1754+ inkscape:label="Layer 1"
1755+ inkscape:groupmode="layer"
1756+ id="layer1"
1757+ transform="translate(67.857146,-78.50504)">
1758+ <g
1759+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
1760+ id="g4845"
1761+ style="display:inline">
1762+ <g
1763+ inkscape:export-ydpi="90"
1764+ inkscape:export-xdpi="90"
1765+ inkscape:export-filename="next01.png"
1766+ transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
1767+ id="g4778"
1768+ inkscape:label="Layer 1">
1769+ <g
1770+ transform="matrix(-1,0,0,1,575.99999,611)"
1771+ id="g4780"
1772+ style="display:inline">
1773+ <rect
1774+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
1775+ id="rect4782"
1776+ width="96.037987"
1777+ height="96"
1778+ x="-438.00244"
1779+ y="345.36221"
1780+ transform="scale(-1,1)" />
1781+ <path
1782+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.46281052px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.99999994"
1783+ d="M 48 20.300781 C 38.61157 20.300781 29.819444 21.544274 21.621094 24.03125 C 13.488864 26.571345 6.28099 29.969999 0 34.097656 L 0 35.232422 L 0 39.271484 L 0 62.966797 L 0 66.166016 C 2.93113 68.718876 6.2948036 70.82159 10.089844 72.392578 C 11.323477 72.888539 12.589509 73.29685 13.880859 73.632812 L 17.244141 70.269531 C 15.283162 69.934471 13.404813 69.408912 11.597656 68.683594 C 8.7613264 67.507089 6.27868 65.947008 4 64.164062 L 4 62.964844 L 4 39.269531 L 4 36.435547 C 9.542 33.009802 15.729632 30.063875 22.794922 27.855469 C 30.583732 25.494273 38.97036 24.298828 48 24.298828 C 52.916134 24.298828 57.624009 24.665973 62.144531 25.369141 L 65.607422 21.90625 C 60.005065 20.842721 54.13948 20.300781 48 20.300781 z M 70.246094 22.925781 L 66.923828 26.248047 C 69.024109 26.704344 71.082361 27.236254 73.095703 27.851562 C 80.229583 30.061849 86.449297 33.012269 91.998047 36.439453 L 91.998047 39.269531 L 91.998047 62.964844 L 91.998047 64.160156 C 89.713992 65.94464 87.218872 67.510852 84.349609 68.689453 L 84.345703 68.691406 C 81.051703 70.023849 77.507023 70.701172 73.601562 70.701172 C 69.696742 70.701172 66.133404 70.021738 62.802734 68.685547 C 59.409344 67.278264 56.432808 65.417224 53.830078 63.150391 L 53.794922 63.121094 L 53.759766 63.089844 C 52.994136 62.453446 52.124454 61.956258 51.214844 61.599609 L 51.207031 61.597656 L 51.199219 61.59375 C 50.171819 61.195817 49.073763 61.009766 48.001953 61.009766 C 46.929423 61.009766 45.832334 61.196839 44.808594 61.589844 L 44.789062 61.597656 L 44.771484 61.603516 C 43.867754 61.960734 43.003398 62.457134 42.242188 63.089844 L 42.207031 63.121094 L 42.169922 63.150391 C 39.568072 65.416474 36.578527 67.280481 33.148438 68.689453 L 33.142578 68.691406 C 29.869696 70.015297 26.348487 70.690591 22.472656 70.699219 L 18.673828 74.498047 C 19.893567 74.626439 21.134248 74.699219 22.400391 74.699219 C 26.781661 74.699219 30.869023 73.93071 34.664062 72.392578 C 38.489962 70.82159 41.869661 68.718876 44.800781 66.166016 C 45.219521 65.817963 45.700038 65.536561 46.242188 65.322266 C 46.788738 65.112449 47.37411 65.007812 48 65.007812 C 48.6259 65.007812 49.209803 65.112279 49.751953 65.322266 C 50.298513 65.536561 50.780489 65.817963 51.199219 66.166016 C 54.130359 68.718876 57.494023 70.82159 61.289062 72.392578 C 65.114953 73.93071 69.218349 74.699219 73.599609 74.699219 C 77.980879 74.699219 82.070194 73.93071 85.865234 72.392578 C 89.691134 70.82159 93.06887 68.718876 96 66.166016 L 96 62.966797 L 96 39.271484 L 96 35.232422 L 96 34.097656 C 89.71902 29.969999 82.4796 26.571345 74.28125 24.03125 C 72.954808 23.625602 71.605967 23.265265 70.246094 22.925781 z "
1784+ transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
1785+ id="path4186" />
1786+ <path
1787+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.46281052px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.99999994"
1788+ d="M 25.599609 42.798828 C 22.470139 42.798828 19.539431 43.32309 16.806641 44.376953 C 14.095901 45.453007 11.693269 46.866326 9.5996094 48.615234 C 11.693269 50.364032 14.095901 51.775398 16.806641 52.851562 C 19.539431 53.905326 22.470139 54.433594 25.599609 54.433594 C 28.729089 54.433594 31.650578 53.905326 34.361328 52.851562 C 34.545269 52.779127 34.71544 52.692671 34.896484 52.617188 L 40.070312 47.443359 C 38.353065 46.225521 36.453033 45.200579 34.361328 44.376953 C 31.650578 43.32309 28.729089 42.798828 25.599609 42.798828 z "
1789+ transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
1790+ id="path4184" />
1791+ <path
1792+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.46281052px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.00019777"
1793+ d="m 395.18763,370.96223 c 0,-3.12948 -0.5255,-6.04967 -1.57978,-8.76042 -1.07648,-2.73278 -2.48958,-5.14591 -4.23918,-7.23958 -1.74949,2.09367 -3.16259,4.5068 -4.23918,7.23958 -1.05418,2.71075 -1.58188,5.63094 -1.58188,8.76042 0,3.12948 0.5277,6.06097 1.58188,8.79376 1.07659,2.71074 2.48969,5.11258 4.23918,7.20625 1.7496,-2.09367 3.1627,-4.49551 4.23918,-7.20625 1.05428,-2.73279 1.57978,-5.66428 1.57978,-8.79376 z"
1794+ id="path4120" />
1795+ <path
1796+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
1797+ d="m 428.58594,351.94727 -80.03321,80 2.83008,2.83007 80.03125,-80 -2.82812,-2.83007 z"
1798+ id="path4205"
1799+ inkscape:connector-curvature="0" />
1800+ </g>
1801+ </g>
1802+ </g>
1803+ </g>
1804+</svg>
1805
1806=== added file 'src/app/webbrowser/assets/tab-active-center@18.png'
1807Binary files src/app/webbrowser/assets/tab-active-center@18.png 1970-01-01 00:00:00 +0000 and src/app/webbrowser/assets/tab-active-center@18.png 2015-07-09 14:01:32 +0000 differ
1808=== added file 'src/app/webbrowser/assets/tab-active-left@18.png'
1809Binary files src/app/webbrowser/assets/tab-active-left@18.png 1970-01-01 00:00:00 +0000 and src/app/webbrowser/assets/tab-active-left@18.png 2015-07-09 14:01:32 +0000 differ
1810=== added file 'src/app/webbrowser/assets/tab-active-right@18.png'
1811Binary files src/app/webbrowser/assets/tab-active-right@18.png 1970-01-01 00:00:00 +0000 and src/app/webbrowser/assets/tab-active-right@18.png 2015-07-09 14:01:32 +0000 differ
1812=== added file 'src/app/webbrowser/assets/tab-hovered-center@18.png'
1813Binary files src/app/webbrowser/assets/tab-hovered-center@18.png 1970-01-01 00:00:00 +0000 and src/app/webbrowser/assets/tab-hovered-center@18.png 2015-07-09 14:01:32 +0000 differ
1814=== added file 'src/app/webbrowser/assets/tab-hovered-left@18.png'
1815Binary files src/app/webbrowser/assets/tab-hovered-left@18.png 1970-01-01 00:00:00 +0000 and src/app/webbrowser/assets/tab-hovered-left@18.png 2015-07-09 14:01:32 +0000 differ
1816=== added file 'src/app/webbrowser/assets/tab-hovered-right@18.png'
1817Binary files src/app/webbrowser/assets/tab-hovered-right@18.png 1970-01-01 00:00:00 +0000 and src/app/webbrowser/assets/tab-hovered-right@18.png 2015-07-09 14:01:32 +0000 differ
1818=== added file 'src/app/webbrowser/assets/tab-inactive-center@18.png'
1819Binary files src/app/webbrowser/assets/tab-inactive-center@18.png 1970-01-01 00:00:00 +0000 and src/app/webbrowser/assets/tab-inactive-center@18.png 2015-07-09 14:01:32 +0000 differ
1820=== added file 'src/app/webbrowser/assets/tab-inactive-left@18.png'
1821Binary files src/app/webbrowser/assets/tab-inactive-left@18.png 1970-01-01 00:00:00 +0000 and src/app/webbrowser/assets/tab-inactive-left@18.png 2015-07-09 14:01:32 +0000 differ
1822=== added file 'src/app/webbrowser/assets/tab-inactive-right@18.png'
1823Binary files src/app/webbrowser/assets/tab-inactive-right@18.png 1970-01-01 00:00:00 +0000 and src/app/webbrowser/assets/tab-inactive-right@18.png 2015-07-09 14:01:32 +0000 differ
1824=== modified file 'src/app/webbrowser/tabs-model.cpp'
1825--- src/app/webbrowser/tabs-model.cpp 2015-02-19 23:56:46 +0000
1826+++ src/app/webbrowser/tabs-model.cpp 2015-07-09 14:01:32 +0000
1827@@ -1,5 +1,5 @@
1828 /*
1829- * Copyright 2013-2014 Canonical Ltd.
1830+ * Copyright 2013-2015 Canonical Ltd.
1831 *
1832 * This file is part of webbrowser-app.
1833 *
1834@@ -36,6 +36,7 @@
1835 */
1836 TabsModel::TabsModel(QObject* parent)
1837 : QAbstractListModel(parent)
1838+ , m_currentIndex(-1)
1839 {
1840 }
1841
1842@@ -81,12 +82,29 @@
1843 }
1844 }
1845
1846+int TabsModel::currentIndex() const
1847+{
1848+ return m_currentIndex;
1849+}
1850+
1851+void TabsModel::setCurrentIndex(int index)
1852+{
1853+ if (!checkValidTabIndex(index)) {
1854+ return;
1855+ }
1856+ if (index != m_currentIndex) {
1857+ m_currentIndex = index;
1858+ Q_EMIT currentIndexChanged();
1859+ Q_EMIT currentTabChanged();
1860+ }
1861+}
1862+
1863 QObject* TabsModel::currentTab() const
1864 {
1865 if (m_tabs.isEmpty()) {
1866- return 0;
1867+ return nullptr;
1868 }
1869- return m_tabs.first();
1870+ return m_tabs.at(m_currentIndex);
1871 }
1872
1873 /*!
1874@@ -97,7 +115,7 @@
1875 */
1876 int TabsModel::add(QObject* tab)
1877 {
1878- if (tab == 0) {
1879+ if (tab == nullptr) {
1880 qWarning() << "Invalid Tab";
1881 return -1;
1882 }
1883@@ -110,7 +128,7 @@
1884 endInsertRows();
1885 Q_EMIT countChanged();
1886 if (index == 0) {
1887- Q_EMIT currentTabChanged();
1888+ setCurrentIndex(0);
1889 }
1890 return index;
1891 }
1892@@ -125,41 +143,53 @@
1893 QObject* TabsModel::remove(int index)
1894 {
1895 if (!checkValidTabIndex(index)) {
1896- return 0;
1897+ return nullptr;
1898 }
1899 beginRemoveRows(QModelIndex(), index, index);
1900 QObject* tab = m_tabs.takeAt(index);
1901 tab->disconnect(this);
1902 endRemoveRows();
1903 Q_EMIT countChanged();
1904- if (index == 0) {
1905- Q_EMIT currentTabChanged();
1906+ if (index == m_currentIndex) {
1907+ if (!checkValidTabIndex(index)) {
1908+ m_currentIndex = m_tabs.count() - 1;
1909+ Q_EMIT currentIndexChanged();
1910+ Q_EMIT currentTabChanged();
1911+ } else {
1912+ Q_EMIT currentTabChanged();
1913+ }
1914 }
1915 return tab;
1916 }
1917
1918-void TabsModel::setCurrent(int index)
1919-{
1920- if (index == 0) {
1921- return;
1922- }
1923- if (!checkValidTabIndex(index)) {
1924- return;
1925- }
1926- beginMoveRows(QModelIndex(), index, index, QModelIndex(), 0);
1927- m_tabs.prepend(m_tabs.takeAt(index));
1928- endMoveRows();
1929- Q_EMIT currentTabChanged();
1930-}
1931-
1932 QObject* TabsModel::get(int index) const
1933 {
1934 if (!checkValidTabIndex(index)) {
1935- return 0;
1936+ return nullptr;
1937 }
1938 return m_tabs.at(index);
1939 }
1940
1941+void TabsModel::move(int from, int to)
1942+{
1943+ if ((from == to) || !checkValidTabIndex(from) || !checkValidTabIndex(to)) {
1944+ return;
1945+ }
1946+ beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
1947+ m_tabs.move(from, to);
1948+ endMoveRows();
1949+ if (m_currentIndex == from) {
1950+ m_currentIndex = to;
1951+ Q_EMIT currentIndexChanged();
1952+ } else if ((m_currentIndex >= to) && (m_currentIndex < from)) {
1953+ m_currentIndex++;
1954+ Q_EMIT currentIndexChanged();
1955+ } else if ((m_currentIndex > from) && (m_currentIndex <= to)) {
1956+ m_currentIndex--;
1957+ Q_EMIT currentIndexChanged();
1958+ }
1959+}
1960+
1961 bool TabsModel::checkValidTabIndex(int index) const
1962 {
1963 if ((index < 0) || (index >= m_tabs.count())) {
1964
1965=== modified file 'src/app/webbrowser/tabs-model.h'
1966--- src/app/webbrowser/tabs-model.h 2014-08-20 14:03:35 +0000
1967+++ src/app/webbrowser/tabs-model.h 2015-07-09 14:01:32 +0000
1968@@ -1,5 +1,5 @@
1969 /*
1970- * Copyright 2013-2014 Canonical Ltd.
1971+ * Copyright 2013-2015 Canonical Ltd.
1972 *
1973 * This file is part of webbrowser-app.
1974 *
1975@@ -31,6 +31,7 @@
1976
1977 Q_ENUMS(Roles)
1978
1979+ Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
1980 Q_PROPERTY(QObject* currentTab READ currentTab NOTIFY currentTabChanged)
1981 Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
1982
1983@@ -50,14 +51,18 @@
1984 int rowCount(const QModelIndex& parent=QModelIndex()) const;
1985 QVariant data(const QModelIndex& index, int role) const;
1986
1987+ int currentIndex() const;
1988+ void setCurrentIndex(int index);
1989+
1990 QObject* currentTab() const;
1991
1992 Q_INVOKABLE int add(QObject* tab);
1993 Q_INVOKABLE QObject* remove(int index);
1994- Q_INVOKABLE void setCurrent(int index);
1995 Q_INVOKABLE QObject* get(int index) const;
1996+ Q_INVOKABLE void move(int from, int to);
1997
1998 Q_SIGNALS:
1999+ void currentIndexChanged() const;
2000 void currentTabChanged() const;
2001 void countChanged() const;
2002
2003@@ -68,6 +73,7 @@
2004
2005 private:
2006 QList<QObject*> m_tabs;
2007+ int m_currentIndex;
2008
2009 bool checkValidTabIndex(int index) const;
2010 void onDataChanged(QObject* tab, int role);
2011
2012=== modified file 'tests/autopilot/webbrowser_app/emulators/browser.py'
2013--- tests/autopilot/webbrowser_app/emulators/browser.py 2015-07-02 13:28:50 +0000
2014+++ tests/autopilot/webbrowser_app/emulators/browser.py 2015-07-09 14:01:32 +0000
2015@@ -227,6 +227,9 @@
2016 return drawer.select_single("AbstractButton", objectName=actionName,
2017 visible=True)
2018
2019+ def get_tabs_bar(self):
2020+ return self.select_single(TabsBar)
2021+
2022
2023 class AddressBar(uitk.UbuntuUIToolkitCustomProxyObjectBase):
2024
2025@@ -264,6 +267,32 @@
2026 return self.select_single("QQuickItem", objectName="bookmarkToggle")
2027
2028
2029+class TabsBar(uitk.UbuntuUIToolkitCustomProxyObjectBase):
2030+
2031+ @autopilot.logging.log_action(logger.info)
2032+ def click_new_tab_button(self):
2033+ button = self.select_single("QQuickMouseArea",
2034+ objectName="newTabButton")
2035+ self.pointing_device.click_object(button)
2036+
2037+ def get_tabs(self):
2038+ return self.select_many("QQuickItem", objectName="tabDelegate")
2039+
2040+ def get_tab(self, index):
2041+ return self.select_single("QQuickItem", objectName="tabDelegate",
2042+ tabIndex=index)
2043+
2044+ @autopilot.logging.log_action(logger.info)
2045+ def select_tab(self, index):
2046+ self.pointing_device.click_object(self.get_tab(index))
2047+
2048+ @autopilot.logging.log_action(logger.info)
2049+ def close_tab(self, index):
2050+ tab = self.get_tab(index)
2051+ close_button = tab.select_single("Icon11", objectName="closeButton")
2052+ self.pointing_device.click_object(close_button)
2053+
2054+
2055 class Suggestions(uitk.UbuntuUIToolkitCustomProxyObjectBase):
2056
2057 def get_ordered_entries(self):
2058
2059=== modified file 'tests/autopilot/webbrowser_app/tests/__init__.py'
2060--- tests/autopilot/webbrowser_app/tests/__init__.py 2015-06-11 18:40:30 +0000
2061+++ tests/autopilot/webbrowser_app/tests/__init__.py 2015-07-09 14:01:32 +0000
2062@@ -19,6 +19,7 @@
2063 import os
2064 import shutil
2065 import tempfile
2066+import time
2067 import urllib.request
2068
2069 import fixtures
2070@@ -125,6 +126,7 @@
2071 self.pointing_device.drag(x, y0, x, y1)
2072
2073 def open_tabs_view(self):
2074+ self.assertFalse(self.main_window.wide)
2075 if model() == 'Desktop':
2076 chrome = self.main_window.chrome
2077 drawer_button = chrome.get_drawer_button()
2078@@ -134,7 +136,12 @@
2079 self.pointing_device.click_object(tabs_action)
2080 else:
2081 self.drag_bottom_edge_upwards(0.75)
2082- return self.main_window.get_tabs_view()
2083+ tabs_view = self.main_window.get_tabs_view()
2084+ # Give some time for the view to settle so that all previews reached
2085+ # their initial position (the animation has a duration of
2086+ # UbuntuAnimation.BriskDuration, i.e. 333ms, so 1s should be plenty).
2087+ time.sleep(1)
2088+ return tabs_view
2089
2090 def open_new_tab(self):
2091 if (self.main_window.incognito):
2092@@ -142,22 +149,32 @@
2093 else:
2094 count = len(self.main_window.get_webviews())
2095
2096- # assumes the tabs view is already open
2097- tabs_view = self.main_window.get_tabs_view()
2098- self.main_window.get_recent_view_toolbar().click_action("newTabButton")
2099- tabs_view.visible.wait_for(False)
2100- max_webviews = self.main_window.maxLiveWebviews
2101- new_count = (count + 1) if (count < max_webviews) else max_webviews
2102+ if self.main_window.wide:
2103+ self.main_window.chrome.get_tabs_bar().click_new_tab_button()
2104+ else:
2105+ # assumes the tabs view is already open
2106+ tabs_view = self.main_window.get_tabs_view()
2107+ toolbar = self.main_window.get_recent_view_toolbar()
2108+ toolbar.click_action("newTabButton")
2109+ tabs_view.visible.wait_for(False)
2110+
2111+ if self.main_window.wide or (model() == 'Desktop'):
2112+ new_count = count + 1
2113+ else:
2114+ max_webviews = self.main_window.maxLiveWebviews
2115+ new_count = (count + 1) if (count < max_webviews) else max_webviews
2116 if (self.main_window.incognito):
2117 self.assert_number_incognito_webviews_eventually(new_count)
2118 new_tab_view = self.main_window.get_new_private_tab_view()
2119 else:
2120 self.assert_number_webviews_eventually(new_count)
2121 new_tab_view = self.main_window.get_new_tab_view()
2122+
2123 if model() == 'Desktop':
2124 self.assertThat(
2125 self.main_window.address_bar.activeFocus,
2126 Eventually(Equals(True)))
2127+
2128 return new_tab_view
2129
2130 def open_settings(self):
2131
2132=== modified file 'tests/autopilot/webbrowser_app/tests/test_addressbar_bookmark.py'
2133--- tests/autopilot/webbrowser_app/tests/test_addressbar_bookmark.py 2015-07-02 13:28:50 +0000
2134+++ tests/autopilot/webbrowser_app/tests/test_addressbar_bookmark.py 2015-07-09 14:01:32 +0000
2135@@ -32,41 +32,51 @@
2136 bookmark_options.wait_until_destroyed()
2137 self.assertThat(chrome.bookmarked, Eventually(Equals(True)))
2138
2139- self.open_tabs_view()
2140+ if not self.main_window.wide:
2141+ self.open_tabs_view()
2142 self.open_new_tab()
2143 url = self.base_url + "/test2"
2144 self.main_window.go_to_url(url)
2145 self.main_window.wait_until_page_loaded(url)
2146 self.assertThat(chrome.bookmarked, Eventually(Equals(False)))
2147
2148- self.open_tabs_view()
2149- tabs_view = self.main_window.get_tabs_view()
2150- self.main_window.get_tabs_view().get_previews()[1].select()
2151- tabs_view.visible.wait_for(False)
2152+ if self.main_window.wide:
2153+ self.main_window.chrome.get_tabs_bar().select_tab(0)
2154+ else:
2155+ tabs_view = self.open_tabs_view()
2156+ tabs_view.get_previews()[1].select()
2157+ tabs_view.visible.wait_for(False)
2158 self.assertThat(chrome.bookmarked, Eventually(Equals(True)))
2159
2160- self.open_tabs_view()
2161- tabs_view = self.main_window.get_tabs_view()
2162- self.main_window.get_tabs_view().get_previews()[1].select()
2163- tabs_view.visible.wait_for(False)
2164+ if self.main_window.wide:
2165+ self.main_window.chrome.get_tabs_bar().select_tab(1)
2166+ else:
2167+ tabs_view = self.open_tabs_view()
2168+ tabs_view.get_previews()[1].select()
2169+ tabs_view.visible.wait_for(False)
2170 self.assertThat(chrome.bookmarked, Eventually(Equals(False)))
2171
2172 def test_cannot_bookmark_empty_page(self):
2173- self.open_tabs_view()
2174+ if not self.main_window.wide:
2175+ self.open_tabs_view()
2176 self.open_new_tab()
2177
2178- self.open_tabs_view()
2179- tabs_view = self.main_window.get_tabs_view()
2180- self.main_window.get_tabs_view().get_previews()[1].select()
2181- tabs_view.visible.wait_for(False)
2182+ if self.main_window.wide:
2183+ self.main_window.chrome.get_tabs_bar().select_tab(0)
2184+ else:
2185+ tabs_view = self.open_tabs_view()
2186+ tabs_view.get_previews()[1].select()
2187+ tabs_view.visible.wait_for(False)
2188 webview = self.main_window.get_current_webview()
2189 self.pointing_device.click_object(webview)
2190 address_bar = self.main_window.address_bar
2191 bookmark_toggle = address_bar.get_bookmark_toggle()
2192 self.assertThat(bookmark_toggle.visible, Eventually(Equals(True)))
2193
2194- self.open_tabs_view()
2195- tabs_view = self.main_window.get_tabs_view()
2196- self.main_window.get_tabs_view().get_previews()[1].select()
2197- tabs_view.visible.wait_for(False)
2198+ if self.main_window.wide:
2199+ self.main_window.chrome.get_tabs_bar().select_tab(1)
2200+ else:
2201+ tabs_view = self.open_tabs_view()
2202+ tabs_view.get_previews()[1].select()
2203+ tabs_view.visible.wait_for(False)
2204 self.assertThat(bookmark_toggle.visible, Eventually(Equals(False)))
2205
2206=== modified file 'tests/autopilot/webbrowser_app/tests/test_bookmark_options.py'
2207--- tests/autopilot/webbrowser_app/tests/test_bookmark_options.py 2015-07-02 13:37:16 +0000
2208+++ tests/autopilot/webbrowser_app/tests/test_bookmark_options.py 2015-07-09 14:01:32 +0000
2209@@ -87,7 +87,8 @@
2210 connection.close()
2211
2212 def _get_bookmarks_folders_list_view(self):
2213- self.open_tabs_view()
2214+ if not self.main_window.wide:
2215+ self.open_tabs_view()
2216 new_tab_view = self.open_new_tab()
2217 more_button = new_tab_view.get_bookmarks_more_button()
2218 self.assertThat(more_button.visible, Equals(True))
2219
2220=== modified file 'tests/autopilot/webbrowser_app/tests/test_keyboard.py'
2221--- tests/autopilot/webbrowser_app/tests/test_keyboard.py 2015-07-01 03:48:54 +0000
2222+++ tests/autopilot/webbrowser_app/tests/test_keyboard.py 2015-07-09 14:01:32 +0000
2223@@ -120,6 +120,8 @@
2224 self.check_tab_number(0)
2225
2226 def test_switch_tabs_from_tabs_view(self):
2227+ if self.main_window.wide:
2228+ self.skipTest("Only on narrow form factors")
2229 self.open_tabs(1)
2230 self.check_tab_number(1)
2231 tabs = self.open_tabs_view()
2232@@ -139,8 +141,12 @@
2233 self.main_window.press_key('Ctrl+F4')
2234 self.check_tab_number(0)
2235 self.main_window.press_key('Ctrl+F4')
2236- webview = self.main_window.get_current_webview()
2237- self.assertThat(webview.url, Equals(""))
2238+ if model() == 'Desktop':
2239+ # On desktop, closing the last open tab exits the application
2240+ self.app.process.wait()
2241+ else:
2242+ webview = self.main_window.get_current_webview()
2243+ self.assertThat(webview.url, Equals(""))
2244
2245 def test_close_tabs_ctrl_w(self):
2246 self.open_tabs(1)
2247@@ -148,18 +154,28 @@
2248 self.main_window.press_key('Ctrl+w')
2249 self.check_tab_number(0)
2250 self.main_window.press_key('Ctrl+w')
2251- webview = self.main_window.get_current_webview()
2252- self.assertThat(webview.url, Equals(""))
2253+ if model() == 'Desktop':
2254+ # On desktop, closing the last open tab exits the application
2255+ self.app.process.wait()
2256+ else:
2257+ webview = self.main_window.get_current_webview()
2258+ self.assertThat(webview.url, Equals(""))
2259
2260 def test_close_tabs_tabs_view(self):
2261+ if self.main_window.wide:
2262+ self.skipTest("Only on narrow form factors")
2263 self.open_tabs(1)
2264 self.check_tab_number(1)
2265 self.open_tabs_view()
2266 self.main_window.press_key('Ctrl+w')
2267 self.check_tab_number(0)
2268 self.main_window.press_key('Ctrl+F4')
2269- webview = self.main_window.get_current_webview()
2270- self.assertThat(webview.url, Equals(""))
2271+ if model() == 'Desktop':
2272+ # On desktop, closing the last open tab exits the application
2273+ self.app.process.wait()
2274+ else:
2275+ webview = self.main_window.get_current_webview()
2276+ self.assertThat(webview.url, Equals(""))
2277
2278 def test_select_address_bar_ctrl_l(self):
2279 self.main_window.press_key('Ctrl+L')
2280
2281=== modified file 'tests/autopilot/webbrowser_app/tests/test_new_tab_view.py'
2282--- tests/autopilot/webbrowser_app/tests/test_new_tab_view.py 2015-07-01 03:45:57 +0000
2283+++ tests/autopilot/webbrowser_app/tests/test_new_tab_view.py 2015-07-09 14:01:32 +0000
2284@@ -18,6 +18,9 @@
2285 import sqlite3
2286 import time
2287
2288+import testtools
2289+
2290+from autopilot.platform import model
2291 from autopilot.matchers import Eventually
2292 from testtools.matchers import Equals, NotEquals
2293
2294@@ -27,52 +30,76 @@
2295 class TestNewTabViewLifetime(StartOpenRemotePageTestCaseBase):
2296
2297 def test_new_tab_view_destroyed_when_browsing(self):
2298- self.open_tabs_view()
2299+ if not self.main_window.wide:
2300+ self.open_tabs_view()
2301 new_tab_view = self.open_new_tab()
2302 self.main_window.go_to_url(self.base_url + "/test2")
2303 new_tab_view.wait_until_destroyed()
2304
2305 def test_new_tab_view_destroyed_when_closing_tab(self):
2306- self.open_tabs_view()
2307+ if not self.main_window.wide:
2308+ self.open_tabs_view()
2309 new_tab_view = self.open_new_tab()
2310- tabs_view = self.open_tabs_view()
2311- tabs_view.get_previews()[0].close()
2312- self.main_window.get_recent_view_toolbar().click_button("doneButton")
2313+ if self.main_window.wide:
2314+ self.main_window.chrome.get_tabs_bar().close_tab(1)
2315+ else:
2316+ tabs_view = self.open_tabs_view()
2317+ tabs_view.get_previews()[0].close()
2318+ toolbar = self.main_window.get_recent_view_toolbar()
2319+ toolbar.click_button("doneButton")
2320 new_tab_view.wait_until_destroyed()
2321
2322 def test_new_tab_view_is_shared_between_tabs(self):
2323 # Open one new tab
2324- self.open_tabs_view()
2325+ if not self.main_window.wide:
2326+ self.open_tabs_view()
2327 new_tab_view = self.open_new_tab()
2328 # Open a second new tab
2329- self.open_tabs_view()
2330+ if not self.main_window.wide:
2331+ self.open_tabs_view()
2332 new_tab_view_2 = self.open_new_tab()
2333 # Verify that they share the same NewTabView instance
2334 self.assertThat(new_tab_view_2.id, Equals(new_tab_view.id))
2335 # Close the second new tab, and verify that the NewTabView instance
2336 # is still there
2337- tabs_view = self.open_tabs_view()
2338- tabs_view.get_previews()[0].close()
2339- self.main_window.get_recent_view_toolbar().click_button("doneButton")
2340- tabs_view.visible.wait_for(False)
2341+ if self.main_window.wide:
2342+ self.main_window.chrome.get_tabs_bar().close_tab(2)
2343+ else:
2344+ tabs_view = self.open_tabs_view()
2345+ tabs_view.get_previews()[0].close()
2346+ toolbar = self.main_window.get_recent_view_toolbar()
2347+ toolbar.click_button("doneButton")
2348+ tabs_view.visible.wait_for(False)
2349 self.assertThat(new_tab_view.visible, Equals(True))
2350 # Close the first new tab, and verify that the NewTabView instance
2351 # is destroyed
2352- tabs_view = self.open_tabs_view()
2353- tabs_view.get_previews()[0].close()
2354- self.main_window.get_recent_view_toolbar().click_button("doneButton")
2355+ if self.main_window.wide:
2356+ self.main_window.chrome.get_tabs_bar().close_tab(1)
2357+ else:
2358+ tabs_view = self.open_tabs_view()
2359+ tabs_view.get_previews()[0].close()
2360+ toolbar = self.main_window.get_recent_view_toolbar()
2361+ toolbar.click_button("doneButton")
2362 new_tab_view.wait_until_destroyed()
2363
2364+ @testtools.skipIf(model() == "Desktop",
2365+ "Closing the last open tab on desktop quits the app")
2366 def test_new_tab_view_not_destroyed_when_closing_last_open_tab(self):
2367- tabs_view = self.open_tabs_view()
2368- tabs_view.get_previews()[0].close()
2369- tabs_view.visible.wait_for(False)
2370+ if self.main_window.wide:
2371+ self.main_window.chrome.get_tabs_bar().close_tab(0)
2372+ else:
2373+ tabs_view = self.open_tabs_view()
2374+ tabs_view.get_previews()[0].close()
2375+ tabs_view.visible.wait_for(False)
2376 new_tab_view = self.main_window.get_new_tab_view()
2377 # Verify that the new tab view is not destroyed and then re-created
2378 # when closing the last open tab if it was a blank one
2379- tabs_view = self.open_tabs_view()
2380- tabs_view.get_previews()[0].close()
2381- tabs_view.visible.wait_for(False)
2382+ if self.main_window.wide:
2383+ self.main_window.chrome.get_tabs_bar().close_tab(0)
2384+ else:
2385+ tabs_view = self.open_tabs_view()
2386+ tabs_view.get_previews()[0].close()
2387+ tabs_view.visible.wait_for(False)
2388 self.assertThat(new_tab_view.visible, Equals(True))
2389
2390
2391@@ -96,26 +123,36 @@
2392 self.main_window.go_to_url(self.base_url + "/test2")
2393 new_private_tab_view.wait_until_destroyed()
2394 # Open one new private tab
2395- self.open_tabs_view()
2396+ if not self.main_window.wide:
2397+ self.open_tabs_view()
2398 new_private_tab_view = self.open_new_tab()
2399 # Open a second new private tab
2400- self.open_tabs_view()
2401+ if not self.main_window.wide:
2402+ self.open_tabs_view()
2403 new_private_tab_view_2 = self.open_new_tab()
2404 # Verify that they share the same NewPrivateTabView instance
2405 self.assertThat(new_private_tab_view_2.id,
2406 Equals(new_private_tab_view.id))
2407 # Close the second new private tab, and verify that the
2408 # NewPrivateTabView instance is still there
2409- tabs_view = self.open_tabs_view()
2410- tabs_view.get_previews()[0].close()
2411- self.main_window.get_recent_view_toolbar().click_button("doneButton")
2412- tabs_view.visible.wait_for(False)
2413+ if self.main_window.wide:
2414+ self.main_window.chrome.get_tabs_bar().close_tab(2)
2415+ else:
2416+ tabs_view = self.open_tabs_view()
2417+ tabs_view.get_previews()[0].close()
2418+ toolbar = self.main_window.get_recent_view_toolbar()
2419+ toolbar.click_button("doneButton")
2420+ tabs_view.visible.wait_for(False)
2421 self.assertThat(new_private_tab_view.visible, Equals(True))
2422 # Close the first new private tab, and verify that the
2423 # NewPrivateTabView instance is destroyed
2424- tabs_view = self.open_tabs_view()
2425- tabs_view.get_previews()[0].close()
2426- self.main_window.get_recent_view_toolbar().click_button("doneButton")
2427+ if self.main_window.wide:
2428+ self.main_window.chrome.get_tabs_bar().close_tab(1)
2429+ else:
2430+ tabs_view = self.open_tabs_view()
2431+ tabs_view.get_previews()[0].close()
2432+ toolbar = self.main_window.get_recent_view_toolbar()
2433+ toolbar.click_button("doneButton")
2434 new_private_tab_view.wait_until_destroyed()
2435
2436
2437@@ -126,6 +163,9 @@
2438 self.populate_config()
2439 self.populate_bookmarks()
2440 super(TestNewTabViewContents, self).setUp()
2441+ if not self.main_window.wide:
2442+ self.open_tabs_view()
2443+ self.new_tab_view = self.open_new_tab()
2444
2445 def populate_config(self):
2446 self.homepage = "http://test/test2"
2447@@ -187,30 +227,24 @@
2448 connection.close()
2449
2450 def test_default_home_bookmark(self):
2451- self.open_tabs_view()
2452- new_tab_view = self.open_new_tab()
2453- homepage_bookmark = new_tab_view.get_homepage_bookmark()
2454+ homepage_bookmark = self.new_tab_view.get_homepage_bookmark()
2455 self.assertThat(homepage_bookmark.url, Equals(self.homepage))
2456 self.pointing_device.click_object(homepage_bookmark)
2457- new_tab_view.wait_until_destroyed()
2458+ self.new_tab_view.wait_until_destroyed()
2459 self.main_window.wait_until_page_loaded(self.homepage)
2460
2461 def test_open_bookmark_when_collapsed(self):
2462- self.open_tabs_view()
2463- new_tab_view = self.open_new_tab()
2464- bookmarks = new_tab_view.get_bookmarks_list()
2465+ bookmarks = self.new_tab_view.get_bookmarks_list()
2466 self.assertThat(lambda: len(bookmarks.get_delegates()),
2467 Eventually(Equals(4)))
2468 bookmark = bookmarks.get_delegates()[1]
2469 url = bookmark.url
2470 self.pointing_device.click_object(bookmark)
2471- new_tab_view.wait_until_destroyed()
2472+ self.new_tab_view.wait_until_destroyed()
2473 self.main_window.wait_until_page_loaded(url)
2474
2475 def test_open_bookmark_when_expanded(self):
2476- self.open_tabs_view()
2477- new_tab_view = self.open_new_tab()
2478- more_button = new_tab_view.get_bookmarks_more_button()
2479+ more_button = self.new_tab_view.get_bookmarks_more_button()
2480 self.assertThat(more_button.visible, Equals(True))
2481 self.pointing_device.click_object(more_button)
2482 folders = self.main_window.get_bookmarks_folder_list_view()
2483@@ -221,20 +255,18 @@
2484 bookmark = folders.get_urls_from_folder(folder_delegate)[0]
2485 url = bookmark.url
2486 self.pointing_device.click_object(bookmark)
2487- new_tab_view.wait_until_destroyed()
2488+ self.new_tab_view.wait_until_destroyed()
2489 self.main_window.wait_until_page_loaded(url)
2490
2491 def test_bookmarks_section_expands_and_collapses(self):
2492- self.open_tabs_view()
2493- new_tab_view = self.open_new_tab()
2494- bookmarks = new_tab_view.get_bookmarks_list()
2495- top_sites = new_tab_view.get_top_sites_list()
2496+ bookmarks = self.new_tab_view.get_bookmarks_list()
2497+ top_sites = self.new_tab_view.get_top_sites_list()
2498 self.assertThat(top_sites.visible, Equals(True))
2499 # When the bookmarks list is collapsed, it shows a maximum of 4 entries
2500 self.assertThat(lambda: len(bookmarks.get_delegates()),
2501 Eventually(Equals(4)))
2502 # When expanded, it shows all entries
2503- more_button = new_tab_view.get_bookmarks_more_button()
2504+ more_button = self.new_tab_view.get_bookmarks_more_button()
2505 self.assertThat(more_button.visible, Equals(True))
2506 self.pointing_device.click_object(more_button)
2507 folders = self.main_window.get_bookmarks_folder_list_view()
2508@@ -251,7 +283,7 @@
2509 self.assertThat(top_sites.visible, Eventually(Equals(True)))
2510
2511 def _remove_first_bookmark(self):
2512- bookmarks = self.main_window.get_new_tab_view().get_bookmarks_list()
2513+ bookmarks = self.new_tab_view.get_bookmarks_list()
2514 delegate = bookmarks.get_delegates()[0]
2515 url = delegate.url
2516 delegate.trigger_leading_action("leadingAction.delete",
2517@@ -273,12 +305,10 @@
2518 Eventually(NotEquals(url)))
2519
2520 def test_remove_bookmarks_when_collapsed(self):
2521- self.open_tabs_view()
2522- new_tab_view = self.open_new_tab()
2523- bookmarks = new_tab_view.get_bookmarks_list()
2524+ bookmarks = self.new_tab_view.get_bookmarks_list()
2525 self.assertThat(lambda: len(bookmarks.get_delegates()),
2526 Eventually(Equals(4)))
2527- more_button = new_tab_view.get_bookmarks_more_button()
2528+ more_button = self.new_tab_view.get_bookmarks_more_button()
2529 for i in range(3):
2530 self._remove_first_bookmark()
2531 self.assertThat(more_button.visible, Eventually(Equals(i < 1)))
2532@@ -286,9 +316,7 @@
2533 Equals(4 if (i < 2) else 3))
2534
2535 def test_remove_bookmarks_when_expanded(self):
2536- self.open_tabs_view()
2537- new_tab_view = self.open_new_tab()
2538- more_button = new_tab_view.get_bookmarks_more_button()
2539+ more_button = self.new_tab_view.get_bookmarks_more_button()
2540 self.assertThat(more_button.visible, Equals(True))
2541 self.pointing_device.click_object(more_button)
2542 folders = self.main_window.get_bookmarks_folder_list_view()
2543@@ -296,17 +324,15 @@
2544 self.assertThat(lambda: len(folders.get_urls_from_folder(
2545 folder_delegate)),
2546 Eventually(Equals(4)))
2547- more_button = new_tab_view.get_bookmarks_more_button()
2548- top_sites = new_tab_view.get_top_sites_list()
2549+ more_button = self.new_tab_view.get_bookmarks_more_button()
2550+ top_sites = self.new_tab_view.get_top_sites_list()
2551 self._remove_first_bookmark_from_folder("Actinide")
2552 self._remove_first_bookmark_from_folder("NobleGas")
2553 self.assertThat(more_button.visible, Eventually(Equals(False)))
2554 self.assertThat(top_sites.visible, Eventually(Equals(True)))
2555
2556 def test_show_bookmarks_folders_when_expanded(self):
2557- self.open_tabs_view()
2558- new_tab_view = self.open_new_tab()
2559- more_button = new_tab_view.get_bookmarks_more_button()
2560+ more_button = self.new_tab_view.get_bookmarks_more_button()
2561 self.assertThat(more_button.visible, Equals(True))
2562 self.pointing_device.click_object(more_button)
2563 folders = self.main_window.get_bookmarks_folder_list_view()
2564@@ -326,9 +352,7 @@
2565 Eventually(Equals(1)))
2566
2567 def test_hide_empty_bookmarks_folders_when_expanded(self):
2568- self.open_tabs_view()
2569- new_tab_view = self.open_new_tab()
2570- more_button = new_tab_view.get_bookmarks_more_button()
2571+ more_button = self.new_tab_view.get_bookmarks_more_button()
2572 self.assertThat(more_button.visible, Equals(True))
2573 self.pointing_device.click_object(more_button)
2574 folders = self.main_window.get_bookmarks_folder_list_view()
2575@@ -351,9 +375,7 @@
2576 Eventually(Equals(1)))
2577
2578 def test_bookmarks_folder_expands_and_collapses(self):
2579- self.open_tabs_view()
2580- new_tab_view = self.open_new_tab()
2581- more_button = new_tab_view.get_bookmarks_more_button()
2582+ more_button = self.new_tab_view.get_bookmarks_more_button()
2583 self.assertThat(more_button.visible, Equals(True))
2584 self.pointing_device.click_object(more_button)
2585 folders = self.main_window.get_bookmarks_folder_list_view()
2586@@ -375,24 +397,20 @@
2587 Eventually(Equals(4)))
2588
2589 def test_open_top_site(self):
2590- self.open_tabs_view()
2591- new_tab_view = self.open_new_tab()
2592- top_sites = new_tab_view.get_top_sites_list()
2593+ top_sites = self.new_tab_view.get_top_sites_list()
2594 self.assertThat(lambda: len(top_sites.get_delegates()),
2595 Eventually(Equals(1)))
2596 top_site = top_sites.get_delegates()[0]
2597 url = top_site.url
2598 self.pointing_device.click_object(top_site)
2599- new_tab_view.wait_until_destroyed()
2600+ self.new_tab_view.wait_until_destroyed()
2601 self.main_window.wait_until_page_loaded(url)
2602
2603 def test_remove_top_sites(self):
2604- self.open_tabs_view()
2605- new_tab_view = self.open_new_tab()
2606- top_sites = new_tab_view.get_top_sites_list()
2607+ top_sites = self.new_tab_view.get_top_sites_list()
2608 self.assertThat(lambda: len(top_sites.get_delegates()),
2609 Eventually(Equals(1)))
2610- notopsites_label = new_tab_view.get_notopsites_label()
2611+ notopsites_label = self.new_tab_view.get_notopsites_label()
2612 self.assertThat(notopsites_label.visible, Eventually(Equals(False)))
2613 delegate = top_sites.get_delegates()[0]
2614 delegate.trigger_leading_action("leadingAction.delete",
2615
2616=== modified file 'tests/autopilot/webbrowser_app/tests/test_private.py'
2617--- tests/autopilot/webbrowser_app/tests/test_private.py 2015-05-28 13:15:47 +0000
2618+++ tests/autopilot/webbrowser_app/tests/test_private.py 2015-07-09 14:01:32 +0000
2619@@ -23,7 +23,8 @@
2620 class TestPrivateView(StartOpenRemotePageTestCaseBase):
2621
2622 def get_url_list_from_top_sites(self):
2623- self.open_tabs_view()
2624+ if not self.main_window.wide:
2625+ self.open_tabs_view()
2626 new_tab_view = self.open_new_tab()
2627 return new_tab_view.get_top_sites_list().get_urls()
2628
2629@@ -50,7 +51,8 @@
2630 self.assertThat(self.main_window.is_in_private_mode,
2631 Eventually(Equals(True)))
2632 self.assertTrue(self.main_window.is_new_private_tab_view_visible())
2633- self.open_tabs_view()
2634+ if not self.main_window.wide:
2635+ self.open_tabs_view()
2636 self.open_new_tab()
2637 self.main_window.leave_private_mode_with_confirmation()
2638 self.assertThat(self.main_window.is_in_private_mode,
2639@@ -61,7 +63,8 @@
2640 self.assertThat(self.main_window.is_in_private_mode,
2641 Eventually(Equals(True)))
2642 self.assertTrue(self.main_window.is_new_private_tab_view_visible())
2643- self.open_tabs_view()
2644+ if not self.main_window.wide:
2645+ self.open_tabs_view()
2646 self.open_new_tab()
2647 self.main_window.leave_private_mode_with_confirmation(confirm=False)
2648 self.assertThat(self.main_window.is_in_private_mode,
2649@@ -85,24 +88,32 @@
2650 self.assertNotIn(url, top_sites)
2651
2652 def test_public_tabs_should_not_be_visible_in_private_mode(self):
2653- self.open_tabs_view()
2654+ if not self.main_window.wide:
2655+ self.open_tabs_view()
2656 self.open_new_tab()
2657 new_tab_view = self.main_window.get_new_tab_view()
2658 url = self.base_url + "/test2"
2659 self.main_window.go_to_url(url)
2660 new_tab_view.wait_until_destroyed()
2661- self.open_tabs_view()
2662- tabs_view = self.main_window.get_tabs_view()
2663- previews = tabs_view.get_previews()
2664- self.assertThat(len(previews), Equals(2))
2665- self.main_window.get_recent_view_toolbar().click_button("doneButton")
2666- tabs_view.visible.wait_for(False)
2667+ if self.main_window.wide:
2668+ tabs = self.main_window.chrome.get_tabs_bar().get_tabs()
2669+ self.assertThat(len(tabs), Equals(2))
2670+ else:
2671+ tabs_view = self.open_tabs_view()
2672+ previews = tabs_view.get_previews()
2673+ self.assertThat(len(previews), Equals(2))
2674+ toolbar = self.main_window.get_recent_view_toolbar()
2675+ toolbar.click_button("doneButton")
2676+ tabs_view.visible.wait_for(False)
2677
2678 self.main_window.enter_private_mode()
2679 self.assertThat(self.main_window.is_in_private_mode,
2680 Eventually(Equals(True)))
2681 self.assertTrue(self.main_window.is_new_private_tab_view_visible())
2682- self.open_tabs_view()
2683- tabs_view = self.main_window.get_tabs_view()
2684- previews = tabs_view.get_previews()
2685- self.assertThat(len(previews), Equals(1))
2686+ if self.main_window.wide:
2687+ tabs = self.main_window.chrome.get_tabs_bar().get_tabs()
2688+ self.assertThat(len(tabs), Equals(1))
2689+ else:
2690+ tabs_view = self.open_tabs_view()
2691+ previews = tabs_view.get_previews()
2692+ self.assertThat(len(previews), Equals(1))
2693
2694=== modified file 'tests/autopilot/webbrowser_app/tests/test_suggestions.py'
2695--- tests/autopilot/webbrowser_app/tests/test_suggestions.py 2015-06-23 20:45:05 +0000
2696+++ tests/autopilot/webbrowser_app/tests/test_suggestions.py 2015-07-09 14:01:32 +0000
2697@@ -33,8 +33,6 @@
2698 """Helper test class that pre-populates history and bookmarks databases."""
2699
2700 def setUp(self):
2701- self.create_temporary_profile()
2702-
2703 self.populate_history()
2704 self.populate_bookmarks()
2705 super(PrepopulatedDatabaseTestCaseBase, self).setUp()
2706@@ -159,7 +157,7 @@
2707 return pattern.format(parts[0], self.highlight(term), parts[1])
2708
2709 def highlight(self, text):
2710- return '<b><font color="#dd4814">{}</font></b>'.format(text)
2711+ return '<font color="#752571">{}</font>'.format(text)
2712
2713 def assert_suggestions_eventually_shown(self):
2714 suggestions = self.main_window.get_suggestions()
2715
2716=== modified file 'tests/autopilot/webbrowser_app/tests/test_tabs.py'
2717--- tests/autopilot/webbrowser_app/tests/test_tabs.py 2015-05-25 19:17:25 +0000
2718+++ tests/autopilot/webbrowser_app/tests/test_tabs.py 2015-07-09 14:01:32 +0000
2719@@ -34,6 +34,8 @@
2720
2721 def setUp(self):
2722 super(TestTabsView, self).setUp()
2723+ if self.main_window.wide:
2724+ self.skipTest("Only on narrow form factors")
2725 self.open_tabs_view()
2726
2727 def test_tabs_model(self):
2728@@ -55,6 +57,10 @@
2729 def test_close_last_open_tab(self):
2730 tabs_view = self.main_window.get_tabs_view()
2731 tabs_view.get_previews()[0].close()
2732+ if model() == 'Desktop':
2733+ # On desktop, closing the last open tab exits the application
2734+ self.app.process.wait()
2735+ return
2736 tabs_view.visible.wait_for(False)
2737 self.assert_number_webviews_eventually(1)
2738 self.main_window.get_new_tab_view()
2739@@ -92,14 +98,12 @@
2740 new_tab_view.wait_until_destroyed()
2741 self.check_current_tab(url)
2742
2743- self.open_tabs_view()
2744- tabs_view = self.main_window.get_tabs_view()
2745+ tabs_view = self.open_tabs_view()
2746 tabs_view.get_previews()[1].select()
2747 tabs_view.visible.wait_for(False)
2748 self.check_current_tab(self.url)
2749
2750- self.open_tabs_view()
2751- tabs_view = self.main_window.get_tabs_view()
2752+ tabs_view = self.open_tabs_view()
2753 tabs_view.get_previews()[1].select()
2754 tabs_view.visible.wait_for(False)
2755 self.check_current_tab(url)
2756@@ -110,8 +114,7 @@
2757 error = self.main_window.get_error_sheet()
2758 self.assertThat(error.visible, Eventually(Equals(True)))
2759
2760- self.open_tabs_view()
2761- tabs_view = self.main_window.get_tabs_view()
2762+ tabs_view = self.open_tabs_view()
2763 tabs_view.get_previews()[1].select()
2764 tabs_view.visible.wait_for(False)
2765 self.assertThat(error.visible, Eventually(Equals(False)))
2766@@ -149,6 +152,8 @@
2767 self.assert_number_webviews_eventually(2)
2768
2769 def test_selecting_tab_focuses_webview(self):
2770+ if self.main_window.wide:
2771+ self.skipTest("Only on narrow form factors")
2772 tabs_view = self.open_tabs_view()
2773 tabs_view.get_previews()[0].select()
2774 tabs_view.visible.wait_for(False)
2775@@ -156,7 +161,8 @@
2776 webview.activeFocus.wait_for(True)
2777
2778 def test_webview_requests_close(self):
2779- self.open_tabs_view()
2780+ if not self.main_window.wide:
2781+ self.open_tabs_view()
2782 self.open_new_tab()
2783 url = self.base_url + "/closeself"
2784 self.main_window.go_to_url(url)
2785@@ -168,14 +174,26 @@
2786 self.assert_number_webviews_eventually(1)
2787
2788 def test_last_webview_requests_close(self):
2789- tabs_view = self.open_tabs_view()
2790- tabs_view.get_previews()[0].close()
2791- tabs_view.visible.wait_for(False)
2792+ if not self.main_window.wide:
2793+ self.open_tabs_view()
2794+ self.open_new_tab()
2795 url = self.base_url + "/closeself"
2796 self.main_window.go_to_url(url)
2797 self.main_window.wait_until_page_loaded(url)
2798+ if self.main_window.wide:
2799+ self.main_window.chrome.get_tabs_bar().close_tab(0)
2800+ else:
2801+ tabs_view = self.open_tabs_view()
2802+ tabs_view.get_previews()[1].close()
2803+ toolbar = self.main_window.get_recent_view_toolbar()
2804+ toolbar.click_button("doneButton")
2805+ tabs_view.visible.wait_for(False)
2806 webview = self.main_window.get_current_webview()
2807 self.pointing_device.click_object(webview)
2808+ if model() == 'Desktop':
2809+ # On desktop, closing the last open tab exits the application
2810+ self.app.process.wait()
2811+ return
2812 webview.wait_until_destroyed()
2813 self.assert_number_webviews_eventually(1)
2814 self.main_window.get_new_tab_view()
2815
2816=== modified file 'tests/unittests/qml/CMakeLists.txt'
2817--- tests/unittests/qml/CMakeLists.txt 2015-05-08 15:52:12 +0000
2818+++ tests/unittests/qml/CMakeLists.txt 2015-07-09 14:01:32 +0000
2819@@ -11,6 +11,7 @@
2820 ${webbrowser-common_SOURCE_DIR}/favicon-fetcher.cpp
2821 ${webbrowser-app_SOURCE_DIR}/file-operations.cpp
2822 ${webbrowser-app_SOURCE_DIR}/searchengine.cpp
2823+ ${webbrowser-app_SOURCE_DIR}/tabs-model.cpp
2824 tst_QmlTests.cpp
2825 )
2826 add_executable(${TEST} ${SOURCES})
2827
2828=== modified file 'tests/unittests/qml/tst_AddressBar.qml'
2829--- tests/unittests/qml/tst_AddressBar.qml 2015-07-07 10:14:03 +0000
2830+++ tests/unittests/qml/tst_AddressBar.qml 2015-07-09 14:01:32 +0000
2831@@ -40,7 +40,8 @@
2832
2833 searchUrl: "http://www.ubuntu.com/search?q={searchTerms}"
2834
2835- canSimplifyText: !activeFocus
2836+ editing: activeFocus
2837+ canSimplifyText: true
2838 }
2839
2840 // only exists to steal focus from the address bar
2841
2842=== modified file 'tests/unittests/qml/tst_QmlTests.cpp'
2843--- tests/unittests/qml/tst_QmlTests.cpp 2015-05-08 18:14:41 +0000
2844+++ tests/unittests/qml/tst_QmlTests.cpp 2015-07-09 14:01:32 +0000
2845@@ -28,6 +28,7 @@
2846 #include "favicon-fetcher.h"
2847 #include "file-operations.h"
2848 #include "searchengine.h"
2849+#include "tabs-model.h"
2850
2851 static QObject* FileOperations_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine)
2852 {
2853@@ -114,11 +115,12 @@
2854
2855 const char* browserUri = "webbrowserapp.private";
2856 qmlRegisterType<SearchEngine>(browserUri, 0, 1, "SearchEngine");
2857+ qmlRegisterType<TabsModel>(browserUri, 0, 1, "TabsModel");
2858 qmlRegisterSingletonType<FileOperations>(browserUri, 0, 1, "FileOperations", FileOperations_singleton_factory);
2859
2860 qmlRegisterSingletonType<TestContext>("webbrowsertest.private", 0, 1, "TestContext", TestContext_singleton_factory);
2861
2862- return quick_test_main(argc, argv, "QmlTests", 0);
2863+ return quick_test_main(argc, argv, "QmlTests", nullptr);
2864 }
2865
2866 #include "tst_QmlTests.moc"
2867
2868=== added file 'tests/unittests/qml/tst_TabsBar.qml'
2869--- tests/unittests/qml/tst_TabsBar.qml 1970-01-01 00:00:00 +0000
2870+++ tests/unittests/qml/tst_TabsBar.qml 2015-07-09 14:01:32 +0000
2871@@ -0,0 +1,173 @@
2872+/*
2873+ * Copyright 2015 Canonical Ltd.
2874+ *
2875+ * This file is part of webbrowser-app.
2876+ *
2877+ * webbrowser-app is free software; you can redistribute it and/or modify
2878+ * it under the terms of the GNU General Public License as published by
2879+ * the Free Software Foundation; version 3.
2880+ *
2881+ * webbrowser-app is distributed in the hope that it will be useful,
2882+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2883+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2884+ * GNU General Public License for more details.
2885+ *
2886+ * You should have received a copy of the GNU General Public License
2887+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2888+ */
2889+
2890+import QtQuick 2.0
2891+import QtTest 1.0
2892+import Ubuntu.Test 1.0
2893+import "../../../src/app/webbrowser"
2894+import webbrowserapp.private 0.1
2895+
2896+Item {
2897+ id: root
2898+
2899+ width: 600
2900+ height: 50
2901+
2902+ TabsModel {
2903+ id: tabsModel
2904+ }
2905+
2906+ Component {
2907+ id: tabComponent
2908+ QtObject {
2909+ property url url
2910+ property string title
2911+ property url icon
2912+ function close() { destroy() }
2913+ }
2914+ }
2915+
2916+ TabsBar {
2917+ id: tabs
2918+ anchors.fill: parent
2919+ model: tabsModel
2920+ onRequestNewTab: appendTab("", "", "")
2921+ function appendTab(url, title, icon) {
2922+ var tab = tabComponent.createObject(root, {"url": url, "title": title, "icon": icon})
2923+ model.add(tab)
2924+ model.currentIndex = model.count - 1
2925+ }
2926+ }
2927+
2928+ SignalSpy {
2929+ id: newTabRequestSpy
2930+ target: tabs
2931+ signalName: "requestNewTab"
2932+ }
2933+
2934+ UbuntuTestCase {
2935+ name: "TabsBar"
2936+ when: windowShown
2937+
2938+ function clickItem(item) {
2939+ var center = centerOf(item)
2940+ mouseClick(item, center.x, center.y)
2941+ }
2942+
2943+ function getTabDelegate(index) {
2944+ var container = findChild(tabs, "tabsContainer")
2945+ for (var i = 0; i < container.children.length; ++i) {
2946+ var child = container.children[i]
2947+ if ((child.objectName == "tabDelegate") && (child.tabIndex == index)) {
2948+ return child
2949+ }
2950+ }
2951+ return null
2952+ }
2953+
2954+ function cleanup() {
2955+ while (tabsModel.count > 0) {
2956+ tabsModel.remove(0).destroy()
2957+ }
2958+ newTabRequestSpy.clear()
2959+ }
2960+
2961+ function populateTabs() {
2962+ for (var i = 0; i < 3; ++i) {
2963+ tabs.appendTab("", "tab " + i, "")
2964+ }
2965+ compare(tabsModel.currentIndex, 2)
2966+ }
2967+
2968+ function test_create_new_tab() {
2969+ var newTabButton = findChild(tabs, "newTabButton")
2970+ for (var i = 0; i < 3; ++i) {
2971+ tryCompare(tabsModel, "count", i)
2972+ clickItem(newTabButton)
2973+ }
2974+ compare(newTabRequestSpy.count, 3)
2975+ }
2976+
2977+ function test_mouse_left_click() {
2978+ // Left click makes the tab current
2979+ populateTabs()
2980+ for (var i = 2; i >= 0; --i) {
2981+ clickItem(getTabDelegate(i))
2982+ compare(tabsModel.currentIndex, i)
2983+ }
2984+ }
2985+
2986+ function test_mouse_middle_click() {
2987+ // Middle click closes the tab
2988+ populateTabs()
2989+ for (var i = 2; i >= 0; --i) {
2990+ var tab0 = getTabDelegate(0)
2991+ mouseClick(tab0, centerOf(tab0).x, centerOf(tab0).y, Qt.MiddleButton)
2992+ compare(tabsModel.count, i)
2993+ }
2994+ }
2995+
2996+ function test_mouse_wheel() {
2997+ // Wheel events cycle through open tabs
2998+ populateTabs()
2999+ var tab0 = getTabDelegate(0)
3000+ var c = centerOf(tab0)
3001+ function wheelUp() { mouseWheel(tab0, c.x, c.y, 0, 120) }
3002+ function wheelDown() { mouseWheel(tab0, c.x, c.y, 0, -120) }
3003+ wheelDown()
3004+ compare(tabsModel.currentIndex, 2)
3005+ wheelUp()
3006+ compare(tabsModel.currentIndex, 1)
3007+ wheelUp()
3008+ compare(tabsModel.currentIndex, 0)
3009+ wheelUp()
3010+ compare(tabsModel.currentIndex, 0)
3011+ wheelDown()
3012+ compare(tabsModel.currentIndex, 1)
3013+ }
3014+
3015+ function test_drag_tab() {
3016+ populateTabs()
3017+
3018+ function dragTab(tab, dx, index) {
3019+ var c = centerOf(tab)
3020+ mouseDrag(tab, c.x, c.y, dx, 0)
3021+ compare(getTabDelegate(index), tab)
3022+ compare(tabsModel.currentIndex, index)
3023+ wait(500)
3024+ }
3025+
3026+ // Move the first tab to the right
3027+ var tab = getTabDelegate(0)
3028+ dragTab(tab, tab.width * 0.8, 1)
3029+
3030+ // Start a move to the right and release too early
3031+ dragTab(tab, tab.width * 0.3, 1)
3032+
3033+ // Start a move to the left and release too early
3034+ dragTab(tab, -tab.width * 0.4, 1)
3035+
3036+ // Move the tab all the way to the right and overshoot
3037+ dragTab(tab, tab.width * 3, 2)
3038+
3039+ // Move another tab all the way to the left and overshoot
3040+ tab = getTabDelegate(1)
3041+ dragTab(tab, -tab.width * 2, 0)
3042+ }
3043+ }
3044+}
3045
3046=== modified file 'tests/unittests/tabs-model/tst_TabsModelTests.cpp'
3047--- tests/unittests/tabs-model/tst_TabsModelTests.cpp 2014-08-20 14:03:35 +0000
3048+++ tests/unittests/tabs-model/tst_TabsModelTests.cpp 2015-07-09 14:01:32 +0000
3049@@ -1,5 +1,5 @@
3050 /*
3051- * Copyright 2013-2014 Canonical Ltd.
3052+ * Copyright 2013-2015 Canonical Ltd.
3053 *
3054 * This file is part of webbrowser-app.
3055 *
3056@@ -64,7 +64,7 @@
3057 void shouldBeInitiallyEmpty()
3058 {
3059 QCOMPARE(model->rowCount(), 0);
3060- QCOMPARE(model->currentTab(), (QObject*) 0);
3061+ QCOMPARE(model->currentTab(), (QObject*) nullptr);
3062 }
3063
3064 void shouldExposeRoleNames()
3065@@ -76,14 +76,20 @@
3066 QVERIFY(roleNames.contains("tab"));
3067 }
3068
3069+ void shouldNotAllowSettingTheIndexToAnInvalidValue_data()
3070+ {
3071+ QTest::addColumn<int>("index");
3072+ QTest::newRow("zero") << 0;
3073+ QTest::newRow("too high") << 2;
3074+ QTest::newRow("too low") << -2;
3075+ }
3076+
3077 void shouldNotAllowSettingTheIndexToAnInvalidValue()
3078 {
3079- model->setCurrent(0);
3080- QCOMPARE(model->currentTab(), (QObject*) 0);
3081- model->setCurrent(2);
3082- QCOMPARE(model->currentTab(), (QObject*) 0);
3083- model->setCurrent(-2);
3084- QCOMPARE(model->currentTab(), (QObject*) 0);
3085+ QFETCH(int, index);
3086+ model->setCurrentIndex(index);
3087+ QCOMPARE(model->currentIndex(), -1);
3088+ QCOMPARE(model->currentTab(), (QObject*) nullptr);
3089 }
3090
3091 void shouldNotAddNullTab()
3092@@ -118,9 +124,9 @@
3093
3094 void shouldNotAllowRemovingAtInvalidIndex()
3095 {
3096- QCOMPARE(model->remove(0), (QObject*) 0);
3097- QCOMPARE(model->remove(2), (QObject*) 0);
3098- QCOMPARE(model->remove(-2), (QObject*) 0);
3099+ QCOMPARE(model->remove(0), (QObject*) nullptr);
3100+ QCOMPARE(model->remove(2), (QObject*) nullptr);
3101+ QCOMPARE(model->remove(-2), (QObject*) nullptr);
3102 }
3103
3104 void shouldReturnTabWhenRemoving()
3105@@ -221,17 +227,19 @@
3106 QVERIFY(roles.contains(TabsModel::Icon));
3107 }
3108
3109- void shouldUpdateCurrentTabWhenSettingCurrent()
3110+ void shouldUpdateCurrentTabWhenSettingCurrentIndex()
3111 {
3112 QQuickItem* tab1 = createTab();
3113 model->add(tab1);
3114 QSignalSpy spy(model, SIGNAL(currentTabChanged()));
3115- model->setCurrent(0);
3116+ model->setCurrentIndex(0);
3117+ QCOMPARE(model->currentIndex(), 0);
3118 QVERIFY(spy.isEmpty());
3119 QCOMPARE(model->currentTab(), tab1);
3120 QQuickItem* tab2 = createTab();
3121 model->add(tab2);
3122- model->setCurrent(1);
3123+ model->setCurrentIndex(1);
3124+ QCOMPARE(model->currentIndex(), 1);
3125 QCOMPARE(spy.count(), 1);
3126 QCOMPARE(model->currentTab(), tab2);
3127 }
3128@@ -270,7 +278,7 @@
3129 spy.clear();
3130 delete model->remove(0);
3131 QCOMPARE(spy.count(), 1);
3132- QCOMPARE(model->currentTab(), (QObject*) 0);
3133+ QCOMPARE(model->currentTab(), (QObject*) nullptr);
3134 }
3135
3136 void shouldReturnData()
3137@@ -289,6 +297,62 @@
3138 QCOMPARE(model->data(model->index(0, 0), TabsModel::Tab).value<QQuickItem*>(), tab);
3139 QVERIFY(!model->data(model->index(0, 0), TabsModel::Tab + 3).isValid());
3140 }
3141+
3142+ void shouldReturnTabAtIndex()
3143+ {
3144+ // valid indexes
3145+ QQuickItem* tab1 = createTab();
3146+ model->add(tab1);
3147+ QQuickItem* tab2 = createTab();
3148+ model->add(tab2);
3149+ QQuickItem* tab3 = createTab();
3150+ model->add(tab3);
3151+ QCOMPARE(model->get(0), tab1);
3152+ QCOMPARE(model->get(1), tab2);
3153+ QCOMPARE(model->get(2), tab3);
3154+
3155+ // invalid indexes
3156+ QCOMPARE(model->get(-1), (QObject*) nullptr);
3157+ QCOMPARE(model->get(3), (QObject*) nullptr);
3158+ }
3159+
3160+private:
3161+ void moveTabs(int from, int to, bool moved, bool indexChanged, int newIndex)
3162+ {
3163+ QSignalSpy spyMoved(model, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)));
3164+ QSignalSpy spyIndex(model, SIGNAL(currentIndexChanged()));
3165+ QSignalSpy spyTab(model, SIGNAL(currentTabChanged()));
3166+
3167+ model->move(from, to);
3168+ QCOMPARE(spyMoved.count(), moved ? 1 : 0);
3169+ if (moved) {
3170+ QList<QVariant> args = spyMoved.takeFirst();
3171+ QCOMPARE(args.at(1).toInt(), from);
3172+ QCOMPARE(args.at(2).toInt(), from);
3173+ QCOMPARE(args.at(4).toInt(), to);
3174+ }
3175+ QCOMPARE(spyIndex.count(), indexChanged ? 1 : 0);
3176+ QCOMPARE(model->currentIndex(), newIndex);
3177+ QVERIFY(spyTab.isEmpty());
3178+ }
3179+
3180+private Q_SLOTS:
3181+ void shouldNotifyWhenMovingTabs()
3182+ {
3183+ for (int i = 0; i < 3; ++i) {
3184+ model->add(createTab());
3185+ }
3186+
3187+ moveTabs(1, 1, false, false, 0);
3188+ moveTabs(4, 1, false, false, 0);
3189+ moveTabs(1, 4, false, false, 0);
3190+ moveTabs(0, 2, true, true, 2);
3191+ moveTabs(1, 0, true, false, 2);
3192+ moveTabs(2, 1, true, true, 1);
3193+ moveTabs(2, 0, true, true, 2);
3194+ moveTabs(2, 1, true, true, 1);
3195+ moveTabs(0, 2, true, true, 0);
3196+ }
3197 };
3198
3199 QTEST_MAIN(TabsModelTests)

Subscribers

People subscribed via source and target branches

to status/vote changes: