Merge lp:~artmello/webbrowser-app/webbrowser-app-bookmarks_top_sites into lp:webbrowser-app

Proposed by Arthur Mello
Status: Merged
Approved by: Olivier Tilloy
Approved revision: 592
Merged at revision: 602
Proposed branch: lp:~artmello/webbrowser-app/webbrowser-app-bookmarks_top_sites
Merge into: lp:webbrowser-app
Diff against target: 1331 lines (+1080/-12)
19 files modified
po/webbrowser-app.pot (+19/-6)
src/app/webbrowser/BookmarksList.qml (+75/-0)
src/app/webbrowser/Browser.qml (+29/-4)
src/app/webbrowser/CMakeLists.txt (+2/-0)
src/app/webbrowser/NewTabView.qml (+176/-0)
src/app/webbrowser/UrlDelegate.qml (+73/-0)
src/app/webbrowser/history-byvisits-model.cpp (+50/-0)
src/app/webbrowser/history-byvisits-model.h (+43/-0)
src/app/webbrowser/limit-proxy-model.cpp (+268/-0)
src/app/webbrowser/limit-proxy-model.h (+70/-0)
src/app/webbrowser/webbrowser-app.cpp (+4/-0)
tests/autopilot/webbrowser_app/emulators/browser.py (+6/-0)
tests/autopilot/webbrowser_app/tests/__init__.py (+8/-2)
tests/autopilot/webbrowser_app/tests/test_tabs.py (+10/-0)
tests/unittests/CMakeLists.txt (+2/-0)
tests/unittests/history-byvisits-model/CMakeLists.txt (+6/-0)
tests/unittests/history-byvisits-model/tst_HistoryByVisitsModelTests.cpp (+89/-0)
tests/unittests/limit-proxy-model/CMakeLists.txt (+6/-0)
tests/unittests/limit-proxy-model/tst_LimitProxyModelTests.cpp (+144/-0)
To merge this branch: bzr merge lp:~artmello/webbrowser-app/webbrowser-app-bookmarks_top_sites
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Olivier Tilloy Approve
Review via email: mp+224592@code.launchpad.net

Commit message

Add the Top Sites Sheet, showing Bookmarks and most visited URLs.
It is displayed when an empty new tab is created.

Description of the change

Add the Top Sites Sheet, showing Bookmarks and most visited URLs.
It is displaced when an empty new Tab is created

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Olivier Tilloy (osomon) wrote :

Can you back out the changes to the bookmarks model? Ugo has been working on a branch to add the timestamp separately (see https://code.launchpad.net/~phablet-team/webbrowser-app/webbrowser-app-bookmark-timestamp/+merge/224117). Additionally, I don’t think we’ll need BookmarksChronologicalModel as we will ensure the original BookmarksModel is always sorted by creation date (AFAIK there is no use case for sorting them otherwise).

So until we merge Ugo’s branch (which needs some rework that I’ll probably take on), bookmarks would not be ordered by creation date in the new tab page, but that shouldn’t be too bad for a start.

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

Functional issue: I have two bookmarks in my database, and when opening the new tab page, there is a "see more" link, which, when clicked, expands the bookmarks section to take up the whole screen, thus hiding the history entries. I think the "see more" link should be visible only when there are actually more bookmarks to display.

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

Functional regression: if I bookmark new pages (the verify that they appear in the bookmarks view), then exit the browser and launch it again, the new bookmarks haven’t been stored in the database.

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

As pointed out in a previous review round: the "see more/see less" action should probably trigger a smooth transition.

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

The copyright notice for tests/unittests/bookmarks-chronological-model/tst_BookmarksChronologicalModelTests.cpp should be dated 2014.

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

What purpose does the TopSitesSheet component serve? It seems all it does is wrap a TopSitesView and proxy some of its properties and signals, so it looks like it could be removed.

On a related note, the naming of TopSitesView is slightly confusing, as it contains both top sites and bookmarks. A better name would probably be "NewTabPage" or "NewTabView".

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

It looks like this could be simplified:

249 + start: {
250 + var date = new Date()
251 + date.setDate(date.getDate() - 60)
252 + date.setHours(0)
253 + date.setMinutes(0)
254 + date.setSeconds(0)
255 + date.setMilliseconds(0)
256 + return date
257 + }
258 + end: {
259 + var date = new Date()
260 + date.setDate(date.getDate())
261 + date.setHours(23)
262 + date.setMinutes(59)
263 + date.setSeconds(59)
264 + date.setMilliseconds(999)
265 + return date
266 + }

For start, you don’t need to call setHours, setMinutes, setSeconds and setMilliseconds.
For end, you can just return a new Date(), i.e.: "end: new Date()"

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

As already discussed, in the current implementation TopSitesSheet is always instantiated. Instead, it should be loaded on demand (encapsulating it in a Component, or using a Loader), only when the user requests a new tab. It should probably be destroyed as soon as the URL of the current webview becomes non empty, and instantiated again when switching to a webview with an empty URL.

564. By Arthur Mello

Merge with trunk

565. By Arthur Mello

Back out the changes to the bookmarks model.
We are not using an ordered sort model anymore.

Revision history for this message
Arthur Mello (artmello) wrote :

> Can you back out the changes to the bookmarks model? Ugo has been working on a
> branch to add the timestamp separately (see https://code.launchpad.net
> /~phablet-team/webbrowser-app/webbrowser-app-bookmark-
> timestamp/+merge/224117). Additionally, I don’t think we’ll need
> BookmarksChronologicalModel as we will ensure the original BookmarksModel is
> always sorted by creation date (AFAIK there is no use case for sorting them
> otherwise).
>
> So until we merge Ugo’s branch (which needs some rework that I’ll probably
> take on), bookmarks would not be ordered by creation date in the new tab page,
> but that shouldn’t be too bad for a start.

Fixed on revision 565

566. By Arthur Mello

Make "see more" link visible only when there are actually more bookmarks to display

Revision history for this message
Arthur Mello (artmello) wrote :

> Functional issue: I have two bookmarks in my database, and when opening the
> new tab page, there is a "see more" link, which, when clicked, expands the
> bookmarks section to take up the whole screen, thus hiding the history
> entries. I think the "see more" link should be visible only when there are
> actually more bookmarks to display.

Fixed on revision 566

Revision history for this message
Arthur Mello (artmello) wrote :

> Functional regression: if I bookmark new pages (the verify that they appear in
> the bookmarks view), then exit the browser and launch it again, the new
> bookmarks haven’t been stored in the database.

I was not able to reproduce the problem after rev 565, when we removed the changes on bookmarks model. Probably there was something wrong with the changes on bookmarks model or on bookmarks chronological model. If you still face the issue, please, let me know.

567. By Arthur Mello

Make the "see more/see less" action trigger a transition

Revision history for this message
Arthur Mello (artmello) wrote :

> As pointed out in a previous review round: the "see more/see less" action
> should probably trigger a smooth transition.

Fixed on revision 567

Revision history for this message
Arthur Mello (artmello) wrote :

> The copyright notice for tests/unittests/bookmarks-chronological-
> model/tst_BookmarksChronologicalModelTests.cpp should be dated 2014.

The unittest was removed since the bookmarks chronological model is not used anymore

568. By Arthur Mello

Remove unnecessary TopSitesSheet
Rename TopSitesView to NewTabView

Revision history for this message
Arthur Mello (artmello) wrote :

> What purpose does the TopSitesSheet component serve? It seems all it does is
> wrap a TopSitesView and proxy some of its properties and signals, so it looks
> like it could be removed.
>
> On a related note, the naming of TopSitesView is slightly confusing, as it
> contains both top sites and bookmarks. A better name would probably be
> "NewTabPage" or "NewTabView".

Fixed on revision 568

569. By Arthur Mello

Simplify bookmarks transition

570. By Arthur Mello

Simplify start and end date calculation for timeframe history model

Revision history for this message
Arthur Mello (artmello) wrote :

> It looks like this could be simplified:
>
> 249 + start: {
> 250 + var date = new Date()
> 251 + date.setDate(date.getDate() - 60)
> 252 + date.setHours(0)
> 253 + date.setMinutes(0)
> 254 + date.setSeconds(0)
> 255 + date.setMilliseconds(0)
> 256 + return date
> 257 + }
> 258 + end: {
> 259 + var date = new Date()
> 260 + date.setDate(date.getDate())
> 261 + date.setHours(23)
> 262 + date.setMinutes(59)
> 263 + date.setSeconds(59)
> 264 + date.setMilliseconds(999)
> 265 + return date
> 266 + }
>
> For start, you don’t need to call setHours, setMinutes, setSeconds and
> setMilliseconds.
> For end, you can just return a new Date(), i.e.: "end: new Date()"

Fixed on revision 570

571. By Arthur Mello

Make NewTabView be loaded on demand using a Loader

Revision history for this message
Arthur Mello (artmello) wrote :

> As already discussed, in the current implementation TopSitesSheet is always
> instantiated. Instead, it should be loaded on demand (encapsulating it in a
> Component, or using a Loader), only when the user requests a new tab. It
> should probably be destroyed as soon as the URL of the current webview becomes
> non empty, and instantiated again when switching to a webview with an empty
> URL.

Fixed on revision 571

Revision history for this message
Arthur Mello (artmello) wrote :

> As already discussed, in the current implementation TopSitesSheet is always
> instantiated. Instead, it should be loaded on demand (encapsulating it in a
> Component, or using a Loader), only when the user requests a new tab. It
> should probably be destroyed as soon as the URL of the current webview becomes
> non empty, and instantiated again when switching to a webview with an empty
> URL.

Fixed on revision 571

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
572. By Arthur Mello

Fix AP tests. Removed the validation for webview visible after closing Activity View.
Sometimes, the New Tab view will be visible after Activity View and not the webview

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Olivier Tilloy (osomon) wrote :

There is a trivial conflict in tests/autopilot/webbrowser_app/emulators/browser.py when merging into the latest trunk, can you please resolve it?

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

For animations and behaviours, you’re using a few NumberAnimation with explicit durations. Please consider using UbuntuNumberAnimation (http://developer.ubuntu.com/api/qml/sdk-14.10/Ubuntu.Components.UbuntuNumberAnimation/) instead.

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

If I open a new empty tab, then type in a URL in the address bar and validate, I’m getting the following warning:

    WARNING: Trying to pop an empty PageStack. Ignoring.

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

The "see more/see less" transition is looking better, but still not perfect IMHO.

When I click "see more", I’m seeing the "top sites" section being abruptly shifted downwards, then fade away. It should probably move downwards smoothly and fade away at the same time.

When I click "see less", the extra bookmarks disappear instantly. Ideally they should fade away progressively as the footer is moved upwards.

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

Is there a need for the newTabViewVisible property? If so, couldn’t it just be defined as "newTabViewLoader.status === Loader.Ready" ?

573. By Arthur Mello

Merge with trunk

Revision history for this message
Arthur Mello (artmello) wrote :

> There is a trivial conflict in
> tests/autopilot/webbrowser_app/emulators/browser.py when merging into the
> latest trunk, can you please resolve it?

Fixed on revision 573

574. By Arthur Mello

Using UbuntuNumberAnimation instead of NumberAnimation

Revision history for this message
Arthur Mello (artmello) wrote :

> For animations and behaviours, you’re using a few NumberAnimation with
> explicit durations. Please consider using UbuntuNumberAnimation (http://develo
> per.ubuntu.com/api/qml/sdk-14.10/Ubuntu.Components.UbuntuNumberAnimation/)
> instead.

Fixed on revision 574

575. By Arthur Mello

Move newTabViewLoader to be a child of webviewContainer and stop using pageStack

Revision history for this message
Arthur Mello (artmello) wrote :

> If I open a new empty tab, then type in a URL in the address bar and validate,
> I’m getting the following warning:
>
> WARNING: Trying to pop an empty PageStack. Ignoring.

Fixed on revision 575

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Arthur Mello (artmello) wrote :

> Is there a need for the newTabViewVisible property? If so, couldn’t it just be
> defined as "newTabViewLoader.status === Loader.Ready" ?

The property was removed since it is not more nece

Revision history for this message
Arthur Mello (artmello) wrote :

> The "see more/see less" transition is looking better, but still not perfect
> IMHO.
>
> When I click "see more", I’m seeing the "top sites" section being abruptly
> shifted downwards, then fade away. It should probably move downwards smoothly
> and fade away at the same time.

The "bookmarks" and the "top sites" lists are inside a ListView. I tried to animate with Behavior on Y the delegate but with no success. My idea was to animate the move from the "top sites" component. Any suggestion?

> When I click "see less", the extra bookmarks disappear instantly. Ideally they
> should fade away progressively as the footer is moved upwards.

Right now the bookmarks list is a Repeater, not a ListView, inside a Column. I am animating the move transition of the Column. That is why we have the effect when we are "expanding" the Repeater. But to use the same effect before removing the entries of the Repeater I would need to use something like the delayRemove from the ListView. I didn't find a way to do that on a Repeater. Any suggestion? Should I change the Repeater to a ListView?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Olivier Tilloy (osomon) wrote :

114 + onUrlChanged: {
119 + function loadNewTabView() {
125 + function unloadNewTabView() {

There is a more declarative way of writing this: have a Component that contains a NewTabView inside the loader, and set the 'sourceComponent' property of the loader to "parent.url ? undefined : newTabViewComponent".

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

Regarding the transitions/animations, they’re not perfect, but I think they’ll do for now, we can tweak them when we implement the new design.

I’m seeing a new issue though (or maybe it was there already and I hadn’t spotted it before): on desktop, if I have more than 5 bookmarks and more than 5 top sites, only the first row of the top sites category can be seen, I can see the top of the second row but I cannot drag it into view, it always springs back to the first row.

review: Needs Fixing
576. By Arthur Mello

Merge with trunk

577. By Arthur Mello

Improve code for newTabView loader

Revision history for this message
Arthur Mello (artmello) wrote :

> 114 + onUrlChanged: {
> 119 + function loadNewTabView() {
> 125 + function unloadNewTabView() {
>
> There is a more declarative way of writing this: have a Component that
> contains a NewTabView inside the loader, and set the 'sourceComponent'
> property of the loader to "parent.url ? undefined : newTabViewComponent".

Fixed on revision 577

578. By Arthur Mello

Stop changing the height property of the topSites Flow since that results on strange behavior

Revision history for this message
Arthur Mello (artmello) wrote :

> Regarding the transitions/animations, they’re not perfect, but I think they’ll
> do for now, we can tweak them when we implement the new design.
>
> I’m seeing a new issue though (or maybe it was there already and I hadn’t
> spotted it before): on desktop, if I have more than 5 bookmarks and more than
> 5 top sites, only the first row of the top sites category can be seen, I can
> see the top of the second row but I cannot drag it into view, it always
> springs back to the first row.

Fixed on revision 578

579. By Arthur Mello

Move newTabView inside a Component

580. By Arthur Mello

Fix url check to see if should load newTabComponent

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Olivier Tilloy (osomon) wrote :
Revision history for this message
Olivier Tilloy (osomon) wrote :

Huh, nevermind that last comment, I was looking at a previous CI run.

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

A tiny remark on semantics:

943 + def ensure_new_tab_view_visible(self):
947 + def ensure_new_tab_view_eventually_hidden(self):

Those two methods should be renamed:

    assert_new_tab_view_eventually_visible()
    assert_new_tab_view_eventually_hidden()

Methods that start with "ensure_" actually perform some actions to ensure that the result is achieved, as opposed to methods that start with "assert_" that merely wait until the condition is verified.

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

971 + def test_hidden_new_tab_view(self):

I would rename the above method "test_new_tab_view()".

We could do with more autopilot tests (opening a bookmarked site, opening a top visited site), but this doesn’t have to be implemented now.

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

1197 + void shouldLimitEntries()
1198 + {
1199 + model->setLimit(2);
1200 +
1201 + history->add(QUrl("http://example1.org/"), "Example 1 Domain", QUrl());
1202 + QTest::qWait(1001);
1203 + history->add(QUrl("http://example2.org/"), "Example 2 Domain", QUrl());
1204 + QTest::qWait(1001);
1205 + history->add(QUrl("http://example3.org/"), "Example 3 Domain", QUrl());
1206 +
1207 + QCOMPARE(model->rowCount(), 2);
1208 + QCOMPARE(model->unlimitedRowCount(), 3);
1209 + }

It would be useful to test additional conditions:
 - checking that the initial value of the limit is -1
 - setting the limit to -1
 - setting the limit to a number greater than the number of entries
 - setting the limit after populating the history model, to verify that it is correctly updated

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

Can you add a comment to state that LimitProxyModel was copied from unity8’s source tree?
Did you have to modify it, or is it a verbatim copy?

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

386 + property alias favIcon: favIcon.source
387 + property alias url: url.text
388 + property alias label: label.text

Can the 'favIcon' property be renamed 'icon', for consistency across the codebase?
Can the 'label' property be renamed 'title', to better express what it contains?

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

36 + width: parent.width

this property shouldn’t be defined here in the component, but rather at the place in the code where it is instantiated, i.e.:

    BookmarksList {
        width: parent.width
    }

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

223 + onVisibleChanged: {
224 + if (visible)
225 + updateSectionsDisplayed();
226 + }

Is this really needed? I have the impression that now that the view is instantiated and destroyed on demand, it could be removed.

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

> Is this really needed? I have the impression that now that the view is
> instantiated and destroyed on demand, it could be removed.

Well, granted: sectionsModel would need to be populated when instantiating the view, but it could be done statically inside the ListModel instead of dynamically.

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

209 + property int bookmarksCountLimit: 5
210 + property bool seeMoreBookmarksView: false

It looks like those two properties don’t need to be exposed publicly, as they’re not being used anywhere outside the component. Can they be made private, by moving them to a QtObject instance with id 'private'?

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

Can you please update the translations template?

    cmake .
    make webbrowser-app.pot

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

Apart from the handful of minor remarks above, the functionality looks good and works fine, so once all the comments are addressed, this will be ok to merge.

581. By Arthur Mello

Change AP test methods names for semantics reasons

Revision history for this message
Arthur Mello (artmello) wrote :

> A tiny remark on semantics:
>
> 943 + def ensure_new_tab_view_visible(self):
> 947 + def ensure_new_tab_view_eventually_hidden(self):
>
> Those two methods should be renamed:
>
> assert_new_tab_view_eventually_visible()
> assert_new_tab_view_eventually_hidden()
>
> Methods that start with "ensure_" actually perform some actions to ensure that
> the result is achieved, as opposed to methods that start with "assert_" that
> merely wait until the condition is verified.

Fixed on revision 581

582. By Arthur Mello

Rename AP method

Revision history for this message
Arthur Mello (artmello) wrote :

> 971 + def test_hidden_new_tab_view(self):
>
> I would rename the above method "test_new_tab_view()".
>
> We could do with more autopilot tests (opening a bookmarked site, opening a
> top visited site), but this doesn’t have to be implemented now.

Fixed on revision 582

583. By Arthur Mello

Add additional conditions to Limit Proxy Model unittest

Revision history for this message
Arthur Mello (artmello) wrote :

> 1197 + void shouldLimitEntries()
> 1198 + {
> 1199 + model->setLimit(2);
> 1200 +
> 1201 + history->add(QUrl("http://example1.org/"), "Example 1
> Domain", QUrl());
> 1202 + QTest::qWait(1001);
> 1203 + history->add(QUrl("http://example2.org/"), "Example 2
> Domain", QUrl());
> 1204 + QTest::qWait(1001);
> 1205 + history->add(QUrl("http://example3.org/"), "Example 3
> Domain", QUrl());
> 1206 +
> 1207 + QCOMPARE(model->rowCount(), 2);
> 1208 + QCOMPARE(model->unlimitedRowCount(), 3);
> 1209 + }
>
> It would be useful to test additional conditions:
> - checking that the initial value of the limit is -1
> - setting the limit to -1
> - setting the limit to a number greater than the number of entries
> - setting the limit after populating the history model, to verify that it is
> correctly updated

Fixed on revision 583

584. By Arthur Mello

State that LimitProxyModel was copied from unity8’s source tree with small changes

Revision history for this message
Arthur Mello (artmello) wrote :

> Can you add a comment to state that LimitProxyModel was copied from unity8’s
> source tree?
> Did you have to modify it, or is it a verbatim copy?

Fixed on revision 584

585. By Arthur Mello

Rename UrlDelegate properties

Revision history for this message
Arthur Mello (artmello) wrote :

> 386 + property alias favIcon: favIcon.source
> 387 + property alias url: url.text
> 388 + property alias label: label.text
>
> Can the 'favIcon' property be renamed 'icon', for consistency across the
> codebase?
> Can the 'label' property be renamed 'title', to better express what it
> contains?

Fixed on revision 585

586. By Arthur Mello

Fix width definition of component

Revision history for this message
Arthur Mello (artmello) wrote :

> 36 + width: parent.width
>
> this property shouldn’t be defined here in the component, but rather at the
> place in the code where it is instantiated, i.e.:
>
> BookmarksList {
> width: parent.width
> }

Fixed on revision 586

587. By Arthur Mello

Populate sectionsMode statically

Revision history for this message
Arthur Mello (artmello) wrote :

> > Is this really needed? I have the impression that now that the view is
> > instantiated and destroyed on demand, it could be removed.
>
> Well, granted: sectionsModel would need to be populated when instantiating the
> view, but it could be done statically inside the ListModel instead of
> dynamically.

Fixed on revision 587

588. By Arthur Mello

Change newTabView properties to private

Revision history for this message
Arthur Mello (artmello) wrote :

> 209 + property int bookmarksCountLimit: 5
> 210 + property bool seeMoreBookmarksView: false
>
> It looks like those two properties don’t need to be exposed publicly, as
> they’re not being used anywhere outside the component. Can they be made
> private, by moving them to a QtObject instance with id 'private'?

Fixed on revision 588

589. By Arthur Mello

Fix AP test

590. By Arthur Mello

Merge with trunk

591. By Arthur Mello

update the translations template

Revision history for this message
Arthur Mello (artmello) wrote :

> Can you please update the translations template?
>
> cmake .
> make webbrowser-app.pot

Fixed on revision 591

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

319 + end: new Date()

This is actually not needed at all, if no 'end' marker is specified, the timeframe will be open on this side, which suits our use case.

592. By Arthur Mello

Rmeove unnecessary code

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

That looks good now. Thanks for your work on this!

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'po/webbrowser-app.pot'
2--- po/webbrowser-app.pot 2014-06-25 16:34:08 +0000
3+++ po/webbrowser-app.pot 2014-07-04 14:19:02 +0000
4@@ -8,7 +8,7 @@
5 msgstr ""
6 "Project-Id-Version: webbrowser-app\n"
7 "Report-Msgid-Bugs-To: \n"
8-"POT-Creation-Date: 2014-06-25 17:33+0100\n"
9+"POT-Creation-Date: 2014-07-04 10:06-0300\n"
10 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12 "Language-Team: LANGUAGE <LL@li.org>\n"
13@@ -81,15 +81,15 @@
14 msgid "Back to safety"
15 msgstr ""
16
17-#: src/app/Chrome.qml:75 src/app/actions/Back.qml:23
18+#: src/app/Chrome.qml:76 src/app/actions/Back.qml:23
19 msgid "Back"
20 msgstr ""
21
22-#: src/app/Chrome.qml:94 src/app/actions/Forward.qml:23
23+#: src/app/Chrome.qml:95 src/app/actions/Forward.qml:23
24 msgid "Forward"
25 msgstr ""
26
27-#: src/app/Chrome.qml:157 src/app/webbrowser/ActivityView.qml:36
28+#: src/app/Chrome.qml:158 src/app/webbrowser/ActivityView.qml:36
29 msgid "Activity"
30 msgstr ""
31
32@@ -249,9 +249,22 @@
33 msgstr ""
34
35 #: src/app/webbrowser/ActivityView.qml:54
36+#: src/app/webbrowser/NewTabView.qml:123
37 msgid "Bookmarks"
38 msgstr ""
39
40+#: src/app/webbrowser/NewTabView.qml:125
41+msgid "Top sites"
42+msgstr ""
43+
44+#: src/app/webbrowser/NewTabView.qml:142
45+msgid "see less"
46+msgstr ""
47+
48+#: src/app/webbrowser/NewTabView.qml:142
49+msgid "see more"
50+msgstr ""
51+
52 #. TRANSLATORS: %1 refers to the number of open tabs
53 #: src/app/webbrowser/TabsList.qml:37
54 #, qt-format
55@@ -307,13 +320,13 @@
56 msgstr ""
57
58 #. TRANSLATORS: %1 refers to the current page’s title
59-#: src/app/webbrowser/webbrowser-app.qml:35
60+#: src/app/webbrowser/webbrowser-app.qml:36
61 #: src/app/webcontainer/webapp-container.qml:54
62 #, qt-format
63 msgid "%1 - Ubuntu Web Browser"
64 msgstr ""
65
66-#: src/app/webbrowser/webbrowser-app.qml:37
67+#: src/app/webbrowser/webbrowser-app.qml:38
68 #: src/app/webcontainer/webapp-container.qml:56
69 msgid "Ubuntu Web Browser"
70 msgstr ""
71
72=== added file 'src/app/webbrowser/BookmarksList.qml'
73--- src/app/webbrowser/BookmarksList.qml 1970-01-01 00:00:00 +0000
74+++ src/app/webbrowser/BookmarksList.qml 2014-07-04 14:19:02 +0000
75@@ -0,0 +1,75 @@
76+/*
77+ * Copyright 2014 Canonical Ltd.
78+ *
79+ * This file is part of webbrowser-app.
80+ *
81+ * webbrowser-app is free software; you can redistribute it and/or modify
82+ * it under the terms of the GNU General Public License as published by
83+ * the Free Software Foundation; version 3.
84+ *
85+ * webbrowser-app is distributed in the hope that it will be useful,
86+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
87+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
88+ * GNU General Public License for more details.
89+ *
90+ * You should have received a copy of the GNU General Public License
91+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
92+ */
93+
94+import QtQuick 2.0
95+import Ubuntu.Components 0.1
96+
97+Column {
98+ id: bookmarksList
99+
100+ property alias model: bookmarksListRepeater.model
101+ property alias footerLabelText: footerLabel.text
102+ property alias footerLabelVisible: footerLabel.visible
103+
104+ signal bookmarkClicked(url url)
105+ signal footerLabelClicked()
106+
107+ spacing: units.gu(1)
108+
109+ move: Transition { UbuntuNumberAnimation { properties: "x, y" } }
110+
111+ Repeater {
112+ id: bookmarksListRepeater
113+
114+ delegate: UrlDelegate{
115+ width: bookmarksList.width
116+ height: units.gu(5)
117+
118+ icon: model.icon
119+ title: model.title ? model.title : model.url
120+ url: model.url
121+
122+ onClicked: bookmarkClicked(model.url)
123+ }
124+ }
125+
126+ Rectangle {
127+ width: parent.width
128+ height: footerLabel.visible ? footerLabel.height + units.gu(6) : 0
129+
130+ MouseArea {
131+ anchors.centerIn: footerLabel
132+
133+ width: footerLabel.width + units.gu(4)
134+ height: footerLabel.height + units.gu(4)
135+
136+ enabled: footerLabel.visible
137+
138+ onClicked: footerLabelClicked()
139+ }
140+
141+ Label {
142+ id: footerLabel
143+ anchors.centerIn: parent
144+
145+ visible: true
146+
147+ font.bold: true
148+ }
149+ }
150+}
151
152=== modified file 'src/app/webbrowser/Browser.qml'
153--- src/app/webbrowser/Browser.qml 2014-06-30 10:40:59 +0000
154+++ src/app/webbrowser/Browser.qml 2014-07-04 14:19:02 +0000
155@@ -48,7 +48,7 @@
156 },
157 Actions.Bookmark {
158 enabled: currentWebview
159- onTriggered: bookmarksModel.add(currentWebview.url, currentWebview.title, "")//currentWebview.icon)
160+ onTriggered: _bookmarksModel.add(currentWebview.url, currentWebview.title, "")//currentWebview.icon)
161 },
162 Actions.NewTab {
163 onTriggered: openUrlInNewTab("", true)
164@@ -118,6 +118,11 @@
165 currentWebview.url = url
166 toggleActivityView()
167 }
168+
169+ function onNewTabUrlRequested(url) {
170+ currentWebview.url = url
171+ currentWebview.forceActiveFocus()
172+ }
173 }
174
175 readonly property bool activityViewVisible: stack.depth > 0
176@@ -126,7 +131,7 @@
177 stack.push(Qt.resolvedUrl("ActivityView.qml"),
178 {tabsModel: tabsModel,
179 historyModel: _historyModel,
180- bookmarksModel: bookmarksModel})
181+ bookmarksModel: _bookmarksModel})
182 var view = stack.currentPage
183 view.onHistoryEntryRequested.connect(internal.onHistoryEntryRequested)
184 view.onNewTabRequested.connect(internal.onNewTabRequested)
185@@ -209,7 +214,7 @@
186 }
187
188 BookmarksModel {
189- id: bookmarksModel
190+ id: _bookmarksModel
191 databasePath: dataLocation + "/bookmarks.sqlite"
192 }
193
194@@ -236,7 +241,7 @@
195 }
196 Actions.BookmarkLink {
197 enabled: contextualData.href.toString()
198- onTriggered: bookmarksModel.add(contextualData.href, contextualData.title, "")
199+ onTriggered: _bookmarksModel.add(contextualData.href, contextualData.title, "")
200 }
201 Actions.CopyLink {
202 enabled: contextualData.href.toString()
203@@ -276,6 +281,26 @@
204 visible = Qt.binding(function() { return current })
205 }
206 }
207+
208+ Loader {
209+ id: newTabViewLoader
210+ anchors.fill: parent
211+
212+ sourceComponent: !parent.url.toString() ? newTabViewComponent : undefined
213+
214+ Component {
215+ id: newTabViewComponent
216+
217+ NewTabView {
218+ anchors.fill: parent
219+
220+ historyModel: _historyModel
221+ bookmarksModel: _bookmarksModel
222+ onBookmarkClicked: internal.onNewTabUrlRequested(url)
223+ onHistoryEntryClicked: internal.onNewTabUrlRequested(url)
224+ }
225+ }
226+ }
227 }
228 }
229
230
231=== modified file 'src/app/webbrowser/CMakeLists.txt'
232--- src/app/webbrowser/CMakeLists.txt 2014-06-30 10:40:59 +0000
233+++ src/app/webbrowser/CMakeLists.txt 2014-07-04 14:19:02 +0000
234@@ -13,7 +13,9 @@
235 history-domainlist-model.cpp
236 history-matches-model.cpp
237 history-model.cpp
238+ history-byvisits-model.cpp
239 history-timeframe-model.cpp
240+ limit-proxy-model.cpp
241 tabs-model.cpp
242 )
243
244
245=== added file 'src/app/webbrowser/NewTabView.qml'
246--- src/app/webbrowser/NewTabView.qml 1970-01-01 00:00:00 +0000
247+++ src/app/webbrowser/NewTabView.qml 2014-07-04 14:19:02 +0000
248@@ -0,0 +1,176 @@
249+/*
250+ * Copyright 2014 Canonical Ltd.
251+ *
252+ * This file is part of webbrowser-app.
253+ *
254+ * webbrowser-app is free software; you can redistribute it and/or modify
255+ * it under the terms of the GNU General Public License as published by
256+ * the Free Software Foundation; version 3.
257+ *
258+ * webbrowser-app is distributed in the hope that it will be useful,
259+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
260+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
261+ * GNU General Public License for more details.
262+ *
263+ * You should have received a copy of the GNU General Public License
264+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
265+ */
266+
267+import QtQuick 2.0
268+import Ubuntu.Components 0.1
269+import Ubuntu.Components.ListItems 0.1 as ListItem
270+import webbrowserapp.private 0.1
271+
272+Item {
273+ id: newTabView
274+
275+ property QtObject bookmarksModel
276+ property QtObject historyModel
277+
278+ signal bookmarkClicked(url url)
279+ signal historyEntryClicked(url url)
280+
281+ QtObject {
282+ id: internal
283+
284+ property int bookmarksCountLimit: 5
285+ property bool seeMoreBookmarksView: false
286+ }
287+
288+ ListModel {
289+ id: sectionsModel
290+
291+ Component.onCompleted: {
292+ if (bookmarksListModel && bookmarksListModel.count !== 0)
293+ sectionsModel.append({ section: "bookmarks" });
294+ if (historyListModel && historyListModel.count !== 0 && !internal.seeMoreBookmarksView )
295+ sectionsModel.append({ section: "topsites" });
296+ }
297+ }
298+
299+ LimitProxyModel {
300+ id: bookmarksListModel
301+
302+ sourceModel: newTabView.bookmarksModel
303+
304+ limit: internal.seeMoreBookmarksView ? -1 : internal.bookmarksCountLimit
305+ }
306+
307+ LimitProxyModel {
308+ id: historyListModel
309+
310+ sourceModel: HistoryByVisitsModel {
311+ sourceModel: HistoryTimeframeModel {
312+ sourceModel: newTabView.historyModel
313+ // We only show sites visited on the last 60 days
314+ start: {
315+ var date = new Date()
316+ date.setDate(date.getDate() - 60)
317+ return date
318+ }
319+ }
320+ }
321+
322+ limit: 10
323+ }
324+
325+ Rectangle {
326+ id: newTabBackground
327+ anchors.fill: parent
328+ color: "white"
329+ }
330+
331+ ListView {
332+ id: newTabListView
333+ anchors.fill: parent
334+
335+ model: sectionsModel
336+
337+ delegate: Loader {
338+ anchors {
339+ left: parent.left
340+ right: parent.right
341+ margins: units.gu(2)
342+ }
343+
344+ width: parent.width
345+ height: children.height
346+
347+ sourceComponent: modelData == "bookmarks" ? bookmarksComponent : topSitesComponent
348+ }
349+
350+ section.property: "section"
351+ section.delegate: Rectangle {
352+ anchors {
353+ left: parent.left
354+ right: parent.right
355+ }
356+
357+ height: sectionHeader.height + units.gu(1)
358+
359+ opacity: section == "topsites" && internal.seeMoreBookmarksView ? 0.0 : 1.0
360+
361+ color: newTabBackground.color
362+
363+ Behavior on opacity { UbuntuNumberAnimation {} }
364+
365+ ListItem.Header {
366+ id: sectionHeader
367+
368+ text: {
369+ if (section == "bookmarks") {
370+ return i18n.tr("Bookmarks")
371+ } else if (section == "topsites") {
372+ return i18n.tr("Top sites")
373+ }
374+ }
375+ }
376+ }
377+
378+ section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
379+ }
380+
381+ Component {
382+ id: bookmarksComponent
383+
384+ BookmarksList {
385+ width: parent.width
386+
387+ model: bookmarksListModel
388+
389+ footerLabelText: internal.seeMoreBookmarksView ? i18n.tr("see less") : i18n.tr("see more")
390+ footerLabelVisible: bookmarksListModel.unlimitedCount > internal.bookmarksCountLimit
391+
392+ onBookmarkClicked: newTabView.bookmarkClicked(url)
393+ onFooterLabelClicked: internal.seeMoreBookmarksView = !internal.seeMoreBookmarksView
394+ }
395+ }
396+
397+ Component {
398+ id: topSitesComponent
399+
400+ Flow {
401+ width: parent.width
402+
403+ spacing: units.gu(1)
404+
405+ opacity: internal.seeMoreBookmarksView ? 0.0 : 1.0
406+
407+ Behavior on opacity { UbuntuNumberAnimation {} }
408+
409+ Repeater {
410+ model: parent.opacity == 0.0 ? "" : historyListModel
411+
412+ delegate: PageDelegate{
413+ width: units.gu(18)
414+ height: units.gu(25)
415+
416+ url: model.url
417+ label: model.title ? model.title : model.url
418+
419+ onClicked: historyEntryClicked(model.url)
420+ }
421+ }
422+ }
423+ }
424+}
425
426=== added file 'src/app/webbrowser/UrlDelegate.qml'
427--- src/app/webbrowser/UrlDelegate.qml 1970-01-01 00:00:00 +0000
428+++ src/app/webbrowser/UrlDelegate.qml 2014-07-04 14:19:02 +0000
429@@ -0,0 +1,73 @@
430+/*
431+ * Copyright 2014 Canonical Ltd.
432+ *
433+ * This file is part of webbrowser-app.
434+ *
435+ * webbrowser-app is free software; you can redistribute it and/or modify
436+ * it under the terms of the GNU General Public License as published by
437+ * the Free Software Foundation; version 3.
438+ *
439+ * webbrowser-app is distributed in the hope that it will be useful,
440+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
441+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
442+ * GNU General Public License for more details.
443+ *
444+ * You should have received a copy of the GNU General Public License
445+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
446+ */
447+
448+import QtQuick 2.0
449+import Ubuntu.Components 0.1
450+
451+Item {
452+ id: urlDelegate
453+
454+ property alias icon: icon.source
455+ property alias url: url.text
456+ property alias title: title.text
457+
458+ signal clicked()
459+
460+ MouseArea {
461+ anchors.fill: parent
462+ onClicked: urlDelegate.clicked()
463+ }
464+
465+ Row {
466+ anchors.fill: parent
467+ spacing: units.gu(1)
468+
469+ UbuntuShape {
470+ id: iconShape
471+ height: parent.height
472+ width: parent.height
473+
474+ image: Image {
475+ id: icon
476+ }
477+ }
478+
479+ Column {
480+ width: parent.width - iconShape.width - spacing
481+ Label {
482+ id: title
483+ width: parent.width
484+ font.bold: true
485+
486+ wrapMode: Text.Wrap
487+ elide: Text.ElideRight
488+ maximumLineCount: 1
489+ }
490+
491+ Label {
492+ id: url
493+ width: parent.width
494+ fontSize: "small"
495+
496+ wrapMode: Text.Wrap
497+ elide: Text.ElideRight
498+ maximumLineCount: 1
499+ }
500+ }
501+ }
502+}
503
504=== added file 'src/app/webbrowser/history-byvisits-model.cpp'
505--- src/app/webbrowser/history-byvisits-model.cpp 1970-01-01 00:00:00 +0000
506+++ src/app/webbrowser/history-byvisits-model.cpp 2014-07-04 14:19:02 +0000
507@@ -0,0 +1,50 @@
508+/*
509+ * Copyright 2014 Canonical Ltd.
510+ *
511+ * This file is part of webbrowser-app.
512+ *
513+ * webbrowser-app is free software; you can redistribute it and/or modify
514+ * it under the terms of the GNU General Public License as published by
515+ * the Free Software Foundation; version 3.
516+ *
517+ * webbrowser-app is distributed in the hope that it will be useful,
518+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
519+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
520+ * GNU General Public License for more details.
521+ *
522+ * You should have received a copy of the GNU General Public License
523+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
524+ */
525+
526+#include "history-byvisits-model.h"
527+#include "history-timeframe-model.h"
528+#include "history-model.h"
529+
530+/*!
531+ \class HistoryByVisitsModel
532+ \brief Proxy model that sorts a history model by number of visits
533+
534+ HistoryByVisitsModel is a proxy model that sorts a
535+ HistoryTimeframeModel by the number of visits
536+ (i.e. the history with the greatest number of visits first).
537+*/
538+HistoryByVisitsModel::HistoryByVisitsModel(QObject* parent)
539+ : QSortFilterProxyModel(parent)
540+{
541+ setDynamicSortFilter(true);
542+ setSortRole(HistoryModel::Visits);
543+ sort(0, Qt::DescendingOrder);
544+}
545+
546+HistoryTimeframeModel* HistoryByVisitsModel::sourceModel() const
547+{
548+ return qobject_cast<HistoryTimeframeModel*>(QSortFilterProxyModel::sourceModel());
549+}
550+
551+void HistoryByVisitsModel::setSourceModel(HistoryTimeframeModel* sourceModel)
552+{
553+ if (sourceModel != this->sourceModel()) {
554+ QSortFilterProxyModel::setSourceModel(sourceModel);
555+ Q_EMIT sourceModelChanged();
556+ }
557+}
558
559=== added file 'src/app/webbrowser/history-byvisits-model.h'
560--- src/app/webbrowser/history-byvisits-model.h 1970-01-01 00:00:00 +0000
561+++ src/app/webbrowser/history-byvisits-model.h 2014-07-04 14:19:02 +0000
562@@ -0,0 +1,43 @@
563+/*
564+ * Copyright 2014 Canonical Ltd.
565+ *
566+ * This file is part of webbrowser-app.
567+ *
568+ * webbrowser-app is free software; you can redistribute it and/or modify
569+ * it under the terms of the GNU General Public License as published by
570+ * the Free Software Foundation; version 3.
571+ *
572+ * webbrowser-app is distributed in the hope that it will be useful,
573+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
574+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
575+ * GNU General Public License for more details.
576+ *
577+ * You should have received a copy of the GNU General Public License
578+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
579+ */
580+
581+#ifndef __HISTORY_BYVISITS_MODEL_H__
582+#define __HISTORY_BYVISITS_MODEL_H__
583+
584+// Qt
585+#include <QtCore/QSortFilterProxyModel>
586+
587+class HistoryTimeframeModel;
588+
589+class HistoryByVisitsModel : public QSortFilterProxyModel
590+{
591+ Q_OBJECT
592+
593+ Q_PROPERTY(HistoryTimeframeModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged)
594+
595+public:
596+ HistoryByVisitsModel(QObject* parent=0);
597+
598+ HistoryTimeframeModel* sourceModel() const;
599+ void setSourceModel(HistoryTimeframeModel* sourceModel);
600+
601+Q_SIGNALS:
602+ void sourceModelChanged() const;
603+};
604+
605+#endif // __HISTORY_BYVISITS_MODEL_H__
606
607=== added file 'src/app/webbrowser/limit-proxy-model.cpp'
608--- src/app/webbrowser/limit-proxy-model.cpp 1970-01-01 00:00:00 +0000
609+++ src/app/webbrowser/limit-proxy-model.cpp 2014-07-04 14:19:02 +0000
610@@ -0,0 +1,268 @@
611+/*
612+ * Copyright 2014 Canonical Ltd.
613+ *
614+ * This file is part of webbrowser-app.
615+ *
616+ * webbrowser-app is free software; you can redistribute it and/or modify
617+ * it under the terms of the GNU General Public License as published by
618+ * the Free Software Foundation; version 3.
619+ *
620+ * webbrowser-app is distributed in the hope that it will be useful,
621+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
622+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
623+ * GNU General Public License for more details.
624+ *
625+ * You should have received a copy of the GNU General Public License
626+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
627+ */
628+
629+#include <QtCore/QAbstractItemModel>
630+
631+#include "limit-proxy-model.h"
632+
633+/*!
634+ \class LimitProxyModel
635+ \brief Identity model that limits the number of rows returned by a model
636+
637+ LimitProxyModel is a proxy model that limits the number
638+ of rows returned by a model
639+ (i.e. only the first N entries are returned).
640+
641+ This proxy model was copied from unity8's source tree with small changes,
642+ like the type of the sourceModel.
643+*/
644+LimitProxyModel::LimitProxyModel(QObject* parent)
645+ : QIdentityProxyModel(parent),
646+ m_limit(-1),
647+ m_sourceInserting(false),
648+ m_sourceRemoving(false),
649+ m_dataChangedBegin(-1),
650+ m_dataChangedEnd(-1)
651+
652+{
653+ connect(this, SIGNAL(modelReset()), SIGNAL(countChanged()));
654+ connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(countChanged()));
655+ connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(countChanged()));
656+ connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(unlimitedCountChanged()));
657+ connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(unlimitedCountChanged()));
658+}
659+
660+QAbstractItemModel* LimitProxyModel::sourceModel() const
661+{
662+ return qobject_cast<QAbstractItemModel*>(QIdentityProxyModel::sourceModel());
663+}
664+
665+void LimitProxyModel::setSourceModel(QObject* sourceModel)
666+{
667+ QAbstractItemModel* model = qobject_cast<QAbstractItemModel*>(sourceModel);
668+
669+ if (model != this->sourceModel()) {
670+ if (this->sourceModel() != NULL) {
671+ this->sourceModel()->disconnect(this);
672+ }
673+
674+ QIdentityProxyModel::setSourceModel(model);
675+
676+ if (this->sourceModel() != NULL) {
677+ // Disconnect the QIdentityProxyModel handling for rows removed/added...
678+ disconnect(this->sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, NULL);
679+ disconnect(this->sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, NULL);
680+ disconnect(this->sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, NULL);
681+ disconnect(this->sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, NULL);
682+
683+ // ... and use our own
684+ connect(this->sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
685+ this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
686+ connect(this->sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
687+ this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
688+ connect(this->sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
689+ this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
690+ connect(this->sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
691+ this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
692+ }
693+ Q_EMIT sourceModelChanged();
694+ }
695+}
696+
697+int LimitProxyModel::rowCount(const QModelIndex &parent) const
698+{
699+ if (parent.isValid()) // We are not a tree
700+ return 0;
701+
702+ const int unlimitedCount = QIdentityProxyModel::rowCount(parent);
703+ return m_limit < 0 ? unlimitedCount : qMin(m_limit, unlimitedCount);
704+}
705+
706+int LimitProxyModel::unlimitedRowCount(const QModelIndex &parent) const
707+{
708+ if (parent.isValid()) // We are not a tree
709+ return 0;
710+
711+ return QIdentityProxyModel::rowCount(parent);
712+}
713+
714+int LimitProxyModel::limit() const
715+{
716+ return m_limit;
717+}
718+
719+void LimitProxyModel::setLimit(int limit)
720+{
721+ if (limit != m_limit) {
722+ bool inserting = false;
723+ bool removing = false;
724+ const int oldCount = rowCount();
725+ const int unlimitedCount = QIdentityProxyModel::rowCount();
726+ if (m_limit < 0) {
727+ if (limit < oldCount) {
728+ removing = true;
729+ beginRemoveRows(QModelIndex(), limit, oldCount - 1);
730+ }
731+ } else if (limit < 0) {
732+ if (m_limit < unlimitedCount) {
733+ inserting = true;
734+ beginInsertRows(QModelIndex(), m_limit, unlimitedCount - 1);
735+ }
736+ } else {
737+ if (limit > m_limit && unlimitedCount > m_limit) {
738+ inserting = true;
739+ beginInsertRows(QModelIndex(), m_limit, qMin(limit, unlimitedCount) - 1);
740+ } else if (limit < m_limit && limit < oldCount) {
741+ removing = true;
742+ beginRemoveRows(QModelIndex(), limit, oldCount - 1);
743+ }
744+ }
745+
746+ m_limit = limit;
747+
748+ if (inserting) {
749+ endInsertRows();
750+ } else if (removing) {
751+ endRemoveRows();
752+ }
753+
754+ Q_EMIT limitChanged();
755+ }
756+}
757+
758+void LimitProxyModel::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
759+{
760+ if (m_limit < 0) {
761+ beginInsertRows(mapFromSource(parent), start, end);
762+ m_sourceInserting = true;
763+ } else if (start < m_limit) {
764+ const int nSourceAddedItems = end - start + 1;
765+ const int currentCount = QIdentityProxyModel::rowCount();
766+ if (currentCount + nSourceAddedItems <= m_limit) {
767+ // After Inserting items we will be under the limit
768+ // so just proceed with the insertion normally
769+ beginInsertRows(mapFromSource(parent), start, end);
770+ m_sourceInserting = true;
771+ } else if (currentCount >= m_limit) {
772+ // We are already over the limit so to our users we are not adding items, just
773+ // changing it's data, i.e we had something like
774+ // A B C D E
775+ // with a limit of 5
776+ // after inserting (let's say three 'F' at position 1) we will have
777+ // A F F F B
778+ // so we just need to signal a dataChanged from 1 to 4
779+ m_dataChangedBegin = start;
780+ m_dataChangedEnd = m_limit - 1;
781+ } else { // currentCount < m_limit && currentCount + nSourceAddedItems > m_limit
782+ // We have less items than the limit but after adding them we will be over
783+ // To our users this means we need to insert some items and change the
784+ // data of some others, i.e we had something like
785+ // A B C
786+ // with a limit of 5
787+ // after inserting (let's say three 'F' at position 1) we will have
788+ // A F F F B
789+ // so we need to signal an insetion from position 1 to 2, instead of from
790+ // position 1 to 3 and a after that a data changed from 3 to 4
791+ const int nItemsToInsert = m_limit - currentCount;
792+ beginInsertRows(mapFromSource(parent), start, start + nItemsToInsert - 1);
793+ m_sourceInserting = true;
794+ m_dataChangedBegin = start + nItemsToInsert;
795+ m_dataChangedEnd = m_limit - 1;
796+ if (m_dataChangedBegin > m_dataChangedEnd) {
797+ // Just in case we were empty and insert 6 items with a limit of 5
798+ // We don't want to signal a dataChanged from 5 to 4
799+ m_dataChangedBegin = -1;
800+ m_dataChangedEnd = -1;
801+ }
802+ }
803+ }
804+}
805+
806+void LimitProxyModel::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
807+{
808+ if (m_limit < 0) {
809+ beginRemoveRows(mapFromSource(parent), start, end);
810+ m_sourceRemoving = true;
811+ } else if (start < m_limit) {
812+ const int nSourceRemovedItems = end - start + 1;
813+ const int currentCount = QIdentityProxyModel::rowCount();
814+ if (currentCount <= m_limit) {
815+ // We are already under the limit so
816+ // so just proceed with the removal normally
817+ beginRemoveRows(mapFromSource(parent), start, end);
818+ m_sourceRemoving = true;
819+ } else if (currentCount - nSourceRemovedItems >= m_limit) {
820+ // Even after removing items we will be at or over the limit
821+ // So to our users we are not removing anything, just changing the data
822+ // i.e. we had a internal model with
823+ // A B C D E F G H
824+ // and a limit of 5, our users just see
825+ // A B C D E
826+ // so if we remove 3 items starting at 1 we have to expose
827+ // A E F G H
828+ // that is, a dataChanged from 1 to 4
829+ m_dataChangedBegin = start;
830+ m_dataChangedEnd = m_limit - 1;
831+ } else { // currentCount > m_limit && currentCount - nSourceRemovedItems < m_limit
832+ // We have more items than the limit but after removing we will be below it
833+ // So to our users we both removing and changing the data
834+ // i.e. we had a internal model with
835+ // A B C D E F G
836+ // and a limit of 5, our users just see
837+ // A B C D E
838+ // so if we remove items from 1 to 3 we have to expose
839+ // A E F G
840+ // that is, a remove from 4 to 4 and a dataChanged from 1 to 3
841+ const int nItemsToRemove = m_limit - (currentCount - nSourceRemovedItems);
842+ beginRemoveRows(mapFromSource(parent), m_limit - nItemsToRemove, m_limit - 1);
843+ m_sourceRemoving = true;
844+ m_dataChangedBegin = start;
845+ m_dataChangedEnd = m_limit - nItemsToRemove - 1;
846+ if (m_dataChangedBegin > m_dataChangedEnd) {
847+ m_dataChangedBegin = -1;
848+ m_dataChangedEnd = -1;
849+ }
850+ }
851+ }
852+}
853+
854+void LimitProxyModel::sourceRowsInserted(const QModelIndex & /*parent*/, int /*start*/, int /*end*/)
855+{
856+ if (m_sourceInserting) {
857+ endInsertRows();
858+ m_sourceInserting = false;
859+ }
860+ if (m_dataChangedBegin != -1 && m_dataChangedEnd != -1) {
861+ dataChanged(index(m_dataChangedBegin, 0), index(m_dataChangedEnd, 0));
862+ m_dataChangedBegin = -1;
863+ m_dataChangedEnd = -1;
864+ }
865+}
866+
867+void LimitProxyModel::sourceRowsRemoved(const QModelIndex & /*parent*/, int /*start*/, int /*end*/)
868+{
869+ if (m_sourceRemoving) {
870+ endRemoveRows();
871+ m_sourceRemoving = false;
872+ }
873+ if (m_dataChangedBegin != -1 && m_dataChangedEnd != -1) {
874+ dataChanged(index(m_dataChangedBegin, 0), index(m_dataChangedEnd, 0));
875+ m_dataChangedBegin = -1;
876+ m_dataChangedEnd = -1;
877+ }
878+}
879
880=== added file 'src/app/webbrowser/limit-proxy-model.h'
881--- src/app/webbrowser/limit-proxy-model.h 1970-01-01 00:00:00 +0000
882+++ src/app/webbrowser/limit-proxy-model.h 2014-07-04 14:19:02 +0000
883@@ -0,0 +1,70 @@
884+/*
885+ * Copyright 2014 Canonical Ltd.
886+ *
887+ * This file is part of webbrowser-app.
888+ *
889+ * webbrowser-app is free software; you can redistribute it and/or modify
890+ * it under the terms of the GNU General Public License as published by
891+ * the Free Software Foundation; version 3.
892+ *
893+ * webbrowser-app is distributed in the hope that it will be useful,
894+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
895+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
896+ * GNU General Public License for more details.
897+ *
898+ * You should have received a copy of the GNU General Public License
899+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
900+ */
901+
902+#ifndef __LIMIT_PROXY_MODEL_H__
903+#define __LIMIT_PROXY_MODEL_H__
904+
905+// Qt
906+#include <QtCore/QIdentityProxyModel>
907+
908+class QSortFilterProxyModel;
909+
910+class LimitProxyModel : public QIdentityProxyModel
911+{
912+ Q_OBJECT
913+
914+ Q_PROPERTY(QObject* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged)
915+ Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged)
916+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
917+ Q_PROPERTY(int unlimitedCount READ unlimitedRowCount NOTIFY unlimitedCountChanged)
918+
919+public:
920+ LimitProxyModel(QObject* parent=0);
921+
922+ QAbstractItemModel* sourceModel() const;
923+ void setSourceModel(QObject* sourceModel);
924+
925+ int limit() const;
926+ void setLimit(int limit);
927+
928+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
929+ int unlimitedRowCount(const QModelIndex &parent = QModelIndex()) const;
930+
931+Q_SIGNALS:
932+ void sourceModelChanged() const;
933+ void limitChanged() const;
934+ void unlimitedCountChanged();
935+ void countChanged();
936+
937+private Q_SLOTS:
938+ void sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int
939+end);
940+ void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int
941+end);
942+ void sourceRowsInserted(const QModelIndex &parent, int start, int end);
943+ void sourceRowsRemoved(const QModelIndex &parent, int start, int end);
944+
945+private:
946+ int m_limit;
947+ bool m_sourceInserting;
948+ bool m_sourceRemoving;
949+ int m_dataChangedBegin;
950+ int m_dataChangedEnd;
951+};
952+
953+#endif // __LIMIT_PROXY_MODEL_H__
954
955=== modified file 'src/app/webbrowser/webbrowser-app.cpp'
956--- src/app/webbrowser/webbrowser-app.cpp 2014-06-30 10:40:59 +0000
957+++ src/app/webbrowser/webbrowser-app.cpp 2014-07-04 14:19:02 +0000
958@@ -21,8 +21,10 @@
959 #include "history-model.h"
960 #include "history-matches-model.h"
961 #include "history-timeframe-model.h"
962+#include "history-byvisits-model.h"
963 #include "history-domainlist-model.h"
964 #include "history-domainlist-chronological-model.h"
965+#include "limit-proxy-model.h"
966 #include "searchengine.h"
967 #include "settings.h"
968 #include "tabs-model.h"
969@@ -75,8 +77,10 @@
970 qmlRegisterType<HistoryModel>(uri, 0, 1, "HistoryModel");
971 qmlRegisterType<HistoryMatchesModel>(uri, 0, 1, "HistoryMatchesModel");
972 qmlRegisterType<HistoryTimeframeModel>(uri, 0, 1, "HistoryTimeframeModel");
973+ qmlRegisterType<HistoryByVisitsModel>(uri, 0 , 1, "HistoryByVisitsModel");
974 qmlRegisterType<HistoryDomainListModel>(uri, 0, 1, "HistoryDomainListModel");
975 qmlRegisterType<HistoryDomainListChronologicalModel>(uri, 0, 1, "HistoryDomainListChronologicalModel");
976+ qmlRegisterType<LimitProxyModel>(uri, 0 , 1, "LimitProxyModel");
977 qmlRegisterType<TabsModel>(uri, 0, 1, "TabsModel");
978 qmlRegisterType<BookmarksModel>(uri, 0, 1, "BookmarksModel");
979
980
981=== modified file 'tests/autopilot/webbrowser_app/emulators/browser.py'
982--- tests/autopilot/webbrowser_app/emulators/browser.py 2014-06-25 20:38:15 +0000
983+++ tests/autopilot/webbrowser_app/emulators/browser.py 2014-07-04 14:19:02 +0000
984@@ -116,6 +116,12 @@
985 def get_geolocation_dialog(self):
986 return self.wait_select_single("GeolocationPermissionRequest")
987
988+ def get_many_new_tab_view(self):
989+ return self.select_many("NewTabView")
990+
991+ def get_new_tab_view(self):
992+ return self.wait_select_single("NewTabView")
993+
994 def get_selection(self):
995 return self.wait_select_single(Selection)
996
997
998=== modified file 'tests/autopilot/webbrowser_app/tests/__init__.py'
999--- tests/autopilot/webbrowser_app/tests/__init__.py 2014-06-11 11:12:14 +0000
1000+++ tests/autopilot/webbrowser_app/tests/__init__.py 2014-07-04 14:19:02 +0000
1001@@ -156,8 +156,6 @@
1002 def assert_activity_view_eventually_hidden(self):
1003 self.assertThat(self.main_window.get_many_activity_view,
1004 Eventually(Equals([])))
1005- self.assertThat(self.main_window.get_current_webview().visible,
1006- Eventually(Equals(True)))
1007
1008 def ensure_activity_view_visible(self):
1009 self.ensure_chrome_is_hidden()
1010@@ -169,6 +167,14 @@
1011 ping = urllib.request.urlopen(self.base_url + "/ping")
1012 self.assertThat(ping.read(), Equals(b"pong"))
1013
1014+ def assert_new_tab_view_eventually_visible(self):
1015+ new_tab_view = self.main_window.get_new_tab_view()
1016+ self.assertThat(new_tab_view.visible, Eventually(Equals(True)))
1017+
1018+ def assert_new_tab_view_eventually_hidden(self):
1019+ self.assertThat(self.main_window.get_many_new_tab_view,
1020+ Eventually(Equals([])))
1021+
1022
1023 class StartOpenRemotePageTestCaseBase(BrowserTestCaseBase):
1024
1025
1026=== modified file 'tests/autopilot/webbrowser_app/tests/test_tabs.py'
1027--- tests/autopilot/webbrowser_app/tests/test_tabs.py 2014-06-27 11:51:42 +0000
1028+++ tests/autopilot/webbrowser_app/tests/test_tabs.py 2014-07-04 14:19:02 +0000
1029@@ -77,6 +77,7 @@
1030 self.assertThat(self.main_window.currentIndex, Equals(0))
1031 self.open_new_tab()
1032 self.assertThat(self.main_window.currentIndex, Eventually(Equals(1)))
1033+ self.assert_new_tab_view_eventually_visible()
1034
1035 def test_switch_tabs(self):
1036 self.ensure_activity_view_visible()
1037@@ -167,3 +168,12 @@
1038 tabs = self.main_window.get_tabslist_view_delegates()
1039 self.pointing_device.click_object(tabs[0])
1040 self.assertThat(error.visible, Eventually(Equals(False)))
1041+
1042+ def test_new_tab_view(self):
1043+ self.ensure_activity_view_visible()
1044+ self.open_new_tab()
1045+ self.assert_new_tab_view_eventually_visible()
1046+ url = self.base_url + "/aleaiactaest"
1047+ self.type_in_address_bar(url)
1048+ self.keyboard.press_and_release("Enter")
1049+ self.assert_new_tab_view_eventually_hidden()
1050
1051=== modified file 'tests/unittests/CMakeLists.txt'
1052--- tests/unittests/CMakeLists.txt 2014-05-22 15:27:38 +0000
1053+++ tests/unittests/CMakeLists.txt 2014-07-04 14:19:02 +0000
1054@@ -6,8 +6,10 @@
1055 add_subdirectory(history-domain-model)
1056 add_subdirectory(history-domainlist-model)
1057 add_subdirectory(history-domainlist-chronological-model)
1058+add_subdirectory(history-byvisits-model)
1059 add_subdirectory(session-utils)
1060 add_subdirectory(tabs-model)
1061 add_subdirectory(bookmarks-model)
1062+add_subdirectory(limit-proxy-model)
1063 add_subdirectory(container-url-patterns)
1064 add_subdirectory(cookie-store)
1065
1066=== added directory 'tests/unittests/history-byvisits-model'
1067=== added file 'tests/unittests/history-byvisits-model/CMakeLists.txt'
1068--- tests/unittests/history-byvisits-model/CMakeLists.txt 1970-01-01 00:00:00 +0000
1069+++ tests/unittests/history-byvisits-model/CMakeLists.txt 2014-07-04 14:19:02 +0000
1070@@ -0,0 +1,6 @@
1071+set(TEST tst_HistoryByVisitsModelTests)
1072+add_executable(${TEST} tst_HistoryByVisitsModelTests.cpp)
1073+include_directories(${webbrowser-app_SOURCE_DIR})
1074+qt5_use_modules(${TEST} Test)
1075+target_link_libraries(${TEST} webbrowser-app-models)
1076+add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml)
1077
1078=== added file 'tests/unittests/history-byvisits-model/tst_HistoryByVisitsModelTests.cpp'
1079--- tests/unittests/history-byvisits-model/tst_HistoryByVisitsModelTests.cpp 1970-01-01 00:00:00 +0000
1080+++ tests/unittests/history-byvisits-model/tst_HistoryByVisitsModelTests.cpp 2014-07-04 14:19:02 +0000
1081@@ -0,0 +1,89 @@
1082+/*
1083+ * Copyright 2014 Canonical Ltd.
1084+ *
1085+ * This file is part of webbrowser-app.
1086+ *
1087+ * webbrowser-app is free software; you can redistribute it and/or modify
1088+ * it under the terms of the GNU General Public License as published by
1089+ * the Free Software Foundation; version 3.
1090+ *
1091+ * webbrowser-app is distributed in the hope that it will be useful,
1092+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1093+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1094+ * GNU General Public License for more details.
1095+ *
1096+ * You should have received a copy of the GNU General Public License
1097+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1098+ */
1099+
1100+// Qt
1101+#include <QtTest/QSignalSpy>
1102+#include <QtTest/QtTest>
1103+
1104+// local
1105+#include "domain-utils.h"
1106+#include "history-model.h"
1107+#include "history-timeframe-model.h"
1108+#include "history-byvisits-model.h"
1109+
1110+class HistoryByVisitsModelTests : public QObject
1111+{
1112+ Q_OBJECT
1113+
1114+private:
1115+ HistoryModel* history;
1116+ HistoryTimeframeModel* timeframe;
1117+ HistoryByVisitsModel* model;
1118+
1119+private Q_SLOTS:
1120+ void init()
1121+ {
1122+ history = new HistoryModel;
1123+ history->setDatabasePath(":memory:");
1124+ timeframe = new HistoryTimeframeModel;
1125+ timeframe->setSourceModel(history);
1126+ model = new HistoryByVisitsModel;
1127+ model->setSourceModel(timeframe);
1128+ }
1129+
1130+ void cleanup()
1131+ {
1132+ delete model;
1133+ delete timeframe;
1134+ delete history;
1135+ }
1136+
1137+ void shouldBeInitiallyEmpty()
1138+ {
1139+ QCOMPARE(model->rowCount(), 0);
1140+ }
1141+
1142+ void shouldNotifyWhenChangingSourceModel()
1143+ {
1144+ QSignalSpy spy(model, SIGNAL(sourceModelChanged()));
1145+ model->setSourceModel(timeframe);
1146+ QVERIFY(spy.isEmpty());
1147+ HistoryTimeframeModel* timeframe2 = new HistoryTimeframeModel;
1148+ model->setSourceModel(timeframe2);
1149+ QCOMPARE(spy.count(), 1);
1150+ QCOMPARE(model->sourceModel(), timeframe2);
1151+ model->setSourceModel(0);
1152+ QCOMPARE(spy.count(), 2);
1153+ QCOMPARE(model->sourceModel(), (HistoryTimeframeModel*) 0);
1154+ delete timeframe2;
1155+ }
1156+
1157+ void shouldBeSortedByVisits()
1158+ {
1159+ history->add(QUrl("http://example.org/"), "Example Domain", QUrl());
1160+ QTest::qWait(1001);
1161+ history->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl());
1162+ QTest::qWait(1001);
1163+ history->add(QUrl("http://ubuntu.com/"), "Ubuntu", QUrl());
1164+ QCOMPARE(model->data(model->index(0, 0), HistoryModel::Domain).toString(), QString("ubuntu.com"));
1165+ QCOMPARE(model->data(model->index(1, 0), HistoryModel::Domain).toString(), QString("example.org"));
1166+ }
1167+};
1168+
1169+QTEST_MAIN(HistoryByVisitsModelTests)
1170+#include "tst_HistoryByVisitsModelTests.moc"
1171
1172=== added directory 'tests/unittests/limit-proxy-model'
1173=== added file 'tests/unittests/limit-proxy-model/CMakeLists.txt'
1174--- tests/unittests/limit-proxy-model/CMakeLists.txt 1970-01-01 00:00:00 +0000
1175+++ tests/unittests/limit-proxy-model/CMakeLists.txt 2014-07-04 14:19:02 +0000
1176@@ -0,0 +1,6 @@
1177+set(TEST tst_LimitProxyModelTests)
1178+add_executable(${TEST} tst_LimitProxyModelTests.cpp)
1179+include_directories(${webbrowser-app_SOURCE_DIR})
1180+qt5_use_modules(${TEST} Test)
1181+target_link_libraries(${TEST} webbrowser-app-models)
1182+add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml)
1183
1184=== added file 'tests/unittests/limit-proxy-model/tst_LimitProxyModelTests.cpp'
1185--- tests/unittests/limit-proxy-model/tst_LimitProxyModelTests.cpp 1970-01-01 00:00:00 +0000
1186+++ tests/unittests/limit-proxy-model/tst_LimitProxyModelTests.cpp 2014-07-04 14:19:02 +0000
1187@@ -0,0 +1,144 @@
1188+/*
1189+ * Copyright 2014 Canonical Ltd.
1190+ *
1191+ * This file is part of webbrowser-app.
1192+ *
1193+ * webbrowser-app is free software; you can redistribute it and/or modify
1194+ * it under the terms of the GNU General Public License as published by
1195+ * the Free Software Foundation; version 3.
1196+ *
1197+ * webbrowser-app is distributed in the hope that it will be useful,
1198+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1199+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1200+ * GNU General Public License for more details.
1201+ *
1202+ * You should have received a copy of the GNU General Public License
1203+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1204+ */
1205+
1206+// Qt
1207+#include <QtTest/QSignalSpy>
1208+#include <QtTest/QtTest>
1209+
1210+// local
1211+#include "domain-utils.h"
1212+#include "history-model.h"
1213+#include "history-timeframe-model.h"
1214+#include "history-byvisits-model.h"
1215+#include "limit-proxy-model.h"
1216+
1217+class LimitProxyModelTests : public QObject
1218+{
1219+ Q_OBJECT
1220+
1221+private:
1222+ HistoryModel* history;
1223+ HistoryTimeframeModel* timeframe;
1224+ HistoryByVisitsModel* byvisits;
1225+ LimitProxyModel* model;
1226+
1227+private Q_SLOTS:
1228+ void init()
1229+ {
1230+ history = new HistoryModel;
1231+ history->setDatabasePath(":memory:");
1232+ timeframe = new HistoryTimeframeModel;
1233+ timeframe->setSourceModel(history);
1234+ byvisits = new HistoryByVisitsModel;
1235+ byvisits->setSourceModel(timeframe);
1236+ model = new LimitProxyModel;
1237+ model->setSourceModel(byvisits);
1238+ }
1239+
1240+ void cleanup()
1241+ {
1242+ delete model;
1243+ delete byvisits;
1244+ delete timeframe;
1245+ delete history;
1246+ }
1247+
1248+ void shouldBeInitiallyEmpty()
1249+ {
1250+ QCOMPARE(model->rowCount(), 0);
1251+ }
1252+
1253+ void shouldLimitBeInitiallyMinusOne()
1254+ {
1255+ QCOMPARE(model->limit(), -1);
1256+ }
1257+
1258+ void shouldNotifyWhenChangingSourceModel()
1259+ {
1260+ QSignalSpy spy(model, SIGNAL(sourceModelChanged()));
1261+ model->setSourceModel(byvisits);
1262+ QVERIFY(spy.isEmpty());
1263+ HistoryByVisitsModel* byvisits2 = new HistoryByVisitsModel;
1264+ model->setSourceModel(byvisits2);
1265+ QCOMPARE(spy.count(), 1);
1266+ QCOMPARE(model->sourceModel(), byvisits2);
1267+ model->setSourceModel(0);
1268+ QCOMPARE(spy.count(), 2);
1269+ QCOMPARE(model->sourceModel(), (HistoryByVisitsModel*) 0);
1270+ delete byvisits2;
1271+ }
1272+
1273+ void shouldLimitEntriesWithLimitSetBeforePopulating()
1274+ {
1275+ model->setLimit(2);
1276+
1277+ history->add(QUrl("http://example1.org/"), "Example 1 Domain", QUrl());
1278+ QTest::qWait(1001);
1279+ history->add(QUrl("http://example2.org/"), "Example 2 Domain", QUrl());
1280+ QTest::qWait(1001);
1281+ history->add(QUrl("http://example3.org/"), "Example 3 Domain", QUrl());
1282+
1283+ QCOMPARE(model->rowCount(), 2);
1284+ QCOMPARE(model->unlimitedRowCount(), 3);
1285+ }
1286+
1287+ void shouldLimitEntriesWithLimitSetAfterPopulating()
1288+ {
1289+ history->add(QUrl("http://example1.org/"), "Example 1 Domain", QUrl());
1290+ QTest::qWait(1001);
1291+ history->add(QUrl("http://example2.org/"), "Example 2 Domain", QUrl());
1292+ QTest::qWait(1001);
1293+ history->add(QUrl("http://example3.org/"), "Example 3 Domain", QUrl());
1294+
1295+ model->setLimit(2);
1296+
1297+ QCOMPARE(model->rowCount(), 2);
1298+ QCOMPARE(model->unlimitedRowCount(), 3);
1299+ }
1300+
1301+ void shouldNotLimitEntriesIfLimitIsMinusOne()
1302+ {
1303+ model->setLimit(-1);
1304+
1305+ history->add(QUrl("http://example1.org/"), "Example 1 Domain", QUrl());
1306+ QTest::qWait(1001);
1307+ history->add(QUrl("http://example2.org/"), "Example 2 Domain", QUrl());
1308+ QTest::qWait(1001);
1309+ history->add(QUrl("http://example3.org/"), "Example 3 Domain", QUrl());
1310+
1311+ QCOMPARE(model->unlimitedRowCount(), 3);
1312+ QCOMPARE(model->rowCount(), model->unlimitedRowCount());
1313+ }
1314+
1315+ void shouldNotLimitEntriesIfLimitIsGreaterThanRowCount()
1316+ {
1317+ model->setLimit(4);
1318+
1319+ history->add(QUrl("http://example1.org/"), "Example 1 Domain", QUrl());
1320+ QTest::qWait(1001);
1321+ history->add(QUrl("http://example2.org/"), "Example 2 Domain", QUrl());
1322+ QTest::qWait(1001);
1323+ history->add(QUrl("http://example3.org/"), "Example 3 Domain", QUrl());
1324+
1325+ QCOMPARE(model->unlimitedRowCount(), 3);
1326+ QCOMPARE(model->rowCount(), model->unlimitedRowCount());
1327+ }
1328+};
1329+
1330+QTEST_MAIN(LimitProxyModelTests)
1331+#include "tst_LimitProxyModelTests.moc"

Subscribers

People subscribed via source and target branches

to status/vote changes: