Merge lp:~rpadovani/webbrowser-app/newTabRefactoring into lp:webbrowser-app

Proposed by Riccardo Padovani on 2015-01-24
Status: Merged
Approved by: Olivier Tilloy on 2015-05-25
Approved revision: 901
Merged at revision: 1038
Proposed branch: lp:~rpadovani/webbrowser-app/newTabRefactoring
Merge into: lp:webbrowser-app
Diff against target: 773 lines (+351/-279)
6 files modified
src/app/webbrowser/Browser.qml (+59/-59)
src/app/webbrowser/NewTabView.qml (+200/-126)
src/app/webbrowser/UrlsList.qml (+72/-93)
src/app/webbrowser/bookmarks-model.cpp (+3/-1)
src/app/webbrowser/bookmarks-model.h (+2/-0)
tests/unittests/bookmarks-model/tst_BookmarksModelTests.cpp (+15/-0)
To merge this branch: bzr merge lp:~rpadovani/webbrowser-app/newTabRefactoring
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing on 2015-05-25
Olivier Tilloy 2015-01-24 Approve on 2015-05-25
Review via email: mp+247498@code.launchpad.net

Commit Message

New tab view refactoring.

Description of the Change

Improved the new tab accordingly to UX

# Fixed bugs
- the "see more" / "see less" actions for bookmarks should be in the section header
- Bookmarks section header in new tab view not updated when a bookmark is added/removed

To post a comment you must log in.
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Olivier Tilloy (osomon) wrote :

That looks closer to the visual spec, but needs tweaking still:
 - the More/Less button should have a strokeColor of "#5d5d5d"
 - it should be taller
 - there should be an orange star to the left of the "bookmarks" section header
 - when expanding the bookmarks section, the "popular sites" one should be hidden (just like the current implementation does, which your patch seems to change)

review: Needs Fixing
Riccardo Padovani (rpadovani) wrote :

> That looks closer to the visual spec, but needs tweaking still:
> - the More/Less button should have a strokeColor of "#5d5d5d"
> - it should be taller

Fixed

> - there should be an orange star to the left of the "bookmarks" section
> header

Added, but I had to rewrite all the header, let me know if it's still good

> - when expanding the bookmarks section, the "popular sites" one should be
> hidden (just like the current implementation does, which your patch seems to
> change)

I thought it was because when you click on "more" in the old implementation it shows all the bookmarks, and now it's 5 by 5. Anyway, I've restored it!

Thanks :-)

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Francis Ginther (fginther) wrote :

The jenkins node for the i386 build failed, I've restarted a new ci run.

PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:879
http://jenkins.qa.ubuntu.com/job/webbrowser-app-ci/1400/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-vivid-touch/1021/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-vivid/475/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/webbrowser-app-vivid-amd64-ci/158
    SUCCESS: http://jenkins.qa.ubuntu.com/job/webbrowser-app-vivid-armhf-ci/158
        deb: http://jenkins.qa.ubuntu.com/job/webbrowser-app-vivid-armhf-ci/158/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/webbrowser-app-vivid-i386-ci/158
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-vivid-mako/900/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/1019
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/1019/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/17507
    FAILURE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-vivid/385/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-amd64/580
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-amd64/580/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/webbrowser-app-ci/1400/rebuild

review: Needs Fixing (continuous-integration)
Olivier Tilloy (osomon) wrote :

This looks better, however I’m seeing some glitches, see the screenshots at http://people.canonical.com/~osomon/newTabRefactoring/ (taken in a vivid desktop VM).

 - capture.png shows how the new page looks like when there are no bookmarks
 - capture2.png shows how it looks like after adding a bookmark if it was initially created with no bookmarks

review: Needs Fixing
Riccardo Padovani (rpadovani) wrote :

As note, this could be related: https://bugreports.qt.io/browse/QTBUG-40271

Riccardo Padovani (rpadovani) wrote :

Ok, now should be ok.

Let me do a summary on this branch and on the new tab view, maybe there are better solutions of the one I thought.

So, designers want the behavior of the new tav view is like a listview with two sections, bookmarks and topsites.

The problem we have the model of bookmarks and of topsites are two different models.
A possible solution is to create a new model that is a merge of the others two, but could be have some problems, so I preferred to avoid this.

The solution already implemnented is to create a lisview with two delegate, a bookmark and a topsite.

Every delegate has another listview (or repeater) inside, with the related model.

The solution works well, but miss a the adding/removing of the section header when the last element is added/removed (as bug #1389605 reports).

My first thought was to use 'visible' element in the section header, but for some reason it doesn't work (at least on vivid). You can try just adding 'visible: false' in the section header.

So I choose to set height of the section header to 0 when there is nothing in the delegate, but I hit the upstream bug I reported in the comment above.

In the last commit I changed the approach: I add/remove section in the main listview when there aren't elements anymore.

I think this worsk well, but it doesn't scale well (if in a future we want to add a third section it's quite a mess) and the code is a bit ugly, but I haven't figured any better solution.

What do you think?

Olivier Tilloy (osomon) wrote :

Mmm… that does look ugly indeed, and is not scalable, but I understand the problem as you clearly expressed it.

In any case with this approach, there’s one thing that needs fixing:

    } else if (numberOfBookmarks == 1 &&
    […]
    } else if (numberOfTopSites == 1 &&

  you need to check for numberOf… > 0, as we cannot assume that only one bookmark or one topsite was added

Another approach we could take is to not use section delegates, i.e. implement the entire new tab view as a column inside a flickable. That would remove much of the complexity of the code, I think.

review: Needs Fixing
Riccardo Padovani (rpadovani) wrote :

We discussed about this on IRC, but I left a comment here as future reference.

I found a couple of bugs with limitedlistview:

- it doesn't emit a signal when the number of bookmarks change so if you have 5 bookmarks, open a new page, bookmark it, and then return to the new tab view, there isn't the button 'show more'. But there is if you open a *new* new tab

- If I place the limitedlistview in a column, how we discussed, there is a strange bug when you remove an element: let's say the model has 6 elements and the limit is 5: column contentHeight is limitedlistview.delegate * 5, as it should be. When I remove an element, the column contentHeight becomes limitedlistview.delegate * 4, while it should remain * 5. Since the limitedlistview isn't the only element in the column, forcing the contentHeight of the column is difficult and require a lot of ugly code

Riccardo Padovani (rpadovani) wrote :

Okay, I changed approach, what do you think? It doesn't hide anymore sections header. I think I removed all unused code, but could be there is some useless code.

Riccardo Padovani (rpadovani) wrote :

Actually, I also found a workaround for the LimitProxyModel bug (it doesn't emit the signal when a delegate is removed), so now it should works well in any case (add more than 5 elements, the show more button appears, fix bug #1442190).

Now we can do whatever we want with sections, we can also hide headers when we want.
And I think code is cleaner.

A thing I can do is adding a count property to BookmarksModel class to avoid to use LimitProxyModel to count number of object (but we still need it for the top sites section).

I propose an implementation I like, but we can discuss it now there isn't any of the bug I reported in previous comments.

What do you think?

Olivier Tilloy (osomon) wrote :

Let’s put this on hold for a week or two: Arthur is currently working on updating the design of the top sites section, and that will likely conflict with your changes (sorry I didn’t see that coming). I think most (if not all) of your changes will still apply, but there’s likely going to be a few conflicts to resolve.

I’d be curious to see if your changes fix bug #1444023 (which I just reported), by the way.

Riccardo Padovani (rpadovani) wrote :

> Let’s put this on hold for a week or two: Arthur is currently working on
> updating the design of the top sites section, and that will likely conflict
> with your changes (sorry I didn’t see that coming). I think most (if not all)
> of your changes will still apply, but there’s likely going to be a few
> conflicts to resolve.

No problem at all, I'll wait for the update :-)

> I’d be curious to see if your changes fix bug #1444023 (which I just
> reported), by the way.

Mhhh maybe, I need to test that

Riccardo Padovani (rpadovani) wrote :

Ok, I updated the MR, now is definitely cleaner and seems to solve in an elegant way all our bugs :-)

Olivier Tilloy (osomon) wrote :

I’m seeing a number of minor discrepancies with the visual spec (https://docs.google.com/presentation/d/1woHjO8K4iqyVZZlfQ4BXL0DhYbwkEmZ7wvcUhYzHDRk/edit?pli=1#slide=id.g34608d763_0170):

 - the star icon for the bookmarks section header is too big
 - the font size for the section headers should be small
 - the spacing above and below the section headers should be increased (there should be 1GU above and 1GU below the More/Less button)
 - the color of the horizontal line is not correct
 - there should be much more spacing between the bottom of the bookmarks section and the top of the top sites section

Also, a functional issue:
 - when I press the More button to expand the bookmarks section, I can scroll the top of the list view out of sight, even though the listview doesn’t completely fill the screen

review: Needs Fixing
Olivier Tilloy (osomon) wrote :

424 + Component.onCompleted: {
425 + if (model.hidden !== undefined && model.hidden) {
426 + height = 0;
427 + limit++;
428 + }
429 + }

The above is not correct. Use the TopSitesModel that filters out hidden entries instead.

review: Needs Fixing
Riccardo Padovani (rpadovani) wrote :

Hi Olivier,
thanks for your review!

I addressed all your comments about UI.

But I've troubles with the TopSitesModel component - I'm sure I'm missing something very easy, but I'm not able to figure what. At the moment no sites are displayed in the top sites area, but no error are printed on the console.

Could you take a look, please?

Olivier Tilloy (osomon) wrote :

This is getting there, nice job! And it now fixes 5 bugs (just added one to the list), you’re going to set a new record here :)

A few comments to address still:

If I have a number of bookmarks (more than 5), then open a new tab and start removing all bookmarks by swiping them to the right and confirming, they are correctly removed (and following bookmarks in the list show up), but the height of the bookmarks section is not updated, it doesn’t shrink when the total number of bookmarks gets < 5. Eventually, when only one bookmark is left, there is a big, almost empty bookmarks section, which looks akward. If I then open another new tab, the section’s height is correct.

There should be some spacing above the message that says that the user hasn’t visited any sites yet, it’s currently stuck to the section separator, which looks ugly. And the text color probably shouldn’t be plain black.

numberOfBookmarks and numberOfTopSites should check that bookmarksModel and historyModel are not null before trying to read their 'count' properties. This is because those models are instantiated asynchronously at startup. If a blank tab is restored at startup and for some reason the models take a long time to load, you will see (harmless) warnings in the console. Those are easy enough to fix that it’s worth doing it.

There should be some spacing between the bottom of the top sites list and the bottom of the window, to account for the bottom edge hint on mobile (I guess it’s ok to have that spacing unconditionally, even on desktop where it’s not strictly needed).

In the UrlList for top sites, why do you increase the limit in the onUrlRemoved handler?

Can you update the unit tests for the bookmarks model to check that the rowCountChanged signal is correctly emitted when expected, and that the value of the count is always as expected?

review: Needs Fixing
Riccardo Padovani (rpadovani) wrote :

> If I have a number of bookmarks (more than 5), then open a new tab and start
> removing all bookmarks by swiping them to the right and confirming, they are
> correctly removed (and following bookmarks in the list show up), but the
> height of the bookmarks section is not updated, it doesn’t shrink when the
> total number of bookmarks gets < 5. Eventually, when only one bookmark is
> left, there is a big, almost empty bookmarks section, which looks akward. If I
> then open another new tab, the section’s height is correct.

Actually, I wasn't able to reproduce this. Are you on vivid?

But I reproduced a similar issue: if I've 2 new tab view open, and I remove bookmarks in the 2nd one, the height in the 1st isn't updated. I fixed that, so I hope it fixes also the one you found.
Also, I found a similar issue with 'See more' - expand in one tab, remove bookmarks in the other one, go back - but I think I fixed also this one

> There should be some spacing above the message that says that the user hasn’t
> visited any sites yet, it’s currently stuck to the section separator, which
> looks ugly. And the text color probably shouldn’t be plain black.

Done, and now the text it's of the same color of 'See more' button

> numberOfBookmarks and numberOfTopSites should check that bookmarksModel and
> historyModel are not null before trying to read their 'count' properties. This
> is because those models are instantiated asynchronously at startup. If a blank
> tab is restored at startup and for some reason the models take a long time to
> load, you will see (harmless) warnings in the console. Those are easy enough
> to fix that it’s worth doing it.

Done

> There should be some spacing between the bottom of the top sites list and the
> bottom of the window, to account for the bottom edge hint on mobile (I guess
> it’s ok to have that spacing unconditionally, even on desktop where it’s not
> strictly needed).

Are you sure? Having a gray line at bottom is ugly, and the effect is very strange.
Take a look to Dekko or Reminders, they don't have any space at bottom, and looks good anyway

> In the UrlList for top sites, why do you increase the limit in the
> onUrlRemoved handler?

Legacy code from the first implementation, thanks, removed

> Can you update the unit tests for the bookmarks model to check that the
> rowCountChanged signal is correctly emitted when expected, and that the value
> of the count is always as expected?

Done

Olivier Tilloy (osomon) wrote :

> I fixed that, so I hope it fixes also the one you found.

It seems to fix the issue I was seeing indeed.

> Are you sure? Having a gray line at bottom is ugly, and the effect
> is very strange.

It shouldn’t be a gray line, just some additional empty space, IMHO. Anyway, I’m fine with keeping it like that for now, changing that in the future is easy if we want to.

Some additional remarks:

The item that contains the "You haven't visited any site yet" text doesn’t need to be rectangle, it could simply be a plain Item. This will avoid an ugly white background. Actually it doesn’t even need a container, the Text could be made larger, and its text vertically centered.

The (bookmarksModel.count !== undefined) and (historyModel.count !== undefined) checks are unnecessary: as long as bookmarksModel and historyModel are not null, it is safe to expect them to have a count property. If they don’t then something much bigger is broken anyway.

In onNumberOfBookmarksChanged, please check if (numberOfBookmarks <= 4) rather than for equality. If for some reason two bookmarks are removed in a batch, that number might go from 5 to 3 without stepping at 4, and the condition wouldn’t be verified.
Also, in the same if() block, you don’t need to check the value of seeMoreBookmarksView, you can inconditionally set it to false. If it was already false, nothing will happen.

Please do not add semi-colons at the end of lines for pure QML code (javascript is OK, although throughout the code base we tend not to use them).

Finally, one suggestion for performance optimization: I don’t know why we have one new tab view per webview in the first place, but it seems to me that one instance for all webviews would be enough. We would avoid lots of potential issues with updating one view and not the others, and of course it would perform better as we would instantiate fewer objects. Do you think you can do that change? In Browser.qml, around line 176, we would need an additional Loader, similar to the two loaders above (one is for the ErrorSheet and the other one for the InvalidCertificateErrorSheet).

review: Needs Fixing
Riccardo Padovani (rpadovani) wrote :

> > Are you sure? Having a gray line at bottom is ugly, and the effect
> > is very strange.
>
> It shouldn’t be a gray line, just some additional empty space, IMHO. Anyway,
> I’m fine with keeping it like that for now, changing that in the future is
> easy if we want to.

Okay, so I leave it as it is now

> Some additional remarks:
>
> The item that contains the "You haven't visited any site yet" text doesn’t
> need to be rectangle, it could simply be a plain Item. This will avoid an ugly
> white background. Actually it doesn’t even need a container, the Text could be
> made larger, and its text vertically centered.

Yap, right

> The (bookmarksModel.count !== undefined) and (historyModel.count !==
> undefined) checks are unnecessary: as long as bookmarksModel and historyModel
> are not null, it is safe to expect them to have a count property. If they
> don’t then something much bigger is broken anyway.

Okay

> In onNumberOfBookmarksChanged, please check if (numberOfBookmarks <= 4) rather
> than for equality. If for some reason two bookmarks are removed in a batch,
> that number might go from 5 to 3 without stepping at 4, and the condition
> wouldn’t be verified.

Right, it makes sense, thanks!

> Also, in the same if() block, you don’t need to check the value of
> seeMoreBookmarksView, you can inconditionally set it to false. If it was
> already false, nothing will happen.

Oh, right, thanks

> Please do not add semi-colons at the end of lines for pure QML code
> (javascript is OK, although throughout the code base we tend not to use them).

Okay, I'll try to avoid them

> Finally, one suggestion for performance optimization: I don’t know why we have
> one new tab view per webview in the first place, but it seems to me that one
> instance for all webviews would be enough. We would avoid lots of potential
> issues with updating one view and not the others, and of course it would
> perform better as we would instantiate fewer objects. Do you think you can do
> that change? In Browser.qml, around line 176, we would need an additional
> Loader, similar to the two loaders above (one is for the ErrorSheet and the
> other one for the InvalidCertificateErrorSheet).

Done, hope it's ok

Olivier Tilloy (osomon) wrote :

In NewTabView.qml, please s/Text/Label/, to ensure consistent theming.

newTabViewLoader is well placed, but there are a few issues with it:
 - it shouldn’t fill the parent, its anchors should be the same as the two other Loaders above it
 - please set its 'asynchronous' property to true, to ensure it doesn’t block rendering at startup
 - the NewTabView component should be loaded only when needed, not always (like it is now)

review: Needs Fixing
Riccardo Padovani (rpadovani) wrote :

Fixed all, I think

Olivier Tilloy (osomon) wrote :

When launching the app, previously open tabs are restored (default behaviour), and the webview for the current tab loads, but for a brief moment I can see the new tab view displayed. It shouldn’t be loaded at all in this case.

Conversely, when I open a new tab, the new tab view is not displayed.

Also, I’m seeing the following warning in the console at startup:

QQmlExpression: Expression file:///home/osomon/dev/phablet/browser/webbrowser-app/src/app/webbrowser/Browser.qml:194:30 depends on non-NOTIFYable properties:
    WebViewImpl_QMLTYPE_85_QML_146::restoreState

To address those issues, I think you need to do the following:
 - sourceComponent should not be set (no property binding)
 - have a Connections object that monitors when currentWebview changes and that does what was done prior to your changes in the Loader’s Component.onCompleted handler

One thing we’ll need to verify is that when switching from one empty tab to another empty tab, the new tab view isn’t deleted and re-created. We could easily write an autopilot test for that, but given that there are zero autopilot tests for this feature so far, let’s add some in a separate MR (I’ll do it).

Another minor remark: I think "anchors.fill: parent" inside the NewTabView instance is unneeded, since by default a component will be anchored to fill its parent loader.

review: Needs Fixing
Olivier Tilloy (osomon) wrote :

I’m still seeing the new tab view being briefly instantiated and destroyed right away at startup, when restoring a tab that isn’t empty. This needs fixing, as it adds unnecessarily to the startup time (and the view can actually be seen for a split second, which is confusing).

Otherwise I verified that when switching from one new tab to another new tab the view isn’t destroyed and re-created, that’s good.

review: Needs Fixing
Olivier Tilloy (osomon) wrote :

After some deep investigation, I finally nailed where the issue is: the problem is that oxide’s WebView.restoreState will always return an empty string (see http://bazaar.launchpad.net/~oxide-developers/oxide/oxide.trunk/view/head:/qt/quick/api/oxideqquickwebview.cc#L1606). So !currentWebview.restoreState will always be true. Instead we need to check the value of restoreState on the tab object.

Also one note that will simplify the code: instead of setting sourceComponent conditionally on the Loader, we can set it once, and then toggle the 'active' property of the loader.

I produced a patch that does those two things and should apply cleanly on top of your branch: http://pastebin.ubuntu.com/11261520/.

Olivier Tilloy (osomon) wrote :

That looks great now, thanks!

Note: there will be conflicts in Browser.qml and NewTabView.qml when merging into trunk after https://code.launchpad.net/~artmello/webbrowser-app/webbrowser-app-private_browsing/+merge/259388 lands (which I expect to happen today), so they will need to be resolved.

review: Approve
Olivier Tilloy (osomon) wrote :

The private browsing branch landed in trunk, and this MR now conflicts.

Riccardo Padovani (rpadovani) wrote :

Fixed (I hope, I don't have oxide updated on this pc and I'm not able to test it)

Olivier Tilloy (osomon) wrote :

8 -import Ubuntu.Components.Popups 1.0

Please do not remove that line, it is needed.

44 + sourceComponent: webviewimpl.incognito ? newPrivateTabViewComponent : newTabViewComponent

This should be browser.incognito, not webviewimpl.incognito.

151 - function resetFocus() {

This function is still being used, it shouldn’t be removed.

168 - var tab = tabComponent.createObject(tabContainer, {"initialUrl": url, 'incognito': browser.incognito})
169 + var tab = tabComponent.createObject(tabContainer, {"initialUrl": url})

This change should be reverted.

177 - for (var i = 0; i < publicTabsModel.count; ++i) {
178 - var tab = publicTabsModel.get(i)
179 + for (var i = 0; i < tabsModel.count; ++i) {
180 + var tab = tabsModel.get(i)

This change should be reverted.

review: Needs Fixing
900. By Riccardo Padovani on 2015-05-25

Fix wronge merge request

Riccardo Padovani (rpadovani) wrote :

Sorry for the wrong merge, I should have fixed all now

Olivier Tilloy (osomon) wrote :

Thanks, that’s better! Can you remove the anchors in the NewPrivateTabView instance in Browser.qml? They are not needed since the parent loader already defines its anchors (the loaded item will fill its parent loader by default).

901. By Riccardo Padovani on 2015-05-25

Remove useless anchors

Riccardo Padovani (rpadovani) wrote :

Done :-)

Olivier Tilloy (osomon) wrote :

This looks good again, thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/app/webbrowser/Browser.qml'
2--- src/app/webbrowser/Browser.qml 2015-05-22 19:21:10 +0000
3+++ src/app/webbrowser/Browser.qml 2015-05-25 07:08:15 +0000
4@@ -185,6 +185,64 @@
5 asynchronous: true
6 }
7
8+ Loader {
9+ id: newTabViewLoader
10+ anchors {
11+ fill: tabContainer
12+ topMargin: (chrome.state == "shown") ? chrome.height : 0
13+ }
14+
15+ // Avoid loading the new tab view if the webview is about to load
16+ // content. Since WebView.restoreState is not a notifyable property,
17+ // this can’t be achieved with a simple property binding.
18+ Connections {
19+ target: currentWebview
20+ onUrlChanged: {
21+ newTabViewLoader.active = false
22+ }
23+ }
24+ active: false
25+
26+ Connections {
27+ target: browser
28+ onCurrentWebviewChanged: {
29+ if (currentWebview) {
30+ var tab = tabsModel.currentTab
31+ newTabViewLoader.active = !tab.url.toString() && !tab.restoreState
32+ }
33+ }
34+ }
35+
36+ sourceComponent: browser.incognito ? newPrivateTabViewComponent : newTabViewComponent
37+
38+ Component {
39+ id: newTabViewComponent
40+
41+ NewTabView {
42+ historyModel: browser.historyModel
43+ bookmarksModel: browser.bookmarksModel
44+ onBookmarkClicked: {
45+ chrome.requestedUrl = url
46+ currentWebview.url = url
47+ currentWebview.forceActiveFocus()
48+ }
49+ onBookmarkRemoved: browser.bookmarksModel.remove(url)
50+ onHistoryEntryClicked: {
51+ chrome.requestedUrl = url
52+ currentWebview.url = url
53+ currentWebview.forceActiveFocus()
54+ }
55+ }
56+ }
57+
58+ Component {
59+ id: newPrivateTabViewComponent
60+
61+ NewPrivateTabView { }
62+ }
63+ asynchronous: true
64+ }
65+
66 SearchEngine {
67 id: currentSearchEngine
68 searchPaths: searchEnginesSearchPaths
69@@ -827,64 +885,6 @@
70 Component.onDestruction: bottomEdgeHint.forceShow = false
71 }
72 }
73-
74- Loader {
75- id: newTabViewLoader
76- anchors.fill: parent
77-
78- // Avoid loading the new tab view if the webview is about to load
79- // content. Since WebView.restoreState is not a notifyable property,
80- // this can’t be achieved with a simple property binding.
81- Component.onCompleted: {
82- if (!parent.url.toString() && !parent.restoreState) {
83- if (!webviewimpl.incognito) {
84- sourceComponent = newTabViewComponent
85- } else {
86- sourceComponent = newPrivateTabViewComponent
87- }
88- }
89- }
90- Connections {
91- target: newTabViewLoader.parent
92- onUrlChanged: newTabViewLoader.sourceComponent = null
93- }
94-
95- Component {
96- id: newTabViewComponent
97-
98- NewTabView {
99- anchors {
100- fill: parent
101- topMargin: (chrome.state == "shown") ? chrome.height : 0
102- }
103-
104- historyModel: browser.historyModel
105- bookmarksModel: browser.bookmarksModel
106- onBookmarkClicked: {
107- chrome.requestedUrl = url
108- currentWebview.url = url
109- currentWebview.forceActiveFocus()
110- }
111- onBookmarkRemoved: browser.bookmarksModel.remove(url)
112- onHistoryEntryClicked: {
113- chrome.requestedUrl = url
114- currentWebview.url = url
115- currentWebview.forceActiveFocus()
116- }
117- }
118- }
119-
120- Component {
121- id: newPrivateTabViewComponent
122-
123- NewPrivateTabView {
124- anchors {
125- fill: parent
126- topMargin: (chrome.state == "shown") ? chrome.height : 0
127- }
128- }
129- }
130- }
131 }
132 }
133 }
134@@ -1100,7 +1100,7 @@
135
136 Component {
137 id: leavePrivateModeDialog
138-
139+
140 LeavePrivateModeDialog {
141 id: dialogue
142 objectName: "leavePrivateModeDialog"
143
144=== modified file 'src/app/webbrowser/NewTabView.qml'
145--- src/app/webbrowser/NewTabView.qml 2015-05-19 14:53:12 +0000
146+++ src/app/webbrowser/NewTabView.qml 2015-05-25 07:08:15 +0000
147@@ -1,5 +1,5 @@
148 /*
149- * Copyright 2014 Canonical Ltd.
150+ * Copyright 2014-2015 Canonical Ltd.
151 *
152 * This file is part of webbrowser-app.
153 *
154@@ -26,149 +26,223 @@
155 id: newTabView
156
157 property QtObject bookmarksModel
158- property QtObject historyModel
159+ property alias historyModel: historyTimeframeModel.sourceModel
160
161 signal bookmarkClicked(url url)
162 signal bookmarkRemoved(url url)
163 signal historyEntryClicked(url url)
164
165+ TopSitesModel {
166+ id: topSitesModel
167+ sourceModel: HistoryTimeframeModel {
168+ id: historyTimeframeModel
169+ }
170+ }
171+
172 QtObject {
173 id: internal
174
175- property int bookmarksCountLimit: 5
176- property bool seeMoreBookmarksView: false
177- }
178-
179- ListModel {
180- id: sectionsModel
181-
182- Component.onCompleted: {
183- if (bookmarksListModel && bookmarksListModel.count !== 0)
184- sectionsModel.append({ section: "bookmarks" });
185- if (historyListModel && historyListModel.count !== 0 && !internal.seeMoreBookmarksView )
186- sectionsModel.append({ section: "topsites" });
187- }
188- }
189-
190- LimitProxyModel {
191- id: bookmarksListModel
192-
193- sourceModel: newTabView.bookmarksModel
194-
195- limit: internal.seeMoreBookmarksView ? -1 : internal.bookmarksCountLimit
196- }
197-
198- LimitProxyModel {
199- id: historyListModel
200-
201- sourceModel: TopSitesModel {
202- sourceModel: HistoryTimeframeModel {
203- sourceModel: newTabView.historyModel
204- // We only show sites visited on the last 60 days
205- start: {
206- var date = new Date()
207- date.setDate(date.getDate() - 60)
208- return date
209- }
210+ property bool seeMoreBookmarksView: bookmarksCountLimit > 4
211+ property int bookmarksCountLimit: Math.min(4, numberOfBookmarks)
212+ property int numberOfBookmarks: bookmarksModel ? bookmarksModel.count : 0
213+ property int numberOfTopSites: historyModel ? historyModel.count : 0
214+
215+ // Force the topsites section to reappear when remove a bookmark while
216+ // the bookmarks list is expanded and there aren't anymore > 5
217+ // bookmarks
218+ onNumberOfBookmarksChanged: {
219+ if (numberOfBookmarks <= 4) {
220+ seeMoreBookmarksView = false
221 }
222 }
223-
224- limit: 10
225 }
226
227 Rectangle {
228- id: newTabBackground
229 anchors.fill: parent
230 color: "#f6f6f6"
231 }
232
233- ListView {
234- id: newTabListView
235+ Flickable {
236 anchors.fill: parent
237-
238- model: sectionsModel
239-
240- delegate: Loader {
241- anchors {
242- left: parent.left
243- right: parent.right
244- margins: units.gu(2)
245- }
246-
247- width: parent.width
248- height: children.height
249-
250- sourceComponent: modelData == "bookmarks" ? bookmarksComponent : topSitesComponent
251- }
252-
253- section.property: "section"
254- section.delegate: Rectangle {
255- anchors {
256- left: parent.left
257- right: parent.right
258- }
259-
260- height: sectionHeader.height + units.gu(1)
261-
262- opacity: section == "topsites" && internal.seeMoreBookmarksView ? 0.0 : 1.0
263-
264- color: newTabBackground.color
265-
266- Behavior on opacity { UbuntuNumberAnimation {} }
267-
268- ListItem.Header {
269- id: sectionHeader
270-
271- text: {
272- if (section == "bookmarks") {
273- return i18n.tr("Bookmarks")
274- } else if (section == "topsites") {
275- return i18n.tr("Top sites")
276- }
277- }
278- }
279- }
280-
281- section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
282- }
283-
284- Component {
285- id: bookmarksComponent
286-
287- UrlsList {
288- id: bookmarksList
289-
290- width: parent.width
291-
292- model: bookmarksListModel
293-
294- footerLabelText: internal.seeMoreBookmarksView ? i18n.tr("see less") : i18n.tr("see more")
295- footerLabelVisible: bookmarksListModel.unlimitedCount > internal.bookmarksCountLimit
296-
297- onUrlClicked: newTabView.bookmarkClicked(url)
298- onUrlRemoved: newTabView.bookmarkRemoved(url)
299- onFooterLabelClicked: internal.seeMoreBookmarksView = !internal.seeMoreBookmarksView
300- }
301- }
302-
303- Component {
304- id: topSitesComponent
305-
306- UrlsList {
307- objectName: "topSitesList"
308-
309- width: parent.width
310- opacity: internal.seeMoreBookmarksView ? 0.0 : 1.0
311-
312- height: opacity == 0.0 ? 0 : childrenRect.height
313- model: historyListModel
314-
315- footerLabelVisible: false
316-
317- onUrlClicked: newTabView.historyEntryClicked(url)
318- onUrlRemoved: newTabView.historyModel.hide(url)
319-
320- Behavior on opacity { UbuntuNumberAnimation {} }
321+ contentHeight: internal.seeMoreBookmarksView ?
322+ bookmarksColumn.height + units.gu(6) :
323+ contentColumn.height
324+
325+ Column {
326+ id: contentColumn
327+ anchors {
328+ left: parent.left
329+ leftMargin: units.gu(1.5)
330+ right: parent.right
331+ rightMargin: units.gu(1.5)
332+ }
333+ height: childrenRect.height
334+
335+ Row {
336+ height: units.gu(6)
337+ anchors { left: parent.left; right: parent.right }
338+ spacing: units.gu(1.5)
339+
340+ Icon {
341+ id: starredIcon
342+ color: "#dd4814"
343+ name: "starred"
344+
345+ height: units.gu(2)
346+ width: height
347+
348+ anchors {
349+ leftMargin: units.gu(1)
350+ topMargin: units.gu(1)
351+ verticalCenter: moreButton.verticalCenter
352+ }
353+ }
354+
355+ Label {
356+ width: parent.width - starredIcon.width - moreButton.width - units.gu(3)
357+ anchors.verticalCenter: moreButton.verticalCenter
358+
359+ text: i18n.tr("Bookmarks")
360+ fontSize: "small"
361+ }
362+
363+ Button {
364+ id: moreButton
365+ height: parent.height - units.gu(2)
366+
367+ anchors { top: parent.top; topMargin: units.gu(1) }
368+
369+ strokeColor: "#5d5d5d"
370+
371+ visible: internal.numberOfBookmarks > 4
372+
373+ text: internal.bookmarksCountLimit >= internal.numberOfBookmarks
374+ ? i18n.tr("Less") : i18n.tr("More")
375+
376+ onClicked: {
377+ internal.numberOfBookmarks > internal.bookmarksCountLimit ?
378+ internal.bookmarksCountLimit += 5:
379+ internal.bookmarksCountLimit = 4
380+ }
381+ }
382+ }
383+
384+ Rectangle {
385+ height: units.gu(0.1)
386+ anchors { left: parent.left; right: parent.right }
387+ color: "#d3d3d3"
388+ }
389+
390+ Column {
391+ id: bookmarksColumn
392+ anchors {
393+ left: parent.left
394+ leftMargin: units.gu(-1.5)
395+ right: parent.right
396+ }
397+
398+ // Force the height to be updated when bookmarks are removed
399+ // in another new tab
400+ height: units.gu(5) * (Math.min(internal.bookmarksCountLimit, internal.numberOfBookmarks) + 1)
401+ spacing: 0
402+
403+ UrlDelegate {
404+ id: homepageBookmark
405+ anchors {
406+ left: parent.left
407+ right: parent.right
408+ }
409+ height: units.gu(5)
410+
411+ title: i18n.tr('Homepage')
412+
413+ url: settings.homepage
414+ onItemClicked: newTabView.bookmarkClicked(url)
415+ }
416+
417+ UrlsList {
418+ id: bookmarksList
419+ anchors {
420+ left: parent.left
421+ right: parent.right
422+ }
423+
424+ spacing: 0
425+ limit: internal.bookmarksCountLimit
426+
427+ model: newTabView.bookmarksModel
428+
429+ onUrlClicked: newTabView.bookmarkClicked(url)
430+ onUrlRemoved: newTabView.bookmarkRemoved(url)
431+ }
432+ }
433+
434+ Rectangle {
435+ height: units.gu(6)
436+ anchors {
437+ left: parent.left
438+ right: parent.right
439+ }
440+ color: "#f6f6f6"
441+
442+ Label {
443+ anchors {
444+ left: parent.left
445+ right: parent.right
446+ bottom: parent.bottom
447+ bottomMargin: units.gu(1)
448+ }
449+
450+ opacity: internal.seeMoreBookmarksView ? 0.0 : 1.0
451+ Behavior on opacity { UbuntuNumberAnimation {} }
452+
453+ text: i18n.tr("Top sites")
454+ fontSize: "small"
455+ }
456+ }
457+
458+ Rectangle {
459+ height: units.gu(0.1)
460+ anchors { left: parent.left; right: parent.right }
461+ color: "#d3d3d3"
462+
463+ opacity: internal.seeMoreBookmarksView ? 0.0 : 1.0
464+ Behavior on opacity { UbuntuNumberAnimation {} }
465+ }
466+
467+ Label {
468+ height: units.gu(11)
469+ anchors {
470+ left: parent.left
471+ right: parent.right
472+ }
473+ visible: internal.numberOfTopSites === 0
474+
475+ horizontalAlignment: Text.AlignHCenter
476+ verticalAlignment: Text.AlignVCenter
477+
478+ text: i18n.tr("You haven't visited any site yet")
479+ color: "#5d5d5d"
480+ }
481+
482+ UrlsList {
483+ anchors {
484+ left: parent.left
485+ leftMargin: units.gu(-1.5)
486+ right: parent.right
487+ }
488+
489+ opacity: internal.seeMoreBookmarksView ? 0.0 : 1.0
490+ Behavior on opacity { UbuntuNumberAnimation {} }
491+
492+ limit: 10
493+ spacing: 0
494+
495+ model: topSitesModel
496+
497+ onUrlClicked: newTabView.historyEntryClicked(url)
498+ onUrlRemoved: newTabView.historyModel.hide(url)
499+ }
500 }
501 }
502 }
503
504=== modified file 'src/app/webbrowser/UrlsList.qml'
505--- src/app/webbrowser/UrlsList.qml 2015-04-10 15:41:57 +0000
506+++ src/app/webbrowser/UrlsList.qml 2015-05-25 07:08:15 +0000
507@@ -1,5 +1,5 @@
508 /*
509- * Copyright 2014 Canonical Ltd.
510+ * Copyright 2014-2015 Canonical Ltd.
511 *
512 * This file is part of webbrowser-app.
513 *
514@@ -22,12 +22,10 @@
515 Column {
516 id: urlsList
517 property alias model: urlsListRepeater.model
518- property alias footerLabelText: footerLabel.text
519- property alias footerLabelVisible: footerLabel.visible
520+ property int limit
521
522 signal urlClicked(url url)
523 signal urlRemoved(url url)
524- signal footerLabelClicked()
525
526 spacing: units.gu(1)
527
528@@ -37,70 +35,76 @@
529 id: urlsListRepeater
530 property var _currentSwipedItem: null
531
532- delegate: UrlDelegate{
533- id: urlDelegate
534- width: urlsList.width
535- height: units.gu(5)
536-
537- icon: model.icon
538- title: model.title ? model.title : model.url
539- url: model.url
540-
541- onItemClicked: urlClicked(model.url)
542-
543- property var removalAnimation
544- function remove() {
545- removalAnimation.start()
546- }
547-
548- onSwippingChanged: {
549- urlsListRepeater._updateSwipeState(urlDelegate)
550- }
551-
552- onSwipeStateChanged: {
553- urlsListRepeater._updateSwipeState(urlDelegate)
554- }
555-
556- leftSideAction: Action {
557- iconName: "delete"
558- text: i18n.tr("Delete")
559- onTriggered: {
560- urlDelegate.remove()
561- }
562- }
563-
564- ListView.onRemove: ScriptAction {
565- script: {
566- if (urlsListRepeater._currentSwipedItem === urlDelegate) {
567- urlsListRepeater._currentSwipedItem = null
568- }
569- }
570- }
571-
572- removalAnimation: SequentialAnimation {
573- alwaysRunToEnd: true
574-
575- PropertyAction {
576- target: urlDelegate
577- property: "ListView.delayRemove"
578- value: true
579- }
580-
581- UbuntuNumberAnimation {
582- target: urlDelegate
583- property: "height"
584- to: 0
585- }
586-
587- PropertyAction {
588- target: urlDelegate
589- property: "ListView.delayRemove"
590- value: false
591- }
592-
593- ScriptAction {
594- script: {
595- urlRemoved(model.url)
596+ delegate: Loader {
597+ sourceComponent: (index < limit) ? realDelegate : undefined
598+ Component {
599+ id: realDelegate
600+ UrlDelegate{
601+ id: urlDelegate
602+ width: urlsList.width
603+ height: units.gu(5)
604+
605+ icon: model.icon
606+ title: model.title ? model.title : model.url
607+ url: model.url
608+
609+ onItemClicked: urlClicked(model.url)
610+
611+ property var removalAnimation
612+ function remove() {
613+ removalAnimation.start()
614+ }
615+
616+ onSwippingChanged: {
617+ urlsListRepeater._updateSwipeState(urlDelegate)
618+ }
619+
620+ onSwipeStateChanged: {
621+ urlsListRepeater._updateSwipeState(urlDelegate)
622+ }
623+
624+ leftSideAction: Action {
625+ iconName: "delete"
626+ text: i18n.tr("Delete")
627+ onTriggered: {
628+ urlDelegate.remove()
629+ }
630+ }
631+
632+ ListView.onRemove: ScriptAction {
633+ script: {
634+ if (urlsListRepeater._currentSwipedItem === urlDelegate) {
635+ urlsListRepeater._currentSwipedItem = null
636+ }
637+ }
638+ }
639+
640+ removalAnimation: SequentialAnimation {
641+ alwaysRunToEnd: true
642+
643+ PropertyAction {
644+ target: urlDelegate
645+ property: "ListView.delayRemove"
646+ value: true
647+ }
648+
649+ UbuntuNumberAnimation {
650+ target: urlDelegate
651+ property: "height"
652+ to: 0
653+ }
654+
655+ PropertyAction {
656+ target: urlDelegate
657+ property: "ListView.delayRemove"
658+ value: false
659+ }
660+
661+ ScriptAction {
662+ script: {
663+ urlRemoved(model.url)
664+ }
665+ }
666 }
667 }
668 }
669@@ -124,29 +128,4 @@
670 }
671 }
672 }
673-
674- Item {
675- width: parent.width
676- height: footerLabel.visible ? footerLabel.height + units.gu(6) : units.gu(3)
677-
678- MouseArea {
679- anchors.centerIn: footerLabel
680-
681- width: footerLabel.width + units.gu(4)
682- height: footerLabel.height + units.gu(4)
683-
684- enabled: footerLabel.visible
685-
686- onClicked: footerLabelClicked()
687- }
688-
689- Label {
690- id: footerLabel
691- anchors.centerIn: parent
692-
693- visible: true
694-
695- font.bold: true
696- }
697- }
698 }
699
700=== modified file 'src/app/webbrowser/bookmarks-model.cpp'
701--- src/app/webbrowser/bookmarks-model.cpp 2014-07-30 15:52:11 +0000
702+++ src/app/webbrowser/bookmarks-model.cpp 2015-05-25 07:08:15 +0000
703@@ -63,6 +63,7 @@
704 createOrAlterDatabaseSchema();
705 endResetModel();
706 populateFromDatabase();
707+ Q_EMIT rowCountChanged();
708 }
709
710 void BookmarksModel::createOrAlterDatabaseSchema()
711@@ -203,6 +204,7 @@
712 endInsertRows();
713 Q_EMIT added(url);
714 insertNewEntryInDatabase(entry);
715+ Q_EMIT rowCountChanged();
716 }
717 }
718
719@@ -236,7 +238,7 @@
720 endRemoveRows();
721 Q_EMIT removed(url);
722 removeExistingEntryFromDatabase(url);
723-
724+ Q_EMIT rowCountChanged();
725 return;
726 } else {
727 index++;
728
729=== modified file 'src/app/webbrowser/bookmarks-model.h'
730--- src/app/webbrowser/bookmarks-model.h 2014-07-30 15:52:11 +0000
731+++ src/app/webbrowser/bookmarks-model.h 2015-05-25 07:08:15 +0000
732@@ -33,6 +33,7 @@
733 Q_OBJECT
734
735 Q_PROPERTY(QString databasePath READ databasePath WRITE setDatabasePath NOTIFY databasePathChanged)
736+ Q_PROPERTY(int count READ rowCount NOTIFY rowCountChanged)
737
738 Q_ENUMS(Roles)
739
740@@ -63,6 +64,7 @@
741 void databasePathChanged() const;
742 void added(const QUrl& url) const;
743 void removed(const QUrl& url) const;
744+ void rowCountChanged();
745
746 private:
747 QSqlDatabase m_database;
748
749=== modified file 'tests/unittests/bookmarks-model/tst_BookmarksModelTests.cpp'
750--- tests/unittests/bookmarks-model/tst_BookmarksModelTests.cpp 2014-07-30 15:52:11 +0000
751+++ tests/unittests/bookmarks-model/tst_BookmarksModelTests.cpp 2015-05-25 07:08:15 +0000
752@@ -179,6 +179,21 @@
753 model->setDatabasePath(fileName);
754 QCOMPARE(model->rowCount(), 2);
755 }
756+
757+ void shouldCountNumberOfEntries()
758+ {
759+ QSignalSpy spyCount(model, SIGNAL(rowCountChanged()));
760+ QCOMPARE(model->property("count").toInt(), 0);
761+ model->add(QUrl("http://example.org/"), "Example Domain", QUrl());
762+ QCOMPARE(model->property("count").toInt(), 1);
763+ QCOMPARE(spyCount.count(), 1);
764+ model->add(QUrl("http://example.com/"), "Example Domain", QUrl());
765+ QCOMPARE(model->property("count").toInt(), 2);
766+ QCOMPARE(spyCount.count(), 2);
767+ model->remove(QUrl("http://example.com/"));
768+ QCOMPARE(model->property("count").toInt(), 1);
769+ QCOMPARE(spyCount.count(), 3);
770+ }
771 };
772
773 QTEST_MAIN(BookmarksModelTests)

Subscribers

People subscribed via source and target branches

to status/vote changes: