Merge lp:~rpadovani/webbrowser-app/settings-page into lp:webbrowser-app
- settings-page
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Olivier Tilloy | Approve | ||
Bill Filler (community) | Approve | ||
Review via email:
|
This proposal supersedes a proposal from 2015-03-24.
Commit message
Add settings page, per design specification. This adds qml-module-
Description of the change
Add settings page, per design specification.

PS Jenkins bot (ps-jenkins) wrote : | # |

Olivier Tilloy (osomon) wrote : | # |
CI builds are failing because urlManagement.js is missing a copyright header.

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:/
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

Olivier Tilloy (osomon) wrote : | # |
browser.
control: Switch {
checked: browser.
onClicked: browser.
}
Instead, you’ll need to check that either the value is "true", or it’s "default" and the form factor is desktop.

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:942
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:943
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:945
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

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:/
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.
Right, fixed

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:948
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

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: !settingsContai
As a side note, run "QSG_VISUALIZE=
- 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?
-

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: !settingsContai
> 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=
> 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?

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:953
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

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.

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?

Olivier Tilloy (osomon) wrote : | # |
In SettingsPage.qml, because you refer to dataLocation, you should "import Ubuntu.Web 0.2".

Olivier Tilloy (osomon) wrote : | # |
Can you please update the visuals of the Search Engine page to match https:/

Olivier Tilloy (osomon) wrote : | # |
Since we’re using the FolderListModel now, we need to add qml-module-

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.

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.

Riccardo Padovani (rpadovani) wrote : | # |
I think I addressed all your comments. Now the branch is synced both upstream and with your branch

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

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.

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:957
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

Riccardo Padovani (rpadovani) wrote : | # |
Fixed all problems plus things we discussed on IRC :-)

Olivier Tilloy (osomon) wrote : | # |
201 + subText: settingsObject.
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.
}
subText: currentSearchEn

Olivier Tilloy (osomon) wrote : | # |
In src/app/

Olivier Tilloy (osomon) wrote : | # |
In src/app/
- 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.

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:961
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

Riccardo Padovani (rpadovani) wrote : | # |
> In src/app/
>
> - 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.
Done

Olivier Tilloy (osomon) wrote : | # |
In src/app/
- Instead of having searchEngineCon
- 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.

Olivier Tilloy (osomon) wrote : | # |
According to the visual spec, the font size for the header is x-large.

Olivier Tilloy (osomon) wrote : | # |
> - Instead of having searchEngineCon
> 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).

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:964
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

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.
- 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

Riccardo Padovani (rpadovani) wrote : | # |
Thanks, fixed :-)

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:965
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:966
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
None: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:967
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

Olivier Tilloy (osomon) wrote : | # |
Looks good now, thanks Riccardo!

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.ImhNoAutoUp
2) predictive text should be turned off on the field (Qt.ImhNoPredic
3) set the hint for url field so the keyboard will display .com keys (Qt.ImhUrlChara
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.
- 968. By Riccardo Padovani
-
Address Bill's comments

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.ImhNoAutoUp
> 2) predictive text should be turned off on the field (Qt.ImhNoPredic
> 3) set the hint for url field so the keyboard will display .com keys
> (Qt.ImhUrlChara
> 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:/
Of course, I can implement that if you know (not only for Privacy but also for search engines), just let me know.

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:968
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

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.

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.
- 969. By Riccardo Padovani
-
Check if a domain is valid in the homepage setting before saving it

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(
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:/

Olivier Tilloy (osomon) wrote : | # |
> BTW, we should take a look to the function looksLikeAUrl(). It checks if
> tld.length is < 5 (.match(
> 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

Olivier Tilloy (osomon) wrote : | # |
Looks good now, thanks Riccardo!

PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:970
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
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) |
FAILED: Continuous integration, rev:939 jenkins. qa.ubuntu. com/job/ webbrowser- app-ci/ 1561/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 1949/console jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- amd64-ci/ 319/console jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- armhf-ci/ 319/console jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- i386-ci/ 319/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 1947/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/webbrowser- app-ci/ 1561/rebuild
http://