Merge lp:~ahayzen/webbrowser-app/migrate-to-tabs-component into lp:webbrowser-app/staging

Proposed by Andrew Hayzen
Status: Merged
Approved by: Olivier Tilloy
Approved revision: 1576
Merged at revision: 1610
Proposed branch: lp:~ahayzen/webbrowser-app/migrate-to-tabs-component
Merge into: lp:webbrowser-app/staging
Diff against target: 1522 lines (+201/-817)
16 files modified
debian/control (+1/-0)
src/app/webbrowser/Browser.qml (+65/-68)
src/app/webbrowser/BrowserTab.qml (+9/-1)
src/app/webbrowser/CMakeLists.txt (+0/-1)
src/app/webbrowser/Chrome.qml (+22/-21)
src/app/webbrowser/TabsBar.qml (+0/-345)
src/app/webbrowser/drag-helper.cpp (+0/-203)
src/app/webbrowser/drag-helper.h (+0/-91)
src/app/webbrowser/tabs-model.cpp (+19/-3)
src/app/webbrowser/webbrowser-app.cpp (+1/-4)
src/app/webbrowser/webbrowser-app.qml (+3/-28)
tests/autopilot/webbrowser_app/emulators/browser.py (+3/-4)
tests/unittests/qml/CMakeLists.txt (+0/-1)
tests/unittests/qml/tst_QmlTests.cpp (+1/-4)
tests/unittests/qml/tst_TabsBar.qml (+56/-38)
tests/unittests/tabs-model/tst_TabsModelTests.cpp (+21/-5)
To merge this branch: bzr merge lp:~ahayzen/webbrowser-app/migrate-to-tabs-component
Reviewer Review Type Date Requested Status
Olivier Tilloy Approve
Review via email: mp+312340@code.launchpad.net

Description of the change

# UI Extras

bzr branch lp:~phablet-team/ubuntu-ui-extras/tabs
cd tabs
cmake .
make

# Running webbrowser

cmake .
make
QML2_IMPORT_PATH=/path/to/ui-extras-build ./src/app/webbrowser/webbrowser-app

# Snapcraft

The snapcraft should build and pull in the ui-extras component

snapcraft
snap install --devmode webbrowser*.snap
snap run webbrowser-app

# Testing

QML2_IMPORT_PATH=/path/to/ui-extras-build ctest -V
QML2_IMPORT_PATH=/path/to/ui-extras-build autopilot3 run webbrowser-app

# My results

## ctest

100% tests passed, 0 tests failed out of 26

Total Test time (real) = 98.53 sec

## Autopilot

Ran 204 tests in 1498.067s
OK

To post a comment you must log in.
Revision history for this message
Olivier Tilloy (osomon) wrote :

qtdeclarative5-ubuntu-ui-extras0.2 needs to be added as a runtime dependency of webbrowser-app in debian/control.

When building the snap locally, the resulting snap file is 20MB, up from 2MB previously. And sure enough it ships lots of Qt libraries in usr/lib/$ARCH/. What’s the reason for building ui-extras from source instead of adding it the stage packages? Or even taking it one step further, shouldn’t we add qtdeclarative5-ubuntu-ui-extras0.2 to the stage packages in the ubuntu-app-platform snap?

review: Needs Fixing
Revision history for this message
Olivier Tilloy (osomon) wrote :

The functionality seems to work well. Visually the tabs look very short though, a few pixels taller would make them easier targets for a mouse cursor. That’s stressed by the vertical space between the window title bar and the tabs themselves, which looks too tall to my eye. And when hovering over a non active tab just next to the currently active one, the highlight itself is taller than the current tab, is that intended? (see http://people.canonical.com/~osomon/new-tabs.png)
I guess my question really is: has that been validated by visual design?

1564. By Andrew Hayzen

* Remove top margin from tabs bar - spec says height should be 3GU
* Change ui-extras to be a stage package

1565. By Andrew Hayzen

* Merge of staging

1566. By Andrew Hayzen

* Add extras to prime

1567. By Andrew Hayzen

* Add libexiv2 to prime

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

Fixed the issues with the snapcraft, at the moment ui-extras isn't within u-a-p (maybe we can request it if we think it is worth it?)

Removed the top margin, and set the height of the tabs bar to be 3GU - which is the same as the ubuntu-terminal-app and what the design spec says.

Please rereview :-)

1568. By Andrew Hayzen

* Add ui-extras to deb build

1569. By Andrew Hayzen

* Set the color of the background of the tabs bar to #D9D9D9 as per design

Revision history for this message
Olivier Tilloy (osomon) wrote :

ubuntu-ui-extras is being added to the platform snap (see https://code.launchpad.net/~osomon/ubuntu-app-platform/+git/ubuntu-app-platform/+merge/316684), so you can remove the extra logic from the packaging.

Can you please update the copyright year of all changed files to 2017 ?

When I right-click on a tab and choose "Move to New Window", a new window is indeed opened, but the tab remains (and the webview is blanked) in the original window. I’m seeing the following in the console:

file://…/src/app/webbrowser/TabsBar.qml:122: TypeError: Property 'removeMovingTab' of object TabsBar_QMLTYPE_198(0x2f79320) is not a function

(a new autopilot test for that context menu option would be useful)

When I right-click on a tab and choose "New Tab", the new tab is not getting focus. This doesn't appear to be a regression, but would you mind fixing this while you're at it?

Please see additional comments/questions inline.

review: Needs Fixing
1570. By Andrew Hayzen

* Fix right-click "Move to Wew Window" using removeMovingTab instead of removeTabWithoutDestroying
* Fix right-click "New Tab" not making the new tab the current
* Remove qml-module-qtqml-models2 depends
* Revert changes to .pot
* Reverted changes to snapcraft to add ui-extras as it is in ubuntu-app-platform now
* Removed extra blank lines
* Made selectedIndex a readonly property
* Move extras import to be grouped with other Ubuntu.Components imports
* Change faviconFactory to be a nested Component
* Change faviconFactory.incubateObject parent to be the tabsBar
* Added whitespace around equals sign
* Remove debug in tst_TabsBar for moveTab and remove whitespace
* Remove overriding onContentMenu in tst_TabsBar as the one in TabsBar.qml can be used
* Fix whitespace in for loop around equals sign and ++ operator

Revision history for this message
Andrew Hayzen (ahayzen) wrote :
Download full text (3.7 KiB)

1) ubuntu-ui-extras is being added to the platform snap (see https://code.launchpad.net/~osomon/ubuntu-app-platform/+git/ubuntu-app-platform/+merge/316684), so you can remove the extra logic from the packaging.

Removed those changes

2) Can you please update the copyright year of all changed files to 2017 ?

TODO

3) file://…/src/app/webbrowser/TabsBar.qml:122: TypeError: Property 'removeMovingTab' of object TabsBar_QMLTYPE_198(0x2f79320) is not a function

FIXED

4) When I right-click on a tab and choose "New Tab", the new tab is not getting focus. This doesn't appear to be a regression, but would you mind fixing this while you're at it?

FIXED

5) What is this module needed for? I’m not seeing any explicit import of QtQml.Models in the diff.

Removed, and adding to ui-extras https://code.launchpad.net/~ahayzen/ubuntu-ui-extras/add-missing-model-depends/+merge/317090

6) Please revert changes to the pot file.

REVERTED

7) ubuntu-ui-extras is being added to the platform snap (see https://code.launchpad.net/~osomon/ubuntu-app-platform/+git/ubuntu-app-platform/+merge/316684), so those changes should be reverted.

Removed those changes

8) Please remove the extra blank line.

Removed

9) Please make it a readonly property.

Made it readonly

10) Can you please remove the extra blank line?

Removed

11) Cosmetics: can this import be grouped together with the other Ubuntu.Components imports?

Moved

12) Does faviconFactory really need to be a property? Can't it simply be a nested Component?

Yup, changed to a nested Component

13) I wonder how this actually works. The incubator may instantiate the component asynchronously, so by the time the function returns its status might not be Component.Ready, and the icon source will be empty.
Also, why make the FaviconFetcher a child of tabsBar.parent, and not of tabsBar itself?

Change the parent to be the tabsBar.
This seems to work like a binding, although it should be tested on low powered machines to ensure it is actually working and not always working as the incubator happens to load in time.

14) Cosmetics: can you please add whitespaces around the equal sign?

Added

15) So this is like the changes in https://code.launchpad.net/~ahayzen/webbrowser-app/fix-1630211-drag-tabs-quickly-incorrect-position/+merge/307709 (sorry for not letting that one aside for so long), except that the indexes in the first beginMoveRows(…) are different. Why is that so?

I found that the old branch was actually wrong, and in the case where you move in a positive direction it would only allow you to move one position. Now with the changed logic it works correctly.

Also see the Qt documentation http://doc.qt.io/qt-5/qabstractitemmodel.html#beginMoveRows
It should be beginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast, const QModelIndex &destinationParent, int destinationChild)

I now have beginMoveRows(QmodelIndex(), i, i, QModelIndex(), i + 2), where i is the sourceFirst and sourceLast as it is the only index moving. And i + 2 is the destinationChild as the row is moving past the index after the current.

16) Is this really needed? There's already a 'tabsModel' property (line below).

This is ...

Read more...

1571. By Andrew Hayzen

* Really revert the pot this time :-)

1572. By Andrew Hayzen

* Fix Kate auto adding new line

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

Note to self, forgot todo this one (even wrote TODO :') ).

2) Can you please update the copyright year of all changed files to 2017 ?

TODO

1573. By Andrew Hayzen

* Bump copyright year to 2017

Revision history for this message
Olivier Tilloy (osomon) wrote :

Mostly good to go, see two comments inline.

1574. By Andrew Hayzen

* Add FaviconFetcher to the BrowserTab so iconSourceFromModelItem can return the value easily
* Remove unused imports from tests

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

Resolved further comments:

1) See documentation: https://doc.qt.io/qt-5/qml-qtqml-component.html#incubateObject-method.
There is no guarantee that the incubator will be ready by the time of the return statement. In fact if it works here it’s by accident. It seems to me the only clean solution here is to use faviconFactory.createObject(…).

One side question: is iconSourceFromModelItem() potentially called more than once for every tab? If so, the instantiated FaviconFetcher should be associated to the tab and re-used for subsequent calls. If we are guaranteed that it won’t be reused, then it should be deleted before the return statement.

So what I've done is add a FaviconFetcher to the BrowserTab, then the iconSourceFromModelItem() can simply call return modelData.tab.favicon.localUrl. Then it seems that QML actually creates a binding, as when the value of localUrl changes, it calls the method a second time.

You can prove this by going to a website that loads the icon slowly and add the following traceline:
console.debug(modelData.url, index, modelData.tab.favicon.localUrl);

I then get this output:
qml: http://www.bbc.co.uk/ 3
...
qml: http://www.bbc.co.uk/ 3 file:///home/andrew/.cache/webbrowser-app/favicons/d2a8da919816b78cc42b7a9f87c1d71b.ico

2) Those two imports don't appear to be needed.

Removed

1575. By Andrew Hayzen

* Add localIcon readonly property to BrowserTab that points to a FaviconFetcher.localUrl

Revision history for this message
Olivier Tilloy (osomon) wrote :

I’m getting the following two warnings in the logs at application startup:

QQmlContext: Cannot set context object on invalid context.
QQmlComponent: Cannot create a component in an invalid context

I verified that I’m not getting them with the staging branch, so this is new. I wonder what’s causing them, and if they are harmful at all?

Revision history for this message
Olivier Tilloy (osomon) wrote :

When closing a tab, I’m seeing the following warning:

TabsBar.qml:57: TypeError: Cannot read property 'localIcon' of undefined

review: Needs Fixing
Revision history for this message
Olivier Tilloy (osomon) wrote :

> QQmlContext: Cannot set context object on invalid context.
> QQmlComponent: Cannot create a component in an invalid context

Not seeing those any longer after closing all tabs and starting again the app.

1576. By Andrew Hayzen

* Check the tab is valid in iconSourceFromModelItem

Revision history for this message
Olivier Tilloy (osomon) wrote :

And seeing them again after re-opening a good number of tabs (11), closing the app and starting it again.

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

1) When closing a tab, I’m seeing the following warning:

TabsBar.qml:57: TypeError: Cannot read property 'localIcon' of undefined

FIXED

2) QQmlContext errors when there are enough tabs open to cause some to be hidden

I've fixed this in the ubuntu-ui-extras, what was happening was at application startup, the loader is set to start loading a component but while it is doing this in async the tab is destroyed (as it is out of view in the listview) and then when the component is complete there is no context for it to go to. So now when the tab is destroyed it unsets the loader source to prevent it from performing the load. Please test with the following branch: https://code.launchpad.net/~ahayzen/ubuntu-ui-extras/fix-context-errors-for-unloaded-tabs/+merge/317364

Revision history for this message
Olivier Tilloy (osomon) wrote :

Everything looks fine now. Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2017-01-05 10:31:30 +0000
3+++ debian/control 2017-02-15 13:53:24 +0000
4@@ -55,6 +55,7 @@
5 qml-module-ubuntu-web (= ${binary:Version}),
6 qtdeclarative5-ubuntu-content1,
7 qtdeclarative5-ubuntu-download-manager0.1,
8+ qtdeclarative5-ubuntu-ui-extras0.2,
9 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3),
10 qtdeclarative5-unity-action-plugin,
11 Replaces: webbrowser-app-assets,
12
13=== modified file 'src/app/webbrowser/Browser.qml'
14--- src/app/webbrowser/Browser.qml 2017-02-06 22:17:36 +0000
15+++ src/app/webbrowser/Browser.qml 2017-02-15 13:53:24 +0000
16@@ -1,5 +1,5 @@
17 /*
18- * Copyright 2013-2016 Canonical Ltd.
19+ * Copyright 2013-2017 Canonical Ltd.
20 *
21 * This file is part of webbrowser-app.
22 *
23@@ -44,9 +44,45 @@
24
25 property bool incognito: false
26
27- property var tabsModel: TabsModel {}
28+ property var tabsModel: TabsModel {
29+ // These methods are required by the TabsBar component
30+ readonly property int selectedIndex: currentIndex
31+
32+ function addTab() {
33+ internal.openUrlInNewTab("", true, true, count)
34+ }
35+
36+ function addExistingTab(tab) {
37+ add(tab);
38+
39+ browser.bindExistingTab(tab);
40+ }
41+
42+ function moveTab(from, to) {
43+ if (from == to
44+ || from < 0 || from >= count
45+ || to < 0 || to >= count) {
46+ return;
47+ }
48+
49+ move(from, to);
50+ }
51+
52+ function removeTab(index) {
53+ internal.closeTab(index, false);
54+ }
55+
56+ function removeTabWithoutDestroying(index) {
57+ internal.closeTab(index, true);
58+ }
59+
60+ function selectTab(index) {
61+ internal.switchToTab(index, true);
62+ }
63+ }
64
65 property BrowserWindow thisWindow
66+ property Component windowFactory
67
68 function serializeTabState(tab) {
69 var state = {}
70@@ -487,11 +523,14 @@
71 showFaviconInAddressBar: !browser.wide
72
73 thisWindow: browser.thisWindow
74+ windowFactory: browser.windowFactory
75
76 availableHeight: tabContainer.height - height - y
77
78 touchEnabled: internal.hasTouchScreen
79
80+ tabsBarDimmed: dropAreaTopCover.containsDrag || dropAreaBottomCover.containsDrag
81+
82 property bool hidden: false
83 y: hidden ? -height : webview ? webview.locationBarController.offset : 0
84 Behavior on y {
85@@ -521,7 +560,6 @@
86
87 onSwitchToTab: internal.switchToTab(index, true)
88 onRequestNewTab: internal.openUrlInNewTab("", makeCurrent, true, index)
89- onRequestNewWindowFromTab: browser.newWindowFromTab(tab, callback)
90 onTabClosed: internal.closeTab(index, moving)
91
92 onFindInPageModeChanged: {
93@@ -1118,6 +1156,7 @@
94 maybeFocusAddressBar()
95 } else {
96 tabContainer.forceActiveFocus()
97+ tab.load();
98 }
99 }
100 }
101@@ -1456,77 +1495,35 @@
102 asynchronous: true
103 }
104
105- DropArea {
106- id: dropArea
107+ // Cover the webview (gaps around tabsbar) with DropArea so that webview doesn't steal events
108+ DropArea {
109+ id: dropAreaTopCover
110+ anchors {
111+ left: parent.left
112+ right: parent.right
113+ top: parent.top
114+ }
115+ height: units.gu(1)
116+ keys: ["webbrowser/tab-" + (incognito ? "incognito" : "public")]
117+ visible: chrome.showTabsBar
118+
119+ onEntered: {
120+ window.raise()
121+ window.requestActivate()
122+ }
123+ }
124+
125+ DropArea {
126+ id: dropAreaBottomCover
127 anchors {
128 fill: parent
129+ topMargin: chrome.tabsBarHeight
130 }
131 keys: ["webbrowser/tab-" + (incognito ? "incognito" : "public")]
132
133- readonly property real heightThreshold: chrome.tabsBarHeight
134-
135- onDropped: {
136- // IgnoreAction - no DropArea accepted so New Window
137- // MoveAction - DropArea accept but different window
138- // CopyAction - DropArea accept but same window
139-
140- if (drag.y > heightThreshold) {
141- // Dropped in bottom area, creating new window
142- drop.accept(Qt.IgnoreAction);
143- } else if (drag.source.tabWindow === thisWindow) {
144- // Dropped in same window
145- drop.accept(Qt.CopyAction);
146- } else {
147- // Dropped in new window, moving tab
148-
149- thisWindow.addExistingTab(drag.source.tab);
150- thisWindow.tabsModel.currentIndex = window.tabsModel.count - 1;
151- thisWindow.show();
152- thisWindow.requestActivate();
153-
154- thisWindow.tabsModel.currentTab.load();
155-
156- drop.accept(Qt.MoveAction);
157- }
158- }
159 onEntered: {
160- thisWindow.raise()
161- thisWindow.requestActivate();
162- }
163- onPositionChanged: {
164- if (drag.source.tabWindow === thisWindow && drag.y < heightThreshold) {
165- // tab drag is within same window and in chrome
166- // so reorder tabs by setting tabDelegate x position
167- drag.source.x = drag.x - (drag.source.width / 2);
168- }
169- }
170-
171- Rectangle {
172- anchors {
173- left: parent.left
174- right: parent.right
175- top: parent.top
176- }
177- color: "#FFF"
178- height: dropArea.heightThreshold
179- opacity: {
180- // Only hide the white shade when this is the active window
181- // and there is no drag being performed or the drag event is
182- // over the tabs bar
183- if (thisWindow.active && !DragHelper.dragging) {
184- 0
185- } else if (dropArea.containsDrag && dropArea.drag.y <= dropArea.heightThreshold) {
186- 0
187- } else {
188- 0.6
189- }
190- }
191-
192- Behavior on opacity {
193- NumberAnimation {
194-
195- }
196- }
197+ window.raise()
198+ window.requestActivate()
199 }
200 }
201 }
202
203=== modified file 'src/app/webbrowser/BrowserTab.qml'
204--- src/app/webbrowser/BrowserTab.qml 2016-11-08 14:19:52 +0000
205+++ src/app/webbrowser/BrowserTab.qml 2017-02-15 13:53:24 +0000
206@@ -1,5 +1,5 @@
207 /*
208- * Copyright 2014-2016 Canonical Ltd.
209+ * Copyright 2014-2017 Canonical Ltd.
210 *
211 * This file is part of webbrowser-app.
212 *
213@@ -21,6 +21,7 @@
214 import Ubuntu.Web 0.2
215 import com.canonical.Oxide 1.4 as Oxide
216 import webbrowserapp.private 0.1
217+import webbrowsercommon.private 0.1
218 import "."
219
220 FocusScope {
221@@ -38,6 +39,7 @@
222 readonly property url url: webview ? webview.url : initialUrl
223 readonly property string title: webview ? webview.title : initialTitle
224 readonly property url icon: webview ? webview.icon : initialIcon
225+ readonly property url localIcon: faviconFetcher.localUrl
226 property url preview
227 property bool current: false
228 readonly property real lastCurrent: internal.lastCurrent
229@@ -61,6 +63,12 @@
230 }
231 }
232
233+ FaviconFetcher {
234+ id: faviconFetcher
235+ shouldCache: !tab.incognito
236+ url: tab.icon
237+ }
238+
239 FocusScope {
240 id: webviewContainer
241 anchors.fill: parent
242
243=== modified file 'src/app/webbrowser/CMakeLists.txt'
244--- src/app/webbrowser/CMakeLists.txt 2016-09-28 12:35:43 +0000
245+++ src/app/webbrowser/CMakeLists.txt 2017-02-15 13:53:24 +0000
246@@ -33,7 +33,6 @@
247
248 set(WEBBROWSER_APP_SRC
249 cache-deleter.cpp
250- drag-helper.cpp
251 file-operations.cpp
252 reparenter.cpp
253 searchengine.cpp
254
255=== modified file 'src/app/webbrowser/Chrome.qml'
256--- src/app/webbrowser/Chrome.qml 2016-11-08 16:11:45 +0000
257+++ src/app/webbrowser/Chrome.qml 2017-02-15 13:53:24 +0000
258@@ -1,5 +1,5 @@
259 /*
260- * Copyright 2013-2016 Canonical Ltd.
261+ * Copyright 2013-2017 Canonical Ltd.
262 *
263 * This file is part of webbrowser-app.
264 *
265@@ -42,12 +42,13 @@
266 property alias availableHeight: navigationBar.availableHeight
267 readonly property alias bookmarkTogglePlaceHolder: navigationBar.bookmarkTogglePlaceHolder
268 property bool touchEnabled: true
269- readonly property real tabsBarHeight: tabsBar.height + content.anchors.topMargin
270+ readonly property real tabsBarHeight: tabsBar.height + tabsBar.anchors.topMargin + content.anchors.topMargin
271 property BrowserWindow thisWindow
272+ property Component windowFactory
273+ property bool tabsBarDimmed: false
274
275 signal switchToTab(int index)
276 signal requestNewTab(int index, bool makeCurrent)
277- signal requestNewWindowFromTab(var tab, var callback)
278 signal tabClosed(int index, bool moving)
279
280 backgroundColor: incognito ? UbuntuColors.darkGrey : "#ffffff"
281@@ -61,7 +62,6 @@
282 FocusScope {
283 id: content
284 anchors.fill: parent
285- anchors.topMargin: showTabsBar ? units.gu(1) : 0
286
287 focus: true
288
289@@ -72,33 +72,34 @@
290
291 Loader {
292 id: tabsBar
293+ anchors {
294+ left: parent.left
295+ right: parent.right
296+ top: parent.top
297+ }
298+ asynchronous: true
299+ height: active ? units.gu(3) : 0
300
301 Component.onCompleted: {
302- tabsBar.setSource("TabsBar.qml", {
303- "model": Qt.binding(function () {return chrome.tabsModel}),
304- "incognito": Qt.binding(function () {return chrome.incognito}),
305- "fgColor": Qt.binding(function () {return navigationBar.fgColor}),
306- "thisWindow": Qt.binding(function () {return chrome.thisWindow}),
307- "touchEnabled": Qt.binding(function () {return chrome.touchEnabled})
308- })
309+ setSource(
310+ Qt.resolvedUrl("TabsBar.qml"),
311+ {
312+ "dimmed": Qt.binding(function() { return chrome.tabsBarDimmed; }),
313+ "model": Qt.binding(function() { return chrome.tabsModel; }),
314+ "incognito": Qt.binding(function() { return chrome.incognito; }),
315+ "dragAndDrop.previewTopCrop": Qt.binding(function() { return chrome.height; }),
316+ "dragAndDrop.thisWindow": Qt.binding(function() { return chrome.thisWindow; }),
317+ "windowFactory": Qt.binding(function() { return chrome.windowFactory; }),
318+ }
319+ )
320 }
321
322 Connections {
323 target: tabsBar.item
324
325- onSwitchToTab: chrome.switchToTab(index)
326 onRequestNewTab: chrome.requestNewTab(index, makeCurrent)
327- onRequestNewWindowFromTab: chrome.requestNewWindowFromTab(tab, callback)
328 onTabClosed: chrome.tabClosed(index, moving)
329 }
330- asynchronous: true
331-
332- anchors {
333- top: parent.top
334- left: parent.left
335- right: parent.right
336- }
337- height: active ? (touchEnabled ? units.gu(4) : units.gu(3)) : 0
338 }
339
340 NavigationBar {
341
342=== added file 'src/app/webbrowser/TabsBar.qml'
343--- src/app/webbrowser/TabsBar.qml 1970-01-01 00:00:00 +0000
344+++ src/app/webbrowser/TabsBar.qml 2017-02-15 13:53:24 +0000
345@@ -0,0 +1,119 @@
346+/*
347+ * Copyright 2013-2017 Canonical Ltd.
348+ *
349+ * This file is part of webbrowser-app.
350+ *
351+ * webbrowser-app is free software; you can redistribute it and/or modify
352+ * it under the terms of the GNU General Public License as published by
353+ * the Free Software Foundation; version 3.
354+ *
355+ * webbrowser-app is distributed in the hope that it will be useful,
356+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
357+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
358+ * GNU General Public License for more details.
359+ *
360+ * You should have received a copy of the GNU General Public License
361+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
362+ */
363+
364+import QtQuick 2.4
365+import Ubuntu.Components 1.3
366+import Ubuntu.Components.Extras 0.3 as Extras
367+import Ubuntu.Components.Popups 1.3
368+import "."
369+import ".."
370+
371+Extras.TabsBar {
372+ id: tabsBar
373+ color: "#D9D9D9" // FIXME: not in palette hardcode for now
374+ dragAndDrop {
375+ enabled: __platformName != "ubuntumirclient"
376+ maxYDiff: height / 12
377+ mimeType: "webbrowser/tab-" + (incognito ? "incognito" : "public")
378+ previewUrlFromIndex: function(index) {
379+ if (tabsBar.model.get(index)) {
380+ return PreviewManager.previewPathFromUrl(tabsBar.model.get(index).url)
381+ } else {
382+ return "";
383+ }
384+ }
385+ }
386+ fallbackIcon: "stock_website"
387+ windowFactoryProperties: {
388+ "incognito": tabsBar.incognito,
389+ "height": window.height,
390+ "width": window.width,
391+ }
392+
393+ property bool incognito
394+
395+ signal requestNewTab(int index, bool makeCurrent)
396+ signal tabClosed(int index, bool moving)
397+
398+ onContextMenu: PopupUtils.open(contextualOptionsComponent, tabDelegate, {"targetIndex": index})
399+
400+ // Note: This works as a binding, when the returned value changes, QML recalls the function
401+ function iconSourceFromModelItem(modelData, index) {
402+ return modelData.tab ? modelData.tab.localIcon : "";
403+ }
404+
405+ function titleFromModelItem(modelItem) {
406+ return modelItem.title ? modelItem.title : (modelItem.url.toString() ? modelItem.url : i18n.tr("New tab"))
407+ }
408+
409+ actions: [
410+ Action {
411+ // FIXME: icon from theme is fuzzy at many GUs
412+// iconSource: Qt.resolvedUrl("Tabs/tab_add.png")
413+ iconName: "add"
414+ objectName: "newTabButton"
415+ onTriggered: tabsBar.model.addTab()
416+ }
417+ ]
418+
419+ Component {
420+ id: contextualOptionsComponent
421+ ActionSelectionPopover {
422+ id: menu
423+ objectName: "tabContextualActions"
424+ property int targetIndex
425+ readonly property var tab: tabsBar.model.get(targetIndex)
426+
427+ actions: ActionList {
428+ Action {
429+ objectName: "tab_action_new_tab"
430+ text: i18n.tr("New Tab")
431+ onTriggered: tabsBar.requestNewTab(menu.targetIndex + 1, true)
432+ }
433+ Action {
434+ objectName: "tab_action_reload"
435+ text: i18n.tr("Reload")
436+ enabled: menu.tab.url.toString().length > 0
437+ onTriggered: menu.tab.reload()
438+ }
439+ Action {
440+ objectName: "tab_action_move_to_new_window"
441+ text: i18n.tr("Move to New Window")
442+ onTriggered: {
443+ // callback function only removes from model
444+ // and not destroy as webview is in new window
445+ // Create new window and add existing tab
446+ var window = tabsBar.windowFactory.createObject(null, windowFactoryProperties);
447+ window.model.addExistingTab(menu.tab);
448+ window.model.selectTab(window.model.count - 1);
449+ window.show();
450+
451+ // Just remove from model and do not destroy
452+ // as webview is used in other window
453+ tabsBar.model.removeTabWithoutDestroying(menu.targetIndex);
454+ }
455+ }
456+ Action {
457+ objectName: "tab_action_close_tab"
458+ text: i18n.tr("Close Tab")
459+ onTriggered: tabsBar.tabClosed(menu.targetIndex, false)
460+ }
461+ }
462+ }
463+ }
464+}
465
466=== removed file 'src/app/webbrowser/TabsBar.qml'
467--- src/app/webbrowser/TabsBar.qml 2016-11-29 15:56:48 +0000
468+++ src/app/webbrowser/TabsBar.qml 1970-01-01 00:00:00 +0000
469@@ -1,345 +0,0 @@
470-/*
471- * Copyright 2015-2016 Canonical Ltd.
472- *
473- * This file is part of webbrowser-app.
474- *
475- * webbrowser-app is free software; you can redistribute it and/or modify
476- * it under the terms of the GNU General Public License as published by
477- * the Free Software Foundation; version 3.
478- *
479- * webbrowser-app is distributed in the hope that it will be useful,
480- * but WITHOUT ANY WARRANTY; without even the implied warranty of
481- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
482- * GNU General Public License for more details.
483- *
484- * You should have received a copy of the GNU General Public License
485- * along with this program. If not, see <http://www.gnu.org/licenses/>.
486- */
487-
488-import QtQuick 2.4
489-import Ubuntu.Components 1.3
490-import Ubuntu.Components.Popups 1.3
491-
492-import webbrowserapp.private 0.1
493-
494-import "."
495-import ".."
496-
497-Item {
498- id: root
499-
500- property alias model: repeater.model
501-
502- property BrowserWindow thisWindow
503-
504- property real minTabWidth: 0 //units.gu(6)
505- property real maxTabWidth: units.gu(20)
506- property real tabWidth: model ? Math.max(Math.min(tabsContainer.maxWidth / model.count, maxTabWidth), minTabWidth) : 0
507-
508- // Minimum size of the larger tab
509- readonly property real minActiveTabWidth: units.gu(10)
510-
511- // When there is a larger tab, calc the smaller tab size
512- readonly property real nonActiveTabWidth: (tabsContainer.maxWidth - minActiveTabWidth) / Math.max(model.count - 1, 1)
513-
514- // The size of the right margin of the tab
515- readonly property real rightMargin: units.dp(1)
516-
517- // Whether there will be one larger tab or not
518- readonly property bool unevenTabWidth: tabWidth + rightMargin < minActiveTabWidth
519-
520- property bool incognito: false
521-
522- property color fgColor: Theme.palette.normal.baseText
523-
524- property bool touchEnabled: true
525-
526- signal switchToTab(int index)
527- signal requestNewTab(int index, bool makeCurrent)
528- signal requestNewWindowFromTab(var tab, var callback)
529- signal tabClosed(int index, bool moving)
530-
531- MouseArea {
532- anchors.fill: parent
533- onWheel: {
534- var angle = (wheel.angleDelta.x != 0) ? wheel.angleDelta.x : wheel.angleDelta.y
535- if ((angle < 0) && (root.model.currentIndex < (root.model.count - 1))) {
536- switchToTab(root.model.currentIndex + 1)
537- } else if ((angle > 0) && (root.model.currentIndex > 0)) {
538- switchToTab(root.model.currentIndex - 1)
539- }
540- }
541- }
542-
543- MouseArea {
544- id: newTabButton
545- objectName: "newTabButton"
546-
547- anchors {
548- left: tabsContainer.right
549- leftMargin: units.gu(1)
550- top: parent.top
551- bottom: parent.bottom
552- }
553- width: height
554-
555- visible: !repeater.reordering
556-
557- Icon {
558- width: units.gu(2)
559- height: units.gu(2)
560- anchors.centerIn: parent
561- name: "add"
562- color: incognito ? "white" : root.fgColor
563- }
564-
565- onClicked: root.requestNewTab(root.model.count, true)
566- }
567-
568- Component {
569- id: contextualOptionsComponent
570- ActionSelectionPopover {
571- id: menu
572- objectName: "tabContextualActions"
573- property int targetIndex
574- readonly property var tab: root.model.get(targetIndex)
575-
576- actions: ActionList {
577- Action {
578- objectName: "tab_action_new_tab"
579- text: i18n.tr("New Tab")
580- onTriggered: root.requestNewTab(menu.targetIndex + 1, false)
581- }
582- Action {
583- objectName: "tab_action_reload"
584- text: i18n.tr("Reload")
585- enabled: menu.tab.url.toString().length > 0
586- onTriggered: menu.tab.reload()
587- }
588- Action {
589- objectName: "tab_action_move_to_new_window"
590- text: i18n.tr("Move to New Window")
591- onTriggered: {
592- // callback function only removes from model
593- // and not destroy as webview is in new window
594- root.requestNewWindowFromTab(menu.tab, function() { root.tabClosed(menu.targetIndex, true); });
595- }
596- }
597- Action {
598- objectName: "tab_action_close_tab"
599- text: i18n.tr("Close Tab")
600- onTriggered: root.tabClosed(menu.targetIndex, false)
601- }
602- }
603- }
604- }
605-
606- Item {
607- id: tabsContainer
608- objectName: "tabsContainer"
609-
610- anchors {
611- top: parent.top
612- bottom: parent.bottom
613- left: parent.left
614- }
615- width: tabWidth * root.model.count
616- readonly property real maxWidth: root.width - newTabButton.width - units.gu(2)
617-
618- readonly property int maxYDiff: height / 4
619-
620- function sign(number) { return number / Math.abs(number); }
621-
622- Repeater {
623- id: repeater
624-
625- property bool reordering: false
626-
627- delegate: MouseArea {
628- id: tabDelegate
629- objectName: "tabDelegate"
630-
631- readonly property int tabIndex: index
632- readonly property var tab: root.model.get(index)
633- readonly property BrowserWindow tabWindow: root.thisWindow
634-
635- property real rightMargin: units.dp(1)
636- width: getSize(index)
637- height: tabsContainer.height
638- y: tabsContainer.y // don't use anchor otherwise drag doesn't work
639-
640- acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
641- readonly property bool dragging: drag.active
642- drag {
643- target: (pressedButtons === Qt.LeftButton) ? tabDelegate : null
644- // FIXME: disable drag and drop on mir pad.lv/1627013
645- axis: __platformName != "ubuntumirclient" ? Drag.XAndYAxis : Drag.XAxis
646- minimumX: 0
647- maximumX: root.width - tabDelegate.width
648- filterChildren: true
649- }
650-
651- TabItem {
652- active: tabIndex === root.model.currentIndex
653- anchors {
654- left: parent.left
655- right: parent.right
656- }
657- height: tabsContainer.height
658- hoverable: true
659- incognito: root.incognito
660- title: model.title ? model.title : (model.url.toString() ? model.url : i18n.tr("New tab"))
661- icon: model.icon
662- fgColor: root.fgColor
663-
664- touchEnabled: root.touchEnabled
665-
666- rightMargin: root.rightMargin
667-
668- // Keep the visual tabitem within maxYDiff of starting point when
669- // dragging vertically so that it doesn't cover other elements
670- y: Math.abs(parent.y) > tabsContainer.maxYDiff ? (tabsContainer.sign(parent.y) * tabsContainer.maxYDiff) - parent.y : 0
671-
672- onClosed: root.tabClosed(index, false)
673- onSelected: root.switchToTab(index)
674- onContextMenu: PopupUtils.open(contextualOptionsComponent, tabDelegate, {"targetIndex": index})
675- }
676-
677- Binding {
678- target: repeater
679- property: "reordering"
680- value: dragging
681- }
682-
683- Behavior on width { NumberAnimation { duration: 250 } }
684-
685- Binding on x {
686- when: !dragging && !DragHelper.dragging
687- value: getLeftX(index)
688- }
689-
690- Behavior on x {
691- NumberAnimation {
692- duration: 250
693- }
694- }
695-
696- NumberAnimation {
697- id: resetVerticalAnimation
698- target: tabDelegate
699- duration: 250
700- property: "y"
701- to: 0
702- }
703-
704- onPositionChanged: {
705- // FIXME: disable drag and drop on mir pad.lv/1627013
706- if (Math.abs(y) > height && __platformName != "ubuntumirclient") {
707- // Reset visual position of tab delegate
708- resetVerticalAnimation.start();
709-
710- if (mouse.buttons === Qt.LeftButton) {
711- // Generate tab preview for drag handle
712- DragHelper.expectedAction = Qt.IgnoreAction | Qt.CopyAction | Qt.MoveAction
713- DragHelper.mimeType = "webbrowser/tab-" + (root.incognito ? "incognito" : "public")
714- DragHelper.previewBorderWidth = units.gu(1)
715- DragHelper.previewSize = Qt.size(units.gu(35), units.gu(22.5))
716- DragHelper.previewTopCrop = chrome.height
717- DragHelper.previewUrl = PreviewManager.previewPathFromUrl(tab.url)
718- DragHelper.source = tabDelegate
719-
720- var dropAction = DragHelper.execDrag(tab.url);
721-
722- // IgnoreAction - no DropArea accepted so New Window
723- // MoveAction - DropArea accept but different window
724- // CopyAction - DropArea accept but same window
725-
726- if (dropAction === Qt.MoveAction) {
727- // Moved into another window
728-
729- // drag.active does not become false when
730- // closing the tab so set reordering back
731- repeater.reordering = false;
732-
733- // Just remove from model and do not destory
734- // as webview is used in other window
735- root.tabClosed(index, true);
736- } else if (dropAction === Qt.CopyAction) {
737- // Moved into the same window
738-
739- // So no action
740- } else if (dropAction === Qt.IgnoreAction) {
741- // Moved outside of any window
742-
743- // drag.active does not become false when
744- // closing the tab so set reordering back
745- repeater.reordering = false;
746-
747- // callback function only removes from model
748- // and not destroy as webview is in new window
749- root.requestNewWindowFromTab(tab, function() { root.tabClosed(index, true); })
750- } else {
751- // Unknown state
752- console.debug("Unknown drop action:", dropAction);
753- }
754- }
755- }
756- }
757- onReleased: resetVerticalAnimation.start();
758-
759- function getLeftX(index) {
760- if (unevenTabWidth) {
761- if (index > root.model.currentIndex) {
762- return minActiveTabWidth + (nonActiveTabWidth * (index - 1))
763- } else {
764- return nonActiveTabWidth * index
765- }
766- } else {
767- // Do not depend on width otherwise X updates after
768- // Width causing the animation to be two stage
769- // instead perform same calculation (tabWidth + rightMargin)
770- return index * (tabWidth + rightMargin)
771- }
772- }
773-
774- function getSize(index) {
775- if (unevenTabWidth) {
776- // Uneven tabs so use large or small depending which index
777- if (index === root.model.currentIndex) {
778- return minActiveTabWidth
779- } else {
780- return nonActiveTabWidth
781- }
782- } else {
783- return tabWidth + rightMargin
784- }
785- }
786-
787- onXChanged: {
788- if (!dragging) return
789-
790- var leftX = getLeftX(index)
791-
792- if (x < (leftX - getSize(index - 1) / 2) && index > 0) {
793- root.model.move(index, index - 1)
794- } else if ((x > (leftX + getSize(index + 1) / 2)) && (index < (root.model.count - 1))) {
795- root.model.move(index + 1, index)
796- }
797- }
798-
799- z: (root.model.currentIndex == index) ? 3 : 1 - index / root.model.count
800- }
801- }
802-
803- Rectangle {
804- anchors {
805- left: parent.left
806- bottom: parent.bottom
807- }
808- width: root.width
809- height: units.dp(1)
810- color: "#cacaca"
811- z: 2
812- }
813- }
814-}
815
816=== removed file 'src/app/webbrowser/drag-helper.cpp'
817--- src/app/webbrowser/drag-helper.cpp 2016-11-29 15:55:31 +0000
818+++ src/app/webbrowser/drag-helper.cpp 1970-01-01 00:00:00 +0000
819@@ -1,203 +0,0 @@
820-/*
821- * Copyright 2016 Canonical Ltd.
822- *
823- * This file is part of webbrowser-app.
824- *
825- * webbrowser-app is free software; you can redistribute it and/or modify
826- * it under the terms of the GNU General Public License as published by
827- * the Free Software Foundation; version 3.
828- *
829- * webbrowser-app is distributed in the hope that it will be useful,
830- * but WITHOUT ANY WARRANTY; without even the implied warranty of
831- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
832- * GNU General Public License for more details.
833- *
834- * You should have received a copy of the GNU General Public License
835- * along with this program. If not, see <http://www.gnu.org/licenses/>.
836- */
837-
838-#include "drag-helper.h"
839-
840-#include <QtCore/QMimeData>
841-#include <QtCore/QPoint>
842-#include <QtCore/QSize>
843-#include <QtCore/QString>
844-#include <QtGui/QDrag>
845-#include <QtGui/QDropEvent>
846-#include <QtGui/QPainter>
847-#include <QtGui/QPen>
848-#include <QtGui/QPixmap>
849-#include <QtQuick/QQuickItem>
850-
851-DragHelper::DragHelper()
852- : QObject(),
853- m_active(false),
854- m_dragging(false),
855- m_expected_action(Qt::IgnoreAction),
856- m_mime_type(QStringLiteral("webbrowser/tab")),
857- m_preview_border_width(8),
858- m_preview_size(QSizeF(200, 150)),
859- m_preview_top_crop(0),
860- m_preview_url(""),
861- m_source(Q_NULLPTR)
862-{
863-
864-}
865-
866-QPixmap DragHelper::drawPixmapWithBorder(QPixmap pixmap, int borderWidth, QColor color)
867-{
868- // Create a transparent pixmap to draw to
869- QPixmap output(pixmap.width() + borderWidth * 2, pixmap.height() + borderWidth * 2);
870- output.fill(QColor(0, 0, 0, 0));
871-
872- // Draw the pixmap with space around the edge for a border
873- QPainter borderPainter(&output);
874- borderPainter.setRenderHint(QPainter::Antialiasing);
875- borderPainter.drawPixmap(borderWidth, borderWidth, pixmap);
876-
877- // Define a pen to use for the border
878- QPen borderPen;
879- borderPen.setColor(color);
880- borderPen.setJoinStyle(Qt::MiterJoin);
881- borderPen.setStyle(Qt::SolidLine);
882- borderPen.setWidth(borderWidth);
883-
884- // Set the pen and draw the border
885- borderPainter.setPen(borderPen);
886- borderPainter.drawRect(borderWidth / 2, borderWidth / 2,
887- output.width() - borderWidth, output.height() - borderWidth);
888-
889- return output;
890-}
891-
892-Qt::DropAction DragHelper::execDrag(QString tabId)
893-{
894- QDrag *drag = new QDrag(m_source);
895-
896- // Create a mimedata object to use for the drag
897- QMimeData *mimeData = new QMimeData;
898- mimeData->setData(mimeType(), tabId.toLatin1());
899-
900- // Get a bordered pixmap of the previewUrl
901- QSize size = previewSize().toSize();
902-
903- QPixmap pixmap = drawPixmapWithBorder(getPreviewUrlAsPixmap(size.width(), size.height()),
904- previewBorderWidth(), QColor(205, 205, 205, 255 * 0.6)); // #cdcdcd
905-
906- // Setup the drag and then execute it
907- drag->setHotSpot(QPoint(size.width() * 0.1, size.height() * 0.1));
908- drag->setMimeData(mimeData);
909- drag->setPixmap(pixmap);
910-
911- setDragging(true);
912-
913- Qt::DropAction action = drag->exec(expectedAction());
914-
915- setDragging(false);
916-
917- return action;
918-}
919-
920-QPixmap DragHelper::getPreviewUrlAsPixmap(int width, int height)
921-{
922- QSize pixmapSize(width, height);
923- QPixmap pixmap(previewUrl());
924-
925- if (pixmap.isNull()) {
926- // If loading pixmap failed, draw a white rectangle
927- pixmap = QPixmap(pixmapSize);
928- QPainter painter(&pixmap);
929- painter.eraseRect(0, 0, pixmapSize.width(), pixmapSize.height());
930- painter.fillRect(0, 0, pixmapSize.width(), pixmapSize.height(), QColor(255, 255, 255, 255));
931- } else {
932- // Crop transparent part off the top of the image
933- pixmap = pixmap.copy(0, previewTopCrop(), pixmap.width(), pixmap.height() - previewTopCrop());
934-
935- // Scale image to fit the expected size
936- pixmap = pixmap.scaled(pixmapSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
937- }
938-
939- return pixmap;
940-}
941-
942-void DragHelper::setActive(bool active)
943-{
944- if (m_active != active) {
945- m_active = active;
946-
947- Q_EMIT activeChanged();
948- }
949-}
950-
951-void DragHelper::setDragging(bool dragging)
952-{
953- if (m_dragging != dragging) {
954- m_dragging = dragging;
955-
956- Q_EMIT draggingChanged();
957- }
958-}
959-
960-void DragHelper::setExpectedAction(Qt::DropAction expectedAction)
961-{
962- if (m_expected_action != expectedAction) {
963- m_expected_action = expectedAction;
964-
965- Q_EMIT expectedActionChanged();
966- }
967-}
968-
969-void DragHelper::setMimeType(QString mimeType)
970-{
971- if (m_mime_type != mimeType) {
972- m_mime_type = mimeType;
973-
974- Q_EMIT mimeTypeChanged();
975- }
976-}
977-
978-void DragHelper::setPreviewBorderWidth(int previewBorderWidth)
979-{
980- if (m_preview_border_width != previewBorderWidth) {
981- m_preview_border_width = previewBorderWidth;
982-
983- Q_EMIT previewBorderWidthChanged();
984- }
985-}
986-
987-void DragHelper::setPreviewSize(QSizeF previewSize)
988-{
989- if (m_preview_size != previewSize) {
990- m_preview_size = previewSize;
991-
992- Q_EMIT previewSizeChanged();
993- }
994-}
995-
996-void DragHelper::setPreviewTopCrop(int previewTopCrop)
997-{
998- if (m_preview_top_crop != previewTopCrop) {
999- m_preview_top_crop = previewTopCrop;
1000-
1001- Q_EMIT previewTopCropChanged();
1002- }
1003-}
1004-
1005-void DragHelper::setPreviewUrl(QString previewUrl)
1006-{
1007- if (m_preview_url != previewUrl) {
1008- m_preview_url = previewUrl;
1009-
1010- Q_EMIT previewUrlChanged();
1011- }
1012-}
1013-
1014-void DragHelper::setSource(QQuickItem *source)
1015-{
1016- if (m_source != source) {
1017- m_source = source;
1018-
1019- Q_EMIT sourceChanged();
1020- }
1021-}
1022-
1023
1024=== removed file 'src/app/webbrowser/drag-helper.h'
1025--- src/app/webbrowser/drag-helper.h 2016-11-21 11:59:03 +0000
1026+++ src/app/webbrowser/drag-helper.h 1970-01-01 00:00:00 +0000
1027@@ -1,91 +0,0 @@
1028-/*
1029- * Copyright 2016 Canonical Ltd.
1030- *
1031- * This file is part of webbrowser-app.
1032- *
1033- * webbrowser-app is free software; you can redistribute it and/or modify
1034- * it under the terms of the GNU General Public License as published by
1035- * the Free Software Foundation; version 3.
1036- *
1037- * webbrowser-app is distributed in the hope that it will be useful,
1038- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1039- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1040- * GNU General Public License for more details.
1041- *
1042- * You should have received a copy of the GNU General Public License
1043- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1044- */
1045-
1046-#ifndef __DRAGHELPER_H__
1047-#define __DRAGHELPER_H__
1048-
1049-#include <QtCore/QSizeF>
1050-#include <QtCore/QObject>
1051-#include <QtCore/QString>
1052-#include <QtGui/QColor>
1053-#include <QtGui/QMouseEvent>
1054-
1055-class QQuickItem;
1056-
1057-class DragHelper : public QObject
1058-{
1059- Q_OBJECT
1060-
1061- Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
1062- Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
1063- Q_PROPERTY(Qt::DropAction expectedAction READ expectedAction WRITE setExpectedAction NOTIFY expectedActionChanged)
1064- Q_PROPERTY(QString mimeType READ mimeType WRITE setMimeType NOTIFY mimeTypeChanged)
1065- Q_PROPERTY(int previewBorderWidth READ previewBorderWidth WRITE setPreviewBorderWidth NOTIFY previewBorderWidthChanged)
1066- Q_PROPERTY(QSizeF previewSize READ previewSize WRITE setPreviewSize NOTIFY previewSizeChanged)
1067- Q_PROPERTY(int previewTopCrop READ previewTopCrop WRITE setPreviewTopCrop NOTIFY previewTopCropChanged)
1068- Q_PROPERTY(QString previewUrl READ previewUrl WRITE setPreviewUrl NOTIFY previewUrlChanged)
1069- Q_PROPERTY(QQuickItem* source READ source WRITE setSource NOTIFY sourceChanged)
1070-public:
1071- DragHelper();
1072- bool active() { return m_active; }
1073- bool dragging() { return m_dragging; }
1074- Qt::DropAction expectedAction() { return m_expected_action; }
1075- QString mimeType() { return m_mime_type; }
1076- int previewBorderWidth() { return m_preview_border_width; }
1077- QSizeF previewSize() { return m_preview_size; }
1078- int previewTopCrop() { return m_preview_top_crop; }
1079- QString previewUrl() { return m_preview_url; }
1080- QQuickItem *source() { return m_source; }
1081-signals:
1082- void activeChanged();
1083- void draggingChanged();
1084- void expectedActionChanged();
1085- void mimeTypeChanged();
1086- void previewBorderWidthChanged();
1087- void previewSizeChanged();
1088- void previewTopCropChanged();
1089- void previewUrlChanged();
1090- void sourceChanged();
1091-public slots:
1092- Q_INVOKABLE Qt::DropAction execDrag(QString tabId);
1093- void setActive(bool active);
1094- void setExpectedAction(Qt::DropAction expectedAction);
1095- void setMimeType(QString mimeType);
1096- void setPreviewBorderWidth(int previewBorderWidth);
1097- void setPreviewSize(QSizeF previewSize);
1098- void setPreviewTopCrop(int previewTopCrop);
1099- void setPreviewUrl(QString previewUrl);
1100- void setSource(QQuickItem *source);
1101-private:
1102- QPixmap drawPixmapWithBorder(QPixmap pixmap, int borderWidth, QColor color);
1103- QPixmap getPreviewUrlAsPixmap(int width, int height);
1104- void setDragging(bool dragging);
1105-
1106- bool m_active;
1107- bool m_dragging;
1108- Qt::DropAction m_expected_action;
1109- QString m_mime_type;
1110- int m_preview_border_width;
1111- QSizeF m_preview_size;
1112- int m_preview_top_crop;
1113- QString m_preview_url;
1114- QQuickItem *m_source;
1115-};
1116-
1117-#endif // __DRAGHELPER_H__
1118-
1119
1120=== modified file 'src/app/webbrowser/tabs-model.cpp'
1121--- src/app/webbrowser/tabs-model.cpp 2017-02-15 11:22:05 +0000
1122+++ src/app/webbrowser/tabs-model.cpp 2017-02-15 13:53:24 +0000
1123@@ -217,9 +217,25 @@
1124 if ((from == to) || !checkValidTabIndex(from) || !checkValidTabIndex(to)) {
1125 return;
1126 }
1127- beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
1128- m_tabs.move(from, to);
1129- endMoveRows();
1130+
1131+ int diff = to - from;
1132+ int i = from;
1133+
1134+ // Shuffle index along until destination
1135+ while (i != to) {
1136+ if (diff > 0) {
1137+ beginMoveRows(QModelIndex(), i, i, QModelIndex(), i + 2);
1138+ m_tabs.move(i + 1, i);
1139+ i += 1;
1140+ } else {
1141+ beginMoveRows(QModelIndex(), i, i, QModelIndex(), i - 1);
1142+ m_tabs.move(i, i - 1);
1143+ i -= 1;
1144+ }
1145+
1146+ endMoveRows();
1147+ }
1148+
1149 if (m_currentIndex == from) {
1150 m_currentIndex = to;
1151 Q_EMIT currentIndexChanged();
1152
1153=== modified file 'src/app/webbrowser/webbrowser-app.cpp'
1154--- src/app/webbrowser/webbrowser-app.cpp 2016-11-10 11:52:26 +0000
1155+++ src/app/webbrowser/webbrowser-app.cpp 2017-02-15 13:53:24 +0000
1156@@ -1,5 +1,5 @@
1157 /*
1158- * Copyright 2013-2016 Canonical Ltd.
1159+ * Copyright 2013-2017 Canonical Ltd.
1160 *
1161 * This file is part of webbrowser-app.
1162 *
1163@@ -21,7 +21,6 @@
1164 #include "cache-deleter.h"
1165 #include "config.h"
1166 #include "downloads-model.h"
1167-#include "drag-helper.h"
1168 #include "file-operations.h"
1169 #include "history-domainlist-model.h"
1170 #include "history-lastvisitdatelist-model.h"
1171@@ -63,7 +62,6 @@
1172 MAKE_SINGLETON_FACTORY(HistoryModel)
1173 MAKE_SINGLETON_FACTORY(DownloadsModel)
1174 MAKE_SINGLETON_FACTORY(Reparenter)
1175-MAKE_SINGLETON_FACTORY(DragHelper)
1176
1177 bool WebbrowserApp::initialize()
1178 {
1179@@ -80,7 +78,6 @@
1180 qmlRegisterSingletonType<CacheDeleter>(uri, 0, 1, "CacheDeleter", CacheDeleter_singleton_factory);
1181 qmlRegisterSingletonType<DownloadsModel>(uri, 0, 1, "DownloadsModel", DownloadsModel_singleton_factory);
1182 qmlRegisterType<TextSearchFilterModel>(uri, 0, 1, "TextSearchFilterModel");
1183- qmlRegisterSingletonType<DragHelper>(uri, 0, 1, "DragHelper", DragHelper_singleton_factory);
1184 qmlRegisterSingletonType<Reparenter>(uri, 0, 1, "Reparenter", Reparenter_singleton_factory);
1185
1186 if (BrowserApplication::initialize("webbrowser/webbrowser-app.qml", QStringLiteral("webbrowser-app"))) {
1187
1188=== modified file 'src/app/webbrowser/webbrowser-app.qml'
1189--- src/app/webbrowser/webbrowser-app.qml 2016-11-08 14:19:52 +0000
1190+++ src/app/webbrowser/webbrowser-app.qml 2017-02-15 13:53:24 +0000
1191@@ -1,5 +1,5 @@
1192 /*
1193- * Copyright 2013-2016 Canonical Ltd.
1194+ * Copyright 2013-2017 Canonical Ltd.
1195 *
1196 * This file is part of webbrowser-app.
1197 *
1198@@ -89,6 +89,7 @@
1199 id: window
1200
1201 property alias incognito: browser.incognito
1202+ readonly property alias model: browser.tabsModel
1203 readonly property var tabsModel: browser.tabsModel
1204
1205 currentWebview: browser.currentWebview
1206@@ -185,25 +186,7 @@
1207 anchors.fill: parent
1208 thisWindow: window
1209 settings: webbrowserapp.settings
1210- onNewWindowFromTab: {
1211- var window = windowFactory.createObject(
1212- null,
1213- {
1214- "incognito": tab.incognito,
1215- "height": parent.height,
1216- "width": parent.width,
1217- }
1218- );
1219-
1220- window.addExistingTab(tab);
1221- window.tabsModel.currentIndex = window.tabsModel.count - 1;
1222- window.show();
1223- window.requestActivate();
1224-
1225- window.tabsModel.currentTab.load();
1226-
1227- callback();
1228- }
1229+ windowFactory: webbrowserapp.windowFactory
1230 onNewWindowRequested: {
1231 var window = windowFactory.createObject(
1232 null,
1233@@ -287,14 +270,6 @@
1234 tabsModel.add(tab)
1235 return tab
1236 }
1237-
1238- function addExistingTab(tab) {
1239- tabsModel.add(tab);
1240-
1241- browser.bindExistingTab(tab);
1242-
1243- return tab;
1244- }
1245 }
1246 }
1247
1248
1249=== modified file 'tests/autopilot/webbrowser_app/emulators/browser.py'
1250--- tests/autopilot/webbrowser_app/emulators/browser.py 2016-11-03 14:25:02 +0000
1251+++ tests/autopilot/webbrowser_app/emulators/browser.py 2017-02-15 13:53:24 +0000
1252@@ -1,6 +1,6 @@
1253 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1254 #
1255-# Copyright 2013-2016 Canonical
1256+# Copyright 2013-2017 Canonical
1257 #
1258 # This program is free software: you can redistribute it and/or modify it
1259 # under the terms of the GNU General Public License version 3, as published
1260@@ -365,8 +365,7 @@
1261
1262 @autopilot.logging.log_action(logger.info)
1263 def click_new_tab_button(self):
1264- button = self.select_single("QQuickMouseArea",
1265- objectName="newTabButton")
1266+ button = self.select_single(objectName="newTabButton")
1267 self.pointing_device.click_object(button)
1268
1269 def get_tabs(self):
1270@@ -383,7 +382,7 @@
1271 @autopilot.logging.log_action(logger.info)
1272 def close_tab(self, index):
1273 tab = self.get_tab(index)
1274- close_button = tab.select_single(objectName="closeButton")
1275+ close_button = tab.select_single(objectName="tabCloseButton")
1276 self.pointing_device.click_object(close_button)
1277
1278
1279
1280=== modified file 'tests/unittests/qml/CMakeLists.txt'
1281--- tests/unittests/qml/CMakeLists.txt 2017-01-05 10:26:58 +0000
1282+++ tests/unittests/qml/CMakeLists.txt 2017-02-15 13:53:24 +0000
1283@@ -18,7 +18,6 @@
1284 ${webbrowser-app_SOURCE_DIR}/bookmarks-model.cpp
1285 ${webbrowser-app_SOURCE_DIR}/bookmarks-folder-model.cpp
1286 ${webbrowser-app_SOURCE_DIR}/bookmarks-folderlist-model.cpp
1287- ${webbrowser-app_SOURCE_DIR}/drag-helper.cpp
1288 ${webbrowser-app_SOURCE_DIR}/file-operations.cpp
1289 ${webbrowser-app_SOURCE_DIR}/history-domain-model.cpp
1290 ${webbrowser-app_SOURCE_DIR}/history-domainlist-model.cpp
1291
1292=== modified file 'tests/unittests/qml/tst_QmlTests.cpp'
1293--- tests/unittests/qml/tst_QmlTests.cpp 2017-01-05 10:26:58 +0000
1294+++ tests/unittests/qml/tst_QmlTests.cpp 2017-02-15 13:53:24 +0000
1295@@ -1,5 +1,5 @@
1296 /*
1297- * Copyright 2013-2016 Canonical Ltd.
1298+ * Copyright 2013-2017 Canonical Ltd.
1299 *
1300 * This file is part of webbrowser-app.
1301 *
1302@@ -28,7 +28,6 @@
1303 // local
1304 #include "bookmarks-model.h"
1305 #include "bookmarks-folderlist-model.h"
1306-#include "drag-helper.h"
1307 #include "favicon-fetcher.h"
1308 #include "file-operations.h"
1309 #include "history-domain-model.h"
1310@@ -168,7 +167,6 @@
1311 MAKE_SINGLETON_FACTORY(HistoryModelMock)
1312 MAKE_SINGLETON_FACTORY(TestContext)
1313 MAKE_SINGLETON_FACTORY(Reparenter)
1314-MAKE_SINGLETON_FACTORY(DragHelper)
1315
1316 int main(int argc, char** argv)
1317 {
1318@@ -187,7 +185,6 @@
1319 qmlRegisterType<LimitProxyModel>(browserUri, 0, 1, "LimitProxyModel");
1320 qmlRegisterType<TextSearchFilterModel>(browserUri, 0, 1, "TextSearchFilterModel");
1321 qmlRegisterSingletonType<FileOperations>(browserUri, 0, 1, "FileOperations", FileOperations_singleton_factory);
1322- qmlRegisterSingletonType<DragHelper>(browserUri, 0, 1, "DragHelper", DragHelper_singleton_factory);
1323 qmlRegisterSingletonType<Reparenter>(browserUri, 0, 1, "Reparenter", Reparenter_singleton_factory);
1324
1325 const char* testUri = "webbrowsertest.private";
1326
1327=== modified file 'tests/unittests/qml/tst_TabsBar.qml'
1328--- tests/unittests/qml/tst_TabsBar.qml 2016-11-18 15:10:47 +0000
1329+++ tests/unittests/qml/tst_TabsBar.qml 2017-02-15 13:53:24 +0000
1330@@ -1,5 +1,5 @@
1331 /*
1332- * Copyright 2015 Canonical Ltd.
1333+ * Copyright 2015-2017 Canonical Ltd.
1334 *
1335 * This file is part of webbrowser-app.
1336 *
1337@@ -32,6 +32,35 @@
1338
1339 TabsModel {
1340 id: tabsModel
1341+
1342+ // These methods are required by the TabsBar component
1343+ property int selectedIndex: currentIndex
1344+
1345+ function addTab() {
1346+ tabs.requestNewTab(count, true);
1347+ }
1348+
1349+ function moveTab(from, to) {
1350+ if (from == to
1351+ || from < 0 || from >= count
1352+ || to < 0 || to >= count) {
1353+ return;
1354+ }
1355+
1356+ move(from, to);
1357+ }
1358+
1359+ // Overload removeTab and add moving property so we can tell when
1360+ // the tab is closing due to moving to a new window
1361+ // This is required because we need to avoid destroying the content
1362+ // of that tab that is moved
1363+ function removeTab(index, moving) {
1364+ tabs.tabClosed(index, false);
1365+ }
1366+
1367+ function selectTab(index) {
1368+ currentIndex = index;
1369+ }
1370 }
1371
1372 Component {
1373@@ -58,8 +87,9 @@
1374 height: 50
1375
1376 model: tabsModel
1377- onSwitchToTab: model.currentIndex = index
1378+
1379 onRequestNewTab: insertTab("", "", "", index)
1380+
1381 function appendTab(url, title, icon) {
1382 insertTab(url, title, icon, model.count)
1383 model.currentIndex = model.count - 1
1384@@ -97,16 +127,16 @@
1385 }
1386
1387 function getTabDelegate(index) {
1388- var container = findChild(tabs, "tabsContainer")
1389- for (var i = 0; i < container.children.length; ++i) {
1390- var child = container.children[i]
1391- if ((child.objectName == "tabDelegate") && (child.tabIndex == index)) {
1392- return child
1393- }
1394+ var listview = findChild(tabs, "tabListView")
1395+ var items = getListItems(listview, "tabDelegate")
1396+
1397+ if (index < items.length) {
1398+ return items[index]
1399+ } else {
1400+ return null
1401 }
1402- return null
1403 }
1404-
1405+
1406 function getTabItem(index) {
1407 return findChild(getTabDelegate(index), "tabItem")
1408 }
1409@@ -208,7 +238,7 @@
1410 var count = populateTabs()
1411 for (var i = 0; i < count; i++) {
1412 var tab = getTabDelegate(count - (i + 1))
1413- var closeButton = findChild(tab, "closeButton")
1414+ var closeButton = findChild(tab, "tabCloseButton")
1415 clickItem(closeButton, data.button)
1416 compare(tabClosedSpy.count, i + 1)
1417 compare(tabClosedSpy.signalArguments[i][0], count - (i + 1))
1418@@ -220,15 +250,25 @@
1419
1420 function dragTab(tab, dx, index) {
1421 var c = centerOf(tab)
1422- mouseDrag(tab, c.x, c.y, dx, 0)
1423- compare(getTabDelegate(index), tab)
1424+
1425+ mousePress(tab, c.x, c.y);
1426+
1427+ // Move tab slowly otherwise it can skip the DropArea
1428+ for (var j = 0; j < dx; j++) {
1429+ mouseMove(tabs, j, c.y)
1430+ wait(1)
1431+ }
1432+
1433+ mouseRelease(tab, c.x + dx, c.y);
1434+
1435+ compare(tabsModel.get(index).title, tab.title)
1436 compare(tabsModel.currentIndex, index)
1437 wait(500)
1438 }
1439
1440 // Move the first tab to the right
1441- var tab = getTabDelegate(0)
1442- dragTab(tab, tab.width * 0.8, 1)
1443+ var tab = getTabItem(0)
1444+ dragTab(tab, tab.width, 1)
1445
1446 // Start a move to the right and release too early
1447 dragTab(tab, tab.width * 0.3, 1)
1448@@ -240,7 +280,7 @@
1449 dragTab(tab, tab.width * 3, 2)
1450
1451 // Move another tab all the way to the left and overshoot
1452- tab = getTabDelegate(1)
1453+ tab = getTabItem(1)
1454 dragTab(tab, -tab.width * 2, 0)
1455 }
1456
1457@@ -300,27 +340,5 @@
1458 compare(tabsModel.get(1).url, "")
1459 compare(tabsModel.get(2).url, baseUrl + "2")
1460 }
1461-
1462- function test_close_icon_invisible() {
1463- var count = 20
1464-
1465- // Add 2 tabs and check both have showCloseIcon
1466- tabs.appendTab("", "tab " + 0, "")
1467- tabs.appendTab("", "tab " + 0, "")
1468-
1469- compare(getTabItem(0).showCloseIcon, true)
1470- compare(getTabItem(1).showCloseIcon, true)
1471-
1472- // Add new tabs and check that both icons are shown
1473- for (var i = 2; i < count; ++i) {
1474- tabs.appendTab("", "tab " + i, "")
1475- compare(tabsModel.currentIndex, i)
1476-
1477- tryCompare(getTabItem(i), "showCloseIcon", true, 1000)
1478- }
1479-
1480- // Check that middle non-selected tab icons are not shown
1481- compare(getTabItem(count - 10).showCloseIcon, false)
1482- }
1483 }
1484 }
1485
1486=== modified file 'tests/unittests/tabs-model/tst_TabsModelTests.cpp'
1487--- tests/unittests/tabs-model/tst_TabsModelTests.cpp 2017-02-15 11:22:05 +0000
1488+++ tests/unittests/tabs-model/tst_TabsModelTests.cpp 2017-02-15 13:53:24 +0000
1489@@ -507,12 +507,28 @@
1490 QSignalSpy spyTab(model, SIGNAL(currentTabChanged()));
1491
1492 model->move(from, to);
1493- QCOMPARE(spyMoved.count(), moved ? 1 : 0);
1494+ QCOMPARE(spyMoved.count(), moved ? abs(from - to) : 0);
1495 if (moved) {
1496- QList<QVariant> args = spyMoved.takeFirst();
1497- QCOMPARE(args.at(1).toInt(), from);
1498- QCOMPARE(args.at(2).toInt(), from);
1499- QCOMPARE(args.at(4).toInt(), to);
1500+ QList<QVariant> argsFirst = spyMoved.first();
1501+ QList<QVariant> argsLast = spyMoved.last();
1502+
1503+ if (to > from) {
1504+ QCOMPARE(argsFirst.at(1).toInt(), from);
1505+ QCOMPARE(argsFirst.at(2).toInt(), from);
1506+ QCOMPARE(argsFirst.at(4).toInt(), from + 2);
1507+
1508+ QCOMPARE(argsLast.at(1).toInt(), to - 1);
1509+ QCOMPARE(argsLast.at(2).toInt(), to - 1);
1510+ QCOMPARE(argsLast.at(4).toInt(), to + 1);
1511+ } else {
1512+ QCOMPARE(argsFirst.at(1).toInt(), from);
1513+ QCOMPARE(argsFirst.at(2).toInt(), from);
1514+ QCOMPARE(argsFirst.at(4).toInt(), from - 1);
1515+
1516+ QCOMPARE(argsLast.at(1).toInt(), to + 1);
1517+ QCOMPARE(argsLast.at(2).toInt(), to + 1);
1518+ QCOMPARE(argsLast.at(4).toInt(), to);
1519+ }
1520 }
1521 QCOMPARE(spyIndex.count(), indexChanged ? 1 : 0);
1522 QCOMPARE(model->currentIndex(), newIndex);

Subscribers

People subscribed via source and target branches