- initial import into git
3
TODO.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
1) Save & load columns width in time table
|
||||||
|
2) Make hierarchical time table
|
||||||
|
3) Add tray icon
|
||||||
14
articles/Intro.rtf
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf210
|
||||||
|
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||||
|
{\colortbl;\red255\green255\blue255;}
|
||||||
|
\paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0
|
||||||
|
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural
|
||||||
|
|
||||||
|
\f0\fs24 \cf0 Litt is little idea & time tracker. It combines functionality of outliner & time tracker in single application.\
|
||||||
|
\
|
||||||
|
The reason of creating of this application was the lack of open source standards based outliner & time tracker. Litt includes password based protection and is based on open source database sqlcipher. So all your data are not lost in proprietary storage - they can be retrieved from database (of course if you will provide password).\
|
||||||
|
\
|
||||||
|
To ease the time tracking Litt can use automatic stop/start time tracking.\
|
||||||
|
Application can stop time tracking automatically if idle interval is detected and start tracking again if user activity is discovered again. \
|
||||||
|
Unlike other time trackers - Litt does NOT capture screenshots. Litt does NOT checks running applications name. Litt is intended to guard your private data - not to gather them. \
|
||||||
|
}
|
||||||
22
client/Info.plist
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
|
||||||
|
<plist version="0.9">
|
||||||
|
<dict>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>appicon.icns</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>Created by Qt/QMake</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>Litt</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.yourcompany.Litt</string>
|
||||||
|
<key>NOTE</key>
|
||||||
|
<string>This file was generated by Qt/QMake.</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
839
client/License.rtf
Normal file
@ -0,0 +1,839 @@
|
|||||||
|
{\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf210
|
||||||
|
{\fonttbl\f0\froman\fcharset0 TimesNewRomanPSMT;\f1\fnil\fcharset0 Verdana;}
|
||||||
|
{\colortbl;\red255\green255\blue255;\red38\green38\blue38;\red249\green249\blue249;}
|
||||||
|
\paperw11900\paperh16840\margl1440\margr1440\vieww17660\viewh14260\viewkind0
|
||||||
|
\deftab720
|
||||||
|
\pard\pardeftab720
|
||||||
|
|
||||||
|
\f0\fs24 \cf2 \cb3 COPYRIGHTS:
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 Copyright 2015 Satorilight.com. All Rights Reserved.
|
||||||
|
\f1\fs26 \
|
||||||
|
\
|
||||||
|
|
||||||
|
\f0\fs24 The Litt software is Copyright 2015 Satorilight.com. All
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 rights reserved. This software may not, in whole or in any part, be
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 copied, reproduced, transmitted, translated (into any language, natural
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 or computer), stored in a retrieval system, reduced to any electronic
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 medium or machine readable format, or by any other form or means
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 without prior consent, in writing, from Satorilight.com.
|
||||||
|
\f1\fs26 \
|
||||||
|
\
|
||||||
|
\
|
||||||
|
|
||||||
|
\f0\fs24 You are granted a limited license to use this software. The software
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 may be used or copied only in accordance with the terms of that
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 license, which is described in the following paragraphs.
|
||||||
|
\f1\fs26 \
|
||||||
|
\
|
||||||
|
\
|
||||||
|
|
||||||
|
\f0\fs24 LICENSE:
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 "THE SOFTWARE" SHALL BE TAKEN TO MEAN THE SOFTWARE
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 CONTAINED IN THIS PACKAGE AND ANY SUBEQUENT
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 VERSIONS OR UPGRADES RECEIVED AS A RESULT OF HAVING
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 PURCHASED OR DOWNLOADING THIS PACKAGE. "BUYER" SHALL BE TAKEN AS
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 THE ORIGINAL PURCHASER OR DOWNLOADER OF THE SOFTWARE.
|
||||||
|
\f1\fs26 \
|
||||||
|
\
|
||||||
|
|
||||||
|
\f0\fs24 BUYER HAS THE NON-EXCLUSIVE RIGHT TO USE THE
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 SOFTWARE ON ANY COMPUTER.
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 BUYER MAY NOT DISTRIBUTE COPIES OF THE SOFTWARE OR
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 THE ACCOMPANYING DOCUMENTATION TO OTHERS EITHER
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 FOR A FEE OR WITHOUT CHARGE. BUYER MAY NOT MODIFY
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 OR TRANSLATE THE PROGRAM OR DOCUMENTATION. USER
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 MAY NOT DISASSEMBLE THE PROGRAM OR ALLOW IT TO BE
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 DISASSEMBLED INTO ITS CONTITUENT SOURCE CODE.
|
||||||
|
\f1\fs26 \
|
||||||
|
\
|
||||||
|
|
||||||
|
\f0\fs24 BUYER'S USE OF THE SOFTWARE INDICATES HIS/HER
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 ACCEPTANCE OF THESE TERMS AND CONDITIONS. IF BUYER
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 DOES NOT AGREE TO THESE CONDITIONS, RETURN THE
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 DISTRIBUTION MEDIA, DOCUMENTATION, AND ASSOCIATED
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 MATERIALS TO THE VENDOR FROM WHOM THE SOFTWARE
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 WAS PURCHASED, AND ERASE THE SOFTWARE FROM ANY
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 AND ALL STORAGE DEVICES UPON WHICH IT MAY HAVE BEEN
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 INSTALLED.
|
||||||
|
\f1\fs26 \
|
||||||
|
\
|
||||||
|
\
|
||||||
|
|
||||||
|
\f0\fs24 DISCLAIMER / LIMITATION OF LIABILITY:
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 BUYER ACKNOWLEDGES THAT THE SOFTWARE MAY NOT BE
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 FREE FROM DEFECTS AND MAY NOT SATISFY ALL OF BUYER'S
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 NEEDS. IN NO EVENT WILL SATORILIGHT.COM BE LIABLE FOR
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 DIRECT, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGE
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 OR DAMAGES RESULTING FROM LOSS OF USE, OR LOSS OF
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 ANTICIPATED PROFITS RESULTING FROM ANY DEFECT IN THE
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 PROGRAM, EVEN IF IT HAS BEEN ADVISED OF THE
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 POSSIBILITY OF SUCH DAMAGE. SOME LAWS DO NOT ALLOW
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 THE EXCLUSION OR LIMITATION OF IMPLIED WARRANTIES OR
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 LIABILITIES FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 SO THE ABOVE LIMITATIONS OR EXCLUSION MAY NOT APPLY.
|
||||||
|
\f1\fs26 \
|
||||||
|
\
|
||||||
|
|
||||||
|
\f0\fs24 SPECIFIC RESTRICTIONS:
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 IN ACCORDANCE WITH THE COMPUTER SOFTWARE RENTAL
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 ACT OF 1990, THIS SOFTWARE MAY NOT BE RENTED, LENT OR
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 LEASED.
|
||||||
|
\f1\fs26 \
|
||||||
|
\
|
||||||
|
|
||||||
|
\f0\fs24 THE SOFTWARE AND ACCOMPANYING DOCUMENTATION MAY
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 NOT BE PROVIDED BY A "BACKUP SERVICE" OR ANY OTHER
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 VENDOR WHICH DOES NOT PROVIDE AN ORIGINAL PACKAGE
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 AS COMPOSED BY SATORILIGHT.COM, INCLUDING BUT NOT
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 LIMITED TO ALL ORIGINAL DISTRIBUTION MEDIA,
|
||||||
|
\f1\fs26 \
|
||||||
|
|
||||||
|
\f0\fs24 DOCUMENTATION, REGISTRATION CARDS, AND INSERTIONS.\
|
||||||
|
\
|
||||||
|
SQLCipher license:\
|
||||||
|
\
|
||||||
|
\
|
||||||
|
Copyright (c) 2008-2012 Zetetic LLC\
|
||||||
|
All rights reserved.\
|
||||||
|
\
|
||||||
|
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 the ZETETIC LLC 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 ZETETIC LLC ''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 ZETETIC LLC 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 LGPL license:\
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE\
|
||||||
|
Version 3, 29 June 2007\
|
||||||
|
\
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies\
|
||||||
|
of this license document, but changing it is not allowed.\
|
||||||
|
\
|
||||||
|
This version of the GNU Lesser General Public License incorporates\
|
||||||
|
the terms and conditions of version 3 of the GNU General Public\
|
||||||
|
License, supplemented by the additional permissions listed below.\
|
||||||
|
\
|
||||||
|
0. Additional Definitions.\
|
||||||
|
\
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser\
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU\
|
||||||
|
General Public License.\
|
||||||
|
\
|
||||||
|
"The Library" refers to a covered work governed by this License,\
|
||||||
|
other than an Application or a Combined Work as defined below.\
|
||||||
|
\
|
||||||
|
An "Application" is any work that makes use of an interface provided\
|
||||||
|
by the Library, but which is not otherwise based on the Library.\
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode\
|
||||||
|
of using an interface provided by the Library.\
|
||||||
|
\
|
||||||
|
A "Combined Work" is a work produced by combining or linking an\
|
||||||
|
Application with the Library. The particular version of the Library\
|
||||||
|
with which the Combined Work was made is also called the "Linked\
|
||||||
|
Version".\
|
||||||
|
\
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the\
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code\
|
||||||
|
for portions of the Combined Work that, considered in isolation, are\
|
||||||
|
based on the Application, and not on the Linked Version.\
|
||||||
|
\
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the\
|
||||||
|
object code and/or source code for the Application, including any data\
|
||||||
|
and utility programs needed for reproducing the Combined Work from the\
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.\
|
||||||
|
\
|
||||||
|
1. Exception to Section 3 of the GNU GPL.\
|
||||||
|
\
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License\
|
||||||
|
without being bound by section 3 of the GNU GPL.\
|
||||||
|
\
|
||||||
|
2. Conveying Modified Versions.\
|
||||||
|
\
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a\
|
||||||
|
facility refers to a function or data to be supplied by an Application\
|
||||||
|
that uses the facility (other than as an argument passed when the\
|
||||||
|
facility is invoked), then you may convey a copy of the modified\
|
||||||
|
version:\
|
||||||
|
\
|
||||||
|
a) under this License, provided that you make a good faith effort to\
|
||||||
|
ensure that, in the event an Application does not supply the\
|
||||||
|
function or data, the facility still operates, and performs\
|
||||||
|
whatever part of its purpose remains meaningful, or\
|
||||||
|
\
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of\
|
||||||
|
this License applicable to that copy.\
|
||||||
|
\
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.\
|
||||||
|
\
|
||||||
|
The object code form of an Application may incorporate material from\
|
||||||
|
a header file that is part of the Library. You may convey such object\
|
||||||
|
code under terms of your choice, provided that, if the incorporated\
|
||||||
|
material is not limited to numerical parameters, data structure\
|
||||||
|
layouts and accessors, or small macros, inline functions and templates\
|
||||||
|
(ten or fewer lines in length), you do both of the following:\
|
||||||
|
\
|
||||||
|
a) Give prominent notice with each copy of the object code that the\
|
||||||
|
Library is used in it and that the Library and its use are\
|
||||||
|
covered by this License.\
|
||||||
|
\
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license\
|
||||||
|
document.\
|
||||||
|
\
|
||||||
|
4. Combined Works.\
|
||||||
|
\
|
||||||
|
You may convey a Combined Work under terms of your choice that,\
|
||||||
|
taken together, effectively do not restrict modification of the\
|
||||||
|
portions of the Library contained in the Combined Work and reverse\
|
||||||
|
engineering for debugging such modifications, if you also do each of\
|
||||||
|
the following:\
|
||||||
|
\
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that\
|
||||||
|
the Library is used in it and that the Library and its use are\
|
||||||
|
covered by this License.\
|
||||||
|
\
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license\
|
||||||
|
document.\
|
||||||
|
\
|
||||||
|
c) For a Combined Work that displays copyright notices during\
|
||||||
|
execution, include the copyright notice for the Library among\
|
||||||
|
these notices, as well as a reference directing the user to the\
|
||||||
|
copies of the GNU GPL and this license document.\
|
||||||
|
\
|
||||||
|
d) Do one of the following:\
|
||||||
|
\
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this\
|
||||||
|
License, and the Corresponding Application Code in a form\
|
||||||
|
suitable for, and under terms that permit, the user to\
|
||||||
|
recombine or relink the Application with a modified version of\
|
||||||
|
the Linked Version to produce a modified Combined Work, in the\
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying\
|
||||||
|
Corresponding Source.\
|
||||||
|
\
|
||||||
|
1) Use a suitable shared library mechanism for linking with the\
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time\
|
||||||
|
a copy of the Library already present on the user's computer\
|
||||||
|
system, and (b) will operate properly with a modified version\
|
||||||
|
of the Library that is interface-compatible with the Linked\
|
||||||
|
Version.\
|
||||||
|
\
|
||||||
|
e) Provide Installation Information, but only if you would otherwise\
|
||||||
|
be required to provide such information under section 6 of the\
|
||||||
|
GNU GPL, and only to the extent that such information is\
|
||||||
|
necessary to install and execute a modified version of the\
|
||||||
|
Combined Work produced by recombining or relinking the\
|
||||||
|
Application with a modified version of the Linked Version. (If\
|
||||||
|
you use option 4d0, the Installation Information must accompany\
|
||||||
|
the Minimal Corresponding Source and Corresponding Application\
|
||||||
|
Code. If you use option 4d1, you must provide the Installation\
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL\
|
||||||
|
for conveying Corresponding Source.)\
|
||||||
|
\
|
||||||
|
5. Combined Libraries.\
|
||||||
|
\
|
||||||
|
You may place library facilities that are a work based on the\
|
||||||
|
Library side by side in a single library together with other library\
|
||||||
|
facilities that are not Applications and are not covered by this\
|
||||||
|
License, and convey such a combined library under terms of your\
|
||||||
|
choice, if you do both of the following:\
|
||||||
|
\
|
||||||
|
a) Accompany the combined library with a copy of the same work based\
|
||||||
|
on the Library, uncombined with any other library facilities,\
|
||||||
|
conveyed under the terms of this License.\
|
||||||
|
\
|
||||||
|
b) Give prominent notice with the combined library that part of it\
|
||||||
|
is a work based on the Library, and explaining where to find the\
|
||||||
|
accompanying uncombined form of the same work.\
|
||||||
|
\
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.\
|
||||||
|
\
|
||||||
|
The Free Software Foundation may publish revised and/or new versions\
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new\
|
||||||
|
versions will be similar in spirit to the present version, but may\
|
||||||
|
differ in detail to address new problems or concerns.\
|
||||||
|
\
|
||||||
|
Each version is given a distinguishing version number. If the\
|
||||||
|
Library as you received it specifies that a certain numbered version\
|
||||||
|
of the GNU Lesser General Public License "or any later version"\
|
||||||
|
applies to it, you have the option of following the terms and\
|
||||||
|
conditions either of that published version or of any later version\
|
||||||
|
published by the Free Software Foundation. If the Library as you\
|
||||||
|
received it does not specify a version number of the GNU Lesser\
|
||||||
|
General Public License, you may choose any version of the GNU Lesser\
|
||||||
|
General Public License ever published by the Free Software Foundation.\
|
||||||
|
\
|
||||||
|
If the Library as you received it specifies that a proxy can decide\
|
||||||
|
whether future versions of the GNU Lesser General Public License shall\
|
||||||
|
apply, that proxy's public statement of acceptance of any version is\
|
||||||
|
permanent authorization for you to choose that version for the\
|
||||||
|
Library.\
|
||||||
|
LGPL version 2.1\
|
||||||
|
\
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE\
|
||||||
|
Version 2.1, February 1999\
|
||||||
|
\
|
||||||
|
Copyright (C) 1991, 1999 Free Software Foundation, Inc.\
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies\
|
||||||
|
of this license document, but changing it is not allowed.\
|
||||||
|
\
|
||||||
|
[This is the first released version of the Lesser GPL. It also counts\
|
||||||
|
as the successor of the GNU Library Public License, version 2, hence\
|
||||||
|
the version number 2.1.]\
|
||||||
|
\
|
||||||
|
Preamble\
|
||||||
|
\
|
||||||
|
The licenses for most software are designed to take away your\
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public\
|
||||||
|
Licenses are intended to guarantee your freedom to share and change\
|
||||||
|
free software--to make sure the software is free for all its users.\
|
||||||
|
\
|
||||||
|
This license, the Lesser General Public License, applies to some\
|
||||||
|
specially designated software packages--typically libraries--of the\
|
||||||
|
Free Software Foundation and other authors who decide to use it. You\
|
||||||
|
can use it too, but we suggest you first think carefully about whether\
|
||||||
|
this license or the ordinary General Public License is the better\
|
||||||
|
strategy to use in any particular case, based on the explanations below.\
|
||||||
|
\
|
||||||
|
When we speak of free software, we are referring to freedom of use,\
|
||||||
|
not price. Our General Public Licenses are designed to make sure that\
|
||||||
|
you have the freedom to distribute copies of free software (and charge\
|
||||||
|
for this service if you wish); that you receive source code or can get\
|
||||||
|
it if you want it; that you can change the software and use pieces of\
|
||||||
|
it in new free programs; and that you are informed that you can do\
|
||||||
|
these things.\
|
||||||
|
\
|
||||||
|
To protect your rights, we need to make restrictions that forbid\
|
||||||
|
distributors to deny you these rights or to ask you to surrender these\
|
||||||
|
rights. These restrictions translate to certain responsibilities for\
|
||||||
|
you if you distribute copies of the library or if you modify it.\
|
||||||
|
\
|
||||||
|
For example, if you distribute copies of the library, whether gratis\
|
||||||
|
or for a fee, you must give the recipients all the rights that we gave\
|
||||||
|
you. You must make sure that they, too, receive or can get the source\
|
||||||
|
code. If you link other code with the library, you must provide\
|
||||||
|
complete object files to the recipients, so that they can relink them\
|
||||||
|
with the library after making changes to the library and recompiling\
|
||||||
|
it. And you must show them these terms so they know their rights.\
|
||||||
|
\
|
||||||
|
We protect your rights with a two-step method: (1) we copyright the\
|
||||||
|
library, and (2) we offer you this license, which gives you legal\
|
||||||
|
permission to copy, distribute and/or modify the library.\
|
||||||
|
\
|
||||||
|
To protect each distributor, we want to make it very clear that\
|
||||||
|
there is no warranty for the free library. Also, if the library is\
|
||||||
|
modified by someone else and passed on, the recipients should know\
|
||||||
|
that what they have is not the original version, so that the original\
|
||||||
|
author's reputation will not be affected by problems that might be\
|
||||||
|
introduced by others.\
|
||||||
|
\
|
||||||
|
Finally, software patents pose a constant threat to the existence of\
|
||||||
|
any free program. We wish to make sure that a company cannot\
|
||||||
|
effectively restrict the users of a free program by obtaining a\
|
||||||
|
restrictive license from a patent holder. Therefore, we insist that\
|
||||||
|
any patent license obtained for a version of the library must be\
|
||||||
|
consistent with the full freedom of use specified in this license.\
|
||||||
|
\
|
||||||
|
Most GNU software, including some libraries, is covered by the\
|
||||||
|
ordinary GNU General Public License. This license, the GNU Lesser\
|
||||||
|
General Public License, applies to certain designated libraries, and\
|
||||||
|
is quite different from the ordinary General Public License. We use\
|
||||||
|
this license for certain libraries in order to permit linking those\
|
||||||
|
libraries into non-free programs.\
|
||||||
|
\
|
||||||
|
When a program is linked with a library, whether statically or using\
|
||||||
|
a shared library, the combination of the two is legally speaking a\
|
||||||
|
combined work, a derivative of the original library. The ordinary\
|
||||||
|
General Public License therefore permits such linking only if the\
|
||||||
|
entire combination fits its criteria of freedom. The Lesser General\
|
||||||
|
Public License permits more lax criteria for linking other code with\
|
||||||
|
the library.\
|
||||||
|
\
|
||||||
|
We call this license the "Lesser" General Public License because it\
|
||||||
|
does Less to protect the user's freedom than the ordinary General\
|
||||||
|
Public License. It also provides other free software developers Less\
|
||||||
|
of an advantage over competing non-free programs. These disadvantages\
|
||||||
|
are the reason we use the ordinary General Public License for many\
|
||||||
|
libraries. However, the Lesser license provides advantages in certain\
|
||||||
|
special circumstances.\
|
||||||
|
\
|
||||||
|
For example, on rare occasions, there may be a special need to\
|
||||||
|
encourage the widest possible use of a certain library, so that it becomes\
|
||||||
|
a de-facto standard. To achieve this, non-free programs must be\
|
||||||
|
allowed to use the library. A more frequent case is that a free\
|
||||||
|
library does the same job as widely used non-free libraries. In this\
|
||||||
|
case, there is little to gain by limiting the free library to free\
|
||||||
|
software only, so we use the Lesser General Public License.\
|
||||||
|
\
|
||||||
|
In other cases, permission to use a particular library in non-free\
|
||||||
|
programs enables a greater number of people to use a large body of\
|
||||||
|
free software. For example, permission to use the GNU C Library in\
|
||||||
|
non-free programs enables many more people to use the whole GNU\
|
||||||
|
operating system, as well as its variant, the GNU/Linux operating\
|
||||||
|
system.\
|
||||||
|
\
|
||||||
|
Although the Lesser General Public License is Less protective of the\
|
||||||
|
users' freedom, it does ensure that the user of a program that is\
|
||||||
|
linked with the Library has the freedom and the wherewithal to run\
|
||||||
|
that program using a modified version of the Library.\
|
||||||
|
\
|
||||||
|
The precise terms and conditions for copying, distribution and\
|
||||||
|
modification follow. Pay close attention to the difference between a\
|
||||||
|
"work based on the library" and a "work that uses the library". The\
|
||||||
|
former contains code derived from the library, whereas the latter must\
|
||||||
|
be combined with the library in order to run.\
|
||||||
|
\
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE\
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\
|
||||||
|
\
|
||||||
|
0. This License Agreement applies to any software library or other\
|
||||||
|
program which contains a notice placed by the copyright holder or\
|
||||||
|
other authorized party saying it may be distributed under the terms of\
|
||||||
|
this Lesser General Public License (also called "this License").\
|
||||||
|
Each licensee is addressed as "you".\
|
||||||
|
\
|
||||||
|
A "library" means a collection of software functions and/or data\
|
||||||
|
prepared so as to be conveniently linked with application programs\
|
||||||
|
(which use some of those functions and data) to form executables.\
|
||||||
|
\
|
||||||
|
The "Library", below, refers to any such software library or work\
|
||||||
|
which has been distributed under these terms. A "work based on the\
|
||||||
|
Library" means either the Library or any derivative work under\
|
||||||
|
copyright law: that is to say, a work containing the Library or a\
|
||||||
|
portion of it, either verbatim or with modifications and/or translated\
|
||||||
|
straightforwardly into another language. (Hereinafter, translation is\
|
||||||
|
included without limitation in the term "modification".)\
|
||||||
|
\
|
||||||
|
"Source code" for a work means the preferred form of the work for\
|
||||||
|
making modifications to it. For a library, complete source code means\
|
||||||
|
all the source code for all modules it contains, plus any associated\
|
||||||
|
interface definition files, plus the scripts used to control compilation\
|
||||||
|
and installation of the library.\
|
||||||
|
\
|
||||||
|
Activities other than copying, distribution and modification are not\
|
||||||
|
covered by this License; they are outside its scope. The act of\
|
||||||
|
running a program using the Library is not restricted, and output from\
|
||||||
|
such a program is covered only if its contents constitute a work based\
|
||||||
|
on the Library (independent of the use of the Library in a tool for\
|
||||||
|
writing it). Whether that is true depends on what the Library does\
|
||||||
|
and what the program that uses the Library does.\
|
||||||
|
\
|
||||||
|
1. You may copy and distribute verbatim copies of the Library's\
|
||||||
|
complete source code as you receive it, in any medium, provided that\
|
||||||
|
you conspicuously and appropriately publish on each copy an\
|
||||||
|
appropriate copyright notice and disclaimer of warranty; keep intact\
|
||||||
|
all the notices that refer to this License and to the absence of any\
|
||||||
|
warranty; and distribute a copy of this License along with the\
|
||||||
|
Library.\
|
||||||
|
\
|
||||||
|
You may charge a fee for the physical act of transferring a copy,\
|
||||||
|
and you may at your option offer warranty protection in exchange for a\
|
||||||
|
fee.\
|
||||||
|
\
|
||||||
|
2. You may modify your copy or copies of the Library or any portion\
|
||||||
|
of it, thus forming a work based on the Library, and copy and\
|
||||||
|
distribute such modifications or work under the terms of Section 1\
|
||||||
|
above, provided that you also meet all of these conditions:\
|
||||||
|
\
|
||||||
|
a) The modified work must itself be a software library.\
|
||||||
|
\
|
||||||
|
b) You must cause the files modified to carry prominent notices\
|
||||||
|
stating that you changed the files and the date of any change.\
|
||||||
|
\
|
||||||
|
c) You must cause the whole of the work to be licensed at no\
|
||||||
|
charge to all third parties under the terms of this License.\
|
||||||
|
\
|
||||||
|
d) If a facility in the modified Library refers to a function or a\
|
||||||
|
table of data to be supplied by an application program that uses\
|
||||||
|
the facility, other than as an argument passed when the facility\
|
||||||
|
is invoked, then you must make a good faith effort to ensure that,\
|
||||||
|
in the event an application does not supply such function or\
|
||||||
|
table, the facility still operates, and performs whatever part of\
|
||||||
|
its purpose remains meaningful.\
|
||||||
|
\
|
||||||
|
(For example, a function in a library to compute square roots has\
|
||||||
|
a purpose that is entirely well-defined independent of the\
|
||||||
|
application. Therefore, Subsection 2d requires that any\
|
||||||
|
application-supplied function or table used by this function must\
|
||||||
|
be optional: if the application does not supply it, the square\
|
||||||
|
root function must still compute square roots.)\
|
||||||
|
\
|
||||||
|
These requirements apply to the modified work as a whole. If\
|
||||||
|
identifiable sections of that work are not derived from the Library,\
|
||||||
|
and can be reasonably considered independent and separate works in\
|
||||||
|
themselves, then this License, and its terms, do not apply to those\
|
||||||
|
sections when you distribute them as separate works. But when you\
|
||||||
|
distribute the same sections as part of a whole which is a work based\
|
||||||
|
on the Library, the distribution of the whole must be on the terms of\
|
||||||
|
this License, whose permissions for other licensees extend to the\
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote\
|
||||||
|
it.\
|
||||||
|
\
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest\
|
||||||
|
your rights to work written entirely by you; rather, the intent is to\
|
||||||
|
exercise the right to control the distribution of derivative or\
|
||||||
|
collective works based on the Library.\
|
||||||
|
\
|
||||||
|
In addition, mere aggregation of another work not based on the Library\
|
||||||
|
with the Library (or with a work based on the Library) on a volume of\
|
||||||
|
a storage or distribution medium does not bring the other work under\
|
||||||
|
the scope of this License.\
|
||||||
|
\
|
||||||
|
3. You may opt to apply the terms of the ordinary GNU General Public\
|
||||||
|
License instead of this License to a given copy of the Library. To do\
|
||||||
|
this, you must alter all the notices that refer to this License, so\
|
||||||
|
that they refer to the ordinary GNU General Public License, version 2,\
|
||||||
|
instead of to this License. (If a newer version than version 2 of the\
|
||||||
|
ordinary GNU General Public License has appeared, then you can specify\
|
||||||
|
that version instead if you wish.) Do not make any other change in\
|
||||||
|
these notices.\
|
||||||
|
\
|
||||||
|
Once this change is made in a given copy, it is irreversible for\
|
||||||
|
that copy, so the ordinary GNU General Public License applies to all\
|
||||||
|
subsequent copies and derivative works made from that copy.\
|
||||||
|
\
|
||||||
|
This option is useful when you wish to copy part of the code of\
|
||||||
|
the Library into a program that is not a library.\
|
||||||
|
\
|
||||||
|
4. You may copy and distribute the Library (or a portion or\
|
||||||
|
derivative of it, under Section 2) in object code or executable form\
|
||||||
|
under the terms of Sections 1 and 2 above provided that you accompany\
|
||||||
|
it with the complete corresponding machine-readable source code, which\
|
||||||
|
must be distributed under the terms of Sections 1 and 2 above on a\
|
||||||
|
medium customarily used for software interchange.\
|
||||||
|
\
|
||||||
|
If distribution of object code is made by offering access to copy\
|
||||||
|
from a designated place, then offering equivalent access to copy the\
|
||||||
|
source code from the same place satisfies the requirement to\
|
||||||
|
distribute the source code, even though third parties are not\
|
||||||
|
compelled to copy the source along with the object code.\
|
||||||
|
\
|
||||||
|
5. A program that contains no derivative of any portion of the\
|
||||||
|
Library, but is designed to work with the Library by being compiled or\
|
||||||
|
linked with it, is called a "work that uses the Library". Such a\
|
||||||
|
work, in isolation, is not a derivative work of the Library, and\
|
||||||
|
therefore falls outside the scope of this License.\
|
||||||
|
\
|
||||||
|
However, linking a "work that uses the Library" with the Library\
|
||||||
|
creates an executable that is a derivative of the Library (because it\
|
||||||
|
contains portions of the Library), rather than a "work that uses the\
|
||||||
|
library". The executable is therefore covered by this License.\
|
||||||
|
Section 6 states terms for distribution of such executables.\
|
||||||
|
\
|
||||||
|
When a "work that uses the Library" uses material from a header file\
|
||||||
|
that is part of the Library, the object code for the work may be a\
|
||||||
|
derivative work of the Library even though the source code is not.\
|
||||||
|
Whether this is true is especially significant if the work can be\
|
||||||
|
linked without the Library, or if the work is itself a library. The\
|
||||||
|
threshold for this to be true is not precisely defined by law.\
|
||||||
|
\
|
||||||
|
If such an object file uses only numerical parameters, data\
|
||||||
|
structure layouts and accessors, and small macros and small inline\
|
||||||
|
functions (ten lines or less in length), then the use of the object\
|
||||||
|
file is unrestricted, regardless of whether it is legally a derivative\
|
||||||
|
work. (Executables containing this object code plus portions of the\
|
||||||
|
Library will still fall under Section 6.)\
|
||||||
|
\
|
||||||
|
Otherwise, if the work is a derivative of the Library, you may\
|
||||||
|
distribute the object code for the work under the terms of Section 6.\
|
||||||
|
Any executables containing that work also fall under Section 6,\
|
||||||
|
whether or not they are linked directly with the Library itself.\
|
||||||
|
\
|
||||||
|
6. As an exception to the Sections above, you may also combine or\
|
||||||
|
link a "work that uses the Library" with the Library to produce a\
|
||||||
|
work containing portions of the Library, and distribute that work\
|
||||||
|
under terms of your choice, provided that the terms permit\
|
||||||
|
modification of the work for the customer's own use and reverse\
|
||||||
|
engineering for debugging such modifications.\
|
||||||
|
\
|
||||||
|
You must give prominent notice with each copy of the work that the\
|
||||||
|
Library is used in it and that the Library and its use are covered by\
|
||||||
|
this License. You must supply a copy of this License. If the work\
|
||||||
|
during execution displays copyright notices, you must include the\
|
||||||
|
copyright notice for the Library among them, as well as a reference\
|
||||||
|
directing the user to the copy of this License. Also, you must do one\
|
||||||
|
of these things:\
|
||||||
|
\
|
||||||
|
a) Accompany the work with the complete corresponding\
|
||||||
|
machine-readable source code for the Library including whatever\
|
||||||
|
changes were used in the work (which must be distributed under\
|
||||||
|
Sections 1 and 2 above); and, if the work is an executable linked\
|
||||||
|
with the Library, with the complete machine-readable "work that\
|
||||||
|
uses the Library", as object code and/or source code, so that the\
|
||||||
|
user can modify the Library and then relink to produce a modified\
|
||||||
|
executable containing the modified Library. (It is understood\
|
||||||
|
that the user who changes the contents of definitions files in the\
|
||||||
|
Library will not necessarily be able to recompile the application\
|
||||||
|
to use the modified definitions.)\
|
||||||
|
\
|
||||||
|
b) Use a suitable shared library mechanism for linking with the\
|
||||||
|
Library. A suitable mechanism is one that (1) uses at run time a\
|
||||||
|
copy of the library already present on the user's computer system,\
|
||||||
|
rather than copying library functions into the executable, and (2)\
|
||||||
|
will operate properly with a modified version of the library, if\
|
||||||
|
the user installs one, as long as the modified version is\
|
||||||
|
interface-compatible with the version that the work was made with.\
|
||||||
|
\
|
||||||
|
c) Accompany the work with a written offer, valid for at\
|
||||||
|
least three years, to give the same user the materials\
|
||||||
|
specified in Subsection 6a, above, for a charge no more\
|
||||||
|
than the cost of performing this distribution.\
|
||||||
|
\
|
||||||
|
d) If distribution of the work is made by offering access to copy\
|
||||||
|
from a designated place, offer equivalent access to copy the above\
|
||||||
|
specified materials from the same place.\
|
||||||
|
\
|
||||||
|
e) Verify that the user has already received a copy of these\
|
||||||
|
materials or that you have already sent this user a copy.\
|
||||||
|
\
|
||||||
|
For an executable, the required form of the "work that uses the\
|
||||||
|
Library" must include any data and utility programs needed for\
|
||||||
|
reproducing the executable from it. However, as a special exception,\
|
||||||
|
the materials to be distributed need not include anything that is\
|
||||||
|
normally distributed (in either source or binary form) with the major\
|
||||||
|
components (compiler, kernel, and so on) of the operating system on\
|
||||||
|
which the executable runs, unless that component itself accompanies\
|
||||||
|
the executable.\
|
||||||
|
\
|
||||||
|
It may happen that this requirement contradicts the license\
|
||||||
|
restrictions of other proprietary libraries that do not normally\
|
||||||
|
accompany the operating system. Such a contradiction means you cannot\
|
||||||
|
use both them and the Library together in an executable that you\
|
||||||
|
distribute.\
|
||||||
|
\
|
||||||
|
7. You may place library facilities that are a work based on the\
|
||||||
|
Library side-by-side in a single library together with other library\
|
||||||
|
facilities not covered by this License, and distribute such a combined\
|
||||||
|
library, provided that the separate distribution of the work based on\
|
||||||
|
the Library and of the other library facilities is otherwise\
|
||||||
|
permitted, and provided that you do these two things:\
|
||||||
|
\
|
||||||
|
a) Accompany the combined library with a copy of the same work\
|
||||||
|
based on the Library, uncombined with any other library\
|
||||||
|
facilities. This must be distributed under the terms of the\
|
||||||
|
Sections above.\
|
||||||
|
\
|
||||||
|
b) Give prominent notice with the combined library of the fact\
|
||||||
|
that part of it is a work based on the Library, and explaining\
|
||||||
|
where to find the accompanying uncombined form of the same work.\
|
||||||
|
\
|
||||||
|
8. You may not copy, modify, sublicense, link with, or distribute\
|
||||||
|
0the Library except as expressly provided under this License. Any\
|
||||||
|
attempt otherwise to copy, modify, sublicense, link with, or\
|
||||||
|
distribute the Library is void, and will automatically terminate your\
|
||||||
|
rights under this License. However, parties who have received copies,\
|
||||||
|
or rights, from you under this License will not have their licenses\
|
||||||
|
terminated so long as such parties remain in full compliance.\
|
||||||
|
\
|
||||||
|
9. You are not required to accept this License, since you have not\
|
||||||
|
signed it. However, nothing else grants you permission to modify or\
|
||||||
|
distribute the Library or its derivative works. These actions are\
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by\
|
||||||
|
modifying or distributing the Library (or any work based on the\
|
||||||
|
Library), you indicate your acceptance of this License to do so, and\
|
||||||
|
all its terms and conditions for copying, distributing or modifying\
|
||||||
|
the Library or works based on it.\
|
||||||
|
\
|
||||||
|
10. Each time you redistribute the Library (or any work based on the\
|
||||||
|
Library), the recipient automatically receives a license from the\
|
||||||
|
original licensor to copy, distribute, link with or modify the Library\
|
||||||
|
subject to these terms and conditions. You may not impose any further\
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.\
|
||||||
|
You are not responsible for enforcing compliance by third parties with\
|
||||||
|
this License.\
|
||||||
|
\
|
||||||
|
11. If, as a consequence of a court judgment or allegation of patent\
|
||||||
|
infringement or for any other reason (not limited to patent issues),\
|
||||||
|
conditions are imposed on you (whether by court order, agreement or\
|
||||||
|
otherwise) that contradict the conditions of this License, they do not\
|
||||||
|
excuse you from the conditions of this License. If you cannot\
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this\
|
||||||
|
License and any other pertinent obligations, then as a consequence you\
|
||||||
|
may not distribute the Library at all. For example, if a patent\
|
||||||
|
license would not permit royalty-free redistribution of the Library by\
|
||||||
|
all those who receive copies directly or indirectly through you, then\
|
||||||
|
the only way you could satisfy both it and this License would be to\
|
||||||
|
refrain entirely from distribution of the Library.\
|
||||||
|
\
|
||||||
|
If any portion of this section is held invalid or unenforceable under any\
|
||||||
|
particular circumstance, the balance of the section is intended to apply,\
|
||||||
|
and the section as a whole is intended to apply in other circumstances.\
|
||||||
|
\
|
||||||
|
It is not the purpose of this section to induce you to infringe any\
|
||||||
|
patents or other property right claims or to contest validity of any\
|
||||||
|
such claims; this section has the sole purpose of protecting the\
|
||||||
|
integrity of the free software distribution system which is\
|
||||||
|
implemented by public license practices. Many people have made\
|
||||||
|
generous contributions to the wide range of software distributed\
|
||||||
|
through that system in reliance on consistent application of that\
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing\
|
||||||
|
to distribute software through any other system and a licensee cannot\
|
||||||
|
impose that choice.\
|
||||||
|
\
|
||||||
|
This section is intended to make thoroughly clear what is believed to\
|
||||||
|
be a consequence of the rest of this License.\
|
||||||
|
\
|
||||||
|
12. If the distribution and/or use of the Library is restricted in\
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the\
|
||||||
|
original copyright holder who places the Library under this License may add\
|
||||||
|
an explicit geographical distribution limitation excluding those countries,\
|
||||||
|
so that distribution is permitted only in or among countries not thus\
|
||||||
|
excluded. In such case, this License incorporates the limitation as if\
|
||||||
|
written in the body of this License.\
|
||||||
|
\
|
||||||
|
13. The Free Software Foundation may publish revised and/or new\
|
||||||
|
versions of the Lesser General Public License from time to time.\
|
||||||
|
Such new versions will be similar in spirit to the present version,\
|
||||||
|
but may differ in detail to address new problems or concerns.\
|
||||||
|
\
|
||||||
|
Each version is given a distinguishing version number. If the Library\
|
||||||
|
specifies a version number of this License which applies to it and\
|
||||||
|
"any later version", you have the option of following the terms and\
|
||||||
|
conditions either of that version or of any later version published by\
|
||||||
|
the Free Software Foundation. If the Library does not specify a\
|
||||||
|
license version number, you may choose any version ever published by\
|
||||||
|
the Free Software Foundation.\
|
||||||
|
\
|
||||||
|
14. If you wish to incorporate parts of the Library into other free\
|
||||||
|
programs whose distribution conditions are incompatible with these,\
|
||||||
|
write to the author to ask for permission. For software which is\
|
||||||
|
copyrighted by the Free Software Foundation, write to the Free\
|
||||||
|
Software Foundation; we sometimes make exceptions for this. Our\
|
||||||
|
decision will be guided by the two goals of preserving the free status\
|
||||||
|
of all derivatives of our free software and of promoting the sharing\
|
||||||
|
and reuse of software generally.\
|
||||||
|
\
|
||||||
|
NO WARRANTY\
|
||||||
|
\
|
||||||
|
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO\
|
||||||
|
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.\
|
||||||
|
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR\
|
||||||
|
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY\
|
||||||
|
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE\
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE\
|
||||||
|
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME\
|
||||||
|
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\
|
||||||
|
\
|
||||||
|
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN\
|
||||||
|
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY\
|
||||||
|
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU\
|
||||||
|
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR\
|
||||||
|
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE\
|
||||||
|
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING\
|
||||||
|
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A\
|
||||||
|
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF\
|
||||||
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH\
|
||||||
|
DAMAGES.\
|
||||||
|
\
|
||||||
|
END OF TERMS AND CONDITIONS\
|
||||||
|
\
|
||||||
|
Some icons are designed by {\field{\*\fldinst{HYPERLINK "http://pixel-mixer.com/"}}{\fldrslt http://pixel-mixer.com/}}\
|
||||||
|
\
|
||||||
|
}
|
||||||
43
client/aboutdlg.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include "config.h"
|
||||||
|
#include "aboutdlg.h"
|
||||||
|
#include "ui_aboutdlg.h"
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
AboutDlg::AboutDlg(QWidget *parent) :
|
||||||
|
QDialog(parent),
|
||||||
|
ui(new Ui::AboutDlg)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
//QPushButton* btn = ui->mButtonBox->addButton(tr("License"), QDialogButtonBox::AcceptRole);
|
||||||
|
//this->connect(btn, SIGNAL(clicked()), this, SLOT(showLicense()));
|
||||||
|
|
||||||
|
QString text(ABOUTTEXT);
|
||||||
|
text += ".\r\n";
|
||||||
|
text += QString("Version %1.").arg(VER);
|
||||||
|
ui->mTextLabel->setText(text);
|
||||||
|
|
||||||
|
QString appPath = QCoreApplication::applicationDirPath();
|
||||||
|
appPath += "/../Resources/License.rtf";
|
||||||
|
ui->mLicenseLabel->setText("<a href=\"" + QUrl::fromLocalFile(appPath).toString() + "\">License</a>");
|
||||||
|
ui->mLicenseLabel->setTextFormat(Qt::RichText);
|
||||||
|
ui->mLicenseLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||||
|
ui->mLicenseLabel->setOpenExternalLinks(true);
|
||||||
|
setWindowTitle(APPNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
AboutDlg::~AboutDlg()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AboutDlg::showLicense()
|
||||||
|
{
|
||||||
|
#ifdef TARGET_OSX
|
||||||
|
QString appPath = QCoreApplication::applicationDirPath();
|
||||||
|
appPath += "/../Resources/License.rtf";
|
||||||
|
QDesktopServices::openUrl(QUrl::fromLocalFile(appPath));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
25
client/aboutdlg.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#ifndef ABOUTDLG_H
|
||||||
|
#define ABOUTDLG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class AboutDlg;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutDlg : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AboutDlg(QWidget *parent = 0);
|
||||||
|
~AboutDlg();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::AboutDlg *ui;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void showLicense();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ABOUTDLG_H
|
||||||
95
client/aboutdlg.ui
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AboutDlg</class>
|
||||||
|
<widget class="QDialog" name="AboutDlg">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>323</width>
|
||||||
|
<height>170</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="mTextLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>werwre
|
||||||
|
werwer
|
||||||
|
</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="mLicenseLabel">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>TextLabel</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="mButtonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
<property name="centerButtons">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>mButtonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>AboutDlg</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>mButtonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>AboutDlg</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
||||||
23
client/appevents.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include "appevents.h"
|
||||||
|
|
||||||
|
ClientInitEvent::ClientInitEvent()
|
||||||
|
:QEvent((Type)ClientInitId)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientCloseEvent::ClientCloseEvent()
|
||||||
|
:QEvent((Type)ClientCloseId)
|
||||||
|
{}
|
||||||
|
|
||||||
|
AttachDatabaseEvent::AttachDatabaseEvent()
|
||||||
|
:QEvent((Type)AttachDatabaseId)
|
||||||
|
{}
|
||||||
|
|
||||||
|
SelectTaskEvent::SelectTaskEvent(PTask task)
|
||||||
|
:QEvent((Type)SelectTaskId), mTask(task)
|
||||||
|
{}
|
||||||
|
|
||||||
|
PTask SelectTaskEvent::task()
|
||||||
|
{
|
||||||
|
return mTask;
|
||||||
|
}
|
||||||
42
client/appevents.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#ifndef APPEVENTS_H
|
||||||
|
#define APPEVENTS_H
|
||||||
|
|
||||||
|
#include <QEvent>
|
||||||
|
#include "task.h"
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
ClientInitId = 62000,
|
||||||
|
ClientCloseId = 62001,
|
||||||
|
AttachDatabaseId,
|
||||||
|
SelectTaskId
|
||||||
|
};
|
||||||
|
|
||||||
|
class ClientInitEvent: public QEvent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ClientInitEvent();
|
||||||
|
};
|
||||||
|
|
||||||
|
class ClientCloseEvent: public QEvent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ClientCloseEvent();
|
||||||
|
};
|
||||||
|
|
||||||
|
class AttachDatabaseEvent: public QEvent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AttachDatabaseEvent();
|
||||||
|
};
|
||||||
|
|
||||||
|
class SelectTaskEvent: public QEvent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SelectTaskEvent(PTask task);
|
||||||
|
PTask task();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PTask mTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APPEVENTS_H
|
||||||
17
client/attachmentsdialog.cpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#include "attachmentsdialog.h"
|
||||||
|
#include "ui_attachmentsdialog.h"
|
||||||
|
|
||||||
|
AttachmentsDialog::AttachmentsDialog(PTask task, QWidget *parent) :
|
||||||
|
QDialog(parent, Qt::Sheet),
|
||||||
|
ui(new Ui::AttachmentsDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
ui->widget->setTask(task);
|
||||||
|
ui->widget->setParentWidget(parent);
|
||||||
|
this->setWindowTitle(tr("Attachments"));
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentsDialog::~AttachmentsDialog()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
23
client/attachmentsdialog.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef ATTACHMENTSDIALOG_H
|
||||||
|
#define ATTACHMENTSDIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class AttachmentsDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AttachmentsDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AttachmentsDialog(PTask task, QWidget *parent = 0);
|
||||||
|
~AttachmentsDialog();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::AttachmentsDialog *ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ATTACHMENTSDIALOG_H
|
||||||
100
client/attachmentsdialog.ui
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AttachmentsDialog</class>
|
||||||
|
<widget class="QDialog" name="AttachmentsDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>600</width>
|
||||||
|
<height>300</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<property name="sizeGripEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="AttachmentsList" name="widget" native="true"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>There is list of attachments in document. Use context menu or drag-and-drop to manage it.</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
<property name="centerButtons">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>AttachmentsList</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>attachmentslist.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>AttachmentsDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>AttachmentsDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
||||||
259
client/attachmentslist.cpp
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
#include "attachmentslist.h"
|
||||||
|
#include "ui_attachmentslist.h"
|
||||||
|
#include "storage.h"
|
||||||
|
#include "taskaction.h"
|
||||||
|
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
AttachmentsListModel::AttachmentsListModel(PTask task, ChangesHistory& history, const AttachmentArray &items, QObject *parent)
|
||||||
|
:QAbstractListModel(parent), mTask(task), mHistory(history), mData(items)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int AttachmentsListModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
return mData.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant AttachmentsListModel::data(const QModelIndex& index, int role) const
|
||||||
|
{
|
||||||
|
// Check that the index is valid and within the correct range first:
|
||||||
|
if (!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
if (index.row() >= mData.size())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
if (role == Qt::DisplayRole || role == Qt::EditRole)
|
||||||
|
{
|
||||||
|
// Only returns something for the roles you support (DisplayRole is a minimum)
|
||||||
|
return QVariant(mData.at(index.row())->filename());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (role == Qt::DecorationRole)
|
||||||
|
{
|
||||||
|
QFileInfo fi(mData.at(index.row())->filename());
|
||||||
|
QIcon icon = mIconProvider.icon(fi);
|
||||||
|
return QVariant(icon);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Qt::ItemFlags AttachmentsListModel::flags(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
Qt::ItemFlags result = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;// | Qt::ItemIsDropEnabled;
|
||||||
|
//if (index.isValid())
|
||||||
|
// result |= Qt::ItemIsDragEnabled;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AttachmentsListModel::setData(const QModelIndex &index, const QVariant &value, int role /* = Qt::EditRole */)
|
||||||
|
{
|
||||||
|
PAttachment att = itemAt(index.row());
|
||||||
|
switch (role)
|
||||||
|
{
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
case Qt::EditRole:
|
||||||
|
MAKE_ACTION(new RenameAttachmentAction(mTask, att, value.toString()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachmentsListModel::addItem(PAttachment att, int index)
|
||||||
|
{
|
||||||
|
if (index == -1)
|
||||||
|
{
|
||||||
|
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||||
|
mData.push_back(att);
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
beginInsertRows(QModelIndex(), index, index);
|
||||||
|
mData.insert(mData.begin() + index, att);
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachmentsListModel::removeItem(int row)
|
||||||
|
{
|
||||||
|
beginRemoveRows(QModelIndex(), row, row);
|
||||||
|
mData.erase(mData.begin() + row);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
PAttachment AttachmentsListModel::itemAt(int row) const
|
||||||
|
{
|
||||||
|
return mData[row];
|
||||||
|
}
|
||||||
|
|
||||||
|
int AttachmentsListModel::findRow(PAttachment att) const
|
||||||
|
{
|
||||||
|
return mData.indexOf(att);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
AttachmentsList::AttachmentsList(QWidget *parent) :
|
||||||
|
QWidget(parent),
|
||||||
|
ui(new Ui::AttachmentsList)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentsList::~AttachmentsList()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachmentsList::setTask(PTask task)
|
||||||
|
{
|
||||||
|
mTask = task;
|
||||||
|
AttachmentArray aa;
|
||||||
|
Storage::instance().loadAttachments(mTask, aa);
|
||||||
|
|
||||||
|
// Create model
|
||||||
|
mModel = new AttachmentsListModel(mTask, mHistory, aa);
|
||||||
|
|
||||||
|
// Set model to history
|
||||||
|
mHistory.setAttachmentsModel(mModel);
|
||||||
|
|
||||||
|
// Set model to list view
|
||||||
|
ui->mListView->setModel(mModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachmentsList::setParentWidget(QWidget *w)
|
||||||
|
{
|
||||||
|
mParentWidget = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachmentsList::updateActionsState()
|
||||||
|
{
|
||||||
|
bool hasSelectedItem = ui->mListView->currentIndex().isValid();
|
||||||
|
ui->mRenameAction->setEnabled(hasSelectedItem);
|
||||||
|
ui->mDeleteAction->setEnabled(hasSelectedItem);
|
||||||
|
ui->mExportAction->setEnabled(hasSelectedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachmentsList::contextualMenu(const QPoint& point)
|
||||||
|
{
|
||||||
|
updateActionsState();
|
||||||
|
|
||||||
|
QMenu* menu = new QMenu();
|
||||||
|
menu->addAction(ui->mRenameAction);
|
||||||
|
menu->addAction(ui->mDeleteAction);
|
||||||
|
menu->addAction(ui->mExportAction);
|
||||||
|
menu->addAction(ui->mImportAction);
|
||||||
|
|
||||||
|
//menu->addAction(tr("Add 10 mins to timeline"), this, SLOT(add10Mins()));
|
||||||
|
menu->exec(this->window()->mapToGlobal(point));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachmentsList::importFile()
|
||||||
|
{
|
||||||
|
// Prepare file open dialog
|
||||||
|
QFileDialog dlg(mParentWidget, Qt::Sheet);
|
||||||
|
dlg.setWindowTitle(tr("Select file(s) for import"));
|
||||||
|
dlg.setAcceptDrops(false);
|
||||||
|
dlg.setAcceptMode(QFileDialog::AcceptOpen);
|
||||||
|
dlg.setFileMode(QFileDialog::ExistingFiles);
|
||||||
|
if (!dlg.exec())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Iterate selected files
|
||||||
|
QStringList files = dlg.selectedFiles();
|
||||||
|
for (QString filename: files)
|
||||||
|
{
|
||||||
|
QFile f(filename);
|
||||||
|
f.open(QFile::ReadOnly);
|
||||||
|
if (f.isOpen())
|
||||||
|
{
|
||||||
|
// Get data from file
|
||||||
|
QByteArray content = f.readAll();
|
||||||
|
|
||||||
|
// Compress them
|
||||||
|
QByteArray compressed = qCompress(content);
|
||||||
|
|
||||||
|
// Put it to Attachment instance
|
||||||
|
PAttachment att(new Attachment());
|
||||||
|
att->setTaskId(mTask->id());
|
||||||
|
att->setIndex(mModel->rowCount());
|
||||||
|
QFileInfo fi(filename);
|
||||||
|
att->setFilename(fi.fileName());
|
||||||
|
|
||||||
|
// Save everything
|
||||||
|
att->save();
|
||||||
|
att->saveContent(compressed);
|
||||||
|
|
||||||
|
mModel->addItem(att);
|
||||||
|
}
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh hasAttachments property on owner task
|
||||||
|
mTask->checkAttachments();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachmentsList::exportFile()
|
||||||
|
{
|
||||||
|
QModelIndexList mil = ui->mListView->selectionModel()->selectedIndexes();
|
||||||
|
foreach (const QModelIndex& index, mil)
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QFileDialog dlg(mParentWidget, Qt::Sheet);
|
||||||
|
dlg.setWindowTitle(tr("Select file(s) for export"));
|
||||||
|
dlg.setAcceptDrops(false);
|
||||||
|
dlg.setAcceptMode(QFileDialog::AcceptSave);
|
||||||
|
PAttachment att = mModel->itemAt(index.row());
|
||||||
|
dlg.selectFile(att->filename());
|
||||||
|
if (!dlg.exec())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QFile outputFile(dlg.selectedFiles().front());
|
||||||
|
outputFile.open(QFile::WriteOnly);
|
||||||
|
outputFile.write(qUncompress(att->loadContent()));
|
||||||
|
outputFile.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachmentsList::deleteFile()
|
||||||
|
{
|
||||||
|
QModelIndexList mil = ui->mListView->selectionModel()->selectedIndexes();
|
||||||
|
foreach (const QModelIndex& index, mil)
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
continue;
|
||||||
|
PAttachment att = mModel->itemAt(index.row());
|
||||||
|
|
||||||
|
// Remove from DB
|
||||||
|
Storage::instance().deleteAttachment(att);
|
||||||
|
|
||||||
|
// Remove from model
|
||||||
|
mModel->removeItem(index.row());
|
||||||
|
|
||||||
|
// Iterate other items and decrease their DB table's orderid field
|
||||||
|
for (int row = index.row() + 1; row < mModel->rowCount(); row++)
|
||||||
|
{
|
||||||
|
Attachment& att = *mModel->itemAt(row);
|
||||||
|
att.setIndex(att.index() - 1);
|
||||||
|
att.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh hasAttachment property value on task
|
||||||
|
mTask->checkAttachments();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AttachmentsList::renameFile()
|
||||||
|
{
|
||||||
|
QModelIndex index = ui->mListView->currentIndex();
|
||||||
|
if (index.isValid())
|
||||||
|
ui->mListView->edit(index);
|
||||||
|
}
|
||||||
64
client/attachmentslist.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#ifndef ATTACHMENTSLIST_H
|
||||||
|
#define ATTACHMENTSLIST_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QFileIconProvider>
|
||||||
|
|
||||||
|
#include "task.h"
|
||||||
|
#include "taskaction.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class AttachmentsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AttachmentsListModel: public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AttachmentsListModel(PTask task, ChangesHistory& history, const AttachmentArray& items, QObject* parent = 0);
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||||
|
QVariant data(const QModelIndex &index, int role) const;
|
||||||
|
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||||
|
bool setData(const QModelIndex &index, const QVariant &value, int role /* = Qt::EditRole */);
|
||||||
|
|
||||||
|
void addItem(PAttachment att, int index = -1);
|
||||||
|
void removeItem(int row);
|
||||||
|
PAttachment itemAt(int row) const;
|
||||||
|
int findRow(PAttachment att) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PTask mTask;
|
||||||
|
ChangesHistory& mHistory;
|
||||||
|
AttachmentArray mData;
|
||||||
|
QFileIconProvider mIconProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AttachmentsList : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AttachmentsList(QWidget *parent = 0);
|
||||||
|
~AttachmentsList();
|
||||||
|
void setTask(PTask task);
|
||||||
|
void setParentWidget(QWidget* w);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::AttachmentsList *ui;
|
||||||
|
PTask mTask;
|
||||||
|
QWidget* mParentWidget;
|
||||||
|
AttachmentsListModel* mModel;
|
||||||
|
ChangesHistory mHistory;
|
||||||
|
|
||||||
|
void updateActionsState();
|
||||||
|
public slots:
|
||||||
|
void contextualMenu(const QPoint& point);
|
||||||
|
void importFile();
|
||||||
|
void exportFile();
|
||||||
|
void deleteFile();
|
||||||
|
void renameFile();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ATTACHMENTSLIST_H
|
||||||
160
client/attachmentslist.ui
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AttachmentsList</class>
|
||||||
|
<widget class="QWidget" name="AttachmentsList">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>425</width>
|
||||||
|
<height>300</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QListView" name="mListView">
|
||||||
|
<property name="contextMenuPolicy">
|
||||||
|
<enum>Qt::CustomContextMenu</enum>
|
||||||
|
</property>
|
||||||
|
<property name="viewMode">
|
||||||
|
<enum>QListView::IconMode</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
<action name="mRenameAction">
|
||||||
|
<property name="text">
|
||||||
|
<string>Rename</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Rename</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="mDeleteAction">
|
||||||
|
<property name="text">
|
||||||
|
<string>Delete</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="mExportAction">
|
||||||
|
<property name="text">
|
||||||
|
<string>Export...</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Export</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="mImportAction">
|
||||||
|
<property name="text">
|
||||||
|
<string>Import new...</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Import</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>mListView</sender>
|
||||||
|
<signal>customContextMenuRequested(QPoint)</signal>
|
||||||
|
<receiver>AttachmentsList</receiver>
|
||||||
|
<slot>contextualMenu(QPoint)</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>212</x>
|
||||||
|
<y>149</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>212</x>
|
||||||
|
<y>149</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>mImportAction</sender>
|
||||||
|
<signal>triggered()</signal>
|
||||||
|
<receiver>AttachmentsList</receiver>
|
||||||
|
<slot>importFile()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>-1</x>
|
||||||
|
<y>-1</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>212</x>
|
||||||
|
<y>149</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>mExportAction</sender>
|
||||||
|
<signal>triggered()</signal>
|
||||||
|
<receiver>AttachmentsList</receiver>
|
||||||
|
<slot>exportFile()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>-1</x>
|
||||||
|
<y>-1</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>212</x>
|
||||||
|
<y>149</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>mDeleteAction</sender>
|
||||||
|
<signal>triggered()</signal>
|
||||||
|
<receiver>AttachmentsList</receiver>
|
||||||
|
<slot>deleteFile()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>-1</x>
|
||||||
|
<y>-1</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>212</x>
|
||||||
|
<y>149</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>mRenameAction</sender>
|
||||||
|
<signal>triggered()</signal>
|
||||||
|
<receiver>AttachmentsList</receiver>
|
||||||
|
<slot>renameFile()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>-1</x>
|
||||||
|
<y>-1</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>212</x>
|
||||||
|
<y>149</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
<slots>
|
||||||
|
<slot>contextualMenu(QPoint)</slot>
|
||||||
|
<slot>importFile()</slot>
|
||||||
|
<slot>exportFile()</slot>
|
||||||
|
<slot>deleteFile()</slot>
|
||||||
|
<slot>renameFile()</slot>
|
||||||
|
</slots>
|
||||||
|
</ui>
|
||||||
142
client/chooka.pro
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#-------------------------------------------------
|
||||||
|
#
|
||||||
|
# Project created by QtCreator 2014-01-21T12:27:27
|
||||||
|
#
|
||||||
|
#-------------------------------------------------
|
||||||
|
|
||||||
|
QT += core gui
|
||||||
|
|
||||||
|
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||||
|
greaterThan(QT_MAJOR_VERSION, 4): QT += printsupport
|
||||||
|
CONFIG += c++11
|
||||||
|
TARGET = Litt
|
||||||
|
TEMPLATE = app
|
||||||
|
VERSION = 0.8.17
|
||||||
|
VERSTR = '\\"$${VERSION}\\"'
|
||||||
|
DEFINES += VER=\"$${VERSTR}\"
|
||||||
|
|
||||||
|
win32 {
|
||||||
|
DEFINES += TARGET_WIN
|
||||||
|
}
|
||||||
|
|
||||||
|
macx {
|
||||||
|
QMAKE_MAC_SDK = macosx10.12
|
||||||
|
DEFINES += TARGET_OSX
|
||||||
|
# DEFINES += USE_LOGGER
|
||||||
|
LIBS += ../lib/osx/libssl.a ../lib/osx/libcrypto.a
|
||||||
|
LIBS += -framework CoreFoundation
|
||||||
|
LIBS += -framework Cocoa
|
||||||
|
QMAKE_CXXFLAGS += -std=c++11
|
||||||
|
QMAKE_CXXFLAGS += -std=c++0x
|
||||||
|
QMAKE_LFLAGS += -std=c++11
|
||||||
|
|
||||||
|
License.files = License.rtf
|
||||||
|
License.path = Contents/Resources
|
||||||
|
QMAKE_BUNDLE_DATA += License
|
||||||
|
}
|
||||||
|
|
||||||
|
ICON = icons/appicon-osx.icns
|
||||||
|
|
||||||
|
DEFINES += USE_ENCRYPTED_DB
|
||||||
|
DEFINES += SQLITE_HAS_CODEC SQLITE_TEMP_STORE=2 SQLITE_THREADSAFE
|
||||||
|
INCLUDEPATH += sqlitecpp/include
|
||||||
|
INCLUDEPATH += ../lib/include
|
||||||
|
|
||||||
|
|
||||||
|
SOURCES += main.cpp\
|
||||||
|
mainwindow.cpp \
|
||||||
|
storage.cpp \
|
||||||
|
task.cpp \
|
||||||
|
tasktreemodel.cpp \
|
||||||
|
helper.cpp \
|
||||||
|
encryption.cpp \
|
||||||
|
newpassworddlg.cpp \
|
||||||
|
passworddlg.cpp \
|
||||||
|
appevents.cpp \
|
||||||
|
sqlitecpp/src/Column.cpp \
|
||||||
|
sqlitecpp/src/Database.cpp \
|
||||||
|
sqlitecpp/src/Statement.cpp \
|
||||||
|
sqlitecpp/src/Transaction.cpp \
|
||||||
|
sqlite3.c \
|
||||||
|
preferencesdlg.cpp \
|
||||||
|
settings.cpp \
|
||||||
|
timetreedlg.cpp \
|
||||||
|
timetreemodel.cpp \
|
||||||
|
timereportwizard.cpp \
|
||||||
|
platforms/hidtracker.cpp \
|
||||||
|
platforms/hidtrackerimpl.cpp \
|
||||||
|
logger.cpp \
|
||||||
|
timeintervaldlg.cpp \
|
||||||
|
aboutdlg.cpp \
|
||||||
|
attachmentslist.cpp \
|
||||||
|
attachmentsdialog.cpp \
|
||||||
|
taskaction.cpp \
|
||||||
|
finddialog.cpp \
|
||||||
|
findsupport.cpp \
|
||||||
|
stopworkdialog.cpp \
|
||||||
|
startworkdialog.cpp \
|
||||||
|
twofish.cpp
|
||||||
|
|
||||||
|
HEADERS += mainwindow.h \
|
||||||
|
storage.h \
|
||||||
|
task.h \
|
||||||
|
tasktreemodel.h \
|
||||||
|
config.h \
|
||||||
|
helper.h \
|
||||||
|
encryption.h \
|
||||||
|
appevents.h \
|
||||||
|
newpassworddlg.h \
|
||||||
|
passworddlg.h \
|
||||||
|
appevents.h \
|
||||||
|
sqlitecpp/include/SQLiteCpp/Assertion.h \
|
||||||
|
sqlitecpp/include/SQLiteCpp/Column.h \
|
||||||
|
sqlitecpp/include/SQLiteCpp/Database.h \
|
||||||
|
sqlitecpp/include/SQLiteCpp/Exception.h \
|
||||||
|
sqlitecpp/include/SQLiteCpp/SQLiteCpp.h \
|
||||||
|
sqlitecpp/include/SQLiteCpp/Statement.h \
|
||||||
|
sqlitecpp/include/SQLiteCpp/Transaction.h \
|
||||||
|
preferencesdlg.h \
|
||||||
|
settings.h \
|
||||||
|
timetreedlg.h \
|
||||||
|
timetreemodel.h \
|
||||||
|
timereportwizard.h \
|
||||||
|
sqlite3.h \
|
||||||
|
sqlite3ext.h \
|
||||||
|
platforms/hidtracker.h \
|
||||||
|
platforms/hidtrackerimpl.h \
|
||||||
|
platforms/osx/hidtrackerimpl_osx.h \
|
||||||
|
logger.h \
|
||||||
|
timeintervaldlg.h \
|
||||||
|
aboutdlg.h \
|
||||||
|
attachmentslist.h \
|
||||||
|
attachmentsdialog.h \
|
||||||
|
platforms/osx/sleeptracker_osx.h \
|
||||||
|
taskaction.h \
|
||||||
|
finddialog.h \
|
||||||
|
findsupport.h \
|
||||||
|
stopworkdialog.h \
|
||||||
|
startworkdialog.h \
|
||||||
|
twofish.h
|
||||||
|
|
||||||
|
FORMS += mainwindow.ui \
|
||||||
|
newpassworddlg.ui \
|
||||||
|
passworddlg.ui \
|
||||||
|
preferencesdlg.ui \
|
||||||
|
timetreedlg.ui \
|
||||||
|
timeintervaldlg.ui \
|
||||||
|
aboutdlg.ui \
|
||||||
|
attachmentslist.ui \
|
||||||
|
attachmentsdialog.ui \
|
||||||
|
finddialog.ui \
|
||||||
|
stopworkdialog.ui \
|
||||||
|
startworkdialog.ui
|
||||||
|
|
||||||
|
RESOURCES = mainwindow.qrc
|
||||||
|
|
||||||
|
OBJECTIVE_SOURCES += \
|
||||||
|
platforms/osx/hidtrackerimpl_osx.mm \
|
||||||
|
platforms/osx/sleeptracker_osx.mm
|
||||||
|
|
||||||
|
!include("fervor/Fervor.pri") {
|
||||||
|
error("Unable to include Fervor autoupdater.")
|
||||||
|
}
|
||||||
51
client/config.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#ifndef __APP_CONFIG_H
|
||||||
|
#define __APP_CONFIG_H
|
||||||
|
|
||||||
|
// Application name
|
||||||
|
#define APPNAME "Litt"
|
||||||
|
|
||||||
|
// Company name
|
||||||
|
#define COMPANY "voipobjects.com"
|
||||||
|
|
||||||
|
// Default database name
|
||||||
|
#define DATABASENAME "database.db"
|
||||||
|
|
||||||
|
// Log file name
|
||||||
|
#define LOGNAME "litt-log.txt"
|
||||||
|
|
||||||
|
// About text
|
||||||
|
#define ABOUTTEXT "Tiny outliner with time tracking capabilities"
|
||||||
|
|
||||||
|
// Text for message box with "not implemented" notification
|
||||||
|
#define NOTIMPLEMENTEDTEXT "Not implemented in this version"
|
||||||
|
|
||||||
|
// Settings file name
|
||||||
|
#define SETTINGS_FILENAME "litt.ini"
|
||||||
|
|
||||||
|
// No password string
|
||||||
|
#define NOPASSWORDSTRING ""
|
||||||
|
|
||||||
|
// Mime type of task items
|
||||||
|
#define NODE_MIME_TYPE "application/litt-node"
|
||||||
|
|
||||||
|
// Exit code to signal about problems with database
|
||||||
|
#define EXITCODE_NO_DATABASE (1)
|
||||||
|
|
||||||
|
// Timeline save to DB interval in seconds
|
||||||
|
#define TIMELINE_FLUSH_INTERVAL (300)
|
||||||
|
|
||||||
|
// Text flushing interval
|
||||||
|
#define TEXT_FLUSH_INTERVAL (10)
|
||||||
|
|
||||||
|
#ifdef TARGET_OSX
|
||||||
|
#define TRAY_START_ICON_NAME ":/icons/icons/starttracking-osx.png"
|
||||||
|
#define TRAY_STOP_ICON_NAME ":/icons/icons/stoptracking-osx.png"
|
||||||
|
|
||||||
|
#define TRAY_DEFAULT_ICON_NAME ":/icons/icons/tray-default.png"
|
||||||
|
#define TRAY_RUNNING_ICON_NAME ":/icons/icons/tray-running.png"
|
||||||
|
|
||||||
|
#define ACTION_START_ICON_NAME ":/icons/icons/clock-32x32.png"
|
||||||
|
#define ACTION_STOP_ICON_NAME ":/icons/icons/process-stop.png"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
191
client/encryption.cpp
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
#include "encryption.h"
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#ifdef TARGET_WIN
|
||||||
|
# include <inttypes.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BlowfishCipher::BlowfishCipher()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
BlowfishCipher::~BlowfishCipher()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlowfishCipher::setKey(const QByteArray &ba)
|
||||||
|
{
|
||||||
|
mKey = ba;
|
||||||
|
BF_set_key(&mContext, ba.count(), (unsigned char*)ba.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray& BlowfishCipher::key() const
|
||||||
|
{
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlowfishCipher::encrypt(const QByteArray &plain, QByteArray &encrypted)
|
||||||
|
{
|
||||||
|
QByteArray plainProxy(plain);
|
||||||
|
if (plainProxy.length() % 8)
|
||||||
|
plainProxy.resize((8 - plainProxy.length() % 8) + plainProxy.length());
|
||||||
|
encrypted.resize(plainProxy.length());
|
||||||
|
|
||||||
|
BF_cbc_encrypt((unsigned char*)plainProxy.data(), (unsigned char*)encrypted.data(), plainProxy.length(), &mContext, (unsigned char*)mIV.data(), BF_ENCRYPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlowfishCipher::decrypt(const QByteArray &encrypted, QByteArray &plain)
|
||||||
|
{
|
||||||
|
assert(encrypted.length() % 8 == 0);
|
||||||
|
plain.resize(encrypted.length());
|
||||||
|
BF_cbc_encrypt((unsigned char*)encrypted.data(), (unsigned char*)plain.data(), encrypted.length(), &mContext, (unsigned char*)mIV.data(), BF_DECRYPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------- Twofish encryption ---------
|
||||||
|
TwofishCipher::TwofishCipher()
|
||||||
|
:mContext(nullptr)
|
||||||
|
{
|
||||||
|
mContext = new Twofish();
|
||||||
|
}
|
||||||
|
|
||||||
|
TwofishCipher::~TwofishCipher()
|
||||||
|
{
|
||||||
|
delete mContext; mContext = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwofishCipher::setKey(const QByteArray &ba)
|
||||||
|
{
|
||||||
|
mKey = ba;
|
||||||
|
mContext->PrepareKey((Twofish_Byte*)ba.data(), ba.count(), &mKeyStruct);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray& TwofishCipher::key() const
|
||||||
|
{
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwofishCipher::setIV(const QByteArray &ba)
|
||||||
|
{
|
||||||
|
assert(ba.size() == 16);
|
||||||
|
mIV = ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray& TwofishCipher::IV() const
|
||||||
|
{
|
||||||
|
return mIV;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwofishCipher::encrypt(const QByteArray &plain, int plainOffset, QByteArray &encrypted, int encryptedOffset)
|
||||||
|
{
|
||||||
|
assert((encrypted.length() - encryptedOffset) % 16 == 0);
|
||||||
|
|
||||||
|
// Prepare XOR template
|
||||||
|
uint8_t xorTemplate[16];
|
||||||
|
memcpy(xorTemplate, mIV.data(), 16);
|
||||||
|
|
||||||
|
// Iterate blocks
|
||||||
|
int blockCount = (plain.size() - plainOffset) / 16;
|
||||||
|
for (int i=0; i<blockCount; i++)
|
||||||
|
{
|
||||||
|
uint8_t* blockInput = (uint8_t*)plain.data() + i * 16 + plainOffset;
|
||||||
|
uint8_t* blockOutput = (uint8_t*)encrypted.data() + i * 16 + encryptedOffset;
|
||||||
|
|
||||||
|
// Xor block at first
|
||||||
|
uint8_t xored[16];
|
||||||
|
for (int xorIndex = 0; xorIndex < 16; xorIndex++)
|
||||||
|
xored[i] = blockInput[xorIndex] ^ xorTemplate[xorIndex];
|
||||||
|
|
||||||
|
// Encrypt block
|
||||||
|
mContext->Encrypt(&mKeyStruct, xored, blockOutput);
|
||||||
|
|
||||||
|
// Replace XOR template
|
||||||
|
memcpy(xorTemplate, blockOutput, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt tail if it exists
|
||||||
|
int tail = (plain.size() - plainOffset) % blocksize();
|
||||||
|
if (tail)
|
||||||
|
{
|
||||||
|
uint8_t* blockInput = (uint8_t*)plain.data() + blockCount * 16 + plainOffset;
|
||||||
|
uint8_t* blockOutput = (uint8_t*)encrypted.data() + blockCount * 16 + encryptedOffset;
|
||||||
|
|
||||||
|
// Xor block at first
|
||||||
|
uint8_t xored[16];
|
||||||
|
for (int xorIndex = 0; xorIndex < 16; xorIndex++)
|
||||||
|
{
|
||||||
|
if (xorIndex < plain.size())
|
||||||
|
xored[xorIndex] = blockInput[xorIndex] ^ xorTemplate[xorIndex];
|
||||||
|
else
|
||||||
|
xored[xorIndex] = xorTemplate[xorIndex];
|
||||||
|
}
|
||||||
|
// Encrypt block
|
||||||
|
mContext->Encrypt(&mKeyStruct, xored, blockOutput);
|
||||||
|
|
||||||
|
// Replace XOR template
|
||||||
|
memcpy(xorTemplate, blockOutput, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwofishCipher::decrypt(const QByteArray &encrypted, int encryptedOffset, QByteArray &plain, int plainOffset)
|
||||||
|
{
|
||||||
|
assert(encrypted.size() - encryptedOffset == plain.size() - plainOffset);
|
||||||
|
assert((encrypted.size() - encryptedOffset) % 16 == 0);
|
||||||
|
|
||||||
|
// Prepare XOR template
|
||||||
|
uint8_t xorTemplate[16];
|
||||||
|
memcpy(xorTemplate, mIV.data(), 16);
|
||||||
|
|
||||||
|
// Iterate blocks
|
||||||
|
int blockCount = encrypted.size() / 16;
|
||||||
|
for (int i=0; i<blockCount; i++)
|
||||||
|
{
|
||||||
|
uint8_t* blockInput = (uint8_t*)encrypted.data() + i * 16 + encryptedOffset;
|
||||||
|
uint8_t* blockOutput = (uint8_t*)plain.data() + i * 16 + plainOffset;
|
||||||
|
|
||||||
|
// Decrypt at first
|
||||||
|
mContext->Decrypt(&mKeyStruct, blockInput, blockOutput);
|
||||||
|
|
||||||
|
// Xor block
|
||||||
|
for (int xorIndex = 0; xorIndex < 16; xorIndex++)
|
||||||
|
blockOutput[i] = blockOutput[xorIndex] ^ xorTemplate[xorIndex];
|
||||||
|
|
||||||
|
// Replace XOR template
|
||||||
|
memcpy(xorTemplate, blockInput, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ----------- SHA256 wrapper -------------
|
||||||
|
|
||||||
|
SHA256::SHA256()
|
||||||
|
{
|
||||||
|
SHA256_Init(&mContext);
|
||||||
|
mDigest.resize(SHA256_DIGEST_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
SHA256::~SHA256()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SHA256::update(const void *data, int length)
|
||||||
|
{
|
||||||
|
SHA256_Update(&mContext, data, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SHA256::final()
|
||||||
|
{
|
||||||
|
SHA256_Final((unsigned char*)mDigest.data(), &mContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------- IV ----------------
|
||||||
|
|
||||||
|
void IV::Generate(QByteArray &buffer)
|
||||||
|
{
|
||||||
|
uint8_t* data = (uint8_t*)buffer.data();
|
||||||
|
for (int i=0; i<buffer.length(); i++)
|
||||||
|
data[i] = qrand() % 256;
|
||||||
|
}
|
||||||
74
client/encryption.h
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#ifndef ENCRYPTION_H
|
||||||
|
#define ENCRYPTION_H
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <openssl/blowfish.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
#include "twofish.h"
|
||||||
|
|
||||||
|
class BlowfishCipher
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
QByteArray mKey;
|
||||||
|
QByteArray mIV;
|
||||||
|
BF_KEY mContext;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BlowfishCipher();
|
||||||
|
~BlowfishCipher();
|
||||||
|
void setKey(const QByteArray& ba);
|
||||||
|
const QByteArray& key() const;
|
||||||
|
void setIV(const QByteArray& ba);
|
||||||
|
QByteArray& IV() const;
|
||||||
|
|
||||||
|
void encrypt(const QByteArray& plain, QByteArray& encrypted);
|
||||||
|
void decrypt(const QByteArray& encrypted, QByteArray& plain);
|
||||||
|
};
|
||||||
|
|
||||||
|
class TwofishCipher
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
QByteArray mKey;
|
||||||
|
QByteArray mIV;
|
||||||
|
TwofishKey mKeyStruct;
|
||||||
|
Twofish* mContext;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TwofishCipher();
|
||||||
|
~TwofishCipher();
|
||||||
|
|
||||||
|
int blocksize() const { return 16; }
|
||||||
|
void setKey(const QByteArray& ba);
|
||||||
|
const QByteArray& key() const;
|
||||||
|
|
||||||
|
// IV vector must be 16 bytes
|
||||||
|
void setIV(const QByteArray& ba);
|
||||||
|
const QByteArray& IV() const;
|
||||||
|
|
||||||
|
// Plain and encrypted must be padded to 16 bytes boundary before call
|
||||||
|
void encrypt(const QByteArray& plain, int plainOffset, QByteArray& encrypted, int encryptedOffset);
|
||||||
|
void decrypt(const QByteArray& encrypted, int encryptedOffset, QByteArray& plain, int plainOffset);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class SHA256
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
SHA256_CTX mContext;
|
||||||
|
QByteArray mDigest;
|
||||||
|
public:
|
||||||
|
SHA256();
|
||||||
|
~SHA256();
|
||||||
|
|
||||||
|
QByteArray& digest();
|
||||||
|
void update(const void* data, int length);
|
||||||
|
void final();
|
||||||
|
};
|
||||||
|
|
||||||
|
class IV
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void Generate(QByteArray& buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ENCRYPTION_H
|
||||||
62
client/fervor/CMakeLists.txt
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.8.3)
|
||||||
|
project(Fervor)
|
||||||
|
option(FERVOR_BUILD_SAMPLE "Should the sample be built?" OFF)
|
||||||
|
option(FERVOR_BUILD_TESTS "Should the tests be built" OFF)
|
||||||
|
find_package(Qt4 REQUIRED)
|
||||||
|
set(QT_USE_QTNETWORK true)
|
||||||
|
set(QT_USE_QTWEBKIT true)
|
||||||
|
include(${QT_USE_FILE})
|
||||||
|
add_definitions(${QT_DEFINITIONS})
|
||||||
|
if(DEFINED FERVOR_APP_VERSION)
|
||||||
|
add_definitions(-DFV_APP_VERSION="${FERVOR_APP_VERSION}")
|
||||||
|
else()
|
||||||
|
add_definitions(-DFV_APP_VERSION="1.0")
|
||||||
|
message(WARNING "No version information for Fervor given (please set FERVOR_APP_VERSION)! Assuming 1.0.")
|
||||||
|
endif()
|
||||||
|
if(DEFINED FERVOR_APP_NAME)
|
||||||
|
add_definitions(-DFV_APP_NAME="${FERVOR_APP_NAME}")
|
||||||
|
else()
|
||||||
|
add_definitions(-DFV_APP_NAME="App Name")
|
||||||
|
message(WARNING "No application name for Fervor given (please set FERVOR_APP_NAME)! Assuming 'App Name'.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
fvavailableupdate.cpp
|
||||||
|
fvignoredversions.cpp
|
||||||
|
fvplatform.cpp
|
||||||
|
fvupdateconfirmdialog.cpp
|
||||||
|
fvupdater.cpp
|
||||||
|
fvupdatewindow.cpp
|
||||||
|
fvversioncomparator.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(MOC_HEADERS
|
||||||
|
fvavailableupdate.h
|
||||||
|
fvignoredversions.h
|
||||||
|
fvplatform.h
|
||||||
|
fvupdateconfirmdialog.h
|
||||||
|
fvupdater.h
|
||||||
|
fvupdatewindow.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(UIS
|
||||||
|
fvupdateconfirmdialog.ui
|
||||||
|
fvupdatewindow.ui
|
||||||
|
)
|
||||||
|
|
||||||
|
qt4_wrap_ui(UI_HEADERS ${UIS})
|
||||||
|
qt4_wrap_cpp(MOC_SOURCES ${MOC_HEADERS})
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${CMAKE_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
)
|
||||||
|
add_library(Fervor ${SOURCES} ${MOC_HEADERS} ${MOC_SOURCES} ${UIS})
|
||||||
|
|
||||||
|
if(FERVOR_BUILD_SAMPLE)
|
||||||
|
add_subdirectory(sample)
|
||||||
|
endif()
|
||||||
|
if(FERVOR_BUILD_TESTS)
|
||||||
|
ENABLE_TESTING()
|
||||||
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
52
client/fervor/Fervor.pri
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
QT += core gui widgets network
|
||||||
|
|
||||||
|
isEmpty(FV_APP_NAME) {
|
||||||
|
warning("Fervor: falling back to application name '$$TARGET'")
|
||||||
|
DEFINES += FV_APP_NAME=\\\"$$TARGET\\\"
|
||||||
|
} else {
|
||||||
|
message("Fervor: building for application name '$$FV_APP_NAME'")
|
||||||
|
DEFINES += FV_APP_NAME=\\\"$$FV_APP_NAME\\\"
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty(FV_APP_VERSION) {
|
||||||
|
warning("Fervor: falling back to application version '$$VERSION'")
|
||||||
|
DEFINES += FV_APP_VERSION=\\\"$$VERSION\\\"
|
||||||
|
} else {
|
||||||
|
message("Fervor: building for application version '$$FV_APP_VERSION'")
|
||||||
|
DEFINES += FV_APP_VERSION=\\\"$$FV_APP_VERSION\\\"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Unit tests
|
||||||
|
#DEFINES += FV_DEBUG=1
|
||||||
|
#DEPENDPATH += "$$PWD/tests/"
|
||||||
|
#INCLUDEPATH += "$$PWD/tests/"
|
||||||
|
#CONFIG += qtestlib
|
||||||
|
#SOURCES += tests/fvversioncomparatortest.cpp
|
||||||
|
#HEADERS += tests/fvversioncomparatortest.h
|
||||||
|
|
||||||
|
DEPENDPATH += "$$PWD"
|
||||||
|
INCLUDEPATH += "$$PWD"
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
$$PWD/fvupdatewindow.cpp \
|
||||||
|
$$PWD/fvupdater.cpp \
|
||||||
|
$$PWD/fvversioncomparator.cpp \
|
||||||
|
$$PWD/fvplatform.cpp \
|
||||||
|
$$PWD/fvignoredversions.cpp \
|
||||||
|
$$PWD/fvavailableupdate.cpp \
|
||||||
|
$$PWD/fvupdateconfirmdialog.cpp
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
$$PWD/fvupdatewindow.h \
|
||||||
|
$$PWD/fvupdater.h \
|
||||||
|
$$PWD/fvversioncomparator.h \
|
||||||
|
$$PWD/fvplatform.h \
|
||||||
|
$$PWD/fvignoredversions.h \
|
||||||
|
$$PWD/fvavailableupdate.h \
|
||||||
|
$$PWD/fvupdateconfirmdialog.h
|
||||||
|
|
||||||
|
FORMS += $$PWD/fvupdatewindow.ui \
|
||||||
|
$$PWD/fvupdateconfirmdialog.ui
|
||||||
|
|
||||||
|
TRANSLATIONS += $$PWD/fervor_lt.ts
|
||||||
|
CODECFORTR = UTF-8
|
||||||
7
client/fervor/LICENSE
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Copyright (c) 2012 Linas Valiukas and others.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
213
client/fervor/README.mdown
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
Check out the [`autoupdate` branch](fervor/tree/autoupdate) by Torben Dannhauer too!
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Fervor is a simple, multiplatform ([Qt](http://qt.nokia.com/)-based) application update tool, inspired by [Sparkle](http://sparkle.andymatuschak.org/).
|
||||||
|
|
||||||
|
|
||||||
|
# Description
|
||||||
|
|
||||||
|
Fervor is a software library that you include into your own [Qt](http://qt.nokia.com/)-based application in order to enable the application to automatically check for updates and suggest to install them.
|
||||||
|
|
||||||
|
When installed and enabled, Fervor downloads a "flavoured" RSS feed (dubbed "appcast") and checks whether an update to the application is available. The RSS feed ("appcast") lists various versions of the application ("1.0", "1.1", "2.0", ...) that are available for download.
|
||||||
|
|
||||||
|
When a newer version of the application is found in the "appcast" (e.g. the user is using 1.0, and 1.1 is available), a dialog is presented to the user (see below for example) that allows the user to choose whether he/she wants to install the update, be reminded about the update later, or skip a particular proposed version altogether. A dialog also shows some release notes about the proposed update that help the user to choose whether or not to install an update.
|
||||||
|
|
||||||
|
At the moment, Fervor is not as cool as [Sparkle](http://sparkle.andymatuschak.org/) -- it is not able to install the actual update automatically (the user is given an option to download and install the update manually). Pull requests with unattended install modules for `.dmg`, `.pkg` (Mac OS X), `.msi` (Windows), `.rpm`, `.deb` (Linux) are welcome!
|
||||||
|
|
||||||
|
|
||||||
|
# Features
|
||||||
|
|
||||||
|
* Multiplaform - works (should work) on Windows, Mac OS X, Linux, ...
|
||||||
|
* Checks for updates both automatically and manually.
|
||||||
|
* Displays release notes.
|
||||||
|
* Proposed updates might be skipped or installed later by the user.
|
||||||
|
* Backwards compatible with Sparkle (can use the very same "appcast" RSS).
|
||||||
|
* BSD license.
|
||||||
|
|
||||||
|
|
||||||
|
# Screenshots
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
# Installation and Usage
|
||||||
|
|
||||||
|
(This is a description of the sample application located in `sample/`.)
|
||||||
|
|
||||||
|
I'm writing an application called *Sample*. It's version is only `1.0`, and I would like to enable the users of my application to be notified when I decide to release new versions of *Sample* (`1.1`, `1.2`, maybe even `2.0` if I'm lucky) and help them to download and install each and every update of the application. Thus, I decide I'll use Fervor for that.
|
||||||
|
|
||||||
|
This is what I do:
|
||||||
|
|
||||||
|
|
||||||
|
## 1. Download Fervor
|
||||||
|
|
||||||
|
Git clone Fervor:
|
||||||
|
|
||||||
|
git clone https://github.com/pypt/fervor.git fervor
|
||||||
|
|
||||||
|
...or add it as a submodule if you're using Git in your project:
|
||||||
|
|
||||||
|
git submodule add https://github.com/pypt/fervor.git fervor
|
||||||
|
git submodule init
|
||||||
|
git submodule update
|
||||||
|
|
||||||
|
You might also download [a tarball](https://github.com/pypt/fervor/tarball/master).
|
||||||
|
|
||||||
|
|
||||||
|
## 2. Include Fervor into your project
|
||||||
|
|
||||||
|
You can use Fervor either in your Qt Creator project or in your CMake project.
|
||||||
|
|
||||||
|
If you want to include Fervor into your Qt Creator project, read section #1 below.
|
||||||
|
|
||||||
|
If you use CMake and would like to build Fervor with it instead of Qt Creator, read section #2 below.
|
||||||
|
|
||||||
|
|
||||||
|
### 1. Include Fervor into your Qt Creator Project (`.pro` file)
|
||||||
|
|
||||||
|
Include Fervor's project include file `Fervor.pri` after setting your application's `TARGET` and `VERSION`:
|
||||||
|
|
||||||
|
QT += core gui
|
||||||
|
|
||||||
|
TARGET = Sample
|
||||||
|
VERSION = 1.0
|
||||||
|
TEMPLATE = app
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# Fervor autoupdater
|
||||||
|
!include("fervor/Fervor.pri") {
|
||||||
|
error("Unable to include Fervor autoupdater.")
|
||||||
|
}
|
||||||
|
|
||||||
|
Fervor will append itself to your application's `HEADERS`, `SOURCES`, `FORMS` and `TRANSLATIONS`, and thus will become an integral part of your application's binary.
|
||||||
|
|
||||||
|
**or**
|
||||||
|
|
||||||
|
### 2. Include Fervor into your CMake Project (`CMakeLists.txt` file)
|
||||||
|
|
||||||
|
In your `CMakeLists.txt` set the application name and version info, include the fervor-directory and link with it:
|
||||||
|
|
||||||
|
set(FERVOR_APP_NAME "Sample")
|
||||||
|
set(FERVOR_APP_VERSION "1.0")
|
||||||
|
|
||||||
|
add_subdirectory(fervor-directory)
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
...
|
||||||
|
fervor-directory
|
||||||
|
)
|
||||||
|
...
|
||||||
|
target_link_libraries(Sample
|
||||||
|
...
|
||||||
|
Fervor
|
||||||
|
)
|
||||||
|
|
||||||
|
*Note:* `fervor-directory` must be a subdirectory in your source-directory.
|
||||||
|
|
||||||
|
## 3. Set your application's `applicationName`, `applicationVersion`, `organizationName` and `organizationDomain` if you haven't done so already
|
||||||
|
|
||||||
|
Fervor uses `QApplication::applicationName()`, `QApplication::applicationVersion()`, `QApplication::organizationName()` and `QApplication::organizationDomain()` for its own needs.
|
||||||
|
|
||||||
|
If you don't set `QApplication::applicationName()` and `QApplication::applicationVersion()`, Fervor will do that for you. However, `QApplication::organizationName()` and `QApplication::organizationDomain()` have to be set by hand.
|
||||||
|
|
||||||
|
Example of setting those four values:
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
|
QApplication::setApplicationName("Sample");
|
||||||
|
QApplication::setApplicationVersion("1.0");
|
||||||
|
QApplication::setOrganizationName("pypt");
|
||||||
|
QApplication::setOrganizationDomain("pypt.lt");
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## 4. Set the Fervor's appcast URL right after you start your application
|
||||||
|
|
||||||
|
Set the "appcast" URL in Fervor's singleton `FVUpdater::sharedUpdater()` before you do anything else, but **after** you set `applicationName`, `applicationVersion`, `organizationName` and `organizationDomain` in `QApplication`. It is probably a good idea to do that in `int main(int argc, char *argv[])` too, right after setting application name, version, organization name and domain:
|
||||||
|
|
||||||
|
#include "fvupdater.h"
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
|
QApplication::setApplicationName("Sample");
|
||||||
|
QApplication::setApplicationVersion("1.0");
|
||||||
|
QApplication::setOrganizationName("pypt");
|
||||||
|
QApplication::setOrganizationDomain("pypt.lt");
|
||||||
|
|
||||||
|
// Set this to your own appcast URL, of course
|
||||||
|
FvUpdater::sharedUpdater()->SetFeedURL("https://raw.github.com/pypt/fervor/master/sample/Appcast.xml");
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## 5. Enable checking for updates right after starting the application (if you want to)
|
||||||
|
|
||||||
|
Call Fervor's `CheckForUpdatesSilent()` whenever you feel like is a good moment for the Fervor to check for updates. "Silent" part here means that Fervor will not display error dialogs or the "No updates found." notification.
|
||||||
|
|
||||||
|
I've decided that I'll check for updates in `int main(int argc, char *argv[])` too:
|
||||||
|
|
||||||
|
#include "fvupdater.h"
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
|
// <...> setApplicationName(), setApplicationVersion(), SetFeedURL(), etc.
|
||||||
|
|
||||||
|
// Check for updates silently -- this will not block the initialization of
|
||||||
|
// your application, just start a HTTP request and return immediately.
|
||||||
|
FvUpdater::sharedUpdater()->CheckForUpdatesSilent();
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## 6. Enable checking for updates manually by the user (if you want to)
|
||||||
|
|
||||||
|
You might want to implement a menu item called "Check for Updates..." that would allow the user to check for updated versions of your application manually. This menu item (or a button, or some other widget) would have to be attached to the `CheckForUpdatesNotSilent()` function of the Fervor's singleton. *Not silent* means that Fervor will notify the user about connection problems (if any) and even when no updates were found.
|
||||||
|
|
||||||
|
#include "fvupdater.h"
|
||||||
|
|
||||||
|
// <...>
|
||||||
|
|
||||||
|
// ui->updateButton is QButton
|
||||||
|
connect(ui->updateButton, SIGNAL(clicked()),
|
||||||
|
FvUpdater::sharedUpdater(), SLOT(CheckForUpdatesNotSilent()));
|
||||||
|
|
||||||
|
|
||||||
|
## 7. Publish an "appcast" somewhere suited for your needs
|
||||||
|
|
||||||
|
Again, "appcast" is an RSS feed with an additional `fervor` XML namespace. A type of "appcast" used by "Fervor" lists various application versions as `<item>`s, and a single `<item>` might point to several platform builds of your application (Windows build, Linux build, Mac OS X build, ...)
|
||||||
|
|
||||||
|
An "appcast" also links to a webpage with each version's "release notes" that are shown to the user when a particular version of the application update is proposed.
|
||||||
|
|
||||||
|
When the user clicks "Install Update", he / she is then shown a link (`<enclosure url="..." />`) of an application update download for his particular platform.
|
||||||
|
|
||||||
|
See [https://raw.github.com/pypt/fervor/master/sample/Appcast.xml](https://raw.github.com/pypt/fervor/master/sample/Appcast.xml) for an "appcast" example, and [http://pypt.github.com/fervor/RelNotes.html](http://pypt.github.com/fervor/RelNotes.html) for an "release notes" example.
|
||||||
|
|
||||||
|
It is up to you to implement "appcasts" and "release notes". If you don't release too many versions of your application, it is even plausible to edit them by hand every time you release an update.
|
||||||
|
|
||||||
|
|
||||||
|
# Donations
|
||||||
|
|
||||||
|
Donors: Marek Pazdan. Thanks!
|
||||||
|
|
||||||
|
|
||||||
|
# Contacts
|
||||||
|
|
||||||
|
I'm Linas, my email is <shirshegsm@gmail.com>
|
||||||
BIN
client/fervor/fervor_lt.qm
Normal file
127
client/fervor/fervor_lt.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.0" language="lt_LT">
|
||||||
|
<defaultcodec>UTF-8</defaultcodec>
|
||||||
|
<context>
|
||||||
|
<name>FvUpdateConfirmDialog</name>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdateconfirmdialog.ui" line="14"/>
|
||||||
|
<source>Software Update</source>
|
||||||
|
<oldsource>Update</oldsource>
|
||||||
|
<translation>Programos naujinimas</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdateconfirmdialog.ui" line="20"/>
|
||||||
|
<source>The update file is located at:</source>
|
||||||
|
<translation>Naujinio adresas:</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdateconfirmdialog.ui" line="27"/>
|
||||||
|
<source><a href="%1">%1</a></source>
|
||||||
|
<translation><a href="%1">%1</a></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdateconfirmdialog.ui" line="40"/>
|
||||||
|
<source>Download this update, close "%1", install it, and then reopen "%1".</source>
|
||||||
|
<translation>Atsisiųskite naujinį, išjunkite %1, įdiekite naujinį ir vėl paleiskite %1.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdateconfirmdialog.ui" line="47"/>
|
||||||
|
<source>When you click "OK", this link will be opened in your browser.</source>
|
||||||
|
<translation>Jei paspauskite „Gerai“, naujinio nuoroda atsidarys Jūsų naršyklėje.</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>FvUpdateWindow</name>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdatewindow.ui" line="14"/>
|
||||||
|
<source>Software Update</source>
|
||||||
|
<translation>Naujinimas</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdatewindow.ui" line="28"/>
|
||||||
|
<source>A new version of %1 is available!</source>
|
||||||
|
<oldsource>A new version of %s is available!</oldsource>
|
||||||
|
<translation>Išleista nauja „%1“ versija!</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdatewindow.ui" line="35"/>
|
||||||
|
<source>%1 %2 is now available - you have %3. Would you like to download it now?</source>
|
||||||
|
<oldsource>%s %s is now available - you have %s. Would you like to download it now?</oldsource>
|
||||||
|
<translation>Išleista nauja %1 versija %2. Jūs naudojate %3. Ar norėtumėte atnaujinti?</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdatewindow.ui" line="48"/>
|
||||||
|
<source>Release Notes:</source>
|
||||||
|
<translation>Atnaujinta:</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdatewindow.ui" line="61"/>
|
||||||
|
<source>about:blank</source>
|
||||||
|
<translation>about:blank</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdatewindow.ui" line="74"/>
|
||||||
|
<source>Skip This Version</source>
|
||||||
|
<translation>Praleisti šį naujinį</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdatewindow.ui" line="94"/>
|
||||||
|
<source>Remind Me Later</source>
|
||||||
|
<translation>Priminti vėliau</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdatewindow.ui" line="101"/>
|
||||||
|
<source>Install Update</source>
|
||||||
|
<translation>Įdiegti naujinį</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>FvUpdater</name>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdater.cpp" line="221"/>
|
||||||
|
<source>Unable to open this link in a browser. Please do it manually.</source>
|
||||||
|
<translation>Nepavyko atidaryti nuorodos naršyklėje. Pabandykite nuorodą nukopijuoti ir atidaryti pats.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdater.cpp" line="338"/>
|
||||||
|
<source>Feed download failed: %1.</source>
|
||||||
|
<translation>Nepavyko atsisiųsti informacijos apie naujinį: %1.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdater.cpp" line="471"/>
|
||||||
|
<source>Feed parsing failed: %1 %2.</source>
|
||||||
|
<translation>Nepavyko perskaityti informacijos apie naujinį: %1 %2.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdater.cpp" line="499"/>
|
||||||
|
<source>Feed error: "release notes" link is empty</source>
|
||||||
|
<translation>Nepavyko rasti nuorodos į pastabas apie naujinį</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdater.cpp" line="508"/>
|
||||||
|
<source>Feed error: invalid "release notes" link</source>
|
||||||
|
<translation>Nekorektiška nuoroda į pastabas apie naujinį</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdater.cpp" line="512"/>
|
||||||
|
<source>Feed error: invalid "enclosure" with the download link</source>
|
||||||
|
<translation>Nepavyko rasti nuorodos į failą su naujiniu</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdater.cpp" line="520"/>
|
||||||
|
<source>No updates were found.</source>
|
||||||
|
<translation>Naujinių nėra.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdater.cpp" line="562"/>
|
||||||
|
<source>Error</source>
|
||||||
|
<translation>Klaida</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="fvupdater.cpp" line="578"/>
|
||||||
|
<source>Information</source>
|
||||||
|
<translation>Informacija</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
97
client/fervor/fvavailableupdate.cpp
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#include "fvavailableupdate.h"
|
||||||
|
|
||||||
|
FvAvailableUpdate::FvAvailableUpdate(QObject *parent) :
|
||||||
|
QObject(parent)
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FvAvailableUpdate::GetTitle()
|
||||||
|
{
|
||||||
|
return m_title;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvAvailableUpdate::SetTitle(QString title)
|
||||||
|
{
|
||||||
|
m_title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl FvAvailableUpdate::GetReleaseNotesLink()
|
||||||
|
{
|
||||||
|
return m_releaseNotesLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvAvailableUpdate::SetReleaseNotesLink(QUrl releaseNotesLink)
|
||||||
|
{
|
||||||
|
m_releaseNotesLink = releaseNotesLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvAvailableUpdate::SetReleaseNotesLink(QString releaseNotesLink)
|
||||||
|
{
|
||||||
|
SetReleaseNotesLink(QUrl(releaseNotesLink));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FvAvailableUpdate::GetPubDate()
|
||||||
|
{
|
||||||
|
return m_pubDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvAvailableUpdate::SetPubDate(QString pubDate)
|
||||||
|
{
|
||||||
|
m_pubDate = pubDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl FvAvailableUpdate::GetEnclosureUrl()
|
||||||
|
{
|
||||||
|
return m_enclosureUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvAvailableUpdate::SetEnclosureUrl(QUrl enclosureUrl)
|
||||||
|
{
|
||||||
|
m_enclosureUrl = enclosureUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvAvailableUpdate::SetEnclosureUrl(QString enclosureUrl)
|
||||||
|
{
|
||||||
|
SetEnclosureUrl(QUrl(enclosureUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FvAvailableUpdate::GetEnclosureVersion()
|
||||||
|
{
|
||||||
|
return m_enclosureVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvAvailableUpdate::SetEnclosureVersion(QString enclosureVersion)
|
||||||
|
{
|
||||||
|
m_enclosureVersion = enclosureVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FvAvailableUpdate::GetEnclosurePlatform()
|
||||||
|
{
|
||||||
|
return m_enclosurePlatform;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvAvailableUpdate::SetEnclosurePlatform(QString enclosurePlatform)
|
||||||
|
{
|
||||||
|
m_enclosurePlatform = enclosurePlatform;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long FvAvailableUpdate::GetEnclosureLength()
|
||||||
|
{
|
||||||
|
return m_enclosureLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvAvailableUpdate::SetEnclosureLength(unsigned long enclosureLength)
|
||||||
|
{
|
||||||
|
m_enclosureLength = enclosureLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FvAvailableUpdate::GetEnclosureType()
|
||||||
|
{
|
||||||
|
return m_enclosureType;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvAvailableUpdate::SetEnclosureType(QString enclosureType)
|
||||||
|
{
|
||||||
|
m_enclosureType = enclosureType;
|
||||||
|
}
|
||||||
51
client/fervor/fvavailableupdate.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#ifndef FVAVAILABLEUPDATE_H
|
||||||
|
#define FVAVAILABLEUPDATE_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
class FvAvailableUpdate : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit FvAvailableUpdate(QObject *parent = 0);
|
||||||
|
|
||||||
|
QString GetTitle();
|
||||||
|
void SetTitle(QString title);
|
||||||
|
|
||||||
|
QUrl GetReleaseNotesLink();
|
||||||
|
void SetReleaseNotesLink(QUrl releaseNotesLink);
|
||||||
|
void SetReleaseNotesLink(QString releaseNotesLink);
|
||||||
|
|
||||||
|
QString GetPubDate();
|
||||||
|
void SetPubDate(QString pubDate);
|
||||||
|
|
||||||
|
QUrl GetEnclosureUrl();
|
||||||
|
void SetEnclosureUrl(QUrl enclosureUrl);
|
||||||
|
void SetEnclosureUrl(QString enclosureUrl);
|
||||||
|
|
||||||
|
QString GetEnclosureVersion();
|
||||||
|
void SetEnclosureVersion(QString enclosureVersion);
|
||||||
|
|
||||||
|
QString GetEnclosurePlatform();
|
||||||
|
void SetEnclosurePlatform(QString enclosurePlatform);
|
||||||
|
|
||||||
|
unsigned long GetEnclosureLength();
|
||||||
|
void SetEnclosureLength(unsigned long enclosureLength);
|
||||||
|
|
||||||
|
QString GetEnclosureType();
|
||||||
|
void SetEnclosureType(QString enclosureType);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_title;
|
||||||
|
QUrl m_releaseNotesLink;
|
||||||
|
QString m_pubDate;
|
||||||
|
QUrl m_enclosureUrl;
|
||||||
|
QString m_enclosureVersion;
|
||||||
|
QString m_enclosurePlatform;
|
||||||
|
unsigned long m_enclosureLength;
|
||||||
|
QString m_enclosureType;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FVAVAILABLEUPDATE_H
|
||||||
86
client/fervor/fvignoredversions.cpp
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#include "fvignoredversions.h"
|
||||||
|
#include "fvversioncomparator.h"
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// QSettings key for the latest skipped version
|
||||||
|
#define FV_IGNORED_VERSIONS_LATEST_SKIPPED_VERSION_KEY "FVLatestSkippedVersion"
|
||||||
|
|
||||||
|
|
||||||
|
FVIgnoredVersions::FVIgnoredVersions(QObject *parent) :
|
||||||
|
QObject(parent)
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FVIgnoredVersions::VersionIsIgnored(QString version)
|
||||||
|
{
|
||||||
|
// We assume that variable 'version' contains either:
|
||||||
|
// 1) The current version of the application (ignore)
|
||||||
|
// 2) The version that was skipped before and thus stored in QSettings (ignore)
|
||||||
|
// 3) A newer version (don't ignore)
|
||||||
|
// 'version' is not likely to contain an older version in any case.
|
||||||
|
|
||||||
|
if (version == FV_APP_VERSION) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef Q_WS_MAC
|
||||||
|
QSettings settings(QSettings::IniFormat,
|
||||||
|
QSettings::UserScope,
|
||||||
|
QApplication::organizationDomain(),
|
||||||
|
QApplication::applicationName());
|
||||||
|
#else
|
||||||
|
QSettings settings(QSettings::IniFormat,
|
||||||
|
QSettings::UserScope,
|
||||||
|
QApplication::organizationName(),
|
||||||
|
QApplication::applicationName());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (settings.contains(FV_IGNORED_VERSIONS_LATEST_SKIPPED_VERSION_KEY)) {
|
||||||
|
QString lastSkippedVersion = settings.value(FV_IGNORED_VERSIONS_LATEST_SKIPPED_VERSION_KEY).toString();
|
||||||
|
if (version == lastSkippedVersion) {
|
||||||
|
// Implicitly skipped version - skip
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string currentAppVersion = std::string(FV_APP_VERSION);
|
||||||
|
std::string suggestedVersion = std::string(version.toStdString());
|
||||||
|
if (FvVersionComparator::CompareVersions(currentAppVersion, suggestedVersion) == FvVersionComparator::kAscending) {
|
||||||
|
// Newer version - do not skip
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback - not skip
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FVIgnoredVersions::IgnoreVersion(QString version)
|
||||||
|
{
|
||||||
|
if (version == FV_APP_VERSION) {
|
||||||
|
// Don't ignore the current version
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef Q_WS_MAC
|
||||||
|
QSettings settings(QSettings::IniFormat,
|
||||||
|
QSettings::UserScope,
|
||||||
|
QApplication::organizationDomain(),
|
||||||
|
QApplication::applicationName());
|
||||||
|
#else
|
||||||
|
QSettings settings(QSettings::IniFormat,
|
||||||
|
QSettings::UserScope,
|
||||||
|
QApplication::organizationName(),
|
||||||
|
QApplication::applicationName());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
settings.setValue(FV_IGNORED_VERSIONS_LATEST_SKIPPED_VERSION_KEY, version);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
19
client/fervor/fvignoredversions.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef FVIGNOREDVERSIONS_H
|
||||||
|
#define FVIGNOREDVERSIONS_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class FVIgnoredVersions : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool VersionIsIgnored(QString version);
|
||||||
|
static void IgnoreVersion(QString version);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit FVIgnoredVersions(QObject *parent = 0);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FVIGNOREDVERSIONS_H
|
||||||
210
client/fervor/fvplatform.cpp
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
#include "fvplatform.h"
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
FvPlatform::FvPlatform(QObject *parent) :
|
||||||
|
QObject(parent)
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FvPlatform::CurrentlyRunningOnPlatform(QString platform)
|
||||||
|
{
|
||||||
|
platform = platform.toUpper().trimmed();
|
||||||
|
if (platform.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defined on AIX.
|
||||||
|
#ifdef Q_OS_AIX
|
||||||
|
if (platform == "Q_OS_AIX") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Q_OS_BSD4 ("Defined on Any BSD 4.4 system") intentionally skipped.
|
||||||
|
|
||||||
|
// Defined on BSD/OS.
|
||||||
|
#ifdef Q_OS_BSDI
|
||||||
|
if (platform == "Q_OS_BSDI") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on Cygwin.
|
||||||
|
#ifdef Q_OS_CYGWIN
|
||||||
|
if (platform == "Q_OS_CYGWIN") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Q_OS_DARWIN ("Defined on Darwin OS (synonym for Q_OS_MAC)") intentionally skipped.
|
||||||
|
|
||||||
|
// Defined on DG/UX.
|
||||||
|
#ifdef Q_OS_DGUX
|
||||||
|
if (platform == "Q_OS_DGUX") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on DYNIX/ptx.
|
||||||
|
#ifdef Q_OS_DYNIX
|
||||||
|
if (platform == "Q_OS_DYNIX") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on FreeBSD.
|
||||||
|
#ifdef Q_OS_FREEBSD
|
||||||
|
if (platform == "Q_OS_FREEBSD") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on HP-UX.
|
||||||
|
#ifdef Q_OS_HPUX
|
||||||
|
if (platform == "Q_OS_HPUX") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on GNU Hurd.
|
||||||
|
#ifdef Q_OS_HURD
|
||||||
|
if (platform == "Q_OS_HURD") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on SGI Irix.
|
||||||
|
#ifdef Q_OS_IRIX
|
||||||
|
if (platform == "Q_OS_IRIX") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on Linux.
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
if (platform == "Q_OS_LINUX") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on LynxOS.
|
||||||
|
#ifdef Q_OS_LYNX
|
||||||
|
if (platform == "Q_OS_LYNX") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on MAC OS (synonym for Darwin).
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
if (platform == "Q_OS_MAC") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Q_OS_MSDOS ("Defined on MS-DOS and Windows") intentionally skipped.
|
||||||
|
|
||||||
|
// Defined on NetBSD.
|
||||||
|
#ifdef Q_OS_NETBSD
|
||||||
|
if (platform == "Q_OS_NETBSD") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on OS/2.
|
||||||
|
#ifdef Q_OS_OS2
|
||||||
|
if (platform == "Q_OS_OS2") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on OpenBSD.
|
||||||
|
#ifdef Q_OS_OPENBSD
|
||||||
|
if (platform == "Q_OS_OPENBSD") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on XFree86 on OS/2 (not PM).
|
||||||
|
#ifdef Q_OS_OS2EMX
|
||||||
|
if (platform == "Q_OS_OS2EMX") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on HP Tru64 UNIX.
|
||||||
|
#ifdef Q_OS_OSF
|
||||||
|
if (platform == "Q_OS_OSF") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on QNX Neutrino.
|
||||||
|
#ifdef Q_OS_QNX
|
||||||
|
if (platform == "Q_OS_QNX") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on Reliant UNIX.
|
||||||
|
#ifdef Q_OS_RELIANT
|
||||||
|
if (platform == "Q_OS_RELIANT") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on SCO OpenServer 5.
|
||||||
|
#ifdef Q_OS_SCO
|
||||||
|
if (platform == "Q_OS_SCO") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on Sun Solaris.
|
||||||
|
#ifdef Q_OS_SOLARIS
|
||||||
|
if (platform == "Q_OS_SOLARIS") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on Symbian.
|
||||||
|
#ifdef Q_OS_SYMBIAN
|
||||||
|
if (platform == "Q_OS_SYMBIAN") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on DEC Ultrix.
|
||||||
|
#ifdef Q_OS_ULTRIX
|
||||||
|
if (platform == "Q_OS_ULTRIX") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Q_OS_UNIX ("Defined on Any UNIX BSD/SYSV system") intentionally skipped.
|
||||||
|
|
||||||
|
// Defined on UnixWare 7, Open UNIX 8.
|
||||||
|
#ifdef Q_OS_UNIXWARE
|
||||||
|
if (platform == "Q_OS_UNIXWARE") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on Windows CE (note: goes before Q_OS_WIN32)
|
||||||
|
#ifdef Q_OS_WINCE
|
||||||
|
if (platform == "Q_OS_WINCE") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Defined on all supported versions of Windows.
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
if (platform == "Q_OS_WIN32") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Fallback
|
||||||
|
return false;
|
||||||
|
}
|
||||||
18
client/fervor/fvplatform.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#ifndef FVPLATFORM_H
|
||||||
|
#define FVPLATFORM_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class FvPlatform : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool CurrentlyRunningOnPlatform(QString platform);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit FvPlatform(QObject *parent = 0);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FVPLATFORM_H
|
||||||
13
client/fervor/fvupdateconfig.h.sample
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef FVUPDATECONFIG_H
|
||||||
|
#define FVUPDATECONFIG_H
|
||||||
|
|
||||||
|
//
|
||||||
|
// Info.plist:
|
||||||
|
//
|
||||||
|
// SUEnableSystemProfiling = YES
|
||||||
|
// SUExpectsDSASignature = YES
|
||||||
|
// SUFeedURL = http://anglonas.fotonija.lt/MacOSX/updates.php
|
||||||
|
// SUPublicDSAKeyFile = aupdate_dsa_pub.pem
|
||||||
|
//
|
||||||
|
|
||||||
|
#endif // FVUPDATECONFIG_H
|
||||||
51
client/fervor/fvupdateconfirmdialog.cpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#include "fvupdateconfirmdialog.h"
|
||||||
|
#include "fvavailableupdate.h"
|
||||||
|
#include "fvupdater.h"
|
||||||
|
#include "ui_fvupdateconfirmdialog.h"
|
||||||
|
#include <QCloseEvent>
|
||||||
|
|
||||||
|
|
||||||
|
FvUpdateConfirmDialog::FvUpdateConfirmDialog(QWidget *parent) :
|
||||||
|
QDialog(parent),
|
||||||
|
m_ui(new Ui::FvUpdateConfirmDialog)
|
||||||
|
{
|
||||||
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
// Delete on close
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||||
|
|
||||||
|
// Set the "close app, then reopen" string
|
||||||
|
QString closeReopenString = m_ui->downloadThisUpdateLabel->text().arg(QString::fromUtf8(FV_APP_NAME));
|
||||||
|
m_ui->downloadThisUpdateLabel->setText(closeReopenString);
|
||||||
|
|
||||||
|
// Connect buttons
|
||||||
|
connect(m_ui->confirmButtonBox, SIGNAL(accepted()),
|
||||||
|
FvUpdater::sharedUpdater(), SLOT(UpdateInstallationConfirmed()));
|
||||||
|
connect(m_ui->confirmButtonBox, SIGNAL(rejected()),
|
||||||
|
FvUpdater::sharedUpdater(), SLOT(UpdateInstallationNotConfirmed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
FvUpdateConfirmDialog::~FvUpdateConfirmDialog()
|
||||||
|
{
|
||||||
|
delete m_ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FvUpdateConfirmDialog::UpdateWindowWithCurrentProposedUpdate()
|
||||||
|
{
|
||||||
|
FvAvailableUpdate* proposedUpdate = FvUpdater::sharedUpdater()->GetProposedUpdate();
|
||||||
|
if (! proposedUpdate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString downloadLinkString = m_ui->updateFileLinkLabel->text()
|
||||||
|
.arg(proposedUpdate->GetEnclosureUrl().toString());
|
||||||
|
m_ui->updateFileLinkLabel->setText(downloadLinkString);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdateConfirmDialog::closeEvent(QCloseEvent* event)
|
||||||
|
{
|
||||||
|
FvUpdater::sharedUpdater()->updateConfirmationDialogWasClosed();
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
27
client/fervor/fvupdateconfirmdialog.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef FVUPDATECONFIRMDIALOG_H
|
||||||
|
#define FVUPDATECONFIRMDIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class FvUpdateConfirmDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FvUpdateConfirmDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FvUpdateConfirmDialog(QWidget *parent = 0);
|
||||||
|
~FvUpdateConfirmDialog();
|
||||||
|
|
||||||
|
// Update the current update proposal from FvUpdater
|
||||||
|
bool UpdateWindowWithCurrentProposedUpdate();
|
||||||
|
|
||||||
|
void closeEvent(QCloseEvent* event);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::FvUpdateConfirmDialog* m_ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FVUPDATECONFIRMDIALOG_H
|
||||||
65
client/fervor/fvupdateconfirmdialog.ui
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>FvUpdateConfirmDialog</class>
|
||||||
|
<widget class="QDialog" name="FvUpdateConfirmDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>480</width>
|
||||||
|
<height>160</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Software Update</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="updateFileIsLocatedLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>The update file is located at:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="updateFileLinkLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string><a href="%1">%1</a></string>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::TextBrowserInteraction</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="downloadThisUpdateLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Download this update, close "%1", install it, and then reopen "%1".</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="whenYouClickOkLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>When you click "OK", this link will be opened in your browser.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="confirmButtonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
602
client/fervor/fvupdater.cpp
Normal file
@ -0,0 +1,602 @@
|
|||||||
|
#include "fvupdater.h"
|
||||||
|
#include "fvupdatewindow.h"
|
||||||
|
#include "fvupdateconfirmdialog.h"
|
||||||
|
#include "fvplatform.h"
|
||||||
|
#include "fvignoredversions.h"
|
||||||
|
#include "fvavailableupdate.h"
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QtNetwork>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#ifndef FV_APP_NAME
|
||||||
|
# error "FV_APP_NAME is undefined (must have been defined by Fervor.pri)"
|
||||||
|
#endif
|
||||||
|
#ifndef FV_APP_VERSION
|
||||||
|
# error "FV_APP_VERSION is undefined (must have been defined by Fervor.pri)"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef FV_DEBUG
|
||||||
|
// Unit tests
|
||||||
|
# include "fvversioncomparatortest.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
FvUpdater* FvUpdater::m_Instance = 0;
|
||||||
|
|
||||||
|
|
||||||
|
FvUpdater* FvUpdater::sharedUpdater()
|
||||||
|
{
|
||||||
|
static QMutex mutex;
|
||||||
|
if (! m_Instance) {
|
||||||
|
mutex.lock();
|
||||||
|
|
||||||
|
if (! m_Instance) {
|
||||||
|
m_Instance = new FvUpdater;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::drop()
|
||||||
|
{
|
||||||
|
static QMutex mutex;
|
||||||
|
mutex.lock();
|
||||||
|
delete m_Instance;
|
||||||
|
m_Instance = 0;
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
FvUpdater::FvUpdater() : QObject(0)
|
||||||
|
{
|
||||||
|
m_reply = 0;
|
||||||
|
m_updaterWindow = 0;
|
||||||
|
m_updateConfirmationDialog = 0;
|
||||||
|
m_proposedUpdate = 0;
|
||||||
|
|
||||||
|
// Translation mechanism
|
||||||
|
installTranslator();
|
||||||
|
|
||||||
|
#ifdef FV_DEBUG
|
||||||
|
// Unit tests
|
||||||
|
FvVersionComparatorTest* test = new FvVersionComparatorTest();
|
||||||
|
test->runAll();
|
||||||
|
delete test;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
FvUpdater::~FvUpdater()
|
||||||
|
{
|
||||||
|
if (m_proposedUpdate) {
|
||||||
|
delete m_proposedUpdate;
|
||||||
|
m_proposedUpdate = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideUpdateConfirmationDialog();
|
||||||
|
hideUpdaterWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::installTranslator()
|
||||||
|
{
|
||||||
|
QTranslator translator;
|
||||||
|
QString locale = QLocale::system().name();
|
||||||
|
translator.load(QString("fervor_") + locale);
|
||||||
|
//QTextCodec::setCodecForTr(QTextCodec::codecForName("utf8"));
|
||||||
|
qApp->installTranslator(&translator);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::showUpdaterWindowUpdatedWithCurrentUpdateProposal()
|
||||||
|
{
|
||||||
|
// Destroy window if already exists
|
||||||
|
hideUpdaterWindow();
|
||||||
|
|
||||||
|
// Create a new window
|
||||||
|
m_updaterWindow = new FvUpdateWindow();
|
||||||
|
m_updaterWindow->UpdateWindowWithCurrentProposedUpdate();
|
||||||
|
m_updaterWindow->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::hideUpdaterWindow()
|
||||||
|
{
|
||||||
|
if (m_updaterWindow) {
|
||||||
|
if (! m_updaterWindow->close()) {
|
||||||
|
qWarning() << "Update window didn't close, leaking memory from now on";
|
||||||
|
}
|
||||||
|
|
||||||
|
// not deleting because of Qt::WA_DeleteOnClose
|
||||||
|
|
||||||
|
m_updaterWindow = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::updaterWindowWasClosed()
|
||||||
|
{
|
||||||
|
// (Re-)nullify a pointer to a destroyed QWidget or you're going to have a bad time.
|
||||||
|
m_updaterWindow = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FvUpdater::showUpdateConfirmationDialogUpdatedWithCurrentUpdateProposal()
|
||||||
|
{
|
||||||
|
// Destroy dialog if already exists
|
||||||
|
hideUpdateConfirmationDialog();
|
||||||
|
|
||||||
|
// Create a new window
|
||||||
|
m_updateConfirmationDialog = new FvUpdateConfirmDialog();
|
||||||
|
m_updateConfirmationDialog->UpdateWindowWithCurrentProposedUpdate();
|
||||||
|
m_updateConfirmationDialog->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::hideUpdateConfirmationDialog()
|
||||||
|
{
|
||||||
|
if (m_updateConfirmationDialog) {
|
||||||
|
if (! m_updateConfirmationDialog->close()) {
|
||||||
|
qWarning() << "Update confirmation dialog didn't close, leaking memory from now on";
|
||||||
|
}
|
||||||
|
|
||||||
|
// not deleting because of Qt::WA_DeleteOnClose
|
||||||
|
|
||||||
|
m_updateConfirmationDialog = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::updateConfirmationDialogWasClosed()
|
||||||
|
{
|
||||||
|
// (Re-)nullify a pointer to a destroyed QWidget or you're going to have a bad time.
|
||||||
|
m_updateConfirmationDialog = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FvUpdater::SetFeedURL(QUrl feedURL)
|
||||||
|
{
|
||||||
|
m_feedURL = feedURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::SetFeedURL(QString feedURL)
|
||||||
|
{
|
||||||
|
SetFeedURL(QUrl(feedURL));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString FvUpdater::GetFeedURL()
|
||||||
|
{
|
||||||
|
return m_feedURL.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
FvAvailableUpdate* FvUpdater::GetProposedUpdate()
|
||||||
|
{
|
||||||
|
return m_proposedUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FvUpdater::InstallUpdate()
|
||||||
|
{
|
||||||
|
qDebug() << "Install update";
|
||||||
|
|
||||||
|
showUpdateConfirmationDialogUpdatedWithCurrentUpdateProposal();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::SkipUpdate()
|
||||||
|
{
|
||||||
|
qDebug() << "Skip update";
|
||||||
|
|
||||||
|
FvAvailableUpdate* proposedUpdate = GetProposedUpdate();
|
||||||
|
if (! proposedUpdate) {
|
||||||
|
qWarning() << "Proposed update is NULL (shouldn't be at this point)";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start ignoring this particular version
|
||||||
|
FVIgnoredVersions::IgnoreVersion(proposedUpdate->GetEnclosureVersion());
|
||||||
|
|
||||||
|
hideUpdaterWindow();
|
||||||
|
hideUpdateConfirmationDialog(); // if any; shouldn't be shown at this point, but who knows
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::RemindMeLater()
|
||||||
|
{
|
||||||
|
qDebug() << "Remind me later";
|
||||||
|
|
||||||
|
hideUpdaterWindow();
|
||||||
|
hideUpdateConfirmationDialog(); // if any; shouldn't be shown at this point, but who knows
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::UpdateInstallationConfirmed()
|
||||||
|
{
|
||||||
|
qDebug() << "Confirm update installation";
|
||||||
|
|
||||||
|
FvAvailableUpdate* proposedUpdate = GetProposedUpdate();
|
||||||
|
if (! proposedUpdate) {
|
||||||
|
qWarning() << "Proposed update is NULL (shouldn't be at this point)";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a link
|
||||||
|
if (! QDesktopServices::openUrl(proposedUpdate->GetEnclosureUrl())) {
|
||||||
|
showErrorDialog(tr("Unable to open this link in a browser. Please do it manually."), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideUpdaterWindow();
|
||||||
|
hideUpdateConfirmationDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::UpdateInstallationNotConfirmed()
|
||||||
|
{
|
||||||
|
qDebug() << "Do not confirm update installation";
|
||||||
|
|
||||||
|
hideUpdateConfirmationDialog(); // if any; shouldn't be shown at this point, but who knows
|
||||||
|
// leave the "update proposal window" inact
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool FvUpdater::CheckForUpdates(bool silentAsMuchAsItCouldGet)
|
||||||
|
{
|
||||||
|
if (m_feedURL.isEmpty()) {
|
||||||
|
qCritical() << "Please set feed URL via setFeedURL() before calling CheckForUpdates().";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_silentAsMuchAsItCouldGet = silentAsMuchAsItCouldGet;
|
||||||
|
|
||||||
|
// Check if application's organization name and domain are set, fail otherwise
|
||||||
|
// (nowhere to store QSettings to)
|
||||||
|
/*
|
||||||
|
if (QApplication::organizationName().isEmpty()) {
|
||||||
|
qCritical() << "QApplication::organizationName is not set. Please do that.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (QApplication::organizationDomain().isEmpty()) {
|
||||||
|
qCritical() << "QApplication::organizationDomain is not set. Please do that.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// Set application name / version is not set yet
|
||||||
|
if (QApplication::applicationName().isEmpty()) {
|
||||||
|
QString appName = QString::fromUtf8(FV_APP_NAME);
|
||||||
|
qWarning() << "QApplication::applicationName is not set, setting it to '" << appName << "'";
|
||||||
|
QApplication::setApplicationName(appName);
|
||||||
|
}
|
||||||
|
if (QApplication::applicationVersion().isEmpty()) {
|
||||||
|
QString appVersion = QString::fromUtf8(FV_APP_VERSION);
|
||||||
|
qWarning() << "QApplication::applicationVersion is not set, setting it to '" << appVersion << "'";
|
||||||
|
QApplication::setApplicationVersion(appVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelDownloadFeed();
|
||||||
|
m_httpRequestAborted = false;
|
||||||
|
startDownloadFeed(m_feedURL);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FvUpdater::CheckForUpdatesSilent()
|
||||||
|
{
|
||||||
|
return CheckForUpdates(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FvUpdater::CheckForUpdatesNotSilent()
|
||||||
|
{
|
||||||
|
return CheckForUpdates(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FvUpdater::startDownloadFeed(QUrl url)
|
||||||
|
{
|
||||||
|
m_xml.clear();
|
||||||
|
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml");
|
||||||
|
request.setHeader(QNetworkRequest::UserAgentHeader, QApplication::applicationName());
|
||||||
|
request.setUrl(url);
|
||||||
|
|
||||||
|
m_reply = m_qnam.get(request);
|
||||||
|
|
||||||
|
connect(m_reply, SIGNAL(readyRead()), this, SLOT(httpFeedReadyRead()));
|
||||||
|
connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(httpFeedUpdateDataReadProgress(qint64, qint64)));
|
||||||
|
connect(m_reply, SIGNAL(finished()), this, SLOT(httpFeedDownloadFinished()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::cancelDownloadFeed()
|
||||||
|
{
|
||||||
|
if (m_reply) {
|
||||||
|
m_httpRequestAborted = true;
|
||||||
|
m_reply->abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::httpFeedReadyRead()
|
||||||
|
{
|
||||||
|
// this slot gets called every time the QNetworkReply has new data.
|
||||||
|
// We read all of its new data and write it into the file.
|
||||||
|
// That way we use less RAM than when reading it at the finished()
|
||||||
|
// signal of the QNetworkReply
|
||||||
|
m_xml.addData(m_reply->readAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::httpFeedUpdateDataReadProgress(qint64 bytesRead,
|
||||||
|
qint64 totalBytes)
|
||||||
|
{
|
||||||
|
Q_UNUSED(bytesRead);
|
||||||
|
Q_UNUSED(totalBytes);
|
||||||
|
|
||||||
|
if (m_httpRequestAborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::httpFeedDownloadFinished()
|
||||||
|
{
|
||||||
|
if (m_httpRequestAborted) {
|
||||||
|
m_reply->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant redirectionTarget = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||||
|
if (m_reply->error()) {
|
||||||
|
|
||||||
|
// Error.
|
||||||
|
showErrorDialog(tr("Feed download failed: %1.").arg(m_reply->errorString()), false);
|
||||||
|
|
||||||
|
} else if (! redirectionTarget.isNull()) {
|
||||||
|
QUrl newUrl = m_feedURL.resolved(redirectionTarget.toUrl());
|
||||||
|
|
||||||
|
m_feedURL = newUrl;
|
||||||
|
m_reply->deleteLater();
|
||||||
|
|
||||||
|
startDownloadFeed(m_feedURL);
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Done.
|
||||||
|
xmlParseFeed();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
m_reply->deleteLater();
|
||||||
|
m_reply = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FvUpdater::xmlParseFeed()
|
||||||
|
{
|
||||||
|
QString currentTag, currentQualifiedTag;
|
||||||
|
|
||||||
|
QString xmlTitle, xmlLink, xmlReleaseNotesLink, xmlPubDate, xmlEnclosureUrl,
|
||||||
|
xmlEnclosureVersion, xmlEnclosurePlatform, xmlEnclosureType;
|
||||||
|
unsigned long xmlEnclosureLength = 0;
|
||||||
|
|
||||||
|
// Parse
|
||||||
|
while (! m_xml.atEnd()) {
|
||||||
|
|
||||||
|
m_xml.readNext();
|
||||||
|
|
||||||
|
if (m_xml.isStartElement()) {
|
||||||
|
|
||||||
|
currentTag = m_xml.name().toString();
|
||||||
|
currentQualifiedTag = m_xml.qualifiedName().toString();
|
||||||
|
|
||||||
|
if (m_xml.name() == "item") {
|
||||||
|
|
||||||
|
xmlTitle.clear();
|
||||||
|
xmlLink.clear();
|
||||||
|
xmlReleaseNotesLink.clear();
|
||||||
|
xmlPubDate.clear();
|
||||||
|
xmlEnclosureUrl.clear();
|
||||||
|
xmlEnclosureVersion.clear();
|
||||||
|
xmlEnclosurePlatform.clear();
|
||||||
|
xmlEnclosureLength = 0;
|
||||||
|
xmlEnclosureType.clear();
|
||||||
|
|
||||||
|
} else if (m_xml.name() == "enclosure") {
|
||||||
|
|
||||||
|
QXmlStreamAttributes attribs = m_xml.attributes();
|
||||||
|
|
||||||
|
if (attribs.hasAttribute("fervor:platform")) {
|
||||||
|
|
||||||
|
if (FvPlatform::CurrentlyRunningOnPlatform(attribs.value("fervor:platform").toString().trimmed())) {
|
||||||
|
|
||||||
|
xmlEnclosurePlatform = attribs.value("fervor:platform").toString().trimmed();
|
||||||
|
|
||||||
|
if (attribs.hasAttribute("url")) {
|
||||||
|
xmlEnclosureUrl = attribs.value("url").toString().trimmed();
|
||||||
|
} else {
|
||||||
|
xmlEnclosureUrl = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check for Sparkle's version, then overwrite with Fervor's version (if any)
|
||||||
|
if (attribs.hasAttribute("sparkle:version")) {
|
||||||
|
QString candidateVersion = attribs.value("sparkle:version").toString().trimmed();
|
||||||
|
if (! candidateVersion.isEmpty()) {
|
||||||
|
xmlEnclosureVersion = candidateVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attribs.hasAttribute("fervor:version")) {
|
||||||
|
QString candidateVersion = attribs.value("fervor:version").toString().trimmed();
|
||||||
|
if (! candidateVersion.isEmpty()) {
|
||||||
|
xmlEnclosureVersion = candidateVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribs.hasAttribute("length")) {
|
||||||
|
xmlEnclosureLength = attribs.value("length").toString().toLong();
|
||||||
|
} else {
|
||||||
|
xmlEnclosureLength = 0;
|
||||||
|
}
|
||||||
|
if (attribs.hasAttribute("type")) {
|
||||||
|
xmlEnclosureType = attribs.value("type").toString().trimmed();
|
||||||
|
} else {
|
||||||
|
xmlEnclosureType = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (m_xml.isEndElement()) {
|
||||||
|
|
||||||
|
if (m_xml.name() == "item") {
|
||||||
|
|
||||||
|
// That's it - we have analyzed a single <item> and we'll stop
|
||||||
|
// here (because the topmost is the most recent one, and thus
|
||||||
|
// the newest version.
|
||||||
|
|
||||||
|
return searchDownloadedFeedForUpdates(xmlTitle,
|
||||||
|
xmlLink,
|
||||||
|
xmlReleaseNotesLink,
|
||||||
|
xmlPubDate,
|
||||||
|
xmlEnclosureUrl,
|
||||||
|
xmlEnclosureVersion,
|
||||||
|
xmlEnclosurePlatform,
|
||||||
|
xmlEnclosureLength,
|
||||||
|
xmlEnclosureType);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (m_xml.isCharacters() && ! m_xml.isWhitespace()) {
|
||||||
|
|
||||||
|
if (currentTag == "title") {
|
||||||
|
xmlTitle += m_xml.text().toString().trimmed();
|
||||||
|
|
||||||
|
} else if (currentTag == "link") {
|
||||||
|
xmlLink += m_xml.text().toString().trimmed();
|
||||||
|
|
||||||
|
} else if (currentQualifiedTag == "sparkle:releaseNotesLink") {
|
||||||
|
xmlReleaseNotesLink += m_xml.text().toString().trimmed();
|
||||||
|
|
||||||
|
} else if (currentTag == "pubDate") {
|
||||||
|
xmlPubDate += m_xml.text().toString().trimmed();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_xml.error() && m_xml.error() != QXmlStreamReader::PrematureEndOfDocumentError) {
|
||||||
|
|
||||||
|
showErrorDialog(tr("Feed parsing failed: %1 %2.").arg(QString::number(m_xml.lineNumber()), m_xml.errorString()), false);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No updates were found if we're at this point
|
||||||
|
// (not a single <item> element found)
|
||||||
|
showInformationDialog(tr("No updates were found."), false);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool FvUpdater::searchDownloadedFeedForUpdates(QString xmlTitle,
|
||||||
|
QString xmlLink,
|
||||||
|
QString xmlReleaseNotesLink,
|
||||||
|
QString xmlPubDate,
|
||||||
|
QString xmlEnclosureUrl,
|
||||||
|
QString xmlEnclosureVersion,
|
||||||
|
QString xmlEnclosurePlatform,
|
||||||
|
unsigned long xmlEnclosureLength,
|
||||||
|
QString xmlEnclosureType)
|
||||||
|
{
|
||||||
|
qDebug() << "Title:" << xmlTitle;
|
||||||
|
qDebug() << "Link:" << xmlLink;
|
||||||
|
qDebug() << "Release notes link:" << xmlReleaseNotesLink;
|
||||||
|
qDebug() << "Pub. date:" << xmlPubDate;
|
||||||
|
qDebug() << "Enclosure URL:" << xmlEnclosureUrl;
|
||||||
|
qDebug() << "Enclosure version:" << xmlEnclosureVersion;
|
||||||
|
qDebug() << "Enclosure platform:" << xmlEnclosurePlatform;
|
||||||
|
qDebug() << "Enclosure length:" << xmlEnclosureLength;
|
||||||
|
qDebug() << "Enclosure type:" << xmlEnclosureType;
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
if (xmlReleaseNotesLink.isEmpty()) {
|
||||||
|
if (xmlLink.isEmpty()) {
|
||||||
|
showErrorDialog(tr("Feed error: \"release notes\" link is empty"), false);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
xmlReleaseNotesLink = xmlLink;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
xmlLink = xmlReleaseNotesLink;
|
||||||
|
}
|
||||||
|
if (! (xmlLink.startsWith("http://") || xmlLink.startsWith("https://"))) {
|
||||||
|
showErrorDialog(tr("Feed error: invalid \"release notes\" link"), false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (xmlEnclosureUrl.isEmpty() || xmlEnclosureVersion.isEmpty() || xmlEnclosurePlatform.isEmpty()) {
|
||||||
|
showErrorDialog(tr("Feed error: invalid \"enclosure\" with the download link"), false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relevant version?
|
||||||
|
if (FVIgnoredVersions::VersionIsIgnored(xmlEnclosureVersion)) {
|
||||||
|
qDebug() << "Version '" << xmlEnclosureVersion << "' is ignored, too old or something like that.";
|
||||||
|
|
||||||
|
showInformationDialog(tr("No updates were found."), false);
|
||||||
|
|
||||||
|
return true; // Things have succeeded when you think of it.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Success! At this point, we have found an update that can be proposed
|
||||||
|
// to the user.
|
||||||
|
//
|
||||||
|
|
||||||
|
if (m_proposedUpdate) {
|
||||||
|
delete m_proposedUpdate; m_proposedUpdate = 0;
|
||||||
|
}
|
||||||
|
m_proposedUpdate = new FvAvailableUpdate();
|
||||||
|
m_proposedUpdate->SetTitle(xmlTitle);
|
||||||
|
m_proposedUpdate->SetReleaseNotesLink(xmlReleaseNotesLink);
|
||||||
|
m_proposedUpdate->SetPubDate(xmlPubDate);
|
||||||
|
m_proposedUpdate->SetEnclosureUrl(xmlEnclosureUrl);
|
||||||
|
m_proposedUpdate->SetEnclosureVersion(xmlEnclosureVersion);
|
||||||
|
m_proposedUpdate->SetEnclosurePlatform(xmlEnclosurePlatform);
|
||||||
|
m_proposedUpdate->SetEnclosureLength(xmlEnclosureLength);
|
||||||
|
m_proposedUpdate->SetEnclosureType(xmlEnclosureType);
|
||||||
|
|
||||||
|
// Show "look, there's an update" window
|
||||||
|
showUpdaterWindowUpdatedWithCurrentUpdateProposal();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FvUpdater::showErrorDialog(QString message, bool showEvenInSilentMode)
|
||||||
|
{
|
||||||
|
if (m_silentAsMuchAsItCouldGet) {
|
||||||
|
if (! showEvenInSilentMode) {
|
||||||
|
// Don't show errors in the silent mode
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox dlFailedMsgBox;
|
||||||
|
dlFailedMsgBox.setIcon(QMessageBox::Critical);
|
||||||
|
dlFailedMsgBox.setText(tr("Error"));
|
||||||
|
dlFailedMsgBox.setInformativeText(message);
|
||||||
|
dlFailedMsgBox.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdater::showInformationDialog(QString message, bool showEvenInSilentMode)
|
||||||
|
{
|
||||||
|
if (m_silentAsMuchAsItCouldGet) {
|
||||||
|
if (! showEvenInSilentMode) {
|
||||||
|
// Don't show information dialogs in the silent mode
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox dlInformationMsgBox;
|
||||||
|
dlInformationMsgBox.setIcon(QMessageBox::Information);
|
||||||
|
dlInformationMsgBox.setText(tr("Information"));
|
||||||
|
dlInformationMsgBox.setInformativeText(message);
|
||||||
|
dlInformationMsgBox.exec();
|
||||||
|
}
|
||||||
150
client/fervor/fvupdater.h
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
#ifndef FVUPDATER_H
|
||||||
|
#define FVUPDATER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
class FvUpdateWindow;
|
||||||
|
class FvUpdateConfirmDialog;
|
||||||
|
class FvAvailableUpdate;
|
||||||
|
|
||||||
|
|
||||||
|
class FvUpdater : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Singleton
|
||||||
|
static FvUpdater* sharedUpdater();
|
||||||
|
static void drop();
|
||||||
|
|
||||||
|
// Set / get feed URL
|
||||||
|
void SetFeedURL(QUrl feedURL);
|
||||||
|
void SetFeedURL(QString feedURL);
|
||||||
|
QString GetFeedURL();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
// Check for updates
|
||||||
|
bool CheckForUpdates(bool silentAsMuchAsItCouldGet = true);
|
||||||
|
|
||||||
|
// Aliases
|
||||||
|
bool CheckForUpdatesSilent();
|
||||||
|
bool CheckForUpdatesNotSilent();
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// ---------------------------------------------------
|
||||||
|
// ---------------------------------------------------
|
||||||
|
// ---------------------------------------------------
|
||||||
|
// ---------------------------------------------------
|
||||||
|
//
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
friend class FvUpdateWindow; // Uses GetProposedUpdate() and others
|
||||||
|
friend class FvUpdateConfirmDialog; // Uses GetProposedUpdate() and others
|
||||||
|
FvAvailableUpdate* GetProposedUpdate();
|
||||||
|
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
|
||||||
|
// Update window button slots
|
||||||
|
void InstallUpdate();
|
||||||
|
void SkipUpdate();
|
||||||
|
void RemindMeLater();
|
||||||
|
|
||||||
|
// Update confirmation dialog button slots
|
||||||
|
void UpdateInstallationConfirmed();
|
||||||
|
void UpdateInstallationNotConfirmed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
//
|
||||||
|
// Singleton business
|
||||||
|
//
|
||||||
|
// (we leave just the declarations, so the compiler will warn us if we try
|
||||||
|
// to use those two functions by accident)
|
||||||
|
FvUpdater(); // Hide main constructor
|
||||||
|
~FvUpdater(); // Hide main destructor
|
||||||
|
FvUpdater(const FvUpdater&); // Hide copy constructor
|
||||||
|
FvUpdater& operator=(const FvUpdater&); // Hide assign op
|
||||||
|
|
||||||
|
static FvUpdater* m_Instance; // Singleton instance
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Windows / dialogs
|
||||||
|
//
|
||||||
|
FvUpdateWindow* m_updaterWindow; // Updater window (NULL if not shown)
|
||||||
|
void showUpdaterWindowUpdatedWithCurrentUpdateProposal(); // Show updater window
|
||||||
|
void hideUpdaterWindow(); // Hide + destroy m_updaterWindow
|
||||||
|
void updaterWindowWasClosed(); // Sent by the updater window when it gets closed
|
||||||
|
|
||||||
|
FvUpdateConfirmDialog* m_updateConfirmationDialog; // Update confirmation dialog (NULL if not shown)
|
||||||
|
void showUpdateConfirmationDialogUpdatedWithCurrentUpdateProposal(); // Show update confirmation dialog
|
||||||
|
void hideUpdateConfirmationDialog(); // Hide + destroy m_updateConfirmationDialog
|
||||||
|
void updateConfirmationDialogWasClosed(); // Sent by the update confirmation dialog when it gets closed
|
||||||
|
|
||||||
|
// Available update (NULL if not fetched)
|
||||||
|
FvAvailableUpdate* m_proposedUpdate;
|
||||||
|
|
||||||
|
// If true, don't show the error dialogs and the "no updates." dialog
|
||||||
|
// (silentAsMuchAsItCouldGet from CheckForUpdates() goes here)
|
||||||
|
// Useful for automatic update checking upon application startup.
|
||||||
|
bool m_silentAsMuchAsItCouldGet;
|
||||||
|
|
||||||
|
// Dialogs (notifications)
|
||||||
|
void showErrorDialog(QString message, bool showEvenInSilentMode = false); // Show an error message
|
||||||
|
void showInformationDialog(QString message, bool showEvenInSilentMode = false); // Show an informational message
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// HTTP feed fetcher infrastructure
|
||||||
|
//
|
||||||
|
QUrl m_feedURL; // Feed URL that will be fetched
|
||||||
|
QNetworkAccessManager m_qnam;
|
||||||
|
QNetworkReply* m_reply;
|
||||||
|
int m_httpGetId;
|
||||||
|
bool m_httpRequestAborted;
|
||||||
|
|
||||||
|
void startDownloadFeed(QUrl url); // Start downloading feed
|
||||||
|
void cancelDownloadFeed(); // Stop downloading the current feed
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
|
||||||
|
void httpFeedReadyRead();
|
||||||
|
void httpFeedUpdateDataReadProgress(qint64 bytesRead,
|
||||||
|
qint64 totalBytes);
|
||||||
|
void httpFeedDownloadFinished();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
//
|
||||||
|
// XML parser
|
||||||
|
//
|
||||||
|
QXmlStreamReader m_xml; // XML data collector and parser
|
||||||
|
bool xmlParseFeed(); // Parse feed in m_xml
|
||||||
|
bool searchDownloadedFeedForUpdates(QString xmlTitle,
|
||||||
|
QString xmlLink,
|
||||||
|
QString xmlReleaseNotesLink,
|
||||||
|
QString xmlPubDate,
|
||||||
|
QString xmlEnclosureUrl,
|
||||||
|
QString xmlEnclosureVersion,
|
||||||
|
QString xmlEnclosurePlatform,
|
||||||
|
unsigned long xmlEnclosureLength,
|
||||||
|
QString xmlEnclosureType);
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Helpers
|
||||||
|
//
|
||||||
|
void installTranslator(); // Initialize translation mechanism
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FVUPDATER_H
|
||||||
73
client/fervor/fvupdatewindow.cpp
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#include "fvupdatewindow.h"
|
||||||
|
#include "ui_fvupdatewindow.h"
|
||||||
|
#include "fvupdater.h"
|
||||||
|
#include "fvavailableupdate.h"
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QCloseEvent>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
FvUpdateWindow::FvUpdateWindow(QWidget *parent) :
|
||||||
|
QWidget(parent),
|
||||||
|
m_ui(new Ui::FvUpdateWindow), m_NetworkReply(nullptr)
|
||||||
|
{
|
||||||
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
// Delete on close
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||||
|
|
||||||
|
// Set the "new version is available" string
|
||||||
|
QString newVersString = m_ui->newVersionIsAvailableLabel->text().arg(QString::fromUtf8(FV_APP_NAME));
|
||||||
|
m_ui->newVersionIsAvailableLabel->setText(newVersString);
|
||||||
|
|
||||||
|
// Connect buttons
|
||||||
|
connect(m_ui->installUpdateButton, SIGNAL(clicked()),
|
||||||
|
FvUpdater::sharedUpdater(), SLOT(InstallUpdate()));
|
||||||
|
connect(m_ui->skipThisVersionButton, SIGNAL(clicked()),
|
||||||
|
FvUpdater::sharedUpdater(), SLOT(SkipUpdate()));
|
||||||
|
connect(m_ui->remindMeLaterButton, SIGNAL(clicked()),
|
||||||
|
FvUpdater::sharedUpdater(), SLOT(RemindMeLater()));
|
||||||
|
|
||||||
|
connect(&m_Network, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));
|
||||||
|
}
|
||||||
|
|
||||||
|
FvUpdateWindow::~FvUpdateWindow()
|
||||||
|
{
|
||||||
|
delete m_ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FvUpdateWindow::UpdateWindowWithCurrentProposedUpdate()
|
||||||
|
{
|
||||||
|
FvAvailableUpdate* proposedUpdate = FvUpdater::sharedUpdater()->GetProposedUpdate();
|
||||||
|
if (! proposedUpdate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString downloadString = m_ui->wouldYouLikeToDownloadLabel->text()
|
||||||
|
.arg(QString::fromUtf8(FV_APP_NAME), proposedUpdate->GetEnclosureVersion(), QString::fromUtf8(FV_APP_VERSION));
|
||||||
|
m_ui->wouldYouLikeToDownloadLabel->setText(downloadString);
|
||||||
|
|
||||||
|
QUrl notesUrl = proposedUpdate->GetReleaseNotesLink();
|
||||||
|
m_NetworkReply = m_Network.get(QNetworkRequest(notesUrl));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdateWindow::closeEvent(QCloseEvent* event)
|
||||||
|
{
|
||||||
|
FvUpdater::sharedUpdater()->updaterWindowWasClosed();
|
||||||
|
if (m_NetworkReply)
|
||||||
|
m_NetworkReply->abort();
|
||||||
|
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvUpdateWindow::downloadFinished(QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
if (reply->error() == QNetworkReply::NoError)
|
||||||
|
m_ui->releaseNotes->setText(QString(reply->readAll()));
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
m_NetworkReply = nullptr;
|
||||||
|
}
|
||||||
35
client/fervor/fvupdatewindow.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#ifndef FVUPDATEWINDOW_H
|
||||||
|
#define FVUPDATEWINDOW_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
|
||||||
|
class QGraphicsScene;
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class FvUpdateWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FvUpdateWindow : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FvUpdateWindow(QWidget *parent = 0);
|
||||||
|
~FvUpdateWindow();
|
||||||
|
|
||||||
|
// Update the current update proposal from FvUpdater
|
||||||
|
bool UpdateWindowWithCurrentProposedUpdate();
|
||||||
|
|
||||||
|
void closeEvent(QCloseEvent* event);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::FvUpdateWindow* m_ui;
|
||||||
|
QNetworkAccessManager m_Network;
|
||||||
|
QNetworkReply* m_NetworkReply;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void downloadFinished(QNetworkReply* reply);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FVUPDATEWINDOW_H
|
||||||
107
client/fervor/fvupdatewindow.ui
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>FvUpdateWindow</class>
|
||||||
|
<widget class="QWidget" name="FvUpdateWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>640</width>
|
||||||
|
<height>480</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Software Update</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="newVersionIsAvailableLabel">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>A new version of %1 is available!</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="wouldYouLikeToDownloadLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>%1 %2 is now available - you have %3. Would you like to download it now?</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Release Notes:</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
|
<item>
|
||||||
|
<widget class="QTextBrowser" name="releaseNotes"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="skipThisVersionButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Skip This Version</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="remindMeLaterButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Remind Me Later</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="installUpdateButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Install Update</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="default">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
165
client/fervor/fvversioncomparator.cpp
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
#include "fvversioncomparator.h"
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <stdlib.h> // for atoi()
|
||||||
|
|
||||||
|
//
|
||||||
|
// Clone of Sparkle's SUStandardVersionComparator.m, so here's original author's
|
||||||
|
// copyright too:
|
||||||
|
//
|
||||||
|
// Copyright 2007 Andy Matuschak. All rights reserved.
|
||||||
|
//
|
||||||
|
// Everything's the same except for TypeOfCharacter()
|
||||||
|
// (because who knows how Foundation does isdigit() and such.)
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
FvVersionComparator::FvVersionComparator()
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
FvVersionComparator::CharacterType FvVersionComparator::TypeOfCharacter(std::string character)
|
||||||
|
{
|
||||||
|
if (character == ".") {
|
||||||
|
return kSeparatorType;
|
||||||
|
} else if (isdigit(character[0])) {
|
||||||
|
return kNumberType;
|
||||||
|
} else if (isspace(character[0])) {
|
||||||
|
return kSeparatorType;
|
||||||
|
} else if (ispunct(character[0])) {
|
||||||
|
return kSeparatorType;
|
||||||
|
} else {
|
||||||
|
return kStringType;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> FvVersionComparator::SplitVersionString(std::string version)
|
||||||
|
{
|
||||||
|
std::string character;
|
||||||
|
std::string s;
|
||||||
|
unsigned long i = 0, n = 0;
|
||||||
|
CharacterType oldType, newType;
|
||||||
|
std::vector<std::string> parts;
|
||||||
|
|
||||||
|
if (version.length() == 0) {
|
||||||
|
// Nothing to do here
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = version.substr(0, 1);
|
||||||
|
oldType = TypeOfCharacter(s);
|
||||||
|
n = version.length() - 1;
|
||||||
|
for (i = 1; i <= n; ++i) {
|
||||||
|
character = version.substr(i, 1)[0];
|
||||||
|
newType = TypeOfCharacter(character);
|
||||||
|
if (oldType != newType || oldType == kSeparatorType) {
|
||||||
|
// We've reached a new segment
|
||||||
|
std::string aPart = s;
|
||||||
|
parts.push_back(aPart);
|
||||||
|
s = character;
|
||||||
|
} else {
|
||||||
|
// Add character to string and continue
|
||||||
|
s.append(character);
|
||||||
|
}
|
||||||
|
oldType = newType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the last part onto the array
|
||||||
|
parts.push_back(s);
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FvVersionComparator::ComparatorResult FvVersionComparator::CompareVersions(std::string versionA,
|
||||||
|
std::string versionB)
|
||||||
|
{
|
||||||
|
std::vector<std::string> partsA = SplitVersionString(versionA);
|
||||||
|
std::vector<std::string> partsB = SplitVersionString(versionB);
|
||||||
|
|
||||||
|
std::string partA = std::string(""), partB = std::string("");
|
||||||
|
unsigned long i = 0, n = 0;
|
||||||
|
int intA, intB;
|
||||||
|
CharacterType typeA, typeB;
|
||||||
|
|
||||||
|
n = std::min(partsA.size(), partsB.size());
|
||||||
|
for (i = 0; i < n; ++i) {
|
||||||
|
partA = partsA.at(i);
|
||||||
|
partB = partsB.at(i);
|
||||||
|
|
||||||
|
typeA = TypeOfCharacter(partA);
|
||||||
|
typeB = TypeOfCharacter(partB);
|
||||||
|
|
||||||
|
// Compare types
|
||||||
|
if (typeA == typeB) {
|
||||||
|
// Same type; we can compare
|
||||||
|
if (typeA == kNumberType) {
|
||||||
|
intA = atoi(partA.c_str());
|
||||||
|
intB = atoi(partB.c_str());
|
||||||
|
|
||||||
|
if (intA > intB) {
|
||||||
|
return kDescending;
|
||||||
|
} else if (intA < intB) {
|
||||||
|
return kAscending;
|
||||||
|
}
|
||||||
|
} else if (typeA == kStringType) {
|
||||||
|
short result = partA.compare(partB);
|
||||||
|
switch (result) {
|
||||||
|
case -1: return kAscending; break;
|
||||||
|
case 1: return kDescending; break;
|
||||||
|
case 0: /* do nothing */ break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not the same type? Now we have to do some validity checking
|
||||||
|
if (typeA != kStringType && typeB == kStringType) {
|
||||||
|
// typeA wins
|
||||||
|
return kDescending;
|
||||||
|
} else if (typeA == kStringType && typeB != kStringType) {
|
||||||
|
// typeB wins
|
||||||
|
return kAscending;
|
||||||
|
} else {
|
||||||
|
// One is a number and the other is a period. The period is invalid
|
||||||
|
if (typeA == kNumberType) {
|
||||||
|
return kDescending;
|
||||||
|
} else {
|
||||||
|
return kAscending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The versions are equal up to the point where they both still have parts
|
||||||
|
// Lets check to see if one is larger than the other
|
||||||
|
if (partsA.size() != partsB.size()) {
|
||||||
|
// Yep. Lets get the next part of the larger
|
||||||
|
// n holds the index of the part we want.
|
||||||
|
std::string missingPart = std::string("");
|
||||||
|
CharacterType missingType;
|
||||||
|
ComparatorResult shorterResult, largerResult;
|
||||||
|
|
||||||
|
if (partsA.size() > partsB.size()) {
|
||||||
|
missingPart = partsA.at(n);
|
||||||
|
shorterResult = kAscending;
|
||||||
|
largerResult = kDescending;
|
||||||
|
} else {
|
||||||
|
missingPart = partsB.at(n);
|
||||||
|
shorterResult = kDescending;
|
||||||
|
largerResult = kAscending;
|
||||||
|
}
|
||||||
|
|
||||||
|
missingType = TypeOfCharacter(missingPart);
|
||||||
|
// Check the type
|
||||||
|
if (missingType == kStringType) {
|
||||||
|
// It's a string. Shorter version wins
|
||||||
|
return shorterResult;
|
||||||
|
} else {
|
||||||
|
// It's a number/period. Larger version wins
|
||||||
|
return largerResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 2 strings are identical
|
||||||
|
return kSame;
|
||||||
|
}
|
||||||
36
client/fervor/fvversioncomparator.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#ifndef FVVERSIONCOMPARATOR_H
|
||||||
|
#define FVVERSIONCOMPARATOR_H
|
||||||
|
|
||||||
|
#include <iosfwd>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
class FvVersionComparator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
kSame = 0,
|
||||||
|
kDescending = 1,
|
||||||
|
kAscending = -1
|
||||||
|
} ComparatorResult;
|
||||||
|
|
||||||
|
static ComparatorResult CompareVersions(std::string versionA,
|
||||||
|
std::string versionB);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
FvVersionComparator();
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
kNumberType,
|
||||||
|
kStringType,
|
||||||
|
kSeparatorType
|
||||||
|
} CharacterType;
|
||||||
|
|
||||||
|
static CharacterType TypeOfCharacter(std::string character);
|
||||||
|
static std::vector<std::string> SplitVersionString(std::string version);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FVVERSIONCOMPARATOR_H
|
||||||
13
client/fervor/tests/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
qt4_wrap_cpp(TEST_MOC_SOURCES fvversioncomparatortest.h)
|
||||||
|
|
||||||
|
add_executable(FervorTests
|
||||||
|
fvversioncomparatortest.cpp ${TEST_MOC_SOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries( FervorTests
|
||||||
|
${QT_LIBRARIES}
|
||||||
|
${QT_QTTEST_LIBRARY}
|
||||||
|
Fervor
|
||||||
|
)
|
||||||
|
|
||||||
|
add_test(FervorTests ${CMAKE_CURRENT_BINARY_DIR}/FervorTests)
|
||||||
47
client/fervor/tests/fvversioncomparatortest.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "fvversioncomparatortest.h"
|
||||||
|
#include "fvversioncomparator.h"
|
||||||
|
|
||||||
|
void FvVersionComparatorTest::testNumbers()
|
||||||
|
{
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0", "1.1") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0", "1.0") == FvVersionComparator::kSame);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("2.0", "1.1") == FvVersionComparator::kDescending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("0.1", "0.0.1") == FvVersionComparator::kDescending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("0.1", "0.1.2") == FvVersionComparator::kAscending);
|
||||||
|
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0.0", "1.0.0") == FvVersionComparator::kSame);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0.0", "1.0.1") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0.0", "1.1.0") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0.0", "2.0.0") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0.0", "0.0.1") == FvVersionComparator::kDescending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0.0", "0.1.0") == FvVersionComparator::kDescending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0.0", "0.9.9") == FvVersionComparator::kDescending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("0.0.1", "0.0.2") == FvVersionComparator::kAscending);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvVersionComparatorTest::testPrereleases()
|
||||||
|
{
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0a1", "1.0b1") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0b1", "1.0") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("0.9", "1.0a1") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0b", "1.0b2") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0b10", "1.0b11") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0b9", "1.0b10") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0rc", "1.0") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0b", "1.0") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0pre1", "1.0") == FvVersionComparator::kAscending);
|
||||||
|
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0", "1.0pre1") == FvVersionComparator::kDescending);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FvVersionComparatorTest::testVersionsWithBuildNumbers()
|
||||||
|
{
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0 (1234)", "1.0 (1235)") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0b1 (1234)", "1.0 (1234)") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0b5 (1234)", "1.0b5 (1235)") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0b5 (1234)", "1.0.1b5 (1234)") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("1.0.1b5 (1234)", "1.0.1b6 (1234)") == FvVersionComparator::kAscending);
|
||||||
|
QVERIFY(FvVersionComparator::CompareVersions("3.3 (5847)", "3.3.1b1 (5902)") == FvVersionComparator::kAscending);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_MAIN(FvVersionComparatorTest)
|
||||||
18
client/fervor/tests/fvversioncomparatortest.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#ifndef FVVERSIONCOMPARATORTEST_H
|
||||||
|
#define FVVERSIONCOMPARATORTEST_H
|
||||||
|
|
||||||
|
#include <QtTest/QtTest>
|
||||||
|
|
||||||
|
|
||||||
|
class FvVersionComparatorTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void testNumbers();
|
||||||
|
void testPrereleases();
|
||||||
|
void testVersionsWithBuildNumbers();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FVVERSIONCOMPARATORTEST_H
|
||||||
88
client/finddialog.cpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#include "finddialog.h"
|
||||||
|
#include "ui_finddialog.h"
|
||||||
|
|
||||||
|
FindInTasksDialog::FindInTasksDialog(QWidget *parent) :
|
||||||
|
QDialog(parent, Qt::Sheet),
|
||||||
|
ui(new Ui::FindDialog)
|
||||||
|
{
|
||||||
|
int typeId = qRegisterMetaType<FindResultItem>("FindResultItem");
|
||||||
|
ui->setupUi(this);
|
||||||
|
mModel = new FindResultsModel(this);
|
||||||
|
ui->mResultList->setModel(mModel);
|
||||||
|
mSearchHelper.moveToThread(&mSearchHelper);
|
||||||
|
|
||||||
|
connect(ui->mSearchButton, SIGNAL(clicked()), this, SLOT(startSearch()));
|
||||||
|
connect(ui->mQueryText, SIGNAL(returnPressed()), this, SLOT(startSearch()));
|
||||||
|
connect(&mSearchHelper, SIGNAL(newResultAvailable(FindResultItem)), this, SLOT(onNewResult(FindResultItem)));
|
||||||
|
connect(&mSearchHelper, SIGNAL(searchComplete()), this, SLOT(searchComplete()));
|
||||||
|
connect(ui->mResultList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(resultSelected(QModelIndex)));
|
||||||
|
connect(ui->mResultList, SIGNAL(activated(QModelIndex)), this, SLOT(resultSelected(QModelIndex)));
|
||||||
|
}
|
||||||
|
|
||||||
|
FindInTasksDialog::~FindInTasksDialog()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
FindResultItem& FindInTasksDialog::getResult()
|
||||||
|
{
|
||||||
|
return mResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindInTasksDialog::startSearch()
|
||||||
|
{
|
||||||
|
if (ui->mSearchButton->text() == tr("Stop"))
|
||||||
|
{
|
||||||
|
// Stop possible previous search
|
||||||
|
mSearchHelper.stop();
|
||||||
|
|
||||||
|
// Enable query edit again
|
||||||
|
ui->mQueryText->setEnabled(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ui->mQueryText->text().isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Stop possible previous search
|
||||||
|
mSearchHelper.stop();
|
||||||
|
|
||||||
|
// Clear from old results
|
||||||
|
mModel->clear();
|
||||||
|
|
||||||
|
ui->mSearchButton->setText(tr("Stop"));
|
||||||
|
ui->mQueryText->setEnabled(false);
|
||||||
|
// Start new search
|
||||||
|
mSearchHelper.start(ui->mQueryText->text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindInTasksDialog::resultSelected(const QModelIndex& index)
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
mResult = mModel->getItem(index.row());
|
||||||
|
accept();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindInTasksDialog::onNewResult(const FindResultItem &item)
|
||||||
|
{
|
||||||
|
// Add new item to list
|
||||||
|
mModel->addItem(item);
|
||||||
|
|
||||||
|
// Move focus to list
|
||||||
|
ui->mResultList->setFocus();
|
||||||
|
if (mModel->rowCount() == 1)
|
||||||
|
{
|
||||||
|
ui->mResultList->selectRow(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindInTasksDialog::searchComplete()
|
||||||
|
{
|
||||||
|
// Sort found results by occurence count
|
||||||
|
ui->mSearchButton->setText(tr("Search"));
|
||||||
|
ui->mQueryText->setEnabled(true);
|
||||||
|
}
|
||||||
33
client/finddialog.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#ifndef FINDDIALOG_H
|
||||||
|
#define FINDDIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include "findsupport.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class FindDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FindInTasksDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FindInTasksDialog(QWidget *parent = 0);
|
||||||
|
~FindInTasksDialog();
|
||||||
|
|
||||||
|
FindResultItem& getResult();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void startSearch();
|
||||||
|
void resultSelected(const QModelIndex& index);
|
||||||
|
void searchComplete();
|
||||||
|
void onNewResult(const FindResultItem& item);
|
||||||
|
private:
|
||||||
|
Ui::FindDialog *ui;
|
||||||
|
FindResultsModel* mModel;
|
||||||
|
TaskSearch mSearchHelper;
|
||||||
|
FindResultItem mResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FINDDIALOG_H
|
||||||
64
client/finddialog.ui
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>FindDialog</class>
|
||||||
|
<widget class="QDialog" name="FindDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>611</width>
|
||||||
|
<height>392</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Text to search:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="mQueryText"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="mSearchButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="default">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTableView" name="mResultList">
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
<property name="showGrid">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="horizontalHeaderDefaultSectionSize">
|
||||||
|
<number>293</number>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="verticalHeaderVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
187
client/findsupport.cpp
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
#include "findsupport.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "storage.h"
|
||||||
|
|
||||||
|
FindResultsModel::FindResultsModel(QObject* parent)
|
||||||
|
:QAbstractTableModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int FindResultsModel::columnCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (parent.isValid())
|
||||||
|
return 0;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FindResultsModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
return mItemList.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant FindResultsModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
FindResultItem item = mItemList[index.row()];
|
||||||
|
switch (role)
|
||||||
|
{
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
return index.column() == 0 ? item.mTask->title() : "..." + item.mPattern + "...";
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags FindResultsModel::flags(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
Qt::ItemFlags result = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant FindResultsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
||||||
|
{
|
||||||
|
switch (section)
|
||||||
|
{
|
||||||
|
case 0: return tr("Task title");
|
||||||
|
case 1: return tr("Occurence");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindResultsModel::addItem(const FindResultItem& item)
|
||||||
|
{
|
||||||
|
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||||
|
mItemList.append(item);
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindResultsModel::clear()
|
||||||
|
{
|
||||||
|
beginRemoveRows(QModelIndex(), 0, rowCount()-1);
|
||||||
|
mItemList.clear();
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
const FindResultItem& FindResultsModel::getItem(int row) const
|
||||||
|
{
|
||||||
|
return mItemList.at(row);
|
||||||
|
}
|
||||||
|
// ------ TaskSearch -------
|
||||||
|
TaskSearch::TaskSearch()
|
||||||
|
:mShutdown(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskSearch::~TaskSearch()
|
||||||
|
{
|
||||||
|
//stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskSearch::start(const QString &request)
|
||||||
|
{
|
||||||
|
mShutdown = false;
|
||||||
|
mRequest = request;
|
||||||
|
QThread::start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskSearch::stop()
|
||||||
|
{
|
||||||
|
mShutdown = true;
|
||||||
|
QThread::wait();
|
||||||
|
mRequest.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TaskSearch::isRunning()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int findInString(const QStringList& pattern, const QString& doc, int& firstIndex, QString& occurenceSubstring, QString& foundWord)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
firstIndex = -1;
|
||||||
|
for (const QString& s: pattern)
|
||||||
|
{
|
||||||
|
int wordIndex = doc.indexOf(s);
|
||||||
|
if (wordIndex >= 0)
|
||||||
|
{
|
||||||
|
if (firstIndex == -1)
|
||||||
|
{
|
||||||
|
firstIndex = wordIndex;
|
||||||
|
|
||||||
|
// Copy pattern
|
||||||
|
int startIndex = firstIndex - 15;
|
||||||
|
if (startIndex < 0)
|
||||||
|
startIndex = 0;
|
||||||
|
int endIndex = firstIndex + 15;
|
||||||
|
if (endIndex >= doc.length())
|
||||||
|
endIndex = doc.length() - 1;
|
||||||
|
occurenceSubstring = doc.mid(startIndex, endIndex - startIndex + 1);
|
||||||
|
occurenceSubstring.replace("\n", " ").replace("\r", "");
|
||||||
|
foundWord = s;
|
||||||
|
}
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskSearch::run()
|
||||||
|
{
|
||||||
|
// Reserve array with initial length to make copy
|
||||||
|
TaskArray tasks(Storage::instance().topOfTaskTree().count());
|
||||||
|
|
||||||
|
// Copy top level tasks
|
||||||
|
std::copy(Storage::instance().topOfTaskTree().begin(), Storage::instance().topOfTaskTree().end(), tasks.begin());
|
||||||
|
|
||||||
|
QStringList sl;
|
||||||
|
if (mRequest.startsWith("\"") && mRequest.endsWith("\""))
|
||||||
|
sl.append(mRequest);
|
||||||
|
else
|
||||||
|
sl = mRequest.split(" ", QString::SkipEmptyParts);
|
||||||
|
|
||||||
|
if (!sl.empty())
|
||||||
|
{
|
||||||
|
while (tasks.count() > 0 && !mShutdown)
|
||||||
|
{
|
||||||
|
PTask current = tasks.front();
|
||||||
|
tasks.erase(tasks.begin());
|
||||||
|
|
||||||
|
// Add children to tasks list
|
||||||
|
for (PTask child: current->children())
|
||||||
|
tasks.push_back(child);
|
||||||
|
|
||||||
|
// Search in title
|
||||||
|
FindResultItem titleSearchItem;
|
||||||
|
titleSearchItem.mIndex = -1;
|
||||||
|
titleSearchItem.mOccurences = findInString(sl, current->title(), titleSearchItem.mIndex, titleSearchItem.mPattern, titleSearchItem.mFoundWord);
|
||||||
|
|
||||||
|
// Get content from task
|
||||||
|
bool loadedThisTime = false;
|
||||||
|
if (!current->isContentLoaded())
|
||||||
|
current->loadContent();
|
||||||
|
QString content = current->html();
|
||||||
|
|
||||||
|
// Search in content
|
||||||
|
FindResultItem contentSearchItem;
|
||||||
|
contentSearchItem.mIndex = -1;
|
||||||
|
contentSearchItem.mOccurences = findInString(sl, content, contentSearchItem.mIndex, contentSearchItem.mPattern, contentSearchItem.mFoundWord);
|
||||||
|
contentSearchItem.mTask = current;
|
||||||
|
|
||||||
|
if (contentSearchItem.mOccurences + titleSearchItem.mOccurences >= sl.count())
|
||||||
|
{
|
||||||
|
emit newResultAvailable(contentSearchItem);
|
||||||
|
}
|
||||||
|
// Unload content if it is not needed now
|
||||||
|
if (loadedThisTime)
|
||||||
|
current->unloadContent();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit searchComplete();
|
||||||
|
}
|
||||||
74
client/findsupport.h
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#ifndef FINDSUPPORT_H
|
||||||
|
#define FINDSUPPORT_H
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QVector>
|
||||||
|
#include <QThread>
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
|
struct FindResultItem
|
||||||
|
{
|
||||||
|
// Visible search result in list
|
||||||
|
QString mPattern;
|
||||||
|
|
||||||
|
// Found word
|
||||||
|
QString mFoundWord;
|
||||||
|
|
||||||
|
// Index of first found word in document
|
||||||
|
int mIndex;
|
||||||
|
|
||||||
|
// Number of discovered words from requested list. It is in range [0..length_of_query_list]
|
||||||
|
int mOccurences;
|
||||||
|
|
||||||
|
// Task
|
||||||
|
PTask mTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(FindResultItem)
|
||||||
|
|
||||||
|
class FindResultsModel: public QAbstractTableModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
friend class TaskSearch;
|
||||||
|
public:
|
||||||
|
explicit FindResultsModel(QObject* parent = 0);
|
||||||
|
int columnCount(const QModelIndex &parent) const;
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||||
|
QVariant data(const QModelIndex &index, int role) const;
|
||||||
|
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||||
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
const FindResultItem& getItem(int row) const;
|
||||||
|
public slots:
|
||||||
|
void addItem(const FindResultItem& item);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QVector<FindResultItem> mItemList;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class TaskSearch: public QThread
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
TaskSearch();
|
||||||
|
~TaskSearch();
|
||||||
|
|
||||||
|
void start(const QString& request);
|
||||||
|
void stop();
|
||||||
|
bool isRunning();
|
||||||
|
void run();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void newResultAvailable(const FindResultItem& item);
|
||||||
|
void searchComplete();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QString mRequest;
|
||||||
|
bool mShutdown;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FINDSUPPORT_H
|
||||||
|
|
||||||
135
client/helper.cpp
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
#include "config.h"
|
||||||
|
#include "helper.h"
|
||||||
|
#include "platforms/hidtracker.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
|
||||||
|
#ifdef TARGET_OSX
|
||||||
|
|
||||||
|
char* __strlcpy_chk (char* dest, const char* src, int len, int destcapacity)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* __strlcat_chk (char* dest, const char* src, int len, int destcapacity)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString TimeHelper::secondsToDisplay(int seconds, bool showSeconds)
|
||||||
|
{
|
||||||
|
int hours = seconds / 3600;
|
||||||
|
int minutes = (seconds % 3600) / 60;
|
||||||
|
int secs = seconds % 60;
|
||||||
|
if (showSeconds)
|
||||||
|
return QString("%1:%2:%3").arg(hours, 2, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')).arg(secs, 2, 10, QLatin1Char('0'));
|
||||||
|
else
|
||||||
|
return QString("%1:%2").arg(hours, 2, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PathHelper::pathToSettings()
|
||||||
|
{
|
||||||
|
#if QT_VERSION >= 0x050000
|
||||||
|
QString folder = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||||
|
#else
|
||||||
|
QString folder = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
|
||||||
|
#endif
|
||||||
|
QString path = folder + "/" + SETTINGS_FILENAME;
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PathHelper::pathToDatabase()
|
||||||
|
{
|
||||||
|
#if QT_VERSION >= 0x050000
|
||||||
|
QString folder = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||||
|
#else
|
||||||
|
QString folder = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
|
||||||
|
#endif
|
||||||
|
QString path = folder + "/" + DATABASENAME;
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PathHelper::pathToDesktop()
|
||||||
|
{
|
||||||
|
#if QT_VERSION >= 0x050000
|
||||||
|
QString folder = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||||
|
#else
|
||||||
|
QString folder = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation);
|
||||||
|
#endif
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PathHelper::pathToDatabaseTemplate()
|
||||||
|
{
|
||||||
|
#ifdef TARGET_WIN
|
||||||
|
return QCoreApplication::applicationDirPath() + "/" + DATABASENAME;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef TARGET_OSX
|
||||||
|
return QCoreApplication::applicationDirPath() + "/../Resources/" + DATABASENAME;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PathHelper::pathToLog()
|
||||||
|
{
|
||||||
|
#if QT_VERSION >= 0x050000
|
||||||
|
QString folder = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||||
|
#else
|
||||||
|
QString folder = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
|
||||||
|
#endif
|
||||||
|
return folder + "/" + LOGNAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActivityTrackerHelper::ensureSmartTrackingIsPossible()
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
HIDActivityTracker tracker;
|
||||||
|
|
||||||
|
result = tracker.isPossible();
|
||||||
|
#ifdef TARGET_OSX
|
||||||
|
if (!result && QSysInfo::MacintoshVersion <= QSysInfo::MV_10_8)
|
||||||
|
{
|
||||||
|
QMessageBox msgbox(QMessageBox::Question, QT_TR_NOOP("Permission required"),
|
||||||
|
QT_TR_NOOP("To use smart time tracking Litt needs access to OS X accessibility features.\
|
||||||
|
Please grant access in Security & Privacy preferences, location in System Preferences."),
|
||||||
|
QMessageBox::NoButton, nullptr);
|
||||||
|
msgbox.addButton(QT_TR_NOOP("Open System Preferences"), QMessageBox::AcceptRole);
|
||||||
|
msgbox.addButton(QT_TR_NOOP("Deny"), QMessageBox::RejectRole);
|
||||||
|
int execResult = msgbox.exec();
|
||||||
|
if (execResult == 0)
|
||||||
|
{
|
||||||
|
system("open /System/Library/PreferencePanes/UniversalAccessPref.prefPane");
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8)
|
||||||
|
result = true;
|
||||||
|
#endif
|
||||||
|
#ifdef TARGET_WIN
|
||||||
|
result = false;
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
EscapeKeyEventFilter::EscapeKeyEventFilter(QObject *parent)
|
||||||
|
:QObject(parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool EscapeKeyEventFilter::eventFilter(QObject *obj, QEvent * event)
|
||||||
|
{
|
||||||
|
if (event->type() == QEvent::KeyPress && ((QKeyEvent*)event)->key() == Qt::Key_Escape )
|
||||||
|
{
|
||||||
|
emit escapePressed(obj);
|
||||||
|
}
|
||||||
|
if (event->type() == QEvent::FocusOut)
|
||||||
|
{
|
||||||
|
emit escapePressed(obj);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
42
client/helper.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#ifndef HELPER_H
|
||||||
|
#define HELPER_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class TimeHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static QString secondsToDisplay(int seconds, bool showSeconds);
|
||||||
|
};
|
||||||
|
|
||||||
|
class PathHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static QString pathToDatabase();
|
||||||
|
static QString pathToDesktop();
|
||||||
|
static QString pathToSettings();
|
||||||
|
static QString pathToDatabaseTemplate();
|
||||||
|
static QString pathToLog();
|
||||||
|
};
|
||||||
|
|
||||||
|
class ActivityTrackerHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool ensureSmartTrackingIsPossible();
|
||||||
|
};
|
||||||
|
|
||||||
|
class EscapeKeyEventFilter: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit EscapeKeyEventFilter(QObject* parent = 0);
|
||||||
|
bool eventFilter(QObject *obj, QEvent * event);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void escapePressed(QObject* obj);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
BIN
client/icons/App_icon_files.zip
Normal file
81
client/icons/StopIcon.dbdoc
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Canvas Background Color</key>
|
||||||
|
<data>
|
||||||
|
YnBsaXN0MDDUAQIDBAUIFxhUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLR
|
||||||
|
BgdUcm9vdIABowkKEVUkbnVsbNMLDA0ODxBcTlNDb2xvclNwYWNlViRjbGFzc1dOU1do
|
||||||
|
aXRlEAOAAk0wLjY2NjY2NjY4NjUA0hITFBVYJGNsYXNzZXNaJGNsYXNzbmFtZaIVFldO
|
||||||
|
U0NvbG9yWE5TT2JqZWN0EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIIERYfKDI1OjxARk1a
|
||||||
|
YWlrbXuAiZSXn6itAAAAAAAAAQEAAAAAAAAAGQAAAAAAAAAAAAAAAAAAAL8=
|
||||||
|
</data>
|
||||||
|
<key>Canvas Color</key>
|
||||||
|
<data>
|
||||||
|
YnBsaXN0MDDUAQIDBAUIFxhUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLR
|
||||||
|
BgdUcm9vdIABowkKEVUkbnVsbNMLDA0ODxBcTlNDb2xvclNwYWNlViRjbGFzc1dOU1do
|
||||||
|
aXRlEAOAAkIxANISExQVWCRjbGFzc2VzWiRjbGFzc25hbWWiFRZXTlNDb2xvclhOU09i
|
||||||
|
amVjdBIAAYagXxAPTlNLZXllZEFyY2hpdmVyCBEWHygyNTo8QEZNWmFpa21wdX6JjJSd
|
||||||
|
ogAAAAAAAAEBAAAAAAAAABkAAAAAAAAAAAAAAAAAAAC0
|
||||||
|
</data>
|
||||||
|
<key>Canvas Height</key>
|
||||||
|
<real>128</real>
|
||||||
|
<key>Canvas Width</key>
|
||||||
|
<real>128</real>
|
||||||
|
<key>Grid Color</key>
|
||||||
|
<data>
|
||||||
|
YnBsaXN0MDDUAQIDBAUIFxhUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLR
|
||||||
|
BgdUcm9vdIABowkKEVUkbnVsbNMLDA0ODxBcTlNDb2xvclNwYWNlViRjbGFzc1dOU1do
|
||||||
|
aXRlEAOAAkQwLjUA0hITFBVYJGNsYXNzZXNaJGNsYXNzbmFtZaIVFldOU0NvbG9yWE5T
|
||||||
|
T2JqZWN0EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIIERYfKDI1OjxARk1aYWlrbXJ3gIuO
|
||||||
|
lp+kAAAAAAAAAQEAAAAAAAAAGQAAAAAAAAAAAAAAAAAAALY=
|
||||||
|
</data>
|
||||||
|
<key>Grid Spacing</key>
|
||||||
|
<real>72</real>
|
||||||
|
<key>Grid Tick Count</key>
|
||||||
|
<integer>5</integer>
|
||||||
|
<key>Layers and shapes</key>
|
||||||
|
<data>
|
||||||
|
YnBsaXN0MDDUAQIDBAUIp6hUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLR
|
||||||
|
BgdUcm9vdIABrxAaCQoQIiMnNzpBZGptcHSAg4iXVhqam5ydoqNVJG51bGzSCwwNDlYk
|
||||||
|
Y2xhc3NaTlMub2JqZWN0c4AHoQ+AAtkREhMUFQsWFxgZGhsaHR4fICFVQWxwaGFXVmlz
|
||||||
|
aWJsZV8QEEJhY2tncm91bmQgSW1hZ2VYRWRpdGFibGVaTGF5ZXIgTmFtZVpCbGVuZCBN
|
||||||
|
b2RlVlNoYXBlc18QGUJhY2tncm91bmQgSW1hZ2UgUG9zaXRpb24iP4AAAAmAAAmAA4AZ
|
||||||
|
EACABIAYWkJhY2tncm91bmTSCwwNJYAHoSaABdgoKSorLAstLi8wGjIzNDU2VlN0cm9r
|
||||||
|
ZVtQb2ludCBjb3VudFpDbG9zZSBQYXRoVlBvaW50c1hSb3RhdGlvblZTaGFkb3dVRmls
|
||||||
|
bHOACBAECYAQIgAAAACAF4ANgAbSCwwNOYAHoNI7PD0+WCRjbGFzc2VzWiRjbGFzc25h
|
||||||
|
bWWjPj9AXk5TTXV0YWJsZUFycmF5V05TQXJyYXlYTlNPYmplY3TfEBRCQ0RFRkdISUpL
|
||||||
|
C0xNTk9QUVJTVBpWHx9XVllaVxpdH15fHxsaHxsaXxAQRmlsbCBTdGFydCBBcnJvd18Q
|
||||||
|
EFNob3cgU3RhcnQgQXJyb3daRGFzaCBTdHlsZV8QD0VuZCBBcnJvdyBTdHlsZV8QFEVu
|
||||||
|
ZCBBcnJvdyBGaWxsIENvbG9yXlNob3cgRW5kIEFycm93XxAPTGluZSBKb2luIFN0eWxl
|
||||||
|
WkxpbmUgV2lkdGhfEBZTdGFydCBBcnJvdyBGaWxsIENvbG9yXxASU3Ryb2tlIFN0YXJ0
|
||||||
|
IEFycm93XkxpbmUgQ2FwIFN0eWxlXFN0cm9rZSBDb2xvcltUZXh0IE9mZnNldF8QEVN0
|
||||||
|
YXJ0IEFycm93IFN0eWxlXVBhdHRlcm4gSW1hZ2VeRmlsbCBFbmQgQXJyb3dbU3Ryb2tl
|
||||||
|
IE1vZGVbU3Ryb2tlIFRleHRfEBBTdHJva2UgRW5kIEFycm93CQiACwgQASJBIAAAgAsJ
|
||||||
|
gAyACSJAAAAAgAAJgAAJ02ULZmdoaVxOU0NvbG9yU3BhY2VXTlNXaGl0ZRADgApCMADS
|
||||||
|
OzxrbKJsQFdOU0NvbG9y02ULZmdob4AKQjEA0js8cXOickBYREJTdHJva2VYREJTdHJv
|
||||||
|
a2XWdXYLd3h5ent8Vn5/XE5TU2hhZG93VmVydF1OU1NoYWRvd0NvbG9yV0VuYWJsZWRd
|
||||||
|
TlNTaGFkb3dIb3Jpel8QEk5TU2hhZG93Qmx1clJhZGl1cyPAJAAAAAAAAIAOgA8II0Ak
|
||||||
|
AAAAAAAAI0AAAAAAAAAA02ULZmdogoAKTxAPMCAwLjMwMDAwMDAxMTkA0js8hIejhYZA
|
||||||
|
WERCU2hhZG93WE5TU2hhZG93WERCU2hhZG930gsMDYqAB6yLjI2OjIyRjIyUjYyAEYAS
|
||||||
|
gBOAFIASgBKAFYASgBKAFoATgBJfEBZ7MjQuMzE5MjA2LCAyMC43MzY1NjF9CAlfEBV7
|
||||||
|
MjQuMjEwOTM4LCAxMDUuMjE2OH1fEBZ7MTA3LjQwNzQ5LCAxMDQuODg5Njl9XxAVezEw
|
||||||
|
Ny41MzUxNiwgMjAuMjgxMjV90js8nqGjn6BAWkRCUG9seWxpbmVXREJTaGFwZVpEQlBv
|
||||||
|
bHlsaW5lVnswLCAwfdI7PKSmoqVAV0RCTGF5ZXJXREJMYXllchIAAYagXxAPTlNLZXll
|
||||||
|
ZEFyY2hpdmVyAAgAEQAWAB8AKAAyADUAOgA8AFkAXwBkAGsAdgB4AHoAfACPAJUAnQCw
|
||||||
|
ALkAxADPANYA8gD3APgA+gD7AP0A/wEBAQMBBQEQARUBFwEZARsBLAEzAT8BSgFRAVoB
|
||||||
|
YQFnAWkBawFsAW4BcwF1AXcBeQF+AYABgQGGAY8BmgGeAa0BtQG+AekB/AIPAhoCLAJD
|
||||||
|
AlICZAJvAogCnQKsArkCxQLZAucC9gMCAw4DIQMiAyMDJQMmAygDLQMvAzADMgM0AzkD
|
||||||
|
OwM8Az4DPwNGA1MDWwNdA18DYgNnA2oDcgN5A3sDfgODA4YDjwOYA6UDsgPAA8gD1gPr
|
||||||
|
A/QD9gP4A/kEAgQLBBIEFAQmBCsELwQ4BEEESgRPBFEEXgRgBGIEZARmBGgEagRsBG4E
|
||||||
|
cARyBHQEdgSPBJAEkQSpBMIE2gTfBOME7gT2BQEFCAUNBRAFGAUgBSUAAAAAAAACAQAA
|
||||||
|
AAAAAACpAAAAAAAAAAAAAAAAAAAFNw==
|
||||||
|
</data>
|
||||||
|
<key>Show Grid</key>
|
||||||
|
<true/>
|
||||||
|
<key>Show Rulers</key>
|
||||||
|
<true/>
|
||||||
|
<key>Snap to grid</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
client/icons/Untitled.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
client/icons/accessories-calculator.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
client/icons/appicon-osx.icns
Normal file
BIN
client/icons/appicon-osx.iconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
client/icons/appicon-osx.iconset/icon_128x128@2x.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
client/icons/appicon-osx.iconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
client/icons/appicon-osx.iconset/icon_16x16@2x.png
Normal file
|
After Width: | Height: | Size: 494 B |
BIN
client/icons/appicon-osx.iconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
client/icons/appicon-osx.iconset/icon_256x256@2x.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
client/icons/appicon-osx.iconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 552 B |
BIN
client/icons/appicon-osx.iconset/icon_32x32@2x.png
Normal file
|
After Width: | Height: | Size: 830 B |
BIN
client/icons/appicon-osx.iconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
client/icons/appicon-osx.iconset/icon_512x512@2x.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
client/icons/appicon-osx.pdf
Normal file
BIN
client/icons/appicon-osx.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
172
client/icons/appicon.dbdoc
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Canvas Background Color</key>
|
||||||
|
<data>
|
||||||
|
YnBsaXN0MDDUAQIDBAUIFxhUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLR
|
||||||
|
BgdUcm9vdIABowkKEVUkbnVsbNMLDA0ODxBcTlNDb2xvclNwYWNlViRjbGFzc1dOU1do
|
||||||
|
aXRlEAOAAk0wLjY2NjY2NjY4NjUA0hITFBVYJGNsYXNzZXNaJGNsYXNzbmFtZaIVFldO
|
||||||
|
U0NvbG9yWE5TT2JqZWN0EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIIERYfKDI1OjxARk1a
|
||||||
|
YWlrbXuAiZSXn6itAAAAAAAAAQEAAAAAAAAAGQAAAAAAAAAAAAAAAAAAAL8=
|
||||||
|
</data>
|
||||||
|
<key>Canvas Color</key>
|
||||||
|
<data>
|
||||||
|
YnBsaXN0MDDUAQIDBAUIFxhUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLR
|
||||||
|
BgdUcm9vdIABowkKEVUkbnVsbNMLDA0ODxBcTlNDb2xvclNwYWNlViRjbGFzc1dOU1do
|
||||||
|
aXRlEAOAAkIxANISExQVWCRjbGFzc2VzWiRjbGFzc25hbWWiFRZXTlNDb2xvclhOU09i
|
||||||
|
amVjdBIAAYagXxAPTlNLZXllZEFyY2hpdmVyCBEWHygyNTo8QEZNWmFpa21wdX6JjJSd
|
||||||
|
ogAAAAAAAAEBAAAAAAAAABkAAAAAAAAAAAAAAAAAAAC0
|
||||||
|
</data>
|
||||||
|
<key>Canvas Height</key>
|
||||||
|
<real>256</real>
|
||||||
|
<key>Canvas Width</key>
|
||||||
|
<real>256</real>
|
||||||
|
<key>Grid Color</key>
|
||||||
|
<data>
|
||||||
|
YnBsaXN0MDDUAQIDBAUIFxhUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLR
|
||||||
|
BgdUcm9vdIABowkKEVUkbnVsbNMLDA0ODxBcTlNDb2xvclNwYWNlViRjbGFzc1dOU1do
|
||||||
|
aXRlEAOAAkQwLjUA0hITFBVYJGNsYXNzZXNaJGNsYXNzbmFtZaIVFldOU0NvbG9yWE5T
|
||||||
|
T2JqZWN0EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIIERYfKDI1OjxARk1aYWlrbXJ3gIuO
|
||||||
|
lp+kAAAAAAAAAQEAAAAAAAAAGQAAAAAAAAAAAAAAAAAAALY=
|
||||||
|
</data>
|
||||||
|
<key>Grid Spacing</key>
|
||||||
|
<real>72</real>
|
||||||
|
<key>Grid Tick Count</key>
|
||||||
|
<integer>5</integer>
|
||||||
|
<key>Layers and shapes</key>
|
||||||
|
<data>
|
||||||
|
YnBsaXN0MDDUAAEAAgADAAQABQAIAlgCWVQkdG9wWCRvYmplY3RzWCR2ZXJzaW9uWSRh
|
||||||
|
cmNoaXZlctEABgAHVHJvb3SAAa8QbAAJAAoAEAAiACMALgBCAEYAXwBgAGUAawBxAHYA
|
||||||
|
fwCAAIEAhgCJAI0AjgCSAJoAngCfAKIApgCnAKgAqQCtALAA0wDWANsA3wDrAO4A8wD0
|
||||||
|
APUA9gD3APgA/QEIAQsBGgEdASQBJwEwAMUAGgEzATQBOQFBAUQBUwFaAV0BZgFnAWgB
|
||||||
|
cAFzAYIBiQGMAZUBlgGXAZ8BogGxAbgBuwHEAcUBxgHQAdMB4gHpAewB7QHuAe8B8AH1
|
||||||
|
Af0CAAIPAhYCGQIiAiMCJAIsAi8CPgJFAkgCUQJSAlMCVFUkbnVsbNIACwAMAA0ADlYk
|
||||||
|
Y2xhc3NaTlMub2JqZWN0c4AeoQAPgALZABEAEgATABQAFQALABYAFwAYABkAGgAbABoA
|
||||||
|
HQAeAB8AIAAhVUFscGhhV1Zpc2libGVfEBBCYWNrZ3JvdW5kIEltYWdlWEVkaXRhYmxl
|
||||||
|
WkxheWVyIE5hbWVaQmxlbmQgTW9kZVZTaGFwZXNfEBlCYWNrZ3JvdW5kIEltYWdlIFBv
|
||||||
|
c2l0aW9uIj+AAAAJgAAJgAOAaxAAgASAalpCYWNrZ3JvdW5k0gALAAwADQAlgB6oACYA
|
||||||
|
JwAoACkAKgArACwALYAFgCyAOIBAgEiAUIBagGLaAC8AMAAxADIACwAzADQANQA2ADcA
|
||||||
|
OAA5ADoAOwA8AD0APgA/AEAAQVZTdHJva2VYUm90YXRpb25cRm91cnRoIFBvaW50W0Zp
|
||||||
|
cnN0IFBvaW50XFNlY29uZCBQb2ludFtUaGlyZCBQb2ludFZTaGFkb3daS25vYiBQb2lu
|
||||||
|
dFVGaWxsc4AfIgAAAACAKYAmgCuAJ4AogCOAKoAG0gALAAwADQBEgB6hAEWAB90ARwBI
|
||||||
|
AEkASgBLAEwATQBOAE8AUAALAFEAUgBTAFQAVQBWAFcAWABZAFoAWwAfAFwAGwBeXlN0
|
||||||
|
YXJ0aW5nIFBvaW50XUVuZGluZyBSYWRpdXNaRHJhdyBQb2ludFlGaWxsIE1vZGVfEA9J
|
||||||
|
bWFnZSBGaWxsIE1vZGVYR3JhZGllbnRZRmlsbCBOYW1lXxAPU3RhcnRpbmcgUmFkaXVz
|
||||||
|
WkZpbGwgQ29sb3JdR3JhZGllbnQgVHlwZVpGaWxsIEltYWdlXEVuZGluZyBQb2ludIAb
|
||||||
|
Ir9/z+iAGhABEGSAC4AIIgAAAACACYAdgACAHFRGaWxs0wBhAGIACwBWAGMAZFxOU0Nv
|
||||||
|
bG9yU3BhY2VVTlNSR0JGMSAxIDEAgArSAGYAZwBoAGlYJGNsYXNzZXNaJGNsYXNzbmFt
|
||||||
|
ZaIAaQBqV05TQ29sb3JYTlNPYmplY3TTAGwACwBtAG4AbwBwXxAUTlNHcmFkaWVudENv
|
||||||
|
bG9yU3BhY2VfEBROU0dyYWRpZW50Q29sb3JBcnJheYARgBmADNIACwAMAHIAc4AYogB0
|
||||||
|
AHWADYAV0wAMAAsAdwB4AHsAfFdOUy5rZXlzogB5AHqAEIATgBSiAH0AfoAOgA9fEA9O
|
||||||
|
U0dyYWRpZW50Q29sb3JfEBJOU0dyYWRpZW50UG9zaXRpb27UAIIACwBhAGIAbgBkAFYA
|
||||||
|
hV8QEk5TQ3VzdG9tQ29sb3JTcGFjZYARgApGMCAwIDAA0gCHAAsAVgCIVE5TSUSAEtIA
|
||||||
|
ZgBnAIoAjKIAiwBqXE5TQ29sb3JTcGFjZVxOU0NvbG9yU3BhY2UiAAAAANIAZgBnAI8A
|
||||||
|
kKMAkACRAGpfEBNOU011dGFibGVEaWN0aW9uYXJ5XE5TRGljdGlvbmFyedMADAALAHcA
|
||||||
|
kwB7AJeiAJQAlYAWgBeAFKIAfQB+gA6AD9QAggALAGEAYgBuAGQAVgCdgBGACkYxIDEg
|
||||||
|
MQAiP4AAANIAZgBnAKAAoaIAoQBqV05TQXJyYXnSAGYAZwCjAKWiAKQAalpOU0dyYWRp
|
||||||
|
ZW50Wk5TR3JhZGllbnRfEB17LTAuOTk5MjY4MDUsIC0yLjU5NTI3MDllLTI0fVx7LWlu
|
||||||
|
ZiwgLWluZn1cey1pbmYsIC1pbmZ90gBmAGcAqgCsogCrAGpWREJGaWxsVkRCRmlsbNIA
|
||||||
|
ZgBnAK4Ar6MArwChAGpeTlNNdXRhYmxlQXJyYXnfEBQAsQCyALMAtAC1ALYAtwC4ALkA
|
||||||
|
ugALALsAvAC9AL4AvwDAAMEAwgDDABoAxQAfAB8AxgDFAFYAyADGABoAywDMAM0AzgAf
|
||||||
|
ABsAGgAfABsAGl8QEEZpbGwgU3RhcnQgQXJyb3dfEBBTaG93IFN0YXJ0IEFycm93WkRh
|
||||||
|
c2ggU3R5bGVfEA9FbmQgQXJyb3cgU3R5bGVfEBRFbmQgQXJyb3cgRmlsbCBDb2xvcl5T
|
||||||
|
aG93IEVuZCBBcnJvd18QD0xpbmUgSm9pbiBTdHlsZVpMaW5lIFdpZHRoXxAWU3RhcnQg
|
||||||
|
QXJyb3cgRmlsbCBDb2xvcl8QElN0cm9rZSBTdGFydCBBcnJvd15MaW5lIENhcCBTdHls
|
||||||
|
ZVxTdHJva2UgQ29sb3JbVGV4dCBPZmZzZXRfEBFTdGFydCBBcnJvdyBTdHlsZV1QYXR0
|
||||||
|
ZXJuIEltYWdlXkZpbGwgRW5kIEFycm93W1N0cm9rZSBNb2RlW1N0cm9rZSBUZXh0XxAQ
|
||||||
|
U3Ryb2tlIEVuZCBBcnJvdwkIgCEIIkCAAACAIQmAIhACgCAiQAAAAIAACYAACdMAYQBi
|
||||||
|
AAsAVgDUAGRPECcwLjgyMTM5NzY2MjIgMC44MDAwOTU2Nzc0IDAuODY0NDcwMTI0MgCA
|
||||||
|
CtMAYQALANcA2ABkANpXTlNXaGl0ZRADgApCMQDSAGYAZwDcAN6iAN0AalhEQlN0cm9r
|
||||||
|
ZVhEQlN0cm9rZdYA4ADhAAsA4gDjAOQA5QDmAOcAxQDpAOpcTlNTaGFkb3dWZXJ0XU5T
|
||||||
|
U2hhZG93Q29sb3JXRW5hYmxlZF1OU1NoYWRvd0hvcml6XxASTlNTaGFkb3dCbHVyUmFk
|
||||||
|
aXVzI8AkAAAAAAAAgCSAJQgjQCQAAAAAAAAjQAAAAAAAAADTAGEACwDXANgAZADtgApP
|
||||||
|
EA8wIDAuMzAwMDAwMDExOQDSAGYAZwDvAPKjAPAA8QBqWERCU2hhZG93WE5TU2hhZG93
|
||||||
|
WERCU2hhZG93XxAWezU3LjYwMDAwNiwgMzkuOTk5OTkyfV8QFnsyMDIuNzk5OTcsIDM5
|
||||||
|
Ljk5OTk5Mn1fEBZ7MjAyLjc5OTk3LCAyMjcuMTk5OTd9XxAWezU3LjYwMDAwNiwgMjI3
|
||||||
|
LjE5OTk3fV8QFnsyMDIuNzk5OTcsIDM5Ljk5OTk5Mn3SAGYAZwD5APyjAPoA+wBqW0RC
|
||||||
|
UmVjdGFuZ2xlV0RCU2hhcGVbREJSZWN0YW5nbGXYAC8A/gD/AQAAMAALADUANwEBAMwA
|
||||||
|
xQEDAQQBBQEGAQdbUG9pbnQgY291bnRaQ2xvc2UgUGF0aFZQb2ludHOALgiAMiIAAAAA
|
||||||
|
gDeAMIAt0gALAAwADQEKgB6g3xAUALEAsgCzALQAtQC2ALcAuAC5ALoACwC7ALwAvQC+
|
||||||
|
AL8AwADBAMIAwwAaAMUAHwAfAMYAxQAfARAAxgAaAMsAHwEUARUAHwAbABoAHwAbABoJ
|
||||||
|
CIAhCCJAAAAAgCEJgCKALyJAAAAAgAAJgAAJ0wBhAAsA1wDYAGQBHIAKQjAA1gDgAOEA
|
||||||
|
CwDiAOMA5AEeAR8A5wDFASIBIyPAJAAAAAAAAIAxgCUII0AkAAAAAAAAI0AAAAAAAAAA
|
||||||
|
0wBhAAsA1wDYAGQBJoAKTxAPMCAwLjMwMDAwMDAxMTkA0gALAAwADQEpgB6mASoBKwEs
|
||||||
|
AS0BKwErgDOANIA1gDaANIA0WHs3MiwgNzJ9CAlfEA97MTg3LjE5OTk4LCA3Mn3SAGYA
|
||||||
|
ZwE1ATijATYBNwBqWkRCUG9seWxpbmVXREJTaGFwZVpEQlBvbHlsaW5l2AAvAP4A/wEA
|
||||||
|
ADAACwA1ADcBOgDMAMUBPAE9AQUBPwFAgDoIgD0iAAAAAIA3gDuAOdIACwAMAA0BQ4Ae
|
||||||
|
oN8QFACxALIAswC0ALUAtgC3ALgAuQC6AAsAuwC8AL0AvgC/AMAAwQDCAMMAGgDFAB8A
|
||||||
|
HwDGAMUAHwFJAMYAGgDLAB8BFAFOAB8AGwAaAB8AGwAaCQiAIQgiQAAAAIAhCYAigC8i
|
||||||
|
QAAAAIAACYAACdYA4ADhAAsA4gDjAOQBVAFVAOcAxQFYAVkjwCQAAAAAAACAPIAlCCNA
|
||||||
|
JAAAAAAAACNAAAAAAAAAANMAYQALANcA2ABkAVyACk8QDzAgMC4zMDAwMDAwMTE5ANIA
|
||||||
|
CwAMAA0BX4AepgFgASsBLAFjASsBK4A+gDSANYA/gDSANF8QD3s3MiwgMTAwLjc5OTk5
|
||||||
|
fV8QFnsxODcuMTk5OTgsIDEwMC43OTk5OX3YAC8A/gD/AQAAMAALADUANwFpAMwAxQFr
|
||||||
|
AWwBBQFuAW+AQgiARSIAAAAAgDeAQ4BB0gALAAwADQFygB6g3xAUALEAsgCzALQAtQC2
|
||||||
|
ALcAuAC5ALoACwC7ALwAvQC+AL8AwADBAMIAwwAaAMUAHwAfAMYAxQAfAXgAxgAaAMsA
|
||||||
|
HwEUAX0AHwAbABoAHwAbABoJCIAhCCJAAAAAgCEJgCKALyJAAAAAgAAJgAAJ1gDgAOEA
|
||||||
|
CwDiAOMA5AGDAYQA5wDFAYcBiCPAJAAAAAAAAIBEgCUII0AkAAAAAAAAI0AAAAAAAAAA
|
||||||
|
0wBhAAsA1wDYAGQBi4AKTxAPMCAwLjMwMDAwMDAxMTkA0gALAAwADQGOgB6mAY8BKwEs
|
||||||
|
AZIBKwErgEaANIA1gEeANIA0XxAPezcyLCAxMjkuNjAwMDF9XxAWezE4Ny4xOTk5OCwg
|
||||||
|
MTI5LjYwMDAxfdgALwD+AP8BAAAwAAsANQA3AZgAzADFAZoBmwEFAZ0BnoBKCIBNIgAA
|
||||||
|
AACAN4BLgEnSAAsADAANAaGAHqDfEBQAsQCyALMAtAC1ALYAtwC4ALkAugALALsAvAC9
|
||||||
|
AL4AvwDAAMEAwgDDABoAxQAfAB8AxgDFAB8BpwDGABoAywAfARQBrAAfABsAGgAfABsA
|
||||||
|
GgkIgCEIIkAAAACAIQmAIoAvIkAAAACAAAmAAAnWAOAA4QALAOIA4wDkAbIBswDnAMUB
|
||||||
|
tgG3I8AkAAAAAAAAgEyAJQgjQCQAAAAAAAAjQAAAAAAAAADTAGEACwDXANgAZAG6gApP
|
||||||
|
EA8wIDAuMzAwMDAwMDExOQDSAAsADAANAb2AHqYBvgErASwBwQErASuAToA0gDWAT4A0
|
||||||
|
gDRfEA97NzIsIDE1OC4zOTk5OX1fEBZ7MTg3LjE5OTk4LCAxNTguMzk5OTl92QAvADAA
|
||||||
|
MQAyAAsAMwA0ADUANwHHAcgByQHKAcsBzAHNAc4Bz4BSIgAAAACAWIBVgFmAVoBXgFOA
|
||||||
|
UdIACwAMAA0B0oAeoN8QFACxALIAswC0ALUAtgC3ALgAuQC6AAsAuwC8AL0AvgC/AMAA
|
||||||
|
wQDCAMMAGgDFAB8AHwDGAMUAHwHYAMYAGgDLAB8BFAHdAB8AGwAaAB8AGwAaCQiAIQgi
|
||||||
|
QAAAAIAhCYAigC8iQAAAAIAACYAACdYA4ADhAAsA4gDjAOQB4wHkAOcAxQHnAegjwCQA
|
||||||
|
AAAAAACAVIAlCCNAJAAAAAAAACNAAAAAAAAAANMAYQALANcA2ABkAeuACk8QDzAgMC4z
|
||||||
|
MDAwMDAwMTE5AF8QEHsxNDQsIDE3Mi43OTk5OX1fEBJ7MTg3LjIsIDE3Mi43OTk5OX1c
|
||||||
|
ezE4Ny4yLCAyMTZ9WnsxNDQsIDIxNn3SAGYAZwHxAfSjAfIB8wBqVkRCT3ZhbFdEQlNo
|
||||||
|
YXBlVkRCT3ZhbNgALwD+AP8BAAAwAAsANQA3AfYAzADFAfgB+QEFAfsB/IBcCIBfIgAA
|
||||||
|
AACAN4BdgFvSAAsADAANAf+AHqDfEBQAsQCyALMAtAC1ALYAtwC4ALkAugALALsAvAC9
|
||||||
|
AL4AvwDAAMEAwgDDABoAxQAfAB8AxgDFAB8CBQDGABoAywAfARQCCgAfABsAGgAfABsA
|
||||||
|
GgkIgCEIIkAAAACAIQmAIoAvIkAAAACAAAmAAAnWAOAA4QALAOIA4wDkAhACEQDnAMUC
|
||||||
|
FAIVI8AkAAAAAAAAgF6AJQgjQCQAAAAAAAAjQAAAAAAAAADTAGEACwDXANgAZAIYgApP
|
||||||
|
EA8wIDAuMzAwMDAwMDExOQDSAAsADAANAhuAHqYCHAErASwCHwErASuAYIA0gDWAYYA0
|
||||||
|
gDRfEBV7MTY1LjE1NDMsIDE5My4wMDM5MX1fEBV7MTY1LjE1NDMsIDE3NS44MjQyMn3Y
|
||||||
|
AC8A/gD/AQAAMAALADUANwIlAMwAxQInAigBBQIqAiuAZAiAZyIAAAAAgDeAZYBj0gAL
|
||||||
|
AAwADQIugB6g3xAUALEAsgCzALQAtQC2ALcAuAC5ALoACwC7ALwAvQC+AL8AwADBAMIA
|
||||||
|
wwAaAMUAHwAfAMYAxQAfAjQAxgAaAMsAHwEUAjkAHwAbABoAHwAbABoJCIAhCCJAAAAA
|
||||||
|
gCEJgCKALyJAAAAAgAAJgAAJ1gDgAOEACwDiAOMA5AI/AkAA5wDFAkMCRCPAJAAAAAAA
|
||||||
|
AIBmgCUII0AkAAAAAAAAI0AAAAAAAAAA0wBhAAsA1wDYAGQCR4AKTxAPMCAwLjMwMDAw
|
||||||
|
MDAxMTkA0gALAAwADQJKgB6mAksBKwEsAk4BKwErgGiANIA1gGmANIA0XxAWezE2NS40
|
||||||
|
MzU1NSwgMTk0LjQ1MTE3fV8QFXsxNDYuODkyNTgsIDE5NC43MzczfVZ7MCwgMH3SAGYA
|
||||||
|
ZwJVAleiAlYAaldEQkxheWVyV0RCTGF5ZXISAAGGoF8QD05TS2V5ZWRBcmNoaXZlcgAI
|
||||||
|
ABkAHgAnADAAOgA/AEQARgEhAScBMAE3AUIBRAFHAUkBbgF0AXwBjwGYAaMBrgG1AdEB
|
||||||
|
1gHXAdkB2gHcAd4B4AHiAeQB7wH4AfoCCwINAg8CEQITAhUCFwIZAhsCRAJLAlQCYQJt
|
||||||
|
AnoChgKNApgCngKgAqUCpwKpAqsCrQKvArECswK1Ar4CwALDAsUC+gMJAxcDIgMsAz4D
|
||||||
|
RwNRA2MDbgN8A4cDlAOWA5sDnQOfA6EDowOlA6oDrAOuA7ADsgO3A8QD0QPXA94D4APp
|
||||||
|
A/ID/QQCBAoEEwQgBDcETgRQBFIEVARdBF8EZARmBGgEdQR9BIIEhASGBIgEjQSPBJEE
|
||||||
|
owS4BMkE3gTgBOIE6QTyBPcE+QUCBQcFFAUhBSYFLwU2BUwFWQVmBWsFbQVvBXEFdgV4
|
||||||
|
BXoFiwWNBY8FlgWbBaQFqQWxBboFvwXKBdUF9QYCBg8GGAYdBiQGKwY0BjsGSgadBrAG
|
||||||
|
wwbOBuAG9wcGBxgHIwc8B1EHYAdtB3kHjQebB6oHtgfCB9UH1gfXB9kH2gffB+EH4gfk
|
||||||
|
B+YH6AftB+8H8AfyB/MIAAgqCCwIOQhBCEMIRQhICFEIVghfCGgIgQiOCJwIpAiyCMcI
|
||||||
|
0AjSCNQI1QjeCOcI9Aj2CQgJEQkYCSEJKgkzCUwJZQl+CZcJsAm5CcAJzAnUCeAKAQoN
|
||||||
|
ChgKHwohCiIKJAopCisKLQovCjgKOgo7Co4KjwqQCpIKkwqYCpoKmwqdCp8KpAqmCqcK
|
||||||
|
qQqqCrcKuQq8CtUK3grgCuIK4wrsCvULAgsECxYLHwshCy4LMAsyCzQLNgs4CzoLQwtE
|
||||||
|
C0ULVwtgC2cLcgt6C4ULpguoC6kLqwuwC7ILtAu2C78LwQvCDBUMFgwXDBkMGgwfDCEM
|
||||||
|
IgwkDCYMKwwtDC4MMAwxDEoMUwxVDFcMWAxhDGoMdwx5DIsMlAyWDKMMpQynDKkMqwyt
|
||||||
|
DK8MwQzaDPsM/Qz+DQANBQ0HDQkNCw0UDRYNFw1qDWsNbA1uDW8NdA12DXcNeQ17DYAN
|
||||||
|
gg2DDYUNhg2fDagNqg2sDa0Ntg2/DcwNzg3gDekN6w34DfoN/A3+DgAOAg4EDhYOLw5Q
|
||||||
|
DlIOUw5VDloOXA5eDmAOaQ5rDmwOvw7ADsEOww7EDskOyw7MDs4O0A7VDtcO2A7aDtsO
|
||||||
|
9A79Dv8PAQ8CDwsPFA8hDyMPNQ8+D0APTQ9PD1EPUw9VD1cPWQ9rD4QPqQ+rD7APsg+0
|
||||||
|
D7YPuA+6D7wPvg/HD8kPyhAdEB4QHxAhECIQJxApECoQLBAuEDMQNRA2EDgQORBSEFsQ
|
||||||
|
XRBfEGAQaRByEH8QgRCTEKYQuxDIENMQ3BDjEOoQ8hD5ERoRHBEdER8RJBEmESgRKhEz
|
||||||
|
ETURNhGJEYoRixGNEY4RkxGVEZYRmBGaEZ8RoRGiEaQRpRG+EccRyRHLEcwR1RHeEesR
|
||||||
|
7RH/EggSChIXEhkSGxIdEh8SIRIjEjsSUxJ0EnYSdxJ5En4SgBKCEoQSjRKPEpAS4xLk
|
||||||
|
EuUS5xLoEu0S7xLwEvIS9BL5EvsS/BL+Ev8TGBMhEyMTJRMmEy8TOBNFE0cTWRNiE2QT
|
||||||
|
cRNzE3UTdxN5E3sTfROWE64TtRO+E8MTyxPTE9gAAAAAAAACAgAAAAAAAAJaAAAAAAAA
|
||||||
|
AAAAAAAAAAAT6g==
|
||||||
|
</data>
|
||||||
|
<key>Show Grid</key>
|
||||||
|
<true/>
|
||||||
|
<key>Show Rulers</key>
|
||||||
|
<true/>
|
||||||
|
<key>Snap to grid</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
client/icons/applications-development.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
client/icons/appointment-new.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
15
client/icons/buildicon-osx.sh
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
APPICON=appicon-osx.png
|
||||||
|
ICONSET=appicon-osx.iconset
|
||||||
|
mkdir $ICONSET
|
||||||
|
sips -z 16 16 $APPICON --out $ICONSET/icon_16x16.png
|
||||||
|
sips -z 32 32 $APPICON --out $ICONSET/icon_16x16@2x.png
|
||||||
|
sips -z 32 32 $APPICON --out $ICONSET/icon_32x32.png
|
||||||
|
sips -z 64 64 $APPICON --out $ICONSET/icon_32x32@2x.png
|
||||||
|
sips -z 128 128 $APPICON --out $ICONSET/icon_128x128.png
|
||||||
|
sips -z 256 256 $APPICON --out $ICONSET/icon_128x128@2x.png
|
||||||
|
sips -z 256 256 $APPICON --out $ICONSET/icon_256x256.png
|
||||||
|
sips -z 512 512 $APPICON --out $ICONSET/icon_256x256@2x.png
|
||||||
|
sips -z 512 512 $APPICON --out $ICONSET/icon_512x512.png
|
||||||
|
cp $APPICON $ICONSET/icon_512x512@2x.png
|
||||||
|
iconutil -c icns appicon-osx.iconset
|
||||||
|
# rm -R $ICONSET
|
||||||
BIN
client/icons/clock-128x128.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
client/icons/clock-32x32.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
client/icons/clock-64x64.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
client/icons/dialog-error.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
client/icons/document-new.png
Normal file
|
After Width: | Height: | Size: 1008 B |
BIN
client/icons/document-print.png
Normal file
|
After Width: | Height: | Size: 1013 B |
BIN
client/icons/document-save.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
client/icons/edit-clear.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
client/icons/edit-copy.png
Normal file
|
After Width: | Height: | Size: 723 B |
BIN
client/icons/edit-cut.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
client/icons/edit-delete.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
client/icons/edit-find-replace.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
client/icons/edit-find.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
client/icons/edit-paste.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
client/icons/edit-redo.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
client/icons/edit-select-all.png
Normal file
|
After Width: | Height: | Size: 631 B |
BIN
client/icons/edit-undo.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
client/icons/emblem-system.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
client/icons/empty.png
Normal file
|
After Width: | Height: | Size: 83 B |
BIN
client/icons/folder-new.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
client/icons/folder-open.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
client/icons/folder-saved-search.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
client/icons/folder.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
client/icons/format-indent-less.png
Normal file
|
After Width: | Height: | Size: 767 B |
BIN
client/icons/format-indent-more.png
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
client/icons/format-justify-center.png
Normal file
|
After Width: | Height: | Size: 526 B |
BIN
client/icons/format-justify-fill.png
Normal file
|
After Width: | Height: | Size: 517 B |