1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge branch 'master' into chatlog_v3_1

This commit is contained in:
krepa098 2015-02-07 18:49:06 +01:00
commit 5506379a97
107 changed files with 4125 additions and 1389 deletions

View File

@ -7,14 +7,14 @@
<a name="dependencies" />
##Dependencies
| Name | Version | Modules |
|--------------|-------------|------------------------------------- |
| Qt | >= 5.2.0 | core, gui, network, widget, xml, sql |
| GCC/MinGW | >= 4.8 | C++11 enabled |
| Tox Core | most recent | core, av |
| OpenCV | >= 2.4.9 | core, highgui, imgproc |
| OpenAL Soft | >= 1.16.0 | |
| filter_audio | most recent | |
| Name | Version | Modules |
|--------------|-------------|------------------------------------------ |
| Qt | >= 5.2.0 | core, gui, network, sql, svg, widget, xml |
| GCC/MinGW | >= 4.8 | C++11 enabled |
| Tox Core | most recent | core, av |
| OpenCV | >= 2.4.9 | core, highgui, imgproc |
| OpenAL Soft | >= 1.16.0 | |
| filter_audio | most recent | |
<a name="linux" />

View File

@ -29,7 +29,7 @@ This client runs on Windows, Linux and Mac natively.<br/>
<h3>Screenshots</h3>
<h5>Note: The screenshots may not always be up to date, but they should give a good idea of the general look and features</h5>
<img src="https://wiki.tox.im/images/5/5d/ToxGUI_tux3.png"/>
<img src="https://i.imgur.com/5wSLGSX.png"/>
<img src="https://i.imgur.com/0XWcqBc.jpg"/>
##Documentation:
@ -38,6 +38,7 @@ This client runs on Windows, Linux and Mac natively.<br/>
##Developer overview:
[Contributing](https://github.com/tux3/qTox/wiki#contributing)<br/>
[GitStats](https://tux3-dev.tox.im/)<br/>
[Mac & Linux jenkins](https://jenkins.libtoxcore.so/user/tux3/my-views/view/qTox/)<br/>
[Windows jenkins](https://tux3-dev.tox.im/jenkins)<br/>

View File

@ -0,0 +1,56 @@
<?xml version="1.0"?>
<manifest package="im.tox.qtox" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="qtox">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="qTox" android:screenOrientation="unspecified" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="qtox"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!--
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/>
-->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
"applicationStateChanged(Qt::ApplicationSuspended)"
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
</activity>
</application>
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="20"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS -->
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<!-- %%INSERT_FEATURES -->
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 B

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 B

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 878 B

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 947 B

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 791 B

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 825 B

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 B

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 B

After

Width:  |  Height:  |  Size: 351 B

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16.1 16" enable-background="new 0 0 16.1 16" xml:space="preserve">
<path fill="#252325" d="M8,0C6.1,0,4.2,1.3,4.2,3.9v1.9h-1c-1.1,0-1.8,0.8-1.8,1.9v6.5c0,1.1,0.7,1.9,1.9,1.9l9.6,0
c1.1,0,1.9-0.8,1.9-1.9V7.6c0-1.1-0.8-1.9-1.9-1.9h-0.9l0-1.9C11.9,1.3,10,0,8,0z M8,1.6c1.3,0,2.2,1,2.2,2.2C10.3,5,9.8,6.2,8,6.9
c0.3-0.5,0.4-0.7,0.5-1.2C6.7,5.9,5.8,4.9,5.8,3.8S6.7,1.6,8,1.6z"/>
<path fill="#CDBE41" d="M8,7.6c-1.8,0-3.3,1.5-3.3,3.3c0,1.8,1.5,3.3,3.3,3.3s3.3-1.5,3.3-3.3C11.3,9.1,9.8,7.6,8,7.6z M5.6,11
c0-1.3,1.1-2.4,2.4-2.4s2.4,1.1,2.4,2.4H5.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 929 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="100%"
height="100%"
viewBox="0 0 16.1 16"
id="Layer_1"
xml:space="preserve">
<metadata
id="metadata19">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs17" />
<path
d="M 8,0 C 6.1,0 4.2,1.3 4.2,3.9 v 1.9 h -1 C 2.1,5.8 1.4,6.6 1.4,7.7 v 6.5 c 0,1.1 0.7,1.9 1.9,1.9 l 9.6,0 c 1.1,0 1.9,-0.8 1.9,-1.9 V 7.6 C 14.8,6.5 14,5.7 12.9,5.7 H 12 L 12,3.8 C 11.9,1.3 10,0 8,0 z m 0,1.6 c 1.3,0 2.2,1 2.2,2.2 C 10.3,5 9.8,6.2 8,6.9 8.3,6.4 8.4,6.2 8.5,5.7 6.7,5.9 5.8,4.9 5.8,3.8 5.8,2.7 6.7,1.6 8,1.6 z"
id="path3"
style="fill:#252325" />
<path
d="m 7.9927197,7.71875 c -1.8159871,0 -3.28125,1.4652629 -3.28125,3.28125 0,1.815987 1.4652629,3.28125 3.28125,3.28125 1.8159871,0 3.3125003,-1.465263 3.3125003,-3.28125 0,-1.8159871 -1.4965132,-3.28125 -3.3125003,-3.28125 z m 0,0.90625 c 1.3198669,0 2.4062503,1.0551331 2.4062503,2.375 0,1.319867 -1.0863834,2.375 -2.4062503,2.375 -1.3198669,0 -2.375,-1.055133 -2.375,-2.375 0,-1.3198669 1.0551331,-2.375 2.375,-2.375 z"
id="path3000"
style="fill:#c94f50;fill-opacity:1" />
<path
d="m -6.5762711,11.932203 a 5.3220339,6.237288 0 1 1 -10.6440679,0 5.3220339,6.237288 0 1 1 10.6440679,0 z"
id="path2996"
style="fill:#ffffff;fill-opacity:1" />
<rect
width="1.1830783"
height="5.3462563"
rx="0"
ry="0.89710045"
x="12.866886"
y="-0.54217148"
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
id="rect3937"
style="fill:#c94f50;fill-opacity:1;fill-rule:nonzero" />
<g
transform="translate(-12.523729,-0.08389831)"
id="g3893">
<g
id="g3877">
<g
id="g3879">
<path
d="m 5.6,8.7 c -1.3,1.3 -1.3,3.4 0,4.7 1.3,1.3 3.4,1.3 4.7,0 1.3,-1.3 1.3,-3.4 0,-4.7 C 9,7.4 6.9,7.4 5.6,8.7 z m 4.1,4 c -0.9,0.9 -2.4,0.9 -3.4,0 -0.5,-0.5 -0.7,-1.1 -0.7,-1.8 0,0 0,0 0,0 0,0 0,-0.1 0,-0.1 0,0 0,0 0,0 0,0 0,-0.1 0,-0.1 0,0 0,0 0,0 0,-0.1 0,-0.2 0.1,-0.4 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,-0.1 0,0 0,0 0,-0.1 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0.1,-0.1 0.1,-0.1 0,0 0,0 0,0 l 0,0 0,0 c 0,0 0.1,-0.1 0.1,-0.1 0,0 0,0 0,0 0,0 0,0 0.1,-0.1 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0.1,0 0.1,-0.1 0,0 0,0 0,0 0,0 0.1,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0.1,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0.1,0 0.1,0 0.2,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0.1,0 0.2,0.1 0.2,0.1 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 l 0,0 c 0.2,0.1 0.3,0.2 0.4,0.3 1.3,1.1 1.3,2.6 0.4,3.5 z"
id="path3881"
style="fill:#ffffff" />
<g
id="g3883">
<polyline
id="polyline3885"
points="7.5,8.5 7.5,13.6 8.5,13.6 8.5,8.5 "
style="fill:#ffffff" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 716 B

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16.1 16" enable-background="new 0 0 16.1 16" xml:space="preserve">
<path fill="#252325" d="M8,0C6.1,0,4.2,1.3,4.2,3.9v1.9h-1c-1.1,0-1.8,0.8-1.8,1.9v6.5c0,1.1,0.7,1.9,1.9,1.9l9.6,0
c1.1,0,1.9-0.8,1.9-1.9V7.6c0-1.1-0.8-1.9-1.9-1.9h-0.9l0-1.9C11.9,1.3,10,0,8,0z M8,1.6c1.3,0,2.2,1,2.2,2.2C10.3,5,9.8,6.2,8,6.9
c0.3-0.5,0.4-0.7,0.5-1.2C6.7,5.9,5.8,4.9,5.8,3.8S6.7,1.6,8,1.6z"/>
<path fill="#ff9400" d="M11.4,11c0,1.9-1.5,3.4-3.4,3.4c-1.9,0-3.4-1.5-3.4-3.4c0-1.9,1.5-3.4,3.4-3.4C9.9,7.6,11.4,9.1,11.4,11"/>
</svg>

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 478 B

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="100%"
height="100%"
viewBox="0 0 16.1 16"
id="Layer_1"
xml:space="preserve">
<metadata
id="metadata19">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs17" />
<path
d="M 8,0 C 6.1,0 4.2,1.3 4.2,3.9 v 1.9 h -1 C 2.1,5.8 1.4,6.6 1.4,7.7 v 6.5 c 0,1.1 0.7,1.9 1.9,1.9 l 9.6,0 c 1.1,0 1.9,-0.8 1.9,-1.9 V 7.6 C 14.8,6.5 14,5.7 12.9,5.7 H 12 L 12,3.8 C 11.9,1.3 10,0 8,0 z m 0,1.6 c 1.3,0 2.2,1 2.2,2.2 C 10.3,5 9.8,6.2 8,6.9 8.3,6.4 8.4,6.2 8.5,5.7 6.7,5.9 5.8,4.9 5.8,3.8 5.8,2.7 6.7,1.6 8,1.6 z"
id="path3"
style="fill:#252325" />
<path
d="m 7.9927197,7.71875 c -1.8159871,0 -3.28125,1.4652629 -3.28125,3.28125 0,1.815987 1.4652629,3.28125 3.28125,3.28125 1.8159871,0 3.3125003,-1.465263 3.3125003,-3.28125 0,-1.8159871 -1.4965132,-3.28125 -3.3125003,-3.28125 z m 0,0.90625 c 1.3198669,0 2.4062503,1.0551331 2.4062503,2.375 0,1.319867 -1.0863834,2.375 -2.4062503,2.375 -1.3198669,0 -2.375,-1.055133 -2.375,-2.375 0,-1.3198669 1.0551331,-2.375 2.375,-2.375 z"
id="path3000"
style="fill:#dedede;fill-opacity:1" />
<path
d="m -6.5762711,11.932203 a 5.3220339,6.237288 0 1 1 -10.6440679,0 5.3220339,6.237288 0 1 1 10.6440679,0 z"
id="path2996"
style="fill:#ffffff;fill-opacity:1" />
<rect
width="1.1830783"
height="5.3462563"
rx="0"
ry="0.89710045"
x="7.4181905"
y="8.3502293"
id="rect3937"
style="fill:#dedede;fill-opacity:1;fill-rule:nonzero" />
<g
transform="translate(-12.523729,-0.08389831)"
id="g3893">
<g
id="g3877">
<g
id="g3879">
<path
d="m 5.6,8.7 c -1.3,1.3 -1.3,3.4 0,4.7 1.3,1.3 3.4,1.3 4.7,0 1.3,-1.3 1.3,-3.4 0,-4.7 C 9,7.4 6.9,7.4 5.6,8.7 z m 4.1,4 c -0.9,0.9 -2.4,0.9 -3.4,0 -0.5,-0.5 -0.7,-1.1 -0.7,-1.8 0,0 0,0 0,0 0,0 0,-0.1 0,-0.1 0,0 0,0 0,0 0,0 0,-0.1 0,-0.1 0,0 0,0 0,0 0,-0.1 0,-0.2 0.1,-0.4 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,-0.1 0,0 0,0 0,-0.1 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0.1,-0.1 0.1,-0.1 0,0 0,0 0,0 l 0,0 0,0 c 0,0 0.1,-0.1 0.1,-0.1 0,0 0,0 0,0 0,0 0,0 0.1,-0.1 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0.1,0 0.1,-0.1 0,0 0,0 0,0 0,0 0.1,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0.1,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0.1,0 0.1,0 0.2,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0.1,0 0.2,0.1 0.2,0.1 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 l 0,0 c 0.2,0.1 0.3,0.2 0.4,0.3 1.3,1.1 1.3,2.6 0.4,3.5 z"
id="path3881"
style="fill:#ffffff" />
<g
id="g3883">
<polyline
id="polyline3885"
points="7.5,8.5 7.5,13.6 8.5,13.6 8.5,8.5 "
style="fill:#ffffff" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 B

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16.1 16" enable-background="new 0 0 16.1 16" xml:space="preserve">
<path fill="#252325" d="M8,0C6.1,0,4.2,1.3,4.2,3.9v1.9h-1c-1.1,0-1.8,0.8-1.8,1.9v6.5c0,1.1,0.7,1.9,1.9,1.9l9.6,0
c1.1,0,1.9-0.8,1.9-1.9V7.6c0-1.1-0.8-1.9-1.9-1.9h-0.9l0-1.9C11.9,1.3,10,0,8,0z M8,1.6c1.3,0,2.2,1,2.2,2.2C10.3,5,9.8,6.2,8,6.9
c0.3-0.5,0.4-0.7,0.5-1.2C6.7,5.9,5.8,4.9,5.8,3.8S6.7,1.6,8,1.6z"/>
<g>
<path fill="#C94F50" d="M8,7.7c-1.8,0-3.3,1.5-3.3,3.3c0,1.8,1.5,3.3,3.3,3.3c1.8,0,3.3-1.5,3.3-3.3C11.3,9.2,9.8,7.7,8,7.7z
M8,13.4c-1.3,0-2.4-1.1-2.4-2.4c0-0.7,0.3-1.3,0.7-1.7c0,0,0,0,0,0c0,0,0,0,0.1-0.1c0,0,0,0,0,0c0,0,0,0,0.1-0.1c0,0,0,0,0,0
C6.6,9.1,6.7,9,6.8,8.9c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0
c0,0,0,0,0.1,0c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0
c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0l0,0l0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0
c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0.1,0,0.1,0c0,0,0,0,0,0
c0,0,0.1,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0.1,0
c0,0,0,0,0,0c0,0,0.1,0,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0
c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0,0,0,0c0,0,0.1,0.1,0.1,0.1c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0
c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0.1,0.1,0.2,0.1,0.2c0,0,0,0,0,0c0,0,0,0,0,0.1
c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0c0,0,0,0,0,0.1c0,0,0,0,0,0l0,0c0,0.2,0.1,0.4,0.1,0.6C10.4,12.3,9.3,13.4,8,13.4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 B

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16.1 16" enable-background="new 0 0 16.1 16" xml:space="preserve">
<path fill="#252325" d="M8,0C6.1,0,4.2,1.3,4.2,3.9v1.9h-1c-1.1,0-1.8,0.8-1.8,1.9v6.5c0,1.1,0.7,1.9,1.9,1.9l9.6,0
c1.1,0,1.9-0.8,1.9-1.9V7.6c0-1.1-0.8-1.9-1.9-1.9h-0.9l0-1.9C11.9,1.3,10,0,8,0z M8,1.6c1.3,0,2.2,1,2.2,2.2C10.3,5,9.8,6.2,8,6.9
c0.3-0.5,0.4-0.7,0.5-1.2C6.7,5.9,5.8,4.9,5.8,3.8S6.7,1.6,8,1.6z"/>
<path fill="#6FC062" d="M11.4,11c0,1.9-1.5,3.4-3.4,3.4c-1.9,0-3.4-1.5-3.4-3.4c0-1.9,1.5-3.4,3.4-3.4C9.9,7.6,11.4,9.1,11.4,11"/>
</svg>

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 B

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M8,0C6.1,0,4.1,1.3,4.1,3.9v1.9h-1C2,5.7,1.3,6.5,1.3,7.6v6.5c0,1.1,0.7,1.9,1.9,1.9l9.6,0
c1.1,0,1.9-0.8,1.9-1.9V7.6c0-1.1-0.8-1.9-1.9-1.9h-0.9l0-1.9C11.9,1.3,9.9,0,8,0z M8,1.6c1.3,0,2.2,1,2.2,2.2
C10.2,5,9.7,6.2,8,6.9c0.3-0.5,0.4-0.7,0.5-1.2C6.7,5.9,5.8,4.9,5.8,3.8S6.7,1.6,8,1.6z"/>
<path fill="#CDBE41" d="M7.9,7.6c-1.8,0-3.3,1.5-3.3,3.3c0,1.8,1.5,3.3,3.3,3.3s3.3-1.5,3.3-3.3C11.3,9.1,9.8,7.6,7.9,7.6z M5.6,11
c0-1.3,1.1-2.4,2.4-2.4s2.4,1.1,2.4,2.4H5.6z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 B

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="100%"
height="100%"
viewBox="0 0 16.1 16"
id="Layer_1"
xml:space="preserve">
<metadata
id="metadata19">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs17" />
<path
d="M 8,0 C 6.1,0 4.2,1.3 4.2,3.9 v 1.9 h -1 C 2.1,5.8 1.4,6.6 1.4,7.7 v 6.5 c 0,1.1 0.7,1.9 1.9,1.9 l 9.6,0 c 1.1,0 1.9,-0.8 1.9,-1.9 V 7.6 C 14.8,6.5 14,5.7 12.9,5.7 H 12 L 12,3.8 C 11.9,1.3 10,0 8,0 z m 0,1.6 c 1.3,0 2.2,1 2.2,2.2 C 10.3,5 9.8,6.2 8,6.9 8.3,6.4 8.4,6.2 8.5,5.7 6.7,5.9 5.8,4.9 5.8,3.8 5.8,2.7 6.7,1.6 8,1.6 z"
id="path3"
style="fill:#ffffff" />
<path
d="m 7.9927197,7.71875 c -1.8159871,0 -3.28125,1.4652629 -3.28125,3.28125 0,1.815987 1.4652629,3.28125 3.28125,3.28125 1.8159871,0 3.3125003,-1.465263 3.3125003,-3.28125 0,-1.8159871 -1.4965132,-3.28125 -3.3125003,-3.28125 z m 0,0.90625 c 1.3198669,0 2.4062503,1.0551331 2.4062503,2.375 0,1.319867 -1.0863834,2.375 -2.4062503,2.375 -1.3198669,0 -2.375,-1.055133 -2.375,-2.375 0,-1.3198669 1.0551331,-2.375 2.375,-2.375 z"
id="path3000"
style="fill:#c94f50;fill-opacity:1" />
<path
d="m -6.5762711,11.932203 a 5.3220339,6.237288 0 1 1 -10.6440679,0 5.3220339,6.237288 0 1 1 10.6440679,0 z"
id="path2996"
style="fill:#ffffff;fill-opacity:1" />
<rect
width="1.1830783"
height="5.3462563"
rx="0"
ry="0.89710045"
x="12.866886"
y="-0.54217148"
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
id="rect3937"
style="fill:#c94f50;fill-opacity:1;fill-rule:nonzero" />
<g
transform="translate(-12.523729,-0.08389831)"
id="g3893">
<g
id="g3877">
<g
id="g3879">
<path
d="m 5.6,8.7 c -1.3,1.3 -1.3,3.4 0,4.7 1.3,1.3 3.4,1.3 4.7,0 1.3,-1.3 1.3,-3.4 0,-4.7 C 9,7.4 6.9,7.4 5.6,8.7 z m 4.1,4 c -0.9,0.9 -2.4,0.9 -3.4,0 -0.5,-0.5 -0.7,-1.1 -0.7,-1.8 0,0 0,0 0,0 0,0 0,-0.1 0,-0.1 0,0 0,0 0,0 0,0 0,-0.1 0,-0.1 0,0 0,0 0,0 0,-0.1 0,-0.2 0.1,-0.4 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,-0.1 0,0 0,0 0,-0.1 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0,0 0,-0.1 0,0 0,0 0,0 0,0 0.1,-0.1 0.1,-0.1 0,0 0,0 0,0 l 0,0 0,0 c 0,0 0.1,-0.1 0.1,-0.1 0,0 0,0 0,0 0,0 0,0 0.1,-0.1 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0.1,0 0.1,-0.1 0,0 0,0 0,0 0,0 0.1,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0.1,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0.1,0 0.1,0 0.2,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0.1,0 0.2,0.1 0.2,0.1 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 0,0 0,0 0.1,0 0,0 0,0 0,0 l 0,0 c 0.2,0.1 0.3,0.2 0.4,0.3 1.3,1.1 1.3,2.6 0.4,3.5 z"
id="path3881"
style="fill:#ffffff" />
<g
id="g3883">
<polyline
id="polyline3885"
points="7.5,8.5 7.5,13.6 8.5,13.6 8.5,8.5 "
style="fill:#ffffff" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M8,0C6.1,0,4.1,1.3,4.1,3.9v1.9h-1C2,5.7,1.3,6.5,1.3,7.6v6.5c0,1.1,0.7,1.9,1.9,1.9l9.6,0
c1.1,0,1.9-0.8,1.9-1.9V7.6c0-1.1-0.8-1.9-1.9-1.9h-0.9l0-1.9C11.9,1.3,9.9,0,8,0z M8,1.6c1.3,0,2.2,1,2.2,2.2
C10.2,5,9.7,6.2,8,6.9c0.3-0.5,0.4-0.7,0.5-1.2C6.7,5.9,5.8,4.9,5.8,3.8S6.7,1.6,8,1.6z"/>
<path fill="#ff9400" d="M11.4,11c0,1.9-1.5,3.4-3.4,3.4c-1.9,0-3.4-1.5-3.4-3.4c0-1.9,1.5-3.4,3.4-3.4C9.9,7.6,11.4,9.1,11.4,11"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="100%"
height="100%"
viewBox="0 0 16.1 16"
id="Layer_1"
xml:space="preserve">
<metadata
id="metadata19">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs17" />
<path
d="M 8,0 C 6.1,0 4.2,1.3 4.2,3.9 v 1.9 h -1 C 2.1,5.8 1.4,6.6 1.4,7.7 v 6.5 c 0,1.1 0.7,1.9 1.9,1.9 l 9.6,0 c 1.1,0 1.9,-0.8 1.9,-1.9 V 7.6 C 14.8,6.5 14,5.7 12.9,5.7 H 12 L 12,3.8 C 11.9,1.3 10,0 8,0 z m 0,1.6 c 1.3,0 2.2,1 2.2,2.2 C 10.3,5 9.8,6.2 8,6.9 8.3,6.4 8.4,6.2 8.5,5.7 6.7,5.9 5.8,4.9 5.8,3.8 5.8,2.7 6.7,1.6 8,1.6 z"
id="path3"
style="fill:#ffffff" />
<path
d="m 7.9927197,7.71875 c -1.8159871,0 -3.28125,1.4652629 -3.28125,3.28125 0,1.815987 1.4652629,3.28125 3.28125,3.28125 1.8159871,0 3.3125003,-1.465263 3.3125003,-3.28125 0,-1.8159871 -1.4965132,-3.28125 -3.3125003,-3.28125 z m 0,0.90625 c 1.3198669,0 2.4062503,1.0551331 2.4062503,2.375 0,1.319867 -1.0863834,2.375 -2.4062503,2.375 -1.3198669,0 -2.375,-1.055133 -2.375,-2.375 0,-1.3198669 1.0551331,-2.375 2.375,-2.375 z"
id="path3000"
style="fill:#4e4e4e;fill-opacity:1" />
<rect
width="1.1830783"
height="5.3462563"
rx="0"
ry="0.89710045"
x="7.4181905"
y="8.3502293"
id="rect3937"
style="fill:#4e4e4e;fill-opacity:1;fill-rule:nonzero" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 B

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M8,0C6.1,0,4.1,1.3,4.1,3.9v1.9h-1C2,5.7,1.3,6.5,1.3,7.6v6.5c0,1.1,0.7,1.9,1.9,1.9l9.6,0
c1.1,0,1.9-0.8,1.9-1.9V7.6c0-1.1-0.8-1.9-1.9-1.9h-0.9l0-1.9C11.9,1.3,9.9,0,8,0z M8,1.6c1.3,0,2.2,1,2.2,2.2
C10.2,5,9.7,6.2,8,6.9c0.3-0.5,0.4-0.7,0.5-1.2C6.7,5.9,5.8,4.9,5.8,3.8S6.7,1.6,8,1.6z"/>
<g>
<path fill="#C94F50" d="M7.9,7.7c-1.8,0-3.3,1.5-3.3,3.3c0,1.8,1.5,3.3,3.3,3.3c1.8,0,3.3-1.4,3.3-3.2C11.3,9.3,9.8,7.7,7.9,7.7z
M7.9,13.4c-2.5,0-3.2-3.4-1.1-4.5C7.4,8.6,8,8.6,8.6,8.7C11.1,9.4,10.8,13.4,7.9,13.4z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 995 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M8,0C6.1,0,4.1,1.3,4.1,3.9v1.9h-1C2,5.7,1.3,6.5,1.3,7.6v6.5c0,1.1,0.7,1.9,1.9,1.9l9.6,0
c1.1,0,1.9-0.8,1.9-1.9V7.6c0-1.1-0.8-1.9-1.9-1.9h-0.9l0-1.9C11.9,1.3,9.9,0,8,0z M8,1.6c1.3,0,2.2,1,2.2,2.2
C10.2,5,9.7,6.2,8,6.9c0.3-0.5,0.4-0.7,0.5-1.2C6.7,5.9,5.8,4.9,5.8,3.8S6.7,1.6,8,1.6z"/>
<path fill="#6FC062" d="M11.4,11c0,1.9-1.5,3.4-3.4,3.4c-1.9,0-3.4-1.5-3.4-3.4c0-1.9,1.5-3.4,3.4-3.4C9.9,7.6,11.4,9.1,11.4,11"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 B

View File

@ -32,10 +32,10 @@ FORMS += \
src/widget/form/settings/identitysettings.ui \
src/widget/form/settings/privacysettings.ui \
src/widget/form/loadhistorydialog.ui \
src/widget/form/inputpassworddialog.ui \
src/widget/form/setpassworddialog.ui \
src/chatlog/content/filetransferwidget.ui \
src/widget/form/settings/advancedsettings.ui
src/widget/form/settings/advancedsettings.ui \
src/android.ui
CONFIG += c++11
@ -71,6 +71,23 @@ contains(ENABLE_SYSTRAY_UNITY_BACKEND, YES) {
LIBS += -lgobject-2.0 -lappindicator -lgtk-x11-2.0
}
android {
ANDROID_TOOLCHAIN=/opt/android/toolchain-r9d-17/
INCLUDEPATH += $$ANDROID_TOOLCHAIN/include/
LIBS += -L$$ANDROID_TOOLCHAIN/lib
DISABLE_PLATFORM_EXT=YES
DISABLE_FILTER_AUDIO=YES
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
contains(ANDROID_TARGET_ARCH,armeabi) {
ANDROID_EXTRA_LIBS = \
$$ANDROID_TOOLCHAIN/lib/libopenal.so \
$$ANDROID_TOOLCHAIN/lib/libsodium.so
}
}
contains(DISABLE_PLATFORM_EXT, YES) {
} else {
@ -92,7 +109,7 @@ contains(JENKINS,YES) {
# Rules for Windows, Mac OSX, and Linux
win32 {
RC_FILE = windows/qtox.rc
LIBS += -L$$PWD/libs/lib -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lsodium -lvpx -lpthread
LIBS += -L$$PWD/libs/lib -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lsodium -lvpx -lpthread
LIBS += -L$$PWD/libs/lib -lopencv_core249 -lopencv_highgui249 -lopencv_imgproc249 -lOpenAL32 -lopus
LIBS += -lopengl32 -lole32 -loleaut32 -luuid -lvfw32 -lws2_32 -liphlpapi -lz
@ -112,31 +129,38 @@ win32 {
contains(DEFINES, QTOX_PLATFORM_EXT) { LIBS += -framework IOKit -framework CoreFoundation }
contains(DEFINES, QTOX_FILTER_AUDIO) { LIBS += -lfilteraudio }
} else {
# If we're building a package, static link libtox[core,av] and libsodium, since they are not provided by any package
contains(STATICPKG, YES) {
target.path = /usr/bin
INSTALLS += target
LIBS += -L$$PWD/libs/lib/ -lopus -lvpx -lopenal -Wl,-Bstatic -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lopencv_highgui -lopencv_imgproc -lopencv_core -lz -Wl,-Bdynamic
LIBS += -Wl,-Bstatic -ljpeg -ltiff -lpng -ljasper -lIlmImf -lIlmThread -lIex -ldc1394 -lraw1394 -lHalf -lz -llzma -ljbig
LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0
android {
LIBS += -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns
LIBS += -lopencv_videoio -lopencv_imgcodecs -lopencv_highgui -lopencv_imgproc -lopencv_androidcamera
LIBS += -llibjpeg -llibwebp -llibpng -llibtiff -llibjasper -lIlmImf -lopencv_core
LIBS += -lopus -lvpx -lsodium -lopenal
} else {
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc
}
contains(DEFINES, QTOX_PLATFORM_EXT) {
LIBS += -lX11 -lXss
}
contains(DEFINES, QTOX_FILTER_AUDIO) {
# If we're building a package, static link libtox[core,av] and libsodium, since they are not provided by any package
contains(STATICPKG, YES) {
LIBS += -Wl,-Bstatic -lfilteraudio
target.path = /usr/bin
INSTALLS += target
LIBS += -L$$PWD/libs/lib/ -lopus -lvpx -lopenal -Wl,-Bstatic -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lopencv_highgui -lopencv_imgproc -lopencv_core -lz -Wl,-Bdynamic
LIBS += -Wl,-Bstatic -ljpeg -ltiff -lpng -ljasper -lIlmImf -lIlmThread -lIex -ldc1394 -lraw1394 -lHalf -lz -llzma -ljbig
LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0
} else {
LIBS += -lfilteraudio
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc
}
}
contains(JENKINS, YES) {
LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxdns.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a ./libs/lib/libfilteraudio.a /usr/lib/libopencv_core.so /usr/lib/libopencv_highgui.so /usr/lib/libopencv_imgproc.so -lopenal -lX11 -lXss -s
contains(DEFINES, QTOX_PLATFORM_EXT) {
LIBS += -lX11 -lXss
}
contains(DEFINES, QTOX_FILTER_AUDIO) {
contains(STATICPKG, YES) {
LIBS += -Wl,-Bstatic -lfilteraudio
} else {
LIBS += -lfilteraudio
}
}
contains(JENKINS, YES) {
LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxdns.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a ./libs/lib/libfilteraudio.a /usr/lib/libopencv_core.so /usr/lib/libopencv_highgui.so /usr/lib/libopencv_imgproc.so -lopenal -lX11 -lXss -s
}
}
}
}
@ -186,7 +210,6 @@ HEADERS += src/widget/form/addfriendform.h \
src/misc/db/genericddinterface.h \
src/misc/db/plaindb.h \
src/misc/db/encrypteddb.h \
src/widget/form/inputpassworddialog.h \
src/widget/form/setpassworddialog.h \
src/widget/form/tabcompleter.h \
src/video/videoframe.h \
@ -215,6 +238,11 @@ HEADERS += src/widget/form/addfriendform.h \
src/chatlog/pixmapcache.h \
src/widget/callconfirmwidget.h \
src/widget/systemtrayicon.h \
src/widget/systemtrayicon_private.h \
src/nexus.h \
src/widget/gui.h \
src/widget/androidgui.h \
src/offlinemsgengine.h
SOURCES += \
src/widget/form/addfriendform.cpp \
@ -232,6 +260,7 @@ SOURCES += \
src/widget/groupwidget.cpp \
src/widget/widget.cpp \
src/core.cpp \
src/coreencryption.cpp \
src/friend.cpp \
src/friendlist.cpp \
src/group.cpp \
@ -260,7 +289,6 @@ SOURCES += \
src/misc/db/genericddinterface.cpp \
src/misc/db/plaindb.cpp \
src/misc/db/encrypteddb.cpp \
src/widget/form/inputpassworddialog.cpp \
src/widget/form/setpassworddialog.cpp \
src/video/netvideosource.cpp \
src/widget/form/tabcompleter.cpp \
@ -289,7 +317,11 @@ SOURCES += \
src/chatlog/documentcache.cpp \
src/chatlog/pixmapcache.cpp \
src/widget/callconfirmwidget.cpp \
src/widget/systemtrayicon.cpp
src/widget/systemtrayicon.cpp \
src/nexus.cpp \
src/widget/gui.cpp \
src/widget/androidgui.cpp \
src/offlinemsgengine.cpp
contains(DEFINES, QTOX_FILTER_AUDIO) {
HEADERS += src/audiofilterer.h

22
res.qrc
View File

@ -37,14 +37,16 @@
<file>img/status/dot_online.png</file>
<file>img/status/dot_online_2x.png</file>
<file>img/status/dot_online_notification.png</file>
<file>img/taskbar/dark/taskbar_online_2x.png</file>
<file>img/taskbar/dark/taskbar_idle_2x.png</file>
<file>img/taskbar/dark/taskbar_busy_2x.png</file>
<file>img/taskbar/dark/taskbar_offline_2x.png</file>
<file>img/taskbar/light/taskbar_online_2x.png</file>
<file>img/taskbar/light/taskbar_idle_2x.png</file>
<file>img/taskbar/light/taskbar_busy_2x.png</file>
<file>img/taskbar/light/taskbar_offline_2x.png</file>
<file>img/taskbar/dark/taskbar_online.svg</file>
<file>img/taskbar/dark/taskbar_away.svg</file>
<file>img/taskbar/dark/taskbar_busy.svg</file>
<file>img/taskbar/dark/taskbar_offline.svg</file>
<file>img/taskbar/dark/taskbar_event.svg</file>
<file>img/taskbar/light/taskbar_online.svg</file>
<file>img/taskbar/light/taskbar_away.svg</file>
<file>img/taskbar/light/taskbar_busy.svg</file>
<file>img/taskbar/light/taskbar_offline.svg</file>
<file>img/taskbar/light/taskbar_event.svg</file>
<file>img/transfer.png</file>
<file>smileys/cylgom/angel.png</file>
<file>smileys/cylgom/angry.png</file>
@ -189,10 +191,6 @@
<file>ui/sendButton/sendButtonPressed.png</file>
<file>ui/settings/mainContent.css</file>
<file>ui/settings/mainHead.css</file>
<file>ui/statusButton/dot_away.png</file>
<file>ui/statusButton/dot_busy.png</file>
<file>ui/statusButton/dot_idle.png</file>
<file>ui/statusButton/dot_online.png</file>
<file>ui/statusButton/menu_indicator.png</file>
<file>ui/statusButton/statusButton.css</file>
<file>ui/stopFileButton/default.png</file>

View File

@ -2,12 +2,12 @@
if which apt-get; then
sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev \
libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev qttools5-dev-tools qtchooser libxss-dev
libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev qttools5-dev-tools qtchooser libxss-dev libqt5svg5*
elif which pacman; then
sudo pacman -S --needed base-devel qt5 opencv openal opus libvpx libxss
sudo pacman -S --needed base-devel qt5 opencv openal opus libvpx libxss qt5-svg
elif which yum; then
sudo yum groupinstall "Development Tools"
sudo yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libtool autoconf automake check check-devel libXScrnSaver-devel
sudo yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libtool autoconf automake check check-devel libXScrnSaver-devel qt5-qtsvg
else
echo "Unknown package manager, attempting to compile anyways"
fi

909
src/android.ui Normal file
View File

@ -0,0 +1,909 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Android</class>
<widget class="QWidget" name="Android">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>320</width>
<height>480</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="statusPanel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="statusHead" native="true">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QHBoxLayout" name="myProfile">
<property name="spacing">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,1">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="CroppingLabel" name="nameLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="NoRole">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="NoRole">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>240</red>
<green>240</green>
<blue>240</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="NoRole">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="inputMethodHints">
<set>Qt::ImhNoPredictiveText|Qt::ImhPreferLatin</set>
</property>
<property name="text">
<string>Your name</string>
</property>
</widget>
</item>
<item>
<widget class="CroppingLabel" name="statusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>193</red>
<green>193</green>
<blue>193</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>193</red>
<green>193</green>
<blue>193</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>193</red>
<green>193</green>
<blue>193</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>193</red>
<green>193</green>
<blue>193</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>14</red>
<green>14</green>
<blue>14</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>14</red>
<green>14</green>
<blue>14</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="inputMethodHints">
<set>Qt::ImhNoPredictiveText|Qt::ImhPreferLatin</set>
</property>
<property name="text">
<string>Your status</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="statusButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>10</width>
<height>10</height>
</size>
</property>
<property name="autoExclusive">
<bool>false</bool>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="AdjustingScrollArea" name="friendList">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>320</width>
<height>385</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5"/>
</widget>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QWidget" name="tooliconsZone" native="true">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="addButton">
<property name="minimumSize">
<size>
<width>55</width>
<height>35</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<height>35</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Add friends</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>:/img/add.png</normaloff>:/img/add.png</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="groupButton">
<property name="minimumSize">
<size>
<width>55</width>
<height>35</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Create a group chat</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>:/img/group_button.png</normaloff>:/img/group_button.png</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="transferButton">
<property name="minimumSize">
<size>
<width>55</width>
<height>35</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<height>35</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>View completed file transfers</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>:/img/transfer.png</normaloff>:/img/transfer.png</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="settingsButton">
<property name="minimumSize">
<size>
<width>55</width>
<height>35</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<height>35</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Change your settings</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>:/img/settings.png</normaloff>:/img/settings.png</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AdjustingScrollArea</class>
<extends>QScrollArea</extends>
<header>src/widget/adjustingscrollarea.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>CroppingLabel</class>
<extends>QLabel</extends>
<header>src/widget/croppinglabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -19,6 +19,7 @@
#include "src/misc/serialize.h"
#include "src/misc/settings.h"
#include "src/widget/widget.h"
#include "src/widget/gui.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QCoreApplication>
@ -490,8 +491,8 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker()
QDir updateDir(updateDirStr);
if ((updateDir.exists() && QFile(updateDirStr+"flist").exists())
|| Widget::getInstance()->askMsgboxQuestion(QObject::tr("Update", "The title of a message box"),
QObject::tr("An update is available, do you want to download it now?\nIt will be installed when qTox restarts.")))
|| GUI::askQuestion(QObject::tr("Update", "The title of a message box"),
QObject::tr("An update is available, do you want to download it now?\nIt will be installed when qTox restarts."), true, false))
{
downloadUpdate();
}

View File

@ -197,7 +197,7 @@ QString ChatMessage::detectQuotes(const QString& str)
QString quotedText;
for (int i=0;i<messageLines.size();++i)
{
if (QRegExp("^&gt;( |&gt;|[^_\\d\\W]).*").exactMatch(messageLines[i]))
if (QRegExp("^&gt;( |[[]|&gt;|[^_\\d\\W]).*").exactMatch(messageLines[i]))
quotedText += "<span class=quote>" + messageLines[i] + "</span>";
else
quotedText += messageLines[i];

View File

@ -15,15 +15,15 @@
*/
#include "core.h"
#include "nexus.h"
#include "misc/cdata.h"
#include "misc/cstring.h"
#include "misc/settings.h"
#include "widget/widget.h"
#include "widget/gui.h"
#include "historykeeper.h"
#include "src/audio.h"
#include <tox/tox.h>
#include <tox/toxencryptsave.h>
#include <ctime>
#include <functional>
@ -39,7 +39,6 @@
#include <QDateTime>
#include <QList>
#include <QBuffer>
#include <QMessageBox>
#include <QMutexLocker>
const QString Core::CONFIG_FILE_NAME = "data";
@ -90,13 +89,25 @@ Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) :
Audio::openInput(inDevDescr);
}
void Core::deadifyTox()
{
if (toxav)
{
toxav_kill(toxav);
toxav = nullptr;
}
if (tox)
{
tox_kill(tox);
tox = nullptr;
}
}
Core::~Core()
{
qDebug() << "Deleting Core";
clearPassword(Core::ptMain);
clearPassword(Core::ptHistory);
saveConfiguration();
toxTimer->stop();
coreThread->exit(0);
while (coreThread->isRunning())
@ -105,13 +116,7 @@ Core::~Core()
coreThread->wait(500);
}
if (tox)
{
toxav_kill(toxav);
toxav = nullptr;
tox_kill(tox);
tox = nullptr;
}
deadifyTox();
if (videobuf)
{
@ -125,7 +130,7 @@ Core::~Core()
Core* Core::getInstance()
{
return Widget::getInstance()->getCore();
return Nexus::getCore();
}
void Core::make_tox()
@ -183,9 +188,6 @@ void Core::make_tox()
{
if (toxOptions.proxy_type != TOX_PROXY_NONE)
{
//QMessageBox::critical(Widget::getInstance(), tr("Proxy failure", "popup title"),
//tr("toxcore failed to start with your proxy settings. qTox cannot run; please modify your "
//"settings and restart.", "popup text"));
qCritical() << "Core: bad proxy! no toxcore!";
emit badProxy();
}
@ -223,27 +225,40 @@ void Core::make_tox()
void Core::start()
{
qDebug() << "Core: Starting up";
make_tox();
qsrand(time(nullptr));
if (loadPath != "")
{
if (!loadConfiguration(loadPath)) // loadPath is meaningless after this
{
qCritical() << "Core: loadConfiguration failed, exiting now";
emit failedToStart();
tox_kill(tox);
tox = nullptr;
return;
while (!loadConfiguration(loadPath))
{
if (loadPath.isEmpty())
{
QString profile;
if ((profile = loadOldInformation()).isEmpty())
{
qCritical() << "Core: loadConfiguration failed, exiting now";
emit failedToStart();
return;
}
else
{
loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT);
Settings::getInstance().switchProfile(profile);
HistoryKeeper::resetInstance(); // I'm not actually sure if this is necessary
}
}
}
// loadPath is meaningless after this
loadPath = "";
}
else // new ID
{
setStatusMessage(tr("Toxing on qTox")); // this also solves the not updating issue
setUsername(tr("qTox User"));
QMetaObject::invokeMethod(Widget::getInstance(), "onSettingsClicked"); // update ui with new profile
}
tox_callback_friend_request(tox, onFriendRequest, this);
@ -309,7 +324,7 @@ void Core::start()
void Core::process()
{
if (!tox)
if (!isReady())
return;
static int tolerance = CORE_DISCONNECT_TOLERANCE;
@ -501,7 +516,6 @@ void Core::onGroupInvite(Tox*, int friendnumber, uint8_t type, const uint8_t *da
void Core::onGroupMessage(Tox*, int groupnumber, int peernumber, const uint8_t * message, uint16_t length, void *_core)
{
Core* core = static_cast<Core*>(_core);
emit core->groupMessageReceived(groupnumber, peernumber, CString::toString(message, length), false);
}
@ -1011,7 +1025,7 @@ void Core::acceptFileRecvRequest(int friendId, int fileNum, QString path)
void Core::removeFriend(int friendId, bool fake)
{
if (!tox || fake)
if (!isReady() || fake)
return;
if (tox_del_friend(tox, friendId) == -1) {
emit failedToRemoveFriend(friendId);
@ -1023,7 +1037,7 @@ void Core::removeFriend(int friendId, bool fake)
void Core::removeGroup(int groupId, bool fake)
{
if (!tox || fake)
if (!isReady() || fake)
return;
tox_del_groupchat(tox, groupId);
@ -1161,7 +1175,8 @@ QString Core::sanitize(QString name)
bool Core::loadConfiguration(QString path)
{
// setting the profile is now the responsibility of the caller
loadPath = ""; // if not empty upon return, then user forgot a password and is switching
QFile configurationFile(path);
qDebug() << "Core::loadConfiguration: reading from " << path;
@ -1185,51 +1200,20 @@ bool Core::loadConfiguration(QString path)
}
else if (error == 1) // Encrypted data save
{
if (!Settings::getInstance().getEncryptTox())
Widget::getInstance()->showWarningMsgBox(tr("Encryption error"), tr("The .tox file is encrypted, but encryption was not checked, continuing regardless."));
uint8_t salt[tox_pass_salt_length()];
tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt);
do
if (!loadEncryptedSave(data))
{
while (!pwsaltedkeys[ptMain])
configurationFile.close();
QString profile = Settings::getInstance().askProfiles();
if (!profile.isEmpty())
{
emit blockingGetPassword(tr("Tox datafile decryption password"), ptMain, salt);
if (!pwsaltedkeys[ptMain])
Widget::getInstance()->showWarningMsgBox(tr("Password error"), tr("Failed to setup password.\nEmpty password."));
loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT);
Settings::getInstance().switchProfile(profile);
HistoryKeeper::resetInstance();
}
error = tox_encrypted_key_load(tox, reinterpret_cast<uint8_t *>(data.data()), data.size(), pwsaltedkeys[ptMain]);
if (error != 0)
{
QMessageBox msgb;
msgb.moveToThread(qApp->thread());
QPushButton *tryAgain = msgb.addButton(tr("Try Again"), QMessageBox::AcceptRole);
QPushButton *cancel = msgb.addButton(tr("Change profile"), QMessageBox::RejectRole);
QPushButton *wipe = msgb.addButton(tr("Reinit current profile"), QMessageBox::ActionRole);
msgb.setDefaultButton(tryAgain);
msgb.setWindowTitle(tr("Password error"));
msgb.setText(tr("Wrong password has been entered"));
// msgb.setInformativeText(tr(""));
msgb.exec();
if (msgb.clickedButton() == tryAgain)
clearPassword(ptMain);
else if (msgb.clickedButton() == cancel)
{
configurationFile.close();
return false;
}
else if (msgb.clickedButton() == wipe)
{
clearPassword(ptMain);
Settings::getInstance().setEncryptTox(false);
error = 0;
}
}
else
Settings::getInstance().setEncryptTox(true);
} while (error != 0);
return false;
}
}
}
configurationFile.close();
@ -1249,53 +1233,7 @@ bool Core::loadConfiguration(QString path)
// tox core is already decrypted
if (Settings::getInstance().getEnableLogging() && Settings::getInstance().getEncryptLogs())
{
// get salt
QFile file(HistoryKeeper::getHistoryPath());
file.open(QIODevice::ReadOnly);
QByteArray data = file.read(tox_pass_encryption_extra_length());
file.close();
uint8_t salt[tox_pass_salt_length()];
int err = tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt);
if (err)
{ // maybe we should handle this better
qWarning() << "Core: history db isn't encrypted, but encryption is set!! No history loaded...";
}
else
{
bool error = true;
do
{
while (!pwsaltedkeys[ptHistory])
{
emit blockingGetPassword(tr("History Log decryption password"), Core::ptHistory, salt);
if (!pwsaltedkeys[ptHistory])
Widget::getInstance()->showWarningMsgBox(tr("Password error"), tr("Failed to setup password.\nEmpty password."));
}
if (!HistoryKeeper::checkPassword())
{
if (QMessageBox::Ok == Widget::getInstance()->showWarningMsgBox(tr("Encrypted log"),
tr("Your history is encrypted with different password.\nDo you want to try another password?"),
QMessageBox::Ok | QMessageBox::Cancel))
{
error = true;
clearPassword(ptHistory);
}
else
{
error = false;
clearPassword(ptHistory);
Widget::getInstance()->showWarningMsgBox(tr("History"), tr("Due to incorret password history will be disabled."));
Settings::getInstance().setEncryptLogs(false);
Settings::getInstance().setEnableLogging(false);
}
} else {
error = false;
}
} while (error);
}
}
checkEncryptedHistory();
loadFriends();
return true;
@ -1306,6 +1244,9 @@ void Core::saveConfiguration()
if (QThread::currentThread() != coreThread)
return (void) QMetaObject::invokeMethod(this, "saveConfiguration");
if (!isReady())
return;
QString dir = Settings::getSettingsDirPath();
QDir directory(dir);
if (!directory.exists() && !directory.mkpath(directory.absolutePath())) {
@ -1322,7 +1263,7 @@ void Core::saveConfiguration()
if (profile == "") // happens on creation of a new Tox ID
profile = getIDString();
Settings::getInstance().setCurrentProfile(profile);
Settings::getInstance().switchProfile(profile);
}
QString path = directory.filePath(profile + TOX_EXT);
@ -1330,66 +1271,6 @@ void Core::saveConfiguration()
saveConfiguration(path);
}
void Core::saveConfiguration(const QString& path)
{
if (QThread::currentThread() != coreThread)
return (void) QMetaObject::invokeMethod(this, "saveConfiguration", Q_ARG(const QString&, path));
if (!tox)
{
qWarning() << "Core::saveConfiguration: Tox not started, aborting!";
return;
}
Settings::getInstance().save();
QSaveFile configurationFile(path);
if (!configurationFile.open(QIODevice::WriteOnly))
{
qCritical() << "File " << path << " cannot be opened";
return;
}
qDebug() << "Core: writing tox_save to " << path;
uint32_t fileSize;
bool encrypt = Settings::getInstance().getEncryptTox();
if (encrypt)
fileSize = tox_encrypted_size(tox);
else
fileSize = tox_size(tox);
if (fileSize > 0 && fileSize <= INT32_MAX)
{
uint8_t *data = new uint8_t[fileSize];
if (encrypt)
{
if (!pwsaltedkeys[ptMain])
{
Widget::getInstance()->showWarningMsgBox(tr("NO Password"), tr("Will be saved without encryption!"));
tox_save(tox, data);
}
else
{
int ret = tox_encrypted_key_save(tox, data, pwsaltedkeys[ptMain]);
if (ret == -1)
{
qCritical() << "Core::saveConfiguration: encryption of save file failed!!!";
return;
}
}
}
else
{
tox_save(tox, data);
}
configurationFile.write(reinterpret_cast<char *>(data), fileSize);
configurationFile.commit();
delete[] data;
}
}
void Core::switchConfiguration(const QString& profile)
{
if (profile.isEmpty())
@ -1398,19 +1279,15 @@ void Core::switchConfiguration(const QString& profile)
qDebug() << "Core: switching from" << Settings::getInstance().getCurrentProfile() << "to" << profile;
saveConfiguration();
saveCurrentInformation(); // part of a hack, see core.h
ready = false;
GUI::setEnabled(false);
clearPassword(ptMain);
clearPassword(ptHistory);
toxTimer->stop();
Widget::getInstance()->setEnabledThreadsafe(false);
if (tox) {
toxav_kill(toxav);
toxav = nullptr;
tox_kill(tox);
tox = nullptr;
}
deadifyTox();
emit selfAvatarChanged(QPixmap(":/img/contact_dark.png"));
emit blockingClearContacts(); // we need this to block, but signals are required for thread safety
@ -1420,15 +1297,12 @@ void Core::switchConfiguration(const QString& profile)
else
loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT);
// the new profile needs to be set before resetting the settings, so that
// we don't load the old profile's profile.ini
Settings::getInstance().setCurrentProfile(profile);
Settings::getInstance().save(false); // save new profile, but don't write old profile info to newprofile.ini
Settings::resetInstance();
HistoryKeeper::getInstance()->resetInstance();
Settings::getInstance().switchProfile(profile);
HistoryKeeper::resetInstance();
start();
Widget::getInstance()->setEnabledThreadsafe(true);
if (isReady())
GUI::setEnabled(true);
}
void Core::loadFriends()
@ -1793,69 +1667,6 @@ QList<CString> Core::splitMessage(const QString &message, int maxLen)
return splittedMsgs;
}
void Core::setPassword(QString& password, PasswordType passtype, uint8_t* salt)
{
if (password.isEmpty())
{
clearPassword(passtype);
return;
}
if (!pwsaltedkeys[passtype])
pwsaltedkeys[passtype] = new uint8_t[tox_pass_key_length()];
CString str(password);
if (salt)
tox_derive_key_with_salt(str.data(), str.size(), salt, pwsaltedkeys[passtype]);
else
tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkeys[passtype]);
password.clear();
}
void Core::clearPassword(PasswordType passtype)
{
if (pwsaltedkeys[passtype])
{
delete[] pwsaltedkeys[passtype];
pwsaltedkeys[passtype] = nullptr;
}
}
QByteArray Core::encryptData(const QByteArray& data, PasswordType passtype)
{
if (!pwsaltedkeys[passtype])
return QByteArray();
uint8_t encrypted[data.size() + tox_pass_encryption_extra_length()];
if (tox_pass_key_encrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(), pwsaltedkeys[passtype], encrypted) == -1)
{
qWarning() << "Core::encryptData: encryption failed";
return QByteArray();
}
return QByteArray(reinterpret_cast<char*>(encrypted), data.size() + tox_pass_encryption_extra_length());
}
QByteArray Core::decryptData(const QByteArray& data, PasswordType passtype)
{
if (!pwsaltedkeys[passtype])
return QByteArray();
int sz = data.size() - tox_pass_encryption_extra_length();
uint8_t decrypted[sz];
if (tox_pass_key_decrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(), pwsaltedkeys[passtype], decrypted) != sz)
{
qWarning() << "Core::decryptData: decryption failed";
return QByteArray();
}
return QByteArray(reinterpret_cast<char*>(decrypted), sz);
}
bool Core::isPasswordSet(PasswordType passtype)
{
if (pwsaltedkeys[passtype])
return true;
return false;
}
QString Core::getPeerName(const ToxID& id) const
{
QString name;
@ -1890,7 +1701,7 @@ QString Core::getPeerName(const ToxID& id) const
bool Core::isReady()
{
return ready;
return toxav && tox && ready;
}
void Core::setNospam(uint32_t nospam)

View File

@ -52,6 +52,8 @@ public:
static QString sanitize(QString name);
static QList<CString> splitMessage(const QString &message, int maxLen);
static QByteArray getSaltFromFile(QString filename);
QString getPeerName(const ToxID& id) const;
int getGroupNumberPeers(int groupId) const; ///< Return the number of peers in the group chat on success, or -1 on failure
@ -83,10 +85,10 @@ public slots:
void start(); ///< Initializes the core, must be called before anything else
void process(); ///< Processes toxcore events and ensure we stay connected, called by its own timer
void bootstrapDht(); ///< Connects us to the Tox network
void switchConfiguration(const QString& profile); ///< Load a different profile and restart the core
void saveConfiguration();
void saveConfiguration(const QString& path);
void switchConfiguration(const QString& profile); ///< Load a different profile and restart the core
void acceptFriendRequest(const QString& userId);
void requestFriendship(const QString& friendAddress, const QString& message);
@ -137,6 +139,7 @@ public slots:
static bool isGroupCallVolEnabled(int groupId);
void setPassword(QString& password, PasswordType passtype, uint8_t* salt = nullptr);
void useOtherPassword(PasswordType type);
void clearPassword(PasswordType passtype);
QByteArray encryptData(const QByteArray& data, PasswordType passtype);
QByteArray decryptData(const QByteArray& data, PasswordType passtype);
@ -145,7 +148,6 @@ signals:
void connected();
void disconnected();
void blockingClearContacts();
void blockingGetPassword(QString info, int passtype, uint8_t* salt = nullptr);
void friendRequestReceived(const QString& userId, const QString& message);
void friendMessageReceived(int friendId, const QString& message, bool isAction);
@ -266,6 +268,8 @@ private:
bool checkConnection();
bool loadConfiguration(QString path); // Returns false for a critical error, true otherwise
bool loadEncryptedSave(QByteArray& data);
void checkEncryptedHistory();
void make_tox();
void loadFriends();
@ -274,6 +278,8 @@ private:
void checkLastOnline(int friendId);
void deadifyTox();
private slots:
void onFileTransferFinished(ToxFile file);
@ -294,7 +300,17 @@ private:
QMutex fileSendMutex, messageSendMutex;
bool ready;
uint8_t* pwsaltedkeys[PasswordType::ptCounter]; // use the pw's hash as the "pw"
uint8_t* pwsaltedkeys[PasswordType::ptCounter] = {nullptr}; // use the pw's hash as the "pw"
// Hack for reloading current profile if switching to an encrypted one fails.
// Testing the passwords before killing the current profile is perfectly doable,
// however it would require major refactoring;
// the Core class as a whole also requires major refactoring (especially to support multiple IDs at once),
// so I'm punting on this until then, when it would get fixed anyways
uint8_t* backupkeys[PasswordType::ptCounter] = {nullptr};
QString* backupProfile = nullptr;
void saveCurrentInformation();
QString loadOldInformation();
static const int videobufsize;
static uint8_t* videobuf;

329
src/coreencryption.cpp Normal file
View File

@ -0,0 +1,329 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
/* This file is part of the Core class, but was separated for my sanity */
/* The load function delegates to loadEncrypted here, and the save function */
/* was permanently moved here to handle encryption */
#include "core.h"
#include "src/widget/gui.h"
#include <tox/tox.h>
#include <tox/toxencryptsave.h>
#include "src/misc/settings.h"
#include "misc/cstring.h"
#include "historykeeper.h"
#include <QApplication>
#include <QDebug>
#include <QSaveFile>
#include <QFile>
#include <QThread>
void Core::setPassword(QString& password, PasswordType passtype, uint8_t* salt)
{
clearPassword(passtype);
if (password.isEmpty())
return;
pwsaltedkeys[passtype] = new uint8_t[tox_pass_key_length()];
CString str(password);
if (salt)
tox_derive_key_with_salt(str.data(), str.size(), salt, pwsaltedkeys[passtype]);
else
tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkeys[passtype]);
password.clear();
}
#include <algorithm>
void Core::useOtherPassword(PasswordType type)
{
clearPassword(type);
pwsaltedkeys[type] = new uint8_t[tox_pass_key_length()];
PasswordType other = (type == ptMain) ? ptHistory : ptMain;
std::copy(pwsaltedkeys[other], pwsaltedkeys[other]+tox_pass_key_length(), pwsaltedkeys[type]);
}
void Core::clearPassword(PasswordType passtype)
{
delete[] pwsaltedkeys[passtype];
pwsaltedkeys[passtype] = nullptr;
}
// part of a hack, see core.h
void Core::saveCurrentInformation()
{
if (pwsaltedkeys[ptMain])
{
backupkeys[ptMain] = new uint8_t[tox_pass_key_length()];
std::copy(pwsaltedkeys[ptMain], pwsaltedkeys[ptMain]+tox_pass_key_length(), backupkeys[ptMain]);
}
if (pwsaltedkeys[ptHistory])
{
backupkeys[ptHistory] = new uint8_t[tox_pass_key_length()];
std::copy(pwsaltedkeys[ptHistory], pwsaltedkeys[ptHistory]+tox_pass_key_length(), backupkeys[ptHistory]);
}
backupProfile = new QString(Settings::getInstance().getCurrentProfile());
}
QString Core::loadOldInformation()
{
QString out;
if (backupProfile)
{
out = *backupProfile;
delete backupProfile;
backupProfile = nullptr;
}
backupProfile = nullptr;
clearPassword(ptMain);
clearPassword(ptHistory);
// we can just copy the pointer, as long as we null out backupkeys
// (if backupkeys was null anyways, then this is a null-op)
pwsaltedkeys[ptMain] = backupkeys[ptMain];
pwsaltedkeys[ptHistory] = backupkeys[ptHistory];
backupkeys[ptMain] = nullptr;
backupkeys[ptHistory] = nullptr;
return out;
}
QByteArray Core::encryptData(const QByteArray& data, PasswordType passtype)
{
if (!pwsaltedkeys[passtype])
return QByteArray();
uint8_t encrypted[data.size() + tox_pass_encryption_extra_length()];
if (tox_pass_key_encrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(), pwsaltedkeys[passtype], encrypted) == -1)
{
qWarning() << "Core::encryptData: encryption failed";
return QByteArray();
}
return QByteArray(reinterpret_cast<char*>(encrypted), data.size() + tox_pass_encryption_extra_length());
}
QByteArray Core::decryptData(const QByteArray& data, PasswordType passtype)
{
if (!pwsaltedkeys[passtype])
return QByteArray();
int sz = data.size() - tox_pass_encryption_extra_length();
uint8_t decrypted[sz];
int decr_size = tox_pass_key_decrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(), pwsaltedkeys[passtype], decrypted);
if (decr_size != sz)
{
qWarning() << "Core::decryptData: decryption failed";
return QByteArray();
}
return QByteArray(reinterpret_cast<char*>(decrypted), sz);
}
bool Core::isPasswordSet(PasswordType passtype)
{
if (pwsaltedkeys[passtype])
return true;
return false;
}
QByteArray Core::getSaltFromFile(QString filename)
{
QFile file(filename);
if (!file.open(QIODevice::ReadOnly))
{
qWarning() << "Core: file" << filename << "doesn't exist";
return QByteArray();
}
QByteArray data = file.read(tox_pass_encryption_extra_length());
file.close();
uint8_t *salt = new uint8_t[tox_pass_salt_length()];
int err = tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt);
if (err)
{
qWarning() << "Core: can't get salt from" << filename << "header";
return QByteArray();
}
QByteArray res = QByteArray::fromRawData(reinterpret_cast<const char*>(salt), tox_pass_salt_length());
return res;
}
bool Core::loadEncryptedSave(QByteArray& data)
{
if (!Settings::getInstance().getEncryptTox())
GUI::showWarning(tr("Encryption error"), tr("The .tox file is encrypted, but encryption was not checked, continuing regardless."));
int error = -1;
QString a(tr("Please enter the password for the %1 profile.", "used in load() when no pw is already set").arg(Settings::getInstance().getCurrentProfile()));
QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()"));
QString dialogtxt;
if (pwsaltedkeys[ptMain]) // password set, try it
{
error = tox_encrypted_key_load(tox, reinterpret_cast<uint8_t *>(data.data()), data.size(), pwsaltedkeys[ptMain]);
if (!error)
{
Settings::getInstance().setEncryptTox(true);
return true;
}
dialogtxt = tr("The profile password failed. Please try another?", "used only when pw set before load() doesn't work");
}
else
dialogtxt = a;
uint8_t salt[tox_pass_salt_length()];
tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt);
do
{
QString pw = GUI::passwordDialog(tr("Change profile"), dialogtxt);
if (pw.isEmpty())
{
clearPassword(ptMain);
return false;
}
else
setPassword(pw, ptMain, salt);
error = tox_encrypted_key_load(tox, reinterpret_cast<uint8_t *>(data.data()), data.size(), pwsaltedkeys[ptMain]);
dialogtxt = a + "\n" + b;
} while (error != 0);
Settings::getInstance().setEncryptTox(true);
return true;
}
void Core::checkEncryptedHistory()
{
QString path = HistoryKeeper::getHistoryPath();
bool exists = QFile::exists(path);
QByteArray salt = getSaltFromFile(path);
if (exists && salt.size() == 0)
{ // maybe we should handle this better
GUI::showWarning(tr("Encrypted chat history"), tr("No encrypted chat history file found, or it was corrupted.\nHistory will be disabled!"));
Settings::getInstance().setEncryptLogs(false);
Settings::getInstance().setEnableLogging(false);
HistoryKeeper::resetInstance();
return;
}
QString a(tr("Please enter the password for the chat history for the %1 profile.", "used in load() when no hist pw set").arg(Settings::getInstance().getCurrentProfile()));
QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()"));
QString c(tr("\nDisabling chat history now will leave the encrypted history intact (but not usable); if you later remember the password, you may re-enable encryption from the Privacy tab with the correct password to use the history.", "part of history password dialog"));
QString dialogtxt;
if (pwsaltedkeys[ptHistory])
{
if (!exists || HistoryKeeper::checkPassword())
return;
dialogtxt = tr("The chat history password failed. Please try another?", "used only when pw set before load() doesn't work");
}
else
dialogtxt = a;
dialogtxt += "\n" + c;
if (pwsaltedkeys[ptMain])
{
useOtherPassword(ptHistory);
if (!exists || HistoryKeeper::checkPassword())
{
qDebug() << "Core: using main password for chat history";
return;
}
clearPassword(ptHistory);
}
bool error = true;
do
{
QString pw = GUI::passwordDialog(tr("Disable chat history"), dialogtxt);
if (pw.isEmpty())
{
clearPassword(ptHistory);
Settings::getInstance().setEncryptLogs(false);
Settings::getInstance().setEnableLogging(false);
HistoryKeeper::resetInstance();
return;
}
else
setPassword(pw, ptHistory, reinterpret_cast<uint8_t*>(salt.data()));
error = exists && !HistoryKeeper::checkPassword();
dialogtxt = a + "\n" + c + "\n" + b;
} while (error);
}
void Core::saveConfiguration(const QString& path)
{
if (QThread::currentThread() != coreThread)
return (void) QMetaObject::invokeMethod(this, "saveConfiguration", Q_ARG(const QString&, path));
if (!isReady())
{
qWarning() << "Core::saveConfiguration: Tox not started, aborting!";
return;
}
QSaveFile configurationFile(path);
if (!configurationFile.open(QIODevice::WriteOnly)) {
qCritical() << "File " << path << " cannot be opened";
return;
}
qDebug() << "Core: writing tox_save to " << path;
uint32_t fileSize; bool encrypt = Settings::getInstance().getEncryptTox();
if (encrypt)
fileSize = tox_encrypted_size(tox);
else
fileSize = tox_size(tox);
if (fileSize > 0 && fileSize <= std::numeric_limits<int32_t>::max()) {
uint8_t *data = new uint8_t[fileSize];
if (encrypt)
{
if (!pwsaltedkeys[ptMain])
{
// probably zero chance event
GUI::showWarning(tr("NO Password"), tr("Encryption is enabled, but there is no password! Encryption will be disabled."));
Settings::getInstance().setEncryptTox(false);
tox_save(tox, data);
}
else
{
int ret = tox_encrypted_key_save(tox, data, pwsaltedkeys[ptMain]);
if (ret == -1)
{
qCritical() << "Core::saveConfiguration: encryption of save file failed!!!";
return;
}
}
}
else
tox_save(tox, data);
configurationFile.write(reinterpret_cast<char *>(data), fileSize);
configurationFile.commit();
delete[] data;
}
Settings::getInstance().save();
}

View File

@ -18,7 +18,7 @@
#include "friendlist.h"
#include "widget/friendwidget.h"
#include "widget/form/chatform.h"
#include "widget/widget.h"
#include "widget/gui.h"
#include "src/core.h"
#include "src/misc/settings.h"
@ -52,7 +52,7 @@ void Friend::setName(QString name)
chatForm->setName(name);
if (widget->isActive())
Widget::getInstance()->setWindowTitle(name);
GUI::setWindowTitle(name);
}
}
@ -65,7 +65,7 @@ void Friend::setAlias(QString name)
chatForm->setName(dispName);
if (widget->isActive())
Widget::getInstance()->setWindowTitle(dispName);
GUI::setWindowTitle(dispName);
}
void Friend::setStatusMessage(QString message)

View File

@ -20,7 +20,7 @@
#include "friendlist.h"
#include "friend.h"
#include "core.h"
#include "widget/widget.h"
#include "widget/gui.h"
#include <QDebug>
#include <QTimer>
@ -90,7 +90,7 @@ void Group::setName(const QString& name)
chatForm->setName(name);
if (widget->isActive())
Widget::getInstance()->setWindowTitle(name);
GUI::setWindowTitle(name);
}
void Group::regeneratePeerList()

View File

@ -68,20 +68,15 @@ HistoryKeeper *HistoryKeeper::getInstance()
return historyInstance;
}
bool HistoryKeeper::checkPassword()
bool HistoryKeeper::checkPassword(int encrypted)
{
if (Settings::getInstance().getEnableLogging())
{
if (Settings::getInstance().getEncryptLogs())
{
QString dbpath = getHistoryPath();
return EncryptedDb::check(dbpath);
} else {
return true;
}
} else {
if (!Settings::getInstance().getEnableLogging() && (encrypted == -1))
return true;
}
if ((encrypted == 1) || (encrypted == -1 && Settings::getInstance().getEncryptLogs()))
return EncryptedDb::check(getHistoryPath(Settings::getInstance().getCurrentProfile(), encrypted));
return true;
}
HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) :
@ -132,9 +127,10 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) :
setSyncType(Settings::getInstance().getDbSyncType());
messageID = 0;
QSqlQuery sqlAnswer = db->exec("select seq from sqlite_sequence where name=\"history\";");
sqlAnswer.first();
messageID = sqlAnswer.value(0).toInt();
if (sqlAnswer.first())
messageID = sqlAnswer.value(0).toLongLong();
}
HistoryKeeper::~HistoryKeeper()
@ -142,16 +138,13 @@ HistoryKeeper::~HistoryKeeper()
delete db;
}
int HistoryKeeper::addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent)
qint64 HistoryKeeper::addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent)
{
int chat_id = getChatID(chat, ctSingle).first;
int sender_id = getAliasID(sender);
QList<QString> cmds = generateAddChatEntryCmd(chat, message, sender, dt, isSent);
db->exec("BEGIN TRANSACTION;");
db->exec(QString("INSERT INTO history (timestamp, chat_id, sender, message) ") +
QString("VALUES (%1, %2, %3, '%4');")
.arg(dt.toMSecsSinceEpoch()).arg(chat_id).arg(sender_id).arg(wrapMessage(message)));
db->exec(QString("INSERT INTO sent_status (status) VALUES (%1);").arg(isSent));
for (auto &it : cmds)
db->exec(it);
db->exec("COMMIT TRANSACTION;");
messageID++;
@ -190,12 +183,66 @@ QList<HistoryKeeper::HistMessage> HistoryKeeper::getChatHistory(HistoryKeeper::C
QDateTime time = QDateTime::fromMSecsSinceEpoch(timeInt);
res.push_back({id, sender,message,time,isSent});
res.push_back(HistMessage(id, "", sender, message, time, isSent));
}
return res;
}
QList<HistoryKeeper::HistMessage> HistoryKeeper::exportMessages()
{
QSqlQuery dbAnswer;
dbAnswer = db->exec(QString("SELECT history.id, timestamp, user_id, message, status, name FROM history LEFT JOIN sent_status ON history.id = sent_status.id ") +
QString("INNER JOIN aliases ON history.sender = aliases.id INNER JOIN chats ON history.chat_id = chats.id;"));
QList<HistMessage> res;
while (dbAnswer.next())
{
qint64 id = dbAnswer.value(0).toLongLong();
qint64 timeInt = dbAnswer.value(1).toLongLong();
QString sender = dbAnswer.value(2).toString();
QString message = unWrapMessage(dbAnswer.value(3).toString());
bool isSent = true;
if (!dbAnswer.value(4).isNull())
isSent = dbAnswer.value(4).toBool();
QString chat = dbAnswer.value(5).toString();
QDateTime time = QDateTime::fromMSecsSinceEpoch(timeInt);
res.push_back(HistMessage(id, chat, sender, message, time, isSent));
}
return res;
}
void HistoryKeeper::importMessages(const QList<HistoryKeeper::HistMessage> &lst)
{
db->exec("BEGIN TRANSACTION;");
for (const HistMessage &msg : lst)
{
QList<QString> cmds = generateAddChatEntryCmd(msg.chat, msg.message, msg.sender, msg.timestamp, msg.isSent);
for (auto &it : cmds)
db->exec(it);
messageID++;
}
db->exec("COMMIT TRANSACTION;");
}
QList<QString> HistoryKeeper::generateAddChatEntryCmd(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent)
{
QList<QString> cmds;
int chat_id = getChatID(chat, ctSingle).first;
int sender_id = getAliasID(sender);
cmds.push_back(QString("INSERT INTO history (timestamp, chat_id, sender, message) VALUES (%1, %2, %3, '%4');")
.arg(dt.toMSecsSinceEpoch()).arg(chat_id).arg(sender_id).arg(wrapMessage(message)));
cmds.push_back(QString("INSERT INTO sent_status (status) VALUES (%1);").arg(isSent));
return cmds;
}
QString HistoryKeeper::wrapMessage(const QString &str)
{
QString wrappedMessage(str);
@ -272,7 +319,7 @@ void HistoryKeeper::resetInstance()
historyInstance = nullptr;
}
int HistoryKeeper::addGroupChatEntry(const QString &chat, const QString &message, const QString &sender, const QDateTime &dt)
qint64 HistoryKeeper::addGroupChatEntry(const QString &chat, const QString &message, const QString &sender, const QDateTime &dt)
{
Q_UNUSED(chat)
Q_UNUSED(message)
@ -342,3 +389,29 @@ void HistoryKeeper::setSyncType(Db::syncType sType)
db->exec(QString("PRAGMA synchronous=%1;").arg(syncCmd));
}
bool HistoryKeeper::isFileExist()
{
QString path = getHistoryPath();
QFile file(path);
return file.exists();
}
bool HistoryKeeper::removeHistory(int encrypted)
{
resetInstance();
QString path = getHistoryPath(QString(), encrypted);
QFile DbFile(path);
return DbFile.remove();
}
QList<HistoryKeeper::HistMessage> HistoryKeeper::exportMessagesDeleteFile(int encrypted)
{
auto msgs = getInstance()->exportMessages();
qDebug() << "HistoryKeeper: count" << msgs.size() << "messages exported";
if (!removeHistory(encrypted))
qWarning() << "HistoryKeeper: couldn't delete old log file!";
return msgs;
}

View File

@ -31,7 +31,11 @@ public:
struct HistMessage
{
HistMessage(qint64 id, QString chat, QString sender, QString message, QDateTime timestamp, bool isSent) :
id(id), chat(chat), sender(sender), message(message), timestamp(timestamp), isSent(isSent) {}
qint64 id;
QString chat;
QString sender;
QString message;
QDateTime timestamp;
@ -44,14 +48,20 @@ public:
static void resetInstance();
static QString getHistoryPath(QString currentProfile = QString(), int encrypted = -1); // -1 defaults to checking settings, 0 or 1 to specify
static bool checkPassword();
static bool checkPassword(int encrypted = -1);
static bool isFileExist();
static void renameHistory(QString from, QString to);
static bool removeHistory(int encrypted = -1);
static QList<HistMessage> exportMessagesDeleteFile(int encrypted = -1);
int addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent);
int addGroupChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt);
qint64 addChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent);
qint64 addGroupChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt);
QList<HistMessage> getChatHistory(ChatType ct, const QString &chat, const QDateTime &time_from, const QDateTime &time_to);
void markAsSent(int m_id);
QList<HistMessage> exportMessages();
void importMessages(const QList<HistoryKeeper::HistMessage> &lst);
void setSyncType(Db::syncType sType);
private:
@ -63,15 +73,16 @@ private:
void updateAliases();
QPair<int, ChatType> getChatID(const QString &id_str, ChatType ct);
int getAliasID(const QString &id_str);
QString wrapMessage(const QString &str);
QString unWrapMessage(const QString &str);
QList<QString> generateAddChatEntryCmd(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt, bool isSent);
static QString wrapMessage(const QString &str);
static QString unWrapMessage(const QString &str);
static ChatType convertToChatType(int);
ChatType convertToChatType(int);
GenericDdInterface *db;
QMap<QString, int> aliases;
QMap<QString, QPair<int, ChatType>> chats;
int messageID;
qint64 messageID;
};
#endif // HISTORYKEEPER_H

View File

@ -16,6 +16,7 @@
#include "widget/widget.h"
#include "misc/settings.h"
#include "src/nexus.h"
#include "src/ipc.h"
#include "src/widget/toxuri.h"
#include "src/widget/toxsave.h"
@ -70,13 +71,14 @@ int main(int argc, char *argv[])
parser.process(a);
Settings::getInstance(); // Build our Settings singleton as soon as QApplication is ready, not before
if (parser.isSet("p"))
{
QString profile = parser.value("p");
if (QDir(Settings::getSettingsDirPath()).exists(profile + ".tox"))
{
qDebug() << "Setting profile to" << profile;
Settings::getInstance().setCurrentProfile(profile);
Settings::getInstance().switchProfile(profile);
}
else
{
@ -121,6 +123,9 @@ int main(int argc, char *argv[])
AutoUpdater::installLocalUpdate(); ///< NORETURN
#endif
Nexus::getInstance().start();
#ifndef Q_OS_ANDROID
// Inter-process communication
IPC ipc;
ipc.registerEventHandler(&toxURIEventHandler);
@ -175,17 +180,18 @@ int main(int argc, char *argv[])
if (!ipc.isCurrentOwner())
return EXIT_SUCCESS;
}
#endif
// Run
a.setQuitOnLastWindowClosed(false);
Widget* w = Widget::getInstance();
int errorcode = a.exec();
delete w;
#ifdef LOG_TO_FILE
delete logFile;
logFile = nullptr;
#endif
Nexus::destroyInstance();
return errorcode;
}

View File

@ -980,6 +980,10 @@ QSplitter:handle{
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../res.qrc">
<normaloff>:/img/status/dot_away.png</normaloff>:/img/status/dot_away.png</iconset>
</property>
<property name="iconSize">
<size>
<width>10</width>
@ -1775,7 +1779,7 @@ QSplitter:handle{
<x>0</x>
<y>0</y>
<width>775</width>
<height>21</height>
<height>20</height>
</rect>
</property>
</widget>

View File

@ -24,28 +24,36 @@
#include <QDebug>
#include <QSqlError>
qint64 EncryptedDb::plainChunkSize = 4096;
qint64 EncryptedDb::encryptedChunkSize = EncryptedDb::plainChunkSize + tox_pass_encryption_extra_length();
qint64 EncryptedDb::encryptedChunkSize = 4096;
qint64 EncryptedDb::plainChunkSize = EncryptedDb::encryptedChunkSize - tox_pass_encryption_extra_length();
EncryptedDb::EncryptedDb(const QString &fname, QList<QString> initList) :
PlainDb(":memory:", initList), encrFile(fname)
PlainDb(":memory:", initList), fileName(fname)
{
QByteArray fileContent;
if (pullFileContent())
if (pullFileContent(fileName, buffer))
{
chunkPosition = encrFile.size() / encryptedChunkSize;
encrFile.seek(0);
qDebug() << "EncryptedDb: writing old data";
encrFile.setFileName(fileName);
encrFile.open(QIODevice::ReadOnly);
fileContent = encrFile.readAll();
} else {
qWarning() << "corrupted history log file will be wiped!";
chunkPosition = 0;
chunkPosition = encrFile.size() / encryptedChunkSize;
encrFile.close();
}
else
chunkPosition = 0;
encrFile.close();
encrFile.open(QIODevice::WriteOnly);
encrFile.write(fileContent);
encrFile.flush();
encrFile.setFileName(fileName);
if (!encrFile.open(QIODevice::WriteOnly))
{
qWarning() << "can't open file:" << fileName;
}
else
{
encrFile.write(fileContent);
encrFile.flush();
}
}
EncryptedDb::~EncryptedDb()
@ -57,27 +65,34 @@ EncryptedDb::~EncryptedDb()
QSqlQuery EncryptedDb::exec(const QString &query)
{
QSqlQuery retQSqlQuery = PlainDb::exec(query);
if (query.startsWith("INSERT", Qt::CaseInsensitive))
if (checkCmd(query))
appendToEncrypted(query);
return retQSqlQuery;
}
bool EncryptedDb::pullFileContent()
bool EncryptedDb::pullFileContent(const QString &fname, QByteArray &buf)
{
encrFile.open(QIODevice::ReadOnly);
QFile dbFile(fname);
if (!dbFile.open(QIODevice::ReadOnly))
{
qDebug() << "EncryptedDb::pullFileContent: file doesn't exist";
buf = QByteArray();
return false;
}
QByteArray fileContent;
while (!encrFile.atEnd())
while (!dbFile.atEnd())
{
QByteArray encrChunk = encrFile.read(encryptedChunkSize);
buffer = Core::getInstance()->decryptData(encrChunk, Core::ptHistory);
if (buffer.size() > 0)
QByteArray encrChunk = dbFile.read(encryptedChunkSize);
qDebug() << "EncryptedDb::pullFileContent: got chunk:" << encrChunk.size();
buf = Core::getInstance()->decryptData(encrChunk, Core::ptHistory);
if (buf.size() > 0)
{
fileContent += buffer;
fileContent += buf;
} else {
qWarning() << "Encrypted history log is corrupted: can't decrypt";
buffer = QByteArray();
qWarning() << "EncryptedDb::pullFileContent: Encrypted history log is corrupted: can't decrypt, will be deleted";
buf = QByteArray();
return false;
}
}
@ -88,31 +103,17 @@ bool EncryptedDb::pullFileContent()
for (auto ba_line : splittedBA)
{
QString line = QByteArray::fromBase64(ba_line);
if (line.size() == 0)
continue;
bool isGoodLine = false;
if (line.startsWith("CREATE", Qt::CaseInsensitive) || line.startsWith("INSERT", Qt::CaseInsensitive))
{
if (line.endsWith(");"))
{
sqlCmds.append(line);
isGoodLine = true;
}
}
if (!isGoodLine)
{
qWarning() << "Encrypted history log is corrupted: errors in content";
buffer = QByteArray();
return false;
}
sqlCmds.append(line);
}
PlainDb::exec("BEGIN TRANSACTION;");
for (auto line : sqlCmds)
{
QSqlQuery r = PlainDb::exec(line);
}
PlainDb::exec("COMMIT TRANSACTION;");
dbFile.close();
return true;
}
@ -133,6 +134,7 @@ void EncryptedDb::appendToEncrypted(const QString &sql)
if (encr.size() > 0)
{
encrFile.write(encr);
encrFile.flush();
}
buffer = buffer.right(buffer.size() - plainChunkSize);
@ -170,3 +172,14 @@ bool EncryptedDb::check(const QString &fname)
file.close();
return state;
}
bool EncryptedDb::checkCmd(const QString &cmd)
{
if (cmd.startsWith("INSERT", Qt::CaseInsensitive) || cmd.startsWith("UPDATE", Qt::CaseInsensitive)
|| cmd.startsWith("DELETE", Qt::CaseInsensitive))
{
return true;
}
return false;
}

View File

@ -32,10 +32,12 @@ public:
static bool check(const QString &fname);
private:
bool pullFileContent();
bool pullFileContent(const QString& fname, QByteArray &buf);
void appendToEncrypted(const QString &sql);
bool checkCmd(const QString &cmd);
QFile encrFile;
QString fileName;
static qint64 plainChunkSize;
static qint64 encryptedChunkSize;

View File

@ -18,6 +18,8 @@
#include "smileypack.h"
#include "src/corestructs.h"
#include "src/misc/db/plaindb.h"
#include "src/core.h"
#include "src/widget/gui.h"
#include <QFont>
#include <QApplication>
@ -58,6 +60,77 @@ void Settings::resetInstance()
}
}
void Settings::switchProfile(const QString& profile)
{
setCurrentProfile(profile);
save(false);
resetInstance();
}
QString Settings::detectProfile()
{
QDir dir(getSettingsDirPath());
QString path, profile = getCurrentProfile();
path = dir.filePath(profile + Core::TOX_EXT);
QFile file(path);
if (profile.isEmpty() || !file.exists())
{
setCurrentProfile("");
#if 1 // deprecation attempt
// if the last profile doesn't exist, fall back to old "data"
path = dir.filePath(Core::CONFIG_FILE_NAME);
QFile file(path);
if (file.exists())
return path;
else if (QFile(path = dir.filePath("tox_save")).exists()) // also import tox_save if no data
return path;
else
#endif
{
profile = askProfiles();
if (profile.isEmpty())
return "";
else
{
switchProfile(profile);
return dir.filePath(profile + Core::TOX_EXT);
}
}
}
else
return path;
}
QList<QString> Settings::searchProfiles()
{
QList<QString> out;
QDir dir(getSettingsDirPath());
dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
dir.setNameFilters(QStringList("*.tox"));
for (QFileInfo file : dir.entryInfoList())
out += file.completeBaseName();
return out;
}
QString Settings::askProfiles()
{ // TODO: allow user to create new Tox ID, even if a profile already exists
QList<QString> profiles = searchProfiles();
if (profiles.empty()) return "";
bool ok;
QString profile = GUI::itemInputDialog(nullptr,
tr("Choose a profile"),
tr("Please choose which identity to use"),
profiles,
0, // which slot to start on
false, // if the user can enter their own input
&ok);
if (!ok) // user cancelled
return "";
else
return profile;
}
void Settings::load()
{
if (loaded)
@ -186,13 +259,6 @@ void Settings::load()
splitterState = s.value("splitterState", QByteArray()).toByteArray();
s.endGroup();
s.beginGroup("Privacy");
typingNotification = s.value("typingNotification", true).toBool();
enableLogging = s.value("enableLogging", false).toBool();
encryptLogs = s.value("encryptLogs", false).toBool();
encryptTox = s.value("encryptTox", false).toBool();
s.endGroup();
s.beginGroup("Audio");
inDev = s.value("inDev", "").toString();
outDev = s.value("outDev", "").toString();
@ -225,38 +291,45 @@ void Settings::load()
loaded = true;
if (currentProfile.isEmpty()) // new profile in Core::switchConfiguration
return;
if (!currentProfile.isEmpty()) // new profile in Core::switchConfiguration
{
// load from a profile specific friend data list if possible
QString tmp = dir.filePath(currentProfile + ".ini");
if (QFile(tmp).exists()) // otherwise, filePath remains the global file
filePath = tmp;
// load from a profile specific friend data list if possible
QString tmp = dir.filePath(currentProfile + ".ini");
if (QFile(tmp).exists())
filePath = tmp;
QSettings ps(filePath, QSettings::IniFormat);
friendLst.clear();
ps.beginGroup("Friends");
int size = ps.beginReadArray("Friend");
for (int i = 0; i < size; i ++)
{
ps.setArrayIndex(i);
friendProp fp;
fp.addr = ps.value("addr").toString();
fp.alias = ps.value("alias").toString();
fp.autoAcceptDir = ps.value("autoAcceptDir").toString();
friendLst[ToxID::fromString(fp.addr).publicKey] = fp;
}
ps.endArray();
ps.endGroup();
QSettings fs(filePath, QSettings::IniFormat);
friendLst.clear();
fs.beginGroup("Friends");
int size = fs.beginReadArray("Friend");
for (int i = 0; i < size; i ++)
{
fs.setArrayIndex(i);
friendProp fp;
fp.addr = fs.value("addr").toString();
fp.alias = fs.value("alias").toString();
fp.autoAcceptDir = fs.value("autoAcceptDir").toString();
friendLst[ToxID::fromString(fp.addr).publicKey] = fp;
}
fs.endArray();
fs.endGroup();
ps.beginGroup("Privacy");
typingNotification = ps.value("typingNotification", false).toBool();
enableLogging = ps.value("enableLogging", false).toBool();
encryptLogs = ps.value("encryptLogs", false).toBool();
encryptTox = ps.value("encryptTox", false).toBool();
ps.endGroup();
}
}
void Settings::save(bool writeFriends)
void Settings::save(bool writePersonal)
{
QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME);
save(filePath, writeFriends);
save(filePath, writePersonal);
}
void Settings::save(QString path, bool writeFriends)
void Settings::save(QString path, bool writePersonal)
{
qDebug() << "Settings: Saving in "<<path;
@ -336,13 +409,6 @@ void Settings::save(QString path, bool writeFriends)
s.setValue("splitterState", splitterState);
s.endGroup();
s.beginGroup("Privacy");
s.setValue("typingNotification", typingNotification);
s.setValue("enableLogging", enableLogging);
s.setValue("encryptLogs", encryptLogs);
s.setValue("encryptTox", encryptTox);
s.endGroup();
s.beginGroup("Audio");
s.setValue("inDev", inDev);
s.setValue("outDev", outDev);
@ -353,23 +419,30 @@ void Settings::save(QString path, bool writeFriends)
s.setValue("camVideoRes",camVideoRes);
s.endGroup();
if (!writeFriends || currentProfile.isEmpty()) // Core::switchConfiguration
return;
if (writePersonal && !currentProfile.isEmpty()) // Core::switchConfiguration
{
QSettings ps(QFileInfo(path).dir().filePath(currentProfile + ".ini"), QSettings::IniFormat);
ps.beginGroup("Friends");
ps.beginWriteArray("Friend", friendLst.size());
int index = 0;
for (auto& frnd : friendLst)
{
ps.setArrayIndex(index);
ps.setValue("addr", frnd.addr);
ps.setValue("alias", frnd.alias);
ps.setValue("autoAcceptDir", frnd.autoAcceptDir);
index++;
}
ps.endArray();
ps.endGroup();
QSettings fs(QFileInfo(path).dir().filePath(currentProfile + ".ini"), QSettings::IniFormat);
fs.beginGroup("Friends");
fs.beginWriteArray("Friend", friendLst.size());
int index = 0;
for (auto& frnd : friendLst)
{
fs.setArrayIndex(index);
fs.setValue("addr", frnd.addr);
fs.setValue("alias", frnd.alias);
fs.setValue("autoAcceptDir", frnd.autoAcceptDir);
index++;
}
fs.endArray();
fs.endGroup();
ps.beginGroup("Privacy");
ps.setValue("typingNotification", typingNotification);
ps.setValue("enableLogging", enableLogging);
ps.setValue("encryptLogs", encryptLogs);
ps.setValue("encryptTox", encryptTox);
ps.endGroup();
}
}
QString Settings::getSettingsDirPath()

View File

@ -32,6 +32,10 @@ class Settings : public QObject
public:
static Settings& getInstance();
static void resetInstance();
void switchProfile(const QString& profile);
QString detectProfile();
QList<QString> searchProfiles();
QString askProfiles();
~Settings() = default;
void executeSettingsDialog(QWidget* parent);
@ -236,8 +240,8 @@ public:
void setCompactLayout(bool compact);
public:
void save(bool writeFriends = true);
void save(QString path, bool writeFriends = true);
void save(bool writePersonal = true);
void save(QString path, bool writePersonal = true);
void load();
private:

View File

@ -16,10 +16,7 @@
#include "style.h"
#include "settings.h"
#include "src/widget/widget.h"
#include "ui_mainwindow.h"
#include "src/widget/genericchatroomwidget.h"
#include "src/widget/gui.h"
#include <QFile>
#include <QDebug>
@ -198,11 +195,9 @@ void Style::setThemeColor(QColor color)
dict["@themeMediumDark"] = getColor(ThemeMediumDark).name();
dict["@themeMedium"] = getColor(ThemeMedium).name();
dict["@themeLight"] = getColor(ThemeLight).name();
applyTheme();
}
void Style::applyTheme()
{
Widget::getInstance()->reloadTheme();
GUI::reloadTheme();
}

156
src/nexus.cpp Normal file
View File

@ -0,0 +1,156 @@
#include "nexus.h"
#include "core.h"
#include "misc/settings.h"
#include "video/camera.h"
#include "widget/gui.h"
#include <QThread>
#include <QDebug>
#ifdef Q_OS_ANDROID
#include <src/widget/androidgui.h>
#else
#include <src/widget/widget.h>
#endif
static Nexus* nexus{nullptr};
Nexus::Nexus(QObject *parent) :
QObject(parent),
core{nullptr},
coreThread{nullptr},
widget{nullptr},
androidgui{nullptr},
started{false}
{
}
Nexus::~Nexus()
{
delete core;
delete coreThread;
#ifdef Q_OS_ANDROID
delete androidgui;
#else
delete widget;
#endif
}
void Nexus::start()
{
if (started)
return;
qDebug() << "Nexus: Starting up";
// Setup the environment
qRegisterMetaType<Status>("Status");
qRegisterMetaType<vpx_image>("vpx_image");
qRegisterMetaType<uint8_t>("uint8_t");
qRegisterMetaType<uint16_t>("uint16_t");
qRegisterMetaType<const int16_t*>("const int16_t*");
qRegisterMetaType<int32_t>("int32_t");
qRegisterMetaType<int64_t>("int64_t");
qRegisterMetaType<QPixmap>("QPixmap");
qRegisterMetaType<ToxFile>("ToxFile");
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
qRegisterMetaType<Core::PasswordType>("Core::PasswordType");
// Create Core
QString profilePath = Settings::getInstance().detectProfile();
coreThread = new QThread(this);
coreThread->setObjectName("qTox Core");
core = new Core(Camera::getInstance(), coreThread, profilePath);
core->moveToThread(coreThread);
connect(coreThread, &QThread::started, core, &Core::start);
// Start GUI
#ifdef Q_OS_ANDROID
androidgui = new AndroidGUI;
androidgui->show();
#else
widget = Widget::getInstance();
#endif
GUI::getInstance();
// Connections
#ifdef Q_OS_ANDROID
connect(core, &Core::connected, androidgui, &AndroidGUI::onConnected);
connect(core, &Core::disconnected, androidgui, &AndroidGUI::onDisconnected);
//connect(core, &Core::failedToStart, androidgui, &AndroidGUI::onFailedToStartCore);
//connect(core, &Core::badProxy, androidgui, &AndroidGUI::onBadProxyCore);
connect(core, &Core::statusSet, androidgui, &AndroidGUI::onStatusSet);
connect(core, &Core::usernameSet, androidgui, &AndroidGUI::setUsername);
connect(core, &Core::statusMessageSet, androidgui, &AndroidGUI::setStatusMessage);
connect(core, &Core::selfAvatarChanged, androidgui, &AndroidGUI::onSelfAvatarLoaded);
connect(androidgui, &AndroidGUI::statusSet, core, &Core::setStatus);
//connect(androidgui, &AndroidGUI::friendRequested, core, &Core::requestFriendship);
//connect(androidgui, &AndroidGUI::friendRequestAccepted, core, &Core::acceptFriendRequest);
//connect(androidgui, &AndroidGUI::changeProfile, core, &Core::switchConfiguration);
#else
connect(core, &Core::connected, widget, &Widget::onConnected);
connect(core, &Core::disconnected, widget, &Widget::onDisconnected);
connect(core, &Core::failedToStart, widget, &Widget::onFailedToStartCore);
connect(core, &Core::badProxy, widget, &Widget::onBadProxyCore);
connect(core, &Core::statusSet, widget, &Widget::onStatusSet);
connect(core, &Core::usernameSet, widget, &Widget::setUsername);
connect(core, &Core::statusMessageSet, widget, &Widget::setStatusMessage);
connect(core, &Core::selfAvatarChanged, widget, &Widget::onSelfAvatarLoaded);
connect(core, &Core::friendAdded, widget, &Widget::addFriend);
connect(core, &Core::failedToAddFriend, widget, &Widget::addFriendFailed);
connect(core, &Core::friendUsernameChanged, widget, &Widget::onFriendUsernameChanged);
connect(core, &Core::friendStatusChanged, widget, &Widget::onFriendStatusChanged);
connect(core, &Core::friendStatusMessageChanged, widget, &Widget::onFriendStatusMessageChanged);
connect(core, &Core::friendRequestReceived, widget, &Widget::onFriendRequestReceived);
connect(core, &Core::friendMessageReceived, widget, &Widget::onFriendMessageReceived);
connect(core, &Core::receiptRecieved, widget, &Widget::onReceiptRecieved);
connect(core, &Core::groupInviteReceived, widget, &Widget::onGroupInviteReceived);
connect(core, &Core::groupMessageReceived, widget, &Widget::onGroupMessageReceived);
connect(core, &Core::groupNamelistChanged, widget, &Widget::onGroupNamelistChanged);
connect(core, &Core::groupTitleChanged, widget, &Widget::onGroupTitleChanged);
connect(core, &Core::emptyGroupCreated, widget, &Widget::onEmptyGroupCreated);
connect(core, &Core::avInvite, widget, &Widget::playRingtone);
connect(core, &Core::blockingClearContacts, widget, &Widget::clearContactsList, Qt::BlockingQueuedConnection);
connect(core, &Core::friendTypingChanged, widget, &Widget::onFriendTypingChanged);
connect(core, SIGNAL(messageSentResult(int,QString,int)), widget, SLOT(onMessageSendResult(int,QString,int)));
connect(core, SIGNAL(groupSentResult(int,QString,int)), widget, SLOT(onGroupSendResult(int,QString,int)));
connect(widget, &Widget::statusSet, core, &Core::setStatus);
connect(widget, &Widget::friendRequested, core, &Core::requestFriendship);
connect(widget, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest);
connect(widget, &Widget::changeProfile, core, &Core::switchConfiguration);
#endif
// Start Core
coreThread->start();
started = true;
}
Nexus& Nexus::getInstance()
{
if (!nexus)
nexus = new Nexus;
return *nexus;
}
void Nexus::destroyInstance()
{
delete nexus;
nexus = nullptr;
}
Core* Nexus::getCore()
{
return getInstance().core;
}
AndroidGUI* Nexus::getAndroidGUI()
{
return getInstance().androidgui;
}
Widget* Nexus::getDesktopGUI()
{
return getInstance().widget;
}

38
src/nexus.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef NEXUS_H
#define NEXUS_H
#include <QObject>
class QThread;
class Core;
class Widget;
class AndroidGUI;
/// This class is in charge of connecting various systems together
/// and forwarding signals appropriately to the right objects
/// It is in charge of starting the GUI and the Core
class Nexus : public QObject
{
Q_OBJECT
public:
void start(); ///< Will initialise the systems (GUI, Core, ...)
static Nexus& getInstance();
static void destroyInstance();
static Core* getCore(); ///< Will return 0 if not started
static AndroidGUI* getAndroidGUI(); ///< Will return 0 if not started
static Widget* getDesktopGUI(); ///< Will return 0 if not started
private:
explicit Nexus(QObject *parent = 0);
~Nexus();
private:
Core* core;
QThread* coreThread;
Widget* widget;
AndroidGUI* androidgui;
bool started;
};
#endif // NEXUS_H

121
src/offlinemsgengine.cpp Normal file
View File

@ -0,0 +1,121 @@
/*
Copyright (C) 2015 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#include "offlinemsgengine.h"
#include "src/friend.h"
#include "src/historykeeper.h"
#include "src/misc/settings.h"
#include "src/core.h"
#include <QMutexLocker>
#include <QTimer>
const int OfflineMsgEngine::offlineTimeout = 2000;
QSet<OfflineMsgEngine*> OfflineMsgEngine::engines;
QMutex OfflineMsgEngine::globalMutex;
OfflineMsgEngine::OfflineMsgEngine(Friend *frnd) :
mutex(QMutex::Recursive),
f(frnd)
{
engines.insert(this);
}
OfflineMsgEngine::~OfflineMsgEngine()
{
engines.remove(this);
}
void OfflineMsgEngine::dischargeReceipt(int receipt)
{
QMutexLocker ml(&mutex);
auto it = receipts.find(receipt);
if (it != receipts.end())
{
int mID = it.value();
auto msgIt = undeliveredMsgs.find(mID);
if (msgIt != undeliveredMsgs.end())
{
HistoryKeeper::getInstance()->markAsSent(mID);
msgIt.value().msg->markAsSent();
msgIt.value().msg->featureUpdate();
undeliveredMsgs.erase(msgIt);
}
receipts.erase(it);
}
}
void OfflineMsgEngine::registerReceipt(int receipt, int messageID, MessageActionPtr msg, const QDateTime &timestamp)
{
QMutexLocker ml(&mutex);
receipts[receipt] = messageID;
undeliveredMsgs[messageID] = {msg, timestamp, receipt};
}
void OfflineMsgEngine::deliverOfflineMsgs()
{
QMutexLocker ml(&mutex);
if (!Settings::getInstance().getFauxOfflineMessaging())
return;
if (f->getStatus() == Status::Offline)
return;
if (undeliveredMsgs.size() == 0)
return;
QMap<int, MsgPtr> msgs = undeliveredMsgs;
removeAllReciepts();
for (auto iter = msgs.begin(); iter != msgs.end(); iter++)
{
if (iter.value().timestamp.msecsTo(QDateTime::currentDateTime()) < offlineTimeout)
{
registerReceipt(iter.value().receipt, iter.key(), iter.value().msg, iter.value().timestamp);
continue;
}
QString messageText = iter.value().msg->getRawMessage();
int rec;
if (iter.value().msg->isAction())
rec = Core::getInstance()->sendAction(f->getFriendID(), messageText);
else
rec = Core::getInstance()->sendMessage(f->getFriendID(), messageText);
registerReceipt(rec, iter.key(), iter.value().msg);
}
}
void OfflineMsgEngine::removeAllReciepts()
{
QMutexLocker ml(&mutex);
receipts.clear();
undeliveredMsgs.clear();
}
void OfflineMsgEngine::processAllMsgs()
{
if (globalMutex.tryLock())
{
for (auto &it : engines)
{
it->deliverOfflineMsgs();
}
globalMutex.unlock();
}
}

61
src/offlinemsgengine.h Normal file
View File

@ -0,0 +1,61 @@
/*
Copyright (C) 2015 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#ifndef OFFLINEMSGENGINE_H
#define OFFLINEMSGENGINE_H
#include <QObject>
#include <QSet>
#include <QMutex>
#include <QDateTime>
#include "src/widget/tool/chatactions/messageaction.h"
struct Friend;
class QTimer;
class OfflineMsgEngine : public QObject
{
Q_OBJECT
public:
OfflineMsgEngine(Friend *);
virtual ~OfflineMsgEngine();
void dischargeReceipt(int receipt);
void registerReceipt(int receipt, int messageID, MessageActionPtr msg, const QDateTime &timestamp = QDateTime::currentDateTime());
public slots:
void deliverOfflineMsgs();
void removeAllReciepts();
static void processAllMsgs();
private:
struct MsgPtr {
MessageActionPtr msg;
QDateTime timestamp;
int receipt;
};
QMutex mutex;
Friend* f;
QHash<int, int> receipts;
QMap<int, MsgPtr> undeliveredMsgs;
static QSet<OfflineMsgEngine*> engines;
static const int offlineTimeout;
static QMutex globalMutex;
};
#endif // OFFLINEMSGENGINE_H

View File

@ -220,8 +220,10 @@ fallbackOnTox1:
#if TOX1_SILENT_FALLBACK
toxIdStr = queryTox1(record, silent);
#elif TOX1_ASK_FALLBACK
QMessageBox::StandardButton btn = QMessageBox::warning(nullptr, "qTox", tr("It appears that qTox has to use the old tox1 protocol.\n\
Unfortunately tox1 is not secure. Should it be used anyway?"), QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
QMessageBox::StandardButton btn = QMessageBox::warning(nullptr, "qTox", tr("It appears that qTox has to use the old tox1 protocol to access DNS record of your friend's Tox ID.\n\
Unfortunately tox1 is not secure, and you are at risk of someone hijacking what is sent between you and ToxDNS service.\n\
Should tox1 be used anyway?\n\
If unsure, press No, so that request to ToxDNS service will not be made using unsecure protocol."), QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
if (btn == QMessageBox::Yes)
queryTox1(record, silent);
#endif
@ -264,8 +266,10 @@ ToxID ToxDNS::resolveToxAddress(const QString &address, bool silent)
#if TOX1_SILENT_FALLBACK
toxId = ToxID::fromString(queryTox1(address, silent));
#elif TOX1_ASK_FALLBACK
QMessageBox::StandardButton btn = QMessageBox::warning(nullptr, "qTox", tr("It appears that qTox has to use the old tox1 protocol.\n\
Unfortunately tox1 is not secure. Should it be used anyway?"), QMessageBox::Ok|QMessageBox::No, QMessageBox::No);
QMessageBox::StandardButton btn = QMessageBox::warning(nullptr, "qTox", tr("It appears that qTox has to use the old tox1 protocol to access DNS record of your friend's Tox ID.\n\
Unfortunately tox1 is not secure, and you are at risk of someone hijacking what is sent between you and ToxDNS service.\n\
Should tox1 be used anyway?\n\
If unsure, press No, so that request to ToxDNS service will not be made using unsecure protocol."), QMessageBox::Ok|QMessageBox::No, QMessageBox::No);
if (btn == QMessageBox::Ok)
toxId = ToxID::fromString(queryTox1(address, silent));
#else

169
src/widget/androidgui.cpp Normal file
View File

@ -0,0 +1,169 @@
#include "androidgui.h"
#include "ui_android.h"
#include "friendlistwidget.h"
#include "maskablepixmapwidget.h"
#include "src/core.h"
#include "src/friend.h"
#include "src/friendlist.h"
#include "src/group.h"
#include "src/grouplist.h"
#include "src/misc/settings.h"
#include "src/misc/style.h"
#include "src/nexus.h"
#include "src/widget/friendwidget.h"
#include "src/widget/groupwidget.h"
#include <QLabel>
#include <QMenu>
AndroidGUI::AndroidGUI(QWidget *parent) :
QWidget(parent),
ui{new Ui::Android}
{
ui->setupUi(this);
ui->friendList->setStyleSheet(Style::resolve(Style::getStylesheet(":ui/friendList/friendList.css")));
profilePicture = new MaskablePixmapWidget(this, QSize(40, 40), ":/img/avatar_mask.png");
profilePicture->setPixmap(QPixmap(":/img/contact_dark.png"));
profilePicture->setClickable(true);
ui->myProfile->insertWidget(0, profilePicture);
ui->myProfile->insertSpacing(1, 7);
ui->tooliconsZone->setStyleSheet(Style::resolve("QPushButton{background-color:@themeDark;border:none;}QPushButton:hover{background-color:@themeMediumDark;border:none;}"));
ui->statusHead->setStyleSheet(Style::getStylesheet(":/ui/window/statusPanel.css"));
contactListWidget = new FriendListWidget();
ui->friendList->setWidget(contactListWidget);
ui->friendList->setLayoutDirection(Qt::RightToLeft);
ui->nameLabel->setEditable(true);
ui->statusLabel->setEditable(true);
ui->statusPanel->setStyleSheet(Style::getStylesheet(":/ui/window/statusPanel.css"));
QMenu *statusButtonMenu = new QMenu(ui->statusButton);
QAction* setStatusOnline = statusButtonMenu->addAction(AndroidGUI::tr("Online","Button to set your status to 'Online'"));
setStatusOnline->setIcon(QIcon(":ui/statusButton/dot_online.png"));
QAction* setStatusAway = statusButtonMenu->addAction(AndroidGUI::tr("Away","Button to set your status to 'Away'"));
setStatusAway->setIcon(QIcon(":ui/statusButton/dot_idle.png"));
QAction* setStatusBusy = statusButtonMenu->addAction(AndroidGUI::tr("Busy","Button to set your status to 'Busy'"));
setStatusBusy->setIcon(QIcon(":ui/statusButton/dot_busy.png"));
ui->statusButton->setMenu(statusButtonMenu);
ui->statusButton->setProperty("status", "offline");
Style::repolish(ui->statusButton);
// Disable some widgets until we're connected to the DHT
ui->statusButton->setEnabled(false);
Style::setThemeColor(Settings::getInstance().getThemeColor());
Style::setThemeColor(1);
reloadTheme();
connect(ui->nameLabel, &CroppingLabel::textChanged, this, &AndroidGUI::onUsernameChanged);
connect(ui->statusLabel, &CroppingLabel::textChanged, this, &AndroidGUI::onStatusMessageChanged);
}
AndroidGUI::~AndroidGUI()
{
delete profilePicture;
delete contactListWidget;
}
void AndroidGUI::reloadTheme()
{
QString statusPanelStyle = Style::getStylesheet(":/ui/window/statusPanel.css");
ui->tooliconsZone->setStyleSheet(Style::resolve("QPushButton{background-color:@themeDark;border:none;}QPushButton:hover{background-color:@themeMediumDark;border:none;}"));
ui->statusPanel->setStyleSheet(statusPanelStyle);
ui->statusHead->setStyleSheet(statusPanelStyle);
ui->friendList->setStyleSheet(Style::getStylesheet(":ui/friendList/friendList.css"));
ui->statusButton->setStyleSheet(Style::getStylesheet(":ui/statusButton/statusButton.css"));
for (Friend* f : FriendList::getAllFriends())
f->getFriendWidget()->reloadTheme();
for (Group* g : GroupList::getAllGroups())
g->getGroupWidget()->reloadTheme();
}
void AndroidGUI::onSelfAvatarLoaded(const QPixmap& pic)
{
profilePicture->setPixmap(pic);
}
void AndroidGUI::onConnected()
{
ui->statusButton->setEnabled(true);
if (beforeDisconnect == Status::Offline)
emit statusSet(Status::Online);
else
emit statusSet(beforeDisconnect);
}
void AndroidGUI::onDisconnected()
{
QString stat = ui->statusButton->property("status").toString();
if (stat == "online")
beforeDisconnect = Status::Online;
else if (stat == "busy")
beforeDisconnect = Status::Busy;
else if (stat == "away")
beforeDisconnect = Status::Away;
else
beforeDisconnect = Status::Offline;
ui->statusButton->setEnabled(false);
emit statusSet(Status::Offline);
}
void AndroidGUI::onUsernameChanged(const QString& newUsername, const QString& oldUsername)
{
setUsername(oldUsername); // restore old username until Core tells us to set it
Nexus::getCore()->setUsername(newUsername);
}
void AndroidGUI::setUsername(const QString& username)
{
ui->nameLabel->setText(username);
ui->nameLabel->setToolTip(username); // for overlength names
QString sanename = username;
sanename.remove(QRegExp("[\\t\\n\\v\\f\\r\\x0000]"));
nameMention = QRegExp("\\b" + QRegExp::escape(username) + "\\b", Qt::CaseInsensitive);
sanitizedNameMention = QRegExp("\\b" + QRegExp::escape(sanename) + "\\b", Qt::CaseInsensitive);
}
void AndroidGUI::onStatusMessageChanged(const QString& newStatusMessage, const QString& oldStatusMessage)
{
ui->statusLabel->setText(oldStatusMessage); // restore old status message until Core tells us to set it
ui->statusLabel->setToolTip(oldStatusMessage); // for overlength messsages
Nexus::getCore()->setStatusMessage(newStatusMessage);
}
void AndroidGUI::setStatusMessage(const QString &statusMessage)
{
ui->statusLabel->setText(statusMessage);
ui->statusLabel->setToolTip(statusMessage); // for overlength messsages
}
void AndroidGUI::onStatusSet(Status status)
{
//We have to use stylesheets here, there's no way to
//prevent the button icon from moving when pressed otherwise
switch (status)
{
case Status::Online:
ui->statusButton->setProperty("status" ,"online");
break;
case Status::Away:
ui->statusButton->setProperty("status" ,"away");
break;
case Status::Busy:
ui->statusButton->setProperty("status" ,"busy");
break;
case Status::Offline:
ui->statusButton->setProperty("status" ,"offline");
break;
}
Style::repolish(ui->statusButton);
}

52
src/widget/androidgui.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef ANDROIDGUI_H
#define ANDROIDGUI_H
#include "src/corestructs.h"
#include <QWidget>
class MaskablePixmapWidget;
class FriendListWidget;
namespace Ui {
class Android;
}
class AndroidGUI : public QWidget
{
Q_OBJECT
public:
explicit AndroidGUI(QWidget *parent = 0);
~AndroidGUI();
public slots:
void onConnected();
void onDisconnected();
void onStatusSet(Status status);
void onSelfAvatarLoaded(const QPixmap &pic);
void setUsername(const QString& username);
void setStatusMessage(const QString &statusMessage);
signals:
void friendRequestAccepted(const QString& userId);
void friendRequested(const QString& friendAddress, const QString& message);
void statusSet(Status status);
void statusSelected(Status status);
void usernameChanged(const QString& username);
void statusMessageChanged(const QString& statusMessage);
private:
void reloadTheme();
private slots:
void onUsernameChanged(const QString& newUsername, const QString& oldUsername);
void onStatusMessageChanged(const QString& newStatusMessage, const QString& oldStatusMessage);
private:
Ui::Android* ui;
MaskablePixmapWidget* profilePicture;
FriendListWidget* contactListWidget;
Status beforeDisconnect = Status::Offline;
QRegExp nameMention, sanitizedNameMention;
};
#endif // ANDROIDGUI_H

View File

@ -1,5 +1,6 @@
#include "callconfirmwidget.h"
#include "widget.h"
#include "gui.h"
#include <assert.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
@ -11,7 +12,7 @@
#include <QPalette>
CallConfirmWidget::CallConfirmWidget(const QWidget *Anchor) :
QWidget(Widget::getInstance()), anchor(Anchor),
QWidget(GUI::getMainWidget()), anchor(Anchor),
rectW{120}, rectH{85},
spikeW{30}, spikeH{15},
roundedFactor{20},
@ -43,7 +44,7 @@ CallConfirmWidget::CallConfirmWidget(const QWidget *Anchor) :
connect(buttonBox, &QDialogButtonBox::accepted, this, &CallConfirmWidget::accepted);
connect(buttonBox, &QDialogButtonBox::rejected, this, &CallConfirmWidget::rejected);
connect(Widget::getInstance(), &Widget::resized, this, &CallConfirmWidget::reposition);
connect(&GUI::getInstance(), &GUI::resized, this, &CallConfirmWidget::reposition);
layout->setMargin(12);
layout->addSpacing(spikeH);
@ -56,7 +57,7 @@ CallConfirmWidget::CallConfirmWidget(const QWidget *Anchor) :
void CallConfirmWidget::reposition()
{
Widget* w = Widget::getInstance();
QWidget* w = GUI::getMainWidget();
QPoint pos = anchor->mapToGlobal({(anchor->width()-rectW)/2,anchor->height()})-w->mapToGlobal({0,0});
// We don't want the widget to overflow past the right of the screen

View File

@ -28,6 +28,9 @@ CroppingLabel::CroppingLabel(QWidget* parent)
textEdit = new QLineEdit(this);
textEdit->hide();
textEdit->setInputMethodHints(Qt::ImhNoAutoUppercase
| Qt::ImhNoPredictiveText
| Qt::ImhPreferLatin);
installEventFilter(this);
textEdit->installEventFilter(this);

View File

@ -43,6 +43,7 @@
#include "src/chatlog/chatlinecontentproxy.h"
#include "src/chatlog/content/text.h"
#include "src/chatlog/chatlog.h"
#include "src/offlinemsgengine.h"
ChatForm::ChatForm(Friend* chatFriend)
: f(chatFriend)
@ -58,6 +59,7 @@ ChatForm::ChatForm(Friend* chatFriend)
statusMessageLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize());
callConfirm = nullptr;
offlineEngine = new OfflineMsgEngine(f);
typingTimer.setSingleShot(true);
@ -73,7 +75,7 @@ ChatForm::ChatForm(Friend* chatFriend)
headTextLayout->addWidget(callDuration, 1, Qt::AlignCenter);
callDuration->hide();
menu.addAction(tr("Load History..."), this, SLOT(onLoadHistory()));
menu.addAction(tr("Load chat history..."), this, SLOT(onLoadHistory()));
connect(Core::getInstance(), &Core::fileSendStarted, this, &ChatForm::startFileSend);
connect(sendButton, &QPushButton::clicked, this, &ChatForm::onSendTriggered);
@ -85,7 +87,7 @@ ChatForm::ChatForm(Friend* chatFriend)
connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle()));
connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle()));
connect(Core::getInstance(), &Core::fileSendFailed, this, &ChatForm::onFileSendFailed);
connect(this, SIGNAL(chatAreaCleared()), this, SLOT(clearReciepts()));
connect(this, SIGNAL(chatAreaCleared()), getOfflineMsgEngine(), SLOT(removeAllReciepts()));
connect(&typingTimer, &QTimer::timeout, this, [=]{Core::getInstance()->sendTyping(f->getFriendID(), false);});
connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig) {
if (text != orig) emit aliasChanged(text);
@ -139,7 +141,7 @@ void ChatForm::onSendTriggered()
else
rec = Core::getInstance()->sendMessage(f->getFriendID(), qt_msg);
registerReceipt(rec, id, ma);
getOfflineMsgEngine()->registerReceipt(rec, id, ma);
msgEdit->setLastMessage(msg); //set last message only when sending it
}
@ -806,7 +808,8 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered)
rec = Core::getInstance()->sendMessage(f->getFriendID(), msg->toString());
else
rec = Core::getInstance()->sendAction(f->getFriendID(), msg->toString());
registerReceipt(rec, it.id, msg);
getOfflineMsgEngine()->registerReceipt(rec, it.id, msg);
}
}
historyMessages.prepend(msg);
@ -889,29 +892,6 @@ QString ChatForm::secondsToDHMS(quint32 duration)
return cD + res.sprintf("%dd%02dh %02dm %02ds", days, hours, minutes, seconds);
}
void ChatForm::registerReceipt(int receipt, int messageID, ChatMessage::Ptr msg)
{
receipts[receipt] = messageID;
undeliveredMsgs[messageID] = msg;
}
void ChatForm::dischargeReceipt(int receipt)
{
auto it = receipts.find(receipt);
if (it != receipts.end())
{
int mID = it.value();
auto msgIt = undeliveredMsgs.find(mID);
if (msgIt != undeliveredMsgs.end())
{
HistoryKeeper::getInstance()->markAsSent(mID);
msgIt.value()->markAsSent(QDateTime::currentDateTime());
undeliveredMsgs.erase(msgIt);
}
receipts.erase(it);
}
}
void ChatForm::setFriendTyping(bool isTyping)
{
chatWidget->setTypingNotificationVisible(isTyping);
@ -922,32 +902,6 @@ void ChatForm::setFriendTyping(bool isTyping)
text->setText("<div class=typing>" + QString("%1 is typing").arg(f->getDisplayedName()) + "</div>");
}
void ChatForm::clearReciepts()
{
receipts.clear();
undeliveredMsgs.clear();
}
void ChatForm::deliverOfflineMsgs()
{
if (!Settings::getInstance().getFauxOfflineMessaging())
return;
QMap<int, ChatMessage::Ptr> msgs = undeliveredMsgs;
clearReciepts();
for (auto iter = msgs.begin(); iter != msgs.end(); iter++)
{
QString messageText = iter.value()->toString();
int rec;
if (iter.value()->isAction())
rec = Core::getInstance()->sendAction(f->getFriendID(), messageText);
else
rec = Core::getInstance()->sendMessage(f->getFriendID(), messageText);
registerReceipt(rec, iter.key(), iter.value());
}
}
void ChatForm::show(Ui::MainWindow &ui)
{
GenericChatForm::show(ui);
@ -961,3 +915,8 @@ void ChatForm::hideEvent(QHideEvent*)
if (callConfirm)
callConfirm->hide();
}
OfflineMsgEngine *ChatForm::getOfflineMsgEngine()
{
return offlineEngine;
}

View File

@ -32,6 +32,7 @@ class QPixmap;
class CallConfirmWidget;
class QHideEvent;
class QMoveEvent;
class OfflineMsgEngine;
class ChatForm : public GenericChatForm
{
@ -44,6 +45,7 @@ public:
void dischargeReceipt(int receipt);
void setFriendTyping(bool isTyping);
OfflineMsgEngine* getOfflineMsgEngine();
virtual void show(Ui::MainWindow &ui);
@ -60,8 +62,6 @@ signals:
void aliasChanged(const QString& alias);
public slots:
void deliverOfflineMsgs();
void clearReciepts();
void startFileSend(ToxFile file);
void onFileRecvRequest(ToxFile file);
void onAvInvite(int FriendId, int CallId, bool video);
@ -113,14 +113,12 @@ private:
QTimer typingTimer;
QTimer *disableCallButtonsTimer;
QElapsedTimer timeElapsed;
OfflineMsgEngine *offlineEngine;
QHash<uint, FileTransferInstance*> ftransWidgets;
void startCounter();
void stopCounter();
QString secondsToDHMS(quint32 duration);
QHash<int, int> receipts;
QMap<int, ChatMessage::Ptr> undeliveredMsgs;
bool isTyping;
CallConfirmWidget *callConfirm;
void enableCallButtons();
};

View File

@ -1,38 +0,0 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#include "inputpassworddialog.h"
#include "ui_inputpassworddialog.h"
InputPasswordDialog::InputPasswordDialog(QString title, QWidget *parent) :
QDialog(parent),
ui(new Ui::InputPasswordDialog)
{
ui->setupUi(this);
if (title != QString())
setWindowTitle(title);
}
InputPasswordDialog::~InputPasswordDialog()
{
delete ui;
}
QString InputPasswordDialog::getPassword()
{
return ui->passwordLineEdit->text();
}

View File

@ -1,40 +0,0 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#ifndef INPUTPASSWORDDIALOG_H
#define INPUTPASSWORDDIALOG_H
#include <QDialog>
namespace Ui {
class InputPasswordDialog;
}
class InputPasswordDialog : public QDialog
{
Q_OBJECT
public:
explicit InputPasswordDialog(QString title = QString(), QWidget *parent = 0);
~InputPasswordDialog();
QString getPassword();
private:
Ui::InputPasswordDialog *ui;
};
#endif // INPUTPASSWORDDIALOG_H

View File

@ -1,97 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>InputPasswordDialog</class>
<widget class="QDialog" name="InputPasswordDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>123</height>
</rect>
</property>
<property name="windowTitle">
<string>Password Dialog</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Input password:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="passwordLineEdit">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>InputPasswordDialog</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>InputPasswordDialog</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>

View File

@ -18,14 +18,27 @@
#include "ui_setpassworddialog.h"
#include <QPushButton>
SetPasswordDialog::SetPasswordDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::SetPasswordDialog)
const double SetPasswordDialog::reasonablePasswordLength = 8.;
SetPasswordDialog::SetPasswordDialog(QString body, QString extraButton, QWidget* parent)
: QDialog(parent)
, ui(new Ui::SetPasswordDialog)
, body(body+"\n")
{
ui->setupUi(this);
connect(ui->passwordlineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPasswordEdit()));
connect(ui-> passwordlineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPasswordEdit()));
connect(ui->repasswordlineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPasswordEdit()));
ui->body->setText(body + "\n" + tr("The passwords don't match."));
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
if (!extraButton.isEmpty())
{
QPushButton* third = new QPushButton(extraButton);
ui->buttonBox->addButton(third, QDialogButtonBox::YesRole);
connect(third, &QPushButton::clicked, this, [&](){this->done(Tertiary);});
}
}
SetPasswordDialog::~SetPasswordDialog()
@ -35,10 +48,53 @@ SetPasswordDialog::~SetPasswordDialog()
void SetPasswordDialog::onPasswordEdit()
{
if (ui->passwordlineEdit->text() == ui->repasswordlineEdit->text())
QString pswd = ui->passwordlineEdit->text();
if (pswd == ui->repasswordlineEdit->text() && pswd.length() > 0)
{
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
ui->body->setText(body);
}
else
{
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
ui->body->setText(body + tr("The passwords don't match."));
}
// Password strength calculator
// Based on code in the Master Password dialog in Firefox
// (pref-masterpass.js)
// Original code triple-licensed under the MPL, GPL, and LGPL
// so is license-compatible with this file
const double lengthFactor = reasonablePasswordLength / 8.0;
int pwlength = (int)(pswd.length() / lengthFactor);
if (pwlength > 5)
pwlength = 5;
const QRegExp numRxp("[0-9]", Qt::CaseSensitive, QRegExp::RegExp);
int numeric = (int)(pswd.count(numRxp) / lengthFactor);
if (numeric > 3)
numeric = 3;
const QRegExp symbRxp("\\W", Qt::CaseInsensitive, QRegExp::RegExp);
int numsymbols = (int)(pswd.count(symbRxp) / lengthFactor);
if (numsymbols > 3)
numsymbols = 3;
const QRegExp upperRxp("[A-Z]", Qt::CaseSensitive, QRegExp::RegExp);
int upper = (int)(pswd.count(upperRxp) / lengthFactor);
if (upper > 3)
upper = 3;
int pwstrength=((pwlength*10)-20) + (numeric*10) + (numsymbols*15) + (upper*10);
if (pwstrength < 0)
pwstrength = 0;
if (pwstrength > 100)
pwstrength = 100;
ui->strengthBar->setValue(pwstrength);
}
QString SetPasswordDialog::getPassword()

View File

@ -28,7 +28,8 @@ class SetPasswordDialog : public QDialog
Q_OBJECT
public:
explicit SetPasswordDialog(QWidget *parent = 0);
enum ReturnCode {Rejected=QDialog::Rejected, Accepted=QDialog::Accepted, Tertiary};
explicit SetPasswordDialog(QString body, QString extraButton, QWidget* parent = 0);
~SetPasswordDialog();
QString getPassword();
@ -37,6 +38,8 @@ private slots:
private:
Ui::SetPasswordDialog *ui;
QString body;
static const double reasonablePasswordLength;
};
#endif // SETPASSWORDDIALOG_H

View File

@ -6,44 +6,77 @@
<rect>
<x>0</x>
<y>0</y>
<width>434</width>
<height>175</height>
<width>325</width>
<height>160</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Dialog</string>
<string>Set your password</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Type Password</string>
</property>
</widget>
<widget class="QLabel" name="body"/>
</item>
<item>
<widget class="QLineEdit" name="passwordlineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Repeat Password</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="repasswordlineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
<layout class="QGridLayout" name="pswdsLayout">
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="alignment">
<set>Qt::AlignRight</set>
</property>
<property name="text">
<string>Repeat password</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="alignment">
<set>Qt::AlignRight</set>
</property>
<property name="text">
<string>Type password</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="repasswordlineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="passwordlineEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_3">
<property name="alignment">
<set>Qt::AlignRight</set>
</property>
<property name="text">
<string>Password strength</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QProgressBar" name="strengthBar">
<property name="value">
<number>0</number>
</property>
<property name="format">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
@ -73,6 +106,10 @@
</item>
</layout>
</widget>
<tabstops>
<tabstop>passwordlineEdit</tabstop>
<tabstop>repasswordlineEdit</tabstop>
</tabstops>
<resources/>
<connections>
<connection>

View File

@ -65,7 +65,7 @@
<item alignment="Qt::AlignTop">
<widget class="QGroupBox" name="historyGroup">
<property name="title">
<string>History</string>
<string>Chat history</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>

View File

@ -27,6 +27,10 @@
#include <AL/al.h>
#endif
#ifndef ALC_ALL_DEVICES_SPECIFIER
#define ALC_ALL_DEVICES_SPECIFIER ALC_DEVICE_SPECIFIER
#endif
AVForm::AVForm() :
GenericForm(tr("Audio/Video"), QPixmap(":/img/settings/av.png"))
{

View File

@ -359,4 +359,5 @@ void GeneralForm::onThemeColorChanged(int)
int index = bodyUI->themeColorCBox->currentIndex();
Settings::getInstance().setThemeColor(index);
Style::setThemeColor(index);
Style::applyTheme();
}

View File

@ -21,6 +21,7 @@
#include "src/misc/settings.h"
#include "src/widget/croppinglabel.h"
#include "src/widget/widget.h"
#include "src/widget/gui.h"
#include "src/historykeeper.h"
#include "src/misc/style.h"
#include <QLabel>
@ -29,7 +30,6 @@
#include <QClipboard>
#include <QInputDialog>
#include <QFileDialog>
#include <QMessageBox>
IdentityForm::IdentityForm() :
GenericForm(tr("Identity"), QPixmap(":/img/settings/identity.png"))
@ -112,7 +112,7 @@ void IdentityForm::present()
toxId->setText(Core::getInstance()->getSelfId().toString());
toxId->setCursorPosition(0);
bodyUI->profiles->clear();
for (QString profile : Widget::searchProfiles())
for (QString profile : Settings::getInstance().searchProfiles())
bodyUI->profiles->addItem(profile);
QString current = Settings::getInstance().getCurrentProfile();
if (current != "")
@ -152,10 +152,11 @@ void IdentityForm::onRenameClicked()
name = Core::sanitize(name);
QDir dir(Settings::getSettingsDirPath());
QString file = dir.filePath(name+Core::TOX_EXT);
if (!QFile::exists(file) || checkContinue(tr("Profile already exists", "rename confirm title"),
if (!QFile::exists(file) || GUI::askQuestion(tr("Profile already exists", "rename confirm title"),
tr("A profile named \"%1\" already exists. Do you want to erase it?", "rename confirm text").arg(cur)))
{
QFile::rename(dir.filePath(cur+Core::TOX_EXT), file);
QFile::rename(dir.filePath(cur+".ini"), dir.filePath(name+".ini"));
bodyUI->profiles->setItemText(bodyUI->profiles->currentIndex(), name);
HistoryKeeper::renameHistory(cur, name);
Settings::getInstance().setCurrentProfile(name);
@ -176,8 +177,6 @@ void IdentityForm::onExportClicked()
if (QFile::exists(path))
{
// should we popup a warning?
// if (!checkContinue(tr("Overwriting a file"), tr("Are you sure you want to overwrite %1?").arg(path)))
// return;
success = QFile::remove(path);
if (!success)
{
@ -199,8 +198,8 @@ void IdentityForm::onDeleteClicked()
}
else
{
if (checkContinue(tr("Deletion imminent!","deletion confirmation title"),
tr("Are you sure you want to delete this profile?\nAssociated friend information and chat logs will be deleted as well.","deletion confirmation text")))
if (GUI::askQuestion(tr("Deletion imminent!","deletion confirmation title"),
tr("Are you sure you want to delete this profile?","deletion confirmation text")))
{
QString profile = bodyUI->profiles->currentText();
QDir dir(Settings::getSettingsDirPath());
@ -238,7 +237,7 @@ void IdentityForm::onImportClicked()
QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT);
if (QFileInfo(profilePath).exists() && !checkContinue(tr("Profile already exists", "import confirm title"),
if (QFileInfo(profilePath).exists() && !GUI::askQuestion(tr("Profile already exists", "import confirm title"),
tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile)))
return;
@ -251,12 +250,6 @@ void IdentityForm::onNewClicked()
emit Widget::getInstance()->changeProfile(QString());
}
bool IdentityForm::checkContinue(const QString& title, const QString& msg)
{
QMessageBox::StandardButton resp = QMessageBox::question(this, title, msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
return resp == QMessageBox::Yes;
}
void IdentityForm::disableSwitching()
{
bodyUI->loadButton->setEnabled(false);

View File

@ -66,7 +66,6 @@ private slots:
void onDeleteClicked();
void onImportClicked();
void onNewClicked();
bool checkContinue(const QString& title, const QString& msg);
void disableSwitching();
void enableSwitching();

View File

@ -21,8 +21,11 @@
#include "src/historykeeper.h"
#include "src/core.h"
#include "src/widget/widget.h"
#include "src/widget/gui.h"
#include "src/widget/form/setpassworddialog.h"
#include <QMessageBox>
#include <QFile>
#include <QDebug>
PrivacyForm::PrivacyForm() :
GenericForm(tr("Privacy"), QPixmap(":/img/settings/privacy.png"))
@ -30,10 +33,15 @@ PrivacyForm::PrivacyForm() :
bodyUI = new Ui::PrivacySettings;
bodyUI->setupUi(this);
bodyUI->encryptToxHLayout->addStretch();
bodyUI->encryptLogsHLayout->addStretch();
connect(bodyUI->cbTypingNotification, SIGNAL(stateChanged(int)), this, SLOT(onTypingNotificationEnabledUpdated()));
connect(bodyUI->cbKeepHistory, SIGNAL(stateChanged(int)), this, SLOT(onEnableLoggingUpdated()));
connect(bodyUI->cbEncryptHistory, SIGNAL(clicked()), this, SLOT(onEncryptLogsUpdated()));
connect(bodyUI->changeLogsPwButton, &QPushButton::clicked, this, &PrivacyForm::setChatLogsPassword);
connect(bodyUI->cbEncryptTox, SIGNAL(clicked()), this, SLOT(onEncryptToxUpdated()));
connect(bodyUI->changeToxPwButton, &QPushButton::clicked, this, &PrivacyForm::setToxPassword);
connect(bodyUI->nospamLineEdit, SIGNAL(editingFinished()), this, SLOT(setNospam()));
connect(bodyUI->randomNosapamButton, SIGNAL(clicked()), this, SLOT(generateRandomNospam()));
connect(bodyUI->nospamLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onNospamEdit()));
@ -48,7 +56,7 @@ void PrivacyForm::onEnableLoggingUpdated()
{
Settings::getInstance().setEnableLogging(bodyUI->cbKeepHistory->isChecked());
bodyUI->cbEncryptHistory->setEnabled(bodyUI->cbKeepHistory->isChecked());
HistoryKeeper::getInstance()->resetInstance();
HistoryKeeper::resetInstance();
Widget::getInstance()->clearAllReceipts();
}
@ -57,79 +65,191 @@ void PrivacyForm::onTypingNotificationEnabledUpdated()
Settings::getInstance().setTypingNotification(bodyUI->cbTypingNotification->isChecked());
}
bool PrivacyForm::setChatLogsPassword()
{
Core* core = Core::getInstance();
SetPasswordDialog* dialog;
// check if an encrypted history exists because it was disabled earlier, and use it if possible
QString path = HistoryKeeper::getHistoryPath(QString(), 1);
QByteArray salt = core->getSaltFromFile(path);
bool haveEncHist = salt.size() > 0;
QString body = tr("Please set your new chat history password.");
if (haveEncHist)
body += "\n\n" + tr("It appears you have an unused encrypted chat history; if the password matches, it will be added to your current history.");
if (core->isPasswordSet(Core::ptMain))
dialog = new SetPasswordDialog(body, tr("Use data file password", "pushbutton text"), this);
else
dialog = new SetPasswordDialog(body, QString(), this);
do {
int r = dialog->exec();
if (r == QDialog::Rejected)
break;
QList<HistoryKeeper::HistMessage> oldMessages = HistoryKeeper::exportMessagesDeleteFile();
QString newpw = dialog->getPassword();
if (r == SetPasswordDialog::Tertiary)
core->useOtherPassword(Core::ptHistory);
else if (haveEncHist)
core->setPassword(newpw, Core::ptHistory, reinterpret_cast<uint8_t*>(salt.data()));
else
core->setPassword(newpw, Core::ptHistory);
if (!haveEncHist || HistoryKeeper::checkPassword(1))
{
Settings::getInstance().setEncryptLogs(true);
HistoryKeeper::getInstance()->importMessages(oldMessages);
if (haveEncHist)
{
Widget::getInstance()->reloadHistory();
GUI::showWarning(tr("Successfully decrypted old chat history","popup title"), tr("You have succesfully decrypted the old chat history, and it has been added to your current history and re-encrypted.", "popup text"));
}
delete dialog;
return true;
}
else
{
if (!GUI::askQuestion(tr("Old encrypted chat history", "popup title"), tr("There is currently an unused encrypted chat history, but the password you just entered doesn't match.\nWould you like to try again?\nCanceling will delete the old history and set the password to what you just entered.", "This happens when enabling encryption after previously \"Disabling History\""), true, true))
haveEncHist = false; // logically this is really just a `break`, but conceptually this is more accurate
}
} while (haveEncHist);
delete dialog;
return false;
}
void PrivacyForm::onEncryptLogsUpdated()
{
bool encrytionState = bodyUI->cbEncryptHistory->isChecked();
Core* core = Core::getInstance();
if (encrytionState)
if (bodyUI->cbEncryptHistory->isChecked())
{
if (!Core::getInstance()->isPasswordSet(Core::ptHistory))
if (!core->isPasswordSet(Core::ptHistory))
{
SetPasswordDialog dialog;
if (dialog.exec())
if (setChatLogsPassword())
{
QString pswd = dialog.getPassword();
if (pswd.size() == 0)
encrytionState = false;
Core::getInstance()->setPassword(pswd, Core::ptHistory);
} else {
encrytionState = false;
Core::getInstance()->clearPassword(Core::ptHistory);
bodyUI->cbEncryptHistory->setChecked(true);
bodyUI->changeLogsPwButton->setEnabled(true);
return;
}
}
}
Settings::getInstance().setEncryptLogs(encrytionState);
if (encrytionState && !HistoryKeeper::checkPassword())
else
{
if (QMessageBox::Ok != QMessageBox::warning(nullptr, tr("Encrypted log"),
tr("You already have history log file encrypted with different password\nDo you want to delete old history file?"),
QMessageBox::Ok | QMessageBox::Cancel))
QMessageBox::StandardButton button = QMessageBox::warning(
Widget::getInstance(),
tr("Old encrypted chat history", "title"),
tr("Would you like to decrypt your chat history?\nOtherwise it will be deleted."),
QMessageBox::Ok | QMessageBox::No | QMessageBox::Cancel,
QMessageBox::Cancel
);
if (button == QMessageBox::Ok)
{
// TODO: ask user about reencryption with new password
encrytionState = false;
QList<HistoryKeeper::HistMessage> oldMessages = HistoryKeeper::exportMessagesDeleteFile(true);
core->clearPassword(Core::ptHistory);
Settings::getInstance().setEncryptLogs(false);
HistoryKeeper::getInstance()->importMessages(oldMessages);
}
else if (button == QMessageBox::No)
{
if (QMessageBox::critical(
Widget::getInstance(),
tr("Old encrypted chat history", "title"),
tr("Are you sure you want to lose your entire chat history?"),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel
)
== QMessageBox::Yes)
{
HistoryKeeper::removeHistory(true);
}
else
{
bodyUI->cbEncryptHistory->setChecked(true);
return;
}
}
else
{
bodyUI->cbEncryptHistory->setChecked(true);
return;
}
}
Settings::getInstance().setEncryptLogs(encrytionState);
bodyUI->cbEncryptHistory->setChecked(encrytionState);
core->clearPassword(Core::ptHistory);
Settings::getInstance().setEncryptLogs(false);
bodyUI->cbEncryptHistory->setChecked(false);
bodyUI->changeLogsPwButton->setEnabled(false);
HistoryKeeper::resetInstance();
}
if (encrytionState)
HistoryKeeper::resetInstance();
bool PrivacyForm::setToxPassword()
{
Core* core = Core::getInstance();
SetPasswordDialog* dialog;
QString body = tr("Please set your new data file password.");
if (core->isPasswordSet(Core::ptHistory))
dialog = new SetPasswordDialog(body, tr("Use chat history password", "pushbutton text"), this);
else
dialog = new SetPasswordDialog(body, QString(), this);
if (!Settings::getInstance().getEncryptLogs())
Core::getInstance()->clearPassword(Core::ptHistory);
if (int r = dialog->exec())
{
QString newpw = dialog->getPassword();
delete dialog;
if (r == SetPasswordDialog::Tertiary)
core->useOtherPassword(Core::ptMain);
else
core->setPassword(newpw, Core::ptMain);
Settings::getInstance().setEncryptTox(true);
core->saveConfiguration();
return true;
}
else
{
delete dialog;
return false;
}
}
void PrivacyForm::onEncryptToxUpdated()
{
bool encrytionState = bodyUI->cbEncryptTox->isChecked();
Core* core = Core::getInstance();
if (encrytionState)
if (bodyUI->cbEncryptTox->isChecked())
{
if (!Core::getInstance()->isPasswordSet(Core::ptMain))
if (!core->isPasswordSet(Core::ptMain))
{
SetPasswordDialog dialog;
if (dialog.exec())
if (setToxPassword())
{
QString pswd = dialog.getPassword();
if (pswd.size() == 0)
encrytionState = false;
Core::getInstance()->setPassword(pswd, Core::ptMain);
} else {
encrytionState = false;
Core::getInstance()->clearPassword(Core::ptMain);
bodyUI->cbEncryptTox->setChecked(true);
bodyUI->changeToxPwButton->setEnabled(true);
return;
}
}
}
else
{
if (!GUI::askQuestion(tr("Decrypt your data file", "title"), tr("Would you like to decrypt your data file?")))
{
bodyUI->cbEncryptTox->setChecked(true);
return;
}
// affirmative answer falls through to the catch all below
}
bodyUI->cbEncryptTox->setChecked(encrytionState);
Settings::getInstance().setEncryptTox(encrytionState);
if (!Settings::getInstance().getEncryptTox())
Core::getInstance()->clearPassword(Core::ptMain);
bodyUI->cbEncryptTox->setChecked(false);
Settings::getInstance().setEncryptTox(false);
bodyUI->changeToxPwButton->setEnabled(false);
core->clearPassword(Core::ptMain);
}
void PrivacyForm::setNospam()
@ -148,8 +268,10 @@ void PrivacyForm::present()
bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled());
bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptHistory->setChecked(Settings::getInstance().getEncryptLogs());
bodyUI->changeLogsPwButton->setEnabled(Settings::getInstance().getEncryptLogs());
bodyUI->cbEncryptHistory->setEnabled(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptTox->setChecked(Settings::getInstance().getEncryptTox());
bodyUI->changeToxPwButton->setEnabled(Settings::getInstance().getEncryptTox());
}
void PrivacyForm::generateRandomNospam()

View File

@ -39,7 +39,9 @@ private slots:
void generateRandomNospam();
void onNospamEdit();
void onEncryptLogsUpdated();
bool setChatLogsPassword();
void onEncryptToxUpdated();
bool setToxPassword();
private:
Ui::PrivacySettings* bodyUI;

View File

@ -54,11 +54,11 @@
<item>
<widget class="QCheckBox" name="cbKeepHistory">
<property name="toolTip">
<string comment="toolTip for Keep History setting">History keeping is still in development.
<string comment="toolTip for Keep History setting">Chat history keeping is still in development.
Save format changes are possible, which may result in data loss.</string>
</property>
<property name="text">
<string>Keep History (unstable)</string>
<string>Keep chat history (mostly stable)</string>
</property>
</widget>
</item>
@ -68,31 +68,63 @@ Save format changes are possible, which may result in data loss.</string>
<bool>true</bool>
</property>
<property name="title">
<string>Encryption</string>
<string>Local file encryption</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="cbEncryptTox">
<property name="enabled">
<bool>false</bool>
</property>
<widget class="QLabel" name="encryptStatement">
<property name="text">
<string>Encrypt Tox datafile</string>
<string>All Tox communications over the internet are encrypted, and this cannot be disabled. However, you may optionally password protect your local Tox files.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbEncryptHistory">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Encrypt History</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
<layout class="QHBoxLayout" name="encryptToxHLayout">
<item>
<widget class="QCheckBox" name="cbEncryptTox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Encrypt Tox data file</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="changeToxPwButton">
<property name="text">
<string>Change password</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="encryptLogsHLayout">
<item>
<widget class="QCheckBox" name="cbEncryptHistory">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Encrypt chat history</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="changeLogsPwButton">
<property name="text">
<string>Change password</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

308
src/widget/gui.cpp Normal file
View File

@ -0,0 +1,308 @@
#include "gui.h"
#include "src/nexus.h"
#include <assert.h>
#include <QCoreApplication>
#include <QDebug>
#include <QDialogButtonBox>
#include <QInputDialog>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QThread>
#ifdef Q_OS_ANDROID
#include "androidgui.h"
#else
#include "widget.h"
#endif
GUI::GUI(QObject *parent) :
QObject(parent)
{
assert(QThread::currentThread() == qApp->thread());
#ifndef Q_OS_ANDROID
assert(Nexus::getDesktopGUI());
connect(Nexus::getDesktopGUI(), &Widget::resized, this, &GUI::resized);
#endif
}
GUI& GUI::getInstance()
{
static GUI gui;
return gui;
}
// Implementation of the public clean interface
void GUI::setEnabled(bool state)
{
if (QThread::currentThread() == qApp->thread())
{
getInstance()._setEnabled(state);
}
else
{
QMetaObject::invokeMethod(&getInstance(), "_setEnabled", Qt::BlockingQueuedConnection,
Q_ARG(bool, state));
}
}
void GUI::setWindowTitle(const QString& title)
{
if (QThread::currentThread() == qApp->thread())
{
getInstance()._setWindowTitle(title);
}
else
{
QMetaObject::invokeMethod(&getInstance(), "_setWindowTitle", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, title));
}
}
void GUI::reloadTheme()
{
if (QThread::currentThread() == qApp->thread())
{
getInstance()._reloadTheme();
}
else
{
QMetaObject::invokeMethod(&getInstance(), "_reloadTheme", Qt::BlockingQueuedConnection);
}
}
void GUI::showInfo(const QString& title, const QString& msg)
{
if (QThread::currentThread() == qApp->thread())
{
getInstance()._showInfo(title, msg);
}
else
{
QMetaObject::invokeMethod(&getInstance(), "_showInfo", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, title), Q_ARG(const QString&, msg));
}
}
void GUI::showWarning(const QString& title, const QString& msg)
{
if (QThread::currentThread() == qApp->thread())
{
getInstance()._showWarning(title, msg);
}
else
{
QMetaObject::invokeMethod(&getInstance(), "_showWarning", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, title), Q_ARG(const QString&, msg));
}
}
void GUI::showError(const QString& title, const QString& msg)
{
if (QThread::currentThread() == qApp->thread())
{
getInstance()._showError(title, msg);
}
else
{
QMetaObject::invokeMethod(&getInstance(), "_showError", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, title), Q_ARG(const QString&, msg));
}
}
bool GUI::askQuestion(const QString& title, const QString& msg,
bool defaultAns, bool warning)
{
if (QThread::currentThread() == qApp->thread())
{
return getInstance()._askQuestion(title, msg, defaultAns, warning);
}
else
{
bool ret;
QMetaObject::invokeMethod(&getInstance(), "_askQuestion", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, ret),
Q_ARG(const QString&, title), Q_ARG(const QString&, msg),
Q_ARG(bool, defaultAns), Q_ARG(bool, warning));
return ret;
}
}
QString GUI::itemInputDialog(QWidget * parent, const QString & title,
const QString & label, const QStringList & items,
int current, bool editable, bool * ok,
Qt::WindowFlags flags,
Qt::InputMethodHints hints)
{
if (QThread::currentThread() == qApp->thread())
{
return getInstance()._itemInputDialog(parent, title, label, items, current, editable, ok, flags, hints);
}
else
{
QString r;
QMetaObject::invokeMethod(&getInstance(), "_itemInputDialog", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QString, r),
Q_ARG(QWidget*, parent), Q_ARG(const QString&, title),
Q_ARG(const QString&,label), Q_ARG(const QStringList&, items),
Q_ARG(int, current), Q_ARG(bool, editable), Q_ARG(bool*, ok),
Q_ARG(Qt::WindowFlags, flags), Q_ARG(Qt::InputMethodHints, hints));
return r;
}
}
QString GUI::passwordDialog(const QString& cancel, const QString& body)
{
if (QThread::currentThread() == qApp->thread())
{
return getInstance()._passwordDialog(cancel, body);
}
else
{
QString r;
QMetaObject::invokeMethod(&getInstance(), "_passwordDialog", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QString, r),
Q_ARG(const QString&, cancel), Q_ARG(const QString&, body));
return r;
}
}
// Private implementations
void GUI::_setEnabled(bool state)
{
#ifdef Q_OS_ANDROID
Nexus::getAndroidGUI()->setEnabled(state);
#else
Nexus::getDesktopGUI()->setEnabled(state);
#endif
}
void GUI::_setWindowTitle(const QString& title)
{
if (title.isEmpty())
getMainWidget()->setWindowTitle("qTox");
else
getMainWidget()->setWindowTitle("qTox - " +title);
}
void GUI::_reloadTheme()
{
#ifndef Q_OS_ANDROID
Nexus::getDesktopGUI()->reloadTheme();
#endif
}
void GUI::_showInfo(const QString& title, const QString& msg)
{
QMessageBox::information(getMainWidget(), title, msg);
}
void GUI::_showWarning(const QString& title, const QString& msg)
{
QMessageBox::warning(getMainWidget(), title, msg);
}
void GUI::_showError(const QString& title, const QString& msg)
{
QMessageBox::critical(getMainWidget(), title, msg);
}
bool GUI::_askQuestion(const QString& title, const QString& msg,
bool defaultAns, bool warning)
{
if (warning)
{
QMessageBox::StandardButton def = QMessageBox::Cancel;
if (defaultAns)
def = QMessageBox::Ok;
return QMessageBox::warning(getMainWidget(), title, msg, QMessageBox::Ok | QMessageBox::Cancel, def) == QMessageBox::Ok;
}
else
{
QMessageBox::StandardButton def = QMessageBox::No;
if (defaultAns)
def = QMessageBox::Yes;
return QMessageBox::question(getMainWidget(), title, msg, QMessageBox::Yes | QMessageBox::No, def) == QMessageBox::Yes;
}
}
QString GUI::_itemInputDialog(QWidget * parent, const QString & title,
const QString & label, const QStringList & items,
int current, bool editable, bool * ok,
Qt::WindowFlags flags,
Qt::InputMethodHints hints)
{
return QInputDialog::getItem(parent, title, label, items, current, editable, ok, flags, hints);
}
QString GUI::_passwordDialog(const QString& cancel, const QString& body)
{
// we use a hack. It is considered that closing the dialog without explicitly clicking
// disable history is confusing. But we can't distinguish between clicking the cancel
// button and closing the dialog. So instead, we reverse the Ok and Cancel roles,
// so that nothing but explicitly clicking disable history closes the dialog
QString ret;
QInputDialog dialog;
dialog.setWindowTitle(tr("Enter your password"));
dialog.setOkButtonText(cancel);
dialog.setCancelButtonText(tr("Decrypt"));
dialog.setInputMode(QInputDialog::TextInput);
dialog.setTextEchoMode(QLineEdit::Password);
dialog.setLabelText(body);
// problem with previous hack: the default button is disable history, not decrypt.
// use another hack to reverse the default buttons.
// http://www.qtcentre.org/threads/49924-Change-property-of-QInputDialog-button
QList<QDialogButtonBox*> l = dialog.findChildren<QDialogButtonBox*>();
if (!l.isEmpty())
{
QPushButton* ok = l.first()->button(QDialogButtonBox::Ok);
QPushButton* cancel = l.first()->button(QDialogButtonBox::Cancel);
if (ok && cancel)
{
ok->setAutoDefault(false);
ok->setDefault(false);
cancel->setAutoDefault(true);
cancel->setDefault(true);
}
else
qWarning() << "PasswordDialog: Missing button!";
}
else
qWarning() << "PasswordDialog: No QDialogButtonBox!";
// using similar code, set QLabels to wrap
for (auto* label : dialog.findChildren<QLabel*>())
label->setWordWrap(true);
while (true)
{
int val = dialog.exec();
if (val == QDialog::Accepted)
return QString();
else
{
ret = dialog.textValue();
if (!ret.isEmpty())
return ret;
}
dialog.setTextValue("");
dialog.setLabelText(body + "\n\n" + tr("You must enter a non-empty password:"));
}
}
// Other
QWidget* GUI::getMainWidget()
{
QWidget* maingui{nullptr};
#ifdef Q_OS_ANDROID
maingui = Nexus::getAndroidGUI();
#else
maingui = Nexus::getDesktopGUI();
#endif
return maingui;
}

75
src/widget/gui.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef GUI_H
#define GUI_H
#include <QObject>
class QWidget;
/// Abstracts the GUI from the target backend (AndroidGUI, DesktopGUI, ...)
/// All the functions exposed here are thread-safe
/// Prefer calling this class to calling a GUI backend directly
class GUI : public QObject
{
Q_OBJECT
public:
static GUI& getInstance();
/// Returns the main QWidget* of the application
static QWidget* getMainWidget();
/// Will enable or disable the GUI.
/// A disabled GUI can't be interacted with by the user
static void setEnabled(bool state);
/// Change the title of the main window
/// This is usually always visible to the user
static void setWindowTitle(const QString& title);
/// Reloads the application theme and redraw the window
static void reloadTheme();
/// Show some text to the user, for example in a message box
static void showInfo(const QString& title, const QString& msg);
/// Show a warning to the user, for example in a message box
static void showWarning(const QString& title, const QString& msg);
/// Show an error to the user, for example in a message box
static void showError(const QString& title, const QString& msg);
/// Asks the user a question, for example in a message box.
/// If warning is true, we will use a special warning style.
/// Returns the answer.
static bool askQuestion(const QString& title, const QString& msg,
bool defaultAns = false, bool warning = true);
/// Asks the user to input text and returns the answer.
/// The interface is equivalent to QInputDialog::getItem()
static QString itemInputDialog(QWidget * parent, const QString & title,
const QString & label, const QStringList & items,
int current = 0, bool editable = true, bool * ok = 0,
Qt::WindowFlags flags = 0,
Qt::InputMethodHints hints = Qt::ImhNone);
/// Asks the user to answer a password
/// cancel is the text on the cancel button and body
/// is descriptive text that will be shown to the user
static QString passwordDialog(const QString& cancel, const QString& body);
signals:
/// Emitted when the GUI is resized on supported platforms
/// Guaranteed to work on desktop platforms
void resized();
private:
explicit GUI(QObject *parent = 0);
// Private implementation, those must be called from the GUI thread
private slots:
void _setEnabled(bool state);
void _setWindowTitle(const QString& title);
void _reloadTheme();
void _showInfo(const QString& title, const QString& msg);
void _showWarning(const QString& title, const QString& msg);
void _showError(const QString& title, const QString& msg);
bool _askQuestion(const QString& title, const QString& msg,
bool defaultAns = false, bool warning = true);
QString _itemInputDialog(QWidget * parent, const QString & title,
const QString & label, const QStringList & items,
int current = 0, bool editable = true, bool * ok = 0,
Qt::WindowFlags flags = 0,
Qt::InputMethodHints inputMethodHints = Qt::ImhNone);
QString _passwordDialog(const QString& cancel, const QString& body);
};
#endif // GUI_H

View File

@ -42,6 +42,11 @@ SystemTrayIcon::SystemTrayIcon()
}
}
SystemTrayIcon::~SystemTrayIcon()
{
qDebug() << "Deleting SystemTrayIcon";
}
QString SystemTrayIcon::extractIconToFile(QIcon icon, QString name)
{
QString iconPath;

View File

@ -12,6 +12,7 @@ class SystemTrayIcon : public QObject
Q_OBJECT
public:
SystemTrayIcon();
~SystemTrayIcon();
void setContextMenu(QMenu* menu);
void show();
void hide();

View File

@ -15,7 +15,7 @@
*/
#include "toxsave.h"
#include "widget.h"
#include "gui.h"
#include "src/core.h"
#include "src/misc/settings.h"
#include <QCoreApplication>
@ -30,12 +30,6 @@ void toxSaveEventHandler(const QByteArray& eventData)
handleToxSave(eventData);
}
static bool checkContinue(const QString& title, const QString& msg)
{
QMessageBox::StandardButton resp = QMessageBox::question(Widget::getInstance(), title, msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
return resp == QMessageBox::Yes;
}
void handleToxSave(const QString& path)
{
Core* core = Core::getInstance();
@ -59,20 +53,19 @@ void handleToxSave(const QString& path)
if (info.suffix() != "tox")
{
QMessageBox::warning(Widget::getInstance(),
QObject::tr("Ignoring non-Tox file", "popup title"),
QObject::tr("Warning: you've chosen a file that is not a Tox save file; ignoring.", "popup text"));
GUI::showWarning(QObject::tr("Ignoring non-Tox file", "popup title"),
QObject::tr("Warning: you've chosen a file that is not a Tox save file; ignoring.", "popup text"));
return;
}
QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT);
if (QFileInfo(profilePath).exists() && !checkContinue(QObject::tr("Profile already exists", "import confirm title"),
if (QFileInfo(profilePath).exists() && !GUI::askQuestion(QObject::tr("Profile already exists", "import confirm title"),
QObject::tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile)))
return;
QFile::copy(path, profilePath);
// no good way to update the ui from here... maybe we need a Widget:refreshUi() function...
// such a thing would simplify other code as well I believe
QMessageBox::information(Widget::getInstance(), QObject::tr("Profile imported"), QObject::tr("%1.tox was successfully imported").arg(profile));
GUI::showInfo(QObject::tr("Profile imported"), QObject::tr("%1.tox was successfully imported").arg(profile));
}

View File

@ -32,11 +32,13 @@
#include "form/chatform.h"
#include "maskablepixmapwidget.h"
#include "src/historykeeper.h"
#include "form/inputpassworddialog.h"
#include "src/autoupdate.h"
#include "src/audio.h"
#include "src/platform/timer.h"
#include "systemtrayicon.h"
#include "src/nexus.h"
#include "src/offlinemsgengine.h"
#include <cassert>
#include <QMessageBox>
#include <QDebug>
#include <QFile>
@ -48,11 +50,18 @@
#include <QThread>
#include <QFileDialog>
#include <QInputDialog>
#include <QDialogButtonBox>
#include <QTimer>
#include <QStyleFactory>
#include <QTranslator>
#include <tox/tox.h>
#ifdef Q_OS_ANDROID
#define IS_ON_DESKTOP_GUI 0
#else
#define IS_ON_DESKTOP_GUI 1
#endif
void toxActivateEventHandler(const QByteArray& data)
{
if (data != "$activate")
@ -64,8 +73,11 @@ Widget *Widget::instance{nullptr};
Widget::Widget(QWidget *parent)
: QMainWindow(parent),
icon{nullptr},
ui(new Ui::MainWindow),
activeChatroomWidget{nullptr}
activeChatroomWidget{nullptr},
eventFlag(false),
eventIcon(false)
{
translator = new QTranslator;
setTranslation();
@ -75,29 +87,32 @@ void Widget::init()
{
ui->setupUi(this);
idleTimer = new QTimer();
idleTimer->start(1000);
timer = new QTimer();
timer->start(1000);
offlineMsgTimer = new QTimer();
offlineMsgTimer->start(15000);
//restore window state
restoreGeometry(Settings::getInstance().getWindowGeometry());
restoreState(Settings::getInstance().getWindowState());
ui->mainSplitter->restoreState(Settings::getInstance().getSplitterState());
statusOnline = new QAction(tr("Online", "Button to set your status to 'Online'"), this);
statusOnline->setIcon(QIcon(":img/status/dot_online.png"));
connect(statusOnline, SIGNAL(triggered()), this, SLOT(setStatusOnline()));
statusAway = new QAction(tr("Away", "Button to set your status to 'Away'"), this);
statusAway->setIcon(QIcon(":img/status/dot_idle.png"));
connect(statusAway, SIGNAL(triggered()), this, SLOT(setStatusAway()));
statusBusy = new QAction(tr("Busy", "Button to set your status to 'Busy'"), this);
statusBusy->setIcon(QIcon(":img/status/dot_busy.png"));
connect(statusBusy, SIGNAL(triggered()), this, SLOT(setStatusBusy()));
if (QSystemTrayIcon::isSystemTrayAvailable())
{
icon = new SystemTrayIcon;
updateTrayIcon();
trayMenu = new QMenu;
statusOnline = new QAction(tr("Online"), this);
statusOnline->setIcon(QIcon(":ui/statusButton/dot_online.png"));
connect(statusOnline, SIGNAL(triggered()), this, SLOT(setStatusOnline()));
statusAway = new QAction(tr("Away"), this);
statusAway->setIcon(QIcon(":ui/statusButton/dot_idle.png"));
connect(statusAway, SIGNAL(triggered()), this, SLOT(setStatusAway()));
statusBusy = new QAction(tr("Busy"), this);
statusBusy->setIcon(QIcon(":ui/statusButton/dot_busy.png"));
connect(statusBusy, SIGNAL(triggered()), this, SLOT(setStatusBusy()));
actionQuit = new QAction(tr("&Quit"), this);
connect(actionQuit, SIGNAL(triggered()), qApp, SLOT(quit()));
@ -113,6 +128,9 @@ void Widget::init()
this,
SLOT(onIconClick(QSystemTrayIcon::ActivationReason)));
icon->show();
icon->hide();
if (Settings::getInstance().getShowSystemTray())
{
icon->show();
@ -121,7 +139,6 @@ void Widget::init()
}
else
this->show();
}
else
{
@ -171,12 +188,9 @@ void Widget::init()
ui->statusPanel->setStyleSheet(Style::getStylesheet(":/ui/window/statusPanel.css"));
QMenu *statusButtonMenu = new QMenu(ui->statusButton);
QAction* setStatusOnline = statusButtonMenu->addAction(Widget::tr("Online","Button to set your status to 'Online'"));
setStatusOnline->setIcon(QIcon(":ui/statusButton/dot_online.png"));
QAction* setStatusAway = statusButtonMenu->addAction(Widget::tr("Away","Button to set your status to 'Away'"));
setStatusAway->setIcon(QIcon(":ui/statusButton/dot_idle.png"));
QAction* setStatusBusy = statusButtonMenu->addAction(Widget::tr("Busy","Button to set your status to 'Busy'"));
setStatusBusy->setIcon(QIcon(":ui/statusButton/dot_busy.png"));
statusButtonMenu->addAction(statusOnline);
statusButtonMenu->addAction(statusAway);
statusButtonMenu->addAction(statusBusy);
ui->statusButton->setMenu(statusButtonMenu);
// disable proportional scaling
@ -190,69 +204,16 @@ void Widget::init()
ui->statusButton->setEnabled(false);
Style::setThemeColor(Settings::getInstance().getThemeColor());
Style::applyTheme();
qRegisterMetaType<Status>("Status");
qRegisterMetaType<vpx_image>("vpx_image");
qRegisterMetaType<uint8_t>("uint8_t");
qRegisterMetaType<uint16_t>("uint16_t");
qRegisterMetaType<const int16_t*>("const int16_t*");
qRegisterMetaType<int32_t>("int32_t");
qRegisterMetaType<int64_t>("int64_t");
qRegisterMetaType<QPixmap>("QPixmap");
qRegisterMetaType<ToxFile>("ToxFile");
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
qRegisterMetaType<Core::PasswordType>("Core::PasswordType");
QString profilePath = detectProfile();
coreThread = new QThread(this);
coreThread->setObjectName("qTox Core");
core = new Core(Camera::getInstance(), coreThread, profilePath);
core->moveToThread(coreThread);
connect(coreThread, &QThread::started, core, &Core::start);
reloadTheme();
filesForm = new FilesForm();
addFriendForm = new AddFriendForm;
settingsWidget = new SettingsWidget();
connect(settingsWidget, &SettingsWidget::setShowSystemTray, this, &Widget::onSetShowSystemTray);
connect(core, &Core::connected, this, &Widget::onConnected);
connect(core, &Core::disconnected, this, &Widget::onDisconnected);
connect(core, &Core::failedToStart, this, &Widget::onFailedToStartCore);
connect(core, &Core::badProxy, this, &Widget::onBadProxyCore);
connect(core, &Core::statusSet, this, &Widget::onStatusSet);
connect(core, &Core::usernameSet, this, &Widget::setUsername);
connect(core, &Core::statusMessageSet, this, &Widget::setStatusMessage);
connect(core, &Core::selfAvatarChanged, this, &Widget::onSelfAvatarLoaded);
Core* core = Nexus::getCore();
connect(core, SIGNAL(fileDownloadFinished(const QString&)), filesForm, SLOT(onFileDownloadComplete(const QString&)));
connect(core, SIGNAL(fileUploadFinished(const QString&)), filesForm, SLOT(onFileUploadComplete(const QString&)));
connect(core, &Core::friendAdded, this, &Widget::addFriend);
connect(core, &Core::failedToAddFriend, this, &Widget::addFriendFailed);
connect(core, &Core::friendUsernameChanged, this, &Widget::onFriendUsernameChanged);
connect(core, &Core::friendStatusChanged, this, &Widget::onFriendStatusChanged);
connect(core, &Core::friendStatusMessageChanged, this, &Widget::onFriendStatusMessageChanged);
connect(core, &Core::friendRequestReceived, this, &Widget::onFriendRequestReceived);
connect(core, &Core::friendMessageReceived, this, &Widget::onFriendMessageReceived);
connect(core, &Core::receiptRecieved, this, &Widget::onReceiptRecieved);
connect(core, &Core::groupInviteReceived, this, &Widget::onGroupInviteReceived);
connect(core, &Core::groupMessageReceived, this, &Widget::onGroupMessageReceived);
connect(core, &Core::groupNamelistChanged, this, &Widget::onGroupNamelistChanged);
connect(core, &Core::groupTitleChanged, this, &Widget::onGroupTitleChanged);
connect(core, &Core::emptyGroupCreated, this, &Widget::onEmptyGroupCreated);
connect(core, &Core::avInvite, this, &Widget::playRingtone);
connect(core, &Core::blockingClearContacts, this, &Widget::clearContactsList, Qt::BlockingQueuedConnection);
connect(core, &Core::blockingGetPassword, this, &Widget::getPassword, Qt::BlockingQueuedConnection);
connect(core, &Core::friendTypingChanged, this, &Widget::onFriendTypingChanged);
connect(core, SIGNAL(messageSentResult(int,QString,int)), this, SLOT(onMessageSendResult(int,QString,int)));
connect(core, SIGNAL(groupSentResult(int,QString,int)), this, SLOT(onGroupSendResult(int,QString,int)));
connect(this, &Widget::statusSet, core, &Core::setStatus);
connect(this, &Widget::friendRequested, core, &Core::requestFriendship);
connect(this, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest);
connect(this, &Widget::changeProfile, core, &Core::switchConfiguration);
connect(settingsWidget, &SettingsWidget::setShowSystemTray, this, &Widget::onSetShowSystemTray);
connect(ui->addButton, SIGNAL(clicked()), this, SLOT(onAddClicked()));
connect(ui->groupButton, SIGNAL(clicked()), this, SLOT(onGroupClicked()));
connect(ui->transferButton, SIGNAL(clicked()), this, SLOT(onTransferClicked()));
@ -261,13 +222,10 @@ void Widget::init()
connect(ui->statusLabel, SIGNAL(textChanged(QString, QString)), this, SLOT(onStatusMessageChanged(QString, QString)));
connect(ui->mainSplitter, &QSplitter::splitterMoved, this, &Widget::onSplitterMoved);
connect(profilePicture, SIGNAL(clicked()), this, SLOT(onAvatarClicked()));
connect(setStatusOnline, SIGNAL(triggered()), this, SLOT(setStatusOnline()));
connect(setStatusAway, SIGNAL(triggered()), this, SLOT(setStatusAway()));
connect(setStatusBusy, SIGNAL(triggered()), this, SLOT(setStatusBusy()));
connect(addFriendForm, SIGNAL(friendRequested(QString, QString)), this, SIGNAL(friendRequested(QString, QString)));
connect(idleTimer, &QTimer::timeout, this, &Widget::onUserAwayCheck);
coreThread->start();
connect(timer, &QTimer::timeout, this, &Widget::onUserAwayCheck);
connect(timer, &QTimer::timeout, this, &Widget::onEventIconTick);
connect(offlineMsgTimer, &QTimer::timeout, &OfflineMsgEngine::processAllMsgs);
addFriendForm->show(*ui);
@ -297,34 +255,32 @@ void Widget::setTranslation()
void Widget::updateTrayIcon()
{
if (!icon)
return;
QString status = ui->statusButton->property("status").toString();
QString pic;
QString color = Settings::getInstance().getLightTrayIcon() ? "light" : "dark";
if (status == "online")
pic = ":img/taskbar/" + color + "/taskbar_online_2x.png";
else if (status == "away")
pic = ":img/taskbar/" + color + "/taskbar_idle_2x.png";
else if (status == "busy")
pic = ":img/taskbar/" + color + "/taskbar_busy_2x.png";
QString status;
if (eventIcon)
status = "event";
else
pic = ":img/taskbar/" + color + "/taskbar_offline_2x.png";
icon->setIcon(QIcon(pic));
{
status = ui->statusButton->property("status").toString();
if (!status.length())
status = "offline";
}
QString color = Settings::getInstance().getLightTrayIcon() ? "light" : "dark";
QString pic = ":img/taskbar/" + color + "/taskbar_" + status + ".svg";
if (icon)
icon->setIcon(QIcon(pic));
}
Widget::~Widget()
{
qDebug() << "Deleting Widget";
core->saveConfiguration();
qDebug() << "Widget: Deleting Widget";
AutoUpdater::abortUpdates();
delete core;
icon->hide();
hideMainForms();
delete settingsWidget;
delete addFriendForm;
delete filesForm;
delete idleTimer;
delete timer;
delete offlineMsgTimer;
FriendList::clear();
GroupList::clear();
@ -336,6 +292,8 @@ Widget::~Widget()
Widget* Widget::getInstance()
{
assert(IS_ON_DESKTOP_GUI); // Widget must only be used on Desktop platforms
if (!instance)
{
instance = new Widget();
@ -344,11 +302,6 @@ Widget* Widget::getInstance()
return instance;
}
QThread* Widget::getCoreThread()
{
return coreThread;
}
void Widget::closeEvent(QCloseEvent *event)
{
if (Settings::getInstance().getShowSystemTray() && Settings::getInstance().getCloseToTray() == true)
@ -384,72 +337,9 @@ void Widget::resizeEvent(QResizeEvent *event)
emit resized();
}
QString Widget::detectProfile()
{
QDir dir(Settings::getSettingsDirPath());
QString path, profile = Settings::getInstance().getCurrentProfile();
path = dir.filePath(profile + Core::TOX_EXT);
QFile file(path);
if (profile == "" || !file.exists())
{
Settings::getInstance().setCurrentProfile("");
#if 1 // deprecation attempt
// if the last profile doesn't exist, fall back to old "data"
path = dir.filePath(Core::CONFIG_FILE_NAME);
QFile file(path);
if (file.exists())
return path;
else if (QFile(path = dir.filePath("tox_save")).exists()) // also import tox_save if no data
return path;
else
#endif
{
profile = askProfiles();
if (profile != "")
return dir.filePath(profile + Core::TOX_EXT);
else
return "";
}
}
else
return path;
}
QList<QString> Widget::searchProfiles()
{
QList<QString> out;
QDir dir(Settings::getSettingsDirPath());
dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
dir.setNameFilters(QStringList("*.tox"));
for (QFileInfo file : dir.entryInfoList())
out += file.completeBaseName();
return out;
}
QString Widget::askProfiles()
{ // TODO: allow user to create new Tox ID, even if a profile already exists
QList<QString> profiles = searchProfiles();
if (profiles.empty()) return "";
bool ok;
QString profile = QInputDialog::getItem(this,
tr("Choose a profile"),
tr("Please choose which identity to use"),
profiles,
0, // which slot to start on
false, // if the user can enter their own input
&ok);
if (!ok) // user cancelled
{
qApp->quit();
return "";
}
else
return profile;
}
QString Widget::getUsername()
{
return core->getUsername();
return Nexus::getCore()->getUsername();
}
void Widget::onAvatarClicked()
@ -493,7 +383,7 @@ void Widget::onAvatarClicked()
return;
}
core->setAvatar(TOX_AVATAR_FORMAT_PNG, bytes);
Nexus::getCore()->setAvatar(TOX_AVATAR_FORMAT_PNG, bytes);
}
void Widget::onSelfAvatarLoaded(const QPixmap& pic)
@ -553,19 +443,22 @@ void Widget::onStatusSet(Status status)
{
case Status::Online:
ui->statusButton->setProperty("status" ,"online");
ui->statusButton->setIcon(QIcon(":img/status/dot_online.png"));
break;
case Status::Away:
ui->statusButton->setProperty("status" ,"away");
ui->statusButton->setIcon(QIcon(":img/status/dot_idle.png"));
break;
case Status::Busy:
ui->statusButton->setProperty("status" ,"busy");
ui->statusButton->setIcon(QIcon(":img/status/dot_busy.png"));
break;
case Status::Offline:
ui->statusButton->setProperty("status" ,"offline");
ui->statusButton->setIcon(QIcon(":img/status/dot_away.png"));
break;
}
updateTrayIcon();
Style::repolish(ui->statusButton);
}
void Widget::setWindowTitle(const QString& title)
@ -589,7 +482,7 @@ void Widget::onAddClicked()
void Widget::onGroupClicked()
{
core->createGroup();
Nexus::getCore()->createGroup();
}
void Widget::onTransferClicked()
@ -662,7 +555,7 @@ void Widget::hideMainForms()
void Widget::onUsernameChanged(const QString& newUsername, const QString& oldUsername)
{
setUsername(oldUsername); // restore old username until Core tells us to set it
core->setUsername(newUsername);
Nexus::getCore()->setUsername(newUsername);
}
void Widget::setUsername(const QString& username)
@ -679,7 +572,7 @@ void Widget::onStatusMessageChanged(const QString& newStatusMessage, const QStri
{
ui->statusLabel->setText(oldStatusMessage); // restore old status message until Core tells us to set it
ui->statusLabel->setToolTip(oldStatusMessage); // for overlength messsages
core->setStatusMessage(newStatusMessage);
Nexus::getCore()->setStatusMessage(newStatusMessage);
}
void Widget::setStatusMessage(const QString &statusMessage)
@ -688,6 +581,12 @@ void Widget::setStatusMessage(const QString &statusMessage)
ui->statusLabel->setToolTip(statusMessage); // for overlength messsages
}
void Widget::reloadHistory()
{
for (auto f : FriendList::getAllFriends())
f->getChatForm()->loadHistory(QDateTime::currentDateTime().addDays(-7), true);
}
void Widget::addFriend(int friendId, const QString &userId)
{
//qDebug() << "Widget: Adding friend with id" << userId;
@ -696,6 +595,7 @@ void Widget::addFriend(int friendId, const QString &userId)
QLayout* layout = contactListWidget->getFriendLayout(Status::Offline);
layout->addWidget(newfriend->getFriendWidget());
Core* core = Nexus::getCore();
connect(settingsWidget, &SettingsWidget::compactToggled, newfriend->getFriendWidget(), &GenericChatroomWidget::onCompactChanged);
connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*)));
connect(newfriend->getFriendWidget(), SIGNAL(removeFriend(int)), this, SLOT(removeFriend(int)));
@ -786,7 +686,7 @@ void Widget::onFriendStatusChanged(int friendId, Status status)
if (isActualChange && status != Status::Offline)
{ // wait a little
QTimer::singleShot(250, f->getChatForm(), SLOT(deliverOfflineMsgs()));
QTimer::singleShot(250, f->getChatForm()->getOfflineMsgEngine(), SLOT(deliverOfflineMsgs()));
}
}
@ -850,7 +750,7 @@ void Widget::onReceiptRecieved(int friendId, int receipt)
if (!f)
return;
f->getChatForm()->dischargeReceipt(receipt);
f->getChatForm()->getOfflineMsgEngine()->dischargeReceipt(receipt);
}
void Widget::newMessageAlert(GenericChatroomWidget* chat)
@ -861,6 +761,9 @@ void Widget::newMessageAlert(GenericChatroomWidget* chat)
QApplication::alert(this);
if (inactiveWindow)
eventFlag = true;
if (Settings::getInstance().getShowWindow())
{
show();
@ -914,7 +817,7 @@ void Widget::removeFriend(Friend* f, bool fake)
onAddClicked();
}
FriendList::removeFriend(f->getFriendID(), fake);
core->removeFriend(f->getFriendID(), fake);
Nexus::getCore()->removeFriend(f->getFriendID(), fake);
delete f;
if (ui->mainHead->layout()->isEmpty())
onAddClicked();
@ -945,7 +848,7 @@ void Widget::copyFriendIdToClipboard(int friendId)
if (f != nullptr)
{
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(core->getFriendAddress(f->getFriendID()), QClipboard::Clipboard);
clipboard->setText(Nexus::getCore()->getFriendAddress(f->getFriendID()), QClipboard::Clipboard);
}
}
@ -953,7 +856,7 @@ void Widget::onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray in
{
if (type == TOX_GROUPCHAT_TYPE_TEXT || type == TOX_GROUPCHAT_TYPE_AV)
{
int groupId = core->joinGroupchat(friendId, type, (uint8_t*)invite.data(), invite.length());
int groupId = Nexus::getCore()->joinGroupchat(friendId, type, (uint8_t*)invite.data(), invite.length());
if (groupId < 0)
{
qWarning() << "Widget::onGroupInviteReceived: Unable to accept group invite";
@ -1000,7 +903,7 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha
g = createGroup(groupnumber);
}
QString name = core->getGroupPeerName(groupnumber, peernumber);
QString name = Nexus::getCore()->getGroupPeerName(groupnumber, peernumber);
TOX_CHAT_CHANGE change = static_cast<TOX_CHAT_CHANGE>(Change);
if (change == TOX_CHAT_CHANGE_PEER_ADD)
{
@ -1020,7 +923,7 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha
// g->getChatForm()->addSystemInfoMessage(tr("%1 has left the chat").arg(name), "white", QDateTime::currentDateTime());
}
else if (change == TOX_CHAT_CHANGE_PEER_NAME) // core overwrites old name before telling us it changed...
g->updatePeer(peernumber,core->getGroupPeerName(groupnumber, peernumber));
g->updatePeer(peernumber,Nexus::getCore()->getGroupPeerName(groupnumber, peernumber));
}
void Widget::onGroupTitleChanged(int groupnumber, const QString& author, const QString& title)
@ -1043,7 +946,7 @@ void Widget::removeGroup(Group* g, bool fake)
onAddClicked();
}
GroupList::removeGroup(g->getGroupId(), fake);
core->removeGroup(g->getGroupId(), fake);
Nexus::getCore()->removeGroup(g->getGroupId(), fake);
delete g;
if (ui->mainHead->layout()->isEmpty())
onAddClicked();
@ -1057,11 +960,6 @@ void Widget::removeGroup(int groupId)
removeGroup(GroupList::findGroup(groupId));
}
Core *Widget::getCore()
{
return core;
}
Group *Widget::createGroup(int groupId)
{
Group* g = GroupList::findGroup(groupId);
@ -1077,6 +975,7 @@ Group *Widget::createGroup(int groupId)
layout->addWidget(newgroup->getGroupWidget());
newgroup->getGroupWidget()->updateStatusLight();
Core* core = Nexus::getCore();
connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*)));
connect(newgroup->getGroupWidget(), SIGNAL(removeGroup(int)), this, SLOT(removeGroup(int)));
connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newgroup->getChatForm(), SLOT(focusInput()));
@ -1101,20 +1000,20 @@ bool Widget::isFriendWidgetCurActiveWidget(Friend* f)
bool Widget::event(QEvent * e)
{
switch(e->type()) {
switch(e->type())
{
case QEvent::WindowActivate:
if (activeChatroomWidget != nullptr)
{
activeChatroomWidget->resetEventFlags();
activeChatroomWidget->updateStatusLight();
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::Wheel:
case QEvent::KeyPress:
case QEvent::KeyRelease:
if (autoAwayActive)
onUserAwayCheck(); // Just so we get back from away faster when interacting with app
if (eventFlag)
{
eventFlag = false;
eventIcon = false;
updateTrayIcon();
}
default:
break;
}
@ -1150,19 +1049,28 @@ void Widget::onUserAwayCheck()
#endif
}
void Widget::onEventIconTick()
{
if (eventFlag)
{
eventIcon ^= true;
updateTrayIcon();
}
}
void Widget::setStatusOnline()
{
core->setStatus(Status::Online);
Nexus::getCore()->setStatus(Status::Online);
}
void Widget::setStatusAway()
{
core->setStatus(Status::Away);
Nexus::getCore()->setStatus(Status::Away);
}
void Widget::setStatusBusy()
{
core->setStatus(Status::Busy);
Nexus::getCore()->setStatus(Status::Busy);
}
void Widget::onMessageSendResult(int friendId, const QString& message, int messageId)
@ -1185,20 +1093,6 @@ void Widget::onGroupSendResult(int groupId, const QString& message, int result)
g->getChatForm()->addSystemInfoMessage(tr("Message failed to send"), ChatMessage::INFO, QDateTime::currentDateTime());
}
void Widget::getPassword(QString info, int passtype, uint8_t* salt)
{
Core::PasswordType pt = static_cast<Core::PasswordType>(passtype);
InputPasswordDialog dialog(info);
if (dialog.exec())
{
QString pswd = dialog.getPassword();
if (pswd.isEmpty())
core->clearPassword(pt);
else
core->setPassword(pswd, pt, salt);
}
}
void Widget::onFriendTypingChanged(int friendId, bool isTyping)
{
Friend* f = FriendList::findFriend(friendId);
@ -1229,69 +1123,21 @@ void Widget::onSplitterMoved(int pos, int index)
saveSplitterGeometry();
}
QMessageBox::StandardButton Widget::showWarningMsgBox(const QString& title, const QString& msg, QMessageBox::StandardButtons buttons)
{
// We can only display widgets from the GUI thread
if (QThread::currentThread() != qApp->thread())
{
QMessageBox::StandardButton ret;
QMetaObject::invokeMethod(this, "showWarningMsgBox", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QMessageBox::StandardButton, ret),
Q_ARG(const QString&, title), Q_ARG(const QString&, msg),
Q_ARG(QMessageBox::StandardButtons, buttons));
return ret;
}
else
{
return QMessageBox::warning(this, title, msg, buttons);
}
}
void Widget::setEnabledThreadsafe(bool enabled)
{
// We can only do this from the GUI thread
if (QThread::currentThread() != qApp->thread())
{
QMetaObject::invokeMethod(this, "setEnabledThreadsafe", Qt::BlockingQueuedConnection,
Q_ARG(bool, enabled));
return;
}
else
{
return setEnabled(enabled);
}
}
bool Widget::askMsgboxQuestion(const QString& title, const QString& msg)
{
// We can only display widgets from the GUI thread
if (QThread::currentThread() != qApp->thread())
{
bool ret;
QMetaObject::invokeMethod(this, "askMsgboxQuestion", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, ret),
Q_ARG(const QString&, title), Q_ARG(const QString&, msg));
return ret;
}
else
{
return QMessageBox::question(this, title, msg) == QMessageBox::StandardButton::Yes;
}
}
void Widget::clearAllReceipts()
{
QList<Friend*> frnds = FriendList::getAllFriends();
for (Friend *f : frnds)
{
f->getChatForm()->clearReciepts();
f->getChatForm()->getOfflineMsgEngine()->removeAllReciepts();
}
}
void Widget::reloadTheme()
{
QString statusPanelStyle = Style::getStylesheet(":/ui/window/statusPanel.css");
ui->tooliconsZone->setStyleSheet(Style::resolve("QPushButton{background-color:@themeDark;border:none;}QPushButton:hover{background-color:@themeMediumDark;border:none;}"));
ui->statusPanel->setStyleSheet(Style::getStylesheet(":/ui/window/statusPanel.css"));
ui->statusPanel->setStyleSheet(statusPanelStyle);
ui->statusHead->setStyleSheet(statusPanelStyle);
ui->friendList->setStyleSheet(Style::getStylesheet(":ui/friendList/friendList.css"));
ui->statusButton->setStyleSheet(Style::getStylesheet(":ui/statusButton/statusButton.css"));

View File

@ -54,21 +54,14 @@ public:
explicit Widget(QWidget *parent = 0);
void setCentralWidget(QWidget *widget, const QString &widgetName);
QString getUsername();
Core* getCore();
QThread* getCoreThread();
Camera* getCamera();
static Widget* getInstance();
void newMessageAlert(GenericChatroomWidget* chat);
bool isFriendWidgetCurActiveWidget(Friend* f);
bool getIsWindowMinimized();
static QList<QString> searchProfiles();
void clearContactsList();
void setTranslation();
void updateTrayIcon();
Q_INVOKABLE QMessageBox::StandardButton showWarningMsgBox(const QString& title, const QString& msg,
QMessageBox::StandardButtons buttonss = QMessageBox::Ok);
Q_INVOKABLE void setEnabledThreadsafe(bool enabled);
Q_INVOKABLE bool askMsgboxQuestion(const QString& title, const QString& msg);
~Widget();
virtual void closeEvent(QCloseEvent *event);
@ -76,6 +69,7 @@ public:
virtual void resizeEvent(QResizeEvent *event);
void clearAllReceipts();
void reloadHistory();
void reloadTheme();
@ -83,6 +77,29 @@ public slots:
void onSettingsClicked();
void setWindowTitle(const QString& title);
void forceShow();
void onConnected();
void onDisconnected();
void onStatusSet(Status status);
void onFailedToStartCore();
void onBadProxyCore();
void onSelfAvatarLoaded(const QPixmap &pic);
void setUsername(const QString& username);
void setStatusMessage(const QString &statusMessage);
void addFriend(int friendId, const QString& userId);
void addFriendFailed(const QString& userId, const QString& errorInfo = QString());
void onFriendStatusChanged(int friendId, Status status);
void onFriendStatusMessageChanged(int friendId, const QString& message);
void onFriendUsernameChanged(int friendId, const QString& username);
void onFriendMessageReceived(int friendId, const QString& message, bool isAction);
void onFriendRequestReceived(const QString& userId, const QString& message);
void onReceiptRecieved(int friendId, int receipt);
void onEmptyGroupCreated(int groupId);
void onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray invite);
void onGroupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction);
void onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t change);
void onGroupTitleChanged(int groupnumber, const QString& author, const QString& title);
void playRingtone();
void onFriendTypingChanged(int friendId, bool isTyping);
signals:
void friendRequestAccepted(const QString& userId);
@ -95,34 +112,13 @@ signals:
void resized();
private slots:
void onConnected();
void onDisconnected();
void onStatusSet(Status status);
void onAddClicked();
void onGroupClicked();
void onTransferClicked();
void onFailedToStartCore();
void onBadProxyCore();
void onAvatarClicked();
void onSelfAvatarLoaded(const QPixmap &pic);
void onUsernameChanged(const QString& newUsername, const QString& oldUsername);
void onStatusMessageChanged(const QString& newStatusMessage, const QString& oldStatusMessage);
void setUsername(const QString& username);
void setStatusMessage(const QString &statusMessage);
void addFriend(int friendId, const QString& userId);
void addFriendFailed(const QString& userId, const QString& errorInfo = QString());
void onFriendStatusChanged(int friendId, Status status);
void onFriendStatusMessageChanged(int friendId, const QString& message);
void onFriendUsernameChanged(int friendId, const QString& username);
void onChatroomWidgetClicked(GenericChatroomWidget *);
void onFriendMessageReceived(int friendId, const QString& message, bool isAction);
void onFriendRequestReceived(const QString& userId, const QString& message);
void onReceiptRecieved(int friendId, int receipt);
void onEmptyGroupCreated(int groupId);
void onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray invite);
void onGroupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction);
void onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t change);
void onGroupTitleChanged(int groupnumber, const QString& author, const QString& title);
void removeFriend(int friendId);
void copyFriendIdToClipboard(int friendId);
void removeGroup(int groupId);
@ -131,11 +127,9 @@ private slots:
void setStatusBusy();
void onMessageSendResult(int friendId, const QString& message, int messageId);
void onGroupSendResult(int groupId, const QString& message, int result);
void playRingtone();
void onIconClick(QSystemTrayIcon::ActivationReason);
void onUserAwayCheck();
void getPassword(QString info, int passtype, uint8_t* salt);
void onFriendTypingChanged(int friendId, bool isTyping);
void onEventIconTick();
void onSetShowSystemTray(bool newValue);
void onSplitterMoved(int pos, int index);
@ -148,8 +142,6 @@ private:
void removeGroup(Group* g, bool fake = false);
void saveWindowGeometry();
void saveSplitterGeometry();
QString askProfiles();
QString detectProfile();
SystemTrayIcon *icon;
QMenu *trayMenu;
QAction *statusOnline,
@ -160,8 +152,6 @@ private:
Ui::MainWindow *ui;
QSplitter *centralLayout;
QPoint dragPosition;
Core* core;
QThread* coreThread;
AddFriendForm* addFriendForm;
SettingsWidget* settingsWidget;
FilesForm* filesForm;
@ -172,9 +162,11 @@ private:
bool notify(QObject *receiver, QEvent *event);
bool autoAwayActive = false;
Status beforeDisconnect = Status::Offline;
QTimer* idleTimer;
QTimer* timer, *offlineMsgTimer;
QTranslator* translator;
QRegExp nameMention, sanitizedNameMention;
bool eventFlag;
bool eventIcon;
};
void toxActivateEventHandler(const QByteArray& data);

141
translations/de.ts vendored
View File

@ -743,10 +743,6 @@ Alias:</translation>
<source>Autoaccept files</source>
<translation>Dateien automatisch annehmen</translation>
</message>
<message>
<source>Save files in</source>
<translation type="vanished">Speichern unter</translation>
</message>
<message>
<location filename="../src/widget/form/settings/generalsettings.ui" line="215"/>
<source>PushButton</source>
@ -1260,24 +1256,119 @@ Soll die alte Historiedatei gelöscht werden?</translation>
<translation>Tippen anzeigen</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="60"/>
<source>Keep History (unstable)</source>
<translation>Historie behalten (instabil)</translation>
<location filename="../src/widget/form/settings/privacysettings.ui" line="61"/>
<source>Keep chat history (mostly stable)</source>
<translation>Chatverlauf speichern (größtenteils stabil)</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="70"/>
<source>Encryption</source>
<translation>Verschlüsselung</translation>
<location filename="../src/widget/form/settings/privacysettings.ui" line="71"/>
<source>Local file encryption</source>
<translation>Lokale Dateien verschlüsseln</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="79"/>
<source>Encrypt Tox datafile</source>
<location filename="../src/widget/form/settings/privacysettings.ui" line="77"/>
<source>All Tox communications over the internet are encrypted, and this cannot be disabled. However, you may optionally password protect your local Tox files.</source>
<translation>Die gesamte Tox-Kommunikation über das Internet ist verschlüsselt und kann auch nicht deaktiviert werden. Es ist optional auch möglich, die lokal gespeicherten Tox-Daten mit einem Passwort zu schützen.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="92"/>
<source>Encrypt Tox data file</source>
<translation>Tox Datendatei verschlüsseln</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="89"/>
<source>Encrypt History</source>
<translation>Historie verschlüsseln</translation>
<location filename="../src/widget/form/settings/privacysettings.ui" line="113"/>
<source>Encrypt chat history</source>
<translation>Chatverlauf verschlüsseln</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="99"/>
<location filename="../src/widget/form/settings/privacysettings.ui" line="123"/>
<source>Change password</source>
<translation>Passwort ändern</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="77"/>
<source>Please set your new chat history password.</source>
<translation>Bitte Passwort zum Verschlüsseln des Chatverlaufs setzen.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="79"/>
<source>It appears you have an unused encrypted chat history; if the password matches, it will be added to your current history.</source>
<translation>Es scheint als gäbe es einen unverschlüsselten Chatverlauf. Wenn die Passwörter übereinstimmen wird dieser zum aktuellen Chatverlauf hinzugefügt.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="82"/>
<source>Use data file password</source>
<translation>Nutze das Tox-Datendatei Passwort</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="109"/>
<source>Successfully decrypted old chat history</source>
<translation>Alter Chatverlauf erfolgreich entschlüsselt</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="109"/>
<source>You have succesfully decrypted the old chat history, and it has been added to your current history and re-encrypted.</source>
<translation>Der alte Chatverlauf wurde erfolgreich entschlüsselt, zum aktuellen Chatverlauf hinzugefügt und wieder verschlüsselt.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="116"/>
<location filename="../src/widget/form/settings/privacyform.cpp" line="145"/>
<location filename="../src/widget/form/settings/privacyform.cpp" line="162"/>
<source>Old encrypted chat history</source>
<translation>Alter verschlüsselter Chatverlauf</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="116"/>
<source>There is currently an unused encrypted chat history, but the password you just entered doesn't match.
Would you like to try again?
Canceling will delete the old history and set the password to what you just entered.</source>
<translation>Es gibt aktuell einen ungenutzten verschlüsselten Chatverlauf, aber das eingegebene Passwort stimmt nicht überein.
Möchten Sie ein anderes probieren?
Wenn Sie abbrechen wird der alte Chatverlauf gelöscht und das soeben eingegebene Passwort verwendet.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="116"/>
<source>This happens when enabling encryption after previously \"Disabling History\"</source>
<translation>Dies passiert, wenn die Verschlüsselung nach einem vorherigen Deaktivieren des Chatverlaufs wieder aktiviert wird.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="146"/>
<source>Would you like to decrypt your chat history?
Otherwise it will be deleted.</source>
<translation>Möchten Sie den Chatverlauf entschlüsseln?
Ansonsten wird dieser gelöscht.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="163"/>
<source>Are you sure you want to lose your entire chat history?</source>
<translation>Sind Sie sicher, dass der gesamte Chatverlauf gelöscht werden soll?</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="195"/>
<source>Please set your new data file password.</source>
<translation>Bitte Passwort zum Verschlüsseln der Datendatei setzen.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="197"/>
<source>Use chat history password</source>
<translation>Nutze das Chatverlauf-Passwort</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="240"/>
<source>Decrypt your data file</source>
<translation>Datendatei entschlüsseln</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacyform.cpp" line="240"/>
<source>Would you like to decrypt your data file?</source>
<translation>Möchten Sie die Datendatei entschlüsseln?</translation>
</message>
<message>
<location filename="../src/widget/form/setpassworddialog.cpp" line="33"/>
<location filename="../src/widget/form/setpassworddialog.cpp" line="61"/>
<source>The passwords don't match.</source>
<translation>Die Passwörter stimmen nicht überein.</translation>
</message>
<message>
<location filename="../src/widget/form/settings/privacysettings.ui" line="102"/>
@ -1399,14 +1490,24 @@ Es wird beim Neustart von qTox installiert.</translation>
<context>
<name>SetPasswordDialog</name>
<message>
<location filename="../src/widget/form/setpassworddialog.ui" line="23"/>
<source>Type Password</source>
<location filename="../src/widget/form/setpassworddialog.ui" line="14"/>
<source>Set your password</source>
<translation>Passwort setzen</translation>
</message>
<message>
<location filename="../src/widget/form/setpassworddialog.ui" line="41"/>
<source>Type password</source>
<translation>Passwort eingeben</translation>
</message>
<message>
<location filename="../src/widget/form/setpassworddialog.ui" line="37"/>
<source>Repeat Password</source>
<translation>Passworteingabe wiederholen</translation>
<location filename="../src/widget/form/setpassworddialog.ui" line="31"/>
<source>Repeat password</source>
<translation>Passwort wiederholen</translation>
</message>
<message>
<location filename="../src/widget/form/setpassworddialog.ui" line="65"/>
<source>Password strength</source>
<translation>Passwortstärke</translation>
</message>
</context>
<context>

Some files were not shown because too many files have changed in this diff Show More