address-book-app-0.2+16.04.20160323/ 0000755 0000156 0000165 00000000000 12674534512 017060 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/COPYING.CC 0000644 0000156 0000165 00000053336 12674534204 020407 0 ustar pbuser pbgroup 0000000 0000000 Creative Commons Legal Code
Attribution-ShareAlike 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and
other pre-existing works, such as a translation, adaptation,
derivative work, arrangement of music or other alterations of a
literary or artistic work, or phonogram or performance and includes
cinematographic adaptations or any other form in which the Work may be
recast, transformed, or adapted including in any form recognizably
derived from the original, except that a work that constitutes a
Collection will not be considered an Adaptation for the purpose of
this License. For the avoidance of doubt, where the Work is a musical
work, performance or phonogram, the synchronization of the Work in
timed-relation with a moving image ("synching") will be considered an
Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as
encyclopedias and anthologies, or performances, phonograms or
broadcasts, or other works or subject matter other than works listed
in Section 1(f) below, which, by reason of the selection and
arrangement of their contents, constitute intellectual creations, in
which the Work is included in its entirety in unmodified form along
with one or more other contributions, each constituting separate and
independent works in themselves, which together are assembled into a
collective whole. A work that constitutes a Collection will not be
considered an Adaptation (as defined below) for the purposes of this
License.
c. "Creative Commons Compatible License" means a license that is listed
at http://creativecommons.org/compatiblelicenses that has been
approved by Creative Commons as being essentially equivalent to this
License, including, at a minimum, because that license: (i) contains
terms that have the same purpose, meaning and effect as the License
Elements of this License; and, (ii) explicitly permits the relicensing
of adaptations of works made available under that license under this
License or a Creative Commons jurisdiction license with the same
License Elements as this License.
d. "Distribute" means to make available to the public the original and
copies of the Work or Adaptation, as appropriate, through sale or
other transfer of ownership.
e. "License Elements" means the following high-level license attributes
as selected by Licensor and indicated in the title of this License:
Attribution, ShareAlike.
f. "Licensor" means the individual, individuals, entity or entities that
offer(s) the Work under the terms of this License.
g. "Original Author" means, in the case of a literary or artistic work,
the individual, individuals, entity or entities who created the Work
or if no individual or entity can be identified, the publisher; and in
addition (i) in the case of a performance the actors, singers,
musicians, dancers, and other persons who act, sing, deliver, declaim,
play in, interpret or otherwise perform literary or artistic works or
expressions of folklore; (ii) in the case of a phonogram the producer
being the person or legal entity who first fixes the sounds of a
performance or other sounds; and, (iii) in the case of broadcasts, the
organization that transmits the broadcast.
h. "Work" means the literary and/or artistic work offered under the terms
of this License including without limitation any production in the
literary, scientific and artistic domain, whatever may be the mode or
form of its expression including digital form, such as a book,
pamphlet and other writing; a lecture, address, sermon or other work
of the same nature; a dramatic or dramatico-musical work; a
choreographic work or entertainment in dumb show; a musical
composition with or without words; a cinematographic work to which are
assimilated works expressed by a process analogous to cinematography;
a work of drawing, painting, architecture, sculpture, engraving or
lithography; a photographic work to which are assimilated works
expressed by a process analogous to photography; a work of applied
art; an illustration, map, plan, sketch or three-dimensional work
relative to geography, topography, architecture or science; a
performance; a broadcast; a phonogram; a compilation of data to the
extent it is protected as a copyrightable work; or a work performed by
a variety or circus performer to the extent it is not otherwise
considered a literary or artistic work.
i. "You" means an individual or entity exercising rights under this
License who has not previously violated the terms of this License with
respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous
violation.
j. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or
process, including by wire or wireless means or public digital
performances; to make available to the public Works in such a way that
members of the public may access these Works from a place and at a
place individually chosen by them; to perform the Work to the public
by any means or process and the communication to the public of the
performances of the Work, including by public digital performance; to
broadcast and rebroadcast the Work by any means including signs,
sounds or images.
k. "Reproduce" means to make copies of the Work by any means including
without limitation by sound or visual recordings and the right of
fixation and reproducing fixations of the Work, including storage of a
protected performance or phonogram in digital form or other electronic
medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
limit, or restrict any uses free from copyright or rights arising from
limitations or exceptions that are provided for in connection with the
copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more
Collections, and to Reproduce the Work as incorporated in the
Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation,
including any translation in any medium, takes reasonable steps to
clearly label, demarcate or otherwise identify that changes were made
to the original Work. For example, a translation could be marked "The
original work was translated from English to Spanish," or a
modification could indicate "The original work has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated
in Collections; and,
d. to Distribute and Publicly Perform Adaptations.
e. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme cannot be waived, the Licensor
reserves the exclusive right to collect such royalties for any
exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme can be waived, the Licensor waives the
exclusive right to collect such royalties for any exercise by You
of the rights granted under this License; and,
iii. Voluntary License Schemes. The Licensor waives the right to
collect royalties, whether individually or, in the event that the
Licensor is a member of a collecting society that administers
voluntary licensing schemes, via that society, from any exercise
by You of the rights granted under this License.
The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights in
other media and formats. Subject to Section 8(f), all rights not expressly
granted by Licensor are hereby reserved.
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms
of this License. You must include a copy of, or the Uniform Resource
Identifier (URI) for, this License with every copy of the Work You
Distribute or Publicly Perform. You may not offer or impose any terms
on the Work that restrict the terms of this License or the ability of
the recipient of the Work to exercise the rights granted to that
recipient under the terms of the License. You may not sublicense the
Work. You must keep intact all notices that refer to this License and
to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Work, You may not impose any effective technological
measures on the Work that restrict the ability of a recipient of the
Work from You to exercise the rights granted to that recipient under
the terms of the License. This Section 4(a) applies to the Work as
incorporated in a Collection, but this does not require the Collection
apart from the Work itself to be made subject to the terms of this
License. If You create a Collection, upon notice from any Licensor You
must, to the extent practicable, remove from the Collection any credit
as required by Section 4(c), as requested. If You create an
Adaptation, upon notice from any Licensor You must, to the extent
practicable, remove from the Adaptation any credit as required by
Section 4(c), as requested.
b. You may Distribute or Publicly Perform an Adaptation only under the
terms of: (i) this License; (ii) a later version of this License with
the same License Elements as this License; (iii) a Creative Commons
jurisdiction license (either this or a later license version) that
contains the same License Elements as this License (e.g.,
Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
License. If you license the Adaptation under one of the licenses
mentioned in (iv), you must comply with the terms of that license. If
you license the Adaptation under the terms of any of the licenses
mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
comply with the terms of the Applicable License generally and the
following provisions: (I) You must include a copy of, or the URI for,
the Applicable License with every copy of each Adaptation You
Distribute or Publicly Perform; (II) You may not offer or impose any
terms on the Adaptation that restrict the terms of the Applicable
License or the ability of the recipient of the Adaptation to exercise
the rights granted to that recipient under the terms of the Applicable
License; (III) You must keep intact all notices that refer to the
Applicable License and to the disclaimer of warranties with every copy
of the Work as included in the Adaptation You Distribute or Publicly
Perform; (IV) when You Distribute or Publicly Perform the Adaptation,
You may not impose any effective technological measures on the
Adaptation that restrict the ability of a recipient of the Adaptation
from You to exercise the rights granted to that recipient under the
terms of the Applicable License. This Section 4(b) applies to the
Adaptation as incorporated in a Collection, but this does not require
the Collection apart from the Adaptation itself to be made subject to
the terms of the Applicable License.
c. If You Distribute, or Publicly Perform the Work or any Adaptations or
Collections, You must, unless a request has been made pursuant to
Section 4(a), keep intact all copyright notices for the Work and
provide, reasonable to the medium or means You are utilizing: (i) the
name of the Original Author (or pseudonym, if applicable) if supplied,
and/or if the Original Author and/or Licensor designate another party
or parties (e.g., a sponsor institute, publishing entity, journal) for
attribution ("Attribution Parties") in Licensor's copyright notice,
terms of service or by other reasonable means, the name of such party
or parties; (ii) the title of the Work if supplied; (iii) to the
extent reasonably practicable, the URI, if any, that Licensor
specifies to be associated with the Work, unless such URI does not
refer to the copyright notice or licensing information for the Work;
and (iv) , consistent with Ssection 3(b), in the case of an
Adaptation, a credit identifying the use of the Work in the Adaptation
(e.g., "French translation of the Work by Original Author," or
"Screenplay based on original Work by Original Author"). The credit
required by this Section 4(c) may be implemented in any reasonable
manner; provided, however, that in the case of a Adaptation or
Collection, at a minimum such credit will appear, if a credit for all
contributing authors of the Adaptation or Collection appears, then as
part of these credits and in a manner at least as prominent as the
credits for the other contributing authors. For the avoidance of
doubt, You may only use the credit required by this Section for the
purpose of attribution in the manner set out above and, by exercising
Your rights under this License, You may not implicitly or explicitly
assert or imply any connection with, sponsorship or endorsement by the
Original Author, Licensor and/or Attribution Parties, as appropriate,
of You or Your use of the Work, without the separate, express prior
written permission of the Original Author, Licensor and/or Attribution
Parties.
d. Except as otherwise agreed in writing by the Licensor or as may be
otherwise permitted by applicable law, if You Reproduce, Distribute or
Publicly Perform the Work either by itself or as part of any
Adaptations or Collections, You must not distort, mutilate, modify or
take other derogatory action in relation to the Work which would be
prejudicial to the Original Author's honor or reputation. Licensor
agrees that in those jurisdictions (e.g. Japan), in which any exercise
of the right granted in Section 3(b) of this License (the right to
make Adaptations) would be deemed to be a distortion, mutilation,
modification or other derogatory action prejudicial to the Original
Author's honor and reputation, the Licensor will waive or not assert,
as appropriate, this Section, to the fullest extent permitted by the
applicable national law, to enable You to reasonably exercise Your
right under Section 3(b) of this License (right to make Adaptations)
but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate
automatically upon any breach by You of the terms of this License.
Individuals or entities who have received Adaptations or Collections
from You under this License, however, will not have their licenses
terminated provided such individuals or entities remain in full
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is
perpetual (for the duration of the applicable copyright in the Work).
Notwithstanding the above, Licensor reserves the right to release the
Work under different license terms or to stop distributing the Work at
any time; provided, however that any such election will not serve to
withdraw this License (or any other license that has been, or is
required to be, granted under the terms of this License), and this
License will continue in full force and effect unless terminated as
stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection,
the Licensor offers to the recipient a license to the Work on the same
terms and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
offers to the recipient a license to the original Work on the same
terms and conditions as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this License, and without further action
by the parties to this agreement, such provision shall be reformed to
the minimum extent necessary to make such provision valid and
enforceable.
d. No term or provision of this License shall be deemed waived and no
breach consented to unless such waiver or consent shall be in writing
and signed by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with
respect to the Work licensed here. There are no understandings,
agreements or representations with respect to the Work not specified
here. Licensor shall not be bound by any additional provisions that
may appear in any communication from You. This License may not be
modified without the mutual written agreement of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this
License were drafted utilizing the terminology of the Berne Convention
for the Protection of Literary and Artistic Works (as amended on
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
and the Universal Copyright Convention (as revised on July 24, 1971).
These rights and subject matter take effect in the relevant
jurisdiction in which the License terms are sought to be enforced
according to the corresponding provisions of the implementation of
those treaty provisions in the applicable national law. If the
standard suite of rights granted under applicable copyright law
includes additional rights not granted under this License, such
additional rights are deemed to be included in the License; this
License is not intended to restrict the license of any rights under
applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty
whatsoever in connection with the Work. Creative Commons will not be
liable to You or any party on any legal theory for any damages
whatsoever, including without limitation any general, special,
incidental or consequential damages arising in connection to this
license. Notwithstanding the foregoing two (2) sentences, if Creative
Commons has expressly identified itself as the Licensor hereunder, it
shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the
Work is licensed under the CCPL, Creative Commons does not authorize
the use by either party of the trademark "Creative Commons" or any
related trademark or logo of Creative Commons without the prior
written consent of Creative Commons. Any permitted use will be in
compliance with Creative Commons' then-current trademark usage
guidelines, as may be published on its website or otherwise made
available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of the License.
Creative Commons may be contacted at http://creativecommons.org/.
address-book-app-0.2+16.04.20160323/cmake/ 0000755 0000156 0000165 00000000000 12674534512 020140 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/cmake/autopilot.cmake 0000644 0000156 0000165 00000002455 12674534204 023166 0 ustar pbuser pbgroup 0000000 0000000
option(ENABLE_AUTOPILOT "Enable or Disable autopilot tests" On)
option(AUTOPILOT_RECORD "Enable or Disable autopilot record tests" OFF)
option(AUTOPILOT_RECORD_PATH "Directory to put recorded tests" OFF)
if(ENABLE_AUTOPILOT)
find_program(AUTOPILOT_BIN autopilot)
if(AUTOPILOT_BIN)
message(STATUS "Autopilot tests enabled.")
else()
message(STATUS "Autopilot tests disabled: autopilot binary not found")
endif()
endif()
if(AUTOPILOT_RECORD OR AUTOPILOT_RECORD_PATH)
find_program(AUTOPILOT_REC_BIN recordmydesktop)
if(AUTOPILOT_REC_BIN)
message(STATUS "Record autopilot enabled")
if(AUTOPILOT_RECORD_PATH)
message(STATUS "Save autopilot tests video in: ${AUTOPILOT_RECORD_PATH}")
set(AUTOPILOT_TESTS_ARGS -r -rd ${AUTOPILOT_RECORD_PATH})
else()
set(AUTOPILOT_TESTS_ARGS -r)
endif()
else()
message(STATUS "recordmydesktop necessary for record autopilot tests not found.")
set(AUTOPILOT_TESTS_ARGS "")
endif()
endif()
function(declare_autopilot_test ENVIROMENT TEST_NAME WORKING_DIR)
add_custom_target(autopilot)
add_custom_command(TARGET autopilot
COMMAND ${ENVIROMENT} autopilot run ${TEST_NAME} ${AUTOPILOT_TESTS_ARGS}
WORKING_DIRECTORY ${WORKING_DIR})
endfunction()
address-book-app-0.2+16.04.20160323/tests/ 0000755 0000156 0000165 00000000000 12674534512 020222 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/qml/ 0000755 0000156 0000165 00000000000 12674534512 021013 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/qml/tst_ContactAvatar.qml 0000644 0000156 0000165 00000013720 12674534204 025153 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import QtTest 1.0
import Ubuntu.Test 0.1
import Ubuntu.Contacts 0.1
import "ContactUtil.js" as ContactUtilJS
Item {
id: root
width: units.gu(40)
height: units.gu(80)
UbuntuTestCase {
id: contactAvatarTestCase
name: 'contactAvatarTestCase'
property var avatarComponent: null
when: windowShown
function createContact(firstName, avatarUrl)
{
var details = [
{detail: 'Name', field: 'firstName', value: firstName },
{detail: 'Avatar', field: 'imageUrl', value: avatarUrl }
];
return ContactUtilJS.createContact(details, contactAvatarTestCase)
}
function init()
{
avatarComponent = Qt.createQmlObject('import Ubuntu.Contacts 0.1; ContactAvatar{ height: 100; width: 100; }', root);
}
function cleanup()
{
if (avatarComponent) {
avatarComponent.destroy()
avatarComponent = null
}
}
function test_initalState()
{
compare(avatarComponent.displayName, avatarComponent.fallbackDisplayName)
compare(avatarComponent.avatarUrl, avatarComponent.fallbackAvatarUrl)
compare(avatarComponent.showAvatarPicture, true)
}
function test_initials_and_fallback_name()
{
var contactName = "Name Lastname"
avatarComponent.fallbackDisplayName = contactName
compare(avatarComponent.displayName, contactName)
compare(avatarComponent.initials, "NL")
contactName = "Fullname"
avatarComponent.fallbackDisplayName = contactName
compare(avatarComponent.displayName, contactName)
compare(avatarComponent.initials, "F")
contactName = "3212300"
avatarComponent.fallbackDisplayName = contactName
compare(avatarComponent.displayName, contactName)
compare(avatarComponent.initials, "")
contactName = "$@"
avatarComponent.fallbackDisplayName = contactName
compare(avatarComponent.displayName, contactName)
compare(avatarComponent.initials, "")
contactName = ""
avatarComponent.fallbackDisplayName = contactName
compare(avatarComponent.displayName, contactName)
compare(avatarComponent.initials, "")
}
function test_show_avatar_with_fallback_name()
{
var fallbackName = "Name Lastname"
avatarComponent.fallbackDisplayName = fallbackName
compare(avatarComponent.showAvatarPicture, false)
}
function test_show_avatar_with_contact_and_fallback_name()
{
var fallbackName = "Name Lastname"
avatarComponent.contactElement = createContact("", "")
avatarComponent.fallbackDisplayName = fallbackName
compare(avatarComponent.showAvatarPicture, false)
}
function test_show_avatar_with_named_contact()
{
avatarComponent.contactElement = createContact("Name Lastname", "")
compare(avatarComponent.showAvatarPicture, false)
}
function test_show_avatar_with_contact_empty_name_valid_image()
{
avatarComponent.contactElement = createContact("", "image://theme/contact")
compare(avatarComponent.showAvatarPicture, true)
}
function test_show_avatar_with_contact_with_name_and_image()
{
avatarComponent.contactElement = createContact("My Name", "image://theme/my_image")
compare(avatarComponent.showAvatarPicture, true)
}
function test_show_avatar_with_contact_with_especial_name_and_image()
{
avatarComponent.contactElement = createContact("3214567", "")
compare(avatarComponent.showAvatarPicture, true)
}
function test_show_avatar_after_update_contact_()
{
var contact = createContact("My Name", "")
avatarComponent.contactElement = contact
compare(avatarComponent.showAvatarPicture, false)
var avatarDetail = contact.detail(ContactDetail.Avatar)
avatarDetail.imageUrl = "image://theme/contact"
avatarComponent.reload()
compare(avatarComponent.showAvatarPicture, true)
}
function test_image_visibility()
{
waitForRendering(avatarComponent);
var avatarInitials = findChild(avatarComponent, "avatarInitials")
var avatarImage = findChild(avatarComponent, "avatarImage")
var avatarFallback = findChild(avatarComponent, "fallbackIcon")
avatarComponent.showAvatarPicture = true
tryCompare(avatarInitials, "visible", false)
tryCompare(avatarImage, "source", "")
tryCompare(avatarFallback, "visible", true)
tryCompare(avatarFallback, "source", avatarComponent.fallbackAvatarUrl)
avatarComponent.showAvatarPicture = false
tryCompare(avatarImage, "source", "")
tryCompare(avatarImage, "status", Image.Null)
tryCompare(avatarFallback, "visible", false)
tryCompare(avatarFallback, "source", "")
tryCompare(avatarInitials, "visible", true)
}
}
}
address-book-app-0.2+16.04.20160323/tests/qml/tst_ContactList.qml 0000644 0000156 0000165 00000007421 12674534204 024651 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtTest 1.0
import Ubuntu.Components 1.3
import Ubuntu.Test 0.1
import Ubuntu.Contacts 0.1
import "ContactUtil.js" as ContactUtilJS
import '../../src/imports/'
Item {
id: root
property var application
property var contactListPageObj
width: units.gu(40)
height: units.gu(80)
application: QtObject {
id: appMock
property string callbackApplication: ""
property bool firstRun: true
property bool disableOnlineAccounts: true
function elapsed()
{
}
function unsetFirstRun()
{
}
}
Component {
id: contactListCmp
ABContactListPage {
id: contactListPage
anchors.fill: parent
}
}
MainView {
id: mainView
anchors.fill: parent
}
function createContact(firstName, phoneNumber, email) {
var details = [
{detail: 'PhoneNumber', field: 'number', value: phoneNumber},
{detail: 'EmailAddress', field: 'emailAddress', value: email},
{detail: 'Name', field: 'firstName', value: firstName}
];
return ContactUtilJS.createContact(details, mainView)
}
UbuntuTestCase {
id: contactListTestCase
name: 'contactListTestCase'
when: windowShown
function init()
{
root.contactListPageObj = contactListCmp.createObject(mainView, {"contactManager": "memory"})
waitForRendering(root.contactListPageObj)
}
function cleanup()
{
root.contactListPageObj.destroy()
}
function test_title()
{
tryCompare(root.contactListPageObj, "title", "Contacts")
}
function test_managerProperty()
{
tryCompare(root.contactListPageObj, "contactManager", "memory")
}
function test_pickMode()
{
var listView = findChild(root.contactListPageObj, "contactListView")
// check initial state
compare(root.contactListPageObj.pickMode, false)
compare(root.contactListPageObj.pickMultipleContacts, false)
// by default the list accepts multi-selection but the selection mode is disabled
compare(listView.multipleSelection, true)
compare(listView.isInSelectionMode, false)
// start multi-selection pick mode
root.contactListPageObj.startPickMode(false /*isSingle*/, null)
// check multi-selection mode
compare(root.contactListPageObj.pickMode, true)
compare(root.contactListPageObj.pickMultipleContacts, true)
compare(listView.multipleSelection, true)
compare(listView.isInSelectionMode, true)
// start single-selection pick mode
root.contactListPageObj.startPickMode(true /*isSingle*/, null)
// check single-selection mode
compare(root.contactListPageObj.pickMode, true)
compare(root.contactListPageObj.pickMultipleContacts, false)
compare(listView.multipleSelection, false)
compare(listView.isInSelectionMode, true)
}
}
}
address-book-app-0.2+16.04.20160323/tests/qml/tst_ContactPreviewPage.qml 0000644 0000156 0000165 00000025400 12674534204 026151 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtTest 1.0
import Ubuntu.Test 0.1
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Contacts 0.1
import Ubuntu.AddressBook.ContactView 0.1
import "ContactUtil.js" as ContactUtilJS
Item {
id: root
function createContactWithName() {
var details = [
{detail: 'Name', field: 'firstName', value: 'Fulano'},
];
return ContactUtilJS.createContact(details, root)
}
function createContactWithNameAndAvatar() {
var details = [
{detail: 'Name', field: 'firstName', value: 'Fulano'},
{detail: 'Avatar', field: 'imageUrl', value: 'image://theme/address-book-app'}
];
return ContactUtilJS.createContact(details, root)
}
function createSignalSpy(target, signalName) {
var spy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', root, "")
spy.target = target
spy.signalName = signalName
return spy
}
VCardParser {
id: vcardParser
vCardUrl: Qt.resolvedUrl("../data/vcard_single.vcf")
}
width: units.gu(40)
height: units.gu(80)
MainView {
id: mainView
anchors.fill: parent
ContactViewPage {
id: contactPreviewPage
anchors.fill: parent
}
}
UbuntuTestCase {
id: contactPreviewPageTestCase
name: 'contactPreviewPageTestCase'
when: windowShown
function findChildOfType(obj, typeName) {
var childs = new Array(0)
var result = new Array(0)
childs.push(obj)
while (childs.length > 0) {
var objTypeName = String(childs[0]).split("_")[0]
if (objTypeName === typeName) {
result.push(childs[0])
}
for (var i in childs[0].children) {
childs.push(childs[0].children[i])
}
childs.splice(0, 1)
}
return result
}
function init()
{
waitForRendering(mainView);
contactPreviewPage.contact = null
}
function test_preview_contact_with_name()
{
contactPreviewPage.contact = createContactWithName()
tryCompare(contactPreviewPage.header, "title", "Fulano")
}
function test_preview_contact_with_name_and_avatar()
{
contactPreviewPage.contact = createContactWithNameAndAvatar()
tryCompare(contactPreviewPage.header, "title", "Fulano")
var avatarField = findChild(root, "contactAvatarDetail")
tryCompare(avatarField, "avatarUrl", "image://theme/address-book-app")
}
function test_preview_with_full_contact()
{
compare(vcardParser.contacts.length, 1)
var contact = vcardParser.contacts[0]
contactPreviewPage.contact = contact
tryCompare(contactPreviewPage.header, "title", "Forrest Gump")
// PhoneNumbers
// TEL;TYPE=WORK,VOICE:(111) 555-12121
// TEL;TYPE=HOME,VOICE:(404) 555-1212
// number of phones
var phoneNumberGroup = findChild(root, "phones")
var phoneNumbers = findChildOfType(phoneNumberGroup, "BasicFieldView")
compare(phoneNumbers.length, 2)
// first phone
var phoneNumber = findChild(phoneNumberGroup, "label_phoneNumber_0.0")
var phoneNumberType = findChild(phoneNumberGroup, "type_phoneNumber_0")
compare(phoneNumber.text, "(111) 555-12121")
compare(phoneNumberType.text, "Work")
// second phone
phoneNumber = findChild(phoneNumberGroup, "label_phoneNumber_1.0")
phoneNumberType = findChild(phoneNumberGroup, "type_phoneNumber_1")
compare(phoneNumber.text, "(404) 555-1212")
compare(phoneNumberType.text, "Home")
// E-mails
// EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com
// number of e-mails
var emailGroup = findChild(root, "emails")
var emails = findChildOfType(emailGroup, "BasicFieldView")
compare(emails.length, 2)
// e-mail address
var email = findChild(emailGroup, "label_emailAddress_0.0")
var emailType = findChild(emailGroup, "type_email_0")
compare(email.text, "forrestgump@example.com")
compare(emailType.text, "Home")
// e-mail address
var email1 = findChild(emailGroup, "label_emailAddress_1.0")
var emailType1 = findChild(emailGroup, "type_email_1")
compare(email1.text, "bubbagump@example.com")
compare(emailType1.text, "Home")
// Address
// ADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America
// ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
// number of addresses
var addressGroup = findChild(root, "addresses")
var addresses = findChildOfType(addressGroup, "BasicFieldView")
compare(addresses.length, 2)
// first address
var address_street = findChild(addressGroup, "label_streetAddress_0.0")
var address_locality = findChild(addressGroup, "label_localityAddress_0.1")
var address_region = findChild(addressGroup, "label_regionAddress_0.2")
var address_postCode = findChild(addressGroup, "label_postcodeAddress_0.3")
var address_country = findChild(addressGroup, "label_countryAddress_0.4")
var address_type = findChild(addressGroup, "type_address_0")
compare(address_street.text, "100 Waters Edge")
compare(address_locality.text, "Baytown")
compare(address_region.text, "LA")
compare(address_postCode.text, "30314")
compare(address_country.text, "United States of America")
compare(address_type.text, "Work")
// second address
address_street = findChild(addressGroup, "label_streetAddress_1.0")
address_locality = findChild(addressGroup, "label_localityAddress_1.1")
address_region = findChild(addressGroup, "label_regionAddress_1.2")
address_postCode = findChild(addressGroup, "label_postcodeAddress_1.3")
address_country = findChild(addressGroup, "label_countryAddress_1.4")
address_type = findChild(addressGroup, "type_address_1")
compare(address_street.text, "42 Plantation St.")
compare(address_locality.text, "Baytown")
compare(address_region.text, "LA")
compare(address_postCode.text, "30314")
compare(address_country.text, "United States of America")
compare(address_type.text, "Home")
// Organization
// ORG:Bubba Gump Shrimp Co.
// TITLE:Shrimp Man
// number of organizations
var orgGroup = findChild(root, "organizations")
var orgs = findChildOfType(orgGroup, "BasicFieldView")
compare(orgs.length, 1)
var org_name = findChild(orgGroup, "label_orgName_0.0")
var org_role = findChild(orgGroup, "label_orgRole_0.1")
var org_title = findChild(orgGroup, "label_orgTitle_0.2")
compare(org_name.text, "Bubba Gump Shrimp Co.")
compare(org_role.text, "")
compare(org_title.text, "Shrimp Man")
}
function test_click_on_email()
{
// load contact from vcard
compare(vcardParser.contacts.length, 1)
var contact = vcardParser.contacts[0]
contactPreviewPage.contact = contact
// wait contact be loaded
waitForRendering(contactPreviewPage);
// find object 0
var emailGroup = findChild(root, "emails")
var email = findChild(emailGroup, "label_emailAddress_0.0")
tryCompare(email, "text", "forrestgump@example.com")
tryCompare(email, "visible", true)
// click on e-mail field
var spy = root.createSignalSpy(contactPreviewPage, "actionTrigerred");
mouseClick(email, email.width / 2, email.height / 2)
tryCompare(spy, "count", 1)
compare(spy.signalArguments[0][0], "mailto")
compare(spy.signalArguments[0][2].value(0), "forrestgump@example.com")
spy.clear()
// find object 1
var email1 = findChild(emailGroup, "label_emailAddress_1.0")
tryCompare(email1, "text", "bubbagump@example.com")
tryCompare(email1, "visible", true)
// click on e-mail field
mouseClick(email1, email1.width / 2, email1.height / 2)
// check new values
tryCompare(spy, "count", 1)
compare(spy.signalArguments[0][0], "mailto")
compare(spy.signalArguments[0][2].value(0), "bubbagump@example.com")
}
function test_editable_property()
{
// load any contact
compare(vcardParser.contacts.length, 1)
var contact = vcardParser.contacts[0]
contactPreviewPage.contact = contact
tryCompare(contactPreviewPage.header, "title", "Forrest Gump")
// page is enabled by default
var avatar = findChild(contactPreviewPage, "avatar")
var favorite = findChild(contactPreviewPage, "contactFavoriteDetail")
tryCompare(contactPreviewPage, "editable", true)
tryCompare(avatar, "editable", true)
tryCompare(favorite, "enabled", true)
contactPreviewPage.editable = false
tryCompare(contactPreviewPage, "editable", false)
tryCompare(avatar, "editable", false)
tryCompare(favorite, "enabled", false)
// click on favorite field
var spy = root.createSignalSpy(favorite, "clicked");
mouseClick(favorite, favorite.width / 2, favorite.height / 2)
// wait the click be processed
wait(1000)
// item is disabled, click not accepted
tryCompare(spy, "count", 0)
}
}
}
address-book-app-0.2+16.04.20160323/tests/qml/tst_ContactListView.qml 0000644 0000156 0000165 00000014767 12674534204 025517 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtTest 1.0
import Ubuntu.Components 1.3
import Ubuntu.Test 0.1
import Ubuntu.Contacts 0.1
import "ContactUtil.js" as ContactUtilJS
Item {
id: root
property var application
property var contactListViewObj
// enable dummy mode for contact list view
property bool runningOnTestMode: true
width: units.gu(40)
height: units.gu(80)
Component {
id: contactListCmp
ContactListView {
id: contactListPage
objectName: "contactListViewTest"
anchors.fill: parent
}
}
MainView {
id: mainView
anchors.fill: parent
}
function createContact(firstName, phoneNumber, email) {
var details = [
{detail: 'PhoneNumber', field: 'number', value: phoneNumber},
{detail: 'EmailAddress', field: 'emailAddress', value: email},
{detail: 'Name', field: 'firstName', value: firstName}
];
return ContactUtilJS.createContact(details, mainView)
}
function createSignalSpy(target, signalName) {
var spy = Qt.createQmlObject('import QtTest 1.0; SignalSpy {}', root, "")
spy.target = target
spy.signalName = signalName
return spy
}
UbuntuTestCase {
id: contactListViewTestCase
name: 'contactListViewTestCase'
when: windowShown
function init()
{
root.contactListViewObj = contactListCmp.createObject(mainView, {"manager": "memory"})
waitForRendering(root.contactListViewObj)
tryCompare(contactListViewObj, "busy", false)
var onlineAccountHelper = findChild(root.contactListViewObj, "onlineAccountHelper")
verify(onlineAccountHelper.sourceFile.indexOf("OnlineAccountsDummy.qml") > 0)
}
function cleanup()
{
root.contactListViewObj = null
}
function test_managerProperty()
{
tryCompare(root.contactListViewObj, "manager", "memory")
}
function test_addNewButtonVisibility()
{
var addNewButton = findChild(root.contactListViewObj, "addNewButton")
tryCompare(root.contactListViewObj, "showAddNewButton", false)
tryCompare(addNewButton, "visible", false)
verify(addNewButton.height === 0)
root.contactListViewObj.showAddNewButton = true
tryCompare(root.contactListViewObj, "showAddNewButton", true)
tryCompare(addNewButton, "visible", true)
verify(addNewButton.height > 0)
}
function test_addNewButtonClick()
{
var spy = root.createSignalSpy(root.contactListViewObj, "addNewContactClicked");
root.contactListViewObj.showAddNewButton = true
// click
var addNewButton = findChild(root.contactListViewObj, "addNewButton")
mouseClick(addNewButton, addNewButton.width / 2, addNewButton.height / 2)
tryCompare(spy, "count", 1)
}
function test_importButtonsVisibility()
{
var bottonsHeader = findChild(root.contactListViewObj, "importFromButtons")
var importButton = findChild(root.contactListViewObj, "contactListViewTest.importFromOnlineAccountButton")
var onlineAccountHelper = findChild(root.contactListViewObj, "onlineAccountHelper")
tryCompare(root.contactListViewObj, "showImportOptions", false)
tryCompare(bottonsHeader, "visible", false)
tryCompare(importButton, "visible", false)
tryCompare(onlineAccountHelper, "status", Loader.Null)
tryCompare(onlineAccountHelper, "isSearching", false)
verify(importButton.height === 0)
root.contactListViewObj.showImportOptions = true
tryCompare(root.contactListViewObj, "showImportOptions", true)
tryCompare(root.contactListViewObj, "count", 0)
tryCompare(onlineAccountHelper, "status", Loader.Ready)
// need to wait a bit more until the list leave the loading state
tryCompare(bottonsHeader, "visible", true, 10000)
tryCompare(importButton, "visible", true)
verify(importButton.height > 0)
// Button should disapear if the list is not empty
var newContact = root.createContact("Phablet", "+558187042133", "phablet@ubuntu.com")
root.contactListViewObj.listModel.saveContact(newContact)
tryCompare(importButton, "visible", false)
// Button should not be visible during a search with empty results
root.contactListViewObj.filterTerm = "xox"
tryCompare(root.contactListViewObj, "count", 0)
tryCompare(onlineAccountHelper, "status", Loader.Null)
tryCompare(importButton, "visible", false)
}
function test_importButtonClick()
{
// onlineAccountDialog
var onlineAccountDialog = findChild(root.contactListViewObj, "onlineAccountHelper")
tryCompare(onlineAccountDialog, "status", Loader.Null)
root.contactListViewObj.showImportOptions = true
tryCompare(onlineAccountDialog, "status", Loader.Ready)
tryCompare(onlineAccountDialog.item, "running", false)
// click
var bottonsHeader = findChild(root.contactListViewObj, "importFromButtons")
var importButton = findChild(root.contactListViewObj, "contactListViewTest.importFromOnlineAccountButton")
// need to wait a bit more until the list leave the loading state
tryCompare(bottonsHeader, "visible", true, 10000)
tryCompare(importButton, "visible", true, 10000)
tryCompare(bottonsHeader, "height", importButton.height)
mouseClick(importButton, importButton.width / 2, importButton.height / 2)
tryCompare(onlineAccountDialog.item, "running", true)
}
}
}
address-book-app-0.2+16.04.20160323/tests/qml/tst_VCardParser.qml 0000644 0000156 0000165 00000003476 12674534204 024604 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtTest 1.0
import Ubuntu.Test 0.1
import QtContacts 5.0
Item {
id: root
width: units.gu(40)
height: units.gu(80)
property var vcardParserComponent
property var spy
UbuntuTestCase {
id: vcardParser
name: 'vcardParserTestCase'
function init()
{
vcardParserComponent = Qt.createQmlObject('import Ubuntu.Contacts 0.1; VCardParser{ }', root);
spy = Qt.createQmlObject('import QtTest 1.0; SignalSpy{ }', root);
spy.target = vcardParserComponent
spy.signalName = "vcardParsed"
}
function cleanup()
{
if (vcardParserComponent) {
vcardParserComponent.destroy()
vcardParserComponent = null
}
if (spy) {
spy.destroy()
spy = null
}
}
function test_import_file()
{
vcardParserComponent.vCardUrl = Qt.resolvedUrl("../data/vcard.vcf")
tryCompare(spy, "count", 1)
compare(spy.signalArguments[0][0], ContactModel.ImportNoError)
compare(vcardParserComponent.contacts.length, 3)
}
}
}
address-book-app-0.2+16.04.20160323/tests/qml/tst_ContactEditor.qml 0000644 0000156 0000165 00000011115 12674534204 025157 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtTest 1.0
import Ubuntu.Components 1.3
import Ubuntu.Test 0.1
import Ubuntu.Contacts 0.1
import "ContactUtil.js" as ContactUtilJS
import '../../src/imports/'
Item {
id: root
width: units.gu(40)
height: units.gu(80)
MainView {
id: mainView
anchors.fill: parent
ListModel {
// dummy data model.
id: dummyDataModel
signal contactsFetched
}
ABContactEditorPage {
id: contactEditor
anchors.fill: parent
model: dummyDataModel
contact: createEmptyContact('')
}
}
function createEmptyContact(phoneNumber) {
var details = [
{detail: 'PhoneNumber', field: 'number', value: phoneNumber},
{detail: 'EmailAddress', field: 'emailAddress', value: ''},
{detail: 'OnlineAccount', field: 'accountUri', value: ''},
{detail: 'Address', field: 'street', value: ''},
{detail: 'Name', field: 'firstName', value: ''},
{detail: 'Organization', field: 'name', value: ''}
];
return ContactUtilJS.createContact(details, mainView)
}
UbuntuTestCase {
id: contactEditorTestCase
name: 'contactEditorTestCase'
when: windowShown
function init() {
waitForRendering(contactEditor);
tryCompare(contactEditor, 'saveActionEnabled', false);
}
function cleanup() {
var textFields = getRequiredTextFields().concat(
getOptionalTextFields());
textFields.forEach(clearTextField);
}
function getRequiredTextFields() {
return ['firstName', 'lastName', 'phoneNumber_0'];
}
function getOptionalTextFields() {
return [
'emailAddress_0', 'imUri_0', 'streetAddress_0',
'localityAddress_0', 'regionAddress_0', 'postcodeAddress_0',
'countryAddress_0', 'orgName_0', 'orgRole_0', 'orgTitle_0'
];
}
function clearTextField(value, index, array) {
var textField = findChild(root, value);
textField.text = '';
}
function test_fillRequiredFieldsMustEnableSaveButton_data() {
var textFields = getRequiredTextFields();
return objectNamesArrayToDataScenarios(textFields);
}
function objectNamesArrayToDataScenarios(objectNamesArray) {
var data = [];
for (var index = 0; index < objectNamesArray.length; index++) {
var objectName = objectNamesArray[index];
data.push({tag: objectName, objectName: objectName});
}
return data;
}
function test_fillRequiredFieldsMustEnableSaveButton(data) {
var textField = findChild(root, data.objectName);
textField.text = 'test'
tryCompare(contactEditor, 'saveActionEnabled', true);
}
function test_fillOptionalFieldsMustNotEnableSaveButton_data() {
var textFields = getOptionalTextFields();
return objectNamesArrayToDataScenarios(textFields)
}
function test_fillOptionalFieldsMustNotEnableSaveButton(data) {
var textField = findChild(root, data.objectName);
textField.text = 'test'
tryCompare(contactEditor, 'saveActionEnabled', false);
}
function test_enterKeyMoveFocusedItem() {
// firstName start with focus
var textField = findChild(root, 'firstName');
textField.forceActiveFocus()
tryCompare(textField, 'activeFocus', true)
// send a keyreturn click
keyClick(Qt.Key_Return)
// firstName must lost focus
tryCompare(textField, 'activeFocus', false)
// lastName must gain focus
textField = findChild(root, 'lastName');
tryCompare(textField, 'activeFocus', true)
}
}
}
address-book-app-0.2+16.04.20160323/tests/qml/tst_UbuntuContacts.qml 0000644 0000156 0000165 00000003077 12674534204 025406 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtTest 1.0
import Ubuntu.Contacts 0.1
Item {
id: root
TestCase {
id: uContactsTest
name: 'UBuntuContactTestCase'
function test_normalize_string()
{
compare(Contacts.normalized("não"), "nao")
compare(Contacts.normalized("josé"), "jose")
compare(Contacts.normalized("açaÃ"), "acai")
compare(Contacts.normalized("Роман Щекин"), "Роман Щекин")
compare(Contacts.normalized("阿娜ä¸å¡”西"), "阿娜ä¸å¡”西")
}
function test_containsLetters()
{
compare(Contacts.containsLetters("123456"), false)
compare(Contacts.containsLetters("(123)"), false)
compare(Contacts.containsLetters("Щекин"), true)
compare(Contacts.containsLetters("阿娜ä¸å¡”西"), true)
compare(Contacts.containsLetters("阿娜23西"), true)
}
}
}
address-book-app-0.2+16.04.20160323/tests/qml/tst_ListWithActions.qml 0000644 0000156 0000165 00000036011 12674534204 025507 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import QtTest 1.0
import Ubuntu.Test 0.1
import Ubuntu.Contacts 0.1
Item {
id: root
property var itemList: null
width: units.gu(40)
height: units.gu(80)
Component {
id: itemListComponent
Column {
id: itemList
readonly property int rightActionsLength: 3
signal actionTriggered(var action)
property var signalSpy: SignalSpy {
target: itemList
signalName: "actionTriggered"
}
anchors.fill: parent
Repeater {
model: 2
ListItemWithActions {
id: listWithActions
objectName: "listWithActions" + index
anchors {
left: parent.left
right: parent.right
}
height: units.gu(8)
triggerActionOnMouseRelease: true
Rectangle {
anchors.fill: parent
color: "green"
}
leftSideAction: Action {
id: deleteAction
objectName: "deleteAction"
iconName: "delete"
onTriggered: itemList.actionTriggered(deleteAction, value)
}
rightSideActions: [
Action {
id: messageAction
iconName: "message"
onTriggered: itemList.actionTriggered(messageAction)
},
Action {
id: shareAction
iconName: "share"
onTriggered: itemList.actionTriggered(shareAction)
},
Action {
id: contactAction
iconName: "stock_contact"
onTriggered: itemList.actionTriggered(contactAction)
}
]
}
}
Repeater {
model: 2
ListItemWithActions {
id: listWithNoRightActions
objectName: "listWithNoRightActions" + index
anchors {
left: parent.left
right: parent.right
}
height: units.gu(8)
triggerActionOnMouseRelease: true
Rectangle {
anchors.fill: parent
color: "pink"
}
leftSideAction: Action {
objectName: "deleteAction2"
iconName: "delete"
onTriggered: itemList.actionTriggered(deleteAction, value)
}
}
}
Repeater {
model: 2
ListItemWithActions {
id: listWithInvisibleActions
objectName: "listWithInvisibleActions"+ index
anchors {
left: parent.left
right: parent.right
}
height: units.gu(8)
triggerActionOnMouseRelease: true
Rectangle {
anchors.fill: parent
color: "blue"
}
leftSideAction: Action {
objectName: "deleteAction2"
iconName: "delete"
onTriggered: itemList.actionTriggered(deleteAction, value)
}
rightSideActions: [
Action {
id: messageAction2
iconName: "message"
onTriggered: itemList.actionTriggered(messageAction2)
},
Action {
id: shareAction2
iconName: "share"
visible: false
onTriggered: itemList.actionTriggered(shareAction2)
},
Action {
id: contactAction2
iconName: "stock_contact"
onTriggered: itemList.actionTriggered(contactAction2)
},
Action {
id: infoAction2
iconName: "info"
visible: false
onTriggered: itemList.actionTriggered(shareAction2)
}
]
}
}
Repeater {
model: 2
ListItemWithActions {
id: listWithActionsDoNotriggerOnMouseRelease
objectName: "listWithActionsDoNotriggerOnMouseRelease"+ index
anchors {
left: parent.left
right: parent.right
}
height: units.gu(8)
triggerActionOnMouseRelease: false
Rectangle {
anchors.fill: parent
color: "yellow"
}
rightSideActions: [
Action {
id: messageAction3
iconName: "message"
onTriggered: itemList.actionTriggered(messageAction3)
},
Action {
id: shareAction3
iconName: "share"
onTriggered: itemList.actionTriggered(shareAction3)
},
Action {
id: contactAction3
iconName: "stock_contact"
onTriggered: itemList.actionTriggered(contactAction3)
}
]
}
}
}
}
UbuntuTestCase {
id: listWithActionsTestCase
name: 'listWithActionsTestCase'
readonly property real actionWidthArea: units.gu(5)
when: windowShown
function init()
{
itemList = itemListComponent.createObject(root)
}
function cleanup()
{
itemList.destroy()
}
function mouseMoveSlowly(item, x, y, dx, dy, steps, stepdelay) {
mouseMove(item, x, y);
var abs_dx = Math.abs(dx)
var abs_dy = Math.abs(dy)
var step_dx = dx / steps;
var step_dy = dy /steps;
var ix = 0;
var iy = 0;
for (var step=0; step < steps; step++) {
if (ix < abs_dx) {
ix += step_dx;
}
if (iy < abs_dy) {
iy += step_dy;
}
mouseMove(item, x + ix, y + iy, stepdelay);
}
}
function swipeToDeleteItem(itemName)
{
var item = findChild(itemList, itemName)
var startX = item.threshold
var startY = item.height / 2
var endX = item.width
var endY = startY
mousePress(item, startX, startY)
mouseMoveSlowly(item,
startX, startY,
endX - startX, endY - startY,
10, 100)
mouseRelease(item, endX, endY)
tryCompare(item, "swipeState", "LeftToRight")
return item
}
function swipeToLeft(itemName, actionIndex, release)
{
var item = findChild(itemList, itemName)
var startX = item.width - item.threshold
var startY = item.height / 2
var endX = 0
var endY = startY
if (actionIndex !== -1) {
var actionsWidth = (actionIndex * actionWidthArea)
endX = item.width - actionsWidth - units.gu(2) - (item.actionThreshold * 2)
} else {
endX = 0 // avoid the safe area
}
mousePress(item, startX, startY)
mouseMoveSlowly(item,
startX, startY,
endX - startX, endY - startY,
10, 100)
if (release)
mouseRelease(item, endX, endY)
wait(1000)
tryCompare(item, "swipeState", "RightToLeft")
return {"item": item, "x": endX, "y": endY}
}
function commom_data()
{
var data = []
data.push({actionIndex: 1, iconName: "message"})
data.push({actionIndex: 2, iconName: "share"})
data.push({actionIndex: 3, iconName: "stock_contact"})
return data
}
function test_cancelSwipeToDelete()
{
var item = swipeToDeleteItem("listWithActions1")
mouseClick(item, item.width / 2, item.height / 2)
compare(itemList.signalSpy.count, 0)
}
function test_swipeToDelete()
{
var item = swipeToDeleteItem("listWithActions1")
mouseClick(item, item.actionThreshold, item.height / 2)
itemList.signalSpy.wait()
compare(itemList.signalSpy.count, 1)
compare(itemList.signalSpy.signalArguments[0][0].iconName, "delete")
}
function test_activeRightActions_data()
{
return commom_data()
}
function test_activeRightActions(data)
{
var itemData = swipeToLeft("listWithActions1", data.actionIndex, false)
compare(itemList.signalSpy.count, 0)
compare(itemData.item.activeAction.iconName, data.iconName)
mouseRelease(itemData.item, itemData.x, itemData.y)
itemList.signalSpy.wait()
compare(itemList.signalSpy.count, 1)
compare(itemList.signalSpy.signalArguments[0][0].iconName, data.iconName)
}
function test_lockOnFullSwipe()
{
var itemData = swipeToLeft("listWithActions1", -1, true)
compare(itemList.signalSpy.count, 0)
tryCompare(itemData.item, "swipeState", "RightToLeft")
}
function test_fullSwipeAndClickOnAction_data()
{
return commom_data()
}
function test_fullSwipeAndClickOnAction(data)
{
var itemData = swipeToLeft("listWithActions1", -1, true)
var actionOffset = (itemList.rightActionsLength - data.actionIndex) + 1
var clickX = itemData.item.width - ((actionOffset * actionWidthArea) + (actionWidthArea / 2) - units.gu(2))
mouseClick(itemData.item, clickX, itemData.item.height / 2)
itemList.signalSpy.wait()
compare(itemList.signalSpy.count, 1)
compare(itemList.signalSpy.signalArguments[0][0].iconName, data.iconName)
}
function test_noSwipeWithEmptyRightActions()
{
var item = findChild(itemList, "listWithNoRightActions1")
var startX = item.width - item.threshold
var y = item.height / 2
mousePress(item, startX, y)
mouseMoveSlowly(item, startX, y, -startX, y, 10, 100)
var mainItem = findChild(item, "mainItem")
compare(mainItem.x, 0)
}
function test_not_visibleActions()
{
var itemData = swipeToLeft("listWithInvisibleActions1", -1, true)
compare(itemData.item._visibleRightSideActions.length, 2)
// check if only 2 actions is visible
var mainItem = findChild(itemData.item, "mainItem")
tryCompare(mainItem, "x", (actionWidthArea * -2) - units.gu(2) - itemData.item.actionThreshold)
}
function test_itemsThatDoNotTriggerActionsOnReleaseFullRevel()
{
var item = findChild(itemList, "listWithActionsDoNotriggerOnMouseRelease1")
var startX = item.width / 4
var startY = item.height / 2
var endX = startX - units.gu(2)
var endY = startY
// move a small amount in to the left
mousePress(item, startX, startY)
mouseMoveSlowly(item,
startX, startY,
endX - startX, endY - startY,
10, 100)
mouseRelease(item, endX, endY)
tryCompare(item, "swipeState", "RightToLeft")
}
function test_itemsThatDoNotTriggerActionsOnReleaseClickOnAction_data()
{
return commom_data()
}
function test_itemsThatDoNotTriggerActionsOnReleaseClickOnAction(data)
{
var itemData = swipeToLeft("listWithActionsDoNotriggerOnMouseRelease1", data.actionIndex, true)
tryCompare(itemData.item, "swipeState", "RightToLeft")
compare(itemList.signalSpy.count, 0)
var actionOffset = (itemList.rightActionsLength - data.actionIndex) + 1
console.debug("Action offset:" + actionOffset)
var clickX = itemData.item.width - ((actionOffset * actionWidthArea) + (actionWidthArea / 2) - units.gu(2))
mouseClick(itemData.item, clickX, itemData.item.height / 2)
itemList.signalSpy.wait()
compare(itemList.signalSpy.count, 1)
compare(itemList.signalSpy.signalArguments[0][0].iconName, data.iconName)
}
function test_itemsThatDoNotTriggerActionsOnReleaseDismissActions()
{
// swipe until the midle
var itemData = swipeToLeft("listWithActionsDoNotriggerOnMouseRelease1", 2, false)
tryCompare(itemData.item, "swipeState", "RightToLeft")
// swipe to dismiss
var finalX = itemData.x - units.gu(2)
mouseMoveSlowly(itemData.item,
itemData.x, itemData.y,
itemData.x - finalX, itemData.y,
10, 100)
mouseRelease(itemData.item, finalX, itemData.y)
tryCompare(itemData.item, "swipeState", "Normal")
}
}
}
address-book-app-0.2+16.04.20160323/tests/qml/ContactUtil.js 0000644 0000156 0000165 00000002266 12674534204 023606 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
function createContact(detailsMap, parent) {
var newContact = Qt.createQmlObject(
'import QtContacts 5.0; Contact{ }', parent);
var detailSourceTemplate = 'import QtContacts 5.0; %1{ %2: "%3" }';
for (var i=0; i < detailsMap.length; i++) {
var detailMetaData = detailsMap[i];
var template = detailSourceTemplate.arg(detailMetaData.detail).arg(
detailMetaData.field).arg(detailMetaData.value);
var newDetail = Qt.createQmlObject(template, parent);
newContact.addDetail(newDetail);
}
return newContact;
}
address-book-app-0.2+16.04.20160323/tests/qml/tst_ContactListModel.qml 0000644 0000156 0000165 00000007545 12674534204 025641 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtTest 1.0
import Ubuntu.Components 1.3
import Ubuntu.Test 0.1
import Ubuntu.Contacts 0.1
Item {
id: root
property var application
property var contactListModelObj
width: units.gu(40)
height: units.gu(80)
Component {
id: contactListModelCmp
ContactListModel {
id: contactListModel
property int contactCount: contacts ? contacts.length : 0
manager: "memory"
Component.onCompleted: importContacts(Qt.resolvedUrl("../data/tst_ContactListModel_data.vcf"))
}
}
UbuntuTestCase {
id: contactListModelTestCase
name: 'contactListModelTestCase'
when: windowShown
function init()
{
root.contactListModelObj = contactListModelCmp.createObject(root)
//waitForRendering(root.contactListModelObj)
}
function cleanup()
{
root.contactListModelObj.destroy()
}
function test_contactImport()
{
tryCompare(root.contactListModelObj, "contactCount", 7)
}
function test_searchByPhoneNumber()
{
root.contactListModelObj.filterTerm = "555"
tryCompare(root.contactListModelObj, "contactCount", 1)
root.contactListModelObj.filterTerm = "5"
tryCompare(root.contactListModelObj, "contactCount", 2)
root.contactListModelObj.filterTerm = "6"
tryCompare(root.contactListModelObj, "contactCount", 3)
root.contactListModelObj.filterTerm = "+55(81)8704-0000"
tryCompare(root.contactListModelObj, "contactCount", 1)
}
function test_searchByContactName()
{
root.contactListModelObj.filterTerm = "First"
tryCompare(root.contactListModelObj, "contactCount", 1)
root.contactListModelObj.filterTerm = "Fulano"
tryCompare(root.contactListModelObj, "contactCount", 2)
root.contactListModelObj.filterTerm = "F"
tryCompare(root.contactListModelObj, "contactCount", 3)
root.contactListModelObj.filterTerm = "tal6"
tryCompare(root.contactListModelObj, "contactCount", 1)
}
function test_searchByNameAndNumber()
{
root.contactListModelObj.filterTerm = "First"
tryCompare(root.contactListModelObj, "contactCount", 1)
root.contactListModelObj.filterTerm = "555"
tryCompare(root.contactListModelObj, "contactCount", 1)
root.contactListModelObj.filterTerm = "1"
tryCompare(root.contactListModelObj, "contactCount", 3)
root.contactListModelObj.filterTerm = "Renato555"
tryCompare(root.contactListModelObj, "contactCount", 0)
}
function test_searchByNonLatinNames()
{
root.contactListModelObj.filterTerm = "Роман Щекин"
tryCompare(root.contactListModelObj, "contactCount", 1)
root.contactListModelObj.filterTerm = "亚"
tryCompare(root.contactListModelObj, "contactCount", 2)
root.contactListModelObj.filterTerm = "阿娜ä¸å¡”西"
tryCompare(root.contactListModelObj, "contactCount", 1)
}
}
}
address-book-app-0.2+16.04.20160323/tests/qml/CMakeLists.txt 0000644 0000156 0000165 00000003307 12674534204 023554 0 ustar pbuser pbgroup 0000000 0000000 find_program(QMLTESTRUNNER_BIN
NAMES qmltestrunner
PATHS /usr/lib/*/qt5/bin
NO_DEFAULT_PATH
)
find_program(XVFB_RUN_BIN
NAMES xvfb-run
)
macro(DECLARE_QML_TEST TST_NAME TST_QML_FILE)
if(USE_XVFB)
set(COMMAND_PREFIX ${XVFB_RUN_BIN} -a -s "-screen 0 1024x768x24")
else()
set(COMMAND_PREFIX "")
endif()
add_test(NAME ${TST_NAME}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMAND ${COMMAND_PREFIX} ${QMLTESTRUNNER_BIN} -import ${imports_BINARY_DIR} -input ${CMAKE_CURRENT_SOURCE_DIR}/${TST_QML_FILE}
)
endmacro()
if(QMLTESTRUNNER_BIN AND XVFB_RUN_BIN)
declare_qml_test("contact_list" tst_ContactList.qml)
declare_qml_test("Contact_list_model" tst_ContactListModel.qml)
declare_qml_test("Contact_list_view" tst_ContactListView.qml)
declare_qml_test("contact_editor" tst_ContactEditor.qml)
declare_qml_test("contact_avatar" tst_ContactAvatar.qml)
declare_qml_test("list_with_actions" tst_ListWithActions.qml)
declare_qml_test("contact_preview_page" tst_ContactPreviewPage.qml)
declare_qml_test("vcard_parser" tst_VCardParser.qml)
declare_qml_test("ubuntu_contact" tst_UbuntuContacts.qml)
else()
if (NOT QMLTESTRUNNER_BIN)
message(WARNING "Qml tests disabled: qmltestrunner not found")
else()
message(WARNING "Qml tests disabled: xvfb-run not found")
endif()
endif()
set(QML_TST_FILES
ContactUtil.js
tst_ContactEditor.qml
tst_ContactAvatar.qml
tst_ContactList.qml
tst_ContactListModel.qml
tst_ContactListView.qml
tst_ListWithActions.qml
tst_ContactPreviewPage.qml
tst_VCardParser.qml
tst_UbuntuContacts.qml
)
add_custom_target(tst_QmlFiles ALL SOURCES ${QML_TST_FILES})
address-book-app-0.2+16.04.20160323/tests/data/ 0000755 0000156 0000165 00000000000 12674534512 021133 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/data/vcard_single.vcf 0000644 0000156 0000165 00000001311 12674534204 024265 0 ustar pbuser pbgroup 0000000 0000000 BEGIN:VCARD
VERSION:3.0
N:Gump;Forrest;;Mr.
FN:Forrest Gump
ORG:Bubba Gump Shrimp Co.
TITLE:Shrimp Man
PHOTO;VALUE=URL;http://assets.ubuntu.com/sites/ubuntu/1166/u/img/pictograms/picto-pack/picto-canonical.svg
TEL;TYPE=WORK,VOICE:(111) 555-12121
TEL;TYPE=HOME,VOICE:(404) 555-1212
ADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America
LABEL;TYPE=WORK:100 Waters Edge\nBaytown, LA 30314\nUnited States of America
ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
LABEL;TYPE=HOME:42 Plantation St.\nBaytown, LA 30314\nUnited States of America
EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com
EMAIL;TYPE=INTERNET:bubbagump@example.com
REV:2008-04-24T19:52:43Z
END:VCARD
address-book-app-0.2+16.04.20160323/tests/data/vcard.vcf 0000644 0000156 0000165 00000001275 12674534204 022735 0 ustar pbuser pbgroup 0000000 0000000 BEGIN:VCARD
VERSION:3.0
UID:47bbbfcab7c9b8ef0e7375074d22ff54905174bd
X-QTPROJECT-EXTENDED-DETAIL:CLIENTPIDMAP;[\n "1"\n]\n
N:teste3;teste3;;;
FN:teste3 teste3
X-QTPROJECT-FAVORITE:false;0
TEL:3333333
CATEGORIES:T
END:VCARD
BEGIN:VCARD
VERSION:3.0
UID:e5bb57fc852541dfc9ad29d583a36f1c353b65ed
X-QTPROJECT-EXTENDED-DETAIL:CLIENTPIDMAP;[\n "1"\n]\n
N:test34;teste;;;
FN:teste test34
X-QTPROJECT-FAVORITE:false;0
TEL:44444
CATEGORIES:T
END:VCARD
BEGIN:VCARD
VERSION:3.0
UID:0d753ce1005dde92f69e4ddb62222240691693a0
X-QTPROJECT-EXTENDED-DETAIL:CLIENTPIDMAP;[\n "1"\n]\n
N:teste2;teste;;;
FN:teste teste2
X-QTPROJECT-FAVORITE:false;0
TEL:111111
CATEGORIES:T
END:VCARD
address-book-app-0.2+16.04.20160323/tests/data/tst_ContactListModel_data.vcf 0000644 0000156 0000165 00000002766 12674534204 026737 0 ustar pbuser pbgroup 0000000 0000000 BEGIN:VCARD
VERSION:3.0
UID:47bbbfcab7c9b8ef0e7375074d22ff54905174bd
X-QTPROJECT-EXTENDED-DETAIL:CLIENTPIDMAP;[\n "1"\n]\n
N:First;Last;;;
FN:First Last
X-QTPROJECT-FAVORITE:false;0
TEL:123456
CATEGORIES:F
END:VCARD
BEGIN:VCARD
VERSION:3.0
UID:e5bb57fc852541dfc9ad29d583a36f1c353b65ed
X-QTPROJECT-EXTENDED-DETAIL:CLIENTPIDMAP;[\n "1"\n]\n
N:Fulano;de;tal6;;
FN:Fulano de Tal6
X-QTPROJECT-FAVORITE:false;0
TEL:123678
CATEGORIES:F
END:VCARD
BEGIN:VCARD
VERSION:3.0
UID:0d753ce1005dde92f69e4ddb62222240691693a0
X-QTPROJECT-EXTENDED-DETAIL:CLIENTPIDMAP;[\n "1"\n]\n
N:Fulano;da;Silva1;;;
FN:Fulano da Silva1
X-QTPROJECT-FAVORITE:false;0
TEL:+55(81)8704-0000
TEL:555666
CATEGORIES:F
END:VCARD
BEGIN:VCARD
VERSION:3.0
UID:pas-id-5591505300000002
NICKNAME:
NOTE:
FN:Дмитрий Ященко
N:Ященко;Дмитрий;;;
EMAIL;X-EVOLUTION-UI-SLOT=1;TYPE=WORK:0987654321
REV:2015-06-29T14:04:03Z(2)
END:VCARD
BEGIN:VCARD
VERSION:3.0
UID:pas-id-5591504300000001
FN:Роман Щекин
N:Щекин;Роман;;;
EMAIL;X-EVOLUTION-UI-SLOT=1;TYPE=WORK:12345678
REV:2015-06-29T14:03:47Z(0)
END:VCARD
BEGIN:VCARD
VERSION:3.0
UID:pas-id-5591510B00000003
FN:亚历山德拉
N:;亚历山德拉;;;
EMAIL;X-EVOLUTION-UI-SLOT=1;TYPE=WORK:5678
REV:2015-06-29T14:07:07Z(4)
END:VCARD
BEGIN:VCARD
VERSION:3.0
UID:pas-id-5591511B00000004
FN:阿娜ä¸å¡”西亚
N:;阿娜ä¸å¡”西亚;;;
EMAIL;X-EVOLUTION-UI-SLOT=1;TYPE=WORK:123890
REV:2015-06-29T14:07:23Z(6)
END:VCARD
address-book-app-0.2+16.04.20160323/tests/data/CMakeLists.txt 0000644 0000156 0000165 00000000234 12674534204 023670 0 ustar pbuser pbgroup 0000000 0000000 project(test_data)
set(AUTOPILOT_DATA
vcard.vcf
)
install(FILES ${AUTOPILOT_DATA}
DESTINATION ${CMAKE_INSTALL_DATADIR}/address-book-app/vcards
)
address-book-app-0.2+16.04.20160323/tests/autopilot/ 0000755 0000156 0000165 00000000000 12674534512 022242 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/ 0000755 0000156 0000165 00000000000 12674534512 025541 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/toolbar.py 0000644 0000156 0000165 00000002436 12674534204 027560 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
""" Toolbar emulator for Addressbook App tests """
# Copyright 2013 Canonical
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
from autopilot.introspection.dbus import StateNotFoundError
import logging
from ubuntuuitoolkit import emulators as toolkit_emulators
logger = logging.getLogger(__name__)
class Toolbar(toolkit_emulators.Toolbar):
"""An emulator class that makes it easy to interact with the tool bar"""
def __init__(self, *args):
super(Toolbar, self).__init__(*args)
def click_action_item_by_text(self, text):
"""Click an action item in the tool labelled 'text'
:param text: label of the ActionItem
"""
try:
action_item = self.select_single('ActionItem', text=text)
self.pointing_device.click_object(action_item)
except StateNotFoundError:
logger.error(
'ActionItem with text "{0}" not found.'.format(text)
)
raise
def click_select(self):
"""Press 'Select' button on the toolbar"""
self.click_action_item_by_text("Select")
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/__init__.py 0000644 0000156 0000165 00000017162 12674534204 027657 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014, 2015 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
"""address-book-app autopilot tests and emulators - top level package."""
import logging
logging.basicConfig(filename='warning.log', level=logging.WARNING)
import autopilot.logging
import ubuntuuitoolkit
from autopilot import (
exceptions,
introspection
)
from address_book_app import pages
from address_book_app import address_book
logger = logging.getLogger(__name__)
class AddressBookApp(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
"""Autopilot custom proxy object for the address book app."""
@classmethod
def validate_dbus_object(cls, path, state):
name = introspection.get_classname_from_path(path)
return (name == b'AddressBookApp' and
state['applicationName'][1] == 'AddressBookApp')
@property
def main_window(self):
return self.select_single(objectName='addressBookAppMainWindow')
class AddressBookAppMainWindow(ubuntuuitoolkit.MainView):
"""An emulator class that makes it easy to interact with the app."""
@classmethod
def validate_dbus_object(cls, path, state):
name = introspection.get_classname_from_path(path)
return (name == b'MainWindow' and
state['objectName'][1] == 'addressBookAppMainWindow')
def get_contact_list_page(self):
# ContactListPage is the only page that can appears multiple times
# Ex.: During the pick mode we alway push a new contactListPage, to
# preserve the current application status.
return self.wait_select_single(pages.ABContactListPage,
objectName='contactListPage', pickMode=False)
def get_contact_edit_page(self):
# We can have two contact editor page because of bottom edge page
# but we will return only the active one
return self.wait_select_single(pages.ABContactEditorPage,
objectName="contactEditorPage", active=True)
def get_contact_view_page(self):
return self.wait_select_single(pages.ABContactViewPage,
objectName="contactViewPage",
active=True)
def get_contact_list_pick_page(self):
contact_list_pages = self.select_many(
pages.ABContactListPage, objectName='contactListPage')
for p in contact_list_pages:
if p.pickMode:
return p
return None
def get_share_page(self):
return self.wait_select_single("ContactSharePage",
objectName="contactSharePage",
active=True)
def start_import_contacts(self):
view = self.get_contact_list_view()
if view.count > 0:
self.click_action_button("importFromSimHeaderButton")
else:
import_buttom = self.select_single(
'ContactListButtonDelegate',
objectName='contactListView.importFromSimCardButton')
self.pointing_device.click_object(import_buttom)
return self.wait_select_single(address_book.SIMCardImportPage,
objectName="simCardImportPage",
active=True)
def get_contact_list_view(self):
"""
Returns a ContactListView object for the current window
"""
return self.wait_select_single("ContactListView",
objectName="contactListView")
def get_action(self, action_name):
actionbars = self.select_many('ActionBar', objectName='headerActionBar')
for actionbar in actionbars:
object_name = action_name + "_action_button"
try:
button = actionbar.select_single(objectName=object_name)
if button:
return button
except introspection.dbus.StateNotFoundError:
continue
return None
def click_action_button(self, action_name):
actions = self.select_many(objectName='%s_button'%action_name)
for action in actions:
if action.enabled:
self.pointing_device.click_object(action)
return
raise exceptions.StateNotFoundError(action_name)
def open_header(self):
header = self.get_header()
if (header.y != 0):
edit_page = self.get_contact_edit_page()
flickable = edit_page.wait_select_single(
"QQuickFlickable",
objectName="scrollArea")
while (header.y != 0):
globalRect = flickable.globalRect
start_x = globalRect.x + (globalRect.width * 0.5)
start_y = globalRect.y + (flickable.height * 0.1)
stop_y = start_y + (flickable.height * 0.1)
self.pointing_device.drag(
start_x, start_y, start_x, stop_y, rate=5)
# wait flicking stops to move to the next field
flickable.flicking.wait_for(False)
return header
def wait_bottom_edge(self, opened):
# wait bottom edge to fully appear
mainStack = self.wait_select_single(objectName='mainStack')
mainStack.bottomEdgeOpened.wait_for(opened)
def reveal_bottom_edge_page(self):
flickable = self.get_contact_list_view()
globalRect = flickable.globalRect
start_x = globalRect.x + (globalRect.width * 0.5)
start_y = globalRect.y + (flickable.height * 0.98)
stop_y = globalRect.y + (flickable.height * 0.4)
self.pointing_device.drag(
start_x, start_y, start_x, stop_y, rate=5)
# wait bottom edge to fully appear
self.wait_bottom_edge(True)
def cancel(self):
"""
Press the 'Cancel' button
"""
buttons = self.select_many(objectName='customBackButton')
for button in buttons:
if button.enabled and button.visible:
self.pointing_device.click_object(button)
return
self.click_action_button('cancel')
#self.click_action_button("customBackButton")
def save(self):
"""
Press the 'Save' button
"""
self.click_action_button("save")
def edit(self):
"""
Press the 'Save' button
"""
self.click_action_button("edit")
def delete(self):
"""
Press the 'Delete' button
"""
self.click_action_button("delete")
def confirm_import(self):
"""
Press the 'confirm' button
"""
self.click_action_button("confirmImport")
def get_toolbar(self):
"""Override base class so we get our expected Toolbar subclass."""
return self.select_single(ubuntuuitoolkit.Toolbar)
@autopilot.logging.log_action(logger.info)
def go_to_add_contact(self):
"""
Press the 'Add' button and return the contact editor page
"""
self.reveal_bottom_edge_page()
return self.get_contact_edit_page()
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/ 0000755 0000156 0000165 00000000000 12674534512 026703 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_data.py 0000644 0000156 0000165 00000016130 12674534204 031224 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright 2014 Canonical
#
# This file is part of address-book-app
#
# address-book-app is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import testtools
import testscenarios
from testtools.matchers import HasLength
from address_book_app.address_book import data
class PhoneTypesTestCase(testscenarios.TestWithScenarios, testtools.TestCase):
valid_types = ['Home', 'Work', 'Mobile', 'Work Mobile', 'Other']
scenarios = [(type_, dict(type=type_)) for type_ in valid_types]
def test_make_phone_with_valid_type(self):
"""Test that we can create a phone data object with a valid type."""
phone = data.Phone(type_=self.type, number='dummy')
self.assertEqual(phone.type, self.type)
class MakePhoneTestCase(testtools.TestCase):
def test_make_phone(self):
"""Test that we can create a phone data object with default values."""
phone = data.Phone.make()
self.assertEqual(phone.type, 'Mobile')
self.assertEqual(phone.number, '(818) 777-7755')
def test_make_phone_with_unknown_type(self):
"""Test the error when we try to create a phone with a wrong type."""
error = self.assertRaises(
data.AddressBookAppDataError, data.Phone, type_='unknown',
number='dummy')
self.assertEqual('Unknown phone type: unknown.', str(error))
class EmailTypesTestCase(testscenarios.TestWithScenarios, testtools.TestCase):
valid_types = ['Home', 'Work', 'Other']
scenarios = [(type_, dict(type=type_)) for type_ in valid_types]
def test_make_email_with_valid_type(self):
"""Test that we can create an email data object with a valid type."""
email = data.Email(type_=self.type, address='dummy')
self.assertEqual(email.type, self.type)
class MakeEmailTestCase(testtools.TestCase):
def test_make_unique_email(self):
"""Test that we can create an email data object with default values."""
email = data.Email.make_unique(unique_id='test_uuid')
self.assertEqual(email.address, 'test+test_uuid@example.com')
def test_make_email_with_unknown_type(self):
"""Test the error when we try to create an email with a wrong type."""
error = self.assertRaises(
data.AddressBookAppDataError, data.Email, type_='unknown',
address='dummy')
self.assertEqual('Unknown email type: unknown.', str(error))
class SocialAliasTypesTestCase(
testscenarios.TestWithScenarios, testtools.TestCase):
valid_types = ['Yahoo', 'Aim', 'ICQ', 'Jabber', 'MSN', 'Skype', 'Yahoo']
scenarios = [(type_, dict(type=type_)) for type_ in valid_types]
def test_make_social_alias_with_valid_type(self):
"""Test that we can create a social alias object with a valid type."""
social_alias = data.SocialAlias(type_=self.type, alias='dummy')
self.assertEqual(social_alias.type, self.type)
class MakeSocialAliasTestCase(testtools.TestCase):
def test_make_unique_social_alias(self):
"""Test that we can create a social alias with default values."""
social_alias = data.SocialAlias.make_unique(unique_id='test_uuid')
self.assertEqual(social_alias.alias, 'Test alias test_uuid')
def test_make_social_alias_with_unknown_type(self):
"""Test the error when we create a social alias with a wrong type."""
error = self.assertRaises(
data.AddressBookAppDataError, data.SocialAlias, type_='unknown',
alias='dummy')
self.assertEqual('Unknown social alias type: unknown.', str(error))
class AddressTypesTestCase(
testscenarios.TestWithScenarios, testtools.TestCase):
valid_types = ['Home', 'Work', 'Other']
scenarios = [(type_, dict(type=type_)) for type_ in valid_types]
def test_make_address_with_valid_type(self):
"""Test that we can create an address data object with a valid type."""
address = data.Address(
type_=self.type, street='dummy', locality='dummy', region='dummy',
postal_code='dummy', country='dummy')
self.assertEqual(address.type, self.type)
class MakeAddressTestCase(testtools.TestCase):
def test_make_unique_address(self):
"""Test that we can create an address object with default values."""
address = data.Address.make_unique(unique_id='test_uuid')
self.assertEqual(address.street, 'Test street test_uuid')
self.assertEqual(address.locality, 'Test locality test_uuid')
self.assertEqual(address.region, 'Test region test_uuid')
self.assertEqual(address.postal_code, 'Test postal code test_uuid')
self.assertEqual(address.country, 'Test country test_uuid')
def test_make_address_with_unknown_type(self):
"""Test the error when we create an address with a wrong type."""
error = self.assertRaises(
data.AddressBookAppDataError, data.Address, type_='unknown',
street='dummy', locality='dummy', region='dummy',
postal_code='dummy', country='dummy')
self.assertEqual('Unknown address type: unknown.', str(error))
class MakeProfessionalDetailsTestCase(testtools.TestCase):
def test_make_unique_professional_details(self):
"""Test that we can create professiona details with default values."""
professional_details = data.ProfessionalDetails.make_unique(
unique_id='test_uuid')
self.assertEqual(
professional_details.organization, 'Test organization test_uuid')
self.assertEqual(
professional_details.role, 'Test role test_uuid')
self.assertEqual(
professional_details.title, 'Test title test_uuid')
class ContactTestCase(testtools.TestCase):
def test_make_unique_contact(self):
contact = data.Contact.make_unique(unique_id='test_uuid')
self.assertEqual(contact.first_name, 'Test first name test_uuid')
self.assertEqual(contact.last_name, 'Test last name test_uuid')
self.assertThat(contact.phones, HasLength(1))
self.assertIsInstance(contact.phones[0], data.Phone)
self.assertThat(contact.emails, HasLength(1))
self.assertIsInstance(contact.emails[0], data.Email)
self.assertThat(contact.social_aliases, HasLength(1))
self.assertIsInstance(contact.social_aliases[0], data.SocialAlias)
self.assertThat(contact.addresses, HasLength(1))
self.assertIsInstance(contact.addresses[0], data.Address)
self.assertThat(contact.professional_details, HasLength(1))
self.assertIsInstance(
contact.professional_details[0], data.ProfessionalDetails)
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/__init__.py 0000644 0000156 0000165 00000027302 12674534204 031016 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2013, 2014, 2015 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
"""address-book-app autopilot tests."""
import os
import time
import subprocess
import re
from autopilot.testcase import AutopilotTestCase
from autopilot.matchers import Eventually
from autopilot.platform import model
from testtools.matchers import Equals
import ubuntuuitoolkit
from ubuntuuitoolkit import emulators as toolkit_emulators
class AddressBookAppTestCase(AutopilotTestCase):
"""A common test case class that provides several useful methods for
address-book-app tests.
"""
DEFAULT_DEV_LOCATION = "../../src/app/address-book-app"
DEB_LOCALTION = "/usr/bin/address-book-app"
VCARD_PATH_BIN = "/usr/share/address-book-app/vcards/vcard.vcf"
VCARD_PATH_DEV = os.path.abspath("../data/vcard.vcf")
ARGS = []
PRELOAD_VCARD = False
MEMORY_BACKEND = True
def setUp(self):
self.pointing_device = toolkit_emulators.get_pointing_device()
super(AddressBookAppTestCase, self).setUp()
# stop vkb
if model() != "Desktop":
maliit_info = subprocess.Popen(['/sbin/initctl', 'status', 'maliit-server'], stdout=subprocess.PIPE)
result = maliit_info.stdout.read()
if b'start/running,' in result.split():
subprocess.check_call(['/sbin/initctl', 'stop', 'maliit-server'])
if 'AUTOPILOT_APP' in os.environ:
self.app_bin = os.environ['AUTOPILOT_APP']
else:
self.app_bin = AddressBookAppTestCase.DEFAULT_DEV_LOCATION
if AddressBookAppTestCase.MEMORY_BACKEND:
os.environ['QTCONTACTS_MANAGER_OVERRIDE'] = 'memory'
else:
os.environ['QTCONTACTS_MANAGER_OVERRIDE'] = 'galera'
os.environ['ADDRESS_BOOK_APP_ICON_THEME'] = 'ubuntu-mobile'
vcard_data = ""
if self.PRELOAD_VCARD:
# Use vcard from source tree and fallback on installed vcard (from
# address-book-app-autopilot package)
if os.path.exists(AddressBookAppTestCase.VCARD_PATH_DEV):
vcard_data = AddressBookAppTestCase.VCARD_PATH_DEV
else:
vcard_data = AddressBookAppTestCase.VCARD_PATH_BIN
os.environ["ADDRESS_BOOK_TEST_DATA"] = vcard_data
os.environ["LANG"] = "en_US.UTF-8"
os.environ["LANGUAGE"] = "en_US"
if vcard_data != "":
print("Using vcard %s" % vcard_data)
if os.path.exists(self.app_bin):
print("Running from: %s" % (self.app_bin))
self.app = self.launch_test_local()
elif os.path.exists(self.DEB_LOCALTION):
print("Running from: %s" % (self.DEB_LOCALTION))
self.app = self.launch_test_installed()
else:
print("Running from click package: address-book-app")
self.app = self.launch_click_installed()
AddressBookAppTestCase.ARGS = []
self.main_window.visible.wait_for(True)
def tearDown(self):
super(AddressBookAppTestCase, self).tearDown()
# start the vkb
if model() != "Desktop":
subprocess.check_call(["/sbin/initctl", "start", "maliit-server"])
def launch_test_local(self):
return self.launch_test_application(
self.app_bin,
*AddressBookAppTestCase.ARGS,
app_type='qt',
emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase)
def launch_test_installed(self):
df = "/usr/share/applications/address-book-app.desktop"
self.ARGS.append("--desktop_file_hint=" + df)
return self.launch_test_application(
"address-book-app",
*AddressBookAppTestCase.ARGS,
app_type='qt',
emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase)
def launch_click_installed(self):
return self.launch_click_package(
'com.ubuntu.address-book',
emulator_base=ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase)
@property
def main_window(self):
return self.app.main_window
def select_a_value(self, field, value_selector, value):
# Make sure the field has focus
self.pointing_device.click_object(field)
self.assertThat(field.activeFocus, Eventually(Equals(True)))
while(value_selector.currentIndex != value):
self.keyboard.press_and_release("Shift+Right")
time.sleep(0.1)
def type_on_field(self, field, text):
edit_page = self.main_window.get_contact_edit_page()
flickable = edit_page.wait_select_single(
"QQuickFlickable",
objectName="scrollArea")
while (not field.activeFocus):
# wait flicking stops to move to the next field
self.assertThat(flickable.flicking, Eventually(Equals(False)))
# use tab to move to the next field
self.keyboard.press_and_release("Tab")
time.sleep(0.1)
self.assertThat(field.activeFocus, Eventually(Equals(True)))
self.keyboard.type(text)
self.assertThat(field.text, Eventually(Equals(text)))
def clear_text_on_field(self, field):
# Make sure the field has focus
self.pointing_device.click_object(field)
self.assertThat(field.activeFocus, Eventually(Equals(True)))
# click on clear button
clear_button = field.select_single("AbstractButton")
self.pointing_device.click_object(clear_button)
self.assertThat(field.text, Eventually(Equals("")))
# FIXME: Remove this function use ContactEditor.add_field
def create_new_detail(self, detailGroup):
detCount = detailGroup.detailsCount
add_button = detailGroup.select_single("Icon",
objectName="newDetailButton")
self.pointing_device.click_object(add_button)
self.assertThat(detailGroup.detailsCount,
Eventually(Equals(detCount + 1)))
def edit_contact(self, index):
list_page = self.main_window.get_contact_list_page()
list_page.open_contact(index)
view_page = self.main_window.get_contact_view_page()
self.assertThat(view_page.visible, Eventually(Equals(True)))
# Edit contact
self.main_window.edit()
edit_page = self.main_window.get_contact_edit_page()
self.assertThat(edit_page.visible, Eventually(Equals(True)))
return edit_page
def set_phone_number(self, idx, phone_number, phone_type=-1):
phoneGroup = self.main_window.select_single(
"ContactDetailGroupWithTypeEditor",
objectName="phones")
if idx > 0:
self.create_new_detail(phoneGroup)
phone_number_input = self.main_window.select_single(
"TextInputDetail",
objectName="phoneNumber_" + str(idx))
self.type_on_field(phone_number_input, phone_number)
if phone_type != -1:
phone_value_selector = self.main_window.select_single(
"ValueSelector",
objectName="type_phoneNumber_" + str(idx))
self.pointing_device.click_object(phone_value_selector)
self.select_a_value(phone_number_input,
phone_value_selector,
phone_type)
def set_email_address(self, idx, email_address, email_type=-1):
emailGroup = self.main_window.select_single(
"ContactDetailGroupWithTypeEditor",
objectName="emails")
if idx > 0:
self.create_new_detail(emailGroup)
email_address_input = self.main_window.select_single(
"TextInputDetail",
objectName="emailAddress_" + str(idx))
self.type_on_field(email_address_input, email_address)
if email_type != -1:
email_value_selector = self.main_window.select_single(
"ValueSelector",
objectName="type_email_" + str(idx))
self.pointing_device.click_object(email_value_selector)
self.select_a_value(email_address_input,
email_value_selector,
email_type)
def add_contact(self,
first_name,
last_name,
phone_numbers=None,
email_address=None,
im_address=None,
street_address=None,
locality_address=None,
region_address=None,
postcode_address=None,
country_address=None):
# execute add new contact
self.main_window.go_to_add_contact()
first_name_field = self.main_window.select_single(
"TextInputDetail",
objectName="firstName")
last_name_field = self.main_window.select_single(
"TextInputDetail",
objectName="lastName")
self.type_on_field(first_name_field, first_name)
self.type_on_field(last_name_field, last_name)
if phone_numbers:
self.main_window.select_single(
"ContactDetailGroupWithTypeEditor",
objectName="phones")
for idx, number in enumerate(phone_numbers):
self.set_phone_number(idx, number)
if email_address:
self.main_window.select_single(
"ContactDetailGroupWithTypeEditor",
objectName="emails")
for idx, address in enumerate(email_address):
self.set_email_address(idx, address)
if im_address:
imGroup = self.main_window.select_single(
"ContactDetailGroupWithTypeEditor",
objectName="ims")
for idx, address in enumerate(im_address):
if idx > 0:
self.create_new_detail(imGroup)
im_address_input = self.main_window.select_single(
"TextInputDetail",
objectName="imUri_" + str(idx))
self.type_on_field(im_address_input, address)
if street_address:
street_0 = self.main_window.selepostcode_addressct_single(
"TextInputDetail",
objectName="streetAddress_0")
self.type_on_field(street_0, street_address)
if locality_address:
locality_0 = self.main_window.select_single(
"TextInputDetail",
objectName="localityAddress_0")
self.type_on_field(locality_0, locality_address)
if region_address:
region_0 = self.main_window.select_single(
"TextInputDetail",
objectName="regionAddress_0")
self.type_on_field(region_0, region_address)
if postcode_address:
postcode_0 = self.main_window.select_single(
"TextInputDetail",
objectName="postcodeAddress_0")
self.type_on_field(postcode_0, postcode_address)
if country_address:
country_0 = self.main_window.select_single(
"TextInputDetail",
objectName="countryAddress_0")
self.type_on_field(country_0, country_address)
self.main_window.save()
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_export_contact.py 0000644 0000156 0000165 00000002016 12674534204 033345 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Copyright 2013 Canonical
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
"""Tests for the AddressBook App"""
from autopilot.matchers import Eventually
from testtools.matchers import Equals
from address_book_app.tests import AddressBookAppTestCase
class TestExportContact(AddressBookAppTestCase):
"""Tests the erxport contact features"""
PRELOAD_VCARD = True
def test_export_contacts(self):
contact_list = self.app.main_window.get_contact_list_page()
# select one contact
contact_list.select_contacts([1])
# click on export header action
self.app.main_window.click_action_button('share')
# expect that the share page appears
share_page = self.app.main_window.get_share_page()
self.assertThat(share_page.active, Eventually(Equals(True)))
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_edit_contact.py 0000644 0000156 0000165 00000017032 12674534204 032755 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Copyright 2013 Canonical
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
"""Tests for the Addressbook App"""
from testtools.matchers import Equals
from autopilot.matchers import Eventually
from address_book_app.address_book import data
from address_book_app.tests import AddressBookAppTestCase
class TestEditContact(AddressBookAppTestCase):
"""Tests edit a contact"""
PHONE_NUMBERS = ['(333) 123-4567', '(333) 123-4568', '(222) 222-2222']
def add_test_contact(self):
test_contact = data.Contact('test', 'test')
# TODO implement the filling of professional details.
# --elopio - 2014-03-01
test_contact.professional_details = []
# execute add new contact
contact_editor = self.main_window.go_to_add_contact()
contact_editor.fill_form(test_contact)
# Save contact
self.main_window.save()
def test_add_new_phone(self):
self.add_contact("Fulano", "de Tal", [self.PHONE_NUMBERS[0]])
edit_page = self.edit_contact(0)
# Add a new phone
edit_page.add_field('phones')
# fill phone number
phone_number_1 = self.app.main_window.select_single(
"TextInputDetail",
objectName="phoneNumber_1")
self.type_on_field(phone_number_1, self.PHONE_NUMBERS[1])
self.app.main_window.save()
# go back to view page
view_page = self.app.main_window.get_contact_view_page()
self.assertThat(view_page.visible, Eventually(Equals(True)))
# check if we have two phones"""
phone_group = view_page.select_single(
"ContactDetailGroupWithTypeView",
objectName="phones")
self.assertThat(phone_group.detailsCount, Eventually(Equals(2)))
# check if the new value is correct
phone_label_1 = view_page.select_single(
"UCLabel",
objectName="label_phoneNumber_1.0")
self.assertThat(phone_label_1.text,
Eventually(Equals(self.PHONE_NUMBERS[1])))
def test_remove_phone(self):
contact_editor = self.app.main_window.go_to_add_contact()
my_phones = []
for n in self.PHONE_NUMBERS[1:3]:
my_phones.append(data.Phone(type_='Mobile', number=n))
test_contact = data.Contact(first_name="Fulano",
last_name="de Tal",
phones=my_phones)
contact_editor.fill_form(test_contact)
# clear phone 1
phone_number_1 = contact_editor.wait_select_single(
"TextInputDetail",
objectName="phoneNumber_1")
self.clear_text_on_field(phone_number_1)
# Save contact
self.app.main_window.save()
# Go to contact view
list_page = self.main_window.get_contact_list_page()
# check if we have onlye one phone
view_page = list_page.open_contact(0)
phone_group = self.main_window.wait_select_single(
"ContactDetailGroupWithTypeView",
objectName="phones")
self.assertThat(phone_group.detailsCount, Eventually(Equals(1)))
# check if the new value is correct
phone_label_1 = phone_group.wait_select_single(
"UCLabel",
objectName="label_phoneNumber_0.0")
self.assertThat(phone_label_1.text,
Eventually(Equals(self.PHONE_NUMBERS[1])))
def test_add_email(self):
self.add_contact("Fulano", "")
edit_page = self.edit_contact(0)
edit_page.add_field("emails")
# fill email address
email_field = edit_page.select_single(
"TextInputDetail",
objectName="emailAddress_0")
self.type_on_field(email_field, "fulano@internet.com.br")
self.app.main_window.save()
# go back to view page
view_page = self.app.main_window.get_contact_view_page()
self.assertThat(view_page.visible, Eventually(Equals(True)))
# check if we have a new email
email_group = self.main_window.select_single(
"ContactDetailGroupWithTypeView",
objectName="emails")
self.assertThat(email_group.detailsCount, Eventually(Equals(1)))
# check if the new value is correct
email_label_1 = email_group.select_single(
"UCLabel",
objectName="label_emailAddress_0.0")
self.assertThat(email_label_1.text,
Eventually(Equals("fulano@internet.com.br")))
def test_remove_email(self):
self.add_contact("Fulano", "de Tal", None, ["fulano@email.com"])
edit_page = self.edit_contact(0)
# clear email
email_address_0 = edit_page.select_single(
"TextInputDetail",
objectName="emailAddress_0")
self.clear_text_on_field(email_address_0)
# Save contact
self.app.main_window.save()
# check if the email list is empty
view_page = self.app.main_window.get_contact_view_page()
emails_group = self.main_window.select_single(
"ContactDetailGroupWithTypeView",
objectName="emails")
self.assertThat(emails_group.detailsCount, Eventually(Equals(0)))
def test_clear_names(self):
self.add_contact("Fulano", "de Tal")
edit_page = self.edit_contact(0)
first_name_field = edit_page.select_single(
"TextInputDetail",
objectName="firstName")
last_name_field = edit_page.select_single(
"TextInputDetail",
objectName="lastName")
# clear names
self.clear_text_on_field(first_name_field)
self.clear_text_on_field(last_name_field)
# check if is possible to save a contact without name
self.assertThat(edit_page.saveActionEnabled, Eventually(Equals(False)))
# Cancel edit
self.app.main_window.cancel()
# Check if the names still there
view_page = self.app.main_window.get_contact_view_page()
self.assertThat(view_page.headerTitle, Eventually(Equals("Fulano de Tal")))
def test_im_type(self):
contact_editor = self.app.main_window.go_to_add_contact()
alias = data.SocialAlias(type_="Skype", alias="im@account.com")
test_contact = data.Contact(first_name="Fulano",
last_name="de Tal",
social_aliases=[alias])
contact_editor.fill_form(test_contact)
# Save contact
self.app.main_window.save()
# edit again
edit_page = self.edit_contact(0)
# Change Im type
im_value_selector = edit_page.select_single(
"ValueSelector",
objectName="type_onlineAccount_0")
self.pointing_device.click_object(im_value_selector)
self.assertThat(im_value_selector.expanded, Eventually(Equals(True)))
im_address_0 = edit_page.select_single(
"TextInputDetail",
objectName="imUri_0")
# select the type with index = 0
self.select_a_value(im_address_0, im_value_selector, 0)
# save contact
self.app.main_window.save()
view_page = self.app.main_window.get_contact_view_page()
# check if the type was saved correct
im_type = view_page.select_single(
"UCLabel",
objectName="type_onlineAccount_0")
self.assertThat(im_type.text, Eventually(Equals("Aim")))
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_import_from_sim.py 0000644 0000156 0000165 00000006145 12674534204 033525 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Copyright 2013 Canonical
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
"""Tests for the Addressbook App"""
from testtools.matchers import Equals
from testtools import skipUnless
from autopilot.matchers import Eventually
from address_book_app.tests import AddressBookAppTestCase
from address_book_app import helpers
@skipUnless(helpers.is_phonesim_running(),
"this test needs to run under with-ofono-phonesim")
class TestImportFromSimContact(AddressBookAppTestCase):
"""Tests import a contact from sim card"""
def setUp(self):
super(TestImportFromSimContact, self).setUp()
helpers.reset_phonesim()
# wait list fully load
view = self.app.main_window.get_contact_list_view()
self.assertThat(
view.busy,
Eventually(Equals(False), timeout=30))
def test_impot_item_is_visible_on_the_list(self):
# contact list is empty
list_page = self.app.main_window.get_contact_list_page()
self.assertThat(len(list_page.get_contacts()), Equals(0))
# button should be visible if list is empty
import_from_sim_button = self.app.main_window.select_single(
'ContactListButtonDelegate',
objectName='contactListView.importFromSimCardButton')
self.assertThat(
import_from_sim_button.visible,
Eventually(Equals(True), timeout=30))
# add a new contact
self.add_contact("Fulano", "de Tal", ["(333) 123-4567"])
# button should be invisible if list is not empty
self.assertThat(
import_from_sim_button.visible,
Eventually(Equals(False), timeout=30))
def test_import_from_sim(self):
list_page = self.app.main_window.get_contact_list_page()
# contact list is empty
self.assertThat(len(list_page.get_contacts()), Equals(0))
# import two contacts
import_page = self.app.main_window.start_import_contacts()
# check if the contacts is available
self.assertThat(
import_page.hasContacts,
Eventually(Equals(True), timeout=30))
contacts = import_page.select_contacts([1, 3])
self.assertThat(len(contacts), Equals(2))
self.app.main_window.confirm_import()
# dismiss imported contact list
self.app.main_window.cancel()
# verify if the contact was imported
new_contacts = list_page.get_contacts()
self.assertThat(len(new_contacts), Equals(2))
for contact in new_contacts:
contacts.remove(contact)
self.assertThat(len(contacts), Equals(0))
def test_import_item_invisible_without_sim_card(self):
list_page = self.app.main_window.get_contact_list_page()
# remove all sim cards
helpers.remove_phonesim()
self.assertThat(
list_page.is_import_from_sim_button_visible,
Eventually(Equals(False), timeout=30))
././@LongLink 0000000 0000000 0000000 00000000147 00000000000 011217 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_create_new_from_uri.py address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_create_new_from_uri.0000644 0000156 0000165 00000004432 12674534204 033762 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Copyright 2013 Canonical
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
"""Tests for the Addressbook App"""
from __future__ import absolute_import
from testtools.matchers import Equals
from autopilot.matchers import Eventually
from address_book_app.tests import AddressBookAppTestCase
class TestCreateNewContactFromURI(AddressBookAppTestCase):
"""Tests call the app with different uri"""
def setUp(self):
self.ARGS.append("addressbook:///create?phone=1234567890")
super(TestCreateNewContactFromURI, self).setUp()
def test_save_new_contact(self):
list_page = self.app.main_window.get_contact_list_page()
edit_page = self.app.main_window.get_contact_edit_page()
self.assertThat(edit_page.visible, Eventually(Equals(True)))
# add name to the contact
firstNameField = edit_page.wait_select_single(
"TextInputDetail",
objectName="firstName")
lastNameField = edit_page.wait_select_single(
"TextInputDetail",
objectName="lastName")
self.type_on_field(firstNameField, "Fulano")
self.type_on_field(lastNameField, "de Tal")
# save the contact
self.app.main_window.save()
# open contact view
list_page = self.app.main_window.get_contact_list_page()
list_page.open_contact(0)
view_page = self.app.main_window.get_contact_view_page()
self.assertThat(view_page.visible, Eventually(Equals(True)))
# check if we have the new phone"""
phone_group = view_page.select_single(
"ContactDetailGroupWithTypeView",
objectName="phones")
self.assertThat(phone_group.detailsCount, Eventually(Equals(1)))
phone_type = view_page.select_single(
"UCLabel",
objectName="type_phoneNumber_0")
phone_label = view_page.select_single(
"UCLabel",
objectName="label_phoneNumber_0.0")
self.assertThat(phone_label.text, Eventually(Equals("1234567890")))
self.assertThat(phone_type.text, Eventually(Equals("Mobile")))
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_add_contact.py 0000644 0000156 0000165 00000015366 12674534204 032570 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Copyright 2013, 2014 Canonical
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
"""Tests for the Addressbook App"""
from testtools.matchers import Equals
from autopilot.matchers import Eventually
from autopilot.introspection import dbus
import address_book_app
from address_book_app.address_book import data
from address_book_app.tests import AddressBookAppTestCase
class TestAddContact(AddressBookAppTestCase):
""" Tests the Add contact """
def test_go_to_add_contact(self):
"""Test to launch the add contact screen using emulator method"""
self.assertRaises(
dbus.StateNotFoundError,
self.app.main_window.get_contact_edit_page)
contact_editor = self.app.main_window.go_to_add_contact()
self.assertTrue(contact_editor.visible)
self.assertIsInstance(
contact_editor, address_book_app.pages.ABContactEditorPage)
def test_add_and_cancel_contact(self):
list_page = self.app.main_window.get_contact_list_page()
# execute add new contact
contact_editor = self.app.main_window.go_to_add_contact()
# Check if the contact list disapear and contact editor appears
self.assertThat(contact_editor.visible, Eventually(Equals(True)))
self.assertThat(contact_editor.active, Eventually(Equals(True)))
# cancel new contact without save
self.app.main_window.cancel()
# Check if the contact list is visible again
self.assertThat(list_page.visible, Eventually(Equals(True)))
self.app.main_window.wait_bottom_edge(False)
# Check if the contact list still empty
list_view = self.app.main_window.get_contact_list_view()
self.assertThat(list_view.count, Eventually(Equals(0)))
def test_add_contact_with_name_and_phone(self):
test_contact = data.Contact(
first_name='Fulano', last_name='de Tal',
phones=[data.Phone.make()])
# execute add new contact
contact_editor = self.app.main_window.go_to_add_contact()
contact_editor.fill_form(test_contact)
# Save contact
self.app.main_window.save()
# Check if contact was added
list_view = self.app.main_window.get_contact_list_view()
self.assertThat(list_view.count, Eventually(Equals(1)))
def test_add_full_contact(self):
test_contact = data.Contact.make_unique()
# TODO implement the filling of professional details.
# --elopio - 2014-03-01
test_contact.professional_details = []
# execute add new contact
contact_editor = self.app.main_window.go_to_add_contact()
contact_editor.fill_form(test_contact)
# Save contact
self.app.main_window.save()
# Check if contact was added
list_view = self.app.main_window.get_contact_list_view()
self.assertThat(list_view.count, Eventually(Equals(1)))
def test_email_label_save(self):
contact_editor = self.app.main_window.go_to_add_contact()
my_emails = []
my_emails.append(data.Email(type_="Home", address="home@email.com"))
my_emails.append(data.Email(type_="Work", address="work@email.com"))
my_emails.append(data.Email(type_="Other", address="other@email.com"))
test_contact = data.Contact(first_name="Sherlock",
last_name="Holmes",
emails=my_emails)
contact_editor.fill_form(test_contact)
# Save contact
self.app.main_window.save()
list_page = self.app.main_window.get_contact_list_page()
view_page = list_page.open_contact(0)
self.assertThat(view_page.visible, Eventually(Equals(True)))
# check if we have 3 emails"""
email_group = view_page.select_single(
"ContactDetailGroupWithTypeView",
objectName="emails")
self.assertThat(email_group.detailsCount, Eventually(Equals(3)))
emails = {"home@email.com": "Home",
"work@email.com": "Work",
"other@email.com": "Other"}
# Check if they have the correct label
for idx in range(3):
email_type = view_page.select_single(
"UCLabel",
objectName="type_email_" + str(idx))
email_label = view_page.select_single(
"UCLabel",
objectName="label_emailAddress_" + str(idx) + ".0")
self.assertThat(emails[email_label.text], Equals(email_type.text))
del emails[email_label.text]
self.assertThat(len(emails), Equals(0))
def test_phone_label_save(self):
contact_editor = self.app.main_window.go_to_add_contact()
my_phones = []
my_phones.append(data.Phone(type_="Home", number="(000) 000-0000"))
my_phones.append(data.Phone(type_="Work", number="(000) 000-0001"))
my_phones.append(data.Phone(type_="Mobile", number="(000) 000-0002"))
my_phones.append(data.Phone(type_="Work Mobile",
number="(000) 000-0003"))
my_phones.append(data.Phone(type_="Other", number="(000) 000-0004"))
test_contact = data.Contact(first_name="Sherlock",
last_name="Holmes",
phones=my_phones)
contact_editor.fill_form(test_contact)
# Save contact
self.app.main_window.save()
# Open contact view
list_page = self.app.main_window.get_contact_list_page()
view_page = list_page.open_contact(0)
self.assertThat(view_page.visible, Eventually(Equals(True)))
# check if we have five phones"""
phone_group = view_page.select_single(
"ContactDetailGroupWithTypeView",
objectName="phones")
self.assertThat(phone_group.detailsCount, Eventually(Equals(5)))
phones = {"(000) 000-0000": "Home",
"(000) 000-0001": "Work",
"(000) 000-0002": "Mobile",
"(000) 000-0003": "Work Mobile",
"(000) 000-0004": "Other"}
# Check if they have the correct label
for idx in range(5):
phone_type = view_page.select_single(
"UCLabel",
objectName="type_phoneNumber_" + str(idx))
phone_label = view_page.select_single(
"UCLabel",
objectName="label_phoneNumber_" + str(idx) + ".0")
self.assertThat(phones[phone_label.text], Equals(phone_type.text))
del phones[phone_label.text]
self.assertThat(len(phones), Equals(0))
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_contactlist.py 0000644 0000156 0000165 00000001350 12674534204 032640 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Copyright 2013 Canonical
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
"""Tests for the Mediaplayer App"""
from autopilot.matchers import Eventually
from testtools.matchers import Equals
from address_book_app.tests import AddressBookAppTestCase
class TestContactList(AddressBookAppTestCase):
"""Tests the contact list features"""
def test_contact_list(self):
contact_list = self.app.main_window.get_contact_list_page()
self.assertThat(contact_list.visible, Eventually(Equals(True)))
pass
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_import_vcard.py 0000644 0000156 0000165 00000003022 12674534204 033000 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Copyright 2015 Canonical
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
"""Tests for the Addressbook App"""
from __future__ import absolute_import
from testtools.matchers import Equals
from autopilot.matchers import Eventually
import os
from address_book_app.tests import AddressBookAppTestCase
class TeseImportVCard(AddressBookAppTestCase):
"""Tests import vcard"""
def setUp(self):
vcard_path = AddressBookAppTestCase.VCARD_PATH_DEV
if os.path.exists(AddressBookAppTestCase.VCARD_PATH_BIN):
vcard_path = AddressBookAppTestCase.VCARD_PATH_BIN
self.ARGS.append("addressbook:///importvcard?url=file://%s" % vcard_path)
super(TeseImportVCard, self).setUp()
def test_import_vcard_results(self):
# check if app enter on import state
list_page = self.app.main_window.get_contact_list_page()
self.assertThat(list_page.state, Eventually(Equals('vcardImported')))
self.assertThat(list_page.headerTitle, Eventually(Equals('Imported contacts')))
self.assertThat(len(list_page.get_contacts()), Equals(3))
#leave import state and show full contact list
self.app.main_window.cancel()
self.assertThat(list_page.state, Eventually(Equals('default')))
self.assertThat(list_page.title, Eventually(Equals('Contacts')))
././@LongLink 0000000 0000000 0000000 00000000146 00000000000 011216 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_multiple_pick_mode.py address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_multiple_pick_mode.p0000644 0000156 0000165 00000005544 12674534204 033776 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Copyright 2013 Canonical
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
"""Tests for the Addressbook App"""
from testtools.matchers import Equals
from autopilot.matchers import Eventually
from address_book_app.tests import AddressBookAppTestCase
class TestMultiplePickerMode(AddressBookAppTestCase):
""" Tests app in single picker mode"""
PRELOAD_VCARD = True
def setUp(self):
self.ARGS.append("addressbook:///pick?single=false")
super(TestMultiplePickerMode, self).setUp()
def test_select_contacts(self):
pick_page = self.app.main_window.get_contact_list_pick_page()
contacts = pick_page.select_many("ContactDelegate")
# all items should be invisible
items = []
item_to_contacts = {}
for contact in contacts:
if (contact.visible):
item = contact.select_single("QQuickRectangle",
objectName="mainItem")
self.assertThat(item.color, Eventually(Equals(contact.color)))
items.append(item)
item_to_contacts[item] = contact
# click on mark 1
selected_items = [items[1]]
self.pointing_device.click_object(items[1])
for item in items:
if item in selected_items:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(True)))
else:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(False)))
# click on mark 2
selected_items.append(items[2])
self.pointing_device.click_object(items[2])
for item in items:
if item in selected_items:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(True)))
else:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(False)))
# click on mark 0
selected_items.append(items[0])
self.pointing_device.click_object(items[0])
for item in items:
if item in selected_items:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(True)))
else:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(False)))
buttons = pick_page.select_many(
"Button",
objectName="DialogButtons.acceptButton")
for b in buttons:
if b.visible:
self.pointing_device.click_object(b)
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_single_pick_mode.py 0000644 0000156 0000165 00000007302 12674534204 033607 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Copyright 2013 Canonical
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
"""Tests for the Addressbook App"""
from testtools.matchers import Equals
from autopilot.matchers import Eventually
from address_book_app.tests import AddressBookAppTestCase
class TestSinglePickerMode(AddressBookAppTestCase):
""" Tests app in single picker mode"""
PRELOAD_VCARD = True
def setUp(self):
AddressBookAppTestCase.ARGS.append("addressbook:///pick?single=true")
super(TestSinglePickerMode, self).setUp()
def test_select_single_contact(self):
pick_page = self.app.main_window.get_contact_list_pick_page()
contacts = pick_page.select_many("ContactDelegate")
# all selection items should be invisible
selected_items = []
item_to_contacts = {}
for contact in contacts:
if (contact.visible):
item = contact.select_single("QQuickRectangle",
objectName="mainItem")
self.assertThat(contact.selected, Eventually(Equals(False)))
self.assertThat(item.color, Eventually(Equals(contact.color)))
selected_items.append(item)
item_to_contacts[item] = contact
# click on item 1
selected_item = selected_items[1]
self.pointing_device.click_object(selected_item)
for item in selected_items:
if item == selected_item:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(True)))
self.assertThat(item.color,
Eventually(Equals(contact.selectedColor)))
else:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(False)))
self.assertThat(item.color,
Eventually(Equals(contact.color)))
# click on item 2
selected_item = selected_items[2]
self.pointing_device.click_object(selected_item)
for item in selected_items:
if item == selected_item:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(True)))
self.assertThat(item.color,
Eventually(Equals(contact.selectedColor)))
else:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(False)))
self.assertThat(item.color,
Eventually(Equals(contact.color)))
# click on item 0
selected_item = selected_items[0]
self.pointing_device.click_object(selected_item)
for item in selected_items:
if item == selected_item:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(True)))
self.assertThat(item.color,
Eventually(Equals(contact.selectedColor)))
else:
self.assertThat(item_to_contacts[item].selected,
Eventually(Equals(False)))
self.assertThat(item.color,
Eventually(Equals(contact.color)))
buttons = pick_page.select_many(
"Button",
objectName="DialogButtons.acceptButton")
for b in buttons:
if b.visible:
self.pointing_device.click_object(b)
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_delete_contact.py 0000644 0000156 0000165 00000004773 12674534204 033302 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
"""Delete tests for the Addressbook App."""
from address_book_app import tests
class TestDeleteSelectContact(tests.AddressBookAppTestCase):
"""
Delete a contact using pick mode and verify the behavior of Cancel and
Delete actions
"""
PRELOAD_VCARD = True
ALL_CONTACTS = [
'teste test34',
'teste teste2',
'teste3 teste3',
]
scenarios = [
('single_cancel', {
'select': [ALL_CONTACTS[1]],
'action': 'cancel',
'expected_result': ALL_CONTACTS}),
('multiple_cancel', {
'select': [ALL_CONTACTS[1], ALL_CONTACTS[2]],
'action': 'cancel',
'expected_result': ALL_CONTACTS}),
('single_delete', {
'select': [ALL_CONTACTS[1]],
'action': 'delete',
'expected_result': [ALL_CONTACTS[0], ALL_CONTACTS[2]]}),
('multiple_delete', {
'select': [ALL_CONTACTS[1], ALL_CONTACTS[2]],
'action': 'delete',
'expected_result': [ALL_CONTACTS[0]]}),
]
def test_select(self):
"""
Delete a contact in pick mode
This test switch the contact list view to pick mode and validate the
behavior of Cancel and delete actions by comparing the numbers of
contact in the list before and after the action.
Note that it doesn't check which contact has been deleted.
"""
list_page = self.app.main_window.get_contact_list_page()
indices = [self.ALL_CONTACTS.index(name) for name in self.select]
list_page.select_contacts(indices)
if self.action == "cancel":
self.app.main_window.cancel()
elif self.action == "delete":
list_page.delete_selected_contacts(self.app.main_window)
self.assertEqual(list_page.get_contacts(), self.expected_result)
././@LongLink 0000000 0000000 0000000 00000000150 00000000000 011211 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_custom_proxy_objects.py address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/tests/test_custom_proxy_objects0000644 0000156 0000165 00000002575 12674534204 034160 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright 2014 Canonical
#
# This file is part of address-book-app
#
# address-book-app is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
from address_book_app.address_book import data
from address_book_app import tests
class ContactEditorTestCase(tests.AddressBookAppTestCase):
def test_fill_form(self):
"""Test that the form can be filled with contact information."""
test_contact = data.Contact.make_unique(unique_id='test_uuid')
# TODO implement the filling of professional details.
# --elopio - 2014-03-01
test_contact.professional_details = []
contact_editor = self.app.main_window.go_to_add_contact()
contact_editor.fill_form(test_contact)
form_values = contact_editor._get_form_values()
self.assertEqual(test_contact, form_values)
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/ 0000755 0000156 0000165 00000000000 12674534512 030200 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/__init__.py 0000644 0000156 0000165 00000002455 12674534204 032315 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
__all__ = [
'ContactEditorPage',
'ContactViewPage',
'SIMCardImportPage',
'RemoveContactDialog',
'PageWithHeader',
'PageWithBottomEdge'
]
from address_book_app.address_book._contact_editor_page \
import ContactEditorPage
from address_book_app.address_book._contact_view_page import ContactViewPage
from address_book_app.address_book._sim_card_import_page \
import SIMCardImportPage
from address_book_app.address_book._common import PageWithHeader
from address_book_app.address_book._common import PageWithBottomEdge
from address_book_app.address_book._remove_contact_dialog \
import RemoveContactsDialog
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/data.py 0000644 0000156 0000165 00000014147 12674534204 031470 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright 2014 Canonical
#
# This file is part of address-book-app
#
# address-book-app is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import uuid
class AddressBookAppDataError(Exception):
"""Exception raised when there is an error with the data."""
class DataMixin(object):
"""Mixin with common methods for data objects."""
def __repr__(self):
return '%s(%r)' % (self.__class__, self.__dict__)
def __eq__(self, other):
return (isinstance(other, self.__class__) and
self.__dict__ == other.__dict__)
def __ne__(self, other):
return not self.__eq__(other)
class Phone(DataMixin):
"""Phone data object for user acceptance tests."""
TYPES = ('Home', 'Work', 'Mobile', 'Work Mobile', 'Other')
def __init__(self, type_, number):
if type_ not in self.TYPES:
raise AddressBookAppDataError(
'Unknown phone type: {}.'.format(type_))
self.type = type_
self.number = number
@classmethod
def make(cls):
"""Return a Phone data object."""
return cls(type_='Mobile', number='(818) 777-7755')
class Email(DataMixin):
"""Email data object for user acceptance tests."""
TYPES = ('Home', 'Work', 'Other')
def __init__(self, type_, address):
if type_ not in self.TYPES:
raise AddressBookAppDataError(
'Unknown email type: {}.'.format(type_))
self.type = type_
self.address = address
@classmethod
def make_unique(cls, unique_id=None):
"""Return a unique Email data object."""
if unique_id is None:
unique_id = str(uuid.uuid1())
type_ = 'Home'
address = 'test+{0}@example.com'.format(unique_id)
return cls(type_=type_, address=address)
class SocialAlias(DataMixin):
"""Social Alias data object for user acceptance tests."""
TYPES = ('Aim', 'ICQ', 'Jabber', 'MSN', 'Skype', 'Yahoo')
def __init__(self, type_, alias):
if type_ not in self.TYPES:
raise AddressBookAppDataError(
'Unknown social alias type: {}.'.format(type_))
self.type = type_
self.alias = alias
@classmethod
def make_unique(cls, unique_id=None):
"""Return a unique Social Alias data object."""
if unique_id is None:
unique_id = str(uuid.uuid1())
type_ = 'Skype'
alias = 'Test alias {}'.format(unique_id)
return cls(type_=type_, alias=alias)
class Address(DataMixin):
"""Address data object for user acceptance tests."""
TYPES = ('Home', 'Work', 'Other')
def __init__(self, type_, street, locality, region, postal_code, country):
if type_ not in self.TYPES:
raise AddressBookAppDataError(
'Unknown address type: {}.'.format(type_))
self.type = type_
self.street = street
self.locality = locality
self.region = region
self.postal_code = postal_code
self.country = country
@classmethod
def make_unique(cls, unique_id=None):
"""Return a unique address data object."""
if unique_id is None:
unique_id = str(uuid.uuid1())
type_ = 'Home'
street = 'Test street {}'.format(unique_id)
locality = 'Test locality {}'.format(unique_id)
region = 'Test region {}'.format(unique_id)
postal_code = 'Test postal code {}'.format(unique_id)
country = 'Test country {}'.format(unique_id)
return cls(
type_=type_, street=street, locality=locality, region=region,
postal_code=postal_code, country=country)
class ProfessionalDetails(DataMixin):
"""Professional Details data objects for user acceptance tests."""
def __init__(self, organization, role, title):
self.organization = organization
self.role = role
self.title = title
@classmethod
def make_unique(cls, unique_id=None):
"""Return a unique data object with Professional Details."""
if unique_id is None:
unique_id = str(uuid.uuid1())
organization = 'Test organization {}'.format(unique_id)
role = 'Test role {}'.format(unique_id)
title = 'Test title {}'.format(unique_id)
return cls(organization=organization, role=role, title=title)
class Contact(DataMixin):
"""Contact data object for user acceptance tests."""
def __init__(
self, first_name=None, last_name=None, phones=None, emails=None,
social_aliases=None, addresses=None, professional_details=None):
self.first_name = first_name
self.last_name = last_name
self.phones = phones
self.emails = emails
self.social_aliases = social_aliases
self.addresses = addresses
self.professional_details = professional_details
@classmethod
def make_unique(cls, unique_id=None):
"""Return a unique Contact data object."""
if unique_id is None:
unique_id = str(uuid.uuid1())
first_name = 'Test first name {}'.format(unique_id)
last_name = 'Test last name {}'.format(unique_id)
phone = Phone.make()
email = Email.make_unique(unique_id)
social_alias = SocialAlias.make_unique(unique_id)
address = Address.make_unique(unique_id)
professional_details = ProfessionalDetails.make_unique(unique_id)
return cls(
first_name=first_name, last_name=last_name, phones=[phone],
emails=[email], social_aliases=[social_alias],
addresses=[address], professional_details=[professional_details])
././@LongLink 0000000 0000000 0000000 00000000150 00000000000 011211 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/_contact_view_page.py address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/_contact_view_page0000644 0000156 0000165 00000001544 12674534204 033745 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
from address_book_app.address_book import _common, _contact_editor_page
class ContactViewPage(_common.PageWithHeader):
"""Autopilot helper for the ContactView page."""
pass
././@LongLink 0000000 0000000 0000000 00000000153 00000000000 011214 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/_sim_card_import_page.py address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/_sim_card_import_p0000644 0000156 0000165 00000007376 12674534204 033767 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import logging
import autopilot.logging
from address_book_app.address_book import _common
logger = logging.getLogger(__name__)
log_action_info = autopilot.logging.log_action(logging.info)
log_action_debug = autopilot.logging.log_action(logging.debug)
class SIMCardImportPage(_common.PageWithHeader):
"""Autopilot helper for the SIMCardImportPage page."""
def _get_sorted_contact_delegates(self):
# FIXME this returns only the contact delegates that are loaded in
# memory. The list might be big, so not all delegates might be in
# memory at the same time.
contact_delegates = self.select_many('ContactDelegate', visible=True)
return sorted(
contact_delegates, key=lambda delegate: delegate.globalRect.y)
def _get_contact_delegate(self, index):
contact_delegates = self._get_sorted_contact_delegates()
return contact_delegates[index]
def _start_selection(self, index):
# TODO change this for click_object once the press duration
# parameter is added. See http://pad.lv/1268782
contact = self._get_contact_delegate(index)
self.pointing_device.move_to_object(contact)
self.pointing_device.press()
time.sleep(2.0)
self.pointing_device.release()
view = self._get_list_view()
view.isInSelectionMode.wait_for(True)
def _get_list_view(self):
return self.wait_select_single(
'ContactListView', objectName='contactListViewFromSimCard')
@log_action_debug
def _deselect_all(self):
"""Deselect all contacts."""
view = self._get_list_view()
if view.isInSelectionMode:
contacts = self.select_many('ContactDelegate', visible=True)
for contact in contacts:
if contact.selected:
logger.info('Deselect {}.'.format(contact.objectName))
self.pointing_device.click_object(contact)
else:
logger.debug('The page is not in selection mode.')
@log_action_info
def get_contacts(self):
"""Return a list with the names of the contacts."""
contact_delegates = self._get_sorted_contact_delegates()
name_labels = [
delegate.select_single('UCLabel', objectName='nameLabel') for
delegate in contact_delegates
]
return [label.text for label in name_labels]
@log_action_info
def select_contacts(self, indices):
""" Select contacts corresponding to the list of index in indices
:param indices: List of integers
"""
contacts = []
self._deselect_all()
if len(indices) > 0:
view = self._get_list_view()
if not view.isInSelectionMode:
self._start_selection(indices[0])
indices = indices[1:]
for index in indices:
contact = self._get_contact_delegate(index)
self.pointing_device.click_object(contact)
contacts.append(contact.select_single(
'UCLabel', objectName='nameLabel').text)
return contacts
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/_errors.py 0000644 0000156 0000165 00000001510 12674534204 032220 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import ubuntuuitoolkit
class AddressBookAppError(ubuntuuitoolkit.ToolkitException):
"""Exception raised when there is an error with the emulator."""
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/_common.py 0000644 0000156 0000165 00000003730 12674534204 032202 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import logging
import ubuntuuitoolkit
from autopilot.introspection import dbus
logger = logging.getLogger(__name__)
class PageWithHeader(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
def get_header(self, main_window_name='MainWindow'):
"""Return the Header custom proxy object of the Page."""
return self.get_root_instance().select_single(
main_window_name).get_header()
class PageWithBottomEdge(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
"""An emulator class that makes it easy to interact with the bottom edge
swipe page"""
def reveal_bottom_edge_page(self):
"""Bring the bottom edge page to the screen"""
try:
action_item = self.wait_select_single(objectName='bottomEdgeTip')
action_item.enabled.wait_for(True)
start_x = (action_item.globalRect.x +
(action_item.globalRect.width * 0.5))
start_y = action_item.globalRect.y + (action_item.height * 0.2)
stop_y = start_y - (self.height * 0.7)
self.pointing_device.drag(
start_x, start_y, start_x, stop_y, rate=2)
self.isReady.wait_for(True)
except dbus.StateNotFoundError:
logger.error('ButtomEdge element not found.')
raise
././@LongLink 0000000 0000000 0000000 00000000152 00000000000 011213 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/_contact_editor_page.py address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/_contact_editor_pa0000644 0000156 0000165 00000026546 12674534204 033756 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import collections
import logging
import time
import autopilot.logging
import ubuntuuitoolkit
from address_book_app.address_book import data, _errors, _common
logger = logging.getLogger(__name__)
_TEXT_FIELD_OBJECT_NAMES = {
'first_name': 'firstName',
'last_name': 'lastName',
'street': 'streetAddress_{}',
'locality': 'localityAddress_{}',
'region': 'regionAddress_{}',
'postal_code': 'postcodeAddress_{}',
'country': 'countryAddress_{}'
}
def _get_text_field(parent, field, index=None):
if field not in _TEXT_FIELD_OBJECT_NAMES:
raise _errors.AddressBookAppError('Unknown field: {}.'.format(field))
object_name = _TEXT_FIELD_OBJECT_NAMES[field]
if index is not None:
object_name = object_name.format(index)
return parent.select_single(TextInputDetail, objectName=object_name)
class ContactEditorPage(_common.PageWithHeader):
"""Custom proxy object for the Contact Editor."""
_DETAIL_ALIAS = {
'phones': 'Phone',
'emails': 'Email',
'ims': 'Social',
'addresses': 'Address',
'professionalDetails': 'Professional Details'
}
@autopilot.logging.log_action(logger.info)
def add_field(self, detail_name):
"""Create a new field into the edit contact form.
:param detail_name: The detail field name
"""
add_field_button = self.select_single(
'ComboButtonAddField', objectName='addNewFieldButton')
add_field_button.swipe_into_view()
self.pointing_device.click_object(add_field_button)
add_field_button.height.wait_for(add_field_button.expandedHeight)
self.wait_to_stop_moving()
options_list = add_field_button.select_single(
"QQuickListView",
objectName="listViewOptions")
new_field_item = options_list.select_single(
"Standard",
objectName=self._DETAIL_ALIAS[detail_name])
new_field_item.swipe_into_view()
self.pointing_device.click_object(new_field_item)
add_field_button.height.wait_for(add_field_button.collapsedHeight)
@autopilot.logging.log_action(logger.info)
def fill_form(self, contact_information):
"""Fill the edit contact form.
:param contact_information: Values of the contact to fill the form.
:type contact_information: data object with the attributes first_name,
last_name, phones, emails, social_aliases, addresses and
professional_details
"""
if contact_information.first_name is not None:
self._fill_first_name(contact_information.first_name)
if contact_information.last_name is not None:
self._fill_last_name(contact_information.last_name)
groups = collections.OrderedDict()
groups['phones'] = contact_information.phones
groups['emails'] = contact_information.emails
groups['ims'] = contact_information.social_aliases
groups['addresses'] = contact_information.addresses
groups['professionalDetails'] = (
contact_information.professional_details)
for key, information in groups.items():
if information:
self._fill_detail_group(
object_name=key, details=information)
def _fill_first_name(self, first_name):
text_field = _get_text_field(self, 'first_name')
text_field.write(first_name)
def _fill_last_name(self, last_name):
text_field = _get_text_field(self, 'last_name')
text_field.write(last_name)
def _fill_detail_group(self, object_name, details):
editor = self.select_single(
ContactDetailGroupWithTypeEditor, objectName=object_name)
editor.fill(self, details)
def _get_form_values(self):
first_name = _get_text_field(self, 'first_name').text
last_name = _get_text_field(self, 'last_name').text
phones = self._get_values_from_detail_group(object_name='phones')
emails = self._get_values_from_detail_group(object_name='emails')
social_aliases = self._get_values_from_detail_group(object_name='ims')
addresses = self._get_values_from_detail_group(object_name='addresses')
professional_details = self._get_values_from_detail_group(
object_name='professionalDetails')
return data.Contact(
first_name=first_name, last_name=last_name, phones=phones,
emails=emails, social_aliases=social_aliases, addresses=addresses,
professional_details=professional_details)
def _get_values_from_detail_group(self, object_name):
editor = self.select_single(
ContactDetailGroupWithTypeEditor, objectName=object_name)
return editor.get_values(object_name)
def wait_to_stop_moving(self):
flickable = self.select_single(
'QQuickFlickable', objectName='scrollArea')
flickable.flicking.wait_for(False)
def wait_get_focus(self, section_name):
editor = self.select_single(
ContactDetailGroupWithTypeEditor, objectName=section_name)
editor.activeFocus.wait_for(True)
class TextInputDetail(ubuntuuitoolkit.TextField):
"""Custom proxy object for the Text Input Detail field."""
class ContactDetailGroupWithTypeEditor(
ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
"""Custom proxy object for the ContactDetailGroupWithTypeEditor widget."""
_DETAIL_EDITORS = {
'phones': 'base_phoneNumber_{}',
'emails': 'base_email_{}',
'ims': 'base_onlineAccount_{}',
'addresses': 'base_address_{}',
# FIXME fix the unknown. --elopio - 2014-03-01
'professionalDetails': 'base_unknown_{}'
}
def fill(self, editor, details):
"""Fill a contact detail group."""
for index, detail in enumerate(details):
if self.detailsCount <= index:
editor.add_field(self.objectName)
self._fill_detail(index, detail)
def _fill_detail(self, index, detail):
detail_editor = self._get_detail_editor_by_index(index)
detail_editor.fill(field=self.objectName, index=index, detail=detail)
def _get_detail_editor_by_index(self, index):
object_name = self._get_contact_detail_editor_object_name(index)
return self.select_single(
ContactDetailWithTypeEditor, objectName=object_name)
def _get_contact_detail_editor_object_name(self, index):
return self._DETAIL_EDITORS[self.objectName].format(index)
def _add_detail(self):
# TODO --elopio - 2014-03-01
raise NotImplementedError('Add extra details not yet implemented.')
def get_values(self, object_name):
"""Return the values of a contact detail group."""
values = []
for index in range(self.detailsCount):
detail_editor = self._get_detail_editor_by_index(index)
value = detail_editor.get_values(field=object_name, index=index)
if (value):
values.append(value)
return values
class ContactDetailWithTypeEditor(
ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
"""Custom proxy object for the ContactDetailWithTypeEditor widget."""
def fill(self, field, index, detail):
self._fill_value(field, index, detail)
self._select_type(detail)
def _select_type(self, detail):
type_index = detail.TYPES.index(detail.type)
value_selector = self.select_single('ValueSelector')
while(value_selector.currentIndex != type_index):
ubuntuuitoolkit.get_keyboard().press_and_release("Shift+Right")
time.sleep(0.1)
def _get_selected_type_index(self):
value_selector = self.select_single('ValueSelector')
return value_selector.currentIndex
def _fill_value(self, field, index, detail):
single_values = {
'phones': 'number',
'emails': 'address',
'ims': 'alias'
}
if field in single_values:
self._fill_single_field(getattr(detail, single_values[field]))
elif field == 'addresses':
self._fill_address(index, detail)
elif field == 'professionalDetails':
self._fill_professional_details(index, detail)
else:
raise _errors.AddressBookAppError(
'Unknown field: {}.'.format(field))
def _fill_single_field(self, value):
text_field = self.select_single(TextInputDetail)
self._make_field_visible_and_write(text_field, value)
def _make_field_visible_and_write(self, text_field, value):
text_field.swipe_into_view()
text_field.write(value)
def _fill_address(self, index, address):
fields = collections.OrderedDict()
fields['street'] = address.street
fields['locality'] = address.locality
fields['region'] = address.region
fields['postal_code'] = address.postal_code
fields['country'] = address.country
for key, value in fields.items():
text_field = _get_text_field(self, key, index)
self._make_field_visible_and_write(text_field, value)
def _fill_professional_details(self, index, address):
# TODO --elopio - 2014-03-01
raise NotImplementedError('Not yet implemented.')
def get_values(self, field, index):
if field == 'phones':
return data.Phone(
type_=data.Phone.TYPES[self._get_selected_type_index()],
number=self._get_single_field_value())
if field == 'emails':
return data.Email(
type_=data.Email.TYPES[self._get_selected_type_index()],
address=self._get_single_field_value())
if field == 'ims':
return data.SocialAlias(
type_=data.SocialAlias.TYPES[self._get_selected_type_index()],
alias=self._get_single_field_value())
if field == 'addresses':
return self._get_address_value(index)
if field == 'professionalDetails':
# TODO --elopio - 2014-03-01
return None
raise _errors.AddressBookAppError('Unknown field: {}.'.format(field))
def _get_single_field_value(self):
return self.select_single(TextInputDetail).text
def _get_address_value(self, index):
street = _get_text_field(self, 'street', index).text
locality = _get_text_field(self, 'locality', index).text
region = _get_text_field(self, 'region', index).text
postal_code = _get_text_field(self, 'postal_code', index).text
country = _get_text_field(self, 'country', index).text
return data.Address(
type_=data.Address.TYPES[self._get_selected_type_index()],
street=street, locality=locality, region=region,
postal_code=postal_code, country=country)
././@LongLink 0000000 0000000 0000000 00000000154 00000000000 011215 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/_remove_contact_dialog.py address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/address_book/_remove_contact_di0000644 0000156 0000165 00000002265 12674534204 033751 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import logging
import autopilot.logging
import ubuntuuitoolkit
logger = logging.getLogger(__name__)
class RemoveContactsDialog(
ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
"""Autopilot helper for the Remove Contacts dialog."""
@autopilot.logging.log_action(logger.debug)
def confirm_removal(self):
button = self.select_single(
'Button', objectName='removeContactsDialog.Yes')
self.pointing_device.click_object(button)
self.wait_until_destroyed()
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/helpers.py 0000644 0000156 0000165 00000003140 12674534204 027551 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright 2014 Canonical Ltd.
# Author: Omer Akram
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import dbus
def remove_phonesim():
bus = dbus.SystemBus()
try:
manager = dbus.Interface(bus.get_object('org.ofono', '/'),
'org.ofono.phonesim.Manager')
except dbus.exceptions.DBusException:
return False
manager.RemoveAll()
def reset_phonesim():
bus = dbus.SystemBus()
try:
manager = dbus.Interface(bus.get_object('org.ofono', '/'),
'org.ofono.phonesim.Manager')
except dbus.exceptions.DBusException:
return False
manager.Reset()
def is_phonesim_running():
"""Determine whether we are running with phonesim."""
bus = dbus.SystemBus()
try:
manager = dbus.Interface(bus.get_object('org.ofono', '/'),
'org.ofono.phonesim.Manager')
except dbus.exceptions.DBusException:
return False
return (manager)
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/pages/ 0000755 0000156 0000165 00000000000 12674534512 026640 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/pages/__init__.py 0000644 0000156 0000165 00000001755 12674534204 030757 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
__all__ = [
'ABContactEditorPage',
'ABContactListPage',
'ABContactViewPage'
]
from address_book_app.pages._ab_contact_editor_page import ABContactEditorPage
from address_book_app.pages._ab_contact_view_page import ABContactViewPage
from address_book_app.pages._ab_contact_list_page import ABContactListPage
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/pages/_ab_contact_view_page.py 0000644 0000156 0000165 00000001716 12674534204 033477 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
""" ContactViewPage emulator for Addressbook App tests """
import logging
import time
import autopilot.logging
import ubuntuuitoolkit
from address_book_app import address_book
class ABContactViewPage(address_book.ContactViewPage):
"""Autopilot helper for the Contact View page."""
address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/pages/_ab_contact_list_page.py 0000644 0000156 0000165 00000012106 12674534204 033473 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
""" ContactListPage emulator for Addressbook App tests """
import logging
import time
import autopilot.logging
import ubuntuuitoolkit
import address_book_app.address_book as address_book
from autopilot.introspection import dbus
from address_book_app.pages import ABContactViewPage
logger = logging.getLogger(__name__)
log_action_info = autopilot.logging.log_action(logging.info)
log_action_debug = autopilot.logging.log_action(logging.debug)
class ABContactListPage(address_book.PageWithHeader):
"""Autopilot helper for the Contact List page."""
@log_action_info
def open_contact(self, index):
"""Open the page with the contact information.
:param index: The index of the contact to open.
:return: The page with the contact information.
"""
contact_delegate = self._get_contact_delegate(index)
self.pointing_device.click_object(contact_delegate)
# WORKAROUND: give some time to the view became available
time.sleep(5.0)
return self.get_root_instance().select_single(
ABContactViewPage, objectName='contactViewPage', active=True)
def _get_contact_delegate(self, index):
contact_delegates = self._get_sorted_contact_delegates()
return contact_delegates[index]
def _get_sorted_contact_delegates(self):
# FIXME this returns only the contact delegates that are loaded in
# memory. The list might be big, so not all delegates might be in
# memory at the same time.
contact_delegates = self.select_many('ContactDelegate', visible=True)
return sorted(
contact_delegates, key=lambda delegate: delegate.globalRect.y)
@log_action_info
def select_contacts(self, indices):
""" Select contacts corresponding to the list of index in indices
:param indices: List of integers
"""
self._deselect_all()
if len(indices) > 0:
view = self._get_list_view()
if not view.isInSelectionMode:
self._start_selection(indices[0])
indices = indices[1:]
for index in indices:
contact = self._get_contact_delegate(index)
self.pointing_device.click_object(contact)
@log_action_debug
def _deselect_all(self):
"""Deselect all contacts."""
view = self._get_list_view()
if view.isInSelectionMode:
contacts = self.select_many('ContactDelegate', visible=True)
for contact in contacts:
if contact.selected:
logger.info('Deselect {}.'.format(contact.objectName))
self.pointing_device.click_object(contact)
else:
logger.debug('The page is not in selection mode.')
def _start_selection(self, index):
# TODO change this for click_object once the press duration
# parameter is added. See http://pad.lv/1268782
contact = self._get_contact_delegate(index)
self.pointing_device.move_to_object(contact)
self.pointing_device.press()
time.sleep(2.0)
self.pointing_device.release()
view = self._get_list_view()
view.isInSelectionMode.wait_for(True)
def _get_list_view(self):
return self.wait_select_single(
'ContactListView', objectName='contactListView')
@log_action_info
def delete_selected_contacts(self, main_window):
main_window.delete()
main_window.wait_bottom_edge(False)
dialog = self.get_root_instance().wait_select_single(
address_book.RemoveContactsDialog, objectName='removeContactsDialog')
dialog.confirm_removal()
def get_contacts(self):
"""Return a list with the names of the contacts."""
contact_delegates = self._get_sorted_contact_delegates()
name_labels = [
delegate.select_single('UCLabel', objectName='nameLabel') for
delegate in contact_delegates
]
return [label.text for label in name_labels]
def get_button(self, buttonName):
try:
return self.get_header()._get_action_button(buttonName)
except ubuntuuitoolkit.ToolkitException:
return None
def is_import_from_sim_button_visible(self):
import_from_sim_button = self.select_single(
'ContactListButtonDelegate',
objectName='contactListView.importFromSimCardButton')
return import_from_sim_button.visible
././@LongLink 0000000 0000000 0000000 00000000146 00000000000 011216 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/pages/_ab_contact_editor_page.py address-book-app-0.2+16.04.20160323/tests/autopilot/address_book_app/pages/_ab_contact_editor_page.p0000644 0000156 0000165 00000001726 12674534204 033623 0 ustar pbuser pbgroup 0000000 0000000 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
""" ContactEditorPage emulator for Addressbook App tests """
import logging
import time
import autopilot.logging
import ubuntuuitoolkit
from address_book_app import address_book
class ABContactEditorPage(address_book.ContactEditorPage):
"""Autopilot helper for the Contact Editor page."""
address-book-app-0.2+16.04.20160323/tests/autopilot/CMakeLists.txt 0000644 0000156 0000165 00000001116 12674534204 024777 0 ustar pbuser pbgroup 0000000 0000000 set(AUTOPILOT_DIR address_book_app)
execute_process(COMMAND python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
OUTPUT_VARIABLE PYTHON_PACKAGE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
if(INSTALL_TESTS)
install(DIRECTORY ${AUTOPILOT_DIR}
DESTINATION ${PYTHON_PACKAGE_DIR}
)
endif()
set(AUTOPILOT_ENV "AUTOPILOT_APP=${address-book-main-app_BINARY_DIR}/address-book-app\;QTCONTACTS_MANAGER_OVERRIDE=memory")
declare_autopilot_test(${AUTOPILOT_ENV}
address_book_app
${CMAKE_CURRENT_SOURCE_DIR})
address-book-app-0.2+16.04.20160323/tests/CMakeLists.txt 0000644 0000156 0000165 00000000157 12674534204 022763 0 ustar pbuser pbgroup 0000000 0000000 add_subdirectory(qml)
if(ENABLE_AUTOPILOT)
add_subdirectory(autopilot)
add_subdirectory(data)
endif()
address-book-app-0.2+16.04.20160323/src/ 0000755 0000156 0000165 00000000000 12674534512 017647 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/src/app/ 0000755 0000156 0000165 00000000000 12674534512 020427 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/src/app/main.cpp 0000644 0000156 0000165 00000001572 12674534204 022062 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "config.h"
#include "addressbookapp.h"
// Qt
#include
int main(int argc, char** argv)
{
AddressBookApp application(argc, argv);
if (!application.setup()) {
return 0;
}
return application.exec();
}
address-book-app-0.2+16.04.20160323/src/app/addressbookapp.h 0000644 0000156 0000165 00000005234 12674534204 023603 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#ifndef ADDRESSBOOK_APP_H
#define ADDRESSBOOK_APP_H
#include
#include
#include
#include
#include
#include
class AddressBookApp : public QGuiApplication
{
Q_OBJECT
Q_PROPERTY(bool firstRun READ isFirstRun CONSTANT)
Q_PROPERTY(QString callbackApplication READ callbackApplication WRITE setCallbackApplication NOTIFY callbackApplicationChanged)
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
Q_PROPERTY(bool serverSafeMode READ serverSafeMode NOTIFY serverSafeModeChanged)
Q_PROPERTY(bool updating READ updating NOTIFY updatingChanged)
public:
AddressBookApp(int &argc, char **argv);
virtual ~AddressBookApp();
bool setup();
QString callbackApplication() const;
void setCallbackApplication(const QString &application);
bool isOnline() const;
bool serverSafeMode() const;
bool updating() const;
Q_SIGNALS:
void callbackApplicationChanged();
void isOnlineChanged();
void serverSafeModeChanged();
void updatingChanged();
void sourcesChanged();
public Q_SLOTS:
void activateWindow();
void parseUrl(const QString &arg);
void onViewStatusChanged(QQuickView::Status status);
void returnVcard(const QUrl &url);
bool isFirstRun() const;
void unsetFirstRun() const;
void goBackToSourceApp();
void startUpdate();
// debug
void elapsed() const;
private Q_SLOTS:
void onUpdateCallFinished(QDBusPendingCallWatcher *watcher);
private:
void callQMLMethod(const QString name, QStringList args);
void connectWithServer();
private:
QQuickView *m_view;
QScopedPointer m_netManager;
QScopedPointer m_server;
QScopedPointer m_updateWatcher;
QString m_initialArg;
QString m_callbackApplication;
bool m_viewReady;
bool m_pickingMode;
bool m_testMode;
bool m_withArgs;
};
#endif
address-book-app-0.2+16.04.20160323/src/app/addressbookapp.cpp 0000644 0000156 0000165 00000034126 12674534204 024140 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "config.h"
#include "addressbookapp.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ADDRESS_BOOK_FIRST_RUN_KEY "first-run"
static QElapsedTimer s_elapsed;
static void printUsage(const QStringList& arguments)
{
qDebug() << "usage:"
<< arguments.at(0).toUtf8().constData()
<< "[addressbook:///contact?id=]"
<< "[addressbook:///create?phone=]"
<< "[addressbook:///pick?single=]"
<< "[addressbook:///importvcard?url=]"
<< "[--fullscreen]"
<< "[--help]"
<< "[-testability]";
}
static bool clickModeEnabled()
{
return ((QString(ADDRESS_BOOK_APP_CLICK_PACKAGE).toLower() == "on") ||
(QString(ADDRESS_BOOK_APP_CLICK_PACKAGE) == "1"));
}
static QString fullPath(const QString &fileName)
{
QString result;
QString appPath = QCoreApplication::applicationDirPath();
if (appPath.startsWith(ADDRESS_BOOK_DEV_BINDIR)) {
result = QString(ADDRESS_BOOK_APP_DEV_DATADIR) + fileName;
} else if (clickModeEnabled()) {
result = appPath + QStringLiteral("/share/address-book-app/") + fileName;
} else {
result = QString(ADDRESS_BOOK_APP_INSTALL_DATADIR) + fileName;
}
return result;
}
static QString importPath(const QString &suffix)
{
QString appPath = QCoreApplication::applicationDirPath();
if (ADDRESS_BOOK_APP_CLICK_MODE) {
return QString(QT_EXTRA_IMPORTS_DIR) + suffix;
} else if (appPath.startsWith(ADDRESS_BOOK_DEV_BINDIR)) {
return QString(ADDRESS_BOOK_APP_DEV_DATADIR) + suffix;
} else {
return "";
}
}
//this is necessary to work on desktop
//On desktop use: export ADDRESS_BOOK_APP_ICON_THEME=ubuntu-mobile
static void installIconPath()
{
QByteArray iconTheme = qgetenv("ADDRESS_BOOK_APP_ICON_THEME");
if (!iconTheme.isEmpty()) {
qDebug() << "Register extra icon theme:" << iconTheme;
QIcon::setThemeName(iconTheme);
}
}
AddressBookApp::AddressBookApp(int &argc, char **argv)
: QGuiApplication(argc, argv),
m_view(0),
m_netManager(new QNetworkConfigurationManager),
m_pickingMode(false),
m_testMode(false),
m_withArgs(false)
{
s_elapsed.start();
setOrganizationName(SETTINGS_ORGANIZATION_NAME);
setApplicationName(SETTINGS_APP_NAME);
setOrganizationDomain(SETTINGS_ORGANIZATION_DOMAIN);
connect(m_netManager.data(),
SIGNAL(onlineStateChanged(bool)),
SIGNAL(isOnlineChanged()),
Qt::QueuedConnection);
}
bool AddressBookApp::setup()
{
installIconPath();
connectWithServer();
bool fullScreen = false;
QString contactKey;
QStringList arguments = this->arguments();
QByteArray defaultManager("galera");
QByteArray testData;
// use galare as default QtContacts Manager
if (qEnvironmentVariableIsSet("QTCONTACTS_MANAGER_OVERRIDE")) {
defaultManager = qgetenv("QTCONTACTS_MANAGER_OVERRIDE");
}
testData = qgetenv("ADDRESS_BOOK_TEST_DATA");
qDebug() << "Using contact manager:" << defaultManager;
if (arguments.contains("--help")) {
printUsage(arguments);
return false;
}
if (arguments.contains("--fullscreen")) {
arguments.removeAll("--fullscreen");
fullScreen = true;
}
// The testability driver is only loaded by QApplication but not by QGuiApplication.
// However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
if (arguments.contains(QLatin1String("-testability")) ||
qgetenv("QT_LOAD_TESTABILITY") == "1") {
arguments.removeAll("-testability");
QLibrary testLib(QLatin1String("qttestability"));
if (testLib.load()) {
typedef void (*TasInitialize)(void);
TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
if (initFunction) {
initFunction();
} else {
qCritical("Library qttestability resolve failed!");
}
} else {
qCritical("Library qttestability load failed!");
}
m_testMode = true;
}
/* Ubuntu APP Manager gathers info on the list of running applications from the .desktop
file specified on the command line with the desktop_file_hint switch, and will also pass a stage hint
So app will be launched like this:
/usr/bin/dialer-app --desktop_file_hint=/usr/share/applications/dialer-app.desktop
--stage_hint=main_stage
So remove whatever --arg still there before continue parsing
*/
for (int i = arguments.count() - 1; i >=0; --i) {
if (arguments[i].startsWith("--")) {
arguments.removeAt(i);
}
}
m_withArgs = arguments.size() > 1;
/* Configure "artwork:" prefix so that any access to a file whose name starts
with that prefix resolves properly. */
QDir::addSearchPath("artwork", fullPath("/artwork"));
m_view = new QQuickView();
m_viewReady = false;
QObject::connect(m_view, SIGNAL(statusChanged(QQuickView::Status)),
this, SLOT(onViewStatusChanged(QQuickView::Status)));
QObject::connect(m_view->engine(), SIGNAL(quit()), SLOT(quit()));
m_view->setMinimumWidth(300);
m_view->setMinimumHeight(500);
m_view->setResizeMode(QQuickView::SizeRootObjectToView);
m_view->setTitle("Contacts");
qDebug() << "New import path:" << QCoreApplication::applicationDirPath() + "/" + importPath("");
m_view->engine()->addImportPath(QCoreApplication::applicationDirPath() + "/" + importPath(""));
m_view->engine()->addImportPath(UNITY8_QML_PATH);
m_view->rootContext()->setContextProperty("QTCONTACTS_MANAGER_OVERRIDE", defaultManager);
m_view->rootContext()->setContextProperty("application", this);
m_view->rootContext()->setContextProperty("contactKey", contactKey);
m_view->rootContext()->setContextProperty("TEST_DATA", testData);
m_view->rootContext()->setContextProperty("i18nDirectory", I18N_DIRECTORY);
QUrl source(fullPath("/imports/MainWindow.qml"));
m_view->setSource(source);
if (fullScreen) {
m_view->showFullScreen();
} else {
m_view->show();
}
if (arguments.size() == 2) {
if (!m_viewReady) {
m_initialArg = arguments.at(1);
} else {
parseUrl(arguments.at(1));
}
}
return true;
}
AddressBookApp::~AddressBookApp()
{
unsetFirstRun();
if (m_view) {
delete m_view;
}
}
void AddressBookApp::onViewStatusChanged(QQuickView::Status status)
{
if (status == QQuickView::Ready) {
m_viewReady = true;
if (!m_initialArg.isEmpty()) {
parseUrl(m_initialArg);
m_initialArg.clear();
}
}
}
void AddressBookApp::returnVcard(const QUrl &url)
{
if (m_pickingMode) {
printf("%s\n", qPrintable(url.toString()));
this->quit();
}
}
bool AddressBookApp::isFirstRun() const
{
// if the app is running on test mode or with arguments we will not show the welcome screen
if (m_testMode || m_withArgs) {
return false;
} else {
QSettings settings;
return settings.value(ADDRESS_BOOK_FIRST_RUN_KEY, true).toBool();
}
}
void AddressBookApp::unsetFirstRun() const
{
// mark first run as false
QSettings settings;
settings.setValue(ADDRESS_BOOK_FIRST_RUN_KEY, false);
settings.sync();
}
void AddressBookApp::goBackToSourceApp()
{
if (!m_callbackApplication.isEmpty()) {
QDesktopServices::openUrl(QUrl(QString("application:///%1").arg(m_callbackApplication)));
m_callbackApplication.clear();
Q_EMIT callbackApplicationChanged();
}
}
void AddressBookApp::startUpdate()
{
if (m_updateWatcher) {
return;
}
QDBusMessage startUpdateCall = QDBusMessage::createMethodCall("com.canonical.pim.updater",
"/com/canonical/pim/Updater",
"com.canonical.pim.Updater",
"startUpdate");
QDBusPendingCall pcall = QDBusConnection::sessionBus().asyncCall(startUpdateCall);
m_updateWatcher.reset(new QDBusPendingCallWatcher(pcall, this));
QObject::connect(m_updateWatcher.data(), SIGNAL(finished(QDBusPendingCallWatcher*)),
this, SLOT(onUpdateCallFinished(QDBusPendingCallWatcher*)));
Q_EMIT updatingChanged();
}
void AddressBookApp::onUpdateCallFinished(QDBusPendingCallWatcher *watcher)
{
m_updateWatcher.reset(0);
Q_EMIT updatingChanged();
}
void AddressBookApp::parseUrl(const QString &arg)
{
QUrl url = QUrl::fromPercentEncoding(arg.toUtf8());
if (url.scheme() != "addressbook") {
return;
}
// Remove the first "/"
QString methodName = url.path().right(url.path().length() -1);
QStringList args;
QMap methodsMetaData;
if (methodsMetaData.isEmpty()) {
QStringList args;
//view
args << "id";
methodsMetaData.insert("contact", args);
args.clear();
//create
args << "phone";
methodsMetaData.insert("create", args);
args.clear();
//pick
args << "single";
methodsMetaData.insert("pick", args);
args.clear();
//vcard
args << "url";
methodsMetaData.insert("importvcard", args);
args.clear();
}
QUrlQuery query(url);
QList > queryItemsPair = query.queryItems();
QMap queryItems;
//convert items to map
for(int i=0; i < queryItemsPair.count(); i++) {
QPair item = queryItemsPair[i];
queryItems.insert(item.first, item.second);
}
// keep callback arg
setCallbackApplication(queryItems.take("callback"));
if (methodsMetaData.contains(methodName)) {
QStringList argsNames = methodsMetaData[methodName];
if (queryItems.size() != argsNames.size()) {
qWarning() << "invalid" << methodName << "arguments size";
return;
}
Q_FOREACH(QString arg, argsNames) {
if (queryItems.contains(arg)) {
args << queryItems[arg];
} else {
qWarning() << "argument" << arg << "not found in method" << methodName << "call";
return;
}
}
} else {
qWarning() << "method" << methodName << "not supported";
return;
}
callQMLMethod(methodName, args);
}
void AddressBookApp::callQMLMethod(const QString name, QStringList args)
{
QQuickItem *mainView = m_view->rootObject();
if (!mainView) {
return;
}
const QMetaObject *mo = mainView->metaObject();
// create QML signature: Ex. function(QVariant, QVariant, QVariant)
QString argsSignature = QString("QVariant,").repeated(args.size());
if (argsSignature.endsWith(",")) {
argsSignature = argsSignature.left(argsSignature.size() - 1);
}
QString methodSignature = QString("%1(%2)").arg(name).arg(argsSignature);
int index = mo->indexOfMethod(methodSignature.toUtf8().data());
if (index != -1) {
QMetaMethod method = mo->method(index);
switch(args.count()) {
case 0:
method.invoke(mainView);
break;
case 1:
method.invoke(mainView, Q_ARG(QVariant, QVariant(args[0])));
break;
case 2:
method.invoke(mainView, Q_ARG(QVariant, QVariant(args[0].toUtf8())),
Q_ARG(QVariant, QVariant(args[1].toUtf8())));
break;
default:
qWarning() << "Invalid arguments";
return;
}
}
m_pickingMode = (name == "pick");
}
void AddressBookApp::connectWithServer()
{
m_server.reset(new QDBusInterface("com.canonical.pim",
"/com/canonical/pim/AddressBook",
"com.canonical.pim.AddressBook"));
if (!m_server->isValid()) {
qWarning() << "Fail to connect with pim service.";
}
connect(m_server.data(), SIGNAL(safeModeChanged()), SIGNAL(serverSafeModeChanged()));
connect(m_server.data(), SIGNAL(sourcesChanged()), SIGNAL(sourcesChanged()));
Q_EMIT serverSafeModeChanged();
}
void AddressBookApp::activateWindow()
{
if (m_view) {
m_view->raise();
m_view->requestActivate();
}
}
void AddressBookApp::elapsed() const
{
qDebug() << "ELAPSED:" << s_elapsed.elapsed() / 1000.0;
}
QString AddressBookApp::callbackApplication() const
{
return m_callbackApplication;
}
void AddressBookApp::setCallbackApplication(const QString &application)
{
if (m_callbackApplication != application) {
m_callbackApplication = application;
Q_EMIT callbackApplicationChanged();
}
}
bool AddressBookApp::isOnline() const
{
return m_netManager->isOnline();
}
bool AddressBookApp::serverSafeMode() const
{
QDBusReply reply = m_server->call("safeMode");
return reply.value();
}
bool AddressBookApp::updating() const
{
return !m_updateWatcher.isNull();
}
address-book-app-0.2+16.04.20160323/src/app/CMakeLists.txt 0000644 0000156 0000165 00000001012 12674534204 023157 0 ustar pbuser pbgroup 0000000 0000000 project(address-book-main-app)
set(ADDRESS_BOOK_APP_BIN address-book-app)
include_directories(
${CMAKE_BINARY_DIR}
${AccountsQt5_INCLUDE_DIRS}
)
set(ADDRESS_BOOK_APP_SRCS
addressbookapp.h
addressbookapp.cpp
main.cpp
)
add_executable(${ADDRESS_BOOK_APP_BIN}
${ADDRESS_BOOK_APP_SRCS}
)
target_link_libraries(${ADDRESS_BOOK_APP_BIN}
Qt5::Core
Qt5::Gui
Qt5::Qml
Qt5::Quick
Qt5::DBus
)
install(TARGETS ${ADDRESS_BOOK_APP_BIN}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
address-book-app-0.2+16.04.20160323/src/imports/ 0000755 0000156 0000165 00000000000 12674534512 021344 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/ABMultiColumnEmptyState.qml 0000644 0000156 0000165 00000005027 12674534204 026554 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2016 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
Page {
id: root
property string headerTitle: i18n.tr("No contacts")
property bool openBottomEdgeWhenReady: false
header: PageHeader {
title: root.headerTitle
}
function commitBottomEdge()
{
if (bottomEdgeLoader.status !== Loader.Ready) {
openBottomEdgeWhenReady = true
} else {
bottomEdgeLoader.item.commit()
}
}
function close()
{
if (bottomEdge.item) {
bottomEdge.item.collapse()
}
}
ABEmptyState {
id: emptyStateScreen
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
leftMargin: units.gu(6)
rightMargin: units.gu(6)
}
height: childrenRect.height
text: ""
}
Loader {
id: bottomEdgeLoader
active: (pageStack.columns > 1)
asynchronous: true
sourceComponent: ABNewContactBottomEdge {
id: bottomEdge
hintVisible: false
parent: root
height: root.height
modelToEdit: root.pageStack.contactListPage.contactModel
hint.flickable: root.flickable
pageStack: root.pageStack
onCommitCompleted: { root.openBottomEdgeWhenReady = false }
}
onStatusChanged: {
if ((status === Loader.Ready) && root.openBottomEdgeWhenReady) {
bottomEdgeLoader.item.commit()
}
}
}
Binding {
target: pageStack
property: 'bottomEdge'
value: bottomEdgeLoader.item
when: bottomEdgeLoader.status === Loader.Ready
}
Connections {
target: pageStack
onColumnsChanged: {
if (pageStack.columns === 1) {
pageStack.removePages(root)
}
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/ABAdaptivePageLayout.qml 0000644 0000156 0000165 00000003361 12674534204 026013 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.0
import Ubuntu.Components 1.3
AdaptivePageLayout {
id: layout
function addFileToNextColumnSync(parentObject, resolvedUrl, properties) {
return addComponentToNextColumnSync(parentObject, Qt.createComponent(resolvedUrl), properties)
}
function addFileToCurrentColumnSync(parentObject, resolvedUrl, properties) {
return addComponentToCurrentColumnSync(parentObject, Qt.createComponent(resolvedUrl), properties)
}
function addComponentToNextColumnSync(parentObject, component, properties) {
if (typeof(properties) === 'undefined') {
properties = {}
}
var incubator = layout.addPageToNextColumn(parentObject, component, properties)
incubator.forceCompletion()
return incubator.object
}
function addComponentToCurrentColumnSync(parentObject, component, properties) {
if (typeof(properties) === 'undefined') {
properties = {}
}
var incubator = layout.addPageToCurrentColumn(parentObject, component, properties)
incubator.forceCompletion()
return incubator.object
}
}
address-book-app-0.2+16.04.20160323/src/imports/ABContactViewPage.qml 0000644 0000156 0000165 00000010263 12674534204 025305 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3 as Popups
import Ubuntu.Contacts 0.1
import Ubuntu.AddressBook.Base 0.1
import Ubuntu.AddressBook.ContactView 0.1
import Ubuntu.AddressBook.ContactShare 0.1
ContactViewPage {
id: root
objectName: "contactViewPage"
// used by autopilot test
readonly property string headerTitle: header.title
readonly property bool editing: _editPage != null
// FIXME: bug #1544745
// Adaptive layout is not destroying all pages correct, we do it manually for now
property var _editPage: null
function cancelEdit()
{
if (_editPage) {
pageStack.removePages(_editPage)
_editPage = null
}
if (pageStack.bottomEdge) {
pageStack.bottomEdge.collapse()
}
}
function editContact(contact)
{
root._editPage = pageStack.addComponentToCurrentColumnSync(root, Qt.resolvedUrl("ABContactEditorPage.qml"),
{ model: root.model, contact: contact, backIconName: 'back'})
root._editPage.Component.onDestruction(function() {
root._editPage = null
})
}
// Shortcut in case of single column
Action {
id: backAction
name: "cancel"
enabled: root.active && root.enabled && (pageStack.columns === 1)
shortcut: "Esc"
onTriggered: {
pageStack.removePages(root)
}
}
headerActions: [
Action {
objectName: "share"
name: "share"
text: i18n.tr("Share")
iconName: "share"
onTriggered: {
pageStack.addPageToCurrentColumn(root,
contactShareComponent,
{contactModel: root.model,
contacts: [root.contact]})
}
},
Action {
objectName: "edit"
name: "edit"
text: i18n.tr("Edit")
iconName: "edit"
enabled: root.active && !root.editing
shortcut: "Ctrl+e"
onTriggered: root.editContact(root.contact)
}
]
onContactRemoved: pageStack.removePages(root)
extensions: ContactDetailSyncTargetView {
contact: root.contact
anchors {
left: parent.left
right: parent.right
}
height: implicitHeight
}
onActionTrigerred: {
// "default" action is used inside of the apps (dialer, messaging) to trigger
// actions based on context.
// For example default action in the dialer app is call the contact number
if (action == "default") {
action = "tel";
}
Qt.openUrlExternally(("%1:%2").arg(action).arg(detail.value(0)))
}
Component {
id: contactShareComponent
ContactSharePage {}
}
Loader {
id: bottomEdgeLoader
active: (pageStack.columns > 1)
asynchronous: true
sourceComponent: ABNewContactBottomEdge {
id: bottomEdge
parent: root
height: root.height
modelToEdit: root.model
hint.flickable: root.flickable
pageStack: root.pageStack
hintVisible: false
enabled: !root.editing
}
}
Binding {
target: pageStack
property: 'bottomEdge'
value: bottomEdgeLoader.item
when: bottomEdgeLoader.status === Loader.Ready
}
}
address-book-app-0.2+16.04.20160323/src/imports/Settings/ 0000755 0000156 0000165 00000000000 12674534512 023144 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Settings/SettingsDefaultSyncTarget.qml 0000644 0000156 0000165 00000014663 12674534204 031000 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3
import Ubuntu.Contacts 0.1
import Ubuntu.AddressBook.Base 0.1
Empty {
id: root
signal changed()
function update()
{
sourceModel.update()
}
function save()
{
var changed = false
var selectedSource = getSelectedSource()
if (!selectedSource) {
return
}
var details = selectedSource.details(ContactDetail.ExtendedDetail)
for(var d in details) {
if (details[d].name === "IS-PRIMARY") {
if (!details[d].data) {
details[d].data = true
changed = true
break
}
}
}
if (changed) {
sourceModel.saveContact(selectedSource)
}
}
function getSelectedSource() {
if (sources.model.count <= 0)
return -1
return sources.model.get(sources.selectedIndex).source
}
height: sources.currentlyExpanded ?
sources.containerHeight + units.gu(6) + label.height :
sources.itemHeight + units.gu(6) + label.height
ContactModel {
id: sourceModel
manager: (typeof(QTCONTACTS_MANAGER_OVERRIDE) !== "undefined") && (QTCONTACTS_MANAGER_OVERRIDE != "") ?
QTCONTACTS_MANAGER_OVERRIDE : "galera"
filter: DetailFilter {
detail: ContactDetail.Type
field: Type.TypeField
value: Type.Group
matchFlags: DetailFilter.MatchExactly
}
autoUpdate: false
onContactsChanged: {
if (contacts.length > 0) {
writableSources.reload()
root.changed()
}
}
}
ListModel {
id: writableSources
function getSourceMetaData(contact) {
var metaData = {'read-only' : false,
'account-provider': '',
'account-id': 0,
'is-primary': false}
var details = contact.details(ContactDetail.ExtendedDetail)
for(var d in details) {
if (details[d].name === "READ-ONLY") {
metaData['read-only'] = details[d].data
} else if (details[d].name === "PROVIDER") {
metaData['account-provider'] = details[d].data
} else if (details[d].name === "APPLICATION-ID") {
metaData['account-id'] = details[d].data
} else if (details[d].name === "IS-PRIMARY") {
metaData['is-primary'] = details[d].data
}
}
return metaData
}
function reload() {
sources._notify = false
clear()
// filter out read-only sources
var contacts = sourceModel.contacts
if (contacts.length === 0) {
return
}
var data = []
for(var i in contacts) {
var sourceMetaData = getSourceMetaData(contacts[i])
if (!sourceMetaData['readOnly']) {
data.push({'sourceId': contacts[i].guid.guid,
'sourceName': contacts[i].displayLabel.label,
'accountId': sourceMetaData['account-id'],
'accountProvider': sourceMetaData['account-provider'],
'readOnly': sourceMetaData['read-only'],
'isPrimary': sourceMetaData['is-primary'],
'source': contacts[i]
})
}
}
data.sort(function(a, b) {
var valA = a.accountId
var valB = b.accountId
if (a.accountId == b.accountId) {
valA = a.sourceName
valB = b.sourceName
}
if (valA == valB) {
return 0
} else if (valA < valB) {
return -1
} else {
return 1
}
})
sources.selectedIndex = 0
var primaryIndex = 0
for (var i in data) {
if (data[i].isPrimary) {
primaryIndex = i
}
append(data[i])
}
// select primary account
sources.selectedIndex = primaryIndex
sources._notify = true
}
}
Label {
id: label
text: i18n.tr("Default address book")
anchors {
left: parent.left
top: parent.top
right: parent.right
margins: units.gu(2)
}
height: units.gu(4)
}
OptionSelector {
id: sources
property bool _notify: true
model: writableSources
anchors {
left: parent.left
leftMargin: units.gu(2)
top: label.bottom
right: parent.right
rightMargin: units.gu(2)
bottom: parent.bottom
bottomMargin: units.gu(2)
}
delegate: OptionSelectorDelegate {
text: {
if ((sourceId != "system-address-book") && (accountProvider == "")) {
return i18n.dtr("address-book-app", "Personal - %1").arg(sourceName)
} else {
return sourceName
}
}
height: units.gu(4)
}
containerHeight: sources.model && sources.model.count > 4 ? itemHeight * 4 : sources.model ? itemHeight * sources.model.count : 0
onSelectedIndexChanged: {
if (_notify && selectedIndex >= 0) {
root.changed()
}
}
}
// In case of sources changed we need to update the model
Connections {
target: application
onSourcesChanged: root.update()
}
}
address-book-app-0.2+16.04.20160323/src/imports/Settings/SettingsPage.qml 0000644 0000156 0000165 00000013204 12674534204 026252 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3 as ListItem
import Ubuntu.Contacts 0.1 as ContactsUI
Page {
id: root
objectName: "settingsPage"
property var contactListModel
title: i18n.tr("Settings")
ContactsUI.SIMList {
id: simList
}
MyselfPhoneNumbersModel {
id: myself
}
flickable: null
Flickable {
id: numberFlickable
contentHeight: childrenRect.height
anchors.fill: parent
clip: true
Column {
anchors{
left: parent.left
right: parent.right
}
height: childrenRect.height + units.gu(4)
Repeater {
anchors {
left: parent.left
right: parent.right
}
model: myself
delegate: ListItem.Subtitled {
text: i18n.tr("My phone number: %1").arg(phoneNumber)
subText: network != "" ? network : i18n.tr("SIM %1").arg(index)
}
onCountChanged: numberFlickable.contentY = 0
}
ListItem.Standard {
id: addGoogleAccountItem
property bool selected: (activeFocus && pageStack.hasKeyboard)
function activate()
{
onlineAccountsHelper.setupExec()
}
text: i18n.tr("Add Google account")
progression: true
onClicked: addGoogleAccountItem.activate()
Keys.onRightPressed: addGoogleAccountItem.activate()
Keys.onDownPressed: {
if (importFromSimItem.enabled) {
importFromSimItem.forceActiveFocus()
}
}
// selection visual feedback
//
// FIXME: Using a private property here. This uses the old list item and the only way to change the text
// color is with this property.
// We should remove it when update the app to the new ListItem.
__foregroundColor: selected ? UbuntuColors.blue : Theme.palette.normal.baseText
Rectangle {
border {
color: UbuntuColors.orange
width: units.dp(1)
}
color: "#E6E6E6"
anchors.fill: parent
visible: addGoogleAccountItem.selected
z: -1
}
}
ListItem.Standard {
id: importFromSimItem
property bool selected: (activeFocus && pageStack.hasKeyboard)
function activate()
{
pageStack.addPageToCurrentColumn(root, simCardImportPageComponent)
}
text: i18n.tr("Import from SIM")
progression: true
enabled: (simList.sims.length > 0) && (simList.present.length > 0)
onClicked: importFromSimItem.activate()
Keys.onRightPressed: importFromSimItem.activate()
Keys.onUpPressed: addGoogleAccountItem.forceActiveFocus()
// selection visual feedback
//
// FIXME: Using a private property here. This uses the old list item and the only way to change the text
// color is with this property.
// We should remove it when update the app to the new ListItem.
__foregroundColor: selected ? UbuntuColors.blue : Theme.palette.normal.baseText
Rectangle {
border {
color: UbuntuColors.orange
width: units.dp(1)
}
color: "#E6E6E6"
anchors.fill: parent
visible: importFromSimItem.selected
z: -1
}
}
SettingsDefaultSyncTarget {
id: defaultSyncTarget
onChanged: save()
}
}
}
ContactsUI.OnlineAccountsHelper {
id: onlineAccountsHelper
}
Binding {
target: pageStack
property: 'bottomEdge'
value: null
}
Component {
id: simCardImportPageComponent
ContactsUI.SIMCardImportPage {
id: importFromSimPage
objectName: "simCardImportPage"
targetModel: root.contactListModel
sims: simList.sims
onImportCompleted: pageStack.removePages(root)
}
}
Keys.onDownPressed: addGoogleAccountItem.forceActiveFocus()
Keys.onRightPressed: addGoogleAccountItem.forceActiveFocus()
Keys.onLeftPressed: pageStack.removePages(root)
Keys.onEscapePressed: pageStack.removePages(root)
onActiveChanged: {
if (active) {
root.forceActiveFocus()
defaultSyncTarget.update()
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Settings/MyselfPhoneNumbersModel.qml 0000644 0000156 0000165 00000004514 12674534204 030427 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import MeeGo.QOfono 0.2
import Ubuntu.Telephony.PhoneNumber 0.1
ListModel {
id: root
function reloadNumbers()
{
root.clear()
for (var i = 0; i < simManagerList.count; i++) {
var item = simManagerList.itemAt(i)
if (item) {
var numbers = item.subscriberNumbers
for (var n in numbers) {
root.append({'phoneNumber': PhoneUtils.format(numbers[n]),
'network': item.networkName })
}
}
}
}
property var priv: Item {
OfonoManager {
id: ofonoManager
}
Repeater {
id: simManagerList
model: ofonoManager.modems
delegate: Item {
property alias subscriberNumbers: simManager.subscriberNumbers
property alias networkName: networkRegistration.name
OfonoSimManager {
id: simManager
modemPath: modelData
onSubscriberNumbersChanged: {
console.debug("New numbers:" + subscriberNumbers)
dirtyModel.restart()
}
}
OfonoNetworkRegistration {
id: networkRegistration
modemPath: modelData
onNameChanged: {
dirtyModel.restart()
}
}
}
onCountChanged: dirtyModel.restart()
}
Timer {
id: dirtyModel
interval: 1000
repeat: false
onTriggered: root.reloadNumbers()
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Settings/CMakeLists.txt 0000644 0000156 0000165 00000000517 12674534204 025705 0 ustar pbuser pbgroup 0000000 0000000 set(CONTACT_SETTINGS_QMLS
MyselfPhoneNumbersModel.qml
SettingsDefaultSyncTarget.qml
SettingsPage.qml
)
install(FILES ${CONTACT_SETTINGS_QMLS}
DESTINATION ${ADDRESS_BOOK_APP_DIR}/imports/Settings
)
# make the files visible on qtcreator
add_custom_target(contact_settings_QmlFiles ALL SOURCES ${CONTACT_SETTINGS_QMLS})
address-book-app-0.2+16.04.20160323/src/imports/ContentHubProxy.qml 0000644 0000156 0000165 00000002716 12674534204 025176 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Content 1.3 as ContentHub
QtObject {
property QtObject pageStack: null
property list objects: [
Connections {
target: ContentHub.ContentHub
onExportRequested: {
// enter in pick mode
mainWindow.pickWithTransfer((transfer.selectionType === ContentHub.ContentTransfer.Single),
transfer)
}
onImportRequested: {
if (transfer.state === ContentHub.ContentTransfer.Charged) {
var urls = []
for(var i=0; i < transfer.items.length; i++) {
urls.push(transfer.items[i].url)
}
mainWindow.importvcards(urls)
}
}
}
]
}
address-book-app-0.2+16.04.20160323/src/imports/ABEmptyState.qml 0000644 0000156 0000165 00000003252 12674534204 024361 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2016 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
Column {
id: root
property alias text: emptyStateLabel.text
spacing: units.gu(2)
//implicitHeight: childrenRect.height
Behavior on visible {
SequentialAnimation {
PauseAnimation {
duration: !root.visible ? 500 : 0
}
PropertyAction {
target: root
property: "visible"
}
}
}
Icon {
id: emptyStateIcon
anchors.horizontalCenter: emptyStateLabel.horizontalCenter
height: units.gu(5)
width: units.gu(5)
opacity: 0.3
name: "contact"
}
Label {
id: emptyStateLabel
anchors {
left: parent.left
right: parent.right
}
height: paintedHeight
text: i18n.tr("Create a new contact by swiping up from the bottom of the screen.")
color: "#5d5d5d"
fontSize: "x-large"
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
address-book-app-0.2+16.04.20160323/src/imports/Components/ 0000755 0000156 0000165 00000000000 12674534512 023471 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/ 0000755 0000156 0000165 00000000000 12674534512 022626 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ 0000755 0000156 0000165 00000000000 12674534512 024404 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/PageWithBottomEdge.qml 0000644 0000156 0000165 00000030243 12674534204 030601 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
Example:
MainView {
objectName: "mainView"
applicationName: "com.ubuntu.developer.boiko.bottomedge"
width: units.gu(100)
height: units.gu(75)
Component {
id: pageComponent
PageWithBottomEdge {
id: mainPage
title: i18n.dtr("address-book-app", "Main Page")
Rectangle {
anchors.fill: parent
color: "white"
}
bottomEdgePageComponent: Page {
title: "Contents"
anchors.fill: parent
//anchors.topMargin: contentsPage.flickable.contentY
ListView {
anchors.fill: parent
model: 50
delegate: ListItems.Standard {
text: "One Content Item: " + index
}
}
}
bottomEdgeTitle: i18n.dtr("address-book-app", "Bottom edge action")
}
}
PageStack {
id: stack
Component.onCompleted: stack.push(pageComponent)
}
}
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
Page {
id: page
property alias bottomEdgePageComponent: edgeLoader.sourceComponent
property alias bottomEdgePageSource: edgeLoader.source
property alias bottomEdgeTitle: tipLabel.text
property bool bottomEdgeEnabled: true
property int bottomEdgeExpandThreshold: page.height * 0.2
property int bottomEdgeExposedArea: bottomEdge.state !== "expanded" ? (page.height - bottomEdge.y - bottomEdge.tipHeight) : _areaWhenExpanded
property bool reloadBottomEdgePage: true
readonly property alias bottomEdgePage: edgeLoader.item
readonly property bool isReady: ((bottomEdge.y === 0) && bottomEdgePageLoaded && edgeLoader.item.active)
readonly property bool isCollapsed: (bottomEdge.y === page.height)
readonly property bool bottomEdgePageLoaded: (edgeLoader.status == Loader.Ready)
property bool _showEdgePageWhenReady: false
property int _areaWhenExpanded: 0
signal bottomEdgeReleased()
signal bottomEdgeDismissed()
function showBottomEdgePage(source, properties)
{
edgeLoader.setSource(source, properties)
_showEdgePageWhenReady = true
}
function setBottomEdgePage(source, properties)
{
edgeLoader.setSource(source, properties)
}
function _pushPage()
{
if (edgeLoader.status === Loader.Ready) {
edgeLoader.item.active = true
page.pageStack.push(edgeLoader.item)
if (edgeLoader.item.flickable) {
edgeLoader.item.flickable.contentY = -page.header.height
edgeLoader.item.flickable.returnToBounds()
}
if (edgeLoader.item.ready)
edgeLoader.item.ready()
}
}
Component.onCompleted: {
// avoid a binding on the expanded height value
var expandedHeight = height;
_areaWhenExpanded = expandedHeight;
}
onActiveChanged: {
if (active) {
bottomEdge.state = "collapsed"
}
}
onBottomEdgePageLoadedChanged: {
if (_showEdgePageWhenReady && bottomEdgePageLoaded) {
bottomEdge.state = "expanded"
_showEdgePageWhenReady = false
}
}
Rectangle {
id: bgVisual
color: "black"
anchors.fill: page
opacity: 0.7 * ((page.height - bottomEdge.y) / page.height)
z: 1
}
UbuntuShape {
id: tip
objectName: "bottomEdgeTip"
property bool hidden: (activeFocus === false) || ((bottomEdge.y - units.gu(1)) < tip.y)
enabled: mouseArea.enabled
visible: page.bottomEdgeEnabled
anchors {
bottom: parent.bottom
horizontalCenter: bottomEdge.horizontalCenter
bottomMargin: hidden ? - height + units.gu(1) : -units.gu(1)
Behavior on bottomMargin {
SequentialAnimation {
// wait some msecs in case of the focus change again, to avoid flickering
PauseAnimation {
duration: 300
}
UbuntuNumberAnimation {
duration: UbuntuAnimation.SnapDuration
}
}
}
}
z: 1
width: tipLabel.paintedWidth + units.gu(6)
height: bottomEdge.tipHeight + units.gu(1)
backgroundColor: Theme.palette.normal.overlay
Label {
id: tipLabel
anchors {
top: parent.top
left: parent.left
right: parent.right
}
height: bottomEdge.tipHeight
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
opacity: tip.hidden ? 0.0 : 1.0
Behavior on opacity {
UbuntuNumberAnimation {
duration: UbuntuAnimation.SnapDuration
}
}
}
}
Rectangle {
id: shadow
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: units.gu(1)
z: 1
opacity: 0.0
gradient: Gradient {
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.2) }
}
}
MouseArea {
id: mouseArea
property real previousY: -1
property string dragDirection: "None"
preventStealing: true
drag {
axis: Drag.YAxis
target: bottomEdge
minimumY: bottomEdge.pageStartY
maximumY: page.height
}
enabled: edgeLoader.status == Loader.Ready
visible: page.bottomEdgeEnabled
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: bottomEdge.tipHeight
z: 1
onReleased: {
page.bottomEdgeReleased()
if ((dragDirection === "BottomToTop") &&
bottomEdge.y < (page.height - bottomEdgeExpandThreshold - bottomEdge.tipHeight)) {
bottomEdge.state = "expanded"
} else {
bottomEdge.state = "collapsed"
}
previousY = -1
dragDirection = "None"
}
onPressed: {
previousY = mouse.y
tip.forceActiveFocus()
}
onMouseYChanged: {
var yOffset = previousY - mouseY
// skip if was a small move
if (Math.abs(yOffset) <= units.gu(2)) {
return
}
previousY = mouseY
dragDirection = yOffset > 0 ? "BottomToTop" : "TopToBottom"
}
}
Rectangle {
id: bottomEdge
objectName: "bottomEdge"
readonly property int tipHeight: units.gu(3)
readonly property int pageStartY: 0
z: 1
color: Theme.palette.normal.background
clip: true
anchors {
left: parent.left
right: parent.right
}
height: page.height
y: height
visible: !page.isCollapsed
state: "collapsed"
states: [
State {
name: "collapsed"
PropertyChanges {
target: bottomEdge
y: bottomEdge.height
}
},
State {
name: "expanded"
PropertyChanges {
target: bottomEdge
y: bottomEdge.pageStartY
}
},
State {
name: "floating"
when: mouseArea.drag.active
PropertyChanges {
target: shadow
opacity: 1.0
}
}
]
transitions: [
Transition {
to: "expanded"
SequentialAnimation {
alwaysRunToEnd: true
SmoothedAnimation {
target: bottomEdge
property: "y"
duration: UbuntuAnimation.FastDuration
easing.type: Easing.Linear
}
SmoothedAnimation {
target: edgeLoader
property: "anchors.topMargin"
to: - units.gu(4)
duration: UbuntuAnimation.FastDuration
easing.type: Easing.Linear
}
SmoothedAnimation {
target: edgeLoader
property: "anchors.topMargin"
to: 0
duration: UbuntuAnimation.FastDuration
easing: UbuntuAnimation.StandardEasing
}
ScriptAction {
script: page._pushPage()
}
}
},
Transition {
from: "expanded"
to: "collapsed"
SequentialAnimation {
alwaysRunToEnd: true
ScriptAction {
script: {
Qt.inputMethod.hide()
edgeLoader.item.parent = edgeLoader
edgeLoader.item.anchors.fill = edgeLoader
edgeLoader.item.active = false
}
}
SmoothedAnimation {
target: bottomEdge
property: "y"
duration: UbuntuAnimation.SlowDuration
}
ScriptAction {
script: {
// destroy current bottom page
if (page.reloadBottomEdgePage) {
edgeLoader.active = false
// tip will receive focus on page active true
} else {
tip.forceActiveFocus()
}
// notify
page.bottomEdgeDismissed()
edgeLoader.active = true
}
}
}
},
Transition {
from: "floating"
to: "collapsed"
SmoothedAnimation {
target: bottomEdge
property: "y"
duration: UbuntuAnimation.FastDuration
}
}
]
Loader {
id: edgeLoader
asynchronous: true
anchors.fill: parent
//WORKAROUND: The SDK move the page contents down to allocate space for the header we need to avoid that during the page dragging
Binding {
target: edgeLoader.status === Loader.Ready ? edgeLoader : null
property: "anchors.topMargin"
value: edgeLoader.item && edgeLoader.item.flickable ? edgeLoader.item.flickable.contentY : 0
when: !page.isReady
}
onLoaded: {
tip.forceActiveFocus()
if (page.isReady && edgeLoader.item.active !== true) {
page._pushPage()
}
}
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/MultipleSelectionListView.qml 0000644 0000156 0000165 00000012612 12674534204 032247 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2013 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3 as Popups
/*!
\qmltype ContactSimpleListView
\inqmlmodule Ubuntu.Contacts 0.1
\ingroup ubuntu
\brief The MultipleSelectionListView provides a ListView with support to multiple selection
The MultipleSelectionListViewprovides a ListView with support to multiple selection which can be used by any
application.
Example:
\qml
import Ubuntu.Contacts 0.1
MultipleSelectionListView {
id: view
anchors.fill: paret
model: 100
delegate: Rectangle {
width: parent.width
height: 100
color: view.selectedItems.indexOf(index) == -1 ? "white" : "blue"
MouseArea {
anchors.fill: parent
onClicked: {
if (view.isInSelectionModel) {
view.selectItem(index)
}
}
onPressAndHold: view.startSelection()
}
}
onSelectionDone: console.debug("Selected items:" + view.selectedItems)
}
\endqml
*/
ListView {
id: listView
/*!
\qmlproperty model selectedItems
This property holds the list of selected items
*/
readonly property alias selectedItems: visualModel.selectedItems
/*!
\qmlproperty bool multipleSelection
This property holds if the selection will accept multiple items or single items
*/
property bool multipleSelection: true
/*!
\qmlproperty model listModel
This property holds the model providing data for the list.
*/
property alias listModel: visualModel.model
/*!
\qmlproperty Component listDelegate
The delegate provides a template defining each item instantiated by the view.
*/
property alias listDelegate: visualModel.delegate
/*!
\qmlproperty bool isInSelectionMode
This property holds a list with the index of selected items
*/
readonly property bool isInSelectionMode: state === "selection"
/*!
This handler is called when the selection mode is finished without be canceled
*/
signal selectionDone(var items)
/*!
This handler is called when the selection mode is canceled
*/
signal selectionCanceled()
/*!
Start the selection mode on the list view.
*/
function startSelection()
{
state = "selection"
}
/*!
Check if the item is selected
Returns true if the item was marked as selected or false if the item is unselected
*/
function isSelected(item)
{
if (item && item.VisualDataModel) {
return (item.VisualDataModel.inSelected === true)
} else {
return false
}
}
/*!
Mark the item as selected
Returns true if the item was marked as selected or false if the item is already selected
*/
function selectItem(item)
{
if (item.VisualDataModel.inSelected) {
return false
} else {
if (!multipleSelection) {
clearSelection()
}
item.VisualDataModel.inSelected = true
return true
}
}
/*!
Remove the index from the selected list
*/
function deselectItem(item)
{
var result = false
if (item.VisualDataModel.inSelected) {
item.VisualDataModel.inSelected = false
result = true
}
return result
}
/*!
Finish the selection mode with sucess
*/
function endSelection()
{
selectionDone(listView.selectedItems)
clearSelection()
state = ""
}
/*!
Cancel the selection
*/
function cancelSelection()
{
selectionCanceled()
clearSelection()
state = ""
}
/*!
Remove any selected item from the selection list
*/
function clearSelection()
{
if (selectedItems.count > 0) {
selectedItems.remove(0, selectedItems.count)
}
}
/*!
Select all items in the list
*/
function selectAll()
{
if (multipleSelection) {
visualModel.items.addGroups(0, visualModel.items.count, ["selected"] )
}
}
model: visualModel
MultipleSelectionVisualModel {
id: visualModel
}
Component.onCompleted: {
// FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
// for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
var scaleFactor = units.gridUnit / 8;
maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
flickDeceleration = flickDeceleration * scaleFactor;
}
}
././@LongLink 0000000 0000000 0000000 00000000150 00000000000 011211 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactDetailOnlineAccountTypeModel.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactDetailOnlineAccountTypeModel.0000644 0000156 0000165 00000005216 12674534204 033432 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0 as QtContacts
ListModel {
id: typeModel
signal loaded()
function getTypeIndex(detail) {
var protocol = detail.value(2)
switch(protocol) {
case 1:
return 0;
case 2:
return 1;
case 4:
return 2;
case 5:
return 3;
case 7:
return 4;
case 8:
return 5;
default:
return 4; //default value is "Skype"
}
}
function updateDetail(detail, index) {
var modelData = get(index)
if (!modelData) {
return false
}
if (detail.value(2) != modelData.value) {
detail.setValue(2, modelData.value)
return true
}
return false
}
Component.onCompleted: {
//append({"value": 0, "label": i18n.dtr("address-book-app", "Other"), icon: "artwork:/protocol-other.svg"})
/*0*/ // TRANSLATORS: This refers to the AIM chat network http://en.wikipedia.org/wiki/AOL_Instant_Messenger
append({"value": 1, "label": i18n.dtr("address-book-app", "Aim"), "icon": "artwork:/protocol-aim.svg"})
/*1*/ append({"value": 2, "label": i18n.dtr("address-book-app", "ICQ"), "icon": "artwork:/protocol-icq.svg"})
//append({"value": 3, "label": i18n.dtr("address-book-app", "IRC"), icon: "artwork:/protocol-irc.svg"})
/*2*/ append({"value": 4, "label": i18n.dtr("address-book-app", "Jabber"), "icon": "artwork:/protocol-jabber.svg"})
/*3*/ append({"value": 5, "label": i18n.dtr("address-book-app", "MSN"), "icon": "artwork:/protocol-msn.svg"})
// append({"value": 6, "label": i18n.dtr("address-book-app", "QQ"), icon: "artwork:/protocol-qq.svg"})
/*4*/ append({"value": 7, "label": i18n.dtr("address-book-app", "Skype"), "icon": "artwork:/protocol-skype.svg"})
/*5*/ append({"value": 8, "label": i18n.dtr("address-book-app", "Yahoo"), "icon": "artwork:/protocol-yahoo.svg"})
loaded()
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/MostCalledModel.qml 0000644 0000156 0000165 00000005603 12674534204 030131 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.History 0.1
import Ubuntu.Contacts 0.1 as ContactUI
VisualDataModel {
id: root
property var contactModel: null
property int currentIndex: -1
property alias callAverage: mostCalledModel.callAverage
signal contactClicked(int index, QtObject contact)
signal addContactClicked(string label)
signal loaded()
property var baseModel: HistoryEventModel {
id: historyEventModel
type: HistoryThreadModel.EventTypeVoice
sort: HistorySort {
sortField: "timestamp"
sortOrder: HistorySort.DescendingOrder
}
filter: HistoryFilter {
filterProperty: "senderId"
filterValue: "self"
matchFlags: HistoryFilter.MatchCaseSensitive
}
onCanFetchMoreChanged: {
if (count === 0) {
mostCalledModel.update()
}
}
}
model: ContactUI.MostCalledContactsModel {
id: mostCalledModel
startInterval: new Date((new Date().getTime() - 2592000000)) // one month ago
maxCount: 5
onLoaded: root.loaded()
sourceModel: historyEventModel
}
delegate: ContactDelegate {
id: contactDelegate
readonly property alias contact: contactFetch.contact
property var contents
defaultAvatarUrl: "image://theme/contacts"
width: parent ? parent.width : 0
isCurrentItem: root.currentIndex === index
locked: true
// collapse the item before remove it, to avoid crash
ListView.onRemove: SequentialAnimation {
ScriptAction {
script: {
if (contactDelegate.state !== "") {
historyModel.currentIndex = -1
}
}
}
}
onClicked: {
if (contact) {
root.contactClicked(index, contact)
} else {
root.addContactClicked(name.text)
}
}
// delegate does not support more than one child
contents: ContactFetch {
id: contactFetch
model: contactsModel
}
Component.onCompleted: contactFetch.fetchContact(contactId)
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactSimpleListView.qml 0000644 0000156 0000165 00000025476 12674534204 031367 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3 as ListItem
import "ContactList.js" as Sections
/*!
\qmltype ContactSimpleListView
\inqmlmodule Ubuntu.Contacts 0.1
\ingroup ubuntu
\brief The ContactSimpleListView provides a simple contact list view
The ContactSimpleListView provide a easy way to show the contact list view
with all default visuals defined by Ubuntu system.
Example:
\qml
import Ubuntu.Contacts 0.1
ContactSimpleListView {
anchors.fill: parent
onContactClicked: console.debug("Contact ID:" + contactId)
}
\endqml
*/
MultipleSelectionListView {
id: contactListView
/*!
\qmlproperty bool showAvatar
This property holds if the contact avatar will appear on the list or not.
By default this is set to true.
*/
property bool showAvatar: true
/*!
\qmlproperty list sortOrders
This property holds a list of sort orders used by the contacts model.
\sa SortOrder
*/
property list sortOrders : [
SortOrder {
detail: ContactDetail.Tag
field: Tag.Tag
direction: Qt.AscendingOrder
blankPolicy: SortOrder.BlanksLast
caseSensitivity: Qt.CaseInsensitive
},
// empty tags will be sorted by display Label
SortOrder {
detail: ContactDetail.DisplayLabel
field: DisplayLabel.Label
direction: Qt.AscendingOrder
blankPolicy: SortOrder.BlanksLast
caseSensitivity: Qt.CaseInsensitive
}
]
/*!
\qmlproperty FetchHint fetchHint
This property holds the fetch hint instance used by the contact model.
\sa FetchHint
*/
property var fetchHint : FetchHint {
detailTypesHint: {
var hints = [ ContactDetail.Tag, // sections
ContactDetail.DisplayLabel // label
]
if (contactListView.showAvatar) {
hints.push(ContactDetail.Avatar)
}
return hints
}
}
/*!
\qmlproperty bool multiSelectionEnabled
This property holds if the multi selection mode is enabled or not
By default this is set to false
*/
property bool multiSelectionEnabled: false
/*!
\qmlproperty string defaultAvatarImage
This property holds the default image url to be used when the current contact does
not contains a photo
*/
property string defaultAvatarImageUrl: "image://theme/contact"
/*!
\qmlproperty bool loading
This property holds when the model still loading new contacts
*/
readonly property bool loading: busyIndicator.busy
/*!
\qmlproperty bool showSections
This property holds if the listview will show or not the section headers
By default this is set to true
*/
property bool showSections: true
/*!
\qmlproperty string manager
This property holds the manager uri of the contact backend engine.
By default this is set to "galera"
*/
property string manager: (typeof(QTCONTACTS_MANAGER_OVERRIDE) !== "undefined") && (QTCONTACTS_MANAGER_OVERRIDE != "") ? QTCONTACTS_MANAGER_OVERRIDE : "galera"
/*!
\qmlproperty Action leftSideAction
This property holds the available actions when swipe the contact item from left to right
*/
property Action leftSideAction
/*!
\qmlproperty list rightSideActions
This property holds the available actions when swipe the contact item from right to left
*/
property list rightSideActions
/*!
\qmlproperty highlightSelected
This property holds if the current contact should be highlighted or not
*/
property bool highlightSelected: false
/* internal */
property var _currentSwipedItem: null
/*!
This handler is called when any error occurs in the contact model
*/
signal error(string message)
/*!
This handler is called when any contact in the list receives a click
*/
signal contactClicked(QtObject contact)
/*!
This handler is called when the contact delegate disapear (height === 0) caused by the function call makeDisappear
*/
signal contactDisappeared(QtObject contact)
/*!
Retrieve the contact index inside of the list based on contact id or contact name if the id is empty
*/
function getIndex(contact)
{
var contacts = listModel.contacts
var contactId = null
var firstName
var middleName
var lastName
if (contact.contactId !== "qtcontacts:::") {
contactId = contact.contactId
} else {
firstName = contact.name.firstName
middleName = contact.name.middleName
lastName = contact.name.lastName
}
for (var i = 0, count = contacts.length; i < count; i++) {
var c = contacts[i]
if (contactId && (c.contactId === contactId)) {
return i
} else if ((c.name.firstName === firstName) &&
(c.name.middleName === middleName) &&
(c.name.lastName === lastName)) {
return i
}
}
return -1
}
/*!
Scroll the list to requested contact if the contact exists in the list
*/
function positionViewAtContact(contact)
{
currentIndex = getIndex(contact)
positionViewAtIndex(currentIndex, ListView.Center)
}
/*!
private
Fetch contact and emit contact clicked signal
*/
function _fetchContact(index, contact)
{
if (contact) {
contactFetch.fetchContact(contact.contactId)
}
}
function _updateSwipeState(item)
{
if (item.swipping) {
return
}
if (item.swipeState !== "Normal") {
if (contactListView._currentSwipedItem !== item) {
if (contactListView._currentSwipedItem) {
contactListView._currentSwipedItem.resetSwipe()
}
contactListView._currentSwipedItem = item
}
} else if (item.swipeState !== "Normal" && contactListView._currentSwipedItem === item) {
contactListView._currentSwipedItem = null
}
}
highlightFollowsCurrentItem: true
section {
property: showSections ? "contact.tag.tag" : ""
criteria: ViewSection.FirstCharacter
labelPositioning: ViewSection.InlineLabels
delegate: SectionDelegate {
anchors {
left: parent.left
right: parent.right
margins: units.gu(2)
}
text: section != "" ? section : "#"
}
}
onCountChanged: {
busyIndicator.ping()
dirtyModel.restart()
}
listDelegate: ContactDelegate {
id: contactDelegate
property var removalAnimation
function remove()
{
removalAnimation.start()
}
flicking: contactListView.flicking
width: parent.width
selected: (contactListView.multiSelectionEnabled && contactListView.isSelected(contactDelegate))
|| (!contactListView.isInSelectionMode && contactListView.highlightSelected && (contactListView.currentIndex == index))
selectionMode: contactListView.isInSelectionMode
defaultAvatarUrl: contactListView.defaultAvatarImageUrl
isCurrentItem: ListView.isCurrentItem
// actions
leftSideAction: contactListView.leftSideAction
rightSideActions: contactListView.rightSideActions
// used by swipe to delete
removalAnimation: SequentialAnimation {
alwaysRunToEnd: true
PropertyAction {
target: contactDelegate
property: "ListView.delayRemove"
value: true
}
UbuntuNumberAnimation {
target: contactDelegate
property: "height"
to: 1
}
PropertyAction {
target: contactDelegate
property: "ListView.delayRemove"
value: false
}
ScriptAction {
script: contactListView.listModel.removeContact(contact.contactId)
}
}
onClicked: {
if (contactListView.isInSelectionMode) {
if (!contactListView.selectItem(contactDelegate)) {
contactListView.deselectItem(contactDelegate)
}
} else {
contactListView.currentIndex = index
contactListView._fetchContact(index, contact)
}
}
onPressAndHold: {
if (contactListView.multiSelectionEnabled) {
contactListView.currentIndex = -1
contactListView.startSelection()
contactListView.selectItem(contactDelegate)
}
}
onSwippingChanged: contactListView._updateSwipeState(contactDelegate)
onSwipeStateChanged: contactListView._updateSwipeState(contactDelegate)
}
ContactFetch {
id: contactFetch
model: root.listModel
onContactFetched: contactListView.contactClicked(contact)
}
// This is a workaround to make sure the spinner will disappear if the model is empty
// FIXME: implement a model property to say if the model still busy or not
Item {
id: busyIndicator
property bool busy: timer.running || priv.currentOperation !== -1
function ping()
{
timer.restart()
}
visible: busy
anchors.fill: parent
Timer {
id: timer
interval: 3000
running: true
repeat: false
}
}
Timer {
id: dirtyModel
interval: 1000
running: false
repeat: false
onTriggered: Sections.initSectionData(contactListView)
}
QtObject {
id: priv
property int currentOperation: -1
property int pendingTargetIndex: 0
property variant pendingTargetMode: null
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactListButtonDelegate.qml 0000644 0000156 0000165 00000004251 12674534204 032175 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
Item {
id: root
property string iconSource
property alias labelText: name.text
property bool expandIcon: false
property bool showContents: true
signal clicked()
anchors {
left: parent.left
right: parent.right
}
height: visible ? units.gu(8) : 0
Rectangle {
anchors.fill: parent
color: Theme.palette.selected.background
opacity: addNewContactButtonArea.pressed ? 1.0 : 0.0
}
UbuntuShape {
id: uShape
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
margins: units.gu(1)
}
width: height
radius: "medium"
backgroundColor: Theme.palette.normal.overlay
source: Image {
source: root.expandIcon ? root.iconSource : ""
}
Image {
anchors.centerIn: parent
source: root.expandIcon ? "" : root.iconSource
visible: !root.expandIcon
width: units.gu(2)
height: units.gu(2)
}
visible: root.showContents
}
Label {
id: name
anchors {
left: uShape.right
leftMargin: units.gu(2)
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: units.gu(2)
}
elide: Text.ElideRight
visible: root.showContents
}
MouseArea {
id: addNewContactButtonArea
anchors.fill: parent
onClicked: root.clicked()
visible: root.showContents
}
}
././@LongLink 0000000 0000000 0000000 00000000153 00000000000 011214 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactDetailPickerPhoneNumberDelegate.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactDetailPickerPhoneNumberDelega0000644 0000156 0000165 00000007712 12674534204 033455 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import QtContacts 5.0 as QtContacts
Item {
id: root
signal detailClicked(QtObject detail, string action)
signal addDetailClicked(int detailType)
function containsPointer(item, point)
{
return ((point.x >= item.x) && (point.x <= item.x + item.width) &&
(point.y >= item.y) && (point.y <= item.y + item.height));
}
function updateDetails(contact)
{
if (contact) {
phoneNumberEntries.model = contact.details(QtContacts.ContactDetail.PhoneNumber)
}
}
height: detailItems.height + units.gu(2)
Column {
id: detailItems
anchors {
top: parent.top
topMargin: units.gu(1)
left: parent.left
right: parent.right
}
height: childrenRect.height
width: parent.width
Item {
id: noNumberMessage
anchors {
left: parent.left
right: parent.right
}
height: visible ? units.gu(8) : 0
Rectangle {
anchors {
fill: parent
leftMargin: units.gu(-2)
rightMargin: units.gu(-2)
}
color: Theme.palette.selected.background
opacity: noNumberMessageArea.pressed ? 1.0 : 0.0
Behavior on opacity {
UbuntuNumberAnimation {}
}
}
Label {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: units.gu(8)
right: parent.right
}
text: i18n.dtr("address-book-app", "Add number...")
}
visible: phoneNumberEntries.count == 0
MouseArea {
id: noNumberMessageArea
anchors.fill: parent
enabled: parent.visible
onClicked: root.addDetailClicked(QtContacts.ContactDetail.PhoneNumber)
}
}
Repeater {
id: phoneNumberEntries
SubtitledWithColors {
anchors {
left: parent.left
leftMargin: units.gu(6)
right: parent.right
}
text: modelData.number
subText: phoneTypeModel.get(phoneTypeModel.getTypeIndex(modelData)).label
onClicked: root.detailClicked(modelData, "call")
MouseArea {
anchors {
top: parent.top
bottom: parent.bottom
right: parent.right
rightMargin: units.gu(1)
}
width: units.gu(4)
z: 100
onClicked: root.detailClicked(modelData, "message")
Icon {
id: messageIcon
name: "message"
height: units.gu(3)
width: height
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
ContactDetailPhoneNumberTypeModel {
id: phoneTypeModel
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactDelegate.qml 0000644 0000156 0000165 00000004645 12674534204 030154 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3 as ListItem
import "Contacts.js" as ContactsJS
ListItemWithActions {
id: root
property bool showAvatar: true
property bool isCurrentItem: false
property string defaultAvatarUrl: ""
property bool flicking: false
readonly property string displayLabel: contact ? ContactsJS.formatToDisplay(contact, "") : ""
signal clicked(int index, QtObject contact)
signal pressAndHold(int index, QtObject contact)
implicitHeight: defaultHeight
width: parent ? parent.width : 0
onItemClicked: root.clicked(index, contact)
onItemPressAndHold: root.pressAndHold(index, contact)
onFlickingChanged: {
if (flicking) {
resetSwipe()
}
}
Item {
id: delegate
anchors {
left: parent.left
right: parent.right
}
height: units.gu(6)
ContactAvatar {
id: avatar
contactElement: contact
fallbackDisplayName: root.displayLabel
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
width: root.showAvatar ? height : 0
visible: width > 0
}
Label {
id: name
objectName: "nameLabel"
anchors {
left: avatar.right
leftMargin: units.gu(2)
verticalCenter: parent.verticalCenter
right: parent.right
}
color: selected ? UbuntuColors.blue : UbuntuColors.darkGrey
text: root.displayLabel != "" ? root.displayLabel : i18n.dtr("address-book-app", "No name")
elide: Text.ElideRight
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/SubtitledWithColors.qml 0000644 0000156 0000165 00000003324 12674534204 031074 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright 2012 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
MouseArea {
id: subtitledListItem
property alias text: label.text
property alias subText: subLabel.text
property alias textColor: label.color
property alias subTextColor: subLabel.color
height: Math.max(middleVisuals.height, units.gu(6))
Item {
id: middleVisuals
anchors {
left: parent.left
leftMargin: units.gu(2)
right: parent.right
verticalCenter: parent.verticalCenter
}
height: childrenRect.height + label.anchors.topMargin + subLabel.anchors.bottomMargin
Label {
id: label
anchors {
top: parent.top
left: parent.left
right: parent.right
}
}
Label {
id: subLabel
anchors {
left: parent.left
right: parent.right
top: label.bottom
}
fontSize: "small"
wrapMode: Text.Wrap
maximumLineCount: 5
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/plugin.h 0000644 0000156 0000165 00000002043 12674534204 026050 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#ifndef _UBUNTU_CONTACTS_PLUGIN_H_
#define _UBUNTU_CONTACTS_PLUGIN_H_
#include
#include
class UbuntuContactsPlugin : public QQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
public:
void initializeEngine(QQmlEngine *engine, const char *uri);
void registerTypes(const char *uri);
};
#endif //_UBUNTU_CONTACTS_PLUGINS_H_
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/SIMList.qml 0000644 0000156 0000165 00000004675 12674534204 026415 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import MeeGo.QOfono 0.2
import GSettings 1.0
Item {
id: root
property var sims: []
property var present: []
function filterPresentSims()
{
var presentSims = []
sims.forEach(function (sim) {
if (sim.present) {
presentSims.push(sim)
}
});
root.present = presentSims
}
function createQML (modems)
{
if (!phoneSettings.simNames) {
return
}
var component = Qt.createComponent(Qt.resolvedUrl("Ofono.qml"));
sims.forEach(function (sim) {
sim.destroy();
});
var newSims = []
var presentSims = []
modems.forEach(function (path, index) {
var sim = component.createObject(root, {
path: path,
name: phoneSettings.simNames[path] ? phoneSettings.simNames[path] :
"SIM " + (index + 1)
});
if (sim === null) {
console.warn('Failed to create Sim qml:', component.errorString());
} else {
newSims.push(sim)
if (sim.present) {
presentSims.push(sim)
}
sim.onPresentChanged.connect(filterPresentSims)
}
});
root.sims = newSims
root.present = presentSims
}
GSettings {
id: phoneSettings
schema.id: "com.ubuntu.phone"
onChanged: {
if (key === "simNames") {
root.createQML(ofonoManager.modems.slice(0).sort())
}
}
onSchemaChanged: root.createQML(ofonoManager.modems.slice(0).sort())
}
OfonoManager {
id: ofonoManager
onModemsChanged: root.createQML(modems.slice(0).sort())
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ListItemWithActionsCheckBox.qml 0000644 0000156 0000165 00000001526 12674534204 032437 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
CheckBox {
checked: root.selected
width: implicitWidth
// disable item mouse area to avoid conflicts with parent mouse area
__mouseArea.enabled: false
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ListItemWithActions.qml 0000644 0000156 0000165 00000033562 12674534204 031035 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
Item {
id: root
property Action leftSideAction: null
property list rightSideActions
property double defaultHeight: units.gu(8)
property bool locked: false
property Action activeAction: null
property var activeItem: null
property bool triggerActionOnMouseRelease: false
property color color: Theme.palette.normal.background
property color selectedColor: "#F7F7F7"
property bool selected: false
property bool selectionMode: false
property alias internalAnchors: mainContents.anchors
property alias animated: behaviorOnX.enabled
default property alias contents: mainContents.children
readonly property double actionWidth: units.gu(4)
readonly property double leftActionWidth: units.gu(10)
readonly property double actionThreshold: actionWidth * 0.4
readonly property double threshold: 0.4
readonly property string swipeState: main.x == 0 ? "Normal" : main.x > 0 ? "LeftToRight" : "RightToLeft"
readonly property alias swipping: mainItemMoving.running
readonly property bool _showActions: mouseArea.pressed || swipeState != "Normal" || swipping
/* internal */
property var _visibleRightSideActions: filterVisibleActions(rightSideActions)
signal itemClicked(var mouse)
signal itemPressAndHold(var mouse)
function returnToBoundsRTL(direction)
{
var actionFullWidth = actionWidth + units.gu(2)
// go back to normal state if swipping reverse
if (direction === "LTR") {
updatePosition(0)
return
} else if (!triggerActionOnMouseRelease) {
updatePosition(-rightActionsView.width + units.gu(2))
return
}
var xOffset = Math.abs(main.x)
var index = Math.min(Math.floor(xOffset / actionFullWidth), _visibleRightSideActions.length)
var newX = 0
if (index === _visibleRightSideActions.length) {
newX = -(rightActionsView.width - units.gu(2))
} else if (index >= 1) {
newX = -(actionFullWidth * index)
}
updatePosition(newX)
}
function returnToBoundsLTR(direction)
{
var finalX = leftActionWidth
if ((direction === "RTL") || (main.x <= (finalX * root.threshold)))
finalX = 0
updatePosition(finalX)
}
function returnToBounds(direction)
{
if (main.x < 0) {
returnToBoundsRTL(direction)
} else if (main.x > 0) {
returnToBoundsLTR(direction)
} else {
updatePosition(0)
}
}
function contains(item, point, marginX)
{
var itemStartX = item.x - marginX
var itemEndX = item.x + item.width + marginX
return (point.x >= itemStartX) && (point.x <= itemEndX) &&
(point.y >= item.y) && (point.y <= (item.y + item.height));
}
function getActionAt(point)
{
if (contains(leftActionView, point, 0)) {
return leftSideAction
} else if (contains(rightActionsView, point, 0)) {
var newPoint = root.mapToItem(rightActionsView, point.x, point.y)
for (var i = 0; i < rightActionsRepeater.count; i++) {
var child = rightActionsRepeater.itemAt(i)
if (contains(child, newPoint, units.gu(1))) {
return i
}
}
}
return -1
}
function updateActiveAction()
{
if (triggerActionOnMouseRelease &&
(main.x <= -(root.actionWidth + units.gu(2))) &&
(main.x > -(rightActionsView.width - units.gu(2)))) {
var actionFullWidth = actionWidth + units.gu(2)
var xOffset = Math.abs(main.x)
var index = Math.min(Math.floor(xOffset / actionFullWidth), _visibleRightSideActions.length)
index = index - 1
if (index > -1) {
root.activeItem = rightActionsRepeater.itemAt(index)
root.activeAction = root._visibleRightSideActions[index]
}
} else {
root.activeAction = null
}
}
function resetSwipe()
{
updatePosition(0)
}
function filterVisibleActions(actions)
{
var visibleActions = []
for(var i = 0; i < actions.length; i++) {
var action = actions[i]
if (action.visible) {
visibleActions.push(action)
}
}
return visibleActions
}
function updatePosition(pos)
{
if (!root.triggerActionOnMouseRelease && (pos !== 0)) {
mouseArea.state = pos > 0 ? "RightToLeft" : "LeftToRight"
} else {
mouseArea.state = ""
}
main.x = pos
}
states: [
State {
name: "select"
when: selectionMode
PropertyChanges {
target: selectionIcon
source: Qt.resolvedUrl("ListItemWithActionsCheckBox.qml")
anchors.leftMargin: units.gu(2)
}
PropertyChanges {
target: root
locked: true
}
PropertyChanges {
target: main
x: 0
}
}
]
height: defaultHeight
clip: height !== defaultHeight
Rectangle {
id: leftActionView
anchors {
top: parent.top
bottom: parent.bottom
right: main.left
}
width: root.leftActionWidth + actionThreshold
visible: leftSideAction
color: UbuntuColors.red
Icon {
anchors {
centerIn: parent
horizontalCenterOffset: actionThreshold / 2
}
name: leftSideAction && _showActions ? leftSideAction.iconName : ""
color: Theme.palette.selected.field
height: units.gu(3)
width: units.gu(3)
}
}
Rectangle {
id: rightActionsView
anchors {
top: main.top
left: main.right
bottom: main.bottom
}
visible: _visibleRightSideActions.length > 0
width: rightActionsRepeater.count > 0 ? rightActionsRepeater.count * (root.actionWidth + units.gu(2)) + root.actionThreshold + units.gu(2) : 0
color: "white"
Row {
anchors{
top: parent.top
left: parent.left
leftMargin: units.gu(2)
right: parent.right
rightMargin: units.gu(2)
bottom: parent.bottom
}
spacing: units.gu(2)
Repeater {
id: rightActionsRepeater
model: _showActions ? _visibleRightSideActions : []
Item {
property alias image: img
height: rightActionsView.height
width: root.actionWidth
Icon {
id: img
anchors.centerIn: parent
width: units.gu(3)
height: units.gu(3)
name: modelData.iconName
color: root.activeAction === modelData ? UbuntuColors.orange : UbuntuColors.lightGrey
}
}
}
}
}
Rectangle {
id: main
objectName: "mainItem"
anchors {
top: parent.top
bottom: parent.bottom
}
width: parent.width
border {
color: UbuntuColors.orange
width: root.selected ? units.dp(1) : 0
}
color: root.selected ? root.selectedColor : root.color
Loader {
id: selectionIcon
anchors {
left: main.left
verticalCenter: main.verticalCenter
}
width: (status === Loader.Ready) ? item.implicitWidth : 0
visible: (status === Loader.Ready) && (item.width === item.implicitWidth)
Behavior on width {
NumberAnimation {
duration: UbuntuAnimation.SnapDuration
}
}
}
Item {
id: mainContents
anchors {
left: selectionIcon.right
leftMargin: units.gu(2)
top: parent.top
topMargin: units.gu(1)
right: parent.right
rightMargin: units.gu(2)
bottom: parent.bottom
bottomMargin: units.gu(1)
}
}
Behavior on x {
id: behaviorOnX
UbuntuNumberAnimation {
id: mainItemMoving
easing.type: Easing.OutElastic
duration: UbuntuAnimation.SlowDuration
}
}
Behavior on color {
ColorAnimation {}
}
}
SequentialAnimation {
id: clickAnimation
running: false
alwaysRunToEnd: true
PropertyAnimation {
target: main
property: "color"
to: root.selectedColor
}
PropertyAction {
target: main
property: "color"
value: root.selected ? root.selectedColor : root.color
}
}
SequentialAnimation {
id: triggerAction
property var currentItem: root.activeItem ? root.activeItem.image : null
running: false
ParallelAnimation {
UbuntuNumberAnimation {
target: triggerAction.currentItem
property: "opacity"
from: 1.0
to: 0.0
duration: UbuntuAnimation.SlowDuration
easing {type: Easing.InOutBack; }
}
UbuntuNumberAnimation {
target: triggerAction.currentItem
properties: "width, height"
from: units.gu(3)
to: root.actionWidth
duration: UbuntuAnimation.SlowDuration
easing {type: Easing.InOutBack; }
}
}
PropertyAction {
target: triggerAction.currentItem
properties: "width, height"
value: units.gu(3)
}
PropertyAction {
target: triggerAction.currentItem
properties: "opacity"
value: 1.0
}
ScriptAction {
script: {
root.activeAction.triggered(root)
mouseArea.state = ""
}
}
PauseAnimation {
duration: 500
}
UbuntuNumberAnimation {
target: main
property: "x"
to: 0
}
}
MouseArea {
id: mouseArea
property bool locked: root.locked || ((root.leftSideAction === null) && (root._visibleRightSideActions.count === 0))
property bool manual: false
property string direction: "None"
property real lastX: -1
anchors.fill: parent
drag {
target: locked ? null : main
axis: Drag.XAxis
minimumX: rightActionsView.visible ? -(rightActionsView.width) : 0
maximumX: leftActionView.visible ? leftActionView.width : 0
threshold: root.actionThreshold
}
states: [
State {
name: "LeftToRight"
PropertyChanges {
target: mouseArea
drag.maximumX: 0
}
},
State {
name: "RightToLeft"
PropertyChanges {
target: mouseArea
drag.minimumX: 0
}
}
]
onMouseXChanged: {
var offset = (lastX - mouseX)
if (Math.abs(offset) <= root.actionThreshold) {
return
}
lastX = mouseX
direction = offset > 0 ? "RTL" : "LTR";
}
onPressed: {
lastX = mouse.x
}
onReleased: {
if (root.triggerActionOnMouseRelease && root.activeAction) {
triggerAction.start()
} else {
root.returnToBounds(direction)
root.activeAction = null
}
lastX = -1
direction = "None"
}
onClicked: {
if (main.x === 0) {
root.itemClicked(mouse)
if (!root.selected)
clickAnimation.start()
} else if (main.x > 0) {
var action = getActionAt(Qt.point(mouse.x, mouse.y))
if (action && action !== -1) {
action.triggered(root)
}
} else {
var actionIndex = getActionAt(Qt.point(mouse.x, mouse.y))
if (actionIndex !== -1) {
root.activeItem = rightActionsRepeater.itemAt(actionIndex)
root.activeAction = root._visibleRightSideActions[actionIndex]
triggerAction.start()
return
}
}
root.resetSwipe()
}
onPositionChanged: {
if (mouseArea.pressed) {
updateActiveAction()
}
}
onPressAndHold: {
if (main.x === 0) {
root.itemPressAndHold(mouse)
}
}
z: -1
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactList.js 0000644 0000156 0000165 00000003236 12674534204 027173 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright 2012-2015 Canonical Ltd.
*
* This file is part of address-book-app.
*
* phone-app is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* phone-app is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
var sectionData = [];
var _sections = [];
function sectionValueForContact(contact) {
if (contact) {
var section = contact.tag.tag.charAt(0).toUpperCase()
return (section === "" ? "#" : section)
} else {
return null
}
}
function initSectionData(list) {
if (!list || !list.listModel) {
return;
}
sectionData = [];
_sections = [];
var current = "";
var item;
var contacts = list.listModel.contacts;
for (var i = 0, count = contacts.length; i < count; i++) {
item = sectionValueForContact(contacts[i])
if (item !== current) {
current = item;
_sections.push(current);
sectionData.push({ index: i, header: current});
}
}
}
function getIndexFor(sectionName) {
var index = _sections.indexOf(sectionName)
if (index != -1) {
var val = sectionData[_sections.indexOf(sectionName)].index;
return val === 0 || val > 0 ? val : -1;
} else {
return -1
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/mostcalledproxymodel.cpp 0000644 0000156 0000165 00000023232 12674534204 031362 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "mostcalledproxymodel.h"
#include
#include
#include
#include
#include
using namespace QtContacts;
bool mostCalledContactsModelDataGreaterThan(const MostCalledContactsModelData &d1, const MostCalledContactsModelData &d2)
{
return d1.callCount > d2.callCount;
}
MostCalledContactsModel::MostCalledContactsModel(QObject *parent)
: QAbstractListModel(parent),
m_sourceModel(0),
m_currentFetch(0),
m_manager(new QContactManager("galera")),
m_maxCount(20),
m_average(0),
m_outdated(true),
m_reloadingModel(false),
m_aboutToQuit(false)
{
connect(this, SIGNAL(sourceModelChanged(QAbstractItemModel*)), SLOT(markAsOutdated()));
connect(this, SIGNAL(maxCountChanged(uint)), SLOT(markAsOutdated()));
connect(this, SIGNAL(startIntervalChanged(QDateTime)), SLOT(markAsOutdated()));
}
MostCalledContactsModel::~MostCalledContactsModel()
{
m_aboutToQuit = true;
m_phones.clear();
if (m_currentFetch) {
m_currentFetch->cancel();
}
}
QVariant MostCalledContactsModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
int row = index.row();
if ((row >= 0) && (row < m_data.size())) {
switch (role)
{
case MostCalledContactsModel::ContactIdRole:
return m_data[row].contactId;
case MostCalledContactsModel::PhoneNumberRole:
return m_data[row].phoneNumber;
case MostCalledContactsModel::CallCountRole:
return m_data[row].callCount;
default:
return QVariant();
}
}
return QVariant();
}
QHash MostCalledContactsModel::roleNames() const
{
static QHash roles;
if (roles.isEmpty()) {
roles.insert(MostCalledContactsModel::ContactIdRole, "contactId");
roles.insert(MostCalledContactsModel::PhoneNumberRole, "phoneNumber");
roles.insert(MostCalledContactsModel::CallCountRole, "callCount");
}
return roles;
}
int MostCalledContactsModel::rowCount(const QModelIndex &) const
{
return m_data.size();
}
QAbstractItemModel *MostCalledContactsModel::sourceModel() const
{
return m_sourceModel;
}
void MostCalledContactsModel::setSourceModel(QAbstractItemModel *model)
{
if (m_sourceModel != model) {
if (m_sourceModel) {
disconnect(m_sourceModel);
}
m_sourceModel = model;
connect(m_sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(markAsOutdated()));
connect(m_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(markAsOutdated()));
connect(m_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(markAsOutdated()));
connect(m_sourceModel, SIGNAL(modelReset()), SLOT(markAsOutdated()));
Q_EMIT sourceModelChanged(m_sourceModel);
}
}
uint MostCalledContactsModel::maxCount() const
{
return m_maxCount;
}
void MostCalledContactsModel::setMaxCount(uint value)
{
if (m_maxCount != value) {
m_maxCount = value;
Q_EMIT maxCountChanged(m_maxCount);
}
}
int MostCalledContactsModel::callAverage() const
{
return m_average;
}
QDateTime MostCalledContactsModel::startInterval() const
{
return m_startInterval;
}
void MostCalledContactsModel::setStartInterval(const QDateTime &value)
{
if (m_startInterval != value) {
m_startInterval = value;
Q_EMIT startIntervalChanged(m_startInterval);
}
}
QVariant MostCalledContactsModel::getSourceData(int row, int role)
{
QAbstractItemModel *source = sourceModel();
if (!source) {
return QVariant();
}
while ((source->rowCount() <= row) && (source->canFetchMore(QModelIndex()))) {
source->fetchMore(QModelIndex());
}
if (source->rowCount() < row) {
return QVariant();
}
QModelIndex sourceIndex = source->index(row, 0);
return source->data(sourceIndex, role);
}
void MostCalledContactsModel::update()
{
// skip update if not necessary
if (!m_outdated || m_reloadingModel) {
return;
}
if (m_maxCount <= 0) {
qWarning() << "update model requested with invalid maxCount";
m_outdated = false;
return;
}
if (!m_startInterval.isValid()) {
qWarning() << "Update model requested with invalid startInterval";
m_outdated = false;
return;
}
QAbstractItemModel *source = sourceModel();
if (!source) {
qWarning() << "Update model requested with null source model";
m_outdated = false;
return;
}
m_totalCalls = 0;
m_phones.clear();
m_phoneToContactCache.clear();
m_contactsData.clear();
queryContacts();
}
void MostCalledContactsModel::queryContacts()
{
// get all phone in the date inteval
QHash roles = sourceModel()->roleNames();
int participantsRole = roles.key("participants", -1);
int timestampRole = roles.key("timestamp", -1);
int row = 0;
Q_ASSERT(participantsRole != -1);
Q_ASSERT(timestampRole != -1);
while(true) {
QVariant date = getSourceData(row, timestampRole);
// end of source model
if (date.isNull()) {
break;
}
// exit if date is out of interval
if (date.toDateTime() < m_startInterval) {
break;
}
QVariant participants = getSourceData(row, participantsRole);
if (participants.isValid()) {
Q_FOREACH(const QString phone, participants.toStringList()) {
m_phones << phone;
}
}
// next row
row++;
}
// query for all phones
nextContact();
}
void MostCalledContactsModel::nextContact()
{
if (m_phones.isEmpty()) {
parseResult();
return;
}
QString nextPhone = m_phones.takeFirst();
if (m_phoneToContactCache.contains(nextPhone)) {
registerCall(nextPhone, m_phoneToContactCache.value(nextPhone));
nextContact();
return;
} else {
QContactFilter filter(QContactPhoneNumber::match(nextPhone));
QContactFetchHint hint;
hint.setDetailTypesHint(QList() << QContactDetail::TypeGuid);
m_currentFetch = new QContactFetchRequest;
m_currentFetch->setProperty("PHONE", nextPhone);
m_currentFetch->setFilter(filter);
m_currentFetch->setFetchHint(hint);
m_currentFetch->setManager(m_manager.data());
connect(m_currentFetch,
SIGNAL(stateChanged(QContactAbstractRequest::State)),
SLOT(fetchContactIdDone()));
m_currentFetch->start();
}
}
void MostCalledContactsModel::fetchContactIdDone()
{
Q_ASSERT(m_currentFetch);
if (m_aboutToQuit) {
m_currentFetch->deleteLater();
m_currentFetch = 0;
return;
}
if (m_currentFetch->state() == QContactAbstractRequest::ActiveState) {
return;
}
if (!m_currentFetch->contacts().isEmpty()) {
QString id = m_currentFetch->contacts().at(0).id().toString();
registerCall(m_currentFetch->property("PHONE").toString(), id);
}
m_currentFetch->deleteLater();
m_currentFetch = 0;
nextContact();
}
void MostCalledContactsModel::registerCall(const QString &phone, const QString &contactId)
{
// update cache
m_phoneToContactCache.insert(phone, contactId);
if (m_contactsData.contains(contactId)) {
MostCalledContactsModelData &data = m_contactsData[contactId];
data.callCount++;
} else {
MostCalledContactsModelData data;
data.contactId = contactId;
data.phoneNumber = phone;
data.callCount = 1;
m_contactsData.insert(contactId, data);
}
m_totalCalls++;
}
void MostCalledContactsModel::parseResult()
{
if (m_aboutToQuit) {
return;
}
Q_EMIT beginResetModel();
m_reloadingModel = true;
m_outdated = false;
m_data.clear();
m_average = 0;
if (!m_contactsData.isEmpty()) {
// sort by callCount
QList data = m_contactsData.values();
qSort(data.begin(), data.end(), mostCalledContactsModelDataGreaterThan);
// average
m_average = qRound(((qreal) (m_totalCalls)) / m_contactsData.size());
Q_FOREACH(const MostCalledContactsModelData &d, data) {
if (d.callCount >= m_average) {
m_data << d;
}
if ((uint) m_data.size() >= m_maxCount) {
break;
}
}
}
m_totalCalls = 0;
m_phones.clear();
m_phoneToContactCache.clear();
m_contactsData.clear();
Q_EMIT endResetModel();
m_reloadingModel = false;
Q_EMIT callAverageChanged(m_average);
Q_EMIT loaded();
}
void MostCalledContactsModel::markAsOutdated()
{
// skip if model is being reloaded
if (m_reloadingModel) {
return;
}
if (!m_outdated) {
m_outdated = true;
Q_EMIT outdatedChange(m_outdated);
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/plugin.cpp 0000644 0000156 0000165 00000002571 12674534204 026411 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "plugin.h"
#include "mostcalledproxymodel.h"
#include "contacts.h"
#include "simcardcontacts.h"
#include
#include
static QObject *contactsProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return new UbuntuContacts;
}
void UbuntuContactsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
{
Q_UNUSED(engine);
Q_UNUSED(uri);
}
void UbuntuContactsPlugin::registerTypes(const char *uri)
{
// @uri Ubuntu.Contacts
qmlRegisterSingletonType(uri, 0, 1, "Contacts", contactsProvider);
qmlRegisterType(uri, 0, 1, "MostCalledContactsModel");
qmlRegisterType(uri, 0, 1, "SimCardContacts");
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/MultipleSelectionVisualModel.qml 0000644 0000156 0000165 00000001545 12674534204 032730 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
VisualDataModel {
id: contactVisualModel
property alias selectedItems: selectedGroup
groups: [
VisualDataGroup {
id: selectedGroup
name: "selected"
}
]
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/OnlineAccountsDummy.qml 0000644 0000156 0000165 00000001405 12674534204 031055 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
Item {
id: root
property bool running: false
function setupExec()
{
root.running = true
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/contacts.h 0000644 0000156 0000165 00000003420 12674534204 026370 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#ifndef _UBUNTU_CONTACTS_H_
#define _UBUNTU_CONTACTS_H_
#include
#include
#include
#include
#include
class UbuntuContacts : public QObject
{
Q_OBJECT
Q_PROPERTY(QString tempPath READ tempPath)
Q_PROPERTY(bool updateIsRunning READ updateIsRunning NOTIFY updateIsRunningChanged)
public:
UbuntuContacts(QObject *parent = 0);
QString tempPath() const;
Q_INVOKABLE QString contactInitialsFromString(const QString &value);
Q_INVOKABLE QString normalized(const QString &value);
Q_INVOKABLE QString copyImage(const QUrl &imageUrl);
Q_INVOKABLE bool containsLetters(const QString &value);
Q_INVOKABLE bool removeFile(const QUrl &file);
Q_INVOKABLE bool updateIsRunning() const;
Q_INVOKABLE QUrl tempFile(const QString &suffix);
Q_INVOKABLE uint qHash(const QString &str);
Q_SIGNALS:
void imageCopyDone(const QString &id, const QString &fileName);
void updateIsRunningChanged();
private:
QScopedPointer m_fileWatcher;
static QString updaterLockFile();
};
#endif //_UBUNTU_CONTACTS_H_
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/FastScroll.js 0000644 0000156 0000165 00000011061 12674534204 027013 0 ustar pbuser pbgroup 0000000 0000000 /****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** Copyright (C) 2014 Canonical Ltda
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Components project.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
** the names of its contributors may be used to endorse or promote
** products derived from this software without specific prior written
** permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
** $QT_END_LICENSE$
**
****************************************************************************/
// FastScroll.js - this is just SectionScroller.js with a fix for
// section.criteria == ViewSection.FirstCharacter
var sectionData = [];
var _sections = [];
function initialize(list) {
initSectionData(list);
}
function contains(name) {
return (_sections.indexOf(name) > -1)
}
function initSectionData(list) {
if (!list || !list.model) return;
sectionData = [];
_sections = [];
var current = "",
prop = list.section.property,
sectionText;
if (list.section.criteria === ViewSection.FullString) {
for (var i = 0, count = list.model.count; i < count; i++) {
sectionText = list.getSectionText(i)
if (sectionText !== current) {
current = sectionText;
_sections.push(current);
sectionData.push({ index: i, header: current });
}
}
} else if (list.section.criteria === ViewSection.FirstCharacter) {
for (var i = 0, count = list.model.count; i < count; i++) {
sectionText = list.getSectionText(i).substring(0, 1)
if (sectionText !== current) {
current = sectionText
_sections.push(sectionText);
sectionData.push({ index: i, header: current });
}
}
}
}
function getSectionPositionString(name) {
var val = _sections.indexOf(name);
return val === 0 ? "first" :
val === _sections.length - 1 ? "last" : false;
}
function getAt(pos) {
return _sections[pos] ? _sections[pos] : "";
}
function getRelativeSections(current) {
var val = _sections.indexOf(current),
sect = [],
sl = _sections.length;
val = val < 1 ? 1 : val >= sl-1 ? sl-2 : val;
sect = [getAt(val - 1), getAt(val), getAt(val + 1)];
return sect;
}
function getClosestSection(pos, down) {
var tmp = (_sections.length) * pos;
var val = Math.ceil(tmp) // TODO: better algorithm
val = val < 2 ? 1 : val;
return _sections[val-1];
}
function getNextSection(current) {
var val = _sections.indexOf(current);
return (val > -1 ? _sections[(val < _sections.length - 1 ? val + 1 : val)] : _sections[0]) || "";
}
function getPreviousSection(current) {
var val = _sections.indexOf(current);
return (val > -1 ? _sections[(val > 0 ? val - 1 : val)] : _sections[0]) || "";
}
function getIndexFor(sectionName) {
var data = sectionData[_sections.indexOf(sectionName)]
if (data) {
var val = data.index;
return val === 0 || val > 0 ? val : -1;
} else {
return -1
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/simcardcontacts.h 0000644 0000156 0000165 00000004403 12674534204 027735 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#ifndef SIMCARDCONTACTS_H
#define SIMCARDCONTACTS_H
#include
#include
#include
#include
#include
class SimCardContacts : public QObject
{
Q_OBJECT
Q_PROPERTY(QString contacts READ contacts NOTIFY contactsChanged)
Q_PROPERTY(QUrl vcardFile READ vcardFile NOTIFY contactsChanged)
Q_PROPERTY(bool hasContacts READ hasContacts NOTIFY contactsChanged)
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
public:
SimCardContacts(QObject *parent=0);
~SimCardContacts();
QString contacts() const;
QUrl vcardFile() const;
bool hasContacts() const;
bool busy() const;
Q_INVOKABLE void unlockModem(const QString &modemPath);
Q_SIGNALS:
void contactsChanged();
void importFail();
void busyChanged();
private Q_SLOTS:
void onModemChanged();
void onPhoneBookIsValidChanged(bool isValid);
void onPhoneBookImported(const QString &vcardData);
void onPhoneBookImportFail();
void onManagerChanged();
void onModemsChanged();
void reload();
private:
QScopedPointer m_ofonoManager;
QSet m_pendingPhoneBooks;
QSet m_availableModems;
QTemporaryFile *m_dataFile;
QStringList m_vcards;
QMutex m_importing;
QTimer m_modemsChangedTimer;
bool m_importingFlag;
bool hasPhoneBook(QOfonoModem *modem);
void writeData();
void reloadContactsFromModem(QOfonoModem* modem);
void cancel();
void startImport();
void importDone();
bool importPhoneBook(QOfonoModem *modem);
void importPhoneBook(QOfonoPhonebook *phoneBook);
};
#endif
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/VCardParser.qml 0000644 0000156 0000165 00000002643 12674534204 027276 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
QtObject {
id: root
property string vCardUrl
property alias contacts: contactsModel.contacts
property var _model
signal vcardParsed(int error)
function clearModel()
{
if (contactsModel.contacts.length === 0)
return;
var ids = []
for(var i=0, iMax=contactsModel.contacts.length; i < iMax; i++) {
ids.push(contactsModel.contacts[i].contactId)
}
contactsModel.removeContacts(ids)
}
_model: ContactModel {
id: contactsModel
manager: "memory"
onImportCompleted: vcardParsed(error)
}
onVCardUrlChanged: {
if (vCardUrl.length > 0) {
clearModel()
contactsModel.importContacts(vCardUrl)
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/SectionDelegate.qml 0000644 0000156 0000165 00000002164 12674534204 030157 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright 2014 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3
Rectangle {
property alias text: title.text
color: Theme.palette.normal.background
height: units.gu(4)
Label {
id: title
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
height: units.gu(3)
}
ThinDivider {
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/imagescalethread.cpp 0000644 0000156 0000165 00000005776 12674534204 030407 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "imagescalethread.h"
#include
#include
#include
#include
#include
#include
ImageScaleThread::ImageScaleThread(const QUrl &imageUrl, QObject *listener)
: m_imageUrl(imageUrl),
m_id(QUuid::createUuid().toString()),
m_listener(listener),
m_tmpFile(0)
{
}
ImageScaleThread::~ImageScaleThread()
{
if (m_tmpFile) {
m_tmpFile->setAutoRemove(false);
m_tmpFile->deleteLater();
m_tmpFile = 0;
}
}
QString ImageScaleThread::id()
{
return m_id;
}
QString ImageScaleThread::outputFile() const
{
if (m_tmpFile) {
return m_tmpFile->fileName();
} else {
return QString();
}
}
void ImageScaleThread::run()
{
// make sure that the old image get deleted
if (m_tmpFile) {
qDebug() << "Delete previous avatar" << m_tmpFile->fileName();
m_tmpFile->setAutoRemove(true);
m_tmpFile->close();
delete m_tmpFile;
}
// Create the temporary file
m_tmpFile = new QTemporaryFile(QString("%1/avatar_XXXXXX.png").arg(QDir::tempPath()));
if (!m_tmpFile->open()) {
qWarning() << "Fail to create avatar temporary file";
return;
}
// try using the Qt's image reader to speedup the scaling
QImage scaledAvatar;
QImageReader reader(m_imageUrl.toLocalFile());
if (reader.canRead()) {
QSize size = reader.size();
if ((size.height() > 720) && (size.width() > 720)) {
size.scale(720, 720, Qt::KeepAspectRatioByExpanding);
}
reader.setScaledSize(size);
scaledAvatar = reader.read();
}
// fallback to use a QImage to load the avatar if the image reader failed
if (scaledAvatar.isNull()) {
QImage img(m_imageUrl.toLocalFile());
if (!img.isNull()) {
if ((img.height() > 720) && (img.width() > 720)) {
scaledAvatar = img.scaled(720, 720, Qt::KeepAspectRatioByExpanding, Qt::FastTransformation);
} else {
scaledAvatar = img;
}
}
}
// and finally, save the image
if (!scaledAvatar.isNull()) {
scaledAvatar.save(m_tmpFile, "png");
}
m_tmpFile->close();
if (m_listener) {
QMetaObject::invokeMethod(m_listener, "imageCopyDone",
Q_ARG(QString, m_id), Q_ARG(QString, m_tmpFile->fileName()));
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/mostcalledproxymodel.h 0000644 0000156 0000165 00000006403 12674534204 031030 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#ifndef __MOSTCALLEDCONTACTSMODEL_H__
#define __MOSTCALLEDCONTACTSMODEL_H__
#include
#include
#include
#include
#include
#include
struct MostCalledContactsModelData
{
QString contactId;
QString phoneNumber;
int callCount;
};
class MostCalledContactsModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged)
Q_PROPERTY(uint maxCount READ maxCount WRITE setMaxCount NOTIFY maxCountChanged)
Q_PROPERTY(int callAverage READ callAverage NOTIFY callAverageChanged)
Q_PROPERTY(QDateTime startInterval READ startInterval WRITE setStartInterval NOTIFY startIntervalChanged)
Q_PROPERTY(bool outdated READ outdated NOTIFY outdatedChange)
public:
enum Role {
ContactIdRole = 0,
PhoneNumberRole,
CallCountRole
};
MostCalledContactsModel(QObject *parent=0);
~MostCalledContactsModel();
QVariant data(const QModelIndex &index, int role) const;
QHash roleNames() const;
int rowCount(const QModelIndex&) const;
QAbstractItemModel *sourceModel() const;
void setSourceModel(QAbstractItemModel *model);
uint maxCount() const;
void setMaxCount(uint value);
int callAverage() const;
bool outdated() const;
QDateTime startInterval() const;
void setStartInterval(const QDateTime &value);
Q_INVOKABLE void update();
Q_SIGNALS:
void maxCountChanged(uint value);
void callAverageChanged(int value);
void startIntervalChanged(const QDateTime &value);
void sourceModelChanged(QAbstractItemModel *value);
void outdatedChange(bool value);
void loaded();
private Q_SLOTS:
void markAsOutdated();
void fetchContactIdDone();
private:
QAbstractItemModel *m_sourceModel;
QtContacts::QContactFetchRequest *m_currentFetch;
QScopedPointer m_manager;
QList m_data;
uint m_maxCount;
int m_average;
QDateTime m_startInterval;
bool m_outdated;
bool m_reloadingModel;
bool m_aboutToQuit;
QStringList m_phones;
QMap m_phoneToContactCache;
QMap m_contactsData;
int m_totalCalls;
void fetchContactId(const QString &phoneNumber);
QVariant getSourceData(int row, int role);
void queryContacts();
void nextContact();
void registerCall(const QString &phone, const QString &contactId);
void parseResult();
};
#endif
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/Ofono.qml 0000644 0000156 0000165 00000002075 12674534204 026201 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import MeeGo.QOfono 0.2
Item {
readonly property alias simMng: simMng
readonly property alias present: simMng.present
property string path
property string name
readonly property string title: {
var number = simMng.subscriberNumbers[0] || simMng.subscriberIdentity;
return name + (number ? " (" + number + ")" : "");
}
OfonoSimManager {
id: simMng
modemPath: path
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/OnlineAccountsHelper.qml 0000644 0000156 0000165 00000002133 12674534204 031200 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.OnlineAccounts 0.1
import Ubuntu.OnlineAccounts.Client 0.1
Item {
id: root
property bool running: false
function setupExec()
{
if (!root.running) {
root.running = true
setup.exec()
}
}
Setup {
id: setup
applicationId: "address-book-app"
providerId: "google"
onFinished: {
root.running = false
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/contacts.cpp 0000644 0000156 0000165 00000006637 12674534204 026740 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "contacts.h"
#include "imagescalethread.h"
#include
#include
#include
#include
#include
#include
#include
#include "config.h"
UbuntuContacts::UbuntuContacts(QObject *parent)
: QObject(parent),
m_fileWatcher(new QFileSystemWatcher)
{
// We need to monitor the tmp dir since the file could not exists at this point
m_fileWatcher->addPath(QDir::tempPath());
connect(m_fileWatcher.data(),
SIGNAL(directoryChanged(QString)),
SIGNAL(updateIsRunningChanged()));
connect(m_fileWatcher.data(),
SIGNAL(fileChanged(QString)),
SIGNAL(updateIsRunningChanged()));
}
QString UbuntuContacts::tempPath() const
{
return QDir::tempPath();
}
QString UbuntuContacts::contactInitialsFromString(const QString &value)
{
if (value.isEmpty() || !value.at(0).isLetter()) {
return QString();
}
QString initials;
QStringList parts = value.split(" ");
initials = parts.first().at(0).toUpper();
if (parts.size() > 1) {
initials += parts.last().at(0).toUpper();
}
return initials;
}
QString UbuntuContacts::normalized(const QString &value)
{
QString s2 = value.normalized(QString::NormalizationForm_D);
QString out;
for (int i=0, j=s2.length(); istart(imgThread);
return imgThread->id();
}
bool UbuntuContacts::containsLetters(const QString &value)
{
foreach (const QChar &c, value) {
if (c.isLetter()) {
return true;
}
}
return false;
}
bool UbuntuContacts::removeFile(const QUrl &file)
{
QString localFile = file.toLocalFile();
if (!localFile.isEmpty() && QFile::exists(localFile)) {
return QFile::remove(localFile);
}
return false;
}
bool UbuntuContacts::updateIsRunning() const
{
return QFile::exists(updaterLockFile());
}
QUrl UbuntuContacts::tempFile(const QString &templateName)
{
QTemporaryFile tmp(QString("%1/%2").arg(tempPath()).arg(templateName));
tmp.setAutoRemove(false);
if (tmp.open())
return QUrl::fromLocalFile(tmp.fileName());
else
return QUrl();
}
uint UbuntuContacts::qHash(const QString &str)
{
return ::qHash(str);
}
QString UbuntuContacts::updaterLockFile()
{
return QString("%1/%2").arg(QDir::tempPath()).arg("/address-book-updater.lock");
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/Contacts.js 0000644 0000156 0000165 00000010274 12674534204 026522 0 ustar pbuser pbgroup 0000000 0000000
var phoneTypeModel = null
var contactColors = ["#DC3023", "#FF8936", "#FFB95A", "#8DB255", "#749F8D", "#48929B", "#A87CA0"]
// Format contact name to be displayed
function formatToDisplayWithDetails(contact, contactDetail, detailFields, defaultTitle)
{
if (!contact) {
return defaultTitle
}
var detail = contact.detail(contactDetail)
var values = ""
for (var i=0; i < detailFields.length; i++) {
if (detail) {
var value = detail.value(detailFields[i]);
if (value) {
if (i > 0 && detail) {
values += " "
}
values += value;
}
}
}
if (values.length === 0) {
return defaultTitle
}
return values
}
function formatToDisplay(contact, defaultTitle)
{
if (!contact) {
return defaultTitle
}
var detail = contact.detail(ContactDetail.DisplayLabel)
if (detail && (detail.label.length > 0)) {
return detail.label
}
detail = contact.detail(ContactDetail.Name)
if (detail) {
var fullName = (detail.firstName + " " + detail.lastName).trim()
if (fullName.length > 0) {
return fullName
}
}
detail = contact.detail(ContactDetail.Organization)
if (detail) {
if (detail.name && (detail.name.length > 0)) {
return detail.name
}
}
var details = contact.details(ContactDetail.PhoneNumber)
if (details.length > 0) {
detail = details[0]
if (detail.number && detail.number.length > 0) {
return detail.number
}
}
details = contact.details(ContactDetail.Email)
if (details.length > 0) {
detail = details[0]
if (detail.emailAddress && detail.emailAddress.length > 0) {
return detail.emailAddress
}
}
details = contact.details(ContactDetail.OnlineAccount)
if (details.length > 0) {
detail = details[0]
if (detail.accountUri && detail.accountUri.length > 0) {
return detail.accountUri
}
}
return defaultTitle
}
function getAvatar(contact, defaultValue)
{
// use this verbose mode to avoid problems with binding loops
var avatarUrl = defaultValue
if (!contact) {
return avatarUrl
}
var avatarDetail = contact.detail(ContactDetail.Avatar)
if (avatarDetail) {
var avatarValue = avatarDetail.value(Avatar.ImageUrl)
if (avatarValue && (avatarValue !== "")) {
avatarUrl = avatarValue
}
}
return avatarUrl
}
function getFavoritePhoneLabel(contact, defaultValue)
{
var phoneLabel = defaultValue
if (!contact) {
return phoneLabel
}
if (!phoneTypeModel) {
phoneTypeModel = Qt.createQmlObject("import Ubuntu.Contacts 0.1; ContactDetailPhoneNumberTypeModel {}",
parent,
"getFavoritePhoneLabel")
}
var prefDetail = contact.preferredDetail("TEL")
if (prefDetail) {
phoneLabel = phoneTypeModel.get(phoneTypeModel.getTypeIndex(prefDetail)).label
}
return phoneLabel
}
function createEmptyContact(phoneNumber, parent)
{
var details = [ {detail: "PhoneNumber", field: "number", value: phoneNumber},
{detail: "EmailAddress", field: "emailAddress", value: ""},
{detail: "Name", field: "firstName", value: ""}
]
var newContact = Qt.createQmlObject("import QtContacts 5.0; Contact{ }", parent)
var detailSourceTemplate = "import QtContacts 5.0; %1{ %2: \"%3\" }"
for (var i=0; i < details.length; i++) {
var detailMetaData = details[i]
var newDetail = Qt.createQmlObject(detailSourceTemplate.arg(detailMetaData.detail)
.arg(detailMetaData.field)
.arg(detailMetaData.value), parent)
newContact.addDetail(newDetail)
}
return newContact
}
function isNewContact(contact)
{
return (contact && (contact.contactId === "qtcontacts:::"))
}
function contactColor(name)
{
var hash = Contacts.qHash(name)
return contactColors[(hash % contactColors.length)]
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/simcardcontacts.cpp 0000644 0000156 0000165 00000016650 12674534204 030277 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "simcardcontacts.h"
#include
#include
#include
SimCardContacts::SimCardContacts(QObject *parent)
: QObject(parent),
m_ofonoManager(new QOfonoManager(this)),
m_dataFile(0)
{
onManagerChanged();
m_modemsChangedTimer.setInterval(1000);
m_modemsChangedTimer.setSingleShot(true);
connect(m_ofonoManager.data(),
SIGNAL(modemsChanged(QStringList)),
SLOT(onManagerChanged()),
Qt::QueuedConnection);
connect(m_ofonoManager.data(),
SIGNAL(availableChanged(bool)),
SLOT(onManagerChanged()),
Qt::QueuedConnection);
connect(&m_modemsChangedTimer,
SIGNAL(timeout()),
SLOT(onModemsChanged()));
}
SimCardContacts::~SimCardContacts()
{
Q_FOREACH(QOfonoModem *m, m_availableModems) {
disconnect(m);
m->deleteLater();
}
m_availableModems.clear();
cancel();
delete m_dataFile;
}
QString SimCardContacts::contacts() const
{
QString result;
Q_FOREACH(const QString &data, m_vcards) {
result += data;
}
return result;
}
QUrl SimCardContacts::vcardFile() const
{
if (m_dataFile) {
return QUrl::fromLocalFile(m_dataFile->fileName());
} else {
return QUrl();
}
}
bool SimCardContacts::hasContacts() const
{
return !m_vcards.isEmpty();
}
bool SimCardContacts::busy() const
{
return (m_modemsChangedTimer.isActive() ||
m_importingFlag);
}
void SimCardContacts::onPhoneBookIsValidChanged(bool isValid)
{
QOfonoPhonebook *pb = qobject_cast(QObject::sender());
Q_ASSERT(pb);
if (isValid) {
importPhoneBook(pb);
} else {
m_pendingPhoneBooks.remove(pb);
if (m_pendingPhoneBooks.isEmpty()) {
importDone();
}
pb->deleteLater();
}
}
void SimCardContacts::onModemsChanged()
{
qDebug() << "Modems changed";
startImport();
Q_FOREACH(QOfonoModem *modem, m_availableModems) {
importPhoneBook(modem);
}
if (m_pendingPhoneBooks.size() == 0) {
importDone();
}
}
void SimCardContacts::onManagerChanged()
{
startImport();
// clear modem list
Q_FOREACH(QObject *m, m_availableModems) {
disconnect(m);
m->deleteLater();
}
m_availableModems.clear();
if (!m_ofonoManager->available()) {
qWarning() << "Manager not available;";
return;
}
QStringList modems = m_ofonoManager->modems();
Q_FOREACH(const QString &modem, modems) {
QOfonoModem *m = new QOfonoModem(this);
m->setModemPath(modem);
m_availableModems << m;
importPhoneBook(m);
connect(m, SIGNAL(interfacesChanged(QStringList)),
SLOT(reload()));
connect(m, SIGNAL(validChanged(bool)),
SLOT(reload()));
}
if (m_pendingPhoneBooks.size() == 0) {
importDone();
}
}
bool SimCardContacts::importPhoneBook(QOfonoModem *modem)
{
if (hasPhoneBook(modem)) {
QOfonoPhonebook *pb = new QOfonoPhonebook(this);
pb->setModemPath(modem->modemPath());
m_pendingPhoneBooks << pb;
if (pb->isValid()) {
importPhoneBook(pb);
} else {
connect(pb,
SIGNAL(validChanged(bool)),
SLOT(onPhoneBookIsValidChanged(bool)),
Qt::QueuedConnection);
}
return true;
} else {
qDebug() << "Modem" << modem->modemPath() << "does not have phonebook interface";
}
return false;
}
void SimCardContacts::importPhoneBook(QOfonoPhonebook *phoneBook)
{
connect(phoneBook,
SIGNAL(importReady(QString)),
SLOT(onPhoneBookImported(QString)));
connect(phoneBook,
SIGNAL(importFailed()),
SLOT(onPhoneBookImportFail()));
phoneBook->beginImport();
}
void SimCardContacts::onPhoneBookImported(const QString &vcardData)
{
QOfonoPhonebook *pb = qobject_cast(QObject::sender());
Q_ASSERT(pb);
if (!vcardData.trimmed().isEmpty()) {
m_vcards << vcardData;
}
m_pendingPhoneBooks.remove(pb);
if (m_pendingPhoneBooks.isEmpty()) {
importDone();
}
pb->deleteLater();
}
void SimCardContacts::onPhoneBookImportFail()
{
QOfonoPhonebook *pb = qobject_cast(QObject::sender());
Q_ASSERT(pb);
qWarning() << "Fail to import contacts from:" << pb->modemPath();
m_pendingPhoneBooks.remove(pb);
if (m_pendingPhoneBooks.isEmpty()) {
importDone();
}
pb->deleteLater();
Q_EMIT importFail();
}
void SimCardContacts::startImport()
{
m_importingFlag = true;
Q_EMIT busyChanged();
if (!m_importing.tryLock()) {
qDebug() << "Import in progress.";
cancel();
if (!m_importing.tryLock()) {
qWarning() << "Fail to cancel current import";
return;
}
}
m_vcards.clear();
Q_EMIT contactsChanged();
}
void SimCardContacts::importDone()
{
writeData();
m_importing.unlock();
Q_EMIT contactsChanged();
m_importingFlag = false;
Q_EMIT busyChanged();
}
void SimCardContacts::writeData()
{
if (m_dataFile) {
delete m_dataFile;
m_dataFile = 0;
}
if (!m_vcards.isEmpty()) {
m_dataFile = new QTemporaryFile();
m_dataFile->open();
Q_FOREACH(const QString &data, m_vcards) {
m_dataFile->write(data.toUtf8());
}
m_dataFile->close();
}
}
void SimCardContacts::cancel()
{
Q_FOREACH(QOfonoPhonebook *m, m_pendingPhoneBooks) {
disconnect(m);
m->deleteLater();
}
m_pendingPhoneBooks.clear();
m_importing.unlock();
m_vcards.clear();
m_importingFlag = false;
Q_EMIT busyChanged();
}
bool SimCardContacts::hasPhoneBook(QOfonoModem *modem)
{
return (modem->isValid() && modem->interfaces().contains("org.ofono.Phonebook"));
}
void SimCardContacts::reload()
{
m_modemsChangedTimer.start();
Q_EMIT busyChanged();
}
void SimCardContacts::unlockModem(const QString &modemPath)
{
static const QString connService("com.ubuntu.connectivity1");
static const QString connObject("/com/ubuntu/connectivity1/Private");
static const QString connInterface("com.ubuntu.connectivity1.Private");
static const QString connUnlockmodemMethod("UnlockModem");
QDBusInterface connectivityIface (connService,
connObject,
connInterface,
QDBusConnection::sessionBus(),
this);
auto reply = connectivityIface.call(connUnlockmodemMethod, modemPath);
if (reply.type() == QDBusMessage::ErrorMessage) {
qWarning() << "Failed to unlock modem" << modemPath << reply.errorMessage();
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/qmldir 0000644 0000156 0000165 00000002631 12674534204 025617 0 ustar pbuser pbgroup 0000000 0000000 module Ubuntu.Contacts
plugin ubuntu-contacts-qml
ContactAvatar 0.1 ContactAvatar.qml
ContactDelegate 0.1 ContactDelegate.qml
ContactDetailOnlineAccountTypeModel 0.1 ContactDetailOnlineAccountTypeModel.qml
ContactDetailPhoneNumberTypeModel 0.1 ContactDetailPhoneNumberTypeModel.qml
ContactFetch 0.1 ContactFetch.qml
ContactListModel 0.1 ContactListModel.qml
ContactListView 0.1 ContactListView.qml
ListItemWithActions 0.1 ListItemWithActions.qml
MultipleSelectionListView 0.1 MultipleSelectionListView.qml
PageWithBottomEdge 0.1 PageWithBottomEdge.qml
VCardParser 0.1 VCardParser.qml
# this is necessary for contact app does not use this on external apps
ContactsJS 0.1 Contacts.js
SIMCardImportPage 0.1 SIMCardImportPage.qml
OnlineAccountsHelper 0.1 OnlineAccountsHelper.qml
SIMList 0.1 SIMList.qml
internal ContactAvatar ContactAvatar.qml
internal ContactDetailPickerPhoneNumberDelegate ContactDetailPickerPhoneNumberDelegate.qml
internal ContactImportButton ContactImportButton.qml
internal ContactList ContactList.js
internal ContactSimpleListView ContactSimpleListView.qml
internal FastScrollJs FastScroll.js
internal FastScroll FastScroll.qml
internal ListItemWithActionsCheckBox ListItemWithActionsCheckBox.qml
internal MostCalledList MostCalledList.qml
internal MostCalledModel MostCalledModel.qml
internal Ofono Ofono.qml
internal SectionDelegate SectionDelegate.qml
internal SubtitledWithColors SubtitledWithColors.qml
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ActionButton.qml 0000644 0000156 0000165 00000001675 12674534204 027537 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
AbstractButton {
id: root
property QtObject actions
property alias iconName: icon.name
property real iconSize: units.gu(2.5)
Icon {
id: icon
anchors.centerIn: parent
height: root.iconSize
width: root.iconSize
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/FastScroll.qml 0000644 0000156 0000165 00000023545 12674534210 027177 0 ustar pbuser pbgroup 0000000 0000000 /****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** Copyright (C) 2014 Canonical Ltda
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Components project.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
** the names of its contributors may be used to endorse or promote
** products derived from this software without specific prior written
** permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
** $QT_END_LICENSE$
**
****************************************************************************/
// FastScroll.qml
import QtQuick 2.4
import Ubuntu.Components 1.3
import "FastScroll.js" as Sections
Item {
id: root
property ListView listView
property int pinSize: units.gu(2)
readonly property var letters: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"]
readonly property alias fastScrolling: internal.fastScrolling
readonly property bool showing: (rail.opacity !== 0.0)
readonly property double minimumHeight: rail.height
width: units.gu(7)
height: rail.height
visible: enabled
onListViewChanged: {
if (listView && listView.model) {
internal.initDirtyObserver();
} else if (listView) {
listView.modelChanged.connect(function() {
if (listView.model) {
internal.initDirtyObserver();
}
});
}
}
UbuntuShape {
id: magnified
aspect: UbuntuShape.Flat
color: Theme.palette.normal.foreground
radius: "medium"
height: units.gu(6)
width: units.gu(8)
opacity: internal.fastScrolling && root.enabled ? 1.0 : 0.0
x: -magnified.width
y: {
if (internal.currentItem) {
var itemCenterY = rail.y + internal.currentItem.y + (internal.currentItem.height / 2)
return (itemCenterY - (magnified.height / 2))
} else {
return 0
}
}
Triangle {
id: arrow
color: magnified.color
fill: true
height: units.gu(1.5)
width: units.gu(0.5)
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: -width
}
}
Label {
color: Theme.palette.selected.backgroundText
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: internal.desireSection
fontSize: "small"
}
Behavior on opacity {
UbuntuNumberAnimation {}
}
}
Rectangle {
id: cursor
property bool showLabel: false
property string currentSectionName: ""
radius: pinSize * 0.3
height: pinSize
width: height
color: Theme.palette.normal.foreground
opacity: rail.opacity
x: rail.x
y: {
if (internal.currentItem) {
var itemCenterY = rail.y + internal.currentItem.y + (internal.currentItem.height / 2)
return (itemCenterY - (cursor.height / 2))
} else {
return 0
}
}
Behavior on y {
enabled: !internal.fastScrolling
UbuntuNumberAnimation { }
}
}
Column {
id: rail
property bool isVisible: root.enabled &&
(listView.flicking || dragArea.pressed)
anchors {
right: parent.right
rightMargin: units.gu(2)
left: parent.left
leftMargin: units.gu(2)
top: parent.top
}
height: childrenRect.height
opacity: 0.0
onIsVisibleChanged: {
if (isVisible) {
rail.opacity = 1.0
hideTimer.stop()
} else if (!root.enabled) {
rail.opacity = 0.0
} else {
hideTimer.restart()
}
}
Behavior on opacity {
UbuntuNumberAnimation { }
}
Repeater {
id: sectionsRepeater
model: root.letters
Label {
id: lbl
anchors.left: parent.left
height: pinSize
width: pinSize
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: modelData
fontSize: "x-small"
color: cursor.y === y ? Theme.palette.selected.backgroundText : Theme.palette.normal.foregroundText
opacity: !internal.modelDirty && Sections.contains(text) ? 1.0 : 0.5
}
}
Timer {
id: hideTimer
running: false
interval: 2000
onTriggered: rail.opacity = 0.0
}
}
MouseArea {
id: dragArea
anchors {
left: parent.left
right: parent.right
}
y: rail.y
height: rail.height
visible: rail.opacity == 1.0
preventStealing: true
onPressed: {
internal.adjustContentPosition(mouseY)
dragginTimer.start()
}
onReleased: {
dragginTimer.stop()
internal.desireSection = ""
internal.fastScrolling = false
}
onPositionChanged: internal.adjustContentPosition(mouseY)
Timer {
id: dragginTimer
running: false
interval: 150
onTriggered: {
internal.fastScrolling = true
}
}
}
Timer {
id: dirtyTimer
interval: 500
running: false
onTriggered: {
Sections.initSectionData(listView);
internal.modelDirty = false;
}
}
Timer {
id: timerScroll
running: false
interval: 10
onTriggered: {
if (internal.desireSection != internal.currentSection) {
var idx = Sections.getIndexFor(internal.desireSection)
if (idx !== -1) {
listView.cancelFlick()
listView.positionViewAtIndex(idx, ListView.Beginning)
}
}
}
}
QtObject {
id: internal
property string currentSection: listView.currentSection
property string desireSection: ""
property string targetSection: fastScrolling ? desireSection : currentSection
property int oldY: 0
property bool modelDirty: false
property bool down: true
property bool fastScrolling: false
property var currentItem: null
onTargetSectionChanged: moveIndicator(targetSection)
function initDirtyObserver() {
Sections.initialize(listView);
function dirtyObserver() {
if (!internal.modelDirty) {
internal.modelDirty = true;
dirtyTimer.running = true;
}
}
if (listView.model.countChanged)
listView.model.countChanged.connect(dirtyObserver);
if (listView.model.itemsChanged)
listView.model.itemsChanged.connect(dirtyObserver);
if (listView.model.itemsInserted)
listView.model.itemsInserted.connect(dirtyObserver);
if (listView.model.itemsMoved)
listView.model.itemsMoved.connect(dirtyObserver);
if (listView.model.itemsRemoved)
listView.model.itemsRemoved.connect(dirtyObserver);
}
function adjustContentPosition(mouseY) {
var child = rail.childAt(rail.width / 2, mouseY)
if (!child || child.text === "") {
return
}
var section = child.text
if (internal.desireSection !== section) {
internal.desireSection = section
moveIndicator(section)
if (dragArea.pressed) {
timerScroll.restart()
}
}
}
function moveIndicator(section)
{
var index = root.letters.indexOf(section)
if (index != -1) {
currentItem = sectionsRepeater.itemAt(index)
}
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/CMakeLists.txt 0000644 0000156 0000165 00000004357 12674534204 027153 0 ustar pbuser pbgroup 0000000 0000000 set(CONTACT_COMPONENTS_PLUGIN "ubuntu-contacts-qml")
set(CONTACT_COMPONENTS_QMLS
ActionButton.qml
ContactAvatar.qml
ContactDelegate.qml
ContactDetailOnlineAccountTypeModel.qml
ContactDetailPhoneNumberTypeModel.qml
ContactDetailPickerPhoneNumberDelegate.qml
ContactFetch.qml
ContactList.js
ContactListButtonDelegate.qml
ContactListModel.qml
ContactListView.qml
ContactSimpleListView.qml
Contacts.js
FastScroll.js
FastScroll.qml
ListItemWithActionsCheckBox.qml
ListItemWithActions.qml
MostCalledList.qml
MostCalledModel.qml
MultipleSelectionListView.qml
MultipleSelectionVisualModel.qml
Ofono.qml
OnlineAccountsDummy.qml
OnlineAccountsHelper.qml
PageWithBottomEdge.qml
qmldir
SectionDelegate.qml
SIMList.qml
SIMCardImportPage.qml
SubtitledWithColors.qml
Triangle.qml
VCardParser.qml
)
set(CONTACT_COMPONENTS_SRC
contacts.h
contacts.cpp
imagescalethread.h
imagescalethread.cpp
mostcalledproxymodel.h
mostcalledproxymodel.cpp
plugin.h
plugin.cpp
simcardcontacts.h
simcardcontacts.cpp
)
include_directories(
${CMAKE_BINARY_DIR}
${QOfono_INCLUDE_DIRS}
)
add_library(${CONTACT_COMPONENTS_PLUGIN} MODULE
${CONTACT_COMPONENTS_SRC}
)
target_link_libraries(${CONTACT_COMPONENTS_PLUGIN}
${QOfono_LIBRARIES}
Qt5::Core
Qt5::Contacts
Qt5::Qml
Qt5::Quick
Qt5::DBus
)
# make the files visible on qtcreator
add_custom_target(contact_components_QmlFiles ALL SOURCES ${CONTACT_COMPONENTS_QMLS})
if(INSTALL_COMPONENTS)
install(FILES ${CONTACT_COMPONENTS_QMLS} DESTINATION ${QMLPLUGIN_INSTALL_PREFIX})
install(TARGETS ${CONTACT_COMPONENTS_PLUGIN} DESTINATION ${QMLPLUGIN_INSTALL_PREFIX})
endif()
#copy qml files to build dir to make it possible to run without install
foreach(QML_FILE ${CONTACT_COMPONENTS_QMLS})
add_custom_command(TARGET contact_components_QmlFiles PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E
copy ${CMAKE_CURRENT_SOURCE_DIR}/${QML_FILE} ${CMAKE_CURRENT_BINARY_DIR}/)
endforeach()
if (NOT ${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
add_dependencies(copyqmlfiles contact_components_QmlFiles)
endif()
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactAvatar.qml 0000644 0000156 0000165 00000006266 12674534204 027661 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Contacts 0.1
import "Contacts.js" as ContactsJS
UbuntuShape {
id: avatar
property var contactElement: null
property string fallbackAvatarUrl: "image://theme/stock_contact"
property string fallbackDisplayName: ""
property bool showAvatarPicture: (avatarUrl != fallbackAvatarUrl) || (initials.length === 0)
readonly property alias initials: initialsLabel.text
readonly property alias displayName: initialsLabel.contactDisplayName
readonly property alias avatarUrl: img.avatarUrl
// this is necessary because the object does not monitor changes on avatarDetail object this will be very expesive and only happens in few cases,
// this need to be called manually on these cases
function reload()
{
img.avatarUrl = Qt.binding(function() { return ContactsJS.getAvatar(contactElement, fallbackAvatarUrl) })
initialsLabel.contactDisplayName = Qt.binding(function() { return ContactsJS.formatToDisplayWithDetails(contactElement, ContactDetail.Name, [Name.FirstName, Name.LastName], fallbackDisplayName) })
}
aspect: UbuntuShape.Flat
radius: "medium"
backgroundColor: ContactsJS.contactColor(displayName)
Label {
id: initialsLabel
objectName: "avatarInitials"
property string contactDisplayName: ContactsJS.formatToDisplayWithDetails(contactElement, ContactDetail.Name, [Name.FirstName, Name.LastName], fallbackDisplayName)
anchors.centerIn: parent
text: Contacts.contactInitialsFromString(contactDisplayName)
color: "white"
visible: (img.status != Image.Ready) && !fallbackIcon.visible
fontSize: "large"
}
source: !img.visible ? img : null
sourceFillMode: UbuntuShape.PreserveAspectCrop
Image {
id: img
objectName: "avatarImage"
property string avatarUrl: ContactsJS.getAvatar(contactElement, fallbackAvatarUrl)
asynchronous: true
source: avatar.showAvatarPicture && (avatar.avatarUrl.indexOf("image://theme/") === -1) ? avatar.avatarUrl : ""
height: avatar.height
width: height
visible: false
sourceSize.width: avatar.width
sourceSize.height: avatar.height
}
Icon {
id: fallbackIcon
objectName: "fallbackIcon"
source: visible ? img.avatarUrl : ""
visible: avatar.showAvatarPicture && (avatar.avatarUrl.indexOf("image://theme/") === 0)
color: "white"
anchors.centerIn: avatar
height: units.gu(3)
width: height
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/SIMCardImportPage.qml 0000644 0000156 0000165 00000020246 12674534204 030333 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Contacts 0.1
import Ubuntu.Components.ListItems 1.3 as ListItem
import MeeGo.QOfono 0.2
Page {
id: root
readonly property string exportFile: Contacts.tempFile("ubuntu_contacts_XXXXXX.vcf")
readonly property alias hasContacts: simCardContacts.hasContacts
property var targetModel: null
property var sims: []
signal importCompleted()
title: i18n.dtr("address-book-app", "SIM contacts")
function lockedSIMCount()
{
var count = 0
for(var i=0; i < sims.length; i++) {
if (sims[i].simMng.pinRequired !== OfonoSimManager.NoPin) {
count++
}
}
return count
}
Timer {
id: simUnlocking
interval: 2000
repeat: false
running: false
}
Column {
id: lockedSIMList
anchors {
left: parent.left
right: parent.right
}
Repeater {
id: lockedSIMRepeater
anchors {
left: parent.left
right: parent.right
}
model: sims.length
delegate: ListItem.Standard {
visible: sims[index].simMng.pinRequired !== OfonoSimManager.NoPin
onVisibleChanged: {
if (visible)
simUnlocking.stop()
else
simUnlocking.start()
}
text: i18n.dtr("address-book-app", "%1 is locked").arg(sims[index].title)
control: Button {
text: i18n.dtr("address-book-app", "Unlock")
onClicked: simCardContacts.unlockModem(sims[index].path)
}
}
}
}
ContactListView {
id: contactList
objectName: "contactListViewFromSimCard"
anchors {
left: parent.left
right: parent.right
top: lockedSIMList.bottom
bottom: parent.bottom
}
multiSelectionEnabled: true
multipleSelection: true
showSections: false
visible: !indicator.visible && !statusMessage.visible
showBusyIndicator: false
manager: "memory"
onSelectionCanceled: pageStack.removePages(root)
}
Label {
id: statusMessage
anchors.centerIn: parent
text: i18n.dtr("address-book-app", "No contacts found")
visible: ((contactList.count === 0) &&
(root.state === "") &&
!contactList.busy &&
(sims.length > root.lockedSIMCount()))
}
Column {
id: indicator
property alias title: activityLabel.text
anchors.centerIn: root
spacing: units.gu(2)
visible: false
ActivityIndicator {
id: activity
anchors.horizontalCenter: parent.horizontalCenter
running: indicator.visible
}
Label {
id: activityLabel
anchors.horizontalCenter: activity.horizontalCenter
}
}
SimCardContacts {
id: simCardContacts
property bool contactImported: false
Component.onCompleted: {
if (vcardFile != "" && !contactImported) {
contactImported = true
contactList.listModel.importContacts(vcardFile)
}
}
onVcardFileChanged: {
if ((vcardFile != "") && !contactImported) {
contactImported = true
contactList.listModel.importContacts(vcardFile)
}
}
onImportFail: {
console.error("Sim card import fail")
root.state = "error"
}
}
Connections {
target: contactList.listModel
onImportCompleted: {
contactList.startSelection()
root.state = ""
}
onExportCompleted: {
if ((error === ContactModel.ExportNoError) && targetModel) {
root.state = "saving"
targetModel.importContacts(url)
} else {
console.error("Failt to export selected contacts")
root.state = "error"
}
}
}
Connections {
target: root.targetModel
onImportCompleted: {
if (error === ContactModel.ImportNoError) {
Contacts.removeFile(root.exportFile)
root.state = ""
if (pageStack.removePages)
pageStack.removePages(root)
else
pageStack.pop()
root.importCompleted()
} else {
console.error("Fail to import contacts on device")
root.state = "error"
}
}
}
head.actions: [
Action {
text: (contactList.selectedItems.count === contactList.count) ?
i18n.dtr("address-book-app", "Unselect All") :
i18n.dtr("address-book-app", "Select All")
iconName: "select"
onTriggered: {
if (contactList.selectedItems.count === contactList.count) {
contactList.clearSelection()
} else {
contactList.selectAll()
}
}
visible: (contactList.count > 0)
},
Action {
text: i18n.dtr("address-book-app", "Import")
objectName: "confirmImport"
iconName: "tick"
enabled: (contactList.selectedItems.count > 0)
onTriggered: {
root.state = "importing"
var contacts = []
var items = contactList.selectedItems
for (var i=0, iMax=items.count; i < iMax; i++) {
contacts.push(items.get(i).model.contact)
}
contactList.listModel.exportContacts(root.exportFile,
[],
contacts)
}
}
]
states: [
State {
name: "loading"
when: (simCardContacts.busy || contactList.busy) &&
(sims.length > root.lockedSIMCount())
PropertyChanges {
target: indicator
title: i18n.dtr("address-book-app", "Loading...")
visible: true
}
},
State {
name: "unlocking"
when: !simCardContacts.busy && (contactList.count == 0) && (simUnlocking.running)
PropertyChanges {
target: indicator
title: i18n.dtr("address-book-app", "Unlocking...")
visible: true
}
},
State {
name: "importing"
PropertyChanges {
target: indicator
title: i18n.dtr("address-book-app", "Reading contacts from SIM...")
visible: true
}
},
State {
name: "saving"
PropertyChanges {
target: indicator
title: i18n.dtr("address-book-app", "Saving contacts on phone...")
visible: true
}
},
State {
name: "error"
PropertyChanges {
target: statusMessage
text: i18n.dtr("address-book-app", "Fail to read SIM card")
visible: true
}
}
]
Component.onDestruction: {
Contacts.removeFile(root.exportFile)
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/imagescalethread.h 0000644 0000156 0000165 00000002172 12674534204 030037 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#ifndef IMAGESCALETHREAD_H
#define IMAGESCALETHREAD_H
#include
#include
#include
#include
#include
class ImageScaleThread : public QRunnable
{
public:
ImageScaleThread(const QUrl &imageUrl, QObject *listener);
~ImageScaleThread();
QString id();
QString outputFile() const;
protected:
virtual void run();
private:
QUrl m_imageUrl;
QString m_id;
QPointer m_listener;
QTemporaryFile *m_tmpFile;
};
#endif
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactFetch.qml 0000644 0000156 0000165 00000006333 12674534204 027467 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
Item {
id: root
readonly property alias contact: connections.contact
readonly property alias contactId: connections.contactId
property alias model: connections.target
property bool running: false
property bool contactIsDirty: false
property string _pendingId: ""
property bool _ready: false
signal contactFetched(QtObject contact)
signal contactRemoved()
signal contactNotFound()
function fetchContact(contactId) {
if (root._ready) {
root._fetchContact(contactId)
} else {
root._pendingId = contactId
}
}
function _fetchContact(contactId) {
if (running) {
console.warn("Fetch already running!")
return
}
if (contact && !contactIsDirty && contact.contacId == contactId) {
contactFetched(contact)
} else if (model) {
contactIsDirty = true
running = true
if (model.manager === "memory") {
// memory backend emit contact fetched before return from "fetchContacts" we will use operation = "-2"
// to say that we are wainting for a operation from memory manager
connections.currentQueryId = -2
model.fetchContacts([contactId])
} else {
connections.currentQueryId = model.fetchContacts([contactId])
if (connections.currentQueryId === -1) {
running = false
}
}
}
}
onContactChanged: {
if (contact == null) {
contactRemoved()
}
}
Connections {
id: connections
property int currentQueryId: -1
property QtObject contact: null
property string contactId: contact ? contact.contactId : ""
ignoreUnknownSignals: true
onContactsFetched: {
// currentQueryId == -2 is used during a fetch using "memory" manager
if ((currentQueryId == -2) || (requestId == currentQueryId)) {
root.contactIsDirty = false
root.running = false
currentQueryId = -1
if (fetchedContacts.length > 0) {
contact = fetchedContacts[0]
root.contactFetched(fetchedContacts[0])
} else {
contactNotFound()
}
}
}
}
Component.onCompleted: {
root._ready = true
if (root._pendingId != "") {
root._fetchContact(root._pendingId)
root._pendingId = ""
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/MostCalledList.qml 0000644 0000156 0000165 00000006341 12674534204 030004 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
Column {
id: root
property alias parentView: scrollAnimation.target
readonly property alias count: callerRepeat.count
/* internal */
property int _nextCurrentIndex: -1
function makeItemVisible(item)
{
if (!item) {
return
}
var itemY = parent.y + root.y + item.y
var areaY = parentView.contentY
if (itemY < areaY) {
// move foward
scrollAnimation.to = itemY
} else if ((areaY + parentView.height) < (itemY + item.height)) {
// move backward
scrollAnimation.to = itemY + item.height
} else {
return
}
scrollAnimation.restart()
}
SmoothedAnimation {
id: scrollAnimation
property: "contentY"
velocity: parentView.highlightMoveVelocity
duration: parentView.highlightMoveDuration
}
SectionDelegate {
anchors {
left: parent.left
right: parent.right
margins: units.gu(2)
}
text: i18n.dtr("address-book-app", "Frequently called")
visible: (root.count > 0)
}
Repeater {
id: callerRepeat
model: MostCalledModel {
id: calledModel
onContactClicked: parentView.contactClicked(contact)
onAddContactClicked: parentView.addContactClicked(label)
onCurrentIndexChanged: {
if (currentIndex !== -1) {
parentView.currentIndex = -1
root._nextCurrentIndex = currentIndex
}
}
// WORKAROUND: The SDK header causes the contactY to move to a wrong postion
// calling the positionViewAtBeginning after the list created fix that
onLoaded: moveToBegining.restart()
}
}
Connections {
target: parentView
onCurrentIndexChanged: {
if (parentView.currentIndex !== -1) {
calledModel.currentIndex = -1
}
}
}
onHeightChanged: {
if (root._nextCurrentIndex != -1) {
heightChangedTimeout.restart()
}
}
Timer {
id: heightChangedTimeout
interval: 100
onTriggered: {
makeItemVisible(callerRepeat.itemAt(root._nextCurrentIndex))
root._nextCurrentIndex = -1
}
}
onVisibleChanged: {
// update the model every time that it became visible
// in fact calling update only reloads the model data if it has changed
if (visible) {
calledModel.model.update()
}
}
}
././@LongLink 0000000 0000000 0000000 00000000146 00000000000 011216 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactDetailPhoneNumberTypeModel.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactDetailPhoneNumberTypeModel.qm0000644 0000156 0000165 00000010561 12674534204 033450 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0 as QtContacts
ListModel {
id: typeModel
signal loaded()
function getTypeIndex(detail) {
var contexts = detail.contexts
var subTypes = detail.subTypes
if (contexts.indexOf(QtContacts.ContactDetail.ContextHome) > -1) {
if (subTypes && subTypes.indexOf(QtContacts.PhoneNumber.Mobile) > -1) {
return 2
} else {
return 0
}
} else if (contexts.indexOf(QtContacts.ContactDetail.ContextWork) > -1) {
if (subTypes && subTypes.indexOf(QtContacts.PhoneNumber.Mobile)> -1) {
return 3
} else {
return 1
}
} else if (contexts.indexOf(QtContacts.ContactDetail.ContextOther) > -1) {
return 4
} else {
// phone without context and voice type is other
if (subTypes && subTypes.indexOf(QtContacts.PhoneNumber.Voice) > -1) {
return 4
} else if (subTypes && subTypes.indexOf(QtContacts.PhoneNumber.Mobile) > -1) {
return 2
} else {
return 2 // Default value is "Mobile"
}
}
}
function compareList(listA, listB) {
if (!listA && !listB) {
return true
}
if (!listA || !listB) {
return false
}
if (listA.length !== listB.length) {
return false
}
for(var i=0; i < listA.length; i++) {
if (listA[i] !== listB[i]) {
return false
}
}
return true
}
function updateDetail(detail, index) {
var modelData = get(index)
if (!modelData) {
return false
}
var newSubTypes = []
var newContext = []
if (modelData.context !== -1) {
newContext.push(modelData.context)
}
newSubTypes.push(modelData.subType)
var changed = false
if (!compareList(newContext, detail.contexts)) {
detail.contexts = newContext
changed = true
}
if (!compareList(newSubTypes, detail.subTypes)) {
detail.subTypes = newSubTypes
changed = true
}
return changed
}
Component.onCompleted: {
append({"value": "Home",
// TRANSLATORS: This refers to home landline phone label
"label": i18n.dtr("address-book-app", "Home"), "icon": null,
"context": QtContacts.ContactDetail.ContextHome, "subType": QtContacts.PhoneNumber.Voice })
append({"value": "Work",
// TRANSLATORS: This refers to landline work phone label
"label": i18n.dtr("address-book-app", "Work"), "icon": null,
"context": QtContacts.ContactDetail.ContextWork, "subType": QtContacts.PhoneNumber.Voice })
append({"value": "Mobile",
// TRANSLATORS: This refers to mobile/cellphone phone label
"label": i18n.dtr("address-book-app", "Mobile"), "icon": null,
"context": -1, "subType": QtContacts.PhoneNumber.Mobile })
append({"value": "Mobile-Work",
// TRANSLATORS: This refers to mobile/cellphone work phone label
"label": i18n.dtr("address-book-app", "Work Mobile"), "icon": null,
"context": QtContacts.ContactDetail.ContextWork, "subType": QtContacts.PhoneNumber.Mobile })
append({"value": "Other",
// TRANSLATORS: This refers to any other phone label
"label": i18n.dtr("address-book-app", "Other"), "icon": null,
"context": QtContacts.ContactDetail.ContextOther, "subType": QtContacts.PhoneNumber.Voice })
loaded()
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactListView.qml 0000644 0000156 0000165 00000050646 12674534204 030212 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3 as ListItem
import Ubuntu.Components.Popups 1.3
import Ubuntu.Contacts 0.1 as Contacts
import Buteo 0.1
/*!
\qmltype ContactListView
\inqmlmodule Ubuntu.Contacts 0.1
\ingroup ubuntu
\brief The ContactListView provides the contact list view integrated with the Favorite List View
The ContactListView is based on ContactSimpleListView and provide a easy way to show the contact
list view with all default visuals defined by Ubuntu system.
Example:
\qml
import Ubuntu.Contacts 0.1
ContactListView {
anchors.fill: parent
onContactClicked: console.debug("Contact ID:" + contact.contactId)
}
\endqml
*/
FocusScope {
id: root
readonly property alias view: view
readonly property alias count: view.count
property var header: []
/*!
\qmlproperty string contactStringFilter
This property holds a string that will be used to filter contacts on the list
By default this is set to empty
*/
property alias filterTerm: contactsModel.filterTerm
/*!
\qmlproperty Filter filter
This property holds the filter instance used by the contact model.
\sa Filter
*/
property alias filter: contactsModel.externalFilter
/*!
\qmlproperty bool showAvatar
This property holds if the contact avatar will appear on the list or not.
By default this is set to true.
*/
property alias showAvatar: view.showAvatar
/*!
\qmlproperty list sortOrders
This property holds a list of sort orders used by the contacts model.
\sa SortOrder
*/
property alias sortOrders: view.sortOrders
/*!
\qmlproperty FetchHint fetchHint
This property holds the fetch hint instance used by the contact model.
\sa FetchHint
*/
property alias fetchHint: view.fetchHint
/*!
\qmlproperty bool multiSelectionEnabled
This property holds if the multi selection mode is enabled or not
By default this is set to false
*/
property alias multiSelectionEnabled: view.multiSelectionEnabled
/*!
\qmlproperty string defaultAvatarImage
This property holds the default image url to be used when the current contact does
not contains a photo
*/
property alias defaultAvatarImageUrl: view.defaultAvatarImageUrl
/*!
\qmlproperty bool loading
This property holds when the model still loading new contacts
*/
readonly property alias loading: view.loading
/*!
\qmlproperty int currentIndex
This property holds the current active item index
*/
property alias currentIndex: view.currentIndex
/*!
\qmlproperty bool showSections
This property holds if the listview will show or not the section headers
By default this is set to true
*/
property alias showSections: view.showSections
/*!
\qmlproperty string manager
This property holds the manager uri of the contact backend engine.
By default this is set to "galera"
*/
property alias manager: view.manager
/*!
\qmlproperty bool fastScrolling
This property holds if the listview is in fast scroll mode or not
*/
property alias fastScrolling: fastScroll.fastScrolling
/*!
\qmlproperty Action leftSideAction
This property holds the available actions when swipe the contact item from left to right
*/
property alias leftSideAction: view.leftSideAction
/*!
\qmlproperty list rightSideActions
This property holds the available actions when swipe the contact item from right to left
*/
property alias rightSideActions: view.rightSideActions
/*!
\qmlproperty model selectedItems
This property holds the list of selected items
*/
readonly property alias selectedItems: view.selectedItems
/*!
\qmlproperty bool multipleSelection
This property holds if the selection will accept multiple items or single items
*/
property alias multipleSelection: view.multipleSelection
/*!
\qmlproperty model listModel
This property holds the model providing data for the list.
*/
property alias listModel: view.listModel
/*!
\qmlproperty Component listDelegate
The delegate provides a template defining each item instantiated by the view.
*/
property alias listDelegate: view.listDelegate
/*!
\qmlproperty bool autoUpdate
This property indicates whether or not the contact model should be updated automatically, default value is true.
*/
property alias autoUpdate: contactsModel.autoUpdate
/*!
\qmlproperty bool autoHideKeyboard
This property indicates if the OSK should disapear when the list starts to flick.
*/
property bool autoHideKeyboard: true
/*!
\qmlproperty bool isInSelectionMode
This property holds a list with the index of selected items
*/
readonly property alias isInSelectionMode: view.isInSelectionMode
/*!
\qmlproperty bool showImportOptions
This property holds if the import options should be visible on the list
*/
property bool showImportOptions: false
/*!
\qmlproperty bool showAddNewButton
This property holds if the add new button should be visible or not
*/
property bool showAddNewButton: false
/*!
\qmlproperty bool showNewContact
This property holds if a draft new contact should be visible or not
*/
property bool showNewContact: false
/*!
\qmlproperty bool syncing
This property holds if the list is running a sync with online accounts or not
*/
readonly property bool syncing: buteoSync.syncing || Contacts.Contacts.updateIsRunning
/*!
\qmlproperty bool syncEnabled
This property holds if there is online account to sync or not
*/
// we are using 'buteoSync.visibleSyncProfiles because' it is a property
// and will re-check if the property changes.
// Using only '(buteoSync.syncProfilesByCategory("contacts").length > 0)'
// the value will be checked only on app startup
readonly property bool syncEnabled: (buteoSync.profilesCount > 0) &&
(buteoSync.syncProfilesByCategory("contacts").length > 0)
property alias headerItem: view.headerItem
/*!
\qmlproperty bool busy
This property holds if the list is busy or not
*/
property alias busy: indicator.isBusy
/*!
\qmlproperty bool showBusyIndicator
This property holds if the busy indicator should became visible
*/
property bool showBusyIndicator: true
/*!
\qmlproperty real verticalVelocity
This property holds the vertical velocity of the list
*/
readonly property real verticalVelocity: view.verticalVelocity
property alias highlightSelected: view.highlightSelected
property var _busyDialog: null
//WORKAROUND: SDK does not allow us to disable focus for items due bug: #1514822
//because of that we need this
property bool _allowFocus: true
/*!
This handler is called when the selection mode is finished without be canceled
*/
signal selectionDone(var items)
/*!
This handler is called when the selection mode is canceled
*/
signal selectionCanceled()
/*!
This handler is called when any error occurs in the contact model
*/
signal error(string message)
/*!
This handler is called when a unknown contact is clicked, the label contains the phone number
*/
signal addContactClicked(string label)
/*!
This handler is called when the contact delegate disapear (height === 0) caused by the function call makeDisappear
*/
signal contactDisappeared(QtObject contact)
/*!
This handler is called when the button add new contact is clicked
*/
signal addNewContactClicked()
/*!
This handler is called when any contact in the list receives a click
*/
signal contactClicked(QtObject contact)
function startSelection()
{
view.startSelection()
}
function isSelected(item)
{
return view.isSelected(item)
}
function selectItem(item)
{
return view.selectItem(item)
}
function deselectItem(item)
{
return view.deselectItem(item)
}
function endSelection()
{
view.endSelection()
}
function cancelSelection()
{
view.cancelSelection()
}
function clearSelection()
{
view.clearSelection()
}
function selectAll()
{
view.selectAll()
}
function returnToBounds()
{
view.returnToBounds()
}
function positionViewAtContact(contact)
{
view.positionViewAtContact(contact)
}
function positionViewAtBeginning()
{
moveToBegining.restart()
}
function changeFilter(newFilter)
{
contactsModel.changeFilter(newFilter)
}
function reset()
{
if (view.favouritesIsSelected) {
showAllContacts()
} else {
positionViewAtBeginning()
}
}
function showFavoritesContacts()
{
//WORKAROUND: clear the model before start populate it with the new contacts
//otherwise the model will wait for all contacts before show any new contact
if (!view.favouritesIsSelected) {
root.changeFilter(root.filter)
view.favouritesIsSelected = true
}
}
function showAllContacts()
{
if (view.favouritesIsSelected) {
root.changeFilter(root.filter)
view.favouritesIsSelected = false
}
}
/*!
Causes the list to update
\l autoUpdate
*/
function update()
{
contactsModel.update()
}
/*!
Start an online account sync opration
*/
function sync()
{
buteoSync.startSyncByCategory("contacts")
}
focus: true
ContactSimpleListView {
id: view
property bool showFavourites: true
property alias favouritesIsSelected: contactsModel.onlyFavorites
property bool contactsLoaded: false
function getSectionText(index) {
var tag = listModel.contacts[index].tag.tag
if (tag == "")
return "#"
else
return tag
}
// if the favorite header became invisible we should move back to all contacts.
onShowFavouritesChanged: {
if (!showFavourites && view.favouritesIsSelected) {
root.changeFilter(root.filter)
view.favouritesIsSelected = false
}
}
onFlickStarted: {
if (autoHideKeyboard)
forceActiveFocus()
}
anchors.fill: parent
// WORKAROUND: The SDK header causes the contactY to move to a wrong postion
// calling the positionViewAtBeginning after the list created fix that
Timer {
id: moveToBegining
interval: 100
running: false
repeat: false
onTriggered: view.positionViewAtBeginning()
}
header: Column {
anchors {
left: parent.left
right: parent.right
}
// top margin
Item {
anchors {
left: parent.left
right: parent.right
}
height: units.gu(0.5)
}
Binding {
target: view
property: 'currentIndex'
value: -1
when: root.showNewContact
}
// AddNewButton
ContactListButtonDelegate {
anchors {
left: parent.left
right: parent.right
margins: units.gu(1)
}
objectName: "addNewButton"
iconSource: "image://theme/add"
// TRANSLATORS: this refers to a new contact
labelText: i18n.dtr("address-book-app", "+ Create New")
onClicked: root.addNewContactClicked()
visible: root.showAddNewButton
}
ContactDelegate {
property var contact: Contact {
Name {
firstName: i18n.tr("New contact")
}
Avatar {
imageUrl: "image://theme/contact"
}
}
selected: pageStack.hasKeyboard
visible: root.showNewContact
height: root.showNewContact ? defaultHeight : 0
onHeightChanged: {
if (visible)
view.positionViewAtBeginning()
}
Behavior on height {UbuntuNumberAnimation {}}
}
Column {
id: importFromButtons
objectName: "importFromButtons"
readonly property bool isSearching: (root.filterTerm && root.filterTerm !== "")
anchors {
left: parent.left
right: parent.right
margins: units.gu(1)
}
height: visible ? childrenRect.height : 0
visible: root.showImportOptions &&
!indicator.visible &&
(root.count === 0) &&
!view.favouritesIsSelected &&
!isSearching
// avoid show the button while the list still loading contacts
Behavior on visible {
SequentialAnimation {
PauseAnimation {
duration: !importFromButtons.visible ? 500 : 0
}
PropertyAction {
target: importFromButtons
property: "visible"
}
}
}
// Import from google
ContactListButtonDelegate {
id: importFromGoogleButton
objectName: "%1.importFromOnlineAccountButton".arg(root.objectName)
visible: (onlineAccountHelper.status === Loader.Ready)
expandIcon: true
iconSource: "image://theme/google"
labelText: i18n.dtr("address-book-app", "Import contacts from Google")
onClicked: onlineAccountHelper.item.setupExec()
}
// Import from sim card
ContactListButtonDelegate {
id: importFromSimCard
objectName: "%1.importFromSimCardButton".arg(root.objectName)
expandIcon: true
iconSource: "image://theme/import"
labelText: i18n.dtr("address-book-app", "Import contacts from SIM card")
// Does not show the button if the list is not in a pageStack
visible: (typeof(pageStack) !== "undefined") &&
((simList.sims.length > 0) && (simList.present.length > 0))
onClicked: {
if (pageStack.addPageToNextColumn)
pageStack.addPageToNextColumn(pageStack.primaryPage, Qt.resolvedUrl("SIMCardImportPage.qml"),
{"objectName": "simCardImportPage",
"targetModel": view.listModel,
"sims": simList.sims})
else
pageStack.push(Qt.resolvedUrl("SIMCardImportPage.qml"),
{"objectName": "simCardImportPage",
"targetModel": view.listModel,
"sims": simList.sims})
}
}
}
MostCalledList {
id: mostCalledView
anchors {
left: parent.left
right: parent.right
margins: units.gu(1)
}
parentView: view
visible: view.favouritesIsSelected
height: visible && (count > 0) ? childrenRect.height : 0
}
}
onError: root.error(message)
onContactClicked: root.contactClicked(contact)
onSelectionDone: root.selectionDone(items)
onSelectionCanceled: root.selectionCanceled()
onContactDisappeared: root.contactDisappeared(contact)
onCountChanged: {
if (view.count > 0) {
view.contactsLoaded = true
}
}
clip: true
listModel: ContactListModel {
id: contactsModel
manager: root.manager
sortOrders: root.sortOrders
fetchHint: root.fetchHint
}
}
Column {
id: indicator
readonly property bool isBusy: ((view.loading && !view.contactsLoaded) ||
(root.syncing && (view.count === 0)) ||
((onlineAccountHelper.status == Loader.Ready) &&
(onlineAccountHelper.item.running)))
anchors.centerIn: view
spacing: units.gu(2)
visible: root.showBusyIndicator && isBusy
ActivityIndicator {
id: activity
anchors.horizontalCenter: parent.horizontalCenter
running: indicator.visible
}
Label {
anchors.horizontalCenter: activity.horizontalCenter
text: root.syncing ? i18n.dtr("address-book-app", "Syncing...") : i18n.dtr("address-book-app", "Loading...")
}
}
FastScroll {
id: fastScroll
listView: view
// only enable FastScroll if the we have more than 2 pages of content and sections is enabled
enabled: showSections &&
(view.contentHeight > (view.height * 2)) &&
(view.height >= minimumHeight) &&
(((view.contentY - view.originY) - view.headerItem.height) >= 0)// hearder already invisble
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
}
}
ButeoSync {
id: buteoSync
}
SIMList {
id: simList
}
Loader {
id: onlineAccountHelper
objectName: "onlineAccountHelper"
readonly property bool isSearching: (root.filterTerm && root.filterTerm !== "")
// if running on test mode does not load online account modules
property string sourceFile: (typeof(runningOnTestMode) !== "undefined") ?
Qt.resolvedUrl("OnlineAccountsDummy.qml") :
Qt.resolvedUrl("OnlineAccountsHelper.qml")
anchors.fill: parent
asynchronous: true
source: root.showImportOptions &&
(root.count === 0) &&
!view.favouritesIsSelected &&
!isSearching ? sourceFile : ""
}
Keys.onUpPressed: {
//WORKAROUND: SDK does not allow us to disable focus for items due bug: #1514822
//because of that we need this
if (view.currentIndex == 0) {
pageStack._nextItemInFocusChain(view, false)
} else {
view.currentIndex -= 1
}
}
Keys.onDownPressed: {
//WORKAROUND: SDK does not allow us to disable focus for items due bug: #1514822
//because of that we need this
if (view.currentIndex == (view.count - 1)) {
//DO nothing
} else {
view.currentIndex += 1
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/Triangle.qml 0000644 0000156 0000165 00000002742 12674534204 026667 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2016 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
Canvas {
id: triangle
antialiasing: true
property color color: "#ffffff"
property int lineWidth: 3
property bool fill: false
onLineWidthChanged:requestPaint();
onFillChanged:requestPaint();
onPaint: {
var ctx = getContext("2d");
ctx.save();
ctx.clearRect(0,0,triangle.width, triangle.height);
ctx.strokeStyle = triangle.color;
ctx.lineWidth = triangle.lineWidth
ctx.fillStyle = triangle.color
ctx.globalAlpha = 1.0
ctx.lineJoin = "round";
ctx.beginPath();
ctx.translate(0, 0);
ctx.lineTo(0, triangle.height);
ctx.lineTo(triangle.width, 0.5 * triangle.height);
ctx.lineTo(0, 0);
if (triangle.fill)
ctx.fill();
if (triangle.stroke)
ctx.stroke();
ctx.restore();
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/Contacts/ContactListModel.qml 0000644 0000156 0000165 00000012513 12674534204 030327 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Contacts 0.1
ContactModel {
id: root
property var externalFilter: null
property string filterTerm: ""
property bool onlyFavorites: false
property var view: null
/* internal */
property bool _clearModel: false
property list _extraFilters
property QtObject _timeout
function changeFilter(newFilter)
{
if (root.contacts.length > 0) {
root._clearModel = true
}
root.externalFilter = newFilter
}
filter: {
if (root._clearModel) {
return invalidFilter
} else if (contactsFilter.active) {
return contactsFilter
} else {
return null
}
}
_extraFilters: [
InvalidFilter {
id: invalidFilter
},
DetailFilter {
id: favouritesFilter
detail: ContactDetail.Favorite
field: Favorite.Favorite
value: true
matchFlags: DetailFilter.MatchExactly
},
UnionFilter {
id: contactTermFilter
property string value: ""
property var phoneNumberFilter: DetailFilter {
detail: ContactDetail.PhoneNumber
field: PhoneNumber.Number
value: contactTermFilter.value
matchFlags: (DetailFilter.MatchPhoneNumber | DetailFilter.MatchContains)
}
filters: [
DetailFilter {
id: nameFilter
detail: (root.manager === "galera" ? ContactDetail.ExtendedDetail : ContactDetail.DisplayLabel)
field: (root.manager === "galera" ? ExtendedDetail.Data : DisplayLabel.Label)
value: (root.manager === "galera" ? Contacts.normalized(contactTermFilter.value) : contactTermFilter.value)
matchFlags: DetailFilter.MatchContains
}
]
onValueChanged: {
var containsLetter = Contacts.containsLetters(value)
if (containsLetter && (filters.length > 1)) {
filters = [nameFilter]
} else if (!containsLetter) {
filters = [nameFilter, phoneNumberFilter]
}
}
},
IntersectionFilter {
id: contactsFilter
// avoid runtime warning "depends on non-NOTIFYable properties"
readonly property alias filtersProxy: contactsFilter.filters
property bool active: {
var filters_ = []
if (contactTermFilter.value.length > 0) {
filters_.push(contactTermFilter)
} else if (root.onlyFavorites) {
filters_.push(favouritesFilter)
}
if (root.externalFilter) {
filters_.push(root.externalFilter)
}
// check if the filter has changed
var oldFilters = filtersProxy
if (oldFilters.length !== filters_.length) {
contactsFilter.filters = filters_
} else {
for(var i=0; i < oldFilters.length; i++) {
if (filters_.indexOf(oldFilters[i]) === -1) {
contactsFilter.filters = filters_
}
}
}
return (filters_.length > 0)
}
}
]
_timeout: Timer {
id: contactSearchTimeout
running: false
repeat: false
interval: 300
onTriggered: {
if (root.view) {
view.positionViewAtBeginning()
}
root.changeFilter(root.externalFilter)
contactTermFilter.value = root.filterTerm
// manually update if autoUpdate is disabled
if (!root.autoUpdate) {
root.update()
}
}
}
onFilterTermChanged: contactSearchTimeout.restart()
onErrorChanged: {
if (error) {
console.error("Contact List error:" + error)
}
}
onContactsChanged: {
//WORKAROUND: clear the model before start populate it with the new contacts
//otherwise the model will wait for all contacts before show any new contact
//after all contacts get removed we can populate the model again, this will show
//new contacts as soon as it arrives in the model
if (root._clearModel && contacts.length === 0) {
root._clearModel = false
// do a new update if autoUpdate is false
if (!root.autoUpdate) {
root.update()
}
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ 0000755 0000156 0000165 00000000000 12674534512 025026 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ 0000755 0000156 0000165 00000000000 12674534512 027570 5 ustar pbuser pbgroup 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000161 00000000000 011213 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailWithTypeEditor.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailWithTy0000644 0000156 0000165 00000011210 12674534204 033373 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Contacts 0.1
import Ubuntu.AddressBook.Base 0.1
ContactDetailBase {
id: root
property bool active: false
property double itemHeight: units.gu(3)
property alias types: detailTypeSelector.values
property int fieldType: -1
property alias selectedTypeIndex: detailTypeSelector.currentIndex
property variant placeholderTexts: []
property int inputMethodHints: Qt.ImhNone
property bool usePhoneFormat: false
readonly property alias repeater: fieldRepeater
function selectType(type) {
detailTypeSelector.selectItem(type)
}
function isEmpty() {
for (var i=0; i < fieldValues.children.length; i++) {
var input = fieldValues.children[i]
if (input.text && (input.text !== "")) {
return false
}
}
return true
}
function save() {
var detailchanged = false
// save field values
for (var i=0; i < fieldValues.children.length; i++) {
var input = fieldValues.children[i]
if (input.detail && (input.field >= 0)) {
if (input.text !== "") {
var originalValue = input.detail.value(input.field)
originalValue = originalValue ? String(originalValue) : ""
if (originalValue !== input.text) {
root.detail.setValue(input.field, input.text)
detailchanged = true
}
}
}
}
return detailchanged
}
// disable listview mouse area
enabled: root.detail ? !root.detail.readOnly : false
implicitHeight: detailTypeSelector.height + fieldValues.height + units.gu(2)
opacity: enabled ? 1.0 : 0.5
Column {
id: fieldValues
anchors {
left: parent.left
leftMargin: units.gu(2)
right: parent.right
rightMargin: units.gu(2)
top: parent.top
topMargin: units.gu(1)
}
height: childrenRect.height
Repeater {
id: fieldRepeater
model: root.fields
TextInputDetail {
id: detail
objectName: root.detail ? detailToString(root.detail.type, modelData) + "_" + root.index : ""
detail: root.detail
field: modelData
placeholderText: root.placeholderTexts[index]
inputMethodHints: root.inputMethodHints
autoFormat: root.usePhoneFormat
onActiveFocusChanged: root.active = activeFocus
anchors {
left: parent.left
right: parent.right
}
height: root.active ? root.itemHeight + units.gu(1) : root.itemHeight
onRemoveClicked: root.contact.removeDetail(root.detail)
}
}
Keys.onReleased: {
if ((event.key == Qt.Key_Right) && (event.modifiers & Qt.ShiftModifier)) {
detailTypeSelector.moveNext()
event.accepted = true
} else if ((event.key == Qt.Key_Left) && (event.modifiers & Qt.ShiftModifier)) {
detailTypeSelector.movePrevious()
event.accepted = true
}
}
}
ValueSelector {
id: detailTypeSelector
objectName: detail ? "type_" + detailToString(detail.type, -1) + "_" + index : ""
anchors {
left: fieldValues.left
right: fieldValues.right
top: fieldValues.bottom
}
readOnly: root.detail ? root.detail.readOnly : false
visible: (currentIndex != -1)
active: root.active
height: visible ? (root.active ? units.gu(4) : units.gu(3)) : 0
onExpandedChanged: {
// Make sure that the inputfield get focus when clicking on type selector
if (expanded) {
root.forceActiveFocus()
}
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/AvatarImport.qml 0000644 0000156 0000165 00000007245 12674534204 032722 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
import Ubuntu.Content 1.3
Item {
id: root
property var importDialog: null
signal avatarReceived(string avatarUrl)
function requestNewAvatar()
{
if (!root.importDialog) {
root.importDialog = PopupUtils.open(contentHubDialog, null)
} else {
console.warn("Import dialog already running")
}
}
Component {
id: contentHubDialog
PopupBase {
id: dialogue
property alias activeTransfer: signalConnections.target
parent: QuickUtils.rootItem(this)
focus: true
Rectangle {
anchors.fill: parent
ContentTransferHint {
anchors.fill: parent
activeTransfer: dialogue.activeTransfer
}
ContentPeerPicker {
id: peerPicker
anchors.fill: parent
contentType: ContentType.Pictures
handler: ContentHandler.Source
onPeerSelected: {
peer.selectionType = ContentTransfer.Single
dialogue.activeTransfer = peer.request()
}
onCancelPressed: {
PopupUtils.close(root.importDialog)
}
}
}
Connections {
id: signalConnections
onStateChanged: {
var done = ((dialogue.activeTransfer.state === ContentTransfer.Charged) ||
(dialogue.activeTransfer.state === ContentTransfer.Aborted))
if (dialogue.activeTransfer.state === ContentTransfer.Charged) {
if (dialogue.activeTransfer.items.length > 0) {
root.avatarReceived(dialogue.activeTransfer.items[0].url)
}
}
if (done) {
acceptTimer.restart()
}
}
}
// WORKAROUND: Work around for application becoming insensitive to touch events
// if the dialog is dismissed while the application is inactive.
// Just listening for changes to Qt.application.active doesn't appear
// to be enough to resolve this, so it seems that something else needs
// to be happening first. As such there's a potential for a race
// condition here, although as yet no problem has been encountered.
Timer {
id: acceptTimer
interval: 100
repeat: true
running: false
onTriggered: {
if(Qt.application.state === Qt.ApplicationActive) {
PopupUtils.close(root.importDialog)
}
}
}
Component.onDestruction: root.importDialog = null
}
}
}
././@LongLink 0000000 0000000 0000000 00000000147 00000000000 011217 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactEditorPage.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactEditorPage.q0000644 0000156 0000165 00000042214 12674534204 033312 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3
import Ubuntu.Components.Popups 1.3
import Ubuntu.Contacts 0.1 as ContactsUI
import Ubuntu.AddressBook.Base 0.1
Page {
id: contactEditor
property QtObject contact: null
property QtObject model: null
property QtObject activeItem: null
property string initialFocusSection: ""
property var newDetails: []
property list leadingActions
property alias headerActions: trailingBar.actions
readonly property bool isNewContact: contact && (contact.contactId === "qtcontacts:::")
readonly property bool isContactValid: !avatarEditor.busy && (!nameEditor.isEmpty() || !phonesEditor.isEmpty())
readonly property alias editorFlickable: scrollArea
signal contactSaved(var contact);
signal canceled()
function cancel() {
for (var i = 0; i < contactEditor.newDetails.length; ++i) {
contactEditor.contact.removeDetail(contactEditor.newDetails[i])
}
contactEditor.newDetails = []
for(var i = 0; i < contents.children.length; ++i) {
var field = contents.children[i]
if (field.cancel) {
field.cancel()
}
}
if (pageStack && pageStack.removePages) {
pageStack.removePages(contactEditor)
} else if (pageStack) {
pageStack.pop()
}
contactEditor.canceled()
}
function save() {
var changed = false
for(var i = 0; i < contents.children.length; ++i) {
var field = contents.children[i]
if (field.save) {
if (field.save()) {
changed = true
}
}
}
// new contact and there is only two details (name, avatar)
// name and avatar, are not removable details, because of that the contact will have at least 2 details
if (isNewContact &&
(contact.contactDetails.length === 2)) {
// if name is empty this means that the contact is empty
var nameDetail = contact.detail(ContactDetail.Name)
if (nameDetail &&
(nameDetail.firstName && nameDetail.firstName != "") ||
(nameDetail.lastName && nameDetail.lastName != "")) {
// save contact
} else {
changed = false
}
}
if (changed) {
// backend error will be handled by the root page (contact list)
var newContact = (contact.model == null)
contactEditor.model.saveContact(contact)
if (newContact) {
contactEditor.contactSaved(contact)
}
}
if (pageStack.removePages) {
pageStack.removePages(contactEditor)
} else {
pageStack.pop()
}
}
function idleMakeMeVisible(item) {
if (!enabled || !item) {
return
}
activeItem = item
timerMakemakeMeVisible.restart()
}
function makeMeVisible(item) {
if (!item)
return
var position = item.mapToItem(editEditor, item.x, item.y);
// check if the item is already visible
var bottomY = scrollArea.contentY + scrollArea.height
var itemBottom = position.y + (item.height * 3) // extra margin
if (position.y >= scrollArea.contentY && itemBottom <= bottomY) {
Qt.inputMethod.show()
return;
}
// if it is not, try to scroll and make it visible
var targetY = itemBottom - scrollArea.height
if (targetY >= 0 && position.y) {
scrollArea.contentY = targetY;
} else if (position.y < scrollArea.contentY) {
// if it is hidden at the top, also show it
scrollArea.contentY = position.y;
}
scrollArea.returnToBounds()
Qt.inputMethod.show()
}
function ready()
{
enabled = true
switch (contactEditor.initialFocusSection)
{
case "phones":
contactEditor.focusToLastPhoneField()
break;
case "name":
default:
nameEditor.fieldDelegates[0].forceActiveFocus()
break;
}
}
function focusToLastPhoneField()
{
var lastPhoneField = phonesEditor.detailDelegates[phonesEditor.detailDelegates.length - 2].item
lastPhoneField.forceActiveFocus()
}
function focusToFirstEntry(field)
{
var itemToFocus = field
if (field.repeater)
itemToFocus = field.repeater.itemAt(0)
if (itemToFocus) {
root.idleMakeMeVisible(itemToFocus)
itemToFocus.forceActiveFocus()
}
}
Timer {
id: timerMakemakeMeVisible
interval: 100
repeat: false
running: false
onTriggered: root.makeMeVisible(root.activeItem)
}
header: PageHeader {
id: pageHeader
title: isNewContact ? i18n.dtr("address-book-app", "New contact") : i18n.dtr("address-book-app", "Edit")
trailingActionBar {
id: trailingBar
}
leadingActionBar {
id: leadingBar
actions: contactEditor.leadingActions
}
}
enabled: false
flickable: null
Timer {
id: focusTimer
interval: 1000
running: false
repeat: false
onTriggered: contactEditor.ready()
}
Flickable {
id: scrollArea
objectName: "scrollArea"
// this is necessary to avoid the page to appear bellow the header
clip: true
flickableDirection: Flickable.VerticalFlick
anchors{
left: parent.left
top: parent.top
topMargin: pageHeader.height
right: parent.right
bottom: keyboardRectangle.top
}
contentHeight: contents.height + units.gu(2)
contentWidth: parent.width
Column {
id: contents
anchors {
top: parent.top
topMargin: units.gu(2)
left: parent.left
right: parent.right
}
height: childrenRect.height
Row {
id: editEditor
function save()
{
var avatarSave = avatarEditor.save()
var nameSave = nameEditor.save();
return (nameSave || avatarSave);
}
function isEmpty()
{
return (avatarEditor.isEmpty() && nameEditor.isEmpty())
}
anchors {
left: parent.left
leftMargin: units.gu(2)
right: parent.right
}
height: Math.max(avatarEditor.height, nameEditor.height) - units.gu(2)
ContactDetailAvatarEditor {
id: avatarEditor
contact: contactEditor.contact
height: implicitHeight
width: implicitWidth
anchors.verticalCenter: editEditor.verticalCenter
}
ContactDetailNameEditor {
id: nameEditor
width: parent.width - avatarEditor.width
height: nameEditor.implicitHeight + units.gu(3)
contact: contactEditor.contact
}
}
ContactDetailPhoneNumbersEditor {
id: phonesEditor
objectName: "phones"
contact: contactEditor.contact
anchors {
left: parent.left
right: parent.right
}
height: implicitHeight
onNewFieldAdded: root.focusToFirstEntry(field)
}
ContactDetailEmailsEditor {
id: emailsEditor
objectName: "emails"
contact: contactEditor.contact
anchors {
left: parent.left
right: parent.right
}
height: implicitHeight
onNewFieldAdded: root.focusToFirstEntry(field)
}
ContactDetailOnlineAccountsEditor {
id: accountsEditor
objectName: "ims"
contact: contactEditor.contact
anchors {
left: parent.left
right: parent.right
}
height: implicitHeight
onNewFieldAdded: root.focusToFirstEntry(field)
}
ContactDetailAddressesEditor {
id: addressesEditor
objectName: "addresses"
contact: contactEditor.contact
anchors {
left: parent.left
right: parent.right
}
height: implicitHeight
onNewFieldAdded: root.focusToFirstEntry(field)
}
ContactDetailOrganizationsEditor {
id: organizationsEditor
objectName: "professionalDetails"
contact: contactEditor.contact
anchors {
left: parent.left
right: parent.right
}
height: implicitHeight
onNewFieldAdded: root.focusToFirstEntry(field)
}
ContactDetailSyncTargetEditor {
id: syncTargetEditor
active: contactEditor.active
contact: contactEditor.contact
anchors {
left: parent.left
right: parent.right
}
onChanged: {
if (contactEditor.enabled &&
!contactEditor.isNewContact &&
syncTargetEditor.contactIsReadOnly(contactEditor.contact)) {
PopupUtils.open(alertMessage)
}
}
}
ThinDivider {}
Item {
anchors {
left: parent.left
right: parent.right
}
height: units.gu(2)
}
ComboButtonAddField {
id: addNewFieldButton
objectName: "addNewFieldButton"
contact: contactEditor.contact
text: i18n.dtr("address-book-app", "Add field")
anchors {
left: parent.left
right: parent.right
margins: units.gu(2)
}
height: implicitHeight
activeFocusOnPress: false
onHeightChanged: {
if (expanded && (height === expandedHeight) && !scrollArea.atYEnd) {
moveToBottom.start()
}
}
UbuntuNumberAnimation {
id: moveToBottom
target: scrollArea
property: "contentY"
to: scrollArea.contentHeight - scrollArea.height
onStopped: {
scrollArea.returnToBounds()
addNewFieldButton.forceActiveFocus()
}
}
onFieldSelected: {
if (qmlTypeName) {
var newDetail = Qt.createQmlObject("import QtContacts 5.0; " + qmlTypeName + "{}", contactEditor)
if (newDetail) {
var newDetailsCopy = contactEditor.newDetails
newDetailsCopy.push(newDetail)
contactEditor.newDetails = newDetailsCopy
contactEditor.contact.addDetail(newDetail)
}
}
}
}
Item {
anchors {
left: parent.left
right: parent.right
}
height: units.gu(2)
}
Button {
id: deleteButton
text: i18n.dtr("address-book-app", "Delete")
visible: !contactEditor.isNewContact
color: UbuntuColors.red
anchors {
left: parent.left
right: parent.right
margins: units.gu(2)
}
action: Action {
enabled: contactEditor.active && deleteButton.visible
shortcut: "Ctrl+Delete"
onTriggered: {
var dialog = PopupUtils.open(removeContactDialog, null)
dialog.contacts = [contactEditor.contact]
}
}
}
Item {
anchors {
left: parent.left
right: parent.right
}
height: units.gu(2)
}
}
}
KeyboardRectangle {
id: keyboardRectangle
onHeightChanged: {
if (addNewFieldButton.expanded) {
scrollArea.contentY = scrollArea.contentHeight - scrollArea.height
scrollArea.returnToBounds()
} else if (activeItem) {
idleMakeMeVisible(activeItem)
}
}
}
onActiveChanged: {
if (!active) {
return
}
if (contactEditor.initialFocusSection != "") {
focusTimer.restart()
} else {
contactEditor.ready()
}
}
Component {
id: alertMessage
Dialog {
id: aletMessageDialog
title: i18n.dtr("address-book-app", "Contact Editor")
text: {
if (ContactsUI.Contacts.updateIsRunning) {
return i18n.dtr("address-book-app",
"Your %1 contact sync account needs to be upgraded.\nWait until the upgrade is complete to edit contacts.")
.arg(contactEditor.contact.syncTarget.syncTarget)
}
if (Qt.application.name === "AddressBookApp") {
i18n.dtr("address-book-app",
"Your %1 contact sync account needs to be upgraded. Use the sync button to upgrade the Contacts app.\nOnly local contacts will be editable until upgrade is complete.")
.arg(contactEditor.contact.syncTarget.syncTarget)
} else {
i18n.dtr("address-book-app",
"Your %1 contact sync account needs to be upgraded by running Contacts app.\nOnly local contacts will be editable until upgrade is complete.")
.arg(contactEditor.contact.syncTarget.syncTarget);
}
}
Button {
text: i18n.dtr("address-book-app", "Close")
onClicked: PopupUtils.close(aletMessageDialog)
}
Component.onCompleted: Qt.inputMethod.hide()
Component.onDestruction: contactEditor.pageStack.removePages(contactEditor)
}
}
Component {
id: removeContactDialog
RemoveContactsDialog {
id: removeContactsDialogMessage
property bool popPages: false
onCanceled: {
PopupUtils.close(removeContactsDialogMessage)
}
onAccepted: {
popPages = true
removeContacts(contactEditor.model)
PopupUtils.close(removeContactsDialogMessage)
}
// hide virtual keyboard if necessary
Component.onCompleted: {
contactEditor.enabled = false
Qt.inputMethod.hide()
}
// WORKAROUND: SDK element crash if pop the page where the dialog was created
Component.onDestruction: {
contactEditor.enabled = true
if (popPages) {
if (contactEditor.pageStack.removePages) {
contactEditor.pageStack.removePages(contactEditor)
} else {
contactEditor.pageStack.pop() // editor page
contactEditor.pageStack.pop() // view page
}
}
if (contactEditor.pageStack.primaryPage)
contactEditor.pageStack.primaryPage.forceActiveFocus()
}
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ValueSelector.qml 0000644 0000156 0000165 00000013545 12674534204 033066 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
Item {
id: root
property bool readOnly: false
property bool active: false
property alias values: listView.model
property alias currentIndex: listView.currentIndex
readonly property bool expanded: (state === "expanded") && listView.opacity == 1.0
readonly property alias text: label.text
function selectItem(text)
{
for(var i=0; i < values.length; i++) {
if (values[i] == text) {
currentIndex = i
return
}
}
currentIndex = -1
}
function moveNext()
{
if (currentIndex < (values.length-1)) {
currentIndex++
} else {
currentIndex = 0
}
}
function movePrevious()
{
if (currentIndex > 0) {
currentIndex--
} else {
currentIndex = values.length - 1
}
}
onExpandedChanged: expanded && timer.start()
// FIXME: workaround to close list after a while.
// we cant rely on focus since it hides the keyboard.
Timer {
id: timer
interval: 5000
running: false
onTriggered: state = ""
}
Item {
id: title
anchors {
top: parent.top
bottom: parent.bottom
}
Behavior on x {
UbuntuNumberAnimation { }
}
Label {
id: label
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
}
width: contentWidth
text: root.currentIndex >= 0 ? root.values[root.currentIndex] : ""
// style
fontSize: root.active ? "medium" : "small"
}
Icon {
name: "go-next"
color: "black"
height: units.gu(1)
width: height
anchors {
verticalCenter: parent.verticalCenter
verticalCenterOffset: units.dp(2)
left: label.right
leftMargin: units.gu(0.5)
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (!readOnly) {
root.state = "expanded"
timer.restart()
}
}
}
ListView {
id: listView
objectName: "valuesListView"
anchors.fill: parent
clip: true
orientation: ListView.Horizontal
visible: !title.visible
snapMode: ListView.SnapToItem
delegate: Item {
objectName: "item_" + index
anchors {
top: parent.top
bottom: parent.bottom
}
width: arrow.width + listLabel.paintedWidth + units.gu(1)
opacity: currentIndex == index ? 1.0 : 0.4
Label {
id: listLabel
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
}
width: paintedWidth
text: modelData
// style
fontSize: "medium"
MouseArea {
width: parent.width + units.gu(0.5)
height: parent.height + units.gu(0.5)
anchors.centerIn: parent
onClicked: {
timer.stop()
currentIndex = index
root.state = ""
}
}
}
Icon {
id: arrow
name: "go-next"
height: visible ? units.gu(1) : 0
width: height
visible: index < (listView.count - 1)
anchors {
verticalCenter: listLabel.verticalCenter
verticalCenterOffset: units.dp(2)
left: listLabel.right
leftMargin: units.gu(0.5)
}
}
}
}
transitions: [
Transition {
to: "expanded"
SequentialAnimation {
UbuntuNumberAnimation {
target: title
property: "x"
to: (listView.currentItem.x - listView.contentX)
}
PropertyAction {
target: title
property: "visible"
value: false
}
UbuntuNumberAnimation {
target: listView
property: "opacity"
from: 0.0
to: 1.0
}
}
},
Transition {
to: ""
SequentialAnimation {
UbuntuNumberAnimation {
target: listView
property: "opacity"
from: 1.0
to: 0.0
}
PropertyAction {
target: title
property: "visible"
value: true
}
UbuntuNumberAnimation {
target: title
property: "x"
to: 0
}
}
}
]
}
././@LongLink 0000000 0000000 0000000 00000000155 00000000000 011216 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailNameEditor.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailNameEd0000644 0000156 0000165 00000005422 12674534204 033304 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.AddressBook.Base 0.1
ContactDetailItem {
id: root
property variant emptyFields: []
function isEmpty() {
return (fields == -1) || (emptyFields.length === fields.length)
}
function save() {
var changed = false;
for(var i=0; i < fieldDelegates.length; i++) {
var delegate = fieldDelegates[i]
// Get item from Loader
if (delegate.item) {
delegate = delegate.item
}
if (delegate.detail && (delegate.field >= 0)) {
if (delegate.text != delegate.detail.value(delegate.field)) {
delegate.detail.setValue(delegate.field, delegate.text)
changed = true;
}
}
}
return changed
}
spacing: units.gu(1)
detail: root.contact ? root.contact.name : null
fields: [ Name.FirstName, Name.LastName ]
highlightOnFocus: false
fieldDelegate: TextInputDetail {
id: textInputDetail
objectName: detailToString(ContactDetail.Name, field)
function checkIsEmpty() {
if (field == -1) {
return;
}
var newEmtpyFields = root.emptyFields
var indexOf = newEmtpyFields.indexOf(field)
if ((text.length > 0) && (indexOf !== -1)) {
newEmtpyFields.splice(indexOf, 1)
} else if ((text.length === 0) && (indexOf === -1)){
newEmtpyFields.push(field)
}
root.emptyFields = newEmtpyFields
}
focus: true
width: root.width - units.gu(4)
x: units.gu(2)
detail: root.detail
height: units.gu(4)
placeholderText: field == Name.FirstName ? i18n.dtr("address-book-app", "First name") :
i18n.dtr("address-book-app", "Last name")
inputMethodHints: Qt.ImhNoPredictiveText
onTextChanged: checkIsEmpty()
onFieldChanged: checkIsEmpty()
//style
font.pixelSize: FontUtils.sizeToPixels("large")
}
}
././@LongLink 0000000 0000000 0000000 00000000163 00000000000 011215 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailSyncTargetEditor.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailSyncTa0000644 0000156 0000165 00000016343 12674534204 033360 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3
import Ubuntu.Contacts 0.1
import Ubuntu.AddressBook.Base 0.1
ContactDetailBase {
id: root
property alias active: sourceModel.autoUpdate
property bool isNewContact: contact && contact.contactId === "qtcontacts:::"
property real myHeight: label.height + units.gu(6) + (sources.currentlyExpanded ? sources.containerHeight :
sources.itemHeight)
signal changed()
function save() {
// only changes the target sync for new contacts
if (!isNewContact) {
return;
}
var activeSource = getSelectedSource()
if (!activeSource) {
return;
}
if (!root.detail) {
root.detail = root.contact.syncTarget
}
root.detail.syncTarget = activeSource
}
function getSelectedSource() {
if (sources.model.count <= 0)
return -1
var selectedSourceId = sources.model.get(sources.selectedIndex).sourceId
if (selectedSourceId) {
return selectedSourceId
} else {
return -1
}
}
function contactIsReadOnly(contact) {
var sources = sourceModel.contacts
var contactSyncTarget = contact.syncTarget.value(SyncTarget.SyncTarget + 1)
for (var i = 0; i < writableSources.count; i++) {
var source = writableSources.get(i)
if (source.sourceId === contactSyncTarget) {
return false
}
}
return true
}
detail: root.contact ? contact.detail(ContactDetail.SyncTarget) : null
implicitHeight: root.isNewContact && sources.model && (sources.model.count > 1) ? myHeight : 0
visible: height > 0
ContactModel {
id: sourceModel
manager: (typeof(QTCONTACTS_MANAGER_OVERRIDE) !== "undefined") && (QTCONTACTS_MANAGER_OVERRIDE != "") ? QTCONTACTS_MANAGER_OVERRIDE : "galera"
filter: DetailFilter {
detail: ContactDetail.Type
field: Type.TypeField
value: Type.Group
matchFlags: DetailFilter.MatchExactly
}
autoUpdate: false
onContactsChanged: {
if (contacts.length > 0) {
writableSources.reload()
root.changed()
}
}
}
ListModel {
id: writableSources
function getSourceMetaData(contact) {
var metaData = {'read-only' : false,
'account-provider': '',
'account-id': 0,
'is-primary': false}
var details = contact.details(ContactDetail.ExtendedDetail)
for(var d in details) {
if (details[d].name === "READ-ONLY") {
metaData['read-only'] = details[d].data
} else if (details[d].name === "PROVIDER") {
metaData['account-provider'] = details[d].data
} else if (details[d].name === "APPLICATION-ID") {
metaData['account-id'] = details[d].data
} else if (details[d].name === "IS-PRIMARY") {
metaData['is-primary'] = details[d].data
}
}
return metaData
}
function reload() {
clear()
// filter out read-only sources
var contacts = sourceModel.contacts
if (contacts.length === 0) {
return
}
var data = []
for(var i in contacts) {
var sourceMetaData = getSourceMetaData(contacts[i])
if (!sourceMetaData['read-only']) {
data.push({'sourceId': contacts[i].guid.guid,
'sourceName': contacts[i].displayLabel.label,
'accountId': sourceMetaData['account-id'],
'accountProvider': sourceMetaData['account-provider'],
'readOnly': sourceMetaData['read-only'],
'isPrimary': sourceMetaData['is-primary']
})
}
}
data.sort(function(a, b) {
var valA = a.accountId
var valB = b.accountId
if (a.accountId == b.accountId) {
valA = a.sourceName
valB = b.sourceName
}
if (valA == valB) {
return 0
} else if (valA < valB) {
return -1
} else {
return 1
}
})
var primaryIndex = 0
for (var i in data) {
if (data[i].isPrimary) {
primaryIndex = i
}
append(data[i])
}
// select primary account
sources.selectedIndex = primaryIndex
}
}
Label {
id: label
text: i18n.dtr("address-book-app", "Addressbook")
anchors {
left: parent.left
top: parent.top
right: parent.right
margins: units.gu(2)
}
height: units.gu(4)
}
ThinDivider {
id: divider
anchors {
top: label.bottom
leftMargin: units.gu(2)
rightMargin: units.gu(2)
}
}
OptionSelector {
id: sources
model: writableSources
anchors {
left: parent.left
leftMargin: units.gu(2)
top: divider.bottom
topMargin: units.gu(2)
right: parent.right
rightMargin: units.gu(2)
bottom: parent.bottom
bottomMargin: units.gu(2)
}
delegate: OptionSelectorDelegate {
text: {
if ((sourceId != "system-address-book") && (accountProvider == "")) {
return i18n.dtr("address-book-app", "Personal - %1").arg(sourceName)
} else {
return sourceName
}
}
height: units.gu(4)
}
containerHeight: sources.model && sources.model.count > 4 ? itemHeight * 4 : sources.model ? itemHeight * sources.model.count : 0
}
onActiveChanged: {
if (active) {
sourceModel.update()
}
}
// In case of sources changed we need to update the model
Connections {
target: application
onSourcesChanged: sourceModel.update()
}
}
././@LongLink 0000000 0000000 0000000 00000000166 00000000000 011220 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailGroupWithTypeEditor.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailGroupW0000644 0000156 0000165 00000011052 12674534204 033372 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3
import Ubuntu.Contacts 0.1
import Ubuntu.AddressBook.Base 0.1
ContactDetailGroupWithTypeBase {
id: root
property string detailQmlTypeName
property int currentItem: -1
property int fieldType: ContactDetail.FieldContext
property variant placeholderTexts: []
property int inputMethodHints: Qt.ImhNone
property variant newDetails: []
property bool usePhoneFormat: false
function cancel() {
for(var i=0; i < root.newDetails.length; i++) {
root.contact.removeDetail(root.newDetails[i])
}
root.newDetails = []
}
function isEmpty() {
for(var i=0; i < detailDelegates.length; i++) {
var delegate = detailDelegates[i]
// Get item from Loader
if (delegate.item) {
delegate = delegate.item
}
if (delegate.isEmpty) {
if (!delegate.isEmpty()) {
return false
}
}
}
return true
}
function save() {
var changed = false
var removedDetails = []
for(var i=0; i < detailDelegates.length; i++) {
var delegate = detailDelegates[i]
// Get item from Loader
if (delegate.item) {
delegate = delegate.item
}
if (delegate.save) {
// check if was removed
if (delegate.isEmpty()) {
removedDetails.push(delegate.detail)
changed = true
} else {
if (updateDetail(delegate.detail, delegate.selectedTypeIndex)) {
changed = true
}
// save field changes
if (delegate.save()) {
changed = true
}
}
}
}
for(var i=0; i < removedDetails.length; i++) {
if (contact.isPreferredDetail("TEL", removedDetails[i])) {
contact.favorite.favorite = false
}
contact.removeDetail(removedDetails[i])
}
return changed
}
headerDelegate: Label {
id: header
width: root.width - units.gu(4)
x: units.gu(2)
height: units.gu(4)
text: root.title
// style
fontSize: "medium"
verticalAlignment: Text.AlignVCenter
ThinDivider {
anchors.bottom: parent.bottom
}
}
detailDelegate: ContactDetailWithTypeEditor {
property variant detailType: null
property bool comboLoaded: false
function updateCombo(reload)
{
// Does not update the combo info after details change (Ex. a new detail field was created)
if (!reload && comboLoaded) {
return;
}
if (!root.typeModel) {
return;
}
comboLoaded = true
var newTypes = []
for(var i=0; i < root.typeModel.count; i++) {
newTypes.push(root.typeModel.get(i).label)
}
types = newTypes
if (detail) {
detailType = getType(detail)
if (detailType) {
selectType(detailType.label)
}
}
}
placeholderTexts: root.placeholderTexts
contact: root.contact
fields: root.fields
height: implicitHeight
width: root.width
inputMethodHints: root.inputMethodHints
onDetailChanged: updateCombo(false)
usePhoneFormat: root.usePhoneFormat
// this is necessary due the default property of ListItem.Empty
Item {
Connections {
target: root.typeModel
onLoaded: updateCombo(true)
}
}
}
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011220 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailEmailsEditor.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailEmails0000644 0000156 0000165 00000001766 12674534204 033374 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Contacts 0.1
ContactDetailGroupWithTypeEditor {
title: i18n.dtr("address-book-app", "Email")
detailQmlTypeName: "EmailAddress"
detailType: ContactDetail.Email
fields: [ 0 ]
placeholderTexts: [ i18n.dtr("address-book-app", "Enter an email address") ]
inputMethodHints: Qt.ImhEmailCharactersOnly
}
././@LongLink 0000000 0000000 0000000 00000000167 00000000000 011221 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailOnlineAccountsEditor.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailOnline0000644 0000156 0000165 00000002107 12674534204 033374 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Contacts 0.1
ContactDetailGroupWithTypeEditor {
title: i18n.dtr("address-book-app", "IM")
detailType: ContactDetail.OnlineAccount
detailQmlTypeName: "OnlineAccount"
fields: [ OnlineAccount.AccountUri ]
placeholderTexts: [ i18n.dtr("address-book-app", "Enter a social alias") ]
typeModel: ContactDetailOnlineAccountTypeModel { }
inputMethodHints: Qt.ImhEmailCharactersOnly
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011220 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailAvatarEditor.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailAvatar0000644 0000156 0000165 00000010644 12674534204 033373 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Contacts 0.1
import Ubuntu.AddressBook.Base 0.1
ContactDetailBase {
id: root
readonly property alias busy: activityIndicator.running
readonly property string defaultAvatar: "image://theme/add"
property string temporaryAvatar: ""
property string temporaryAvatarId: ""
function isEmpty() {
return false;
}
function save() {
// create the avatar detail
if (avatarImage.source != root.defaultAvatar) {
if (root.detail && (root.detail.imageUrl === avatarImage.source)) {
return false
} else {
// create the avatar detail
if (!root.detail) {
root.detail = root.contact.avatar
}
root.detail.imageUrl = avatarImage.source
return true
}
}
return false
}
function getAvatar(avatarDetail)
{
// use this verbose mode to avoid problems with binding loops
var avatarUrl = root.defaultAvatar
if (avatarDetail) {
var avatarValue = avatarDetail.value(Avatar.ImageUrl)
if (avatarValue && (avatarValue != "")) {
avatarUrl = avatarValue
}
}
return avatarUrl
}
detail: contact ? contact.detail(ContactDetail.Avatar) : null
implicitHeight: units.gu(8)
implicitWidth: units.gu(8)
highlightOnFocus: false
UbuntuShape {
id: avatar
radius: "medium"
anchors.fill: parent
source: avatarImage.source != defaultAvatar ? avatarImage : null
sourceFillMode: UbuntuShape.PreserveAspectCrop
Image {
id: avatarImage
objectName: "avatarImage"
asynchronous: true
source: root.getAvatar(root.detail)
anchors.centerIn: visible ? avatar : undefined
height: units.gu(3)
width: units.gu(3)
visible: source == defaultAvatar
sourceSize {
width: avatarImage.visible ? units.gu(3) : avatar.width
height: avatarImage.visible ? units.gu(3) : avatar.height
}
// When updating the avatar using the content picker the temporary file returned
// can contain the same name as the previous one and if the cache is enabled this
// will cause the image to not be updated
cache: false
}
}
ActivityIndicator {
id: activityIndicator
anchors.centerIn: avatar
running: (avatarImport.importDialog != null) || (root.temporaryAvatarId != "")
visible: running
}
AvatarImport {
id: avatarImport
onAvatarReceived: {
Contacts.removeFile(root.temporaryAvatar)
// remove the previous image, this is nessary to make sure that the new image
// get updated otherwise if the new image has the same name the image will not
// be updated
avatarImage.source = ""
// copy and resize image
root.temporaryAvatarId = Contacts.copyImage(avatarUrl, null);
}
}
Connections {
target: Contacts
onImageCopyDone: {
if (root.temporaryAvatarId === id) {
root.temporaryAvatar = fileName
avatarImage.source = fileName
root.temporaryAvatarId = ""
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
// make sure the OSK disappear
root.forceActiveFocus()
avatarImport.requestNewAvatar()
}
}
Component.onDestruction: {
Contacts.removeFile("file:///" + root.temporaryAvatar)
root.temporaryAvatar = ""
}
}
././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011212 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ComboButtonAddField.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ComboButtonAddField0000644 0000156 0000165 00000010421 12674534204 033317 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3
ComboButton {
id: root
property QtObject contact: null
property int selectedDetail: -1
readonly property var validDetails: [ ContactDetail.PhoneNumber,
ContactDetail.Email,
ContactDetail.Address,
ContactDetail.OnlineAccount,
ContactDetail.Organization
// TODO: Not supported yet
// ContactDetail.Birthday,
// ContactDetail.Note,
// ContactDetail.Url
]
readonly property var singleValueDetails: [ ContactDetail.Organization ]
signal fieldSelected(string fieldName, string qmlTypeName)
function nameFromEnum(value)
{
switch (value)
{
case ContactDetail.PhoneNumber:
return i18n.dtr("address-book-app", "Phone")
case ContactDetail.Email:
return i18n.dtr("address-book-app", "Email")
case ContactDetail.Address:
return i18n.dtr("address-book-app", "Address")
case ContactDetail.OnlineAccount:
return i18n.dtr("address-book-app", "Social")
case ContactDetail.Organization:
return i18n.dtr("address-book-app", "Professional Details")
default:
console.error("Invalid contact detail enum value:" + value)
return ""
}
}
function qmlTypeFromEnum(value)
{
switch (value)
{
case ContactDetail.PhoneNumber:
return "PhoneNumber"
case ContactDetail.Email:
return "EmailAddress"
case ContactDetail.Address:
return "Address"
case ContactDetail.OnlineAccount:
return "OnlineAccount"
case ContactDetail.Organization:
return "Organization"
default:
console.error("Invalid contact detail enum value:" + value)
return ""
}
}
// check which details will be allowed to create
// some details we only support one value
function filterSingleDetails(details, contact)
{
var result = []
if (contact) {
for(var i=0; i < details.length; i++) {
var det = details[i]
if (singleValueDetails.indexOf(det) != -1) {
if (contact.details(det).length === 0) {
result.push(det)
}
} else {
result.push(det)
}
}
}
return result
}
collapsedHeight: units.gu(4)
implicitHeight: expanded ? expandedHeight : collapsedHeight
onClicked: expanded = !expanded
// make sure that the signal will be fired after the item collapse
onHeightChanged: {
if (!expanded && (selectedDetail !== -1) && (height === collapsedHeight)) {
root.fieldSelected(root.nameFromEnum(root.selectedDetail), root.qmlTypeFromEnum(root.selectedDetail))
root.selectedDetail = -1
}
}
ListView {
id: view
objectName: "listViewOptions"
model: root.filterSingleDetails(validDetails, root.contact)
delegate: Standard {
objectName: text
text: root.nameFromEnum(modelData)
onClicked: {
root.selectedDetail = modelData
root.expanded = false
}
}
}
}
././@LongLink 0000000 0000000 0000000 00000000165 00000000000 011217 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailPhoneNumbersEditor.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailPhoneN0000644 0000156 0000165 00000002124 12674534204 033336 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Contacts 0.1
ContactDetailGroupWithTypeEditor {
title: i18n.dtr("address-book-app", "Phone")
detailType: ContactDetail.PhoneNumber
detailQmlTypeName: "PhoneNumber"
fields: [ PhoneNumber.Number ]
placeholderTexts: [ i18n.dtr("address-book-app", "Enter a number") ]
typeModel: ContactDetailPhoneNumberTypeModel { }
inputMethodHints: Qt.ImhDialableCharactersOnly
usePhoneFormat: true
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/qmldir 0000644 0000156 0000165 00000001726 12674534204 031007 0 ustar pbuser pbgroup 0000000 0000000 module Ubuntu.AddressBook.ContactView
ContactEditorPage 0.1 ContactEditorPage.qml
internal AvatarImport AvatarImport.qml
internal ComboButtonAddField ComboButtonAddField.qml
internal ContactDetailAddressesEditor ContactDetailAddressesEditor.qml
internal ContactDetailAvatarEditor ContactDetailAvatarEditor.qml
internal ContactDetailEmailsEditor ContactDetailEmailsEditor.qml
internal ContactDetailGroupWithTypeEditor ContactDetailGroupWithTypeEditor.qml
internal ContactDetailNameEditor ContactDetailNameEditor.qml
internal ContactDetailOnlineAccountsEditor ContactDetailOnlineAccountsEditor.qml
internal ContactDetailOrganizationsEditor ContactDetailOrganizationsEditor.qml
internal ContactDetailPhoneNumbersEditor ContactDetailPhoneNumbersEditor.qml
internal ContactDetailSyncTargetEditor ContactDetailSyncTargetEditor.qml
internal ContactDetailWithTypeEditor ContactDetailWithTypeEditor.qml
internal TextInputDetail TextInputDetail.qml
internal ValueSelector ValueSelector.qml
././@LongLink 0000000 0000000 0000000 00000000166 00000000000 011220 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailOrganizationsEditor.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailOrgani0000644 0000156 0000165 00000002334 12674534204 033371 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
ContactDetailGroupWithTypeEditor {
title: i18n.dtr("address-book-app", "Professional Details")
typeModel: null
detailQmlTypeName: "Organization"
detailType: ContactDetail.Organization
fields: [ Organization.Name,
Organization.Role,
Organization.Title ]
placeholderTexts: [ i18n.dtr("address-book-app", "Organization"),
i18n.dtr("address-book-app", "Role"),
i18n.dtr("address-book-app", "Title")
]
inputMethodHints: Qt.ImhNoPredictiveText
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/CMakeLists.txt 0000644 0000156 0000165 00000002404 12674534204 032326 0 ustar pbuser pbgroup 0000000 0000000 set(AB_CONTACT_EDITOR_QMLS
AvatarImport.qml
ComboButtonAddField.qml
ContactDetailAddressesEditor.qml
ContactDetailAvatarEditor.qml
ContactDetailEmailsEditor.qml
ContactDetailGroupWithTypeEditor.qml
ContactDetailNameEditor.qml
ContactDetailOnlineAccountsEditor.qml
ContactDetailOrganizationsEditor.qml
ContactDetailPhoneNumbersEditor.qml
ContactDetailSyncTargetEditor.qml
ContactDetailWithTypeEditor.qml
ContactEditorPage.qml
TextInputDetail.qml
ValueSelector.qml
qmldir
)
# make the files visible on qtcreator
add_custom_target(contact_editor_QmlFiles ALL SOURCES ${AB_CONTACT_EDITOR_QMLS})
if(INSTALL_COMPONENTS)
install(FILES ${AB_CONTACT_EDITOR_QMLS} DESTINATION ${ADDRESS_BOOK_QMLPLUGIN_INSTALL_PREFIX}/ContactEditor)
endif()
#copy qml files to build dir to make it possible to run without install
foreach(QML_FILE ${AB_CONTACT_EDITOR_QMLS})
add_custom_command(TARGET contact_editor_QmlFiles PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E
copy ${CMAKE_CURRENT_SOURCE_DIR}/${QML_FILE} ${CMAKE_CURRENT_BINARY_DIR}/)
endforeach()
if (NOT ${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
add_dependencies(copyqmlfiles contact_editor_QmlFiles)
endif()
././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011214 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailAddressesEditor.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/ContactDetailAddres0000644 0000156 0000165 00000002531 12674534204 033353 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Contacts 0.1
ContactDetailGroupWithTypeEditor {
title: i18n.dtr("address-book-app", "Address")
detailQmlTypeName: "Address"
detailType: ContactDetail.Address
fields: [ Address.Street,
Address.Locality,
Address.Region,
Address.Postcode,
Address.Country ]
placeholderTexts: [ i18n.dtr("address-book-app", "Street"),
i18n.dtr("address-book-app", "Locality"),
i18n.dtr("address-book-app", "Region"),
i18n.dtr("address-book-app", "Post code"),
i18n.dtr("address-book-app", "Country") ]
inputMethodHints: Qt.ImhNoPredictiveText
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactEditor/TextInputDetail.qml0000644 0000156 0000165 00000007375 12674534204 033404 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Keyboard 0.1
import Ubuntu.Telephony.PhoneNumber 0.1
//style
import Ubuntu.Components.Themes.Ambiance 0.1
FocusScope {
id: root
//WORKAROUND: SDK does not allow us to disable focus for items due bug: #1514822
//because of that we need this
readonly property bool _allowFocus: true
readonly property bool isTextField: true
property QtObject detail
property int field: -1
property variant originalValue: root.detail && (root.field >= 0) ? root.detail.value(root.field) : null
// proxy textField
property alias font: field.font
property alias placeholderText: field.placeholderText
property alias inputMethodHints: field.inputMethodHints
property alias text: field.text
property alias hasClearButton: field.hasClearButton
// proxy PhoneNumberField
property alias autoFormat: field.autoFormat
signal removeClicked()
//FIXME: Move this property to TextField as soon as the SDK get ported to QtQuick 2.2
activeFocusOnTab: true
onOriginalValueChanged: {
if (originalValue && (originalValue !== "")) {
field.text = originalValue
}
}
// propage focus to text field
onActiveFocusChanged: {
if (activeFocus)
field.forceActiveFocus()
}
PhoneNumberField {
id: field
//WORKAROUND: Due the SDK bug #1514822, #1514850 we can not disable focus for some items
//because of that we keep the focus only for textFields. This will block the user
//to use keyboard on "add-field" combo box and some other functionalities
function forceActiveFocusForNextField(keyEvent)
{
var backward = (keyEvent.modifiers & Qt.ShiftModifier)
var next = field.nextItemInFocusChain(!backward)
// only focus on TextInputDetails
while (!next || !next.hasOwnProperty("isTextField")) {
next = next.nextItemInFocusChain(!backward)
}
if (next) {
next.forceActiveFocus()
}
}
anchors.fill: parent
defaultRegion: PhoneUtils.defaultRegion
autoFormat: false
// Ubuntu.Keyboard
// TRANSLATORS: This is the text that will be used on the "return" key for the virtual keyboard,
// this word must be less than 5 characters
InputMethod.extensions: { "enterKeyText": i18n.dtr("address-book-app", "Next") }
readOnly: root.detail ? root.detail.readOnly : true
style: TextFieldStyle {
overlaySpacing: 0
frameSpacing: 0
background: Item {}
}
onActiveFocusChanged: {
if (activeFocus) {
makeMeVisible(root)
}
}
// default style
font {
family: "Ubuntu"
pixelSize: activeFocus ? FontUtils.sizeToPixels("large") : FontUtils.sizeToPixels("medium")
}
Keys.onReturnPressed: forceActiveFocusForNextField(event)
Keys.onTabPressed: forceActiveFocusForNextField(event)
Keys.onBacktabPressed: forceActiveFocusForNextField(event)
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ 0000755 0000156 0000165 00000000000 12674534512 027254 5 ustar pbuser pbgroup 0000000 0000000 ././@LongLink 0000000 0000000 0000000 00000000151 00000000000 011212 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailNameView.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailNameView0000644 0000156 0000165 00000003325 12674534204 033352 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import QtContacts 5.0
import Ubuntu.AddressBook.Base 0.1
ContactDetailBase {
id: root
detail: root.contact ? root.contact.name : null
implicitHeight: label.paintedHeight + (label.anchors.margins * 2)
activeFocusOnTab: false
Label {
id: label
function formatNameToDisplay(contact) {
if (!contact) {
return ""
}
if (contact.name) {
var detail = contact.name
return detail.firstName + " " + detail.lastName
} else if (contact.displayLabel && contact.displayLabel.label && contact.displayLabel.label !== "") {
return contact.displayLabel.label
} else {
return ""
}
}
anchors {
fill: parent
margins: units.gu(2)
}
fontSize: "x-large"
elide: Text.ElideRight
color: Qt.rgba(0.4, 0.4, 0.4, 1.0)
style: Text.Raised
styleColor: "white"
text: formatNameToDisplay(root.contact)
}
}
././@LongLink 0000000 0000000 0000000 00000000163 00000000000 011215 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailOnlineAccountsView.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailOnlineAc0000644 0000156 0000165 00000002072 12674534204 033325 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0 as QtContacts
import Ubuntu.Components 1.3
import Ubuntu.Contacts 0.1
ContactDetailGroupWithTypeView {
detailType: QtContacts.ContactDetail.OnlineAccount
fields: [ QtContacts.OnlineAccount.AccountUri ]
title: i18n.dtr("address-book-app", "Social")
typeModel: ContactDetailOnlineAccountTypeModel { }
defaultAction: Action {
text: i18n.dtr("address-book-app", "Touch")
}
}
././@LongLink 0000000 0000000 0000000 00000000161 00000000000 011213 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailPhoneNumbersView.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailPhoneNum0000644 0000156 0000165 00000003134 12674534204 033366 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0 as QtContacts
import Ubuntu.Contacts 0.1
import Ubuntu.Components 1.3
ContactDetailGroupWithTypeView {
id: root
detailType: QtContacts.ContactDetail.PhoneNumber
fields: [ QtContacts.PhoneNumber.Number ]
title: i18n.dtr("address-book-app", "Phone")
typeModel: ContactDetailPhoneNumberTypeModel { }
defaultAction: Action {
text: i18n.dtr("address-book-app", "Phone")
name: "default"
}
detailDelegate: ContactDetailPhoneNumberView {
property variant detailType: detail && root.contact && root.typeModelReady ? root.getType(detail) : null
action: root.defaultAction
contact: root.contact
fields: root.fields
typeLabel: detailType ? detailType.label : ""
height: implicitHeight
width: root.width
onActionTrigerred: root.actionTrigerred(actionName, detail)
onClicked: root.actionTrigerred(root.defaultAction.name, detail)
}
}
././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011214 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailOrganizationsView.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailOrganiza0000644 0000156 0000165 00000002041 12674534204 033403 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0 as QtContacts
ContactDetailGroupWithTypeView {
id: root
title: i18n.dtr("address-book-app", "Professional Details")
defaultIcon: "image://theme/location"
detailType: QtContacts.ContactDetail.Organization
typeModel: null
fields: [ QtContacts.Organization.Name,
QtContacts.Organization.Role,
QtContacts.Organization.Title ]
}
././@LongLink 0000000 0000000 0000000 00000000156 00000000000 011217 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailAddressesView.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailAddresse0000644 0000156 0000165 00000002142 12674534204 033365 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0 as QtContacts
import Ubuntu.Components 1.3
ContactDetailGroupWithTypeView {
id: root
title: i18n.dtr("address-book-app", "Address")
defaultIcon: "image://theme/location"
detailType: QtContacts.ContactDetail.Address
fields: [ QtContacts.Address.Street,
QtContacts.Address.Locality,
QtContacts.Address.Region,
QtContacts.Address.Postcode,
QtContacts.Address.Country]
}
././@LongLink 0000000 0000000 0000000 00000000153 00000000000 011214 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailEmailsView.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailEmailsVi0000644 0000156 0000165 00000001773 12674534204 033355 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0 as QtContacts
import Ubuntu.Components 1.3
ContactDetailGroupWithTypeView {
id: root
detailType: QtContacts.ContactDetail.Email
title: i18n.dtr("address-book-app", "Email")
fields: [ 0 ]
defaultAction: Action {
text: i18n.dtr("address-book-app", "Email")
name: "mailto"
iconName: "email"
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactFetchError.qml0000644 0000156 0000165 00000002064 12674534204 033346 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
Component {
Dialog {
id: dialogue
title: i18n.dtr("address-book-app", "Error")
text: i18n.dtr("address-book-app", "Contact not found")
Button {
text: i18n.dtr("address-book-app", "Cancel")
gradient: UbuntuColors.greyGradient
onClicked: PopupUtils.close(dialogue)
}
}
}
././@LongLink 0000000 0000000 0000000 00000000157 00000000000 011220 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailSyncTargetView.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailSyncTarg0000644 0000156 0000165 00000005723 12674534204 033375 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import QtContacts 5.0
import Ubuntu.Contacts 0.1
ContactDetailGroupWithTypeView {
id: root
// does not show the field if there is only one addressbook
function filterDetails(details) {
var result = []
if (sourceModel.contacts.length <= 1) {
return result;
}
for(var d in details) {
var isEmpty = true
for(var f in root.fields) {
var fieldValue = details[d].value(root.fields[f])
if (fieldValue && (String(fieldValue) !== "")) {
isEmpty = false
break;
}
}
if (!isEmpty) {
result.push(details[d])
}
}
return result
}
title: i18n.dtr("address-book-app", "Addressbook")
defaultIcon: "image://theme/contact-group"
detailType: ContactDetail.SyncTarget
typeModel: null
activeFocusOnTab: false
fields: [ SyncTarget.SyncTarget ]
ContactModel {
id: sourceModel
manager: (typeof(QTCONTACTS_MANAGER_OVERRIDE) !== "undefined") && (QTCONTACTS_MANAGER_OVERRIDE != "") ? QTCONTACTS_MANAGER_OVERRIDE : "galera"
filter: DetailFilter {
detail: ContactDetail.Type
field: Type.TypeField
value: Type.Group
matchFlags: DetailFilter.MatchExactly
}
autoUpdate: false
}
detailDelegate: ContactDetailWithTypeView {
property variant detailType: detail && root.contact && root.typeModelReady ? root.getType(detail) : ""
action: root.defaultAction
contact: root.contact
fields: root.fields
typeLabel: detailType ? detailType.label : ""
height: implicitHeight
width: root.width
activeFocusOnTab: false
onClicked: root.actionTrigerred(root.defaultAction.name, detail)
function overrideValue(detail, field)
{
if (detail.value(field + 1) == "system-address-book") {
return detail.value(field)
} else if (detail.value(field + 2) == "0") {
return i18n.dtr("address-book-app", "Personal - %1").arg(detail.value(field))
} else {
return detail.value(field)
}
}
}
Component.onCompleted: sourceModel.update()
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactHeaderView.qml0000644 0000156 0000165 00000003507 12674534204 033331 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2013 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3 as ListItem
FocusScope {
id: contactHeader
property variant contact: null
implicitHeight: units.gu(12)
ContactDetailAvatarView {
id: detailAvatar
contact: contactHeader.contact
anchors {
top: parent.top
topMargin: units.gu(2)
left: parent.left
leftMargin: units.gu(1)
}
width: units.gu(8)
height: units.gu(8)
}
ContactDetailNameView {
id: detailName
contact: contactHeader.contact
anchors {
left: detailAvatar.right
right: detailFavorite.right
top: parent.top
margins: units.gu(2)
}
height: implicitHeight
}
ContactDetailFavoriteView {
id: detailFavorite
contact: contactHeader.contact
anchors {
right: parent.right
rightMargin: units.gu(1)
verticalCenter: parent.verticalCenter
}
height: units.gu(2)
width: units.gu(2)
}
ListItem.ThinDivider {
id: bottomDividerLine
anchors.bottom: parent.bottom
}
}
././@LongLink 0000000 0000000 0000000 00000000160 00000000000 011212 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailPhoneNumberView.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailPhoneNum0000644 0000156 0000165 00000004722 12674534204 033372 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.AddressBook.Base 0.1
ContactDetailBase {
id: root
property alias typeLabel: view.typeLabel
property alias lineHeight: view.lineHeight
readonly property bool isReady: (fields != null) && (detail != null)
signal actionTrigerred(string actionName, QtObject detail)
function populateValues()
{
if (isReady) {
var values = []
for(var i=0; i < fields.length; i++) {
values.push(detail.value(fields[i]))
}
view.values = values
}
}
implicitHeight: view.implicitHeight
onIsReadyChanged: populateValues()
Connections {
target: root.detail
onDetailChanged: populateValues()
}
BasicFieldView {
id: view
parentIndex: root.index
detail: root.detail
fields: root.fields
anchors {
left: parent.left
top: parent.top
right: messageActions.left
bottom: parent.bottom
leftMargin: units.gu(2)
}
}
ActionButton {
id: messageActions
objectName: "message-contact"
anchors {
right: callActions.left
rightMargin: units.gu(1)
verticalCenter: parent.verticalCenter
}
width: units.gu(4)
height: units.gu(4)
iconName: "message"
onClicked: root.actionTrigerred("message", root.detail)
}
ActionButton {
id: callActions
objectName: "tel-contact"
anchors {
right: parent.right
rightMargin: units.gu(2)
top: parent.top
verticalCenter: parent.verticalCenter
}
width: units.gu(4)
height: units.gu(4)
iconName: "call-start"
onClicked: root.actionTrigerred("tel", root.detail)
}
}
././@LongLink 0000000 0000000 0000000 00000000153 00000000000 011214 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailAvatarView.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailAvatarVi0000644 0000156 0000165 00000004571 12674534204 033360 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtGraphicalEffects 1.0
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Contacts 0.1
import Ubuntu.AddressBook.Base 0.1
ContactDetailBase {
id: root
property alias editable: favImage.enabled
implicitHeight: units.gu(12)
implicitWidth: parent.width
activeFocusOnTab: false
Connections {
id: connections
target: avatar.contactElement
ignoreUnknownSignals: true
onContactChanged: avatar.reload()
}
Image {
id: imageBg
source: avatar.avatarUrl
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
visible: false
}
FastBlur {
anchors.fill: imageBg
source: imageBg
radius: 32
visible: avatar.avatarUrl !== avatar.fallbackAvatarUrl
}
ContactAvatar {
id: avatar
objectName: "contactAvatarDetail"
contactElement: root.contact
height: units.gu(8)
width: height
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: units.gu(2)
}
}
ActionButton {
id: favImage
objectName: "contactFavoriteDetail"
iconName: root.contact && root.contact.favorite.favorite ? "starred" : "non-starred"
height: units.gu(4)
visible: root.editable || (root.contact && root.contact.favorite.favorite)
iconSize: units.gu(3)
width: height
anchors {
right: parent.right
rightMargin: units.gu(2)
verticalCenter: parent.verticalCenter
}
onClicked: {
root.contact.favorite.favorite = !root.contact.favorite.favorite
root.contact.save()
}
}
}
././@LongLink 0000000 0000000 0000000 00000000155 00000000000 011216 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailWithTypeView.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailWithType0000644 0000156 0000165 00000004324 12674534204 033414 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.AddressBook.Base 0.1
ContactDetailBase {
id: root
property alias typeLabel: view.typeLabel
property alias lineHeight: view.lineHeight
readonly property bool isReady: (fields != null) && (detail != null)
function populateValues()
{
if (isReady) {
var values = []
for(var i=0; i < fields.length; i++) {
values.push(overrideValue(detail, fields[i]))
}
view.values = values
}
}
function overrideValue(detail, field)
{
return detail.value(field)
}
activeFocusOnTab: icon.visible
implicitHeight: view.implicitHeight
onIsReadyChanged: populateValues()
Connections {
target: root.detail
onDetailChanged: populateValues()
}
BasicFieldView {
id: view
detail: root.detail
fields: root.fields
parentIndex: root.index
anchors {
left: parent.left
leftMargin: units.gu(2)
right: icon.left
rightMargin: units.gu(2)
top: parent.top
}
}
Icon {
id: icon
anchors {
right: parent.right
rightMargin: units.gu(3)
verticalCenter: parent.verticalCenter
}
width: root.action && (root.action.iconName !== "") ? units.gu(2.5) : 0
height: width
name: root.action ? root.action.iconName : ""
color: root.activeFocus ? UbuntuColors.orange : "gray"
visible: width > 0
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/qmldir 0000644 0000156 0000165 00000001760 12674534204 030471 0 ustar pbuser pbgroup 0000000 0000000 module Ubuntu.AddressBook.ContactView
ContactViewPage 0.1 ContactViewPage.qml
ContactDetailSyncTargetView 0.1 ContactDetailSyncTargetView.qml
internal ActionButton ActionButton.qml
internal BasicFieldView BasicFieldView.qml
internal ContactDetailAddressesView ContactDetailAddressesView.qml
internal ContactDetailAvatarView ContactDetailAvatarView.qml
internal ContactDetailEmailsView ContactDetailEmailsView.qml
internal ContactDetailGroupWithTypeView ContactDetailGroupWithTypeView.qml
#internal ContactDetailNameView ContactDetailNameView.qml
internal ContactDetailOnlineAccountsView ContactDetailOnlineAccountsView.qml
internal ContactDetailOrganizationsView ContactDetailOrganizationsView.qml
internal ContactDetailPhoneNumbersView ContactDetailPhoneNumbersView.qml
internal ContactDetailPhoneNumberView ContactDetailPhoneNumberView.qml
internal ContactDetailWithTypeView ContactDetailWithTypeView.qml
internal ContactFetchError ContactFetchError.qml
#internal ContactHeaderView ContactHeaderView.qml
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ActionButton.qml 0000644 0000156 0000165 00000001774 12674534204 032407 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
AbstractButton {
id: root
property QtObject actions
property alias iconName: icon.name
property real iconSize: units.gu(2.5)
Icon {
id: icon
anchors.centerIn: parent
height: root.iconSize
width: root.iconSize
color: root.activeFocus ? UbuntuColors.orange : "gray"
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/BasicFieldView.qml 0000644 0000156 0000165 00000004662 12674534204 032615 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
Item {
id: root
property alias typeLabel: typeLabel.text
property alias values: valueList.model
property double lineHeight: units.gu(2)
property QtObject detail: null
property variant fields: null
property int parentIndex: -1
activeFocusOnTab: false
implicitHeight: typeLabel.height + fieldValues.height + units.gu(2)
Column {
id: fieldValues
anchors {
left: parent.left
top: parent.top
topMargin: units.gu(1)
right: parent.right
}
height: (valueList.count * root.lineHeight)
Repeater {
id: valueList
Label {
id: label
objectName: detail && fields ? "label_" + detailToString(detail.type, fields[index]) + "_" + root.parentIndex + "." + index : ""
anchors {
left: parent ? parent.left : undefined
right: parent ? parent.right : undefined
}
height: root.lineHeight
verticalAlignment: Text.AlignVCenter
text: modelData ? modelData : ""
elide: Text.ElideRight
// style
fontSize: "medium"
}
}
}
Label {
id: typeLabel
objectName: detail ? "type_" + detailToString(detail.type, -1) + "_" + root.parentIndex : ""
elide: Text.ElideRight
visible: text != ""
anchors {
left: parent.left
top: fieldValues.bottom
//topMargin: units.gu(0.0)
right: parent.right
}
height: visible ? units.gu(2) : 0
verticalAlignment: Text.AlignVCenter
// style
fontSize: "small"
opacity: 0.8
}
}
././@LongLink 0000000 0000000 0000000 00000000162 00000000000 011214 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailGroupWithTypeView.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactDetailGroupWit0000644 0000156 0000165 00000003327 12674534204 033421 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0 as QtContacts
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3
import Ubuntu.AddressBook.Base 0.1
ContactDetailGroupWithTypeBase {
id: root
property QtObject defaultAction: null
signal actionTrigerred(string actionName, QtObject detail)
showEmpty: false
headerDelegate: Label {
id: header
width: root.width - units.gu(4)
x: units.gu(2)
height: units.gu(4)
text: root.title
// style
fontSize: "medium"
verticalAlignment: Text.AlignVCenter
ThinDivider {
anchors.bottom: parent.bottom
}
}
detailDelegate: ContactDetailWithTypeView {
property variant detailType: detail && root.contact && root.typeModelReady ? root.getType(detail) : ""
action: root.defaultAction
contact: root.contact
fields: root.fields
typeLabel: detailType ? detailType.label : ""
height: implicitHeight
width: root.width
onClicked: root.actionTrigerred(root.defaultAction.name, detail)
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/ContactViewPage.qml 0000644 0000156 0000165 00000013656 12674534204 033023 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
import Ubuntu.Contacts 0.1
Page {
id: root
property QtObject contact: null
property string contactId
property alias extensions: extensionsContents.children
property alias model: contactFetch.model
property alias editable: contactDetailAvatar.editable
property alias headerActions: trailingBar.actions
signal contactFetched(QtObject contact)
signal contactRemoved()
signal actionTrigerred(string action, QtObject contact, QtObject detail)
function fetchContact(contactId)
{
if (contactId !== "") {
contactFetch.fetchContact(contactId)
}
}
header: PageHeader {
id: pageHeader
flickable: flickable
title: contact ? ContactsJS.formatToDisplay(contact, i18n.dtr("address-book-app", "No name")) : ""
trailingActionBar {
id: trailingBar
}
}
Connections {
target: contact
onContactChanged: {
pageHeader.title = ContactsJS.formatToDisplay(contact, i18n.dtr("address-book-app", "No name"))
}
}
// Pop page if the contact get removed
onContactChanged: {
if (!contact) {
root.contactRemoved()
}
}
onActiveChanged: {
if (active) {
//WORKAROUND: to correct scroll back the page
flickable.returnToBounds()
}
}
ActivityIndicator {
id: busyIndicator
parent: root
running: (root.contact === null) && contactFetch.running
visible: running
anchors.centerIn: parent
}
ContactFetchError {
id: fetchErrorDialog
}
ContactFetch {
id: contactFetch
onContactRemoved: root.contactRemoved()
onContactNotFound: PopupUtils.open(fetchErrorDialog, root)
onContactFetched: {
root.contact = contact
root.contactFetched(root.contact)
}
}
Flickable {
id: flickable
flickableDirection: Flickable.VerticalFlick
anchors.fill: parent
clip: true
//WORKAROUND: There is a bug on SDK page that causes the page to appear flicked with small contents
// see bug #1223050
contentHeight: Math.max(contents.height, parent.height) + units.gu(2)
contentWidth: parent.width
Column {
id: contents
height: childrenRect.height
anchors {
top: parent.top
left: parent.left
right: parent.right
}
ContactDetailAvatarView {
id: contactDetailAvatar
objectName: "avatar"
contact: root.contact
anchors.left: parent.left
height: implicitHeight
width: implicitWidth
}
ContactDetailPhoneNumbersView {
objectName: "phones"
contact: root.contact
anchors {
left: parent.left
right: parent.right
}
height: implicitHeight
onActionTrigerred: root.actionTrigerred(actionName, root.contact, detail)
}
ContactDetailEmailsView {
objectName: "emails"
contact: root.contact
anchors {
left: parent.left
right: parent.right
}
height: implicitHeight
onActionTrigerred: root.actionTrigerred(actionName, root.contact, detail)
}
ContactDetailOnlineAccountsView {
contact: root.contact
anchors {
left: parent.left
right: parent.right
}
height: implicitHeight
onActionTrigerred: root.actionTrigerred(actionName, root.contact, detail)
}
ContactDetailAddressesView {
objectName: "addresses"
contact: root.contact
anchors {
left: parent.left
right: parent.right
}
height: implicitHeight
onActionTrigerred: root.actionTrigerred(actionName, root.contact, detail)
}
ContactDetailOrganizationsView {
objectName: "organizations"
contact: root.contact
anchors {
left: parent.left
right: parent.right
}
height: implicitHeight
onActionTrigerred: root.actionTrigerred(actionName, root.contact, detail)
}
Item {
id: extensionsContents
anchors {
left: parent.left
right: parent.right
}
height: childrenRect.height
}
}
}
Component.onCompleted: {
if (contact == null) {
fetchContact(root.contactId)
}
}
onContactIdChanged: {
if (contact == null) {
fetchContact(root.contactId)
}
}
onModelChanged: {
if (contact == null) {
fetchContact(root.contactId)
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactView/CMakeLists.txt 0000644 0000156 0000165 00000002404 12674534204 032012 0 ustar pbuser pbgroup 0000000 0000000 set(AB_CONTACT_VIEW_QMLS
ActionButton.qml
BasicFieldView.qml
ContactDetailAddressesView.qml
ContactDetailAvatarView.qml
ContactDetailEmailsView.qml
ContactDetailGroupWithTypeView.qml
ContactDetailNameView.qml
ContactDetailOnlineAccountsView.qml
ContactDetailOrganizationsView.qml
ContactDetailPhoneNumbersView.qml
ContactDetailPhoneNumberView.qml
ContactDetailSyncTargetView.qml
ContactDetailWithTypeView.qml
ContactFetchError.qml
#ContactHeaderView.qml
ContactViewPage.qml
qmldir
)
# make the files visible on qtcreator
add_custom_target(contact_view_QmlFiles ALL SOURCES ${AB_CONTACT_VIEW_QMLS})
if(INSTALL_COMPONENTS)
install(FILES ${AB_CONTACT_VIEW_QMLS} DESTINATION ${ADDRESS_BOOK_QMLPLUGIN_INSTALL_PREFIX}/ContactView)
endif()
#copy qml files to build dir to make it possible to run without install
foreach(QML_FILE ${AB_CONTACT_VIEW_QMLS})
add_custom_command(TARGET contact_view_QmlFiles PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E
copy ${CMAKE_CURRENT_SOURCE_DIR}/${QML_FILE} ${CMAKE_CURRENT_BINARY_DIR}/)
endforeach()
if (NOT ${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
add_dependencies(copyqmlfiles contact_view_QmlFiles)
endif()
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactShare/ 0000755 0000156 0000165 00000000000 12674534512 027404 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactShare/ContactSharePage.qml0000644 0000156 0000165 00000003671 12674534204 033277 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Content 1.3 as ContentHub
import Ubuntu.AddressBook.Base 0.1
Page {
id: root
property alias contactModel: exporter.contactModel
property var contacts
signal canceled()
signal completed()
// invisible header
header: Item { height: 0 }
ContentHub.ContentPeerPicker {
visible: true
anchors.fill: parent
contentType: ContentHub.ContentType.Contacts
handler: ContentHub.ContentHandler.Share
onPeerSelected: {
exporter.activeTransfer = peer.request();
if (exporter.activeTransfer.state === ContentHub.ContentTransfer.InProgress) {
exporter.start(root.contacts)
}
}
onCancelPressed: {
if (exporter.activeTransfer) {
exporter.activeTransfer.state = ContentHub.ContentTransfer.Aborted
}
if (root.pageStack.removePages)
root.pageStack.removePages(root)
else
root.pageStack.pop()
}
}
ContactExporter {
id: exporter
onDone: {
if (root.pageStack.removePages)
root.pageStack.removePages(root)
else
root.pageStack.pop()
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactShare/qmldir 0000644 0000156 0000165 00000000122 12674534204 030610 0 ustar pbuser pbgroup 0000000 0000000 module Ubuntu.AddressBook.ContactShare
ContactSharePage 0.1 ContactSharePage.qml
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/ContactShare/CMakeLists.txt 0000644 0000156 0000165 00000001613 12674534204 032143 0 ustar pbuser pbgroup 0000000 0000000 set(AB_CONTACT_SHARE_QMLS
ContactSharePage.qml
qmldir
)
install(FILES ${AB_CONTACT_SHARE_QMLS}
DESTINATION ${ADDRESS_BOOK_APP_DIR}/imports/ContactShare
)
# make the files visible on qtcreator
add_custom_target(contact_share_QmlFiles ALL SOURCES ${AB_CONTACT_SHARE_QMLS})
if(INSTALL_COMPONENTS)
install(FILES ${AB_CONTACT_SHARE_QMLS} DESTINATION ${ADDRESS_BOOK_QMLPLUGIN_INSTALL_PREFIX}/ContactShare)
endif()
#copy qml files to build dir to make it possible to run without install
foreach(QML_FILE ${AB_CONTACT_SHARE_QMLS})
add_custom_command(TARGET contact_share_QmlFiles PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E
copy ${CMAKE_CURRENT_SOURCE_DIR}/${QML_FILE} ${CMAKE_CURRENT_BINARY_DIR}/)
endforeach()
if (NOT ${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
add_dependencies(copyqmlfiles contact_share_QmlFiles)
endif()
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/CMakeLists.txt 0000644 0000156 0000165 00000000164 12674534204 027565 0 ustar pbuser pbgroup 0000000 0000000 add_subdirectory(Base)
add_subdirectory(ContactEditor)
add_subdirectory(ContactView)
add_subdirectory(ContactShare)
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/Base/ 0000755 0000156 0000165 00000000000 12674534512 025700 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/Base/ContactExporter.qml 0000644 0000156 0000165 00000013660 12674534204 031543 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Content 1.3
import Ubuntu.Components.Popups 1.3
Item {
id: root
property var contactModel
property bool exportToDisk: true
property var activeTransfer: null
signal contactsFetched(var contacts)
signal done(string outputFile)
function start(contacts) {
if (!contactModel) {
console.error("No contact model defined")
return
}
// skip if a query is running
if (priv.currentQueryId != -1) {
console.error("Export already running")
return
}
if (!priv.busyDialog) {
priv.busyDialog = PopupUtils.open(busyDialogComponent, root)
}
var ids = []
for (var i=0; i < contacts.length; i++) {
ids.push(contacts[i].contactId)
}
if (ids.length == 0) {
console.debug("The contact list is empty")
done("")
} else {
if (root.contactModel.manager === "memory") {
// memory backend emit contact fetched before return from "fetchContacts" we will use operation = "-2"
// to say that we are wainting for a operation from memory manager
priv.currentQueryId = -2
contactModel.fetchContacts(ids)
} else {
priv.currentQueryId = contactModel.fetchContacts(ids)
}
}
}
function dismissBusyDialog()
{
if (priv.busyDialog) {
PopupUtils.close(priv.busyDialog)
priv.busyDialog = null
}
}
Item {
id: priv
property var busyDialog: null
property int currentQueryId: -1
readonly property var detailsBlackList: [ ContactDetail.Favorite,
ContactDetail.Tag,
ContactDetail.ExtendedDetail,
ContactDetail.Guid ]
function filterContactDetails(contact)
{
var newContact = Qt.createQmlObject("import QtContacts 5.0; Contact { }", root)
var allDetails = contact.contactDetails
for(var i=0; i < allDetails.length; i++) {
var det = allDetails[i]
if (detailsBlackList.indexOf(det.type) == -1) {
newContact.addDetail(det)
}
}
return newContact
}
function generateOutputFileName(contacts)
{
if (contacts.length === 1) {
return "file:///tmp/%1.vcf".arg(contacts[0].displayLabel.label.replace(/\s/g, ''))
} else {
return "file:///tmp/ubuntu_contacts.vcf";
}
}
Connections {
target: root.contactModel
onExportCompleted: {
// send contacts back to source app (pick mode)
if (error === ContactModel.ExportNoError) {
var obj = Qt.createQmlObject("import Ubuntu.Content 1.3; ContentItem { url: '" + url + "' }", root)
if (root.activeTransfer) {
root.activeTransfer.items = [obj]
root.activeTransfer.state = ContentTransfer.Charged
} else {
console.error("No active transfer")
}
} else {
root.activeTransfer = ContentHub.ContentTransfer.Aborted
console.error("Fail to export contacts:" + error)
}
root.dismissBusyDialog()
root.done(url)
}
onContactsFetched: {
// currentQueryId == -2 is used during a fetch using "memory" manager
if ((priv.currentQueryId == -2) || (requestId == priv.currentQueryId)) {
if (root.exportToDisk) {
var contacts = []
// remove unnecessary info from contacts
for(var i=0; i < fetchedContacts.length; i++) {
contacts.push(priv.filterContactDetails(fetchedContacts[i]))
}
// update outputFile with a friendly name
var outputFile = priv.generateOutputFileName(contacts)
root.contactModel.exportContacts(outputFile,
[],
contacts)
}
root.contactsFetched(fetchedContacts)
priv.currentQueryId = -1
}
}
}
Connections {
target: root.activeTransfer
onStateChanged: {
if (root.activeTransfer.state === ContentTransfer.Aborted) {
root.activeTransfer = null
root.done("")
}
}
}
}
Component {
id: busyDialogComponent
Dialog {
title: i18n.dtr("address-book-app", "Exporting contacts...")
ActivityIndicator {
id: activity
anchors.horizontalCenter: parent.horizontalCenter
running: true
}
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/Base/RemoveContactsDialog.qml 0000644 0000156 0000165 00000004643 12674534204 032474 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2014 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
Dialog {
objectName: "removeContactsDialog"
property var contacts: []
signal canceled()
signal accepted()
function removeContacts(model)
{
var ids = []
for(var i=0, iMax=contacts.length; i < iMax; i++) {
ids.push(contacts[i].contactId)
}
model.removeContacts(ids)
}
title: {
if (contacts.length == 0) {
return i18n.dtr("address-book-app", "No contact selected.")
} else if (contacts.length == 1) {
return contacts[0].displayLabel.label
} else {
return i18n.dtr("address-book-app", "Multiple contacts")
}
}
text: {
if (contacts.length == 1) {
return i18n.dtr("address-book-app", "Are you sure that you want to remove this contact?")
} else {
return i18n.dtr("address-book-app", "Are you sure that you want to remove all selected contacts?")
}
}
Button {
id: acceptButton
objectName: "removeContactsDialog.Yes"
anchors {
left: parent.left
right: parent.right
margins: units.gu(1)
}
text: i18n.dtr("address-book-app", "Yes")
color: UbuntuColors.green
action: Action {
shortcut: "return"
onTriggered: accepted()
}
}
Button {
id: cancelButton
objectName: "removeContactsDialog.No"
anchors {
left: parent.left
right: parent.right
margins: units.gu(1)
}
text: i18n.dtr("address-book-app", "No")
color: UbuntuColors.red
action: Action {
shortcut: "esc"
onTriggered: canceled()
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/Base/ContactDetailBase.qml 0000644 0000156 0000165 00000010511 12674534204 031720 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0 as QtContacts
import Ubuntu.Components.ListItems 1.3 as ListItem
FocusScope {
id: root
objectName: detail ? "base_" + detailToString(detail.type, -1) + "_" + index : ""
property variant action: null
property QtObject contact: null
property QtObject detail: null
property variant fields: null
// help to test used to retrieve the correct element
property int index: -1
property alias highlightOnFocus: highlight.visible
signal clicked()
function detailToString(detail, field)
{
// name
var nameMap = {}
nameMap[QtContacts.Name.FirstName] = "firstName"
nameMap[QtContacts.Name.LastName] = "lastName"
// phone
var phoneMap = {}
phoneMap[QtContacts.PhoneNumber.Number] = "phoneNumber"
// email
var emailMap = {}
emailMap[QtContacts.EmailAddress.EmailAddress] = "emailAddress"
// address
var addressMap = {}
addressMap[QtContacts.Address.Street] = "streetAddress"
addressMap[QtContacts.Address.Locality] = "localityAddress"
addressMap[QtContacts.Address.Region] = "regionAddress"
addressMap[QtContacts.Address.Postcode] = "postcodeAddress"
addressMap[QtContacts.Address.Country] = "countryAddress"
addressMap[QtContacts.Address.PostOfficeBox] = "postOfficeBoxAddress"
// im
var imMap = {}
imMap[QtContacts.OnlineAccount.AccountUri] = "imUri"
imMap[QtContacts.OnlineAccount.ServiceProvider] = "imProvider"
imMap[QtContacts.OnlineAccount.Protocol] = "imProtocol"
imMap[QtContacts.OnlineAccount.Capabilities] = "imCaps"
// organization
var organizationMap = {}
organizationMap[QtContacts.Organization.Name] = 'orgName'
organizationMap[QtContacts.Organization.Role] = 'orgRole'
organizationMap[QtContacts.Organization.Title] = 'orgTitle'
// SyncTarget
var syncTargetMap = {}
syncTargetMap[QtContacts.SyncTarget.SyncTarget] = "syncTarget"
// all
var detailMap = {}
detailMap[QtContacts.ContactDetail.Name] = nameMap
detailMap[QtContacts.ContactDetail.PhoneNumber] = phoneMap
detailMap[QtContacts.ContactDetail.Email] = emailMap
detailMap[QtContacts.ContactDetail.Address] = addressMap
detailMap[QtContacts.ContactDetail.OnlineAccount] = imMap
detailMap[QtContacts.ContactDetail.Organization] = organizationMap
detailMap[QtContacts.ContactDetail.SyncTarget] = syncTargetMap
// detail name
var detailNameMap = {}
detailNameMap[QtContacts.ContactDetail.Name] = "name"
detailNameMap[QtContacts.ContactDetail.PhoneNumber] = "phoneNumber"
detailNameMap[QtContacts.ContactDetail.Email] = "email"
detailNameMap[QtContacts.ContactDetail.Address] = "address"
detailNameMap[QtContacts.ContactDetail.OnlineAccount] = "onlineAccount"
detailNameMap[QtContacts.ContactDetail.SyncTarget] = "syncTarget"
if ((detail in detailMap) && (field in detailMap[detail])) {
return detailMap[detail][field]
} else if ((detail in detailNameMap) && (field == -1)){
return detailNameMap[detail]
} else {
console.debug("Unknown : [" + detail + "] [" + field + "]")
return "unknown"
}
}
Rectangle {
id: highlight
anchors.fill: parent
visible: root.activeFocus
color: Theme.palette.selected.background
z: -1
}
MouseArea {
anchors.fill: parent
onClicked: {
if (action) {
action.triggered(action)
}
root.clicked()
}
}
}
././@LongLink 0000000 0000000 0000000 00000000153 00000000000 011214 L ustar 0000000 0000000 address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/Base/ContactDetailGroupWithTypeBase.qml address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/Base/ContactDetailGroupWithTypeBa0000644 0000156 0000165 00000007744 12674534204 033331 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0 as QtContacts
ContactDetailGroupBase {
id: root
property string defaultIcon : "artwork:/protocol-other.png"
property ListModel typeModel
property bool typeModelReady: false
function getType(detail) {
if (typeModel) {
return typeModel.get(typeModel.getTypeIndex(detail))
} else {
return ""
}
}
function updateDetail(detail, index) {
if (typeModel) {
return typeModel.updateDetail(detail, index)
}
return false
}
typeModel: ListModel {
signal loaded()
function getTypeIndex(detail) {
var context = -1;
for (var i = 0; i < detail.contexts.length; i++) {
context = detail.contexts[i];
break;
}
var subType = -1;
// not all details have subTypes
if (detail.subTypes) {
for (var i = 0; i < detail.subTypes.length; i++) {
subType = detail.subTypes[i];
break;
}
}
if (context === QtContacts.ContactDetail.ContextHome) {
return 0
} else if (context === QtContacts.ContactDetail.ContextWork) {
return 1
} else if (context === QtContacts.ContactDetail.ContextOther) {
return 2
} else {
return 0 // default value is "Home"
}
}
function isNotAModelValue(value) {
for(var i=0; i < count; i++) {
if (value === get(i).value) {
return false
}
}
return true
}
function compareList(listA, listB) {
if (!listA && !listB) {
return true
}
if (!listA || !listB) {
return false
}
if (listA.length != listB.length) {
return false
}
for(var i=0; i < listA.length; i++) {
if (listA[i] != listB[i]) {
return false
}
}
return true
}
function updateDetail(detail, index) {
var modelData = get(index)
if (!modelData) {
return false
}
var newContext = detail.contexts.filter(isNotAModelValue)
newContext.push(modelData.value)
if (!compareList(newContext, detail.contexts)) {
detail.contexts = newContext
return true
}
return false
}
Component.onCompleted: {
append({"value": QtContacts.ContactDetail.ContextHome,
"label": i18n.dtr("address-book-app", "Home"),
"icon": null})
append({"value": QtContacts.ContactDetail.ContextWork,
"label": i18n.dtr("address-book-app", "Work"),
"icon": null})
append({"value": QtContacts.ContactDetail.ContextOther,
"label": i18n.dtr("address-book-app", "Other"),
"icon": null})
loaded()
}
}
onTypeModelChanged: root.typeModelReady = false
Connections {
target: root.typeModel
onLoaded: root.typeModelReady = true
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/Base/ContactDetailGroupBase.qml 0000644 0000156 0000165 00000010543 12674534204 032742 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
FocusScope {
id: root
property var details: []
readonly property alias detailDelegates: contents.children
readonly property int detailsCount: detailsModel.count
property variant inputFields: []
property QtObject contact: null
property int detailType: 0
property variant fields
property string title: null
property alias headerDelegate: headerItem.sourceComponent
property Component detailDelegate
property int minimumHeight: 0
property bool loaded: false
property bool showEmpty: true
signal newFieldAdded(int fieldIndex, QtObject field)
function reloadDetails(clearFields)
{
if (clearFields) {
root.inputFields = []
}
if (contact && detailType) {
root.details = contact.details(detailType)
} else {
root.details = []
}
}
function filterDetails(details) {
var result = []
for(var d in details) {
var isEmpty = true
for(var f in root.fields) {
var fieldValue = details[d].value(root.fields[f])
if (fieldValue && (String(fieldValue) !== "")) {
isEmpty = false
break;
}
}
if (!isEmpty) {
result.push(details[d])
}
}
return result
}
onContactChanged: reloadDetails(true)
onDetailTypeChanged: reloadDetails(true)
Connections {
target: root.contact
onContactChanged: reloadDetails(false)
}
implicitHeight: detailsCount > 0 ? contents.implicitHeight : minimumHeight
visible: implicitHeight > 0
// This model is used to avoid rebuild the repeater every time that the details change
// With this model the changed info on the fields will remain after add a new field
ListModel {
id: detailsModel
property var values: root.showEmpty && root.details ? root.details : filterDetails(root.details)
onValuesChanged: {
if (!values) {
root.inputFields = []
clear()
return
}
while (count > values.length) {
remove(count - 1)
}
var modelCount = count
for(var i=0; i < values.length; i++) {
if (modelCount <= i) {
append({"detail": values[i]})
} else if (get(i) != values[i]) {
set(i, {"detail": values[i]})
}
}
}
}
Column {
id: contents
anchors {
left: parent.left
right: parent.right
}
Loader {
id: headerItem
}
Repeater {
id: detailFields
model: detailsModel
Loader {
id: detailItem
sourceComponent: root.detailDelegate
Binding {
target: detailItem.item
property: "detail"
value: model.detail
}
Binding {
target: detailItem.item
property: "index"
value: index
}
onStatusChanged: {
if (status === Loader.Ready) {
var newFields = root.inputFields
newFields.push(detailItem.item)
root.inputFields = newFields
if (root.loaded) {
root.newFieldAdded(detailItem.item, item)
}
}
}
}
}
}
Component.onCompleted: root.loaded = true
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/Base/qmldir 0000644 0000156 0000165 00000000572 12674534204 027115 0 ustar pbuser pbgroup 0000000 0000000 module Ubuntu.AddressBook.Base
ContactDetailBase 0.1 ContactDetailBase.qml
ContactDetailItem 0.1 ContactDetailItem.qml
ContactDetailGroupBase 0.1 ContactDetailGroupBase.qml
ContactDetailGroupWithTypeBase 0.1 ContactDetailGroupWithTypeBase.qml
ContactExporter 0.1 ContactExporter.qml
RemoveContactsDialog 0.1 RemoveContactsDialog.qml
KeyboardRectangle 0.1 KeyboardRectangle.qml
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/Base/CMakeLists.txt 0000644 0000156 0000165 00000001725 12674534204 030443 0 ustar pbuser pbgroup 0000000 0000000 set(AB_CONTACT_COMMON_QMLS
ContactDetailBase.qml
ContactDetailItem.qml
ContactDetailGroupBase.qml
ContactDetailGroupWithTypeBase.qml
ContactExporter.qml
KeyboardRectangle.qml
RemoveContactsDialog.qml
qmldir
)
# make the files visible on qtcreator
add_custom_target(contact_common_QmlFiles ALL SOURCES ${AB_CONTACT_COMMON_QMLS})
if(INSTALL_COMPONENTS)
install(FILES ${AB_CONTACT_COMMON_QMLS} DESTINATION ${ADDRESS_BOOK_QMLPLUGIN_INSTALL_PREFIX}/Base)
endif()
#copy qml files to build dir to make it possible to run without install
foreach(QML_FILE ${AB_CONTACT_COMMON_QMLS})
add_custom_command(TARGET contact_common_QmlFiles PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E
copy ${CMAKE_CURRENT_SOURCE_DIR}/${QML_FILE} ${CMAKE_CURRENT_BINARY_DIR}/)
endforeach()
if (NOT ${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
add_dependencies(copyqmlfiles contact_common_QmlFiles)
endif()
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/Base/KeyboardRectangle.qml 0000644 0000156 0000165 00000003644 12674534204 032005 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
Item {
id: keyboardRect
property bool active: true
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: active && Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0
states: [
State {
name: "hidden"
when: keyboardRect.height == 0
},
State {
name: "shown"
when: keyboardRect.height == Qt.inputMethod.keyboardRectangle.height
}
]
function recursiveFindFocusedItem(parent) {
if (parent.activeFocus) {
return parent;
}
for (var i in parent.children) {
var child = parent.children[i];
if (child.activeFocus) {
return child;
}
var item = recursiveFindFocusedItem(child);
if (item != null) {
return item;
}
}
return null;
}
Connections {
target: Qt.inputMethod
onVisibleChanged: {
if (!Qt.inputMethod.visible) {
var focusedItem = recursiveFindFocusedItem(keyboardRect.parent);
if (focusedItem != null) {
focusedItem.focus = false;
}
}
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/AddressBook/Base/ContactDetailItem.qml 0000644 0000156 0000165 00000003171 12674534204 031750 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Contacts 0.1
ContactDetailBase {
id: root
readonly property alias fieldDelegates: fieldsColumn.children
property Component fieldDelegate: null
property alias spacing: fieldsColumn.spacing
implicitHeight: fieldsColumn.height
Column {
id: fieldsColumn
anchors {
left: parent.left
right: parent.right
}
spacing: units.gu(2)
height: childrenRect.height
Repeater {
id: fieldRepeater
model: root.fields
Loader {
id: field
sourceComponent: fieldDelegate
Binding {
target: item
property: "field"
value: modelData
}
Binding {
target: item
property: "detail"
value: root.detail
}
}
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/Ubuntu/CMakeLists.txt 0000644 0000156 0000165 00000000132 12674534204 025360 0 ustar pbuser pbgroup 0000000 0000000 add_custom_target(copyqmlfiles)
add_subdirectory(Contacts)
add_subdirectory(AddressBook)
address-book-app-0.2+16.04.20160323/src/imports/ABNewContactBottomEdge.qml 0000644 0000156 0000165 00000005224 12674534204 026302 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Contacts 0.1 as ContactsUI
BottomEdge {
id: bottomEdge
objectName: "bottomEdge"
property var modelToEdit: null
property var pageStack: null
property alias hintVisible: bottomEdgeHint.visible
property var _contactToEdit: null
// WORKAROUND: BottomEdge component loads the page async while draging it
// this cause a very bad visual.
// To avoid that we create it as soon as the component is ready and keep
// it invisible until the user start to drag it.
property var _realPage: null
function editContact(contact)
{
_contactToEdit = contact
commit()
}
hint {
id: bottomEdgeHint
action: Action {
iconName: "contact-new"
enabled: bottomEdge.enabled
onTriggered: bottomEdge.commit()
}
}
contentComponent: Item {
id: pageContent
implicitWidth: bottomEdge.width
implicitHeight: bottomEdge.height
children: bottomEdge._realPage
}
onCommitCompleted: {
if (bottomEdge._contactToEdit)
editorPage.contact = bottomEdge._contactToEdit
bottomEdge._contactToEdit = null
}
onCollapseCompleted: {
_realPage = editorPageBottomEdge.createObject(null)
}
Component.onCompleted: {
_realPage = editorPageBottomEdge.createObject(null)
}
Component {
id: editorPageBottomEdge
ABContactEditorPage {
implicitWidth: bottomEdge.width
implicitHeight: bottomEdge.height
contact: ContactsUI.ContactsJS.createEmptyContact("", bottomEdge)
model: bottomEdge.modelToEdit
enabled: bottomEdge.status === BottomEdge.Committed
active: bottomEdge.status === BottomEdge.Committed
visible: bottomEdge.status !== BottomEdge.Hidden
onCanceled: bottomEdge.collapse()
onContactSaved: bottomEdge.collapse()
pageStack: bottomEdge.pageStack
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/MainWindow.qml 0000644 0000156 0000165 00000016111 12674534204 024131 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3 as Popups
import Unity.InputInfo 0.1
MainView {
id: mainWindow
objectName: "addressBookAppMainWindow"
property string modelErrorMessage: ""
readonly property bool appActive: Qt.application.active
signal applicationReady()
function contact(contactId)
{
mainStack.resetStack()
if (mainStack.contactListPage) {
mainStack.contactListPage.showContactWithId(contactId)
} else {
console.error("Contact preview requested but ContactListPage not loaded")
}
}
function create(phoneNumber)
{
mainStack.resetStack()
if (mainStack.contactListPage) {
mainStack.contactListPage.createContactWithPhoneNumber(phoneNumber)
} else {
console.error("Contact creation requested but ContactListPage not loaded")
}
}
function pick(single)
{
console.debug("Pick mode:" + single)
pickWithTransfer(single === "true", null)
}
function pickWithTransfer(single, activeTransfer)
{
mainStack.resetStack()
if (mainStack.contactListPage) {
mainStack.contactListPage.startPickMode(single, activeTransfer)
} else {
console.error("Pick mode requested but ContactListPage not loaded")
}
}
function importvcard(_url)
{
importvcards([_url])
}
function importvcards(_urls)
{
mainStack.resetStack()
if (mainStack.contactListPage) {
mainStack.contactListPage.importContact(_urls)
} else {
console.error("Import vcard requested but ContactListPage not loaded")
}
}
width: units.gu(90)
height: units.gu(71)
anchorToKeyboard: false
InputDeviceModel {
id: miceModel
deviceFilter: InputInfo.Mouse
}
InputDeviceModel {
id: touchPadModel
deviceFilter: InputInfo.TouchPad
}
InputDeviceModel {
id: keyboardsModel
deviceFilter: InputInfo.Keyboard
}
ABAdaptivePageLayout {
id: mainStack
objectName: "mainStack"
property var contactListPage: null
property var bottomEdge: null
readonly property bool bottomEdgeOpened: (bottomEdge && bottomEdge.status === BottomEdge.Committed)
readonly property bool hasMouse: ((miceModel.count > 0) || (touchPadModel.count > 0))
readonly property bool hasKeyboard: (keyboardsModel.count > 0)
function resetStack()
{
mainStack.removePages(primaryPage);
}
function _nextItemInFocusChain(item, foward)
{
var next = item.nextItemInFocusChain(foward)
var first = next
//WORKAROUND: SDK does not allow us to disable focus for items due bug: #1514822
//because of that we need this
while (!next || !next.hasOwnProperty("_allowFocus")) {
next = next.nextItemInFocusChain(foward)
// avoid loop
if (next === first) {
next = null
break
}
}
if (next) {
next.forceActiveFocus()
}
return next
}
primaryPage: contactPage
onContactListPageChanged: {
if (contentHubLoader.status === Loader.Ready) {
contentHubLoader.item.pageStack = mainStack
} else {
contentHubLoader.setSource(Qt.resolvedUrl("ContentHubProxy.qml"), {"pageStack": mainStack})
}
}
anchors.fill: parent
layouts: [
PageColumnsLayout {
when: mainStack.width >= units.gu(90)
PageColumn {
maximumWidth: units.gu(50)
minimumWidth: units.gu(40)
preferredWidth: units.gu(40)
}
PageColumn {
fillWidth: true
}
},
PageColumnsLayout {
when: true
PageColumn {
fillWidth: true
}
}
]
onColumnsChanged: {
if (mainStack.columns > 1) {
if (mainStack.contactListPage)
mainStack.contactListPage.delayFetchContact()
else
mainStack.addPageToNextColumn(contactPage, Qt.resolvedUrl("./ABMultiColumnEmptyState.qml"))
}
}
}
ABContactListPage {
id: contactPage
pageStack: mainStack
}
Component.onCompleted: {
application.elapsed()
i18n.domain = "address-book-app"
i18n.bindtextdomain("address-book-app", i18nDirectory)
mainWindow.applicationReady()
}
// WORKAROUND: Due the missing feature on SDK, they can not detect if
// there is a mouse attached to device or not. And this will cause the
// bootom edge component to not work correct on desktop.
// We will consider that a mouse is always attached until it get implement on SDK.
Binding {
target: QuickUtils
property: "mouseAttached"
value: mainStack.hasMouse
}
Component {
id: errorDialog
Popups.Dialog {
id: dialogue
title: i18n.tr("Error")
text: mainWindow.modelErrorMessage
Button {
text: i18n.tr("Cancel")
gradient: UbuntuColors.greyGradient
onClicked: PopupUtils.close(dialogue)
}
}
}
Connections {
target: UriHandler
onOpened: {
for (var i = 0; i < uris.length; ++i) {
application.parseUrl(uris[i])
}
}
}
Loader {
id: contentHubLoader
//We can not use async load, the export requested signal can be received before the component get ready
//asynchronous: true
source: Qt.resolvedUrl("ContentHubProxy.qml")
onStatusChanged: {
if (status === Loader.Ready) {
item.pageStack = mainStack
}
}
}
// If application was called from uri handler and lost the focus reset the app to normal state
onAppActiveChanged: {
if (appActive) {
mainStack.forceActiveFocus()
}
if (!appActive && mainStack.contactListPage) {
mainStack.contactListPage.returnToNormalState()
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/ABContactEditorPage.qml 0000644 0000156 0000165 00000003720 12674534204 025621 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.AddressBook.ContactEditor 0.1
ContactEditorPage {
id: root
objectName: "contactEditorPage"
property alias backIconName: backAction.iconName
// Property used on unit tests
readonly property alias saveActionEnabled: saveAction.enabled
leadingActions: [
Action {
id: backAction
objectName: "cancel"
name: "cancel"
text: i18n.tr("Cancel")
iconName: "down"
enabled: root.active && root.enabled
shortcut: "Esc"
onTriggered: {
root.cancel()
if (root.pageStack.contactListPage)
root.pageStack.contactListPage.forceActiveFocus()
}
}
]
headerActions: [
Action {
id: saveAction
objectName: "save"
name: "save"
text: i18n.tr("Save")
iconName: "ok"
enabled: root.isContactValid && root.active && root.enabled
shortcut: "Ctrl+s"
onTriggered: root.save()
}
]
onContactSaved: {
if (pageStack.contactListPage) {
pageStack.contactListPage.moveListToContact(contact)
pageStack.contactListPage.forceActiveFocus()
}
}
}
address-book-app-0.2+16.04.20160323/src/imports/CMakeLists.txt 0000644 0000156 0000165 00000001036 12674534204 024102 0 ustar pbuser pbgroup 0000000 0000000 project(imports)
set(ADDRESS_BOOK_APP_QMLS
ABAdaptivePageLayout.qml
ABContactListPage.qml
ABContactEditorPage.qml
ABContactViewPage.qml
ABEmptyState.qml
ABNewContactBottomEdge.qml
ABMultiColumnEmptyState.qml
ContentHubProxy.qml
MainWindow.qml
)
install(FILES ${ADDRESS_BOOK_APP_QMLS}
DESTINATION ${ADDRESS_BOOK_APP_DIR}/imports
)
# make the files visible on qtcreator
add_custom_target(address_book_QMlFiles ALL SOURCES ${ADDRESS_BOOK_APP_QMLS})
add_subdirectory(Settings)
add_subdirectory(Ubuntu)
address-book-app-0.2+16.04.20160323/src/imports/ABContactListPage.qml 0000644 0000156 0000165 00000071364 12674534204 025317 0 ustar pbuser pbgroup 0000000 0000000 /*
* Copyright (C) 2012-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import QtQuick 2.4
import QtContacts 5.0
import Ubuntu.Components 1.3
import Ubuntu.Components.ListItems 1.3 as ListItem
import Ubuntu.Components.Popups 1.3 as Popups
import Ubuntu.Contacts 0.1 as ContactsUI
import Ubuntu.Content 1.3 as ContentHub
import Ubuntu.AddressBook.Base 0.1
import Ubuntu.AddressBook.ContactShare 0.1
Page {
id: mainPage
objectName: "contactListPage"
property var viewPage: null
property var emptyPage: null
property bool pickMode: false
property alias contentHubTransfer: contactExporter.activeTransfer
property bool pickMultipleContacts: false
property QtObject contactIndex: null
property alias contactManager: contactList.manager
property var _busyDialog: null
property bool _importingTestData: false
property bool _creatingContact: false
readonly property string currentViewContactId: viewPage && viewPage.contact ? viewPage.contact.contactId : ""
readonly property bool isEmpty: (contactList.count === 0)
readonly property bool allowToQuit: (application.callbackApplication.length > 0)
readonly property var contactModel: contactList.listModel ? contactList.listModel : null
readonly property bool searching: state === "searching"
readonly property string headerTitle: pageHeader.title
// this function is used to reset the contact list page to the default state if it was called
// from the uri. For example when called to add a new contact
function returnToNormalState()
{
application.callbackApplication = ""
}
function createContactWithPhoneNumber(phoneNumber)
{
var newContact = ContactsJS.createEmptyContact(phoneNumber, mainPage);
if (bottomEdgeLoader.status == Loader.Ready) {
bottomEdgeLoader.editContact(newContact)
} else {
contactList.currentIndex = -1
var incubator = pageStack.addPageToNextColumn(mainPage,
Qt.resolvedUrl("ABContactEditorPage.qml"),
{ model: mainPage.contactModel,
contact: newContact,
backIconName: 'back',
enabled: false
})
}
}
function clearViewPage()
{
viewPage = null
}
function openViewPage(viewPageProperties)
{
if (currentViewContactId === viewPageProperties.contact.contactId) {
return
}
if (viewPage) {
viewPage.Component.onDestruction.disconnect(clearViewPage)
}
pageStack.removePages(mainPage)
viewPage = null
viewPage = pageStack.addFileToNextColumnSync(mainPage, Qt.resolvedUrl("ABContactViewPage.qml"), viewPageProperties)
viewPage.Component.onDestruction.connect(clearViewPage)
}
function showContact(contact)
{
var currentContact = contactList.listModel.contacts[contactList.currentIndex]
if (currentContact && (mainPage.currentViewContactId === currentContact.contactId)) {
// contact view already opened with this contact
return
}
// go back to normal state if not searching
if ((state !== "searching") &&
(state !== "vcardImported")) {
mainPage.state = "default";
}
openViewPage({model: contactList.listModel,
contact: contact});
}
function showEmptyPage(openBottomEdge)
{
if (pageStack.columns === 1)
return
if (!emptyPage) {
contactList.currentIndex = -1
pageStack.removePages(mainPage)
if (pageStack.columns > 1) {
emptyPage = pageStack.addFileToNextColumnSync(pageStack.primaryPage,
Qt.resolvedUrl("ABMultiColumnEmptyState.qml"),
{ 'headerTitle': "",
'pageStack': mainPage.pageStack })
emptyPage.Component.onDestruction.connect(function() {
mainPage.emptyPage = null
})
}
}
if (openBottomEdge) {
mainPage.emptyPage.commitBottomEdge()
}
}
function showSettingsPage()
{
pageStack.removePages(mainPage)
var incubator = pageStack.addPageToNextColumn(mainPage,
Qt.resolvedUrl("./Settings/SettingsPage.qml"),
{"contactListModel": contactList.listModel})
incubator.onStatusChanged = function(status) {
if (status === Component.Ready) {
incubator.object.onActiveChanged.connect(function(active) {
if (!incubator.object.active) {
mainPage.delayFetchContact()
contactList.forceActiveFocus()
}
})
}
}
}
function showContactWithId(contactId)
{
openViewPage({model: contactList.listModel,
contactId: contactId});
}
function importContact(urls)
{
mainPage._busyDialog = PopupUtils.open(busyDialogComponent, mainPage)
var importing = false
for(var i=0, iMax=urls.length; i < iMax; i++) {
var url = urls[i]
if (url && url != "") {
importing = true
contactList.listModel.importContacts(url)
}
}
if (!importing) {
PopupUtils.close(mainPage._busyDialog)
mainPage._busyDialog = null
}
}
function startPickMode(isSingleSelection, activeTransfer)
{
contentHubTransfer = activeTransfer
pickMode = true
pickMultipleContacts = !isSingleSelection
contactList.startSelection()
}
function moveListToContact(contact)
{
if ((state !== "searching") &&
(state !== "vcardImported")) {
mainPage.state = "default"
}
contactIndex = contact
// this means a new contact was created
if (mainPage.allowToQuit) {
application.goBackToSourceApp()
}
}
function onNewContactSaved(contact) {
_creatingContact = true
moveListToContact(contact)
if (pageStack.columns > 1) {
showContact(contact);
}
}
// Delay contact fetch for some msecs (check 'fetchNewContactTimer')
function delayFetchContact()
{
fetchNewContactTimer.restart()
}
function fetchContact()
{
if (pageStack.columns > 1 && !contactList.showNewContact) {
var currentContact = null
if (contactList.currentIndex >= 0)
currentContact = contactList.listModel.contacts[contactList.currentIndex]
if (!currentContact) {
showEmptyPage()
return
} else if (currentContact && (mainPage.currentViewContactId === currentContact.contactId)) {
return
}
console.debug("Will fetch new contact")
contactList.view._fetchContact(contactList.currentIndex, currentContact)
}
}
header: PageHeader {
id: pageHeader
property alias leadingActions: leadingBar.actions
property alias trailingActions: trailingBar.actions
property alias sectionsModel: sections.model
title: i18n.tr("Contacts")
//flickable: contactList.view
trailingActionBar {
id: trailingBar
}
leadingActionBar {
id: leadingBar
}
extension: Sections {
id: sections
anchors {
left: parent.left
leftMargin: units.gu(2)
bottom: parent.bottom
}
onSelectedIndexChanged: {
switch (selectedIndex) {
case 0:
contactList.showAllContacts()
break;
case 1:
contactList.showFavoritesContacts()
break;
default:
break;
}
}
}
}
// This timer is to avoid fetch unecessary contact if the user select the contacts too fast
// while navigating on contact list with keyboard
Timer {
id: fetchNewContactTimer
interval: 300
repeat: false
onTriggered: mainPage.fetchContact()
}
title: i18n.tr("Contacts")
flickable: null
ContactsUI.ContactListView {
id: contactList
objectName: "contactListView"
focus: true
showImportOptions: !mainPage.pickMode &&
pageStack.bottomEdge &&
(pageStack.bottomEdge.status !== BottomEdge.Committed)
anchors {
top: parent.top
topMargin: pageHeader.height
left: parent.left
bottom: keyboard.top
right: parent.right
}
currentIndex: -1
filterTerm: searchField.text
multiSelectionEnabled: true
multipleSelection: (mainPage.pickMode && mainPage.pickMultipleContacts) || !mainPage.pickMode
showNewContact: (pageStack.columns > 1) && pageStack.bottomEdge && (pageStack.bottomEdge.status === BottomEdge.Committed)
highlightSelected: !showNewContact && pageStack.hasKeyboard && !mainPage._creatingContact
onAddContactClicked: mainPage.createContactWithPhoneNumber(label)
onContactClicked: mainPage.showContact(contact)
onIsInSelectionModeChanged: mainPage.state = isInSelectionMode ? "selection" : "default"
onSelectionCanceled: {
if (pickMode) {
if (contentHubTransfer) {
contentHubTransfer.state = ContentHub.ContentTransfer.Aborted
}
pickMode = false
contentHubTransfer = null
application.returnVcard("")
}
mainPage.state = "default"
}
onError: pageStack.contactModelError(error)
onCountChanged: {
if (mainPage.state === "searching") {
currentIndex = 0
}
mainPage.delayFetchContact()
}
onCurrentIndexChanged: {
if (!mainPage.contactIndex)
mainPage.delayFetchContact()
}
Keys.onReturnPressed: {
var currentContact = contactList.listModel.contacts[contactList.currentIndex]
if (mainPage.currentViewContactId === currentContact.contactId)
return
contactList.view._fetchContact(contactList.currentIndex, currentContact)
}
//WORKAROUND: SDK does not allow us to disable focus for items due bug: #1514822
//because of that we need this
Keys.onRightPressed: {
// only move focus away when in edit mode
if (pageStack.bottomEdge.status === BottomEdge.Committed) {
var next = pageStack._nextItemInFocusChain(view, true)
if (next === searchField) {
pageStack._nextItemInFocusChain(next, true)
}
}
}
Keys.onTabPressed: {
var next = pageStack._nextItemInFocusChain(view, true)
if (next === searchField) {
pageStack._nextItemInFocusChain(next, true)
}
}
}
TextField {
id: searchField
//WORKAROUND: SDK does not allow us to disable focus for items due bug: #1514822
//because of that we need this
readonly property bool _allowFocus: true
anchors {
top: parent ? parent.top : undefined
left: parent ? parent.left : undefined
right: parent ? parent.right : undefined
margins: units.gu(1)
rightMargin: units.gu(2)
}
visible: false
inputMethodHints: Qt.ImhNoPredictiveText
placeholderText: i18n.tr("Search...")
onVisibleChanged: {
if (visible) {
if (activeFocus) {
Qt.inputMethod.show()
} else {
searchField.forceActiveFocus()
}
}
}
Keys.onTabPressed: contactList.forceActiveFocus()
Keys.onDownPressed: contactList.forceActiveFocus()
}
state: "default"
states: [
State {
id: defaultState
name: "default"
property list leadingActions: [
Action {
visible: mainPage.allowToQuit
iconName: "back"
text: i18n.tr("Quit")
onTriggered: {
application.goBackToSourceApp()
mainPage.returnToNormalState()
}
}
]
property list trailingActions: [
Action {
text: i18n.tr("Search")
iconName: "search"
visible: !mainPage.isEmpty
enabled: visible && (mainPage.state === "default")
shortcut: "Ctrl+F"
onTriggered: {
if (viewPage) {
viewPage.cancelEdit()
}
mainPage.state = "searching"
contactList.showAllContacts()
if (pageStack.bottomEdge) {
pageStack.bottomEdge.collapse()
} else {
showEmptyPage(false)
}
searchField.forceActiveFocus()
}
},
Action {
iconName: "contact-new"
enabled: visible && (!pageStack.bottomEdge || (pageStack.bottomEdge.enabled && (pageStack.bottomEdge.status === BottomEdge.Hidden)))
visible: (pageStack.columns > 1)
shortcut: "Ctrl+N"
onTriggered: {
if (pageStack.bottomEdge) {
pageStack.bottomEdge.commit()
} else {
showEmptyPage(true)
}
}
},
Action {
visible: (application.isOnline && (contactList.syncEnabled || application.serverSafeMode))
text: contactList.syncing ? i18n.tr("Syncing") : i18n.tr("Sync")
iconName: application.serverSafeMode ? "reset" : "reload"
enabled: !contactList.syncing && !application.updating
onTriggered: {
if (application.serverSafeMode) {
application.startUpdate()
} else {
contactList.sync()
}
}
},
Action {
text: i18n.tr("Settings")
iconName: "settings"
onTriggered: mainPage.showSettingsPage()
}
]
PropertyChanges {
target: pageHeader
// TRANSLATORS: this refers to all contacts
sectionsModel: [i18n.tr("All"), i18n.tr("Favorites")]
leadingActions: defaultState.leadingActions
trailingActions: defaultState.trailingActions
}
PropertyChanges {
target: searchField
text: ""
}
PropertyChanges {
target: bottomEdgeLoader
enabled: true
}
},
State {
id: searchingState
name: "searching"
property list leadingActions: [
Action {
iconName: "back"
text: i18n.tr("Cancel")
enabled: (mainPage.state === "searching") && mainPage.active
shortcut:"Esc"
onTriggered: {
mainPage.head.sections.selectedIndex = 0
mainPage.state = "default"
contactList.forceActiveFocus()
}
}
]
PropertyChanges {
target: pageHeader
contents: searchField
leadingActions: searchingState.leadingActions
}
PropertyChanges {
target: bottomEdgeLoader
enabled: false
}
PropertyChanges {
target: searchField
visible: true
focus: true
text: ""
}
},
State {
id: selectionState
name: "selection"
property list leadingActions: [
Action {
objectName: "cancel"
name: "cancel"
text: i18n.tr("Cancel selection")
iconName: "back"
enabled: mainPage.state === "selection"
onTriggered: contactList.cancelSelection()
shortcut: "Esc"
}
]
property list trailingActions: [
Action {
text: (contactList.selectedItems.count === contactList.count) ? i18n.tr("Unselect All") : i18n.tr("Select All")
iconName: "select"
onTriggered: {
if (contactList.selectedItems.count === contactList.count) {
contactList.clearSelection()
} else {
contactList.selectAll()
}
}
visible: contactList.multipleSelection && !mainPage.isEmpty
},
Action {
objectName: "share"
text: i18n.tr("Share")
iconName: mainPage.pickMode ? "tick" : "share"
enabled: (contactList.selectedItems.count > 0)
visible: contactList.isInSelectionMode
onTriggered: {
var contacts = []
var items = contactList.selectedItems
for (var i=0, iMax=items.count; i < iMax; i++) {
contacts.push(items.get(i).model.contact)
}
contactExporter.start(contacts)
contactList.endSelection()
}
},
Action {
objectName: "delete"
text: i18n.tr("Delete")
iconName: "delete"
enabled: (contactList.selectedItems.count > 0)
visible: contactList.isInSelectionMode && !mainPage.pickMode
onTriggered: {
var contacts = []
var items = contactList.selectedItems
for (var i=0, iMax=items.count; i < iMax; i++) {
contacts.push(items.get(i).model.contact)
}
var dialog = PopupUtils.open(removeContactDialog, null)
dialog.contacts = contacts
contactList.endSelection()
}
}
]
PropertyChanges {
target: pageHeader
leadingActions: selectionState.leadingActions
trailingActions: selectionState.trailingActions
}
PropertyChanges {
target: bottomEdgeLoader
enabled: false
}
},
State {
id: vcardImportedState
name: "vcardImported"
property list leadingActions: [
Action {
objectName: "cancel"
name: "cancel"
iconName: "back"
text: i18n.tr("Back")
onTriggered: {
contactList.forceActiveFocus()
mainPage.state = "default"
importedIdsFilter.ids = []
}
}
]
PropertyChanges {
target: pageHeader
leadingActions: vcardImportedState.leadingActions
title: i18n.tr("Imported contacts")
}
PropertyChanges {
target: bottomEdgeLoader
enabled: false
}
}
]
//WORKAROUND: we need to call 'changeFilter' manually to make sure that the model will be cleared
// before update it with the new model. This is faster than do a match of contacts
transitions: [
Transition {
from: "vcardImported"
ScriptAction {
script: contactList.listModel.changeFilter(null)
}
},
Transition {
to: "vcardImported"
ScriptAction {
script: contactList.listModel.changeFilter(importedIdsFilter)
}
}
]
onActiveChanged: {
if (active && contactList.showAddNewButton) {
contactList.positionViewAtBeginning()
}
if (active && (state === "searching")) {
searchField.forceActiveFocus()
} else if (active) {
contactList.forceActiveFocus()
}
}
IdFilter {
id: importedIdsFilter
}
KeyboardRectangle {
id: keyboard
active: mainPage.active &&
(pageStack.bottomEdge && (pageStack.bottomEdge.status === BottomEdge.Hidden))
}
ABEmptyState {
id: emptyStateScreen
anchors {
verticalCenter: parent.verticalCenter
verticalCenterOffset: contactList.headerItem ? contactList.headerItem.height / 2 : 0
left: parent.left
right: parent.right
leftMargin: units.gu(6)
rightMargin: units.gu(6)
}
height: childrenRect.height
visible: ((pageStack.columns === 1) &&
!contactList.busy &&
!contactList.favouritesIsSelected &&
mainPage.isEmpty &&
!(contactList.filterTerm && contactList.filterTerm !== ""))
text: mainPage.pickMode ?
i18n.tr("You have no contacts.") :
i18n.tr("Create a new contact by swiping up from the bottom of the screen.")
}
ContactExporter {
id: contactExporter
contactModel: contactList.listModel
exportToDisk: mainPage.pickMode
onDone: {
mainPage.pickMode = false
mainPage.state = "default"
application.returnVcard(outputFile)
}
onContactsFetched: {
// Share contacts to an application chosen by the user
if (!mainPage.pickMode) {
contactExporter.dismissBusyDialog()
pageStack.addPageToNextColumn(mainPage,
contactShareComponent,
{contactModel: contactExporter.contactModel,
contacts: contacts })
}
}
}
Component {
id: removeContactDialog
RemoveContactsDialog {
id: removeContactsDialogMessage
onCanceled: {
PopupUtils.close(removeContactsDialogMessage)
}
onAccepted: {
removeContacts(contactList.listModel)
PopupUtils.close(removeContactsDialogMessage)
}
}
}
Component {
id: busyDialogComponent
Popups.Dialog {
id: busyDialog
property alias allowToClose: closeButton.visible
property alias showActivity: busyIndicator.visible
title: i18n.tr("Importing...")
ActivityIndicator {
id: busyIndicator
running: visible
visible: true
}
Button {
id: closeButton
text: i18n.tr("Close")
visible: false
color: UbuntuColors.red
onClicked: {
PopupUtils.close(mainPage._busyDialog)
mainPage._busyDialog = null
}
}
}
}
Component {
id: contactShareComponent
ContactSharePage {
objectName: "contactSharePage"
}
}
Component.onCompleted: {
application.elapsed()
if ((typeof(TEST_DATA) !== "undefined") && (TEST_DATA != "")) {
mainPage._importingTestData = true
contactList.listModel.importContacts("file://" + TEST_DATA)
}
if (pageStack) {
pageStack.contactListPage = mainPage
mainPage.delayFetchContact()
}
}
Loader {
id: bottomEdgeLoader
enabled: false
active: (pageStack.columns === 1) && bottomEdgeLoader.enabled
asynchronous: true
sourceComponent: ABNewContactBottomEdge {
id: bottomEdge
parent: mainPage
modelToEdit: mainPage.contactModel
hint.flickable: contactList.view
pageStack: mainPage.pageStack
enabled: mainPage.active
}
}
Action {
iconName: "contact-new"
enabled: mainPage.active &&
(bottomEdgeLoader.status === Loader.Ready) &&
(bottomEdgeLoader.item.status === BottomEdge.Hidden)
shortcut: "Ctrl+N"
onTriggered: {
bottomEdgeLoader.item.commit()
}
}
Binding {
target: pageStack
property: 'bottomEdge'
value: bottomEdgeLoader.item
when: bottomEdgeLoader.status == Loader.Ready
}
Connections {
target: mainPage.contactModel
onContactsChanged: {
if (contactIndex) {
contactList.positionViewAtContact(mainPage.contactIndex)
mainPage.contactIndex = null
// at this point the operation has finished already
mainPage._creatingContact = false
mainPage.delayFetchContact()
}
}
onImportCompleted: {
if (mainPage._importingTestData) {
mainPage._importingTestData = false
return
}
if (error !== ContactModel.ImportNoError) {
console.error("Fail to import vcard:" + error)
mainPage._busyDialog.title = i18n.tr("Fail to import contacts!")
mainPage._busyDialog.allowToClose = true
mainPage._busyDialog.showActivity = false
} else {
var importedIds = ids
importedIds.concat(importedIdsFilter.ids)
importedIdsFilter.ids = importedIds
console.debug("Imported ids:" + importedIds)
mainPage.state = "vcardImported"
if (mainPage._busyDialog) {
PopupUtils.close(mainPage._busyDialog)
mainPage._busyDialog = null
}
}
}
}
Connections {
target: pageStack.bottomEdge
onCommitCompleted: {
if (mainPage.state !== "default") {
mainPage.head.sections.selectedIndex = 0
mainPage.state = "default"
}
}
onCollapseCompleted: {
if (!mainPage._creatingContact) {
if (contactList.currentIndex === -1)
contactList.currentIndex = 0
mainPage.delayFetchContact()
}
if (mainPage.status === "default") {
contactList.forceActiveFocus()
}
}
}
}
address-book-app-0.2+16.04.20160323/src/artwork/ 0000755 0000156 0000165 00000000000 12674534512 021340 5 ustar pbuser pbgroup 0000000 0000000 address-book-app-0.2+16.04.20160323/src/artwork/protocol-other.svg 0000644 0000156 0000165 00000002467 12674534204 025050 0 ustar pbuser pbgroup 0000000 0000000
address-book-app-0.2+16.04.20160323/src/artwork/address-book-app.png 0000755 0000156 0000165 00000012410 12674534204 025200 0 ustar pbuser pbgroup 0000000 0000000 ‰PNG
IHDR Ó?1 sBITÛáOà pHYs b b8z™Û tEXtSoftware www.inkscape.org›î< †IDATxÚíisÉyǧ{z0 €”xIER’¥%×);v¼Îîz-ï:åÊ«¤ÊßÁyáò‹|‚|…|€|‚ä]Ê©ŠÅëµÖ»¦´:¬Ãº%RO70W AäÄ1GÏô¿‹bQØót?¿çyþÝs€l¾~¥ ½i¤Û÷oާiº8ƒŠÚñ=è—ù>f“xŽ+:Ç÷¬S@8‰èÒ/û _ZúQ {d”=€'~Çqº”ÓݸHtBϯ~è
ý-ôw@ë¿Û(“.ÑoýÉþ±ÔOxD~9 z²§}g»í3®}Øm¿ôvä]‰úr@„?o¶m[k+[ð†Ð†$•r…µó¿®?oý é„1WÐÛÁ°gD0ñË QÝíi¡ßjõÿøw÷7©ê`sCöŸŸì¿þ7Ã%¢L¿<ÕÝž¶àiÑoš–±ø8ÜÅ÷[1@"Œ¾<ùmþV pú-ËT',ZèoýñÄ/O D˜þ·Ó¿cÙV½^§v˜K‘
úã 19Éõ&,Ë0=¤
ÀO)õþÐÀ$§?çw·ö,®‚섚Ršëäv„"E\ nô·¶ý[ë`Å sRÝ7‚"Kü n×ölŸ ʶ
qì5ýB8€IH´.kk¿U lG*g! ¤¦¿óÚžíÀaÖ¢˜Ñ ˆñ%ÍŽŽ#‰³ 2'þ½[„%ˆ>` ?bƒr@?@æ;¹¢'„v ‹1ý±¼‰Ñ‰˜ÝôGŒôK ¸=B‹àÈ8€ÅŒ’ø?»ÁýÒ èÐ"8J>? {0(y ‰ãò½QÐJd‹ôl‚~*f€ÄqÉ+@?Æ%i€ì‰Ö â3.!J€>Æ? ú1.I+ dèHLðÙúh¾ºŠ‚~Ð/-ýÁK È4±\Å¤è£æ*
úA¿´ôS {ÐÄu„ Š«(èýÒÒï_€ìAôEcªXˆ^ú½ôûà*
ú‘ø¥¥ßÛ
Ùú£7U,àC}È¡\EA?¿´ô^ {@´§Šph ô…*
úA¿´ô÷W {@|¦Šùth ô#1Uôƒ~™]Å4èé\ÅÓUÌÃC} 9WQÐúevƒìý2»Š!ñ}™]EA?è—ÙU²ôËì*†Äôevý _fW1ÈžHÑO@¿¯k /râ'@ß? úA¿tôw ²'Æ£b@‰_Nô¨ ôË0$
úE•= ? ä¹;ï/Öb;ŸnÇ?=Ç2ñÿ×¥uUe„Ò@¤õ=ˆö³ Hü_P,-רjt W@ØÇÇyã è#ñKÛ(èýR è[ö€~1Ö @‰ ôƒ~T Ðï»ì‰^û§ÇSIëD qÙ% ãÄl\cŒQê ‘®eôƒ~™‡Ä0¥=2Ša>‘øeÅ”‚~™‡Ä0¥=2Š}pÒýâ7*
úA¿¼åL!ô#KÊÚHX‰M”’Æ@?è—ýP »=Qmÿ“{‚wšÍâ‡~µnu›ÿ %h2A‘ø=i±¸'ØÅfúW6Í¥WÆÊ¦Q(Z¥š³ãÕ!d3êÄáÄÔa ²'à ð‹’bÕ¾ó¸òä¥Q®Úû¼‡D©f¾\3¯ßS†ÒêìÑÄ;3z&I>è÷; |Dÿú½êÃ%žížþ°T¶n=¨ÜyTÔ¾u6Ýo€þ˜Ðïk øB‰m+wžT¾~P³L{€NœG/ê_Õæg“sfˆRÐ/#ú¾€/”ðÄi¡°Q°<éͱÉG5.>úv¶»R ôãF¿âÃ¥~
}¾fü÷ç›^Ñßnëy‹wûbÕ ýÒïyð‹’¥óÒÕ‚mûÒy½îüf!ÿá{Ù™# пWûçƒî Žúž€_3²¸\¿t¥äø9m\}vôC•g@ßµísOpté÷JùxØFÞüìZ¹×ÝžþVÆ¿¿šç‡ýî>ýÌWŸæÑ°Ü•81•ß^)š–Ìš–ò»…’¹¥´@„ÿÎú|ÐåÅ·Nrù^”RÕúâzôG§j
%캓þæÓWõÎߤSêhŽù}ÜG/j/ÖàîJç÷Ðßw ø>~.Cî–vüR×ÈHV=6¦©Ô_¾üKÙ¶Áü¶/Nœ¬ýü‚ÕÏò/~Þî=«æK;·üZã{R'ÓÚ«
³ZóÒ|ɼÿ¼~f:!9úÖÜ ã'ÿÈ.¼;’ͪª§Äß_ 7þÛO\>Y,Á¶êS ¯…²½úÚ°ýYÜ~X–9 ÌÙµOÊÎ3—Í$“:§¿•ûˆ
`Ò¿¸\/”w¦®z4ö–
Ù4M'«›V±lyn¯?/VŒ£ãš|èÏU?ù©zîÂp.—ÔýöÞØÈ—ã2ÑèçíÁ’Ë”+ŸÝ3Ï“‡Ø¡Œº‘7‹Uу5©Àœ™«|ò”£ŸÍéºÞ$Ÿvžù
5ýŸzeB¡ßpƒ<_uÑ?)}OS™Õ†kÎzÁ¬x·0X|i˜lFiìÑ7ff+BÎ]ÈesÉdrú´9¡îÿÿºdBÑÏÛò†aº)šTâ cx‰˜Ò5Ãpòe›Ûtq`XöÚ†=9ç 0ŽÏ–.~JçÏg‚'ټشÐïT>áÑOüî• E?o/Vê»É}ÁÓ|7®idtX=”S‹%«P±yAÄkÏ׌ÉQKôëÇgJBæÏ熇9úªªºjž¸%þ]½2qÐoµu·žSzo.à+æ\Få_¼”«N±Â¥‘ÓGIX{ÍW#©¸¡?}¼ÀÑ?{~x8§'“LeMö…JüþÊžý ä}®RÕ…Ó¡~oáå«älš%8ý†éTë6ÿª›Ši:V¨ÅJ¬Î‡Õ8úýX9{Ž£ŸL¥:ѹâß»WÁ®þ%JɹLŠ
x%w%Q MÍ
©ß81LÛ´œV Ø–³²iîŒÆZL zìxþ£‹äì|.—K¥R*c*UwÓ/Àv' ¸W&úÍ- Þœ]ôs÷xº‹÷Ù‰7/ÖìÊÛ{©<*L[a4ÒèOo~ð#e}¦imô['w…Ñ<ÁÉžÝ ÊÛL—°2”Rƒ±"—¦•]'LSaÑ<#\™âèì¼3ÏG_Ó´ùôÍb7æèw×+êq…uÛEº%²pHçÑܱVnÞ~ Ø´[ômü€£¶~[ó´èFî‡L¿òdì21±‹4.Ùó
¡J6î¸Oœa´‹s±å£Së?ø¡s¦~:Þ¾Hr?4Ù#F ¸îIí#™@-ɨ›E«“öi€6§û_ŒÀÑ_ûþ‡ö™ù&ú©D"Ñ8«¥ªLeí•®Hš'äÄv ìµ'E©J•ö-Ù÷_ viŒ¥Hy{+–/Ã]w<ŠöhÙþê÷?°NŸæèkÍÖB_H¹/ýaÀA&¦“jûRБ¬¼gç´ruk1žJ’ÐiË1z'ú÷uæl.›mg}µùOT¹/„ì / º°’Cß
€¡íòòo›®‘LZm]bJ¶§Ê騴‰?ÛdÕ©éµ÷?4NÉf3|™›H4žYÒÞâÜKîÇýÁze¢}lX}öŠ¿—4oÿ
§Îª¥²í(ÎØH"tL¶S8ápsmS;‘ÿà¢yòd&•ÖõFÊoµ¶Ü"_$ôE¤?¨ èÅÄ™£‰«Ê©;n x%p(K×óÖìQ-\F8¸ŽãlëÕQ´Ú¿ür8Ù¸ls«,´^pÛÝß
ì~tÉDx.eédÈ›#Y–ÔÕL°fìE‹u.øãã¶³µKоŽAÔ•®¸‰? èoþß;¾ÿ¬²Óˆòî7R"`²có‡CÞ¾®µ}!ƒ¨è‹N¿0Èü°'/ë†á„è7M#c#,tFZE É=m}·;Øâ*ôEz|¿K&ýÍʮ՞¾¬‡èº#£%B0Òºs+sÇKR ïO¯T4úÛü©jhîä‚bj4ˆýŸ.‘mó½ý!W ]ÀTåØ8{ú2œGNO&TUôw¿ÿÖ£JßD±;‘öõrM& ývt,ñbÕ4Ì W\ýs&ýP–ì¡>Ç8“~¥y7ãÌdç¡f'u•
J¿tÍÿ©¢ƒ£ïCNÖ2é@/FÈ©‡ýÛ# ¿ÿ2Ut@ú}
|Þÿ©i=¸;v99•ð~P-Nâ÷ |Küoõ;”¤Sãz@kßq-ãÏí— _Lúû _e‹(Ÿ`iÿoN'éñ ÍôA¿h²g ð[ö¸¼@ÉÙ™$õó´ ïüÙ$ñúÔÐ6ñ·€þƒ;MêäÔ1ýÞÓªO³prJOé4ôŸ;‘Š!§á%
*ú=}|„m´å
ïOÓ&1ñÑoµöƒ½bBØ5’F‚þ<=ð|10”ROID…þ¸Éóð§ŠÐèßoB릳öÚt VBæç’šw÷Iò®ÎιKÿå
³Z·{U\éWbIÿ(øÄŸ/Û÷ŸU.ÕmÇùô{¹‘ÌÎ|¯käÜ\êÆÃŠm
z‰_ør=»…ÓfѾtµ@ej\ã‹ã±a&+úJ\ÿÁk€ éwgqÅüëÓZ§Äÿãâ§ßÞýX”¡}gF¿ý¸¦ðü/Ï×]:mš?´Õ|0ÅÓWÿVOÏè3“ú^s‚ÄQúÿÉo®øJÿ½ÅÚ¾è+‹+õ*<éî~õô´þù´k·/׌‡Kýß5vjZŸ<ì¾ëÿåò·žsiòî©Ôñ]a°ƒþÓÇ{;mÇ}ò¨ô»T€ ÿ«uãÚ½ÊZÞÚ'xeé©é¤ËÖͨf;äñó~6Fç¦{ѱö`¸Ê—Ïo”‡V›a€ì‰:ú.ý\ë/Ü.½\7üË…»¾qáujŒfbi¹Þ+ýScîIwuÓ¼z·¼ÿŸo–ì?\/MŒÔ¾{~(—V~¤éW:·AƒÙí±mçæƒÊÿ\Þì†~Þ,[ùÃõr¹æ.÷g$ŽM$<¡¿Tµ?ÿºhv·ñ³òÚüÕå