Merge lp:~rpadovani/webbrowser-app/settings-page into lp:webbrowser-app

Proposed by Riccardo Padovani
Status: Merged
Approved by: Olivier Tilloy
Approved revision: 970
Merged at revision: 956
Proposed branch: lp:~rpadovani/webbrowser-app/settings-page
Merge into: lp:webbrowser-app
Prerequisite: lp:~osomon/webbrowser-app/qt-labs-settings
Diff against target: 663 lines (+473/-36)
10 files modified
debian/control (+1/-0)
src/app/webbrowser/AddressBar.qml (+4/-35)
src/app/webbrowser/Browser.qml (+25/-0)
src/app/webbrowser/CMakeLists.txt (+1/-1)
src/app/webbrowser/SettingsPage.qml (+277/-0)
src/app/webbrowser/SettingsPageHeader.qml (+94/-0)
src/app/webbrowser/history-model.cpp (+4/-0)
src/app/webbrowser/history-model.h (+2/-0)
src/app/webbrowser/urlManagement.js (+50/-0)
tests/unittests/history-model/tst_HistoryModelTests.cpp (+15/-0)
To merge this branch: bzr merge lp:~rpadovani/webbrowser-app/settings-page
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Olivier Tilloy Approve
Bill Filler (community) Approve
Review via email: mp+253975@code.launchpad.net

This proposal supersedes a proposal from 2015-03-24.

Commit message

Add settings page, per design specification. This adds qml-module-qt-labs-folderlistmodel and qml-module-qt-labs-settings as runtime dependencies for webbrowser-app.

Description of the change

Add settings page, per design specification.

To post a comment you must log in.
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 :

CI builds are failing because urlManagement.js is missing a copyright header.

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

Visually, this looks good except for the separation between the header and the list items. I think you should add a Divider there (https://developer.ubuntu.com/api/qml/sdk-14.10/Ubuntu.Components.ListItems.Divider/).

A few comments on the code itself:

 - In SettingsPage.qml there are a bunch of references to 'browser', which breaks encapsulation: the SettingsPage component isn’t aware of its parent context. To avoid this, you should add top-level properties to the component for each setting, and in Browser.qml where the component is instantiated, listen to changes on those properties and update the corresponding properties on the Browser instance.

 - Instead of a historyRemoved() signal, I would add a top-level 'historyModel' property, and have the corresponding action call clearAll() on the model. Also, the opacity shouldn’t be set to 0.5 after clearing. Instead, we need to add a 'count' property to the history model, and bind the opacity to its value being ≠ 0.

 - The back button should probably be implemented using an AbstractButton (and it should be highlighted when pressed, to match the standard header).

 - "Restore old session on startup" should probably be "Restore previous session at startup"

 - "Open new tab in background" should probably be "Allow opening new tabs in background"

 - The Column needs to be inside a Flickable, in case all entries don’t fit on the screen

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

browser.allowOpenInBackgroundTab is a string, not a boolean, so the following won’t work as expected:

    control: Switch {
        checked: browser.allowOpenInBackgroundTab
        onClicked: browser.allowOpenInBackgroundTab = checked;
    }

Instead, you’ll need to check that either the value is "true", or it’s "default" and the form factor is desktop.

review: Needs Fixing
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: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Riccardo Padovani (rpadovani) wrote :

Hi Olivier,
thanks for the fast review.

I think I addressed all your requests. I split all fixes in single commits so you can review them one by one, if you prefer

> CI builds are failing because urlManagement.js is missing a copyright header.

Added

> Visually, this looks good except for the separation between the header and the list items. I think you should add a Divider there (https://developer.ubuntu.com/api/qml/sdk-14.10/Ubuntu.Components.ListItems.Divider/).

Indeed, I added it, now looks like a classic page header

> In SettingsPage.qml there are a bunch of references to 'browser', which breaks encapsulation: the SettingsPage component isn’t aware of its parent context. To avoid this, you should add top-level properties to the component for each setting, and in Browser.qml where the component is instantiated, listen to changes on those properties and update the corresponding properties on the Browser instance.

I'm not sure I understood this, I hope I fixed well

> Instead of a historyRemoved() signal, I would add a top-level 'historyModel' property, and have the corresponding action call clearAll() on the model. Also, the opacity shouldn’t be set to 0.5 after clearing. Instead, we need to add a 'count' property to the history model, and bind the opacity to its value being ≠ 0.

Done. I also added a test, as you asked on IRC - it's the first unit test I write, so let me know if could be improved. Also, let me know if we need other tests

> The back button should probably be implemented using an AbstractButton (and it should be highlighted when pressed, to match the standard header).

The higlighted when pressed was a bit tricky to implement, mainly to reproduce the shadow as it's in other apps, with a leftMargin, but I think I achieved that

> "Restore old session on startup" should probably be "Restore previous session at startup"
> "Open new tab in background" should probably be "Allow opening new tabs in background"

My bad english strikes again :-)

> The Column needs to be inside a Flickable, in case all entries don’t fit on the screen

Indeed, I added it also to the privacy page, because I think it will expande in the future

> browser.allowOpenInBackgroundTab is a string, not a boolean

Right, fixed

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 :

A bunch of additional comments, in no particular order:

 - Can the 'use strict' statement go after the license and copyright header? It looks weird before it…

 - We need to make the current webview invisible while the settings page is visible, to avoid redrawing it uselessly (note that this isn’t done for the history view, but it should, really).
Setting 'visible: !settingsContainer.visible' on the top-level Item (line 108) that contains the chrome and the webviews should probably be good enough.
As a side note, run "QSG_VISUALIZE=overdraw ./src/app/webbrowser/webbrowser-app" to visualize when several opaque items overlap.

 - Clicking "Reset browser settings" when a value has just been changed won’t update the corresponding entry in the view (try e.g. to change the homepage and just after that reset the settings)

 - In the visual spec, it looks like all items have a thin divider at the bottom, so the "Reset browser settings" one probably needs one as well

 - The new unit test looks good, can you please also test the emission of the signal, by adding a QSignalSpy?

 -

review: Needs Fixing
Revision history for this message
Riccardo Padovani (rpadovani) wrote :

> - Can the 'use strict' statement go after the license and copyright header?
> It looks weird before it…

Ok

> - We need to make the current webview invisible while the settings page is
> visible, to avoid redrawing it uselessly (note that this isn’t done for the
> history view, but it should, really).
> Setting 'visible: !settingsContainer.visible' on the top-level Item (line 108)
> that contains the chrome and the webviews should probably be good enough.

Indeed it works. I know it isn't a good practice to mix code that should belong to a different branch, but since it's an half line change, I added also a flag for the history view - of course, if you don't agree, I remove that

> As a side note, run "QSG_VISUALIZE=overdraw ./src/app/webbrowser/webbrowser-
> app" to visualize when several opaque items overlap.

Thanks for the trick!

> - Clicking "Reset browser settings" when a value has just been changed won’t
> update the corresponding entry in the view (try e.g. to change the homepage
> and just after that reset the settings)

I'm totally lost on this one - I tried with all possible combination of browser.property and setting.property in Browser.qml, but nothing works.

Seems updating the property breaks the binding - only the view is broken, not the function itself.

Maybe after some sleep I'll be able to fix that, but every suggestion is welcome!

> - In the visual spec, it looks like all items have a thin divider at the
> bottom, so the "Reset browser settings" one probably needs one as well

I added it, but I totally don't like it: it's a divider, if there isn't anything to divide 'cause it's the last element, why do we have to add it? :-)

> - The new unit test looks good, can you please also test the emission of the
> signal, by adding a QSignalSpy?

Added

Also, I implemented the search engine choice, works as a charm (at least with DuckDuckGo).
Your solution is very elegant and very simple - I just have to don't think how much time I spent on C++ model (actually, I learned a lot, so I'm happy) :-)

I didn't add yet the search engine files, which ones do we want?

Could I suggest Google, Bing, Yahoo and DuckDuckGo?

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 :

> Indeed it works. I know it isn't a good practice to mix code that should
> belong to a different branch, but since it's an half line change, I added also
> a flag for the history view - of course, if you don't agree, I remove that

Very good initiative!

> > - Clicking "Reset browser settings" when a value has just been changed
> won’t
> > update the corresponding entry in the view (try e.g. to change the homepage
> > and just after that reset the settings)
>
> I'm totally lost on this one - I tried with all possible combination of
> browser.property and setting.property in Browser.qml, but nothing works.
>
> Seems updating the property breaks the binding - only the view is broken, not
> the function itself.
>
> Maybe after some sleep I'll be able to fix that, but every suggestion is
> welcome!

I’ll take a closer look now.

> > - In the visual spec, it looks like all items have a thin divider at the
> > bottom, so the "Reset browser settings" one probably needs one as well
>
> I added it, but I totally don't like it: it's a divider, if there isn't
> anything to divide 'cause it's the last element, why do we have to add it? :-)

I don’t like it either, but let’s stick to the visual spec, it’s easy enough to change it later on.

> Also, I implemented the search engine choice, works as a charm (at least with
> DuckDuckGo).
> Your solution is very elegant and very simple - I just have to don't think how
> much time I spent on C++ model (actually, I learned a lot, so I'm happy) :-)
>
> I didn't add yet the search engine files, which ones do we want?
>
> Could I suggest Google, Bing, Yahoo and DuckDuckGo?

Yeah, I’d like to add more search engines to the list by default, but that will require more work as we would probably need to monitor several folders (system-wide + locally installed by the user), so let’s do that separately.

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

Note: a bunch of branches were merged earlier today, and now there are minor conflicts in Browser.qml when merging this branch into trunk. Can you please address them?

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

In SettingsPage.qml, because you refer to dataLocation, you should "import Ubuntu.Web 0.2".

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

Since we’re using the FolderListModel now, we need to add qml-module-qt-labs-folderlistmodel as a runtime dependency of the webbrowser-app binary package (in debian/control, before line 39).

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

Indeed changing an option in the settings page breaks the initial property binding, and thus the properties are not reset in the settings page (even though their values are correctly reset).

We need to tackle this differently: the SettingsPage object needs a 'settings' property, which will be the shared Settings instance, and changing values there will update the properties on the shared instance directly, instead of going through an additional indirection. I have update my qt-labs-settings branch to allow that, please merge it into yours.
As well as getting rid of most of the top-level properties on the SettingsPage component (except historyModel, which is still needed), this will also allow us to remove the restoreDefaults signal.

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

> > Could I suggest Google, Bing, Yahoo and DuckDuckGo?
>
> Yeah, I’d like to add more search engines to the list by default, but that
> will require more work as we would probably need to monitor several folders
> (system-wide + locally installed by the user), so let’s do that separately.

Until we enable that (and we ensure that at least two options are shipped by default), we’ll need to hide the Search Engines entry in the settings if we detect that the number of xml files in the searchengines/ folder is < 2. So the FolderListModel instance needs to move a few levels higher in the hierarchy and gain an id in order to be referenced from various places in the SettingsPage component.

review: Needs Fixing
Revision history for this message
Riccardo Padovani (rpadovani) wrote :

I think I addressed all your comments. Now the branch is synced both upstream and with your branch

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

I managed to get in a situation where two search engine entries are checked:

 - open settings
 - click "Search Engines"
 - verify that e.g. the first one is checked
 - check the second one
 - click "Search Engines" again
 - check the first one
 - click "Search Engines" again

 -> both entries are checked

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

"Reset browser settings" doesn’t seem to reset the default values for checkbox options like "Restore previous session at startup" and "Allow opening new tabs in background". You might need to bind the checked value of the checkbox to the value of the setting itself.

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

Fixed all problems plus things we discussed on IRC :-)

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

201 + subText: settingsObject.searchEngine

this is the filename (minus the .xml extension), not very user friendly. Instead, the subtext should have the display name of the engine, for which we need to instantiate a SearchEngine object:

    SearchEngine {
        id: currentSearchEngine
        filename: settingsObject.searchEngine
    }
    subText: currentSearchEngine.name

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

In src/app/webbrowser/Browser.qml, a blank line was removed, and one extra line was added. Can you please revert those two changes?

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

In src/app/webbrowser/SettingsPageHeader.qml:

 - Does the actual header really need to be a ListItem.Empty ? Can’t it just be a Rectangle?

 - The 'text' property should be of type string, not var

 - Can you rename 'trigger' to 'back' ?

 - Does the ListItem.Divider really need a coloured Rectangle inside itself? Isn’t it themed correctly by default?

 - The top-level item should be a Column, its anchors shouldn’t be defined in the definition of the component itself, and its height should probably be set to childrenRect.height.

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

> In src/app/webbrowser/SettingsPageHeader.qml:
>
> - Does the actual header really need to be a ListItem.Empty ? Can’t it just
> be a Rectangle?

Done

> - The 'text' property should be of type string, not var

Done

> - Can you rename 'trigger' to 'back' ?

Done

> - Does the ListItem.Divider really need a coloured Rectangle inside itself?
> Isn’t it themed correctly by default?

Yes, it needs the rectangle, otherwise when flickable goes under it it is semitrasparent

> - The top-level item should be a Column, its anchors shouldn’t be defined in
> the definition of the component itself, and its height should probably be set
> to childrenRect.height.

Done

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

In src/app/webbrowser/SettingsPage.qml:

 - Instead of having searchEngineContainer and privacyContainer, we could have one single subpageContainer, as we will never have two subpages instantiated at the same time anyway.

 - Most ListItems have an action defined with onTriggered, but the one for "Reset browser settings" only has onClicked. The result is the same, but can the code be made consistent (I don’t have any preference either way, other than the use of onClicked results in less code :))

 - Instead of directly setting the opacity on the list item for "Clear Browsing History", you should set the 'enabled' property to (historyModel.count > 0)

 - The code would be more readable if the header came before the flickable (just set 'clip' to true on the flickable, I’m told that rectangular clipping is cheap these days, and as a bonus you won’t need a coloured rectangle inside the divider in the header).

 - Just a personal preference: I would instantiate the Bindings next to the switches their refer to, for improved readability.

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

According to the visual spec, the font size for the header is x-large.

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

> - Instead of having searchEngineContainer and privacyContainer, we could have
> one single subpageContainer, as we will never have two subpages instantiated
> at the same time anyway.

And most importantly, that would allow us to set the visibility of the main settings page accordingly (i.e. hide it whenever there’s a subpage overlaid on top of it).

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 :

 - "clip: true" needs to be set on the flickables, not on the headers.

 - you can simplify the anchors of the header’s label a good deal by setting "anchors.verticalCenter: parent.verticalCenter"

 - instead of trying to set the visibility of the entire page (which won’t work indeed), you can set the visibility of the main header and of the main flickable

review: Needs Fixing
Revision history for this message
Riccardo Padovani (rpadovani) wrote :

Thanks, fixed :-)

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: Needs Fixing (continuous-integration)
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 :

Looks good now, thanks Riccardo!

review: Approve
Revision history for this message
Bill Filler (bfiller) wrote :

I found some problems with the Homepage setting after clicking on it:
1) autocaps should be turned off on the field (Qt.ImhNoAutoUppercase)
2) predictive text should be turned off on the field (Qt.ImhNoPredictiveText)
3) set the hint for url field so the keyboard will display .com keys (Qt.ImhUrlCharactersOnly) - actually doing this might mean you don't need 1) and 2) as it might happen automatically
4) Upon displaying the dialog you should request focus on the input field with the caret at the end of the line. This will cause osk to appear by default saving the user an extra click
5) after making a change to a new site and pressing "Save", it is not saved and does not appear in the Homepage field in the main Settings.

There is no visual difference between "Privacy" and "Reset browser settings", but one brings you to another page (Privacy) and the other executes an action immediately. I believe Privacy should have a expansion arrow (like Privacy >) such that it's clear it leads you to another page.

review: Needs Fixing
968. By Riccardo Padovani

Address Bill's comments

Revision history for this message
Riccardo Padovani (rpadovani) wrote :

Thanks for your review, Bill.

> I found some problems with the Homepage setting after clicking on it:
> 1) autocaps should be turned off on the field (Qt.ImhNoAutoUppercase)
> 2) predictive text should be turned off on the field (Qt.ImhNoPredictiveText)
> 3) set the hint for url field so the keyboard will display .com keys
> (Qt.ImhUrlCharactersOnly) - actually doing this might mean you don't need 1)
> and 2) as it might happen automatically

Right, done. I explicitly set all 3 properties

> 4) Upon displaying the dialog you should request focus on the input field with
> the caret at the end of the line. This will cause osk to appear by default
> saving the user an extra click

Right, done

> 5) after making a change to a new site and pressing "Save", it is not saved
> and does not appear in the Homepage field in the main Settings.

Sorry, I'm not able to reproduce that, works fine here.
Do you have any log? Does it happens only in some conditions?

> There is no visual difference between "Privacy" and "Reset browser settings",
> but one brings you to another page (Privacy) and the other executes an action
> immediately. I believe Privacy should have a expansion arrow (like Privacy >)
> such that it's clear it leads you to another page.

While I agree with you on that, Oliver and I chose to stick to design for the first implementation: we will probably remove also the divider after the last element, but at the moment we follow that slide: https://docs.google.com/presentation/d/1woHjO8K4iqyVZZlfQ4BXL0DhYbwkEmZ7wvcUhYzHDRk/edit#slide=id.g354125fb9_030

Of course, I can implement that if you know (not only for Privacy but also for search engines), just let me know.

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 :

Bill raised some good points, thanks for addressing them so quickly.
Following up on this, I think we should validate the contents of the homepage text field before saving them. For example, if I type something that is clearly not a URL (e.g. "foo bar baz"), it shouldn’t override the current value.

For this I think you’ll need to move the looksLikeAUrl() function from AddressBar.qml to urlManagement.js. We probably want to enable the "Save" button conditionnally only when the contents of the text field is a valid URL.

Revision history for this message
Bill Filler (bfiller) wrote :

All working well for me now. I can't reproduce the problem with changing the home page not showing up for me. Seems to work all the time now. +1 to land this.

review: Approve
969. By Riccardo Padovani

Check if a domain is valid in the homepage setting before saving it

Revision history for this message
Riccardo Padovani (rpadovani) wrote :

Thanks for the suggestion Olivier, I implemented that.

BTW, we should take a look to the function looksLikeAUrl(). It checks if tld.length is < 5 (.match(/\.[a-zA-Z]{2,4}$/))
Now TLD could be very long[0], so we should remove that restriction (also if they aren't used so much at the moment, the only one I know is com.google)

[0]: https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains#ICANN-era_generic_top-level_domains

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

> BTW, we should take a look to the function looksLikeAUrl(). It checks if
> tld.length is < 5 (.match(/\.[a-zA-Z]{2,4}$/))
> Now TLD could be very long[0], so we should remove that restriction (also if
> they aren't used so much at the moment, the only one I know is com.google)

Good point. Can you file a bug report to track the issue separately?

970. By Riccardo Padovani

Merge from trunk and resolve conflicts

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

Looks good now, thanks Riccardo!

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 'debian/control'
2--- debian/control 2015-04-07 17:11:13 +0000
3+++ debian/control 2015-04-07 17:11:13 +0000
4@@ -36,6 +36,7 @@
5 fonts-liberation,
6 liboxideqt-qmlplugin (>= 1.5),
7 libqt5sql5-sqlite,
8+ qml-module-qt-labs-folderlistmodel,
9 qml-module-qt-labs-settings,
10 qml-module-qtquick2 (>= 5.4) | qtdeclarative5-qtquick2-plugin (>= 5.4),
11 qml-module-qtquick-dialogs | qtdeclarative5-dialogs-plugin,
12
13=== modified file 'src/app/webbrowser/AddressBar.qml'
14--- src/app/webbrowser/AddressBar.qml 2015-03-25 14:12:12 +0000
15+++ src/app/webbrowser/AddressBar.qml 2015-04-07 17:11:13 +0000
16@@ -21,6 +21,7 @@
17 import Ubuntu.Components.Popups 1.0
18 import com.canonical.Oxide 1.0 as Oxide
19 import ".."
20+import "urlManagement.js" as UrlManagement
21
22 FocusScope {
23 id: addressbar
24@@ -88,7 +89,7 @@
25
26 readonly property bool reload: addressbar.activeFocus && addressbar.text &&
27 (addressbar.text == addressbar.actualUrl)
28- readonly property bool looksLikeAUrl: internal.looksLikeAUrl(addressbar.text.trim())
29+ readonly property bool looksLikeAUrl: UrlManagement.looksLikeAUrl(addressbar.text.trim())
30
31 name: addressbar.loading ? "stop" :
32 reload ? "reload" :
33@@ -235,38 +236,6 @@
34
35 property var securityCertificateDetails: null
36
37- function looksLikeAUrl(address) {
38- var terms = address.split(/\s/)
39- if (terms.length > 1) {
40- return false
41- }
42- if (address.substr(0, 1) == "/") {
43- return true
44- }
45- if (address.match(/^https?:\/\//i) ||
46- address.match(/^file:\/\//i) ||
47- address.match(/^[a-z]+:\/\//i)) {
48- return true
49- }
50- if (address.split('/', 1)[0].match(/\.[a-zA-Z]{2,4}$/)) {
51- return true
52- }
53- if (address.split('/', 1)[0].match(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)) {
54- return true
55- }
56- return false
57- }
58-
59- function fixUrl(address) {
60- var url = address
61- if (address.substr(0, 1) == "/") {
62- url = "file://" + address
63- } else if (address.indexOf("://") == -1) {
64- url = "http://" + address
65- }
66- return url
67- }
68-
69 function escapeHtmlEntities(query) {
70 return query.replace(/\W/, encodeURIComponent)
71 }
72@@ -278,8 +247,8 @@
73
74 function validate() {
75 var query = text.trim()
76- if (internal.looksLikeAUrl(query)) {
77- requestedUrl = internal.fixUrl(query)
78+ if (UrlManagement.looksLikeAUrl(query)) {
79+ requestedUrl = UrlManagement.fixUrl(query)
80 } else {
81 requestedUrl = internal.buildSearchUrl(query)
82 }
83
84=== modified file 'src/app/webbrowser/Browser.qml'
85--- src/app/webbrowser/Browser.qml 2015-04-07 17:11:13 +0000
86+++ src/app/webbrowser/Browser.qml 2015-04-07 17:11:13 +0000
87@@ -102,6 +102,7 @@
88
89 Item {
90 anchors.fill: parent
91+ visible: !settingsContainer.visible && !historyViewContainer.visible
92
93 TabChrome {
94 id: invisibleTabChrome
95@@ -245,6 +246,12 @@
96 iconName: "tab-new"
97 enabled: formFactor != "mobile"
98 onTriggered: browser.openUrlInNewTab("", true)
99+ },
100+ Action {
101+ objectName: "settings"
102+ text: i18n.tr("Settings")
103+ iconName: "settings"
104+ onTriggered: settingsComponent.createObject(settingsContainer)
105 }
106 ]
107 }
108@@ -478,6 +485,24 @@
109 }
110 }
111
112+ Item {
113+ id: settingsContainer
114+
115+ visible: children.length > 0
116+ anchors.fill: parent
117+
118+ Component {
119+ id: settingsComponent
120+
121+ SettingsPage {
122+ anchors.fill: parent
123+ historyModel: browser.historyModel
124+ settingsObject: settings
125+ onDone: destroy()
126+ }
127+ }
128+ }
129+
130 TabsModel {
131 id: tabsModel
132
133
134=== modified file 'src/app/webbrowser/CMakeLists.txt'
135--- src/app/webbrowser/CMakeLists.txt 2015-04-07 17:11:13 +0000
136+++ src/app/webbrowser/CMakeLists.txt 2015-04-07 17:11:13 +0000
137@@ -42,7 +42,7 @@
138 install(TARGETS ${WEBBROWSER_APP}
139 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
140
141-file(GLOB QML_FILES *.qml)
142+file(GLOB QML_FILES *.qml *.js)
143 install(FILES ${QML_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/webbrowser-app/webbrowser)
144
145 install(DIRECTORY assets
146
147=== added file 'src/app/webbrowser/SettingsPage.qml'
148--- src/app/webbrowser/SettingsPage.qml 1970-01-01 00:00:00 +0000
149+++ src/app/webbrowser/SettingsPage.qml 2015-04-07 17:11:13 +0000
150@@ -0,0 +1,277 @@
151+/*
152+ * Copyright 2015 Canonical Ltd.
153+ *
154+ * This file is part of webbrowser-app.
155+ *
156+ * webbrowser-app is free software; you can redistribute it and/or modify
157+ * it under the terms of the GNU General Public License as published by
158+ * the Free Software Foundation; version 3.
159+ *
160+ * webbrowser-app is distributed in the hope that it will be useful,
161+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
162+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
163+ * GNU General Public License for more details.
164+ *
165+ * You should have received a copy of the GNU General Public License
166+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
167+ */
168+
169+import QtQuick 2.0
170+import Qt.labs.folderlistmodel 2.1
171+import Qt.labs.settings 1.0
172+import Ubuntu.Components 1.1
173+import Ubuntu.Components.Popups 1.0
174+import Ubuntu.Components.ListItems 1.0 as ListItem
175+import Ubuntu.Web 0.2
176+import webbrowserapp.private 0.1
177+
178+import "urlManagement.js" as UrlManagement
179+
180+Item {
181+ id: settingsItem
182+
183+ property QtObject historyModel
184+ property Settings settingsObject
185+
186+ signal done()
187+
188+ Rectangle {
189+ anchors.fill: parent
190+ color: "#f6f6f6"
191+ }
192+
193+ FolderListModel {
194+ id: searchEngineFolder
195+ folder: dataLocation +"/searchengines"
196+ showDirs: false
197+ nameFilters: ["*.xml"]
198+ sortField: FolderListModel.Name
199+ }
200+
201+ SettingsPageHeader {
202+ id: title
203+
204+ onBack: settingsItem.done()
205+ text: i18n.tr("Settings")
206+ visible: !subpageContainer.visible
207+ }
208+
209+ Flickable {
210+ anchors {
211+ top: title.bottom
212+ left: parent.left
213+ right: parent.right
214+ bottom: parent.bottom
215+ }
216+
217+ visible: !subpageContainer.visible
218+ clip: true
219+ contentHeight: settingsCol.height
220+
221+ Column {
222+ id: settingsCol
223+
224+ width: parent.width
225+
226+ ListItem.Subtitled {
227+ SearchEngine {
228+ id: currentSearchEngine
229+ filename: settingsObject.searchEngine
230+ }
231+ text: i18n.tr("Search engine")
232+ subText: currentSearchEngine.name
233+
234+ visible: searchEngineFolder.count > 1
235+
236+ onClicked: searchEngineComponent.createObject(subpageContainer);
237+ }
238+
239+ ListItem.Subtitled {
240+ text: i18n.tr("Homepage")
241+ subText: settingsObject.homepage
242+
243+ onClicked: PopupUtils.open(homepageDialog)
244+ }
245+
246+ ListItem.Standard {
247+ text: i18n.tr("Restore previous session at startup")
248+ highlightWhenPressed: false
249+
250+ control: Switch {
251+ id: restoreSessionSwitch
252+ onClicked: settingsObject.restoreSession = checked;
253+ }
254+
255+ Binding {
256+ target: restoreSessionSwitch; property: "checked";
257+ value: settingsObject.restoreSession
258+ }
259+ }
260+
261+ ListItem.Standard {
262+ text: i18n.tr("Allow opening new tabs in background")
263+ highlightWhenPressed: false
264+
265+ control: Switch {
266+ id: allowOpenInBackgroundTabSwitch
267+
268+ onClicked: settingsObject.allowOpenInBackgroundTab = checked ? 'true' : 'false';
269+ }
270+
271+ Binding {
272+ target: allowOpenInBackgroundTabSwitch; property: "checked";
273+ value: settingsObject.allowOpenInBackgroundTab === 'true' ||
274+ (settingsObject.allowOpenInBackgroundTab === 'default' &&
275+ formFactor === "desktop")
276+ }
277+ }
278+
279+ ListItem.Standard {
280+ text: i18n.tr("Privacy")
281+
282+ onClicked: privacyComponent.createObject(subpageContainer);
283+ }
284+
285+ ListItem.Standard {
286+ text: i18n.tr("Reset browser settings")
287+ onClicked: settingsObject.restoreDefaults();
288+ }
289+ }
290+ }
291+
292+ Item {
293+ id: subpageContainer
294+
295+ visible: children.length > 0
296+ anchors.fill: parent
297+
298+ Component {
299+ id: searchEngineComponent
300+
301+ Item {
302+ id: searchEngineItem
303+ anchors.fill: parent
304+
305+ Rectangle {
306+ anchors.fill: parent
307+ color: "#f6f6f6"
308+ }
309+
310+ SettingsPageHeader {
311+ id: searchEngineTitle
312+
313+ onBack: searchEngineItem.destroy();
314+ text: i18n.tr("Search engine")
315+ }
316+
317+ ListView {
318+ anchors {
319+ top: searchEngineTitle.bottom
320+ left: parent.left
321+ right: parent.right
322+ bottom: parent.bottom
323+ }
324+
325+ model: searchEngineFolder
326+
327+ delegate: ListItem.Standard {
328+ SearchEngine {
329+ id: searchEngineDelegate
330+ filename: model.fileBaseName
331+ }
332+ text: searchEngineDelegate.name
333+
334+ control: CheckBox {
335+ checked: settingsObject.searchEngine == searchEngineDelegate.filename;
336+ onClicked: {
337+ settingsObject.searchEngine = searchEngineDelegate.filename;
338+ searchEngineItem.destroy();
339+ }
340+ }
341+ }
342+ }
343+ }
344+ }
345+
346+ Component {
347+ id: privacyComponent
348+
349+ Item {
350+ id: privacyItem
351+ anchors.fill: parent
352+
353+ Rectangle {
354+ anchors.fill: parent
355+ color: "#f6f6f6"
356+ }
357+
358+ SettingsPageHeader {
359+ id: privacyTitle
360+ onBack: privacyItem.destroy();
361+ text: i18n.tr("Privacy")
362+ }
363+
364+ Flickable {
365+ anchors {
366+ top: privacyTitle.bottom
367+ left: parent.left
368+ right: parent.right
369+ bottom: parent.bottom
370+ }
371+
372+ clip: true
373+
374+ contentHeight: privacyCol.height
375+
376+ Column {
377+ id: privacyCol
378+ width: parent.width
379+
380+ ListItem.Standard {
381+ text: i18n.tr("Clear Browsing History")
382+ onClicked: historyModel.clearAll();
383+ enabled: historyModel.count > 0
384+ }
385+ }
386+ }
387+ }
388+ }
389+ }
390+
391+ Component {
392+ id: homepageDialog
393+ Dialog {
394+ id: dialogue
395+ title: i18n.tr("Homepage")
396+
397+ Component.onCompleted: {
398+ homepageTextField.forceActiveFocus();
399+ homepageTextField.cursorPosition = homepageTextField.text.length
400+ }
401+
402+ TextField {
403+ id: homepageTextField
404+ text: settingsObject.homepage
405+ inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhUrlCharactersOnly
406+ }
407+
408+ Button {
409+ anchors { left: parent.left; right: parent.right }
410+ text: i18n.tr("Cancel")
411+ onClicked: PopupUtils.close(dialogue);
412+ }
413+
414+ Button {
415+ anchors { left: parent.left; right: parent.right }
416+ text: i18n.tr("Save")
417+ enabled: UrlManagement.looksLikeAUrl(homepageTextField.text)
418+ color: "#3fb24f"
419+ onClicked: {
420+ settingsObject.homepage = UrlManagement.fixUrl(homepageTextField.text);
421+ PopupUtils.close(dialogue);
422+ }
423+ }
424+ }
425+ }
426+}
427+
428
429=== added file 'src/app/webbrowser/SettingsPageHeader.qml'
430--- src/app/webbrowser/SettingsPageHeader.qml 1970-01-01 00:00:00 +0000
431+++ src/app/webbrowser/SettingsPageHeader.qml 2015-04-07 17:11:13 +0000
432@@ -0,0 +1,94 @@
433+ /*
434+ * Copyright 2015 Canonical Ltd.
435+ *
436+ * This file is part of webbrowser-app.
437+ *
438+ * webbrowser-app is free software; you can redistribute it and/or modify
439+ * it under the terms of the GNU General Public License as published by
440+ * the Free Software Foundation; version 3.
441+ *
442+ * webbrowser-app is distributed in the hope that it will be useful,
443+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
444+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
445+ * GNU General Public License for more details.
446+ *
447+ * You should have received a copy of the GNU General Public License
448+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
449+ */
450+import QtQuick 2.0
451+import Ubuntu.Components 1.1
452+import Ubuntu.Components.ListItems 1.0 as ListItem
453+
454+/*
455+ * Component to use as page header in settings page and subpages
456+ *
457+ * It has a back() signal fired when back button is pressed and a text
458+ * property to set the page title
459+ */
460+
461+Column {
462+ id: root
463+ signal back()
464+ property string text
465+
466+ height: childrenRect.height
467+
468+ anchors {
469+ left: parent.left
470+ right: parent.right
471+ }
472+
473+ Rectangle {
474+ id: title
475+
476+ height: units.gu(7) - divider.height
477+ anchors { left: parent.left; right: parent.right }
478+
479+ Rectangle {
480+ anchors.fill: parent
481+ color: "#f6f6f6"
482+ }
483+
484+ AbstractButton {
485+ id: backButton
486+ width: height
487+
488+ onTriggered: root.back()
489+ anchors {
490+ top: parent.top
491+ bottom: parent.bottom
492+ left: parent.left
493+ }
494+
495+ Rectangle {
496+ anchors.fill: parent
497+ anchors.leftMargin: units.gu(1)
498+ anchors.rightMargin: units.gu(1)
499+ color: "#E6E6E6"
500+ visible: parent.pressed
501+ }
502+
503+ Icon {
504+ name: "back"
505+ anchors {
506+ fill: parent
507+ topMargin: units.gu(2)
508+ bottomMargin: units.gu(2)
509+ }
510+ }
511+ }
512+
513+ Label {
514+ anchors {
515+ left: backButton.right
516+ verticalCenter: parent.verticalCenter
517+ }
518+ text: root.text
519+ fontSize: 'x-large'
520+ }
521+ }
522+
523+ ListItem.Divider {
524+ id: divider
525+ }
526+}
527
528=== modified file 'src/app/webbrowser/history-model.cpp'
529--- src/app/webbrowser/history-model.cpp 2014-08-05 17:39:44 +0000
530+++ src/app/webbrowser/history-model.cpp 2015-04-07 17:11:13 +0000
531@@ -222,6 +222,7 @@
532 m_entries.prepend(entry);
533 endInsertRows();
534 insertNewEntryInDatabase(entry);
535+ Q_EMIT rowCountChanged();
536 } else {
537 QVector<int> roles;
538 roles << Visits;
539@@ -281,6 +282,7 @@
540
541 removeByIndex(getEntryIndex(url));
542 removeEntryFromDatabaseByUrl(url);
543+ Q_EMIT rowCountChanged();
544 }
545
546 /*!
547@@ -298,6 +300,7 @@
548 }
549 }
550 removeEntriesFromDatabaseByDomain(domain);
551+ Q_EMIT rowCountChanged();
552 }
553
554 void HistoryModel::removeByIndex(int index)
555@@ -363,6 +366,7 @@
556 m_entries.clear();
557 endResetModel();
558 clearDatabase();
559+ Q_EMIT rowCountChanged();
560 }
561 }
562
563
564=== modified file 'src/app/webbrowser/history-model.h'
565--- src/app/webbrowser/history-model.h 2014-08-05 17:39:44 +0000
566+++ src/app/webbrowser/history-model.h 2015-04-07 17:11:13 +0000
567@@ -32,6 +32,7 @@
568 Q_OBJECT
569
570 Q_PROPERTY(QString databasePath READ databasePath WRITE setDatabasePath NOTIFY databasePathChanged)
571+ Q_PROPERTY(int count READ rowCount NOTIFY rowCountChanged)
572
573 Q_ENUMS(Roles)
574
575@@ -64,6 +65,7 @@
576
577 Q_SIGNALS:
578 void databasePathChanged() const;
579+ void rowCountChanged();
580
581 private:
582 QSqlDatabase m_database;
583
584=== added file 'src/app/webbrowser/urlManagement.js'
585--- src/app/webbrowser/urlManagement.js 1970-01-01 00:00:00 +0000
586+++ src/app/webbrowser/urlManagement.js 2015-04-07 17:11:13 +0000
587@@ -0,0 +1,50 @@
588+/*
589+ * Copyright 2015 Canonical Ltd.
590+ *
591+ * This file is part of webbrowser-app.
592+ *
593+ * webbrowser-app is free software; you can redistribute it and/or modify
594+ * it under the terms of the GNU General Public License as published by
595+ * the Free Software Foundation; version 3.
596+ *
597+ * webbrowser-app is distributed in the hope that it will be useful,
598+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
599+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
600+ * GNU General Public License for more details.
601+ *
602+ * You should have received a copy of the GNU General Public License
603+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
604+*/
605+'use strict';
606+
607+function fixUrl(address) {
608+ var url = address
609+ if (address.substr(0, 1) == "/") {
610+ url = "file://" + address
611+ } else if (address.indexOf("://") == -1) {
612+ url = "http://" + address
613+ }
614+ return url
615+}
616+
617+function looksLikeAUrl(address) {
618+ var terms = address.split(/\s/)
619+ if (terms.length > 1) {
620+ return false
621+ }
622+ if (address.substr(0, 1) == "/") {
623+ return true
624+ }
625+ if (address.match(/^https?:\/\//i) ||
626+ address.match(/^file:\/\//i) ||
627+ address.match(/^[a-z]+:\/\//i)) {
628+ return true
629+ }
630+ if (address.split('/', 1)[0].match(/\.[a-zA-Z]{2,4}$/)) {
631+ return true
632+ }
633+ if (address.split('/', 1)[0].match(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}/)) {
634+ return true
635+ }
636+ return false
637+}
638
639=== modified file 'tests/unittests/history-model/tst_HistoryModelTests.cpp'
640--- tests/unittests/history-model/tst_HistoryModelTests.cpp 2014-08-05 17:39:44 +0000
641+++ tests/unittests/history-model/tst_HistoryModelTests.cpp 2015-04-07 17:11:13 +0000
642@@ -243,6 +243,21 @@
643 QCOMPARE(model->rowCount(), 0);
644 }
645
646+ void shouldCountNumberOfEntries()
647+ {
648+ QSignalSpy spyCount(model, SIGNAL(rowCountChanged()));
649+ QCOMPARE(model->property("count").toInt(), 0);
650+ model->add(QUrl("http://example.org/"), "Example Domain", QUrl());
651+ QCOMPARE(model->property("count").toInt(), 1);
652+ QCOMPARE(spyCount.count(), 1);
653+ model->add(QUrl("http://example.com/"), "Example Domain", QUrl());
654+ QCOMPARE(model->property("count").toInt(), 2);
655+ QCOMPARE(spyCount.count(), 2);
656+ model->clearAll();
657+ QCOMPARE(model->property("count").toInt(), 0);
658+ QCOMPARE(spyCount.count(), 3);
659+ }
660+
661 };
662
663 QTEST_MAIN(HistoryModelTests)

Subscribers

People subscribed via source and target branches

to status/vote changes: