Compare commits
83 Commits
work/tfell
...
1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5837a208a | ||
|
|
3b45c0b2f7 | ||
|
|
e1a72816d0 | ||
|
|
aea46c547f | ||
|
|
fe009f764e | ||
|
|
8c14f8d2eb | ||
|
|
559da66197 | ||
|
|
759496620f | ||
|
|
1f7b1ad29f | ||
|
|
9338567137 | ||
|
|
75d2488dbc | ||
|
|
4bed6a07cd | ||
|
|
cb1c9d1869 | ||
|
|
76b2d4ac49 | ||
|
|
5fc931484e | ||
|
|
e60ed3b889 | ||
|
|
1314567fde | ||
|
|
047aa793e7 | ||
|
|
d60f8f4dfa | ||
|
|
20139c08d8 | ||
|
|
fb59bac165 | ||
|
|
5a65dc4d9a | ||
|
|
03ec17366e | ||
|
|
15137cd704 | ||
|
|
2aea0015ba | ||
|
|
f6fd099808 | ||
|
|
04082fc095 | ||
|
|
bc977c3fc6 | ||
|
|
c4c283c85a | ||
|
|
56f49fabf7 | ||
|
|
fe407a3421 | ||
|
|
bbe539885e | ||
|
|
ff978b9586 | ||
|
|
885b75e35f | ||
|
|
cb81eaf26f | ||
|
|
22732b801b | ||
|
|
ae3e395b47 | ||
|
|
96c91e2a35 | ||
|
|
e36204bbd8 | ||
|
|
1804140ac0 | ||
|
|
5a28a93ab6 | ||
|
|
76bd529c3c | ||
|
|
293288a0b6 | ||
|
|
51b6593f96 | ||
|
|
51e73568c4 | ||
|
|
e461e2098b | ||
|
|
79ceb45fae | ||
|
|
0c292b34ff | ||
|
|
db6640ba49 | ||
|
|
3827249f0c | ||
|
|
f40a3daef4 | ||
|
|
8d2608a230 | ||
|
|
3e5628def3 | ||
|
|
3b3673fdff | ||
|
|
d81e4c417d | ||
|
|
44d3f628d9 | ||
|
|
0db9c0454f | ||
|
|
e5c65a662e | ||
|
|
8913aa8a66 | ||
|
|
5db3e14ae6 | ||
|
|
c5a3fc0431 | ||
|
|
fc791d41fa | ||
|
|
127ad19109 | ||
|
|
066ea4f8bd | ||
|
|
cf60337b27 | ||
|
|
98672cf870 | ||
|
|
abd03299ec | ||
|
|
41b64f977c | ||
|
|
ac75dd57c0 | ||
|
|
1d3d61ed77 | ||
|
|
1e047a8ff1 | ||
|
|
530b4c24a0 | ||
|
|
ada7bcef65 | ||
|
|
ad4ca3ad9e | ||
|
|
4103c44eb5 | ||
|
|
dd4ed7539e | ||
|
|
52ad911b2d | ||
|
|
f09dff979e | ||
|
|
0476398f91 | ||
|
|
41993bfe24 | ||
|
|
b3d90ebf82 | ||
|
|
ef0a6e276c | ||
|
|
a104968a29 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,4 +5,3 @@ build
|
|||||||
neochat.kdev4
|
neochat.kdev4
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
.cache/
|
.cache/
|
||||||
.vscode/
|
|
||||||
|
|||||||
29
.reuse/dep5
29
.reuse/dep5
@@ -13,32 +13,3 @@ License: CC0-1.0
|
|||||||
Files: android/res/drawable/splash.xml
|
Files: android/res/drawable/splash.xml
|
||||||
Copyright: 2020 Tobias Fella <fella@posteo.de>
|
Copyright: 2020 Tobias Fella <fella@posteo.de>
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
|
|
||||||
Files: */qmldir .gitignore
|
|
||||||
Copyright: None
|
|
||||||
License: CC0-1.0
|
|
||||||
|
|
||||||
Files: .gitlab/issue_templates/bug.md
|
|
||||||
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
|
|
||||||
License: CC0-1.0
|
|
||||||
|
|
||||||
Files: res.qrc
|
|
||||||
Copyright: None
|
|
||||||
License: CC0-1.0
|
|
||||||
|
|
||||||
Files: cmake/Flatpak/99-noto-mono-color-emoji.conf
|
|
||||||
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
|
|
||||||
License: BSD-2-Clause
|
|
||||||
|
|
||||||
Files: src/neochatconfig.kcfg
|
|
||||||
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>
|
|
||||||
Copyright: 2020-2021 Tobias Fella <fella@posteo.de>
|
|
||||||
License: BSD-2-Clause
|
|
||||||
|
|
||||||
Files: neochat.notifyrc
|
|
||||||
Copyright: 2020 Tobias Fella <fella@posteo.de>
|
|
||||||
License: BSD-2-Clause
|
|
||||||
|
|
||||||
Files: imports/NeoChat/Component/confetti.png imports/NeoChat/Component/glowdot.png
|
|
||||||
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
|
|
||||||
License: CC0-1.0
|
|
||||||
|
|||||||
@@ -4,22 +4,20 @@
|
|||||||
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
|
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
|
||||||
# SPDX-License-Identifier: BSD-2-Clause
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.1)
|
||||||
|
|
||||||
project(NeoChat)
|
project(NeoChat)
|
||||||
|
|
||||||
set(KF5_MIN_VERSION "5.87.0")
|
set(KF5_MIN_VERSION "5.77.0")
|
||||||
set(QT_MIN_VERSION "5.15.0")
|
set(QT_MIN_VERSION "5.15.0")
|
||||||
|
|
||||||
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
|
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
|
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
set(KDE_COMPILERSETTINGS_LEVEL 5.84)
|
|
||||||
|
|
||||||
include(FeatureSummary)
|
include(FeatureSummary)
|
||||||
include(ECMSetupVersion)
|
include(ECMSetupVersion)
|
||||||
include(KDEInstallDirs)
|
include(KDEInstallDirs)
|
||||||
@@ -28,8 +26,6 @@ include(KDEClangFormat)
|
|||||||
include(KDECMakeSettings)
|
include(KDECMakeSettings)
|
||||||
include(KDECompilerSettings NO_POLICY_SCOPE)
|
include(KDECompilerSettings NO_POLICY_SCOPE)
|
||||||
include(ECMAddAppIcon)
|
include(ECMAddAppIcon)
|
||||||
include(KDEGitCommitHooks)
|
|
||||||
include(ECMCheckOutboundLicense)
|
|
||||||
|
|
||||||
if(NEOCHAT_FLATPAK)
|
if(NEOCHAT_FLATPAK)
|
||||||
include(cmake/Flatpak.cmake)
|
include(cmake/Flatpak.cmake)
|
||||||
@@ -38,7 +34,7 @@ endif()
|
|||||||
# Fix a crash due to problems with quotient's event system. Can probably be removed once the reworked event system is in
|
# Fix a crash due to problems with quotient's event system. Can probably be removed once the reworked event system is in
|
||||||
cmake_policy(SET CMP0063 OLD)
|
cmake_policy(SET CMP0063 OLD)
|
||||||
|
|
||||||
ecm_setup_version(1.2.80
|
ecm_setup_version(1.2.0
|
||||||
VARIABLE_PREFIX NEOCHAT
|
VARIABLE_PREFIX NEOCHAT
|
||||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
||||||
)
|
)
|
||||||
@@ -72,11 +68,10 @@ if(ANDROID)
|
|||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
find_package(Qt5 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
find_package(Qt5 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||||
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem Sonnet)
|
find_package(KF5QQC2DesktopStyle ${KF5_MIN_VERSION} REQUIRED)
|
||||||
set_package_properties(KF5QQC2DesktopStyle PROPERTIES
|
set_package_properties(KF5QQC2DesktopStyle PROPERTIES
|
||||||
TYPE RUNTIME
|
TYPE RUNTIME
|
||||||
)
|
)
|
||||||
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||||
@@ -110,17 +105,6 @@ set_package_properties(KQuickImageEditor PROPERTIES
|
|||||||
PURPOSE "Add image editing capability to image attachments"
|
PURPOSE "Add image editing capability to image attachments"
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(QCoro REQUIRED)
|
|
||||||
|
|
||||||
qcoro_enable_coroutines()
|
|
||||||
|
|
||||||
include(FindPkgConfig)
|
|
||||||
pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.16 gstreamer-webrtc-1.0>=1.16)
|
|
||||||
|
|
||||||
if(ANDROID)
|
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
install(FILES org.kde.neochat.desktop DESTINATION ${KDE_INSTALL_APPDIR})
|
install(FILES org.kde.neochat.desktop DESTINATION ${KDE_INSTALL_APPDIR})
|
||||||
install(FILES org.kde.neochat.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
|
install(FILES org.kde.neochat.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
|
||||||
install(FILES org.kde.neochat.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
|
install(FILES org.kde.neochat.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
|
||||||
@@ -138,7 +122,3 @@ feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAG
|
|||||||
|
|
||||||
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
|
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
|
||||||
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
|
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
|
||||||
|
|
||||||
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
|
|
||||||
file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h *.qml)
|
|
||||||
ecm_check_outbound_license(LICENSES GPL-3.0-only FILES ${ALL_SOURCE_FILES})
|
|
||||||
|
|||||||
@@ -1,446 +0,0 @@
|
|||||||
GNU LIBRARY GENERAL PUBLIC LICENSE
|
|
||||||
|
|
||||||
Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc.
|
|
||||||
|
|
||||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies of this license
|
|
||||||
document, but changing it is not allowed.
|
|
||||||
|
|
||||||
[This is the first released version of the library GPL. It is numbered 2 because
|
|
||||||
it goes with version 2 of the ordinary GPL.]
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The licenses for most software are designed to take away your freedom to share
|
|
||||||
and change it. By contrast, the GNU General Public Licenses are intended to
|
|
||||||
guarantee your freedom to share and change free software--to make sure the
|
|
||||||
software is free for all its users.
|
|
||||||
|
|
||||||
This license, the Library General Public License, applies to some specially
|
|
||||||
designated Free Software Foundation software, and to any other libraries whose
|
|
||||||
authors decide to use it. You can use it for your libraries, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not price. Our
|
|
||||||
General Public Licenses are designed to make sure that you have the freedom
|
|
||||||
to distribute copies of free software (and charge for this service if you
|
|
||||||
wish), that you receive source code or can get it if you want it, that you
|
|
||||||
can change the software or use pieces of it in new free programs; and that
|
|
||||||
you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to make restrictions that forbid anyone to
|
|
||||||
deny you these rights or to ask you to surrender the rights. These restrictions
|
|
||||||
translate to certain responsibilities for you if you distribute copies of
|
|
||||||
the library, or if you modify it.
|
|
||||||
|
|
||||||
For example, if you distribute copies of the library, whether gratis or for
|
|
||||||
a fee, you must give the recipients all the rights that we gave you. You must
|
|
||||||
make sure that they, too, receive or can get the source code. If you link
|
|
||||||
a program with the library, you must provide complete object files to the
|
|
||||||
recipients so that they can relink them with the library, after making changes
|
|
||||||
to the library and recompiling it. And you must show them these terms so they
|
|
||||||
know their rights.
|
|
||||||
|
|
||||||
Our method of protecting your rights has two steps: (1) copyright the library,
|
|
||||||
and (2) offer you this license which gives you legal permission to copy, distribute
|
|
||||||
and/or modify the library.
|
|
||||||
|
|
||||||
Also, for each distributor's protection, we want to make certain that everyone
|
|
||||||
understands that there is no warranty for this free library. If the library
|
|
||||||
is modified by someone else and passed on, we want its recipients to know
|
|
||||||
that what they have is not the original version, so that any problems introduced
|
|
||||||
by others will not reflect on the original authors' reputations.
|
|
||||||
|
|
||||||
Finally, any free program is threatened constantly by software patents. We
|
|
||||||
wish to avoid the danger that companies distributing free software will individually
|
|
||||||
obtain patent licenses, thus in effect transforming the program into proprietary
|
|
||||||
software. To prevent this, we have made it clear that any patent must be licensed
|
|
||||||
for everyone's free use or not licensed at all.
|
|
||||||
|
|
||||||
Most GNU software, including some libraries, is covered by the ordinary GNU
|
|
||||||
General Public License, which was designed for utility programs. This license,
|
|
||||||
the GNU Library General Public License, applies to certain designated libraries.
|
|
||||||
This license is quite different from the ordinary one; be sure to read it
|
|
||||||
in full, and don't assume that anything in it is the same as in the ordinary
|
|
||||||
license.
|
|
||||||
|
|
||||||
The reason we have a separate public license for some libraries is that they
|
|
||||||
blur the distinction we usually make between modifying or adding to a program
|
|
||||||
and simply using it. Linking a program with a library, without changing the
|
|
||||||
library, is in some sense simply using the library, and is analogous to running
|
|
||||||
a utility program or application program. However, in a textual and legal
|
|
||||||
sense, the linked executable is a combined work, a derivative of the original
|
|
||||||
library, and the ordinary General Public License treats it as such.
|
|
||||||
|
|
||||||
Because of this blurred distinction, using the ordinary General Public License
|
|
||||||
for libraries did not effectively promote software sharing, because most developers
|
|
||||||
did not use the libraries. We concluded that weaker conditions might promote
|
|
||||||
sharing better.
|
|
||||||
|
|
||||||
However, unrestricted linking of non-free programs would deprive the users
|
|
||||||
of those programs of all benefit from the free status of the libraries themselves.
|
|
||||||
This Library General Public License is intended to permit developers of non-free
|
|
||||||
programs to use free libraries, while preserving your freedom as a user of
|
|
||||||
such programs to change the free libraries that are incorporated in them.
|
|
||||||
(We have not seen how to achieve this as regards changes in header files,
|
|
||||||
but we have achieved it as regards changes in the actual functions of the
|
|
||||||
Library.) The hope is that this will lead to faster development of free libraries.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and modification
|
|
||||||
follow. Pay close attention to the difference between a "work based on the
|
|
||||||
library" and a "work that uses the library". The former contains code derived
|
|
||||||
from the library, while the latter only works together with the library.
|
|
||||||
|
|
||||||
Note that it is possible for a library to be covered by the ordinary General
|
|
||||||
Public License rather than by this special one.
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. This License Agreement applies to any software library which contains a
|
|
||||||
notice placed by the copyright holder or other authorized party saying it
|
|
||||||
may be distributed under the terms of this Library General Public License
|
|
||||||
(also called "this License"). Each licensee is addressed as "you".
|
|
||||||
|
|
||||||
A "library" means a collection of software functions and/or data prepared
|
|
||||||
so as to be conveniently linked with application programs (which use some
|
|
||||||
of those functions and data) to form executables.
|
|
||||||
|
|
||||||
The "Library", below, refers to any such software library or work which has
|
|
||||||
been distributed under these terms. A "work based on the Library" means either
|
|
||||||
the Library or any derivative work under copyright law: that is to say, a
|
|
||||||
work containing the Library or a portion of it, either verbatim or with modifications
|
|
||||||
and/or translated straightforwardly into another language. (Hereinafter, translation
|
|
||||||
is included without limitation in the term "modification".)
|
|
||||||
|
|
||||||
"Source code" for a work means the preferred form of the work for making modifications
|
|
||||||
to it. For a library, complete source code means all the source code for all
|
|
||||||
modules it contains, plus any associated interface definition files, plus
|
|
||||||
the scripts used to control compilation and installation of the library.
|
|
||||||
|
|
||||||
Activities other than copying, distribution and modification are not covered
|
|
||||||
by this License; they are outside its scope. The act of running a program
|
|
||||||
using the Library is not restricted, and output from such a program is covered
|
|
||||||
only if its contents constitute a work based on the Library (independent of
|
|
||||||
the use of the Library in a tool for writing it). Whether that is true depends
|
|
||||||
on what the Library does and what the program that uses the Library does.
|
|
||||||
|
|
||||||
1. You may copy and distribute verbatim copies of the Library's complete source
|
|
||||||
code as you receive it, in any medium, provided that you conspicuously and
|
|
||||||
appropriately publish on each copy an appropriate copyright notice and disclaimer
|
|
||||||
of warranty; keep intact all the notices that refer to this License and to
|
|
||||||
the absence of any warranty; and distribute a copy of this License along with
|
|
||||||
the Library.
|
|
||||||
|
|
||||||
You may charge a fee for the physical act of transferring a copy, and you
|
|
||||||
may at your option offer warranty protection in exchange for a fee.
|
|
||||||
|
|
||||||
2. You may modify your copy or copies of the Library or any portion of it,
|
|
||||||
thus forming a work based on the Library, and copy and distribute such modifications
|
|
||||||
or work under the terms of Section 1 above, provided that you also meet all
|
|
||||||
of these conditions:
|
|
||||||
|
|
||||||
a) The modified work must itself be a software library.
|
|
||||||
|
|
||||||
b) You must cause the files modified to carry prominent notices stating that
|
|
||||||
you changed the files and the date of any change.
|
|
||||||
|
|
||||||
c) You must cause the whole of the work to be licensed at no charge to all
|
|
||||||
third parties under the terms of this License.
|
|
||||||
|
|
||||||
d) If a facility in the modified Library refers to a function or a table of
|
|
||||||
data to be supplied by an application program that uses the facility, other
|
|
||||||
than as an argument passed when the facility is invoked, then you must make
|
|
||||||
a good faith effort to ensure that, in the event an application does not supply
|
|
||||||
such function or table, the facility still operates, and performs whatever
|
|
||||||
part of its purpose remains meaningful.
|
|
||||||
|
|
||||||
(For example, a function in a library to compute square roots has a purpose
|
|
||||||
that is entirely well-defined independent of the application. Therefore, Subsection
|
|
||||||
2d requires that any application-supplied function or table used by this function
|
|
||||||
must be optional: if the application does not supply it, the square root function
|
|
||||||
must still compute square roots.)
|
|
||||||
|
|
||||||
These requirements apply to the modified work as a whole. If identifiable
|
|
||||||
sections of that work are not derived from the Library, and can be reasonably
|
|
||||||
considered independent and separate works in themselves, then this License,
|
|
||||||
and its terms, do not apply to those sections when you distribute them as
|
|
||||||
separate works. But when you distribute the same sections as part of a whole
|
|
||||||
which is a work based on the Library, the distribution of the whole must be
|
|
||||||
on the terms of this License, whose permissions for other licensees extend
|
|
||||||
to the entire whole, and thus to each and every part regardless of who wrote
|
|
||||||
it.
|
|
||||||
|
|
||||||
Thus, it is not the intent of this section to claim rights or contest your
|
|
||||||
rights to work written entirely by you; rather, the intent is to exercise
|
|
||||||
the right to control the distribution of derivative or collective works based
|
|
||||||
on the Library.
|
|
||||||
|
|
||||||
In addition, mere aggregation of another work not based on the Library with
|
|
||||||
the Library (or with a work based on the Library) on a volume of a storage
|
|
||||||
or distribution medium does not bring the other work under the scope of this
|
|
||||||
License.
|
|
||||||
|
|
||||||
3. You may opt to apply the terms of the ordinary GNU General Public License
|
|
||||||
instead of this License to a given copy of the Library. To do this, you must
|
|
||||||
alter all the notices that refer to this License, so that they refer to the
|
|
||||||
ordinary GNU General Public License, version 2, instead of to this License.
|
|
||||||
(If a newer version than version 2 of the ordinary GNU General Public License
|
|
||||||
has appeared, then you can specify that version instead if you wish.) Do not
|
|
||||||
make any other change in these notices.
|
|
||||||
|
|
||||||
Once this change is made in a given copy, it is irreversible for that copy,
|
|
||||||
so the ordinary GNU General Public License applies to all subsequent copies
|
|
||||||
and derivative works made from that copy.
|
|
||||||
|
|
||||||
This option is useful when you wish to copy part of the code of the Library
|
|
||||||
into a program that is not a library.
|
|
||||||
|
|
||||||
4. You may copy and distribute the Library (or a portion or derivative of
|
|
||||||
it, under Section 2) in object code or executable form under the terms of
|
|
||||||
Sections 1 and 2 above provided that you accompany it with the complete corresponding
|
|
||||||
machine-readable source code, which must be distributed under the terms of
|
|
||||||
Sections 1 and 2 above on a medium customarily used for software interchange.
|
|
||||||
|
|
||||||
If distribution of object code is made by offering access to copy from a designated
|
|
||||||
place, then offering equivalent access to copy the source code from the same
|
|
||||||
place satisfies the requirement to distribute the source code, even though
|
|
||||||
third parties are not compelled to copy the source along with the object code.
|
|
||||||
|
|
||||||
5. A program that contains no derivative of any portion of the Library, but
|
|
||||||
is designed to work with the Library by being compiled or linked with it,
|
|
||||||
is called a "work that uses the Library". Such a work, in isolation, is not
|
|
||||||
a derivative work of the Library, and therefore falls outside the scope of
|
|
||||||
this License.
|
|
||||||
|
|
||||||
However, linking a "work that uses the Library" with the Library creates an
|
|
||||||
executable that is a derivative of the Library (because it contains portions
|
|
||||||
of the Library), rather than a "work that uses the library". The executable
|
|
||||||
is therefore covered by this License. Section 6 states terms for distribution
|
|
||||||
of such executables.
|
|
||||||
|
|
||||||
When a "work that uses the Library" uses material from a header file that
|
|
||||||
is part of the Library, the object code for the work may be a derivative work
|
|
||||||
of the Library even though the source code is not. Whether this is true is
|
|
||||||
especially significant if the work can be linked without the Library, or if
|
|
||||||
the work is itself a library. The threshold for this to be true is not precisely
|
|
||||||
defined by law.
|
|
||||||
|
|
||||||
If such an object file uses only numerical parameters, data structure layouts
|
|
||||||
and accessors, and small macros and small inline functions (ten lines or less
|
|
||||||
in length), then the use of the object file is unrestricted, regardless of
|
|
||||||
whether it is legally a derivative work. (Executables containing this object
|
|
||||||
code plus portions of the Library will still fall under Section 6.)
|
|
||||||
|
|
||||||
Otherwise, if the work is a derivative of the Library, you may distribute
|
|
||||||
the object code for the work under the terms of Section 6. Any executables
|
|
||||||
containing that work also fall under Section 6, whether or not they are linked
|
|
||||||
directly with the Library itself.
|
|
||||||
|
|
||||||
6. As an exception to the Sections above, you may also compile or link a "work
|
|
||||||
that uses the Library" with the Library to produce a work containing portions
|
|
||||||
of the Library, and distribute that work under terms of your choice, provided
|
|
||||||
that the terms permit modification of the work for the customer's own use
|
|
||||||
and reverse engineering for debugging such modifications.
|
|
||||||
|
|
||||||
You must give prominent notice with each copy of the work that the Library
|
|
||||||
is used in it and that the Library and its use are covered by this License.
|
|
||||||
You must supply a copy of this License. If the work during execution displays
|
|
||||||
copyright notices, you must include the copyright notice for the Library among
|
|
||||||
them, as well as a reference directing the user to the copy of this License.
|
|
||||||
Also, you must do one of these things:
|
|
||||||
|
|
||||||
a) Accompany the work with the complete corresponding machine-readable source
|
|
||||||
code for the Library including whatever changes were used in the work (which
|
|
||||||
must be distributed under Sections 1 and 2 above); and, if the work is an
|
|
||||||
executable linked with the Library, with the complete machine-readable "work
|
|
||||||
that uses the Library", as object code and/or source code, so that the user
|
|
||||||
can modify the Library and then relink to produce a modified executable containing
|
|
||||||
the modified Library. (It is understood that the user who changes the contents
|
|
||||||
of definitions files in the Library will not necessarily be able to recompile
|
|
||||||
the application to use the modified definitions.)
|
|
||||||
|
|
||||||
b) Accompany the work with a written offer, valid for at least three years,
|
|
||||||
to give the same user the materials specified in Subsection 6a, above, for
|
|
||||||
a charge no more than the cost of performing this distribution.
|
|
||||||
|
|
||||||
c) If distribution of the work is made by offering access to copy from a designated
|
|
||||||
place, offer equivalent access to copy the above specified materials from
|
|
||||||
the same place.
|
|
||||||
|
|
||||||
d) Verify that the user has already received a copy of these materials or
|
|
||||||
that you have already sent this user a copy.
|
|
||||||
|
|
||||||
For an executable, the required form of the "work that uses the Library" must
|
|
||||||
include any data and utility programs needed for reproducing the executable
|
|
||||||
from it. However, as a special exception, the source code distributed need
|
|
||||||
not include anything that is normally distributed (in either source or binary
|
|
||||||
form) with the major components (compiler, kernel, and so on) of the operating
|
|
||||||
system on which the executable runs, unless that component itself accompanies
|
|
||||||
the executable.
|
|
||||||
|
|
||||||
It may happen that this requirement contradicts the license restrictions of
|
|
||||||
other proprietary libraries that do not normally accompany the operating system.
|
|
||||||
Such a contradiction means you cannot use both them and the Library together
|
|
||||||
in an executable that you distribute.
|
|
||||||
|
|
||||||
7. You may place library facilities that are a work based on the Library side-by-side
|
|
||||||
in a single library together with other library facilities not covered by
|
|
||||||
this License, and distribute such a combined library, provided that the separate
|
|
||||||
distribution of the work based on the Library and of the other library facilities
|
|
||||||
is otherwise permitted, and provided that you do these two things:
|
|
||||||
|
|
||||||
a) Accompany the combined library with a copy of the same work based on the
|
|
||||||
Library, uncombined with any other library facilities. This must be distributed
|
|
||||||
under the terms of the Sections above.
|
|
||||||
|
|
||||||
b) Give prominent notice with the combined library of the fact that part of
|
|
||||||
it is a work based on the Library, and explaining where to find the accompanying
|
|
||||||
uncombined form of the same work.
|
|
||||||
|
|
||||||
8. You may not copy, modify, sublicense, link with, or distribute the Library
|
|
||||||
except as expressly provided under this License. Any attempt otherwise to
|
|
||||||
copy, modify, sublicense, link with, or distribute the Library is void, and
|
|
||||||
will automatically terminate your rights under this License. However, parties
|
|
||||||
who have received copies, or rights, from you under this License will not
|
|
||||||
have their licenses terminated so long as such parties remain in full compliance.
|
|
||||||
|
|
||||||
9. You are not required to accept this License, since you have not signed
|
|
||||||
it. However, nothing else grants you permission to modify or distribute the
|
|
||||||
Library or its derivative works. These actions are prohibited by law if you
|
|
||||||
do not accept this License. Therefore, by modifying or distributing the Library
|
|
||||||
(or any work based on the Library), you indicate your acceptance of this License
|
|
||||||
to do so, and all its terms and conditions for copying, distributing or modifying
|
|
||||||
the Library or works based on it.
|
|
||||||
|
|
||||||
10. Each time you redistribute the Library (or any work based on the Library),
|
|
||||||
the recipient automatically receives a license from the original licensor
|
|
||||||
to copy, distribute, link with or modify the Library subject to these terms
|
|
||||||
and conditions. You may not impose any further restrictions on the recipients'
|
|
||||||
exercise of the rights granted herein. You are not responsible for enforcing
|
|
||||||
compliance by third parties to this License.
|
|
||||||
|
|
||||||
11. If, as a consequence of a court judgment or allegation of patent infringement
|
|
||||||
or for any other reason (not limited to patent issues), conditions are imposed
|
|
||||||
on you (whether by court order, agreement or otherwise) that contradict the
|
|
||||||
conditions of this License, they do not excuse you from the conditions of
|
|
||||||
this License. If you cannot distribute so as to satisfy simultaneously your
|
|
||||||
obligations under this License and any other pertinent obligations, then as
|
|
||||||
a consequence you may not distribute the Library at all. For example, if a
|
|
||||||
patent license would not permit royalty-free redistribution of the Library
|
|
||||||
by all those who receive copies directly or indirectly through you, then the
|
|
||||||
only way you could satisfy both it and this License would be to refrain entirely
|
|
||||||
from distribution of the Library.
|
|
||||||
|
|
||||||
If any portion of this section is held invalid or unenforceable under any
|
|
||||||
particular circumstance, the balance of the section is intended to apply,
|
|
||||||
and the section as a whole is intended to apply in other circumstances.
|
|
||||||
|
|
||||||
It is not the purpose of this section to induce you to infringe any patents
|
|
||||||
or other property right claims or to contest validity of any such claims;
|
|
||||||
this section has the sole purpose of protecting the integrity of the free
|
|
||||||
software distribution system which is implemented by public license practices.
|
|
||||||
Many people have made generous contributions to the wide range of software
|
|
||||||
distributed through that system in reliance on consistent application of that
|
|
||||||
system; it is up to the author/donor to decide if he or she is willing to
|
|
||||||
distribute software through any other system and a licensee cannot impose
|
|
||||||
that choice.
|
|
||||||
|
|
||||||
This section is intended to make thoroughly clear what is believed to be a
|
|
||||||
consequence of the rest of this License.
|
|
||||||
|
|
||||||
12. If the distribution and/or use of the Library is restricted in certain
|
|
||||||
countries either by patents or by copyrighted interfaces, the original copyright
|
|
||||||
holder who places the Library under this License may add an explicit geographical
|
|
||||||
distribution limitation excluding those countries, so that distribution is
|
|
||||||
permitted only in or among countries not thus excluded. In such case, this
|
|
||||||
License incorporates the limitation as if written in the body of this License.
|
|
||||||
|
|
||||||
13. The Free Software Foundation may publish revised and/or new versions of
|
|
||||||
the Library General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to address
|
|
||||||
new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the Library specifies
|
|
||||||
a version number of this License which applies to it and "any later version",
|
|
||||||
you have the option of following the terms and conditions either of that version
|
|
||||||
or of any later version published by the Free Software Foundation. If the
|
|
||||||
Library does not specify a license version number, you may choose any version
|
|
||||||
ever published by the Free Software Foundation.
|
|
||||||
|
|
||||||
14. If you wish to incorporate parts of the Library into other free programs
|
|
||||||
whose distribution conditions are incompatible with these, write to the author
|
|
||||||
to ask for permission. For software which is copyrighted by the Free Software
|
|
||||||
Foundation, write to the Free Software Foundation; we sometimes make exceptions
|
|
||||||
for this. Our decision will be guided by the two goals of preserving the free
|
|
||||||
status of all derivatives of our free software and of promoting the sharing
|
|
||||||
and reuse of software generally.
|
|
||||||
|
|
||||||
NO WARRANTY
|
|
||||||
|
|
||||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
|
|
||||||
THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
|
|
||||||
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY
|
|
||||||
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
|
|
||||||
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
||||||
FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
|
|
||||||
OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
|
||||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
|
|
||||||
THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
|
||||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
|
|
||||||
OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
|
|
||||||
OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
|
|
||||||
OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
|
|
||||||
HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Libraries
|
|
||||||
|
|
||||||
If you develop a new library, and you want it to be of the greatest possible
|
|
||||||
use to the public, we recommend making it free software that everyone can
|
|
||||||
redistribute and change. You can do so by permitting redistribution under
|
|
||||||
these terms (or, alternatively, under the terms of the ordinary General Public
|
|
||||||
License).
|
|
||||||
|
|
||||||
To apply these terms, attach the following notices to the library. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively convey
|
|
||||||
the exclusion of warranty; and each file should have at least the "copyright"
|
|
||||||
line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
one line to give the library's name and an idea of what it does.
|
|
||||||
|
|
||||||
Copyright (C) year name of author
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or modify it under
|
|
||||||
the terms of the GNU Library General Public License as published by the Free
|
|
||||||
Software Foundation; either version 2 of the License, or (at your option)
|
|
||||||
any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more
|
|
||||||
details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Library General Public License
|
|
||||||
along with this library; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or your school,
|
|
||||||
if any, to sign a "copyright disclaimer" for the library, if necessary. Here
|
|
||||||
is a sample; alter the names:
|
|
||||||
|
|
||||||
Yoyodyne, Inc., hereby disclaims all copyright interest in
|
|
||||||
|
|
||||||
the library `Frob' (a library for tweaking knobs) written
|
|
||||||
|
|
||||||
by James Random Hacker.
|
|
||||||
|
|
||||||
signature of Ty Coon, 1 April 1990
|
|
||||||
|
|
||||||
Ty Coon, President of Vice
|
|
||||||
|
|
||||||
That's all there is to it!
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU General Public License as
|
|
||||||
published by the Free Software Foundation; either version 3 of
|
|
||||||
the license or (at your option) at any later version that is
|
|
||||||
accepted by the membership of KDE e.V. (or its successor
|
|
||||||
approved by the membership of KDE e.V.), which shall act as a
|
|
||||||
proxy as defined in Section 14 of version 3 of the license.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
MIT License Copyright (c) <year> <copyright holders>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is furnished
|
|
||||||
to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice (including the next
|
|
||||||
paragraph) shall be included in all copies or substantial portions of the
|
|
||||||
Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
|
||||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
|
||||||
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
@@ -1,4 +1,2 @@
|
|||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
# SPDX-FileCopyrightText: None
|
|
||||||
# SPDX-License-Identifier: CC0-1.0
|
|
||||||
$XGETTEXT `find . \( -name \*.cpp -o -name \*.h -o -name \*.qml \)` -o $podir/neochat.pot
|
$XGETTEXT `find . \( -name \*.cpp -o -name \*.h -o -name \*.qml \)` -o $podir/neochat.pot
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carlschwan@kde.org>
|
|
||||||
SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
|
|
||||||
SPDX-License-Identifier: CC0-1.0
|
|
||||||
-->
|
|
||||||
# NeoChat
|
# NeoChat
|
||||||
|
|
||||||
NeoChat is a client for Matrix, the decentralized communication protocol for instant
|
NeoChat is a client for Matrix, the decentralized communication protocol for instant
|
||||||
@@ -27,7 +22,7 @@ flatpak install kdeapps org.kde.neochat
|
|||||||
```
|
```
|
||||||
|
|
||||||
A nightly build is also available for Android in the [KDE nightly F-Droid repo](https://community.kde.org/Android/FDroid)
|
A nightly build is also available for Android in the [KDE nightly F-Droid repo](https://community.kde.org/Android/FDroid)
|
||||||
and can also directly be downloaded from the [binary factory](https://binary-factory.kde.org/view/Android/job/NeoChat_Nightly_android-arm64/).
|
and can also directly be downloaded from the [binary factory](https://binary-factory.kde.org/view/Android/job/Neochat_android/).
|
||||||
|
|
||||||
Nightly builds for [Windows](https://binary-factory.kde.org/job/NeoChat_Nightly_win64/), [MacOS](https://binary-factory.kde.org/job/NeoChat_Nightly_macos/) and [AppImages](https://binary-factory.kde.org/job/NeoChat_Nightly_appimage/) can also be downloaded from the [binary factory](https://binary-factory.kde.org/search/?q=neochat).
|
Nightly builds for [Windows](https://binary-factory.kde.org/job/NeoChat_Nightly_win64/), [MacOS](https://binary-factory.kde.org/job/NeoChat_Nightly_macos/) and [AppImages](https://binary-factory.kde.org/job/NeoChat_Nightly_appimage/) can also be downloaded from the [binary factory](https://binary-factory.kde.org/search/?q=neochat).
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
-->
|
-->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.kde.neochat"
|
package="org.kde.neochat"
|
||||||
android:versionName="${versionName}"
|
android:versionName="0.0.1"
|
||||||
android:versionCode="${versionCode}"
|
android:versionCode="1604412458"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat">
|
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat">
|
||||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
SPDX-FileCopyrightText: 2018-2020 Volker Krause <vkrause@kde.org>
|
|
||||||
SPDX-FileCopyrightText: 2019 Nicolas Fella <nicolas.fella@gmx.de>
|
|
||||||
SPDX-FileCopyrightText: 2020 Gabriel Souza Franco <gabrielfrancosouza@gmail.com>
|
|
||||||
SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:3.6.4'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply from: '../version.gradle'
|
|
||||||
def timestamp = (int)(new Date().getTime()/1000)
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
/*******************************************************
|
|
||||||
* The following variables:
|
|
||||||
* - androidBuildToolsVersion,
|
|
||||||
* - androidCompileSdkVersion
|
|
||||||
* - qt5AndroidDir - holds the path to qt android files
|
|
||||||
* needed to build any Qt application
|
|
||||||
* on Android.
|
|
||||||
*
|
|
||||||
* are defined in gradle.properties file. This file is
|
|
||||||
* updated by QtCreator and androiddeployqt tools.
|
|
||||||
* Changing them manually might break the compilation!
|
|
||||||
*******************************************************/
|
|
||||||
|
|
||||||
compileSdkVersion androidCompileSdkVersion.toInteger()
|
|
||||||
|
|
||||||
buildToolsVersion androidBuildToolsVersion
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
main {
|
|
||||||
manifest.srcFile 'AndroidManifest.xml'
|
|
||||||
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
|
|
||||||
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
|
|
||||||
res.srcDirs = [qt5AndroidDir + '/res', 'res']
|
|
||||||
resources.srcDirs = ['src']
|
|
||||||
renderscript.srcDirs = ['src']
|
|
||||||
assets.srcDirs = ['assets']
|
|
||||||
jniLibs.srcDirs = ['libs']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
lintOptions {
|
|
||||||
abortOnError false
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdkVersion qtMinSdkVersion
|
|
||||||
targetSdkVersion qtTargetSdkVersion
|
|
||||||
manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp]
|
|
||||||
}
|
|
||||||
|
|
||||||
packagingOptions {
|
|
||||||
exclude 'lib/*/*RemoteObjects*'
|
|
||||||
exclude 'lib/*/*StateMachine*'
|
|
||||||
exclude 'lib/*/*_imageformats_qico_*'
|
|
||||||
exclude 'lib/*/*_imageformats_qicns_*'
|
|
||||||
exclude 'lib/*/*_imageformats_qtga_*'
|
|
||||||
exclude 'lib/*/*_imageformats_qtiff_*'
|
|
||||||
exclude 'lib/*/*_qmltooling_*'
|
|
||||||
}
|
|
||||||
|
|
||||||
aaptOptions {
|
|
||||||
// different syntax than above
|
|
||||||
// see https://android.googlesource.com/platform/frameworks/base/+/refs/heads/pie-release/tools/aapt2/util/Files.h#90
|
|
||||||
ignoreAssetsPattern '!<dir>ECM:!<dir>aclocal:!<dir>doc:!<dir>gtk-doc:!<dir>iso-codes:!<dir>man:!<dir>mime:!<dir>pkgconfig:!<dir>qlogging-categories5:!<file>iso_15924.mo:!<file>iso_3166-2.mo:!<file>iso_3166-3.mo:!<file>iso_4217.mo:!<file>iso_639-2.mo:!<file>iso_639-3.mo:!<file>iso_639-5.mo:!<file>kcodecs5_qt.qm:!<file>kde5_xml_mimetypes.qm'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
ext {
|
|
||||||
projectVersionFull = "@NEOCHAT_VERSION@"
|
|
||||||
}
|
|
||||||
@@ -5,9 +5,7 @@
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Templates 2.15 as T
|
|
||||||
import Qt.labs.platform 1.1 as Platform
|
import Qt.labs.platform 1.1 as Platform
|
||||||
import QtQuick.Window 2.15
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
@@ -68,8 +66,7 @@ ToolBar {
|
|||||||
id: fontMetrics
|
id: fontMetrics
|
||||||
font: inputField.font
|
font: inputField.font
|
||||||
}
|
}
|
||||||
|
TextArea {
|
||||||
T.TextArea {
|
|
||||||
id: inputField
|
id: inputField
|
||||||
focus: true
|
focus: true
|
||||||
/* Some QQC2 styles will have their own predefined backgrounds for TextAreas.
|
/* Some QQC2 styles will have their own predefined backgrounds for TextAreas.
|
||||||
@@ -84,7 +81,6 @@ ToolBar {
|
|||||||
cursorShape: Qt.IBeamCursor
|
cursorShape: Qt.IBeamCursor
|
||||||
z: 1
|
z: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
leftPadding: mirrored ? 0 : Kirigami.Units.largeSpacing
|
leftPadding: mirrored ? 0 : Kirigami.Units.largeSpacing
|
||||||
rightPadding: !mirrored ? 0 : Kirigami.Units.largeSpacing
|
rightPadding: !mirrored ? 0 : Kirigami.Units.largeSpacing
|
||||||
topPadding: 0
|
topPadding: 0
|
||||||
@@ -101,82 +97,6 @@ ToolBar {
|
|||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
readOnly: currentRoom.usesEncryption
|
readOnly: currentRoom.usesEncryption
|
||||||
|
|
||||||
palette: Kirigami.Theme.palette
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
||||||
Kirigami.Theme.inherit: false
|
|
||||||
|
|
||||||
implicitWidth: Math.max(contentWidth + leftPadding + rightPadding,
|
|
||||||
implicitBackgroundWidth + leftInset + rightInset,
|
|
||||||
placeholder.implicitWidth + leftPadding + rightPadding)
|
|
||||||
implicitHeight: Math.max(contentHeight + topPadding + bottomPadding,
|
|
||||||
implicitBackgroundHeight + topInset + bottomInset,
|
|
||||||
placeholder.implicitHeight + topPadding + bottomPadding)
|
|
||||||
|
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
selectionColor: Kirigami.Theme.highlightColor
|
|
||||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
|
||||||
hoverEnabled: !Kirigami.Settings.tabletMode
|
|
||||||
|
|
||||||
selectByMouse: !Kirigami.Settings.tabletMode
|
|
||||||
|
|
||||||
cursorDelegate: Loader {
|
|
||||||
visible: inputField.activeFocus && !inputField.readOnly && inputField.selectionStart === inputField.selectionEnd
|
|
||||||
active: visible
|
|
||||||
sourceComponent: CursorDelegate { target: inputField }
|
|
||||||
}
|
|
||||||
|
|
||||||
CursorHandle {
|
|
||||||
id: selectionStartHandle
|
|
||||||
target: inputField
|
|
||||||
}
|
|
||||||
|
|
||||||
CursorHandle {
|
|
||||||
id: selectionEndHandle
|
|
||||||
target: inputField
|
|
||||||
isSelectionEnd: true
|
|
||||||
}
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
|
|
||||||
// unfortunately, taphandler's pressed event only triggers when the press is lifted
|
|
||||||
// we need to use the longpress signal since it triggers when the button is first pressed
|
|
||||||
longPressThreshold: 0
|
|
||||||
onLongPressed: TextFieldContextMenu.targetClick(point, inputField, spellcheckhighlighter, inputField.positionAt(point.position.x, point.position.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
onPressAndHold: {
|
|
||||||
if (!Kirigami.Settings.tabletMode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
forceActiveFocus();
|
|
||||||
cursorPosition = positionAt(event.x, event.y);
|
|
||||||
selectWord();
|
|
||||||
}
|
|
||||||
|
|
||||||
onFocusChanged: {
|
|
||||||
if (focus) {
|
|
||||||
MobileTextActionsToolBar.controlRoot = inputField;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: placeholder
|
|
||||||
x: inputField.leftPadding
|
|
||||||
y: inputField.topPadding
|
|
||||||
width: inputField.width - (inputField.leftPadding + inputField.rightPadding)
|
|
||||||
height: inputField.height - (inputField.topPadding + inputField.bottomPadding)
|
|
||||||
|
|
||||||
text: inputField.placeholderText
|
|
||||||
font: inputField.font
|
|
||||||
color: Kirigami.Theme.disabledTextColor
|
|
||||||
horizontalAlignment: inputField.horizontalAlignment
|
|
||||||
verticalAlignment: inputField.verticalAlignment
|
|
||||||
visible: !inputField.length && !inputField.preeditText && (!inputField.activeFocus || inputField.horizontalAlignment !== Qt.AlignHCenter)
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ChatDocumentHandler {
|
ChatDocumentHandler {
|
||||||
id: documentHandler
|
id: documentHandler
|
||||||
@@ -187,18 +107,6 @@ ToolBar {
|
|||||||
room: currentRoom ?? null
|
room: currentRoom ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
SpellcheckHighlighter {
|
|
||||||
id: spellcheckhighlighter
|
|
||||||
document: inputField.textDocument
|
|
||||||
cursorPosition: inputField.cursorPosition
|
|
||||||
selectionStart: inputField.selectionStart
|
|
||||||
selectionEnd: inputField.selectionEnd
|
|
||||||
onChangeCursorPosition: {
|
|
||||||
inputField.cursorPosition = start;
|
|
||||||
inputField.moveCursorSelection(end, TextEdit.SelectCharacters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: timeoutTimer
|
id: timeoutTimer
|
||||||
repeat: false
|
repeat: false
|
||||||
@@ -239,9 +147,6 @@ ToolBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: {
|
Keys.onPressed: {
|
||||||
// trigger if context menu button is pressed
|
|
||||||
TextFieldContextMenu.targetKeyPressed(event, inputField)
|
|
||||||
|
|
||||||
if (event.key === Qt.Key_PageDown) {
|
if (event.key === Qt.Key_PageDown) {
|
||||||
switchRoomDown();
|
switchRoomDown();
|
||||||
} else if (event.key === Qt.Key_PageUp) {
|
} else if (event.key === Qt.Key_PageUp) {
|
||||||
@@ -278,25 +183,6 @@ ToolBar {
|
|||||||
chatBar.complete();
|
chatBar.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
// yes, decrement goes up and increment goes down visually.
|
|
||||||
Keys.onUpPressed: (event) => {
|
|
||||||
if (chatBar.isCompleting) {
|
|
||||||
event.accepted = true
|
|
||||||
completionMenu.listView.decrementCurrentIndex()
|
|
||||||
autoAppeared = true;
|
|
||||||
}
|
|
||||||
event.accepted = false
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onDownPressed: (event) => {
|
|
||||||
if (chatBar.isCompleting) {
|
|
||||||
event.accepted = true
|
|
||||||
completionMenu.listView.incrementCurrentIndex()
|
|
||||||
autoAppeared = true;
|
|
||||||
}
|
|
||||||
event.accepted = false
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onTabPressed: {
|
Keys.onTabPressed: {
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
switchRoomDown();
|
switchRoomDown();
|
||||||
@@ -325,27 +211,17 @@ ToolBar {
|
|||||||
chatBar.complete();
|
chatBar.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressed: MobileTextActionsToolBar.shouldBeVisible = true;
|
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
MobileTextActionsToolBar.shouldBeVisible = false;
|
|
||||||
timeoutTimer.restart()
|
timeoutTimer.restart()
|
||||||
repeatTimer.start()
|
repeatTimer.start()
|
||||||
currentRoom.cachedInput = text
|
currentRoom.cachedInput = text
|
||||||
autoAppeared = false;
|
autoAppeared = false;
|
||||||
|
|
||||||
const completionInfo = documentHandler.getAutocompletionInfo(isCompleting);
|
const completionInfo = documentHandler.getAutocompletionInfo();
|
||||||
|
|
||||||
if (completionInfo.type === ChatDocumentHandler.Ignore) {
|
if (completionInfo.type === ChatDocumentHandler.Ignore) {
|
||||||
if (completionInfo.keyword) {
|
|
||||||
// custom emojis
|
|
||||||
const idx = completionMenu.currentIndex;
|
|
||||||
completionMenu.model = Array.from(chatBar.customEmojiModel.filterModel(completionInfo.keyword)).concat(EmojiModel.filterModel(completionInfo.keyword))
|
|
||||||
completionMenu.currentIndex = idx;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (completionInfo.type === ChatDocumentHandler.None) {
|
if (completionInfo.type === ChatDocumentHandler.None) {
|
||||||
isCompleting = false;
|
isCompleting = false;
|
||||||
return;
|
return;
|
||||||
@@ -357,19 +233,16 @@ ToolBar {
|
|||||||
} else if (completionInfo.type === ChatDocumentHandler.Command) {
|
} else if (completionInfo.type === ChatDocumentHandler.Command) {
|
||||||
completionMenu.model = CommandModel.filterModel(completionInfo.keyword);
|
completionMenu.model = CommandModel.filterModel(completionInfo.keyword);
|
||||||
} else {
|
} else {
|
||||||
completionMenu.model = Array.from(chatBar.customEmojiModel.filterModel(completionInfo.keyword)).concat(EmojiModel.filterModel(completionInfo.keyword))
|
completionMenu.model = EmojiModel.filterModel(completionInfo.keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (completionMenu.model.length === 0) {
|
if (completionMenu.model.length === 0) {
|
||||||
isCompleting = false;
|
isCompleting = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
isCompleting = true
|
||||||
if (!isCompleting) {
|
autoAppeared = true;
|
||||||
isCompleting = true
|
completionMenu.endPosition = cursorPosition
|
||||||
autoAppeared = true;
|
|
||||||
completionMenu.endPosition = cursorPosition
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,10 +341,6 @@ ToolBar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property CustomEmojiModel customEmojiModel: CustomEmojiModel {
|
|
||||||
connection: Controller.activeConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
function pasteImage() {
|
function pasteImage() {
|
||||||
let localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png";
|
let localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png";
|
||||||
if (!Clipboard.saveImage(localPath)) {
|
if (!Clipboard.saveImage(localPath)) {
|
||||||
@@ -486,7 +355,7 @@ ToolBar {
|
|||||||
if (ChatBoxHelper.hasAttachment) {
|
if (ChatBoxHelper.hasAttachment) {
|
||||||
// send attachment but don't reset the text
|
// send attachment but don't reset the text
|
||||||
actionsHandler.postMessage("", ChatBoxHelper.attachmentPath,
|
actionsHandler.postMessage("", ChatBoxHelper.attachmentPath,
|
||||||
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, {}, this.customEmojiModel);
|
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, {});
|
||||||
currentRoom.markAllMessagesAsRead();
|
currentRoom.markAllMessagesAsRead();
|
||||||
messageSent();
|
messageSent();
|
||||||
return;
|
return;
|
||||||
@@ -499,7 +368,7 @@ ToolBar {
|
|||||||
} else {
|
} else {
|
||||||
// send normal message
|
// send normal message
|
||||||
actionsHandler.postMessage(inputField.text.trim(), ChatBoxHelper.attachmentPath,
|
actionsHandler.postMessage(inputField.text.trim(), ChatBoxHelper.attachmentPath,
|
||||||
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, userAutocompleted, this.customEmojiModel);
|
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, userAutocompleted);
|
||||||
}
|
}
|
||||||
currentRoom.markAllMessagesAsRead();
|
currentRoom.markAllMessagesAsRead();
|
||||||
inputField.clear();
|
inputField.clear();
|
||||||
|
|||||||
@@ -61,10 +61,6 @@ Item {
|
|||||||
QQC2.Pane {
|
QQC2.Pane {
|
||||||
id: connectionPane
|
id: connectionPane
|
||||||
padding: fontMetrics.lineSpacing * 0.25
|
padding: fontMetrics.lineSpacing * 0.25
|
||||||
FontMetrics {
|
|
||||||
id: fontMetrics
|
|
||||||
font: networkLabel.font
|
|
||||||
}
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
@@ -73,7 +69,6 @@ Item {
|
|||||||
visible: !Controller.isOnline
|
visible: !Controller.isOnline
|
||||||
width: parent.width
|
width: parent.width
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: networkLabel
|
|
||||||
text: i18n("NeoChat is offline. Please check your network connection.")
|
text: i18n("NeoChat is offline. Please check your network connection.")
|
||||||
}
|
}
|
||||||
anchors.bottom: emojiPickerLoaderSeparator.top
|
anchors.bottom: emojiPickerLoaderSeparator.top
|
||||||
|
|||||||
@@ -10,14 +10,12 @@ import Qt.labs.qmlmodels 1.0
|
|||||||
import org.kde.kirigami 2.15 as Kirigami
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Component 1.0
|
|
||||||
|
|
||||||
Popup {
|
Popup {
|
||||||
id: control
|
id: control
|
||||||
|
|
||||||
// Expose internal ListView properties.
|
// Expose internal ListView properties.
|
||||||
property alias model: completionListView.model
|
property alias model: completionListView.model
|
||||||
property alias listView: completionListView
|
|
||||||
property alias currentIndex: completionListView.currentIndex
|
property alias currentIndex: completionListView.currentIndex
|
||||||
property alias currentItem: completionListView.currentItem
|
property alias currentItem: completionListView.currentItem
|
||||||
property alias count: completionListView.count
|
property alias count: completionListView.count
|
||||||
@@ -44,7 +42,7 @@ Popup {
|
|||||||
completionListView.currentIndex = 0;
|
completionListView.currentIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitHeight: Math.min(completionListView.contentHeight, Kirigami.Units.gridUnit * 10)
|
implicitHeight: Math.min(completionListView.contentHeight, Kirigami.Units.gridUnit * 5)
|
||||||
|
|
||||||
contentItem: ScrollView {
|
contentItem: ScrollView {
|
||||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
@@ -96,40 +94,23 @@ Popup {
|
|||||||
Kirigami.BasicListItem {
|
Kirigami.BasicListItem {
|
||||||
id: emojiItem
|
id: emojiItem
|
||||||
width: ListView.view.width ?? implicitWidth
|
width: ListView.view.width ?? implicitWidth
|
||||||
property string displayName: modelData.isCustom ? modelData.shortname : modelData.unicode
|
property string displayName: modelData.unicode
|
||||||
text: modelData.shortname
|
text: modelData.unicode + " " + modelData.shortname
|
||||||
height: Kirigami.Units.gridUnit * 2
|
|
||||||
|
|
||||||
leading: Image {
|
leading: Label {
|
||||||
source: modelData.isCustom ? modelData.unicode : ""
|
id: unicodeLabel
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||||
width: height
|
Layout.preferredWidth: textMetrics.tightBoundingRect.width
|
||||||
sourceSize.width: width
|
font.pointSize: Kirigami.Units.gridUnit * 0.75
|
||||||
sourceSize.height: height
|
text: modelData.unicode
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
Rectangle {
|
verticalAlignment: Text.AlignVCenter
|
||||||
anchors.fill: parent
|
}
|
||||||
visible: parent.status === Image.Loading
|
TextMetrics {
|
||||||
radius: height/2
|
id: textMetrics
|
||||||
gradient: ShimmerGradient { }
|
text: modelData.unicode
|
||||||
}
|
font: unicodeLabel.font
|
||||||
|
|
||||||
Label {
|
|
||||||
id: unicodeLabel
|
|
||||||
|
|
||||||
visible: !modelData.isCustom
|
|
||||||
|
|
||||||
font.family: 'emoji'
|
|
||||||
font.pixelSize: height - 2
|
|
||||||
|
|
||||||
text: modelData.unicode
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: completeTriggered();
|
onClicked: completeTriggered();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
/* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
|
|
||||||
* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
|
||||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Window 2.15
|
|
||||||
import QtQuick.Templates 2.15
|
|
||||||
import org.kde.kirigami 2.14 as Kirigami
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
property alias target: root.parent
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: cursorLine
|
|
||||||
property real previousX: 0
|
|
||||||
property real previousY: 0
|
|
||||||
parent: target
|
|
||||||
implicitWidth: target.cursorRectangle.width
|
|
||||||
implicitHeight: target.cursorRectangle.height
|
|
||||||
x: Math.floor(target.cursorRectangle.x)
|
|
||||||
y: Math.floor(target.cursorRectangle.y)
|
|
||||||
|
|
||||||
color: target.color
|
|
||||||
SequentialAnimation {
|
|
||||||
id: blinkAnimation
|
|
||||||
running: root.visible && Qt.styleHints.cursorFlashTime != 0 && target.selectionStart === target.selectionEnd
|
|
||||||
PropertyAction {
|
|
||||||
target: cursorLine
|
|
||||||
property: "opacity"
|
|
||||||
value: 1
|
|
||||||
}
|
|
||||||
PauseAnimation {
|
|
||||||
duration: Qt.styleHints.cursorFlashTime/2
|
|
||||||
}
|
|
||||||
SequentialAnimation {
|
|
||||||
loops: Animation.Infinite
|
|
||||||
OpacityAnimator {
|
|
||||||
target: cursorLine
|
|
||||||
from: 1
|
|
||||||
to: 0
|
|
||||||
duration: Qt.styleHints.cursorFlashTime/2
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
OpacityAnimator {
|
|
||||||
target: cursorLine
|
|
||||||
from: 0
|
|
||||||
to: 1
|
|
||||||
duration: Qt.styleHints.cursorFlashTime/2
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root.target
|
|
||||||
function onCursorPositionChanged() {
|
|
||||||
blinkAnimation.restart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
/* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
|
|
||||||
* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
|
||||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Window 2.15
|
|
||||||
import QtQuick.Templates 2.15
|
|
||||||
import org.kde.kirigami 2.14 as Kirigami
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: root
|
|
||||||
property Item target
|
|
||||||
property bool isSelectionEnd: false
|
|
||||||
visible: Kirigami.Settings.tabletMode && target.activeFocus && (isSelectionEnd ? target.selectionStart !== target.selectionEnd : true)
|
|
||||||
active: visible
|
|
||||||
sourceComponent: Kirigami.ShadowedRectangle {
|
|
||||||
id: handle
|
|
||||||
property real selectionStartX: Math.floor(Qt.inputMethod.anchorRectangle.x + (Qt.inputMethod.cursorRectangle.width - width)/2)
|
|
||||||
property real selectionStartY: Math.floor(Qt.inputMethod.anchorRectangle.y + Qt.inputMethod.cursorRectangle.height + pointyBitVerticalOffset)
|
|
||||||
property real selectionEndX: Math.floor(Qt.inputMethod.cursorRectangle.x + (Qt.inputMethod.cursorRectangle.width - width)/2)
|
|
||||||
property real selectionEndY: Math.floor(Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height + pointyBitVerticalOffset)
|
|
||||||
property real pointyBitVerticalOffset: Math.abs(pointyBit.y*2)
|
|
||||||
parent: Overlay.overlay
|
|
||||||
x: isSelectionEnd ? selectionEndX : selectionStartX
|
|
||||||
y: isSelectionEnd ? selectionEndY : selectionStartY
|
|
||||||
|
|
||||||
// HACK: make it appear above most popups that show up in the
|
|
||||||
// overlay in case any of them use TextField or TextArea
|
|
||||||
z: 999
|
|
||||||
|
|
||||||
//opacity: target.activeFocus ? 1 : 0
|
|
||||||
implicitHeight: {
|
|
||||||
let h = Kirigami.Units.gridUnit
|
|
||||||
return h - (h % 2 == 0 ? 1 : 0)
|
|
||||||
}
|
|
||||||
implicitWidth: implicitHeight
|
|
||||||
radius: width/2
|
|
||||||
|
|
||||||
color: target.selectionColor
|
|
||||||
|
|
||||||
shadow {
|
|
||||||
color: Qt.rgba(0,0,0,0.2)
|
|
||||||
size: 3
|
|
||||||
yOffset: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: pointyBit
|
|
||||||
x: (parent.width - width)/2
|
|
||||||
y: -height/4 + 0.2 // magic number to get it to line up with the edge of the circle
|
|
||||||
implicitHeight: parent.implicitHeight/2
|
|
||||||
implicitWidth: implicitHeight
|
|
||||||
antialiasing: true
|
|
||||||
rotation: 45
|
|
||||||
color: parent.color
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.ShadowedRectangle {
|
|
||||||
id: inner
|
|
||||||
visible: target.selectionStart !== target.selectionEnd && (handle.y < selectionStartY || handle.y < selectionEndY)
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Kirigami.Units.smallBorder
|
|
||||||
color: target.selectedTextColor
|
|
||||||
radius: height/2
|
|
||||||
Rectangle {
|
|
||||||
id: innerPointyBit
|
|
||||||
x: (parent.width - width)/2
|
|
||||||
y: -height/4 + 0.8 // magic number to get it to line up with the edge of the circle
|
|
||||||
implicitHeight: pointyBit.implicitHeight
|
|
||||||
implicitWidth: implicitHeight
|
|
||||||
antialiasing: true
|
|
||||||
rotation: 45
|
|
||||||
color: parent.color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
enabled: handle.visible
|
|
||||||
anchors.fill: parent
|
|
||||||
// preventStealing: true
|
|
||||||
onPositionChanged: {
|
|
||||||
let pos = mapToItem(root.target, mouse.x, mouse.y);
|
|
||||||
pos = root.target.positionAt(pos.x, pos.y - handle.height - handle.pointyBitVerticalOffset);
|
|
||||||
|
|
||||||
if (target.selectionStart !== target.selectionEnd) {
|
|
||||||
if (!isSelectionEnd) {
|
|
||||||
root.target.select(Math.min(pos, root.target.selectionEnd - 1), root.target.selectionEnd);
|
|
||||||
} else {
|
|
||||||
root.target.select(root.target.selectionStart, Math.max(pos, root.target.selectionStart + 1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
root.target.cursorPosition = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma Singleton
|
|
||||||
|
|
||||||
import QtQuick 2.1
|
|
||||||
import QtQuick.Layouts 1.2
|
|
||||||
import QtQuick.Window 2.2
|
|
||||||
import QtQuick.Controls 2.15
|
|
||||||
import org.kde.kirigami 2.5 as Kirigami
|
|
||||||
|
|
||||||
Popup {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property Item controlRoot
|
|
||||||
parent: controlRoot ? controlRoot.Window.contentItem : undefined
|
|
||||||
modal: false
|
|
||||||
focus: false
|
|
||||||
closePolicy: Popup.NoAutoClose
|
|
||||||
property bool shouldBeVisible: false
|
|
||||||
|
|
||||||
x: {
|
|
||||||
if (!controlRoot || !controlRoot.Window.contentItem) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return Math.min(Math.max(0, controlRoot.mapToItem(root.parent, controlRoot.positionToRectangle(controlRoot.selectionStart).x, 0).x - root.width/2), controlRoot.Window.contentItem.width - root.width);
|
|
||||||
}
|
|
||||||
|
|
||||||
y: {
|
|
||||||
if (!controlRoot || !controlRoot.Window.contentItem) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
var desiredY = controlRoot.mapToItem(root.parent, 0, controlRoot.positionToRectangle(controlRoot.selectionStart).y).y - root.height;
|
|
||||||
|
|
||||||
if (desiredY >= 0) {
|
|
||||||
return Math.min(desiredY, controlRoot.Window.contentItem.height - root.height);
|
|
||||||
} else {
|
|
||||||
return Math.min(Math.max(0, controlRoot.mapToItem(root.parent, 0, controlRoot.positionToRectangle(controlRoot.selectionEnd).y + Math.round(Kirigami.Units.gridUnit*1.5)).y), controlRoot.Window.contentItem.height - root.height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
visible: controlRoot ? shouldBeVisible && Qt.platform.os !== "android" && Kirigami.Settings.tabletMode && (controlRoot.selectedText.length > 0 || controlRoot.canPaste) : false
|
|
||||||
|
|
||||||
width: contentItem.implicitWidth + leftPadding + rightPadding
|
|
||||||
|
|
||||||
contentItem: RowLayout {
|
|
||||||
ToolButton {
|
|
||||||
focusPolicy: Qt.NoFocus
|
|
||||||
icon.name: "edit-cut"
|
|
||||||
visible: controlRoot && controlRoot.selectedText.length > 0 && (!controlRoot.hasOwnProperty("echoMode") || controlRoot.echoMode === TextInput.Normal)
|
|
||||||
onClicked: {
|
|
||||||
controlRoot.cut();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolButton {
|
|
||||||
focusPolicy: Qt.NoFocus
|
|
||||||
icon.name: "edit-copy"
|
|
||||||
visible: controlRoot && controlRoot.selectedText.length > 0 && (!controlRoot.hasOwnProperty("echoMode") || controlRoot.echoMode === TextInput.Normal)
|
|
||||||
onClicked: {
|
|
||||||
controlRoot.copy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolButton {
|
|
||||||
focusPolicy: Qt.NoFocus
|
|
||||||
icon.name: "edit-paste"
|
|
||||||
visible: controlRoot && controlRoot.canPaste
|
|
||||||
onClicked: {
|
|
||||||
controlRoot.paste();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ Loader {
|
|||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
text: {
|
text: {
|
||||||
let heading = "<b>%1</b>"
|
let heading = "<b>%1</b>"
|
||||||
let userName = user ? "<font color=\""+ user.color +"\">" + currentRoom.htmlSafeMemberName(user.id) + "</font>" : ""
|
let userName = user ? "<font color=\""+ user.color +"\">" + user.displayName + "</font>" : ""
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
heading = heading.arg(i18n("Editing message:")) + "<br/>"
|
heading = heading.arg(i18n("Editing message:")) + "<br/>"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,259 +0,0 @@
|
|||||||
/*
|
|
||||||
SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
|
|
||||||
SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
pragma Singleton
|
|
||||||
|
|
||||||
import QtQuick 2.6
|
|
||||||
import QtQml 2.2
|
|
||||||
import QtQuick.Controls 2.15
|
|
||||||
import org.kde.kirigami 2.5 as Kirigami
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
id: contextMenu
|
|
||||||
|
|
||||||
property Item target
|
|
||||||
property bool deselectWhenMenuClosed: true
|
|
||||||
property int restoredCursorPosition: 0
|
|
||||||
property int restoredSelectionStart
|
|
||||||
property int restoredSelectionEnd
|
|
||||||
property bool persistentSelectionSetting
|
|
||||||
property var spellcheckhighlighter: null
|
|
||||||
property var suggestions: ([])
|
|
||||||
Component.onCompleted: persistentSelectionSetting = persistentSelectionSetting // break binding
|
|
||||||
|
|
||||||
property var runOnMenuClose
|
|
||||||
|
|
||||||
parent: Overlay.overlay
|
|
||||||
|
|
||||||
function storeCursorAndSelection() {
|
|
||||||
contextMenu.restoredCursorPosition = target.cursorPosition;
|
|
||||||
contextMenu.restoredSelectionStart = target.selectionStart;
|
|
||||||
contextMenu.restoredSelectionEnd = target.selectionEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// target is pressed with mouse
|
|
||||||
function targetClick(handlerPoint, newTarget, spellcheckhighlighter, mousePosition) {
|
|
||||||
if (handlerPoint.pressedButtons === Qt.RightButton) { // only accept just right click
|
|
||||||
if (contextMenu.visible) {
|
|
||||||
deselectWhenMenuClosed = false; // don't deselect text if menu closed by right click on textfield
|
|
||||||
dismiss();
|
|
||||||
} else {
|
|
||||||
contextMenu.target = newTarget;
|
|
||||||
contextMenu.target.persistentSelection = true; // persist selection when menu is opened
|
|
||||||
contextMenu.spellcheckhighlighter = spellcheckhighlighter
|
|
||||||
contextMenu.suggestions = spellcheckhighlighter.suggestions(mousePosition);
|
|
||||||
storeCursorAndSelection();
|
|
||||||
popup(contextMenu.target);
|
|
||||||
// slightly locate context menu away from mouse so no item is selected when menu is opened
|
|
||||||
x += 1
|
|
||||||
y += 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// context menu keyboard key
|
|
||||||
function targetKeyPressed(event, newTarget) {
|
|
||||||
if (event.modifiers === Qt.NoModifier && event.key === Qt.Key_Menu) {
|
|
||||||
contextMenu.target = newTarget;
|
|
||||||
target.persistentSelection = true; // persist selection when menu is opened
|
|
||||||
storeCursorAndSelection();
|
|
||||||
popup(contextMenu.target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property bool targetIsPassword: target !== null && (target.echoMode === TextInput.PasswordEchoOnEdit || target.echoMode === TextInput.Password)
|
|
||||||
|
|
||||||
onAboutToShow: {
|
|
||||||
if (Overlay.overlay) {
|
|
||||||
let tempZ = 0
|
|
||||||
for (let i in Overlay.overlay.visibleChildren) {
|
|
||||||
tempZ = Math.max(tempZ, Overlay.overlay.visibleChildren[i].z)
|
|
||||||
}
|
|
||||||
z = tempZ + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deal with whether or not text should be deselected
|
|
||||||
onClosed: {
|
|
||||||
// restore text field's original persistent selection setting
|
|
||||||
target.persistentSelection = persistentSelectionSetting
|
|
||||||
// deselect text field text if menu is closed not because of a right click on the text field
|
|
||||||
if (deselectWhenMenuClosed) {
|
|
||||||
target.deselect();
|
|
||||||
}
|
|
||||||
deselectWhenMenuClosed = true;
|
|
||||||
|
|
||||||
// restore cursor position
|
|
||||||
target.forceActiveFocus();
|
|
||||||
target.cursorPosition = restoredCursorPosition;
|
|
||||||
target.select(restoredSelectionStart, restoredSelectionEnd);
|
|
||||||
|
|
||||||
// run action
|
|
||||||
runOnMenuClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
onOpened: {
|
|
||||||
runOnMenuClose = function() {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Instantiator {
|
|
||||||
active: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
|
||||||
model: suggestions
|
|
||||||
delegate: MenuItem {
|
|
||||||
text: modelData
|
|
||||||
onClicked: {
|
|
||||||
deselectWhenMenuClosed = false;
|
|
||||||
runOnMenuClose = function() {
|
|
||||||
spellcheckhighlighter.replaceWord(modelData);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onObjectAdded: contextMenu.insertItem(0, object)
|
|
||||||
onObjectRemoved: contextMenu.removeItem(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled && suggestions.length === 0
|
|
||||||
action: Action {
|
|
||||||
text: spellcheckhighlighter ? i18nc("@action:inmenu", "No suggestions for %1", spellcheckhighlighter.wordUnderMouse) : ""
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuSeparator {
|
|
||||||
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
|
||||||
action: Action {
|
|
||||||
text: i18n("Add to dictionary")
|
|
||||||
onTriggered: {
|
|
||||||
deselectWhenMenuClosed = false;
|
|
||||||
runOnMenuClose = function() {
|
|
||||||
spellcheckhighlighter.addWordToDictionary(spellcheckhighlighter.wordUnderMouse)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
|
||||||
action: Action {
|
|
||||||
text: i18n("Ignore")
|
|
||||||
onTriggered: {
|
|
||||||
deselectWhenMenuClosed = false;
|
|
||||||
runOnMenuClose = function() {
|
|
||||||
spellcheckhighlighter.ignoreWord(spellcheckhighlighter.wordUnderMouse)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuSeparator {
|
|
||||||
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
visible: target !== null && !target.readOnly
|
|
||||||
action: Action {
|
|
||||||
icon.name: "edit-undo-symbolic"
|
|
||||||
text: i18nc("@action:inmenu", "Undo")
|
|
||||||
shortcut: StandardKey.Undo
|
|
||||||
}
|
|
||||||
enabled: target !== null && target.canUndo
|
|
||||||
onTriggered: {
|
|
||||||
deselectWhenMenuClosed = false;
|
|
||||||
runOnMenuClose = function() {target.undo()};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
visible: target !== null && !target.readOnly
|
|
||||||
action: Action {
|
|
||||||
icon.name: "edit-redo-symbolic"
|
|
||||||
text: i18nc("@action:inmenu", "Redo")
|
|
||||||
shortcut: StandardKey.Redo
|
|
||||||
}
|
|
||||||
enabled: target !== null && target.canRedo
|
|
||||||
onTriggered: {
|
|
||||||
deselectWhenMenuClosed = false;
|
|
||||||
runOnMenuClose = function() {target.redo()};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuSeparator {
|
|
||||||
visible: target !== null && !target.readOnly
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
visible: target !== null && !target.readOnly && !targetIsPassword
|
|
||||||
action: Action {
|
|
||||||
icon.name: "edit-cut-symbolic"
|
|
||||||
text: i18nc("@action:inmenu", "Cut")
|
|
||||||
shortcut: StandardKey.Cut
|
|
||||||
}
|
|
||||||
enabled: target !== null && target.selectedText
|
|
||||||
onTriggered: {
|
|
||||||
deselectWhenMenuClosed = false;
|
|
||||||
runOnMenuClose = function() {target.cut()}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
action: Action {
|
|
||||||
icon.name: "edit-copy-symbolic"
|
|
||||||
text: i18nc("@action:inmenu", "Copy")
|
|
||||||
shortcut: StandardKey.Copy
|
|
||||||
}
|
|
||||||
enabled: target !== null && target.selectedText
|
|
||||||
visible: !targetIsPassword
|
|
||||||
onTriggered: {
|
|
||||||
deselectWhenMenuClosed = false;
|
|
||||||
runOnMenuClose = function() {target.copy()}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
visible: target !== null && !target.readOnly
|
|
||||||
action: Action {
|
|
||||||
icon.name: "edit-paste-symbolic"
|
|
||||||
text: i18nc("@action:inmenu", "Paste")
|
|
||||||
shortcut: StandardKey.Paste
|
|
||||||
}
|
|
||||||
enabled: target !== null && target.canPaste
|
|
||||||
onTriggered: {
|
|
||||||
deselectWhenMenuClosed = false;
|
|
||||||
runOnMenuClose = function() {target.paste()};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
visible: target !== null && !target.readOnly
|
|
||||||
action: Action {
|
|
||||||
icon.name: "edit-delete-symbolic"
|
|
||||||
text: i18nc("@action:inmenu", "Delete")
|
|
||||||
shortcut: StandardKey.Delete
|
|
||||||
}
|
|
||||||
enabled: target !== null && target.selectedText
|
|
||||||
onTriggered: {
|
|
||||||
deselectWhenMenuClosed = false;
|
|
||||||
runOnMenuClose = function() {target.remove(target.selectionStart, target.selectionEnd)};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuSeparator {
|
|
||||||
visible: !targetIsPassword
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
action: Action {
|
|
||||||
icon.name: "edit-select-all-symbolic"
|
|
||||||
text: i18nc("@action:inmenu", "Select All")
|
|
||||||
shortcut: StandardKey.SelectAll
|
|
||||||
}
|
|
||||||
visible: !targetIsPassword
|
|
||||||
onTriggered: {
|
|
||||||
deselectWhenMenuClosed = false;
|
|
||||||
runOnMenuClose = function() {target.selectAll()};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,3 @@ ReplyPane 1.0 ReplyPane.qml
|
|||||||
AttachmentPane 1.0 AttachmentPane.qml
|
AttachmentPane 1.0 AttachmentPane.qml
|
||||||
CompletionMenu 1.0 CompletionMenu.qml
|
CompletionMenu 1.0 CompletionMenu.qml
|
||||||
EmojiPickerPane 1.0 EmojiPickerPane.qml
|
EmojiPickerPane 1.0 EmojiPickerPane.qml
|
||||||
singleton TextFieldContextMenu 1.0 TextFieldContextMenu.qml
|
|
||||||
CursorDelegate 1.0 CursorDelegate.qml
|
|
||||||
CursorHandle 1.0 CursorHandle.qml
|
|
||||||
singleton MobileTextActionsToolBar 1.0 MobileTextActionsToolBar.qml
|
|
||||||
|
|||||||
@@ -10,16 +10,10 @@ import org.kde.neochat 1.0 as NeoChat
|
|||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: _picker
|
|
||||||
|
|
||||||
property string emojiCategory: "history"
|
property string emojiCategory: "history"
|
||||||
property var textArea
|
property var textArea
|
||||||
readonly property var emojiModel: NeoChat.EmojiModel
|
readonly property var emojiModel: NeoChat.EmojiModel
|
||||||
|
|
||||||
property NeoChat.CustomEmojiModel customModel: NeoChat.CustomEmojiModel {
|
|
||||||
connection: NeoChat.Controller.activeConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
signal chosen(string emoji)
|
signal chosen(string emoji)
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
@@ -35,7 +29,6 @@ ColumnLayout {
|
|||||||
orientation: ListView.Horizontal
|
orientation: ListView.Horizontal
|
||||||
|
|
||||||
model: ListModel {
|
model: ListModel {
|
||||||
ListElement { label: "custom"; category: "custom" }
|
|
||||||
ListElement { label: "⌛️"; category: "history" }
|
ListElement { label: "⌛️"; category: "history" }
|
||||||
ListElement { label: "😏"; category: "people" }
|
ListElement { label: "😏"; category: "people" }
|
||||||
ListElement { label: "🌲"; category: "nature" }
|
ListElement { label: "🌲"; category: "nature" }
|
||||||
@@ -48,23 +41,16 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
delegate: ItemDelegate {
|
||||||
id: del
|
width: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
required property string label
|
|
||||||
required property string category
|
|
||||||
|
|
||||||
width: contentItem.Layout.preferredWidth
|
|
||||||
height: Kirigami.Units.gridUnit * 2
|
height: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
contentItem: Kirigami.Heading {
|
contentItem: Kirigami.Heading {
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
level: del.label === "custom" ? 4 : 1
|
level: 1
|
||||||
|
|
||||||
Layout.preferredWidth: del.label === "custom" ? implicitWidth + Kirigami.Units.largeSpacing : Kirigami.Units.gridUnit * 2
|
font.family: 'emoji'
|
||||||
|
text: label
|
||||||
font.family: del.label === "custom" ? "" : 'emoji'
|
|
||||||
text: del.label === "custom" ? i18n("Custom") : del.label
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -101,8 +87,6 @@ ColumnLayout {
|
|||||||
|
|
||||||
model: {
|
model: {
|
||||||
switch (emojiCategory) {
|
switch (emojiCategory) {
|
||||||
case "custom":
|
|
||||||
return _picker.customModel
|
|
||||||
case "history":
|
case "history":
|
||||||
return emojiModel.history
|
return emojiModel.history
|
||||||
case "people":
|
case "people":
|
||||||
@@ -134,32 +118,11 @@ ColumnLayout {
|
|||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
font.family: 'emoji'
|
font.family: 'emoji'
|
||||||
text: modelData.isCustom ? "" : modelData.unicode
|
text: modelData.unicode
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
visible: modelData.isCustom
|
|
||||||
source: visible ? modelData.unicode : ""
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 2
|
|
||||||
|
|
||||||
sourceSize.width: width
|
|
||||||
sourceSize.height: height
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: parent.status === Image.Loading
|
|
||||||
radius: height/2
|
|
||||||
gradient: ShimmerGradient { }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (modelData.isCustom) {
|
chosen(modelData.unicode)
|
||||||
chosen(modelData.shortname)
|
|
||||||
} else {
|
|
||||||
chosen(modelData.unicode)
|
|
||||||
}
|
|
||||||
emojiModel.emojiUsed(modelData)
|
emojiModel.emojiUsed(modelData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ ApplicationWindow {
|
|||||||
|
|
||||||
property string filename
|
property string filename
|
||||||
property url localPath
|
property url localPath
|
||||||
property string blurhash: ""
|
|
||||||
property int imageWidth: -1
|
|
||||||
property int imageHeight: -1
|
|
||||||
|
|
||||||
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
|
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
|
||||||
visibility: Qt.WindowFullScreen
|
visibility: Qt.WindowFullScreen
|
||||||
@@ -32,29 +29,22 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BusyIndicator {
|
BusyIndicator {
|
||||||
visible: image.status !== Image.Ready && root.blurhash === ""
|
visible: image.status !== Image.Ready
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
running: visible
|
running: visible
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedImage {
|
AnimatedImage {
|
||||||
id: image
|
id: image
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
width: Math.min(root.imageWidth !== -1 ? root.imageWidth : sourceSize.width, root.width)
|
width: Math.min(sourceSize.width, root.width)
|
||||||
height: Math.min(root.imageHeight !== -1 ? root.imageWidth : sourceSize.height, root.height)
|
height: Math.min(sourceSize.height, root.height)
|
||||||
|
|
||||||
|
cache: false
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
source: localPath
|
source: localPath
|
||||||
|
|
||||||
Image {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: image.width
|
|
||||||
height: image.height
|
|
||||||
source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : ""
|
|
||||||
visible: root.blurhash !== "" && parent.status !== Image.Ready
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
|||||||
@@ -10,18 +10,11 @@ import org.kde.kirigami 2.15 as Kirigami
|
|||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
Kirigami.PlaceholderMessage {
|
QQC2.BusyIndicator {
|
||||||
|
|
||||||
property var showContinueButton: false
|
property var showContinueButton: false
|
||||||
property var showBackButton: false
|
property var showBackButton: false
|
||||||
property string title: i18n("Loading")
|
property string title: i18n("Loading")
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
QQC2.Label {
|
|
||||||
text: i18n("Please wait. This might take a little while.")
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.BusyIndicator {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,10 @@ LoginStep {
|
|||||||
Kirigami.FormLayout {
|
Kirigami.FormLayout {
|
||||||
Connections {
|
Connections {
|
||||||
target: LoginHelper
|
target: LoginHelper
|
||||||
function onSsoUrlChanged() {
|
onSsoUrlChanged: {
|
||||||
Qt.openUrlExternally(LoginHelper.ssoUrl)
|
Qt.openUrlExternally(LoginHelper.ssoUrl)
|
||||||
}
|
}
|
||||||
function onConnected() {
|
onConnected: processed("qrc:/imports/NeoChat/Component/Login/Loading.qml")
|
||||||
processed("qrc:/imports/NeoChat/Component/Login/Loading.qml")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Layouts 1.10
|
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
|
||||||
import org.kde.kirigami 2.14 as Kirigami
|
|
||||||
import org.kde.kitemmodels 1.0
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
QQC2.Popup {
|
|
||||||
id: _popup
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (!visible) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
quickSearch.forceActiveFocus()
|
|
||||||
quickSearch.text = ""
|
|
||||||
}
|
|
||||||
anchors.centerIn: QQC2.Overlay.overlay
|
|
||||||
background: Kirigami.Card {}
|
|
||||||
height: 2 * Math.round(implicitHeight / 2)
|
|
||||||
padding: Kirigami.Units.largeSpacing * 2
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
spacing: Kirigami.Units.largeSpacing * 2
|
|
||||||
|
|
||||||
Kirigami.SearchField {
|
|
||||||
id: quickSearch
|
|
||||||
|
|
||||||
// TODO: get this broken property removed/disabled by default in Kirigami,
|
|
||||||
// we used to be able to expect that the text field wouldn't attempt to
|
|
||||||
// perform a mini-DDOS attack using signals.
|
|
||||||
autoAccept: false
|
|
||||||
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 21 // 3 * 7 = 21, roughly 7 avatars on screen
|
|
||||||
Keys.onLeftPressed: cView.decrementCurrentIndex()
|
|
||||||
Keys.onRightPressed: cView.incrementCurrentIndex()
|
|
||||||
onAccepted: {
|
|
||||||
const item = cView.itemAtIndex(cView.currentIndex)
|
|
||||||
|
|
||||||
RoomManager.enterRoom(item.currentRoom)
|
|
||||||
|
|
||||||
_popup.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ListView {
|
|
||||||
id: cView
|
|
||||||
|
|
||||||
orientation: Qt.Horizontal
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
model: SortFilterRoomListModel {
|
|
||||||
id: sortFilterRoomListModel
|
|
||||||
sourceModel: RoomListModel {
|
|
||||||
id: roomListModel
|
|
||||||
connection: Controller.activeConnection
|
|
||||||
}
|
|
||||||
filterText: quickSearch.text
|
|
||||||
roomSortOrder: SortFilterRoomListModel.LastActivity
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
delegate: Kirigami.Avatar {
|
|
||||||
id: del
|
|
||||||
|
|
||||||
implicitHeight: Kirigami.Units.gridUnit * 3
|
|
||||||
implicitWidth: Kirigami.Units.gridUnit * 3
|
|
||||||
|
|
||||||
required property string avatar
|
|
||||||
required property var currentRoom
|
|
||||||
|
|
||||||
source: avatar != "" ? "image://mxc/" + avatar : ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modal: true
|
|
||||||
focus: true
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
|
||||||
|
|
||||||
// Not to be confused with the Shimmer project.
|
|
||||||
// I like their gradiented GTK themes though.
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
Gradient {
|
|
||||||
id: gradient
|
|
||||||
|
|
||||||
orientation: Gradient.Horizontal
|
|
||||||
|
|
||||||
property color color: Kirigami.Theme.textColor
|
|
||||||
property color translucent: Qt.rgba(color.r, color.g, color.b, 0.2)
|
|
||||||
property color bright: Qt.rgba(color.r, color.g, color.b, 0.3)
|
|
||||||
property real pos: 0.5
|
|
||||||
property real offset: 0.6
|
|
||||||
|
|
||||||
property SequentialAnimation ani: SequentialAnimation {
|
|
||||||
running: true
|
|
||||||
loops: Animation.Infinite
|
|
||||||
NumberAnimation {
|
|
||||||
from: -2.0
|
|
||||||
to: 2.0
|
|
||||||
duration: 700
|
|
||||||
target: gradient
|
|
||||||
properties: "pos"
|
|
||||||
}
|
|
||||||
PauseAnimation {
|
|
||||||
duration: 300
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GradientStop { position: gradient.pos-gradient.offset; color: gradient.translucent }
|
|
||||||
GradientStop { position: gradient.pos; color: gradient.bright }
|
|
||||||
GradientStop { position: gradient.pos+gradient.offset; color: gradient.translucent }
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
TextEdit {
|
|
||||||
text: i18n("This message is encrypted and the sender has not shared the key with this device.")
|
|
||||||
color: Kirigami.Theme.disabledTextColor
|
|
||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize
|
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
|
||||||
readOnly: true
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
textFormat: Text.RichText
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15 as QQC2
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
import QtGraphicalEffects 1.15
|
import QtGraphicalEffects 1.15
|
||||||
import Qt.labs.platform 1.1
|
import Qt.labs.platform 1.1
|
||||||
@@ -23,84 +23,31 @@ RowLayout {
|
|||||||
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
states: [
|
onDownloadedChanged: if (downloaded && openOnFinished) {
|
||||||
State {
|
openSavedFile();
|
||||||
name: "downloaded"
|
|
||||||
when: progressInfo.completed
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
target: downloadButton
|
|
||||||
|
|
||||||
icon.name: "document-open"
|
|
||||||
|
|
||||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
|
|
||||||
|
|
||||||
onClicked: openSavedFile()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "downloading"
|
|
||||||
when: progressInfo.active
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
target: sizeLabel
|
|
||||||
text: i18nc("file download progress", "%1 / %2", Controller.formatByteSize(progressInfo.progress), Controller.formatByteSize(progressInfo.total))
|
|
||||||
}
|
|
||||||
PropertyChanges {
|
|
||||||
target: downloadButton
|
|
||||||
icon.name: "media-playback-stop"
|
|
||||||
|
|
||||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
|
|
||||||
onClicked: currentRoom.cancelFileTransfer(eventId)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "raw"
|
|
||||||
when: true
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
target: downloadButton
|
|
||||||
|
|
||||||
onClicked: root.saveFileAs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
id: ikon
|
|
||||||
source: model.fileMimetypeIcon
|
|
||||||
fallback: "unknown"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
icon.name: progressInfo.completed ? "document-open" : "document-save"
|
||||||
|
onClicked: progressInfo.completed ? openSavedFile() : saveFileAs()
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Kirigami.Heading {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
level: 4
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
QQC2.Label {
|
|
||||||
text: model.display
|
text: model.display
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Label.Wrap
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
}
|
||||||
QQC2.Label {
|
|
||||||
id: sizeLabel
|
|
||||||
|
|
||||||
text: Controller.formatByteSize(content.info ? content.info.size : 0)
|
|
||||||
opacity: 0.7
|
|
||||||
|
|
||||||
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
text: !progressInfo.completed && progressInfo.active ? (Controller.formatByteSize(progressInfo.progress) + "/" + Controller.formatByteSize(progressInfo.total)) : Controller.formatByteSize(content.info ? content.info.size : 0)
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
wrapMode: Label.Wrap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.Button {
|
|
||||||
id: downloadButton
|
|
||||||
icon.name: "download"
|
|
||||||
|
|
||||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
|
|
||||||
QQC2.ToolTip.visible: hovered
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: fileDialog
|
id: fileDialog
|
||||||
|
|
||||||
@@ -114,11 +61,21 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saveFileAs() {
|
function saveFileAs() {
|
||||||
var dialog = fileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
var dialog = fileDialog.createObject(ApplicationWindow.overlay)
|
||||||
dialog.open()
|
dialog.open()
|
||||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function downloadAndOpen() {
|
||||||
|
if (downloaded) {
|
||||||
|
openSavedFile();
|
||||||
|
} else {
|
||||||
|
openOnFinished = true;
|
||||||
|
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/"
|
||||||
|
+ eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function openSavedFile() {
|
function openSavedFile() {
|
||||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||||
|
|||||||
@@ -29,12 +29,6 @@ Image {
|
|||||||
|
|
||||||
source: "image://mxc/" + mediaId
|
source: "image://mxc/" + mediaId
|
||||||
|
|
||||||
Image {
|
|
||||||
anchors.fill: parent
|
|
||||||
source: content.info["xyz.amorgan.blurhash"] ? ("image://blurhash/" + content.info["xyz.amorgan.blurhash"]) : ""
|
|
||||||
visible: parent.status !== Image.Ready
|
|
||||||
}
|
|
||||||
|
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
ToolTip.text: display
|
ToolTip.text: display
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Flow {
|
|||||||
shadow.size: Kirigami.Units.smallSpacing
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
shadow.color: !model.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
shadow.color: !model.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
||||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
border.width: 1
|
border.width: Kirigami.Units.devicePixelRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,11 @@ MouseArea {
|
|||||||
id: replyLeftBorder
|
id: replyLeftBorder
|
||||||
width: Kirigami.Units.smallSpacing
|
width: Kirigami.Units.smallSpacing
|
||||||
height: parent.height
|
height: parent.height
|
||||||
x: Config.compactLayout ? Kirigami.Units.largeSpacing : 0
|
|
||||||
color: Kirigami.Theme.highlightColor
|
color: Kirigami.Theme.highlightColor
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Avatar {
|
Kirigami.Avatar {
|
||||||
id: replyAvatar
|
id: avatatReply
|
||||||
anchors.left: replyLeftBorder.right
|
anchors.left: replyLeftBorder.right
|
||||||
anchors.leftMargin: Kirigami.Units.smallSpacing
|
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||||
width: visible ? Kirigami.Units.gridUnit : 0
|
width: visible ? Kirigami.Units.gridUnit : 0
|
||||||
@@ -47,12 +46,12 @@ MouseArea {
|
|||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: replyName
|
id: replyName
|
||||||
anchors {
|
anchors {
|
||||||
left: replyAvatar.right
|
left: avatatReply.right
|
||||||
leftMargin: Kirigami.Units.smallSpacing
|
leftMargin: Kirigami.Units.smallSpacing
|
||||||
right: parent.right
|
right: parent.right
|
||||||
rightMargin: Kirigami.Units.smallSpacing
|
rightMargin: Kirigami.Units.smallSpacing
|
||||||
}
|
}
|
||||||
text: currentRoom.htmlSafeMemberName(reply.author.id)
|
text: reply.author.displayName
|
||||||
color: reply.author.color
|
color: reply.author.color
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
@@ -79,9 +78,9 @@ MouseArea {
|
|||||||
id: replyText
|
id: replyText
|
||||||
textMessage: reply.display
|
textMessage: reply.display
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
hasContextMenu: false
|
wrapMode: Text.WordWrap
|
||||||
width: Math.min(implicitWidth, bubbleMaxWidth - Kirigami.Units.largeSpacing * 3)
|
width: Math.min(implicitWidth, bubbleMaxWidth - Kirigami.Units.largeSpacing * 3)
|
||||||
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
x: Kirigami.Units.smallSpacing * 3 + avatatReply.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +94,9 @@ MouseArea {
|
|||||||
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
|
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
|
||||||
source: "image://mxc/" + mediaId
|
source: "image://mxc/" + mediaId
|
||||||
|
|
||||||
width: bubbleMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width
|
width: bubbleMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - avatatReply.width
|
||||||
height: reply.content.info.h / reply.content.info.w * width
|
height: reply.content.info.h / reply.content.info.w * width
|
||||||
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
x: Kirigami.Units.smallSpacing * 3 + avatatReply.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import org.kde.kirigami 2.15 as Kirigami
|
|||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
level: 4
|
level: 4
|
||||||
text: model.showSection ? section : ""
|
text: model.showSection ? section : ""
|
||||||
color: Kirigami.Theme.disabledTextColor
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
topPadding: Kirigami.Units.largeSpacing * 2
|
topPadding: Kirigami.Units.largeSpacing * 2
|
||||||
bottomPadding: Kirigami.Units.smallSpacing
|
bottomPadding: Kirigami.Units.smallSpacing
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ RowLayout {
|
|||||||
id: row
|
id: row
|
||||||
|
|
||||||
Kirigami.Avatar {
|
Kirigami.Avatar {
|
||||||
id: icon
|
|
||||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
@@ -38,10 +37,9 @@ RowLayout {
|
|||||||
Label {
|
Label {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: icon.height
|
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
text: "<style>a {text-decoration: none;}</style><a href=\"https://matrix.to/#/" + author.id + "\" style='color: " + author.color + "'>" + currentRoom.htmlSafeMemberName(author.id) + "</a> " + display
|
text: "<style>a {text-decoration: none;}</style><a href=\"https://matrix.to/#/" + author.id + "\" style='color: " + author.color + "'>" + author.displayName + "</a> " + display
|
||||||
onLinkActivated: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open()
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,22 +11,13 @@ import org.kde.kirigami 2.15 as Kirigami
|
|||||||
TextEdit {
|
TextEdit {
|
||||||
id: contentLabel
|
id: contentLabel
|
||||||
|
|
||||||
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
|
Layout.topMargin: 0
|
||||||
|
|
||||||
readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
|
readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
|
||||||
readonly property var hasSpoiler: /data-mx-spoiler/g
|
|
||||||
|
|
||||||
property bool isEmote: false
|
property bool isEmote: false
|
||||||
property string textMessage: model.display
|
property string textMessage: model.display
|
||||||
property bool spoilerRevealed: !hasSpoiler.test(textMessage)
|
|
||||||
|
|
||||||
property bool hasContextMenu: true
|
|
||||||
|
|
||||||
signal requestOpenMessageContext()
|
|
||||||
|
|
||||||
ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage))
|
|
||||||
|
|
||||||
Layout.fillWidth: Config.compactLayout
|
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
|
||||||
|
|
||||||
text: "<style>
|
text: "<style>
|
||||||
table {
|
table {
|
||||||
@@ -47,21 +38,17 @@ a{
|
|||||||
color: " + Kirigami.Theme.linkColor + ";
|
color: " + Kirigami.Theme.linkColor + ";
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
" + (!spoilerRevealed ? "
|
|
||||||
[data-mx-spoiler] {
|
|
||||||
color: transparent;
|
|
||||||
background: " + Kirigami.Theme.textColor + ";
|
|
||||||
}
|
|
||||||
" : "") + "
|
|
||||||
</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + textMessage + (isEdited ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + "<span style='font-size: " + Kirigami.Theme.defaultFont.pixelSize +"px'>" + i18n(" (edited)") + "</span>") : "")
|
</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + textMessage + (isEdited ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + "<span style='font-size: " + Kirigami.Theme.defaultFont.pixelSize +"px'>" + i18n(" (edited)") + "</span>") : "")
|
||||||
|
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
font.pointSize: model.reply === undefined && isEmoji.test(model.display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
font.pointSize: model.reply === undefined && isEmoji.test(model.display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
selectByMouse: !Kirigami.Settings.isMobile
|
||||||
readOnly: true
|
readOnly: true
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.WordWrap
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
onLinkActivated: RoomManager.openResource(link)
|
onLinkActivated: RoomManager.openResource(link)
|
||||||
onHoveredLinkChanged: if (hoveredLink.length > 0) {
|
onHoveredLinkChanged: if (hoveredLink.length > 0) {
|
||||||
applicationWindow().hoverLinkIndicator.text = hoveredLink;
|
applicationWindow().hoverLinkIndicator.text = hoveredLink;
|
||||||
@@ -69,24 +56,9 @@ a{
|
|||||||
applicationWindow().hoverLinkIndicator.text = "";
|
applicationWindow().hoverLinkIndicator.text = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
MouseArea {
|
||||||
cursorShape: (parent.hoveredLink || !spoilerRevealed) ? Qt.PointingHandCursor : Qt.IBeamCursor
|
anchors.fill: parent
|
||||||
}
|
acceptedButtons: Qt.NoButton
|
||||||
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||||
TapHandler {
|
|
||||||
enabled: !parent.hoveredLink && !spoilerRevealed
|
|
||||||
onTapped: spoilerRevealed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onTapped: openMessageContext(model, parent.selectedText)
|
|
||||||
enabled: hasContextMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
onLongPressed: requestOpenMessageContext()
|
|
||||||
enabled: hasContextMenu
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ QQC2.ItemDelegate {
|
|||||||
property bool isEmote: false
|
property bool isEmote: false
|
||||||
property bool cardBackground: true
|
property bool cardBackground: true
|
||||||
|
|
||||||
readonly property int bubbleMaxWidth: Config.compactLayout && !Config.showAvatarInTimeline ? width : (Config.compactLayout ? width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 4 : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 6, Kirigami.Units.gridUnit * 20))
|
readonly property int bubbleMaxWidth: !Config.showAvatarInTimeline ? width : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 4, Kirigami.Units.gridUnit * 20)
|
||||||
|
|
||||||
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !applicationWindow().wideScreen
|
|
||||||
|
|
||||||
signal saveFileAs()
|
signal saveFileAs()
|
||||||
signal openExternally()
|
signal openExternally()
|
||||||
@@ -50,7 +48,7 @@ QQC2.ItemDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
height: sectionDelegate.height + Math.max(model.showAuthor ? avatar.height : 0, bubble.implicitHeight) + loader.height + (showAuthor ? Kirigami.Units.largeSpacing : 0)
|
height: sectionDelegate.height + Math.max(avatar.height, bubble.implicitHeight) + loader.height + (model.showAuthor ? Kirigami.Units.smallSpacing : 0) - (Config.showAvatarInTimeline ? 0 : Kirigami.Units.largeSpacing)
|
||||||
|
|
||||||
SectionDelegate {
|
SectionDelegate {
|
||||||
id: sectionDelegate
|
id: sectionDelegate
|
||||||
@@ -69,12 +67,12 @@ QQC2.ItemDelegate {
|
|||||||
sourceSize.height: width
|
sourceSize.height: width
|
||||||
anchors {
|
anchors {
|
||||||
top: sectionDelegate.bottom
|
top: sectionDelegate.bottom
|
||||||
topMargin: model.showAuthor ? Kirigami.Units.largeSpacing : 0
|
topMargin: model.showAuthor ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.smallSpacing
|
||||||
left: parent.left
|
left: parent.left
|
||||||
leftMargin: Kirigami.Units.largeSpacing
|
leftMargin: Kirigami.Units.largeSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
visible: model.showAuthor && Config.showAvatarInTimeline && !showUserMessageOnRight
|
visible: model.showAuthor && Config.showAvatarInTimeline
|
||||||
name: model.author.name ?? model.author.displayName
|
name: model.author.name ?? model.author.displayName
|
||||||
source: visible && model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : ""
|
source: visible && model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : ""
|
||||||
color: model.author.color
|
color: model.author.color
|
||||||
@@ -96,50 +94,18 @@ QQC2.ItemDelegate {
|
|||||||
|
|
||||||
QQC2.Control {
|
QQC2.Control {
|
||||||
id: bubble
|
id: bubble
|
||||||
topPadding: !Config.compactLayout ? Kirigami.Units.largeSpacing : 0
|
topPadding: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||||
bottomPadding: !Config.compactLayout ? Kirigami.Units.largeSpacing : 0
|
bottomPadding: 0
|
||||||
leftPadding: Kirigami.Units.smallSpacing
|
leftPadding: 0
|
||||||
rightPadding: Config.compactLayout ? Kirigami.Units.largeSpacing : Kirigami.Units.smallSpacing
|
rightPadding: 0
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
||||||
// state: Config.compactLayout ? "compactLayout" : "default"
|
|
||||||
state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
|
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: avatar.top
|
top: avatar.top
|
||||||
leftMargin: Kirigami.Units.largeSpacing
|
left: avatar.right
|
||||||
rightMargin: showUserMessageOnRight ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
|
leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
right: Config.showAvatarInTimeline ? undefined : parent.right
|
||||||
|
rightMargin: Config.showAvatarInTimeline ? undefined : Kirigami.Units.largeSpacing
|
||||||
}
|
}
|
||||||
// HACK: anchoring didn't reset anchors.right when switching from parent.right to undefined reliably
|
|
||||||
width: Config.compactLayout ? messageDelegate.width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) + Kirigami.Units.largeSpacing * 2 : implicitWidth
|
|
||||||
|
|
||||||
|
|
||||||
// states for anchor animations on window resize
|
|
||||||
// as setting anchors to undefined did not work reliably
|
|
||||||
states: [
|
|
||||||
State {
|
|
||||||
name: "userMessageOnRight"
|
|
||||||
AnchorChanges {
|
|
||||||
target: bubble
|
|
||||||
anchors.left: undefined
|
|
||||||
anchors.right: parent.right
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "userMessageOnLeft"
|
|
||||||
AnchorChanges {
|
|
||||||
target: bubble
|
|
||||||
anchors.left: avatar.right
|
|
||||||
anchors.right: undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
transitions: [
|
|
||||||
Transition {
|
|
||||||
AnchorAnimation{duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
id: column
|
id: column
|
||||||
@@ -150,7 +116,7 @@ QQC2.ItemDelegate {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.preferredWidth: nameLabel.implicitWidth + timeLabel.implicitWidth + Kirigami.Units.largeSpacing * 2
|
Layout.preferredWidth: nameLabel.implicitWidth + timeLabel.implicitWidth + Kirigami.Units.largeSpacing
|
||||||
Layout.maximumWidth: bubbleMaxWidth
|
Layout.maximumWidth: bubbleMaxWidth
|
||||||
implicitHeight: visible ? nameLabel.implicitHeight : 0
|
implicitHeight: visible ? nameLabel.implicitHeight : 0
|
||||||
|
|
||||||
@@ -164,7 +130,6 @@ QQC2.ItemDelegate {
|
|||||||
anchors.rightMargin: Kirigami.Units.smallSpacing
|
anchors.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
text: visible ? author.displayName : ""
|
text: visible ? author.displayName : ""
|
||||||
textFormat: Text.PlainText
|
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
color: author.color
|
color: author.color
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
@@ -195,8 +160,7 @@ QQC2.ItemDelegate {
|
|||||||
active: model.reply !== undefined
|
active: model.reply !== undefined
|
||||||
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
|
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
|
||||||
visible: active
|
visible: active
|
||||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||||
Layout.bottomMargin: Config.compactLayout ? 0 : Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: replyLoader.item
|
target: replyLoader.item
|
||||||
@@ -207,31 +171,14 @@ QQC2.ItemDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Item {
|
background: Kirigami.ShadowedRectangle {
|
||||||
Rectangle {
|
visible: cardBackground && Config.showAvatarInTimeline
|
||||||
visible: messageDelegate.hovered
|
color: model.isHighlighted ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
||||||
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
radius: Kirigami.Units.smallSpacing
|
||||||
radius: Kirigami.Units.smallSpacing
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
anchors.fill: parent
|
shadow.color: !model.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
||||||
}
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
Kirigami.ShadowedRectangle {
|
border.width: Kirigami.Units.devicePixelRatio
|
||||||
visible: cardBackground && !Config.compactLayout
|
|
||||||
anchors.fill: parent
|
|
||||||
color: {
|
|
||||||
if (model.author.isLocalUser) {
|
|
||||||
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
|
||||||
} else if (model.isHighlighted) {
|
|
||||||
return Kirigami.Theme.positiveBackgroundColor
|
|
||||||
} else {
|
|
||||||
return Kirigami.Theme.backgroundColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
radius: Kirigami.Units.smallSpacing
|
|
||||||
shadow.size: Kirigami.Units.smallSpacing
|
|
||||||
shadow.color: !model.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
|
||||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
|
||||||
border.width: 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +188,7 @@ QQC2.ItemDelegate {
|
|||||||
left: bubble.left
|
left: bubble.left
|
||||||
right: parent.right
|
right: parent.right
|
||||||
top: bubble.bottom
|
top: bubble.bottom
|
||||||
topMargin: active && !Config.compactLayout ? Kirigami.Units.smallSpacing : 0
|
topMargin: active && Config.showAvatarInTimeline ? Kirigami.Units.smallSpacing : 0
|
||||||
}
|
}
|
||||||
height: active ? item.implicitHeight : 0
|
height: active ? item.implicitHeight : 0
|
||||||
//Layout.bottomMargin: readMarker ? Kirigami.Units.smallSpacing : 0
|
//Layout.bottomMargin: readMarker ? Kirigami.Units.smallSpacing : 0
|
||||||
|
|||||||
@@ -8,4 +8,3 @@ FileDelegate 1.0 FileDelegate.qml
|
|||||||
VideoDelegate 1.0 VideoDelegate.qml
|
VideoDelegate 1.0 VideoDelegate.qml
|
||||||
ReactionDelegate 1.0 ReactionDelegate.qml
|
ReactionDelegate 1.0 ReactionDelegate.qml
|
||||||
AudioDelegate 1.0 AudioDelegate.qml
|
AudioDelegate 1.0 AudioDelegate.qml
|
||||||
EncryptedDelegate 1.0 EncryptedDelegate.qml
|
|
||||||
@@ -3,5 +3,3 @@ FullScreenImage 1.0 FullScreenImage.qml
|
|||||||
ChatTextInput 1.0 ChatTextInput.qml
|
ChatTextInput 1.0 ChatTextInput.qml
|
||||||
FancyEffectsContainer 1.0 FancyEffectsContainer.qml
|
FancyEffectsContainer 1.0 FancyEffectsContainer.qml
|
||||||
TypingPane 1.0 TypingPane.qml
|
TypingPane 1.0 TypingPane.qml
|
||||||
QuickSwitcher 1.0 QuickSwitcher.qml
|
|
||||||
ShimmerGradient 1.0 ShimmerGradient.qml
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ Kirigami.OverlaySheet {
|
|||||||
|
|
||||||
parent: applicationWindow().overlay
|
parent: applicationWindow().overlay
|
||||||
|
|
||||||
title: i18n("Create a Room")
|
header: Kirigami.Heading {
|
||||||
|
text: i18n("Create a Room")
|
||||||
|
}
|
||||||
|
|
||||||
contentItem: Kirigami.FormLayout {
|
contentItem: Kirigami.FormLayout {
|
||||||
TextField {
|
TextField {
|
||||||
|
|||||||
207
imports/NeoChat/Dialog/RoomSettingsDialog.qml
Normal file
207
imports/NeoChat/Dialog/RoomSettingsDialog.qml
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
|
Kirigami.OverlaySheet {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var room
|
||||||
|
|
||||||
|
readonly property bool canChangeAvatar: room.canSendState("m.room.avatar")
|
||||||
|
readonly property bool canChangeName: room.canSendState("m.room.name")
|
||||||
|
readonly property bool canChangeTopic: room.canSendState("m.room.topic")
|
||||||
|
readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias")
|
||||||
|
|
||||||
|
parent: applicationWindow().overlay
|
||||||
|
|
||||||
|
|
||||||
|
header: Kirigami.Heading {
|
||||||
|
text: i18nc("%1 is the room name", "Room Settings - %1", room.displayName)
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
|
Kirigami.Avatar {
|
||||||
|
Layout.preferredWidth: 72
|
||||||
|
Layout.preferredHeight: 72
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
name: room.name
|
||||||
|
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
enabled: canChangeAvatar
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
||||||
|
|
||||||
|
fileDialog.chosen.connect(function(path) {
|
||||||
|
if (!path) return
|
||||||
|
|
||||||
|
room.changeAvatar(path)
|
||||||
|
})
|
||||||
|
|
||||||
|
fileDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: roomNameField
|
||||||
|
text: room.name
|
||||||
|
Kirigami.FormData.label: i18n("Room Name:")
|
||||||
|
enabled: canChangeName
|
||||||
|
}
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
id: roomTopicField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: room.topic
|
||||||
|
Kirigami.FormData.label: i18n("Room topic:")
|
||||||
|
enabled: canChangeTopic
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
|
||||||
|
visible: canChangeName || canChangeTopic
|
||||||
|
|
||||||
|
text: i18n("Save")
|
||||||
|
highlighted: true
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (room.name != roomNameField.text) {
|
||||||
|
room.setName(roomNameField.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (room.topic != roomTopicField.text) {
|
||||||
|
room.setTopic(roomTopicField.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: canonicalAliasComboBox.visible || altAlias.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
id: canonicalAliasComboBox
|
||||||
|
visible: room.aliases && room.aliases.length
|
||||||
|
Kirigami.FormData.label: i18n("Canonical Alias:")
|
||||||
|
popup.z: 999; // HACK This is an absolute hack, but combos inside OverlaySheets have their popups show up underneath, because of fun z ordering stuff
|
||||||
|
|
||||||
|
enabled: canChangeCanonicalAlias
|
||||||
|
|
||||||
|
model: room.aliases
|
||||||
|
|
||||||
|
currentIndex: room.aliases.indexOf(room.canonicalAlias)
|
||||||
|
onCurrentIndexChanged: {
|
||||||
|
if (room.canonicalAlias != room.aliases[currentIndex]) {
|
||||||
|
room.setCanonicalAlias(room.aliases[currentIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: altAlias
|
||||||
|
Kirigami.FormData.label: i18n("Alt Aliases")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
visible: room.altAliases && room.altAliases.length
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: room.altAliases
|
||||||
|
|
||||||
|
delegate: RowLayout {
|
||||||
|
Layout.maximumWidth: parent.width
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: modelData
|
||||||
|
color: Kirigami.Theme.disabledColor
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
icon.name: ""
|
||||||
|
onClicked: room.removeLocalAlias(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: next.visible || prev.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
id: next
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
visible: room.predecessorId && room.connection.room(room.predecessorId)
|
||||||
|
|
||||||
|
padding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
contentItem: Kirigami.InlineMessage {
|
||||||
|
text: i18n("This room continues another conversation.")
|
||||||
|
actions: Kirigami.Action {
|
||||||
|
text: i18n("See older messages...")
|
||||||
|
onTriggered: {
|
||||||
|
roomListForm.enteredRoom = Controller.activeConnection.room(room.predecessorId)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
id: prev
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
visible: room.successorId && room.connection.room(room.successorId)
|
||||||
|
|
||||||
|
padding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
contentItem: Kirigami.InlineMessage {
|
||||||
|
text: i18n("This room has been replaced.")
|
||||||
|
actions: Kirigami.Action {
|
||||||
|
text: i18n("See new room...")
|
||||||
|
onTriggered: {
|
||||||
|
roomListForm.enteredRoom = Controller.activeConnection.room(room.successorId)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: openFileDialog
|
||||||
|
|
||||||
|
OpenFileDialog {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,13 @@ Kirigami.OverlaySheet {
|
|||||||
rightPadding: 0
|
rightPadding: 0
|
||||||
topPadding: 0
|
topPadding: 0
|
||||||
|
|
||||||
title: i18nc("@title:menu Account detail dialog", "Account detail")
|
header: Kirigami.Heading {
|
||||||
|
id: heading
|
||||||
|
text: i18nc("@title:menu Account detail dialog", "Account detail")
|
||||||
|
elide: Text.ElideRight
|
||||||
|
QQC2.ToolTip.visible: truncated && hovered
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
}
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
@@ -68,7 +74,7 @@ Kirigami.OverlaySheet {
|
|||||||
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
wrapMode: Text.NoWrap
|
wrapMode: Text.NoWrap
|
||||||
text: room.htmlSafeMemberName(user.id)
|
text: displayName
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
@@ -123,32 +129,6 @@ Kirigami.OverlaySheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Kirigami.BasicListItem {
|
Kirigami.BasicListItem {
|
||||||
visible: user !== room.localUser && room.canSendState("ban") && room.isUserBanned(user.id)
|
|
||||||
|
|
||||||
action: Kirigami.Action {
|
|
||||||
text: i18n("Unban this user")
|
|
||||||
icon.name: "im-irc"
|
|
||||||
icon.color: Kirigami.Theme.negativeTextColor
|
|
||||||
onTriggered: {
|
|
||||||
room.unban(user.id)
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Kirigami.BasicListItem {
|
|
||||||
visible: user === room.localUser || room.canSendState("redact")
|
|
||||||
|
|
||||||
action: Kirigami.Action {
|
|
||||||
text: i18n("Delete recent messages by this user")
|
|
||||||
icon.name: "delete"
|
|
||||||
icon.color: Kirigami.Theme.negativeTextColor
|
|
||||||
onTriggered: {
|
|
||||||
room.deleteMessagesByUser(user.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Kirigami.BasicListItem {
|
|
||||||
visible: user !== room.localUser
|
|
||||||
action: Kirigami.Action {
|
action: Kirigami.Action {
|
||||||
text: i18n("Open a private chat")
|
text: i18n("Open a private chat")
|
||||||
icon.name: "document-send"
|
icon.name: "document-send"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
module NeoChat.Dialog
|
module NeoChat.Dialog
|
||||||
|
RoomSettingsDialog 1.0 RoomSettingsDialog.qml
|
||||||
UserDetailDialog 1.0 UserDetailDialog.qml
|
UserDetailDialog 1.0 UserDetailDialog.qml
|
||||||
LoginDialog 1.0 LoginDialog.qml
|
LoginDialog 1.0 LoginDialog.qml
|
||||||
CreateRoomDialog 1.0 CreateRoomDialog.qml
|
CreateRoomDialog 1.0 CreateRoomDialog.qml
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
import Qt.labs.platform 1.1 as Labs
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
|
||||||
import QtQuick.Layouts 1.10
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
Labs.Menu {
|
|
||||||
id: editMenu
|
|
||||||
|
|
||||||
required property Item field
|
|
||||||
|
|
||||||
Labs.MenuItem {
|
|
||||||
enabled: editMenu.field !== null && editMenu.field.canUndo
|
|
||||||
text: i18nc("text editing menu action", "Undo")
|
|
||||||
shortcut: StandardKey.Undo
|
|
||||||
onTriggered: {
|
|
||||||
editMenu.field.undo()
|
|
||||||
editMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Labs.MenuItem {
|
|
||||||
enabled: editMenu.field !== null && editMenu.field.canRedo
|
|
||||||
text: i18nc("text editing menu action", "Redo")
|
|
||||||
shortcut: StandardKey.Redo
|
|
||||||
onTriggered: {
|
|
||||||
editMenu.field.undo()
|
|
||||||
editMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Labs.MenuSeparator {
|
|
||||||
}
|
|
||||||
|
|
||||||
Labs.MenuItem {
|
|
||||||
enabled: editMenu.field !== null && editMenu.field.selectedText
|
|
||||||
text: i18nc("text editing menu action", "Cut")
|
|
||||||
shortcut: StandardKey.Cut
|
|
||||||
onTriggered: {
|
|
||||||
editMenu.field.cut()
|
|
||||||
editMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Labs.MenuItem {
|
|
||||||
enabled: editMenu.field !== null && editMenu.field.selectedText
|
|
||||||
text: i18nc("text editing menu action", "Copy")
|
|
||||||
shortcut: StandardKey.Copy
|
|
||||||
onTriggered: {
|
|
||||||
editMenu.field.copy()
|
|
||||||
editMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Labs.MenuItem {
|
|
||||||
enabled: editMenu.field !== null && editMenu.field.canPaste
|
|
||||||
text: i18nc("text editing menu action", "Paste")
|
|
||||||
shortcut: StandardKey.Paste
|
|
||||||
onTriggered: {
|
|
||||||
editMenu.field.paste()
|
|
||||||
editMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Labs.MenuItem {
|
|
||||||
enabled: editMenu.field !== null && editMenu.field.selectedText !== ""
|
|
||||||
text: i18nc("text editing menu action", "Delete")
|
|
||||||
shortcut: ""
|
|
||||||
onTriggered: {
|
|
||||||
editMenu.field.remove(editMenu.field.selectionStart, editMenu.field.selectionEnd)
|
|
||||||
editMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Labs.MenuSeparator {
|
|
||||||
}
|
|
||||||
|
|
||||||
Labs.MenuItem {
|
|
||||||
enabled: editMenu.field !== null
|
|
||||||
text: i18nc("text editing menu action", "Select All")
|
|
||||||
shortcut: StandardKey.SelectAll
|
|
||||||
onTriggered: {
|
|
||||||
editMenu.field.selectAll()
|
|
||||||
editMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
import Qt.labs.platform 1.1 as Labs
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Window 2.15
|
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
|
||||||
import QtQuick.Layouts 1.10
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
import NeoChat.Component 1.0
|
|
||||||
import NeoChat.Dialog 1.0
|
|
||||||
import NeoChat.Page 1.0
|
|
||||||
import NeoChat.Panel 1.0
|
|
||||||
|
|
||||||
Labs.MenuBar {
|
|
||||||
Labs.Menu {
|
|
||||||
title: i18nc("menu", "NeoChat")
|
|
||||||
|
|
||||||
// TODO: make about page its own thing so we can go to it instead of settings where it's currently at
|
|
||||||
// Labs.MenuItem {
|
|
||||||
// text: i18nc("menu", "About NeoChat")
|
|
||||||
// }
|
|
||||||
Labs.MenuItem {
|
|
||||||
enabled: pageStack.layers.currentItem.title !== i18n("Settings")
|
|
||||||
text: i18nc("menu", "Preferences…")
|
|
||||||
|
|
||||||
shortcut: StandardKey.Preferences
|
|
||||||
onTriggered: pageStack.pushDialogLayer("qrc:/imports/NeoChat/Settings/SettingsPage.qml")
|
|
||||||
}
|
|
||||||
Labs.MenuItem {
|
|
||||||
text: i18nc("menu", "Quit NeoChat")
|
|
||||||
|
|
||||||
shortcut: StandardKey.Quit
|
|
||||||
onTriggered: Qt.quit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Labs.Menu {
|
|
||||||
title: i18nc("menu", "File")
|
|
||||||
|
|
||||||
Labs.MenuItem {
|
|
||||||
text: i18nc("menu", "New Private Chat…")
|
|
||||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
|
||||||
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {"connection": Controller.activeConnection})
|
|
||||||
}
|
|
||||||
Labs.MenuItem {
|
|
||||||
text: i18nc("menu", "New Group…")
|
|
||||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
|
||||||
shortcut: StandardKey.New
|
|
||||||
onTriggered: {
|
|
||||||
const dialog = createRoomDialog.createObject(root.overlay)
|
|
||||||
dialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Labs.MenuItem {
|
|
||||||
text: i18nc("menu", "Browse Chats…")
|
|
||||||
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": Controller.activeConnection})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EditMenu {
|
|
||||||
title: i18nc("menu", "Edit")
|
|
||||||
field: (root.activeFocusItem instanceof TextEdit || root.activeFocusItem instanceof TextInput) ? root.activeFocusItem : null
|
|
||||||
}
|
|
||||||
Labs.Menu {
|
|
||||||
title: i18nc("menu", "View")
|
|
||||||
|
|
||||||
Labs.MenuItem {
|
|
||||||
text: i18nc("menu item that opens a UI element called the 'Quick Switcher', which offers a fast keyboard-based interface for switching in between chats.", "Open Quick Switcher")
|
|
||||||
shortcut: "Ctrl+K"
|
|
||||||
onTriggered: quickView.item.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Labs.Menu {
|
|
||||||
title: i18nc("menu", "Window")
|
|
||||||
|
|
||||||
// Labs.MenuItem {
|
|
||||||
// text: settings.userWantsSidebars ? i18nc("menu", "Hide Sidebar") : i18nc("menu", "Show Sidebar")
|
|
||||||
// onTriggered: settings.userWantsSidebars = !settings.userWantsSidebars
|
|
||||||
// }
|
|
||||||
Labs.MenuItem {
|
|
||||||
text: root.visibility === Window.FullScreen ? i18nc("menu", "Exit Full Screen") : i18nc("menu", "Enter Full Screen")
|
|
||||||
onTriggered: root.visibility === Window.FullScreen ? root.showNormal() : root.showFullScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: offline help system (https://invent.kde.org/network/neochat/-/issues/411)
|
|
||||||
Labs.Menu {
|
|
||||||
title: i18nc("menu", "Help")
|
|
||||||
|
|
||||||
Labs.MenuItem {
|
|
||||||
text: i18nc("menu", "Matrix FAQ")
|
|
||||||
onTriggered: Qt.openUrlExternally("https://matrix.org/faq/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,8 +5,6 @@
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
import org.kde.kirigami 2.19 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Page 1.0
|
import NeoChat.Page 1.0
|
||||||
|
|
||||||
@@ -18,15 +16,11 @@ Menu {
|
|||||||
property var room
|
property var room
|
||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
id: newWindow
|
|
||||||
text: i18n("Open in new window")
|
text: i18n("Open in new window")
|
||||||
onTriggered: RoomManager.openWindow(room);
|
onTriggered: RoomManager.openWindow(room);
|
||||||
visible: !Kirigami.Settings.isMobile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuSeparator {
|
MenuSeparator {}
|
||||||
visible: newWindow.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: room.isFavourite ? i18n("Remove from Favourites") : i18n("Add to Favourites")
|
text: room.isFavourite ? i18n("Remove from Favourites") : i18n("Add to Favourites")
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Context menu when clicking on a room in the room list
|
|
||||||
*/
|
|
||||||
Menu {
|
|
||||||
id: root
|
|
||||||
property var selectedText
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: WebShortcutModel {
|
|
||||||
selectedText: root.selectedText
|
|
||||||
}
|
|
||||||
delegate: MenuItem {
|
|
||||||
text: model.display
|
|
||||||
icon.name: model.decoration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuSeparator {}
|
|
||||||
|
|
||||||
onClosed: destroy()
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,6 @@ Loader {
|
|||||||
property string eventType: ""
|
property string eventType: ""
|
||||||
property string formattedBody: ""
|
property string formattedBody: ""
|
||||||
required property string source
|
required property string source
|
||||||
property string selectedText: ""
|
|
||||||
|
|
||||||
property list<Kirigami.Action> actions: [
|
property list<Kirigami.Action> actions: [
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
@@ -43,7 +42,7 @@ Loader {
|
|||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18n("Copy")
|
text: i18n("Copy")
|
||||||
icon.name: "edit-copy"
|
icon.name: "edit-copy"
|
||||||
onTriggered: Clipboard.saveText(loadRoot.selectedText === "" ? loadRoot.message : loadRoot.selectedText)
|
onTriggered: Clipboard.saveText(message)
|
||||||
},
|
},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18n("View Source")
|
text: i18n("View Source")
|
||||||
@@ -66,33 +65,89 @@ Loader {
|
|||||||
onClicked: loadRoot.item.close();
|
onClicked: loadRoot.item.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QQC2.Menu {
|
}
|
||||||
id: webshortcutmenu
|
/*
|
||||||
title: i18n("Search for '%1'", webshortcutmodel.trunkatedSearchText)
|
Kirigami.OverlaySheet {
|
||||||
property bool isVisible: selectedText && selectedText.length > 0 && webshortcutmodel.enabled
|
id: root
|
||||||
Component.onCompleted: webshortcutmenu.parent.visible = isVisible
|
|
||||||
onIsVisibleChanged: webshortcutmenu.parent.visible = isVisible
|
parent: applicationWindow().overlay
|
||||||
Instantiator {
|
|
||||||
model: WebShortcutModel {
|
leftPadding: 0
|
||||||
id: webshortcutmodel
|
rightPadding: 0
|
||||||
selectedText: loadRoot.selectedText
|
|
||||||
onOpenUrl: RoomManager.visitNonMatrix(url)
|
header: Kirigami.Heading {
|
||||||
|
text: i18nc("@title:menu Message detail dialog", "Message detail")
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
RowLayout {
|
||||||
|
id: headerLayout
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
Kirigami.Avatar {
|
||||||
|
id: avatar
|
||||||
|
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
name: author.displayName
|
||||||
|
color: author.color
|
||||||
}
|
}
|
||||||
delegate: QQC2.MenuItem {
|
ColumnLayout {
|
||||||
text: model.display
|
Layout.fillWidth: true
|
||||||
icon.name: model.decoration
|
Kirigami.Heading {
|
||||||
onTriggered: webshortcutmodel.trigger(model.edit)
|
level: 3
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: author.displayName
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
text: message
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: Kirigami.Units.gridUnit * 24
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
|
onLinkActivated: RoomManager.openResource(link);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onObjectAdded: webshortcutmenu.insertItem(0, object)
|
|
||||||
}
|
}
|
||||||
QQC2.MenuSeparator {}
|
Kirigami.Separator {
|
||||||
QQC2.MenuItem {
|
Layout.fillWidth: true
|
||||||
text: i18n("Configure Web Shortcuts...")
|
}
|
||||||
icon.name: "configure"
|
RowLayout {
|
||||||
onTriggered: webshortcutmodel.configureWebShortcuts()
|
spacing: 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||||
|
Repeater {
|
||||||
|
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
|
||||||
|
delegate: QQC2.ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
contentItem: QQC2.Label {
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.family: "emoji"
|
||||||
|
text: modelData
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
currentRoom.toggleReaction(eventId, modelData)
|
||||||
|
loadRoot.item.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
Component {
|
Component {
|
||||||
id: mobileMenu
|
id: mobileMenu
|
||||||
@@ -130,7 +185,7 @@ Loader {
|
|||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
level: 3
|
level: 3
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: currentRoom.htmlSafeMemberName(author.id)
|
text: author.displayName
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
|
|||||||
@@ -5,26 +5,20 @@
|
|||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
import org.kde.syntaxhighlighting 1.0
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
Kirigami.OverlaySheet {
|
Kirigami.OverlaySheet {
|
||||||
|
|
||||||
property string sourceText
|
property string sourceText
|
||||||
|
|
||||||
title: i18n("Message Source")
|
header: Kirigami.Heading {
|
||||||
|
text: i18n("Message Source")
|
||||||
|
}
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
id: sourceTextArea
|
|
||||||
text: sourceText
|
text: sourceText
|
||||||
readOnly: true
|
readOnly: true
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
SyntaxHighlighter {
|
|
||||||
textEdit: sourceTextArea
|
|
||||||
repository: Repository
|
|
||||||
definition: "JSON"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
module NeoChat.Menu
|
module NeoChat.Menu
|
||||||
RoomListContextMenu 1.0 RoomListContextMenu.qml
|
RoomListContextMenu 1.0 RoomListContextMenu.qml
|
||||||
GlobalMenu 1.0 GlobalMenu.qml
|
|
||||||
EditMenu 1.0 EditMenu.qml
|
|
||||||
|
|||||||
@@ -11,99 +11,52 @@ import org.kde.kirigami 2.15 as Kirigami
|
|||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
|
|
||||||
Kirigami.Page {
|
Kirigami.ScrollablePage {
|
||||||
title: i18n("Accounts")
|
title: i18n("Accounts")
|
||||||
|
|
||||||
leftPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
ListView {
|
||||||
topPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
model: AccountListModel { }
|
||||||
bottomPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
delegate: Kirigami.SwipeListItem {
|
||||||
rightPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
actions.main: Kirigami.Action {
|
action: Kirigami.Action {
|
||||||
text: i18n("Add an account")
|
onTriggered: Controller.activeConnection = model.connection
|
||||||
icon.name: "list-add-user"
|
|
||||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/WelcomePage.qml")
|
|
||||||
visible: !pageSettingStack.wideMode
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: pageSettingStack
|
|
||||||
function onWideModeChanged() {
|
|
||||||
scroll.background.visible = pageSettingStack.wideMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Controls.ScrollView {
|
|
||||||
id: scroll
|
|
||||||
Component.onCompleted: background.visible = pageSettingStack.wideMode
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
Controls.ScrollBar.horizontal.policy: Controls.ScrollBar.AlwaysOff
|
|
||||||
ListView {
|
|
||||||
clip: true
|
|
||||||
model: AccountListModel { }
|
|
||||||
delegate: Kirigami.SwipeListItem {
|
|
||||||
leftPadding: 0
|
|
||||||
rightPadding: 0
|
|
||||||
Kirigami.BasicListItem {
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
text: model.user.displayName
|
|
||||||
labelItem.textFormat: Text.PlainText
|
|
||||||
subtitle: model.user.id
|
|
||||||
icon: model.user.avatarMediaId ? ("image://mxc/" + model.user.avatarMediaId) : "im-user"
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
Controller.activeConnection = model.connection
|
|
||||||
pageStack.layers.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
actions: [
|
|
||||||
Kirigami.Action {
|
|
||||||
text: i18n("Edit this account")
|
|
||||||
iconName: "document-edit"
|
|
||||||
onTriggered: {
|
|
||||||
userEditSheet.connection = model.connection
|
|
||||||
userEditSheet.open()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
text: i18n("Logout")
|
|
||||||
iconName: "im-kick-user"
|
|
||||||
onTriggered: {
|
|
||||||
Controller.logout(model.connection, true)
|
|
||||||
if(Controller.accountCount === 1)
|
|
||||||
pageStack.layers.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
Kirigami.BasicListItem {
|
||||||
}
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
footer: Column {
|
text: model.user.displayName
|
||||||
height: visible ? implicitHeight : 0
|
subtitle: model.user.id
|
||||||
Kirigami.ActionToolBar {
|
icon: model.user.avatarMediaId ? ("image://mxc/" + model.user.avatarMediaId) : "im-user"
|
||||||
alignment: Qt.AlignRight
|
|
||||||
visible: pageSettingStack.wideMode
|
onClicked: {
|
||||||
rightPadding: Kirigami.Units.smallSpacing
|
Controller.activeConnection = model.connection
|
||||||
width: parent.width
|
pageStack.layers.pop()
|
||||||
flat: false
|
}
|
||||||
|
}
|
||||||
actions: [
|
actions: [
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18n("Add an account")
|
text: i18n("Edit this account")
|
||||||
icon.name: "list-add-user"
|
iconName: "document-edit"
|
||||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/WelcomePage.qml")
|
onTriggered: {
|
||||||
|
userEditSheet.connection = model.connection
|
||||||
|
userEditSheet.open()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Logout")
|
||||||
|
iconName: "im-kick-user"
|
||||||
|
onTriggered: {
|
||||||
|
Controller.logout(model.connection, true)
|
||||||
|
if(Controller.accountCount === 1)
|
||||||
|
pageStack.layers.pop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: Kirigami.Units.smallSpacing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Controller
|
target: Controller
|
||||||
function onConnectionAdded() {
|
function onConnectionAdded() {
|
||||||
@@ -120,6 +73,12 @@ Kirigami.Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actions.main: Kirigami.Action {
|
||||||
|
text: i18n("Add an account")
|
||||||
|
iconName: "list-add-user"
|
||||||
|
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/WelcomePage.qml")
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: openFileDialog
|
id: openFileDialog
|
||||||
|
|
||||||
@@ -133,13 +92,16 @@ Kirigami.Page {
|
|||||||
|
|
||||||
property var connection
|
property var connection
|
||||||
|
|
||||||
title: i18n("Edit Account")
|
header: Kirigami.Heading {
|
||||||
|
text: i18n("Edit Account")
|
||||||
|
}
|
||||||
|
|
||||||
Kirigami.FormLayout {
|
Kirigami.FormLayout {
|
||||||
|
anchors.top: passwordsMessage.bottom
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Kirigami.Avatar {
|
Kirigami.Avatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
source: userEditSheet.connection && userEditSheet.connection.localUser.avatarMediaId ? ("image://mxc/" + userEditSheet.connection.localUser.avatarMediaId) : ""
|
source: userEditSheet.connection.localUser.avatarMediaId ? ("image://mxc/" + userEditSheet.connection.localUser.avatarMediaId) : ""
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
@@ -176,7 +138,7 @@ Kirigami.Page {
|
|||||||
}
|
}
|
||||||
Controls.TextField {
|
Controls.TextField {
|
||||||
id: name
|
id: name
|
||||||
text: userEditSheet.connection ? userEditSheet.connection.localUser.displayName : ""
|
text: userEditSheet.connection.localUser.displayName
|
||||||
Kirigami.FormData.label: i18n("Name:")
|
Kirigami.FormData.label: i18n("Name:")
|
||||||
}
|
}
|
||||||
Controls.TextField {
|
Controls.TextField {
|
||||||
100
imports/NeoChat/Page/DevicesPage.qml
Normal file
100
imports/NeoChat/Page/DevicesPage.qml
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as Controls
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
Kirigami.ScrollablePage {
|
||||||
|
title: i18n("Devices")
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
model: DevicesModel {
|
||||||
|
id: devices
|
||||||
|
}
|
||||||
|
delegate: Kirigami.SwipeListItem {
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
|
text: model.displayName
|
||||||
|
subtitle: model.id
|
||||||
|
icon: "network-connect"
|
||||||
|
}
|
||||||
|
actions: [
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Edit device name")
|
||||||
|
iconName: "document-edit"
|
||||||
|
onTriggered: {
|
||||||
|
renameSheet.index = model.index
|
||||||
|
renameSheet.name = model.displayName
|
||||||
|
renameSheet.open()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Logout device")
|
||||||
|
iconName: "edit-delete-remove"
|
||||||
|
onTriggered: {
|
||||||
|
passwordSheet.index = index
|
||||||
|
passwordSheet.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.OverlaySheet {
|
||||||
|
id: passwordSheet
|
||||||
|
|
||||||
|
property var index
|
||||||
|
|
||||||
|
header: Kirigami.Heading {
|
||||||
|
text: i18n("Remove device")
|
||||||
|
}
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
Controls.TextField {
|
||||||
|
id: passwordField
|
||||||
|
Kirigami.FormData.label: i18n("Password:")
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
}
|
||||||
|
Controls.Button {
|
||||||
|
text: i18n("Confirm")
|
||||||
|
onClicked: {
|
||||||
|
devices.logout(passwordSheet.index, passwordField.text)
|
||||||
|
passwordField.text = ""
|
||||||
|
passwordSheet.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.OverlaySheet {
|
||||||
|
id: renameSheet
|
||||||
|
property int index
|
||||||
|
property string name
|
||||||
|
|
||||||
|
header: Kirigami.Heading {
|
||||||
|
text: i18n("Edit device")
|
||||||
|
}
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
Controls.TextField {
|
||||||
|
id: nameField
|
||||||
|
Kirigami.FormData.label: i18n("Name:")
|
||||||
|
text: renameSheet.name
|
||||||
|
}
|
||||||
|
Controls.Button {
|
||||||
|
text: i18n("Save")
|
||||||
|
onClicked: {
|
||||||
|
devices.setName(renameSheet.index, nameField.text)
|
||||||
|
renameSheet.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,14 +21,14 @@ Kirigami.Page {
|
|||||||
title: i18n("Edit")
|
title: i18n("Edit")
|
||||||
leftPadding: 0
|
leftPadding: 0
|
||||||
rightPadding: 0
|
rightPadding: 0
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
|
|
||||||
function crop() {
|
function crop() {
|
||||||
const ratioX = editImage.paintedWidth / editImage.nativeWidth;
|
const ratioX = editImage.paintedWidth / editImage.nativeWidth;
|
||||||
const ratioY = editImage.paintedHeight / editImage.nativeHeight;
|
const ratioY = editImage.paintedHeight / editImage.nativeHeight;
|
||||||
rootEditorView.resizing = false
|
rootEditorView.resizing = false
|
||||||
imageDoc.crop(selectionTool.selectionX / ratioX, selectionTool.selectionY / ratioY, selectionTool.selectionWidth / ratioX, selectionTool.selectionHeight / ratioY);
|
imageDoc.crop(resizeRectangle.insideX / ratioX, resizeRectangle.insideY / ratioY, resizeRectangle.insideWidth / ratioX, resizeRectangle.insideHeight / ratioY);
|
||||||
}
|
}
|
||||||
|
|
||||||
actions {
|
actions {
|
||||||
@@ -58,11 +58,8 @@ Kirigami.Page {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
KQuickImageEditor.ImageItem {
|
contentItem: KQuickImageEditor.ImageItem {
|
||||||
id: editImage
|
id: editImage
|
||||||
// Assigning this to the contentItem and setting the padding causes weird positioning issues
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Kirigami.Units.gridUnit
|
|
||||||
fillMode: KQuickImageEditor.ImageItem.PreserveAspectFit
|
fillMode: KQuickImageEditor.ImageItem.PreserveAspectFit
|
||||||
image: imageDoc.image
|
image: imageDoc.image
|
||||||
|
|
||||||
@@ -79,41 +76,12 @@ Kirigami.Page {
|
|||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: StandardKey.SaveAs
|
sequence: StandardKey.SaveAs
|
||||||
onActivated: saveAsAction.trigger();
|
onActivated: saveAsAction.trigger();
|
||||||
}
|
} anchors.fill: parent
|
||||||
|
|
||||||
KQuickImageEditor.ImageDocument {
|
KQuickImageEditor.ImageDocument {
|
||||||
id: imageDoc
|
id: imageDoc
|
||||||
path: rootEditorView.imagePath
|
path: rootEditorView.imagePath
|
||||||
}
|
}
|
||||||
|
|
||||||
KQuickImageEditor.SelectionTool {
|
|
||||||
id: selectionTool
|
|
||||||
visible: rootEditorView.resizing
|
|
||||||
width: editImage.paintedWidth
|
|
||||||
height: editImage.paintedHeight
|
|
||||||
x: editImage.horizontalPadding
|
|
||||||
y: editImage.verticalPadding
|
|
||||||
KQuickImageEditor.CropBackground {
|
|
||||||
anchors.fill: parent
|
|
||||||
z: -1
|
|
||||||
insideX: selectionTool.selectionX
|
|
||||||
insideY: selectionTool.selectionY
|
|
||||||
insideWidth: selectionTool.selectionWidth
|
|
||||||
insideHeight: selectionTool.selectionHeight
|
|
||||||
}
|
|
||||||
Connections {
|
|
||||||
target: selectionTool.selectionArea
|
|
||||||
function onDoubleClicked() {
|
|
||||||
rootEditorView.crop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onImageChanged: {
|
|
||||||
selectionTool.selectionX = 0
|
|
||||||
selectionTool.selectionY = 0
|
|
||||||
selectionTool.selectionWidth = Qt.binding(() => selectionTool.width)
|
|
||||||
selectionTool.selectionHeight = Qt.binding(() => selectionTool.height)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header: QQC2.ToolBar {
|
header: QQC2.ToolBar {
|
||||||
@@ -177,4 +145,22 @@ Kirigami.Page {
|
|||||||
showCloseButton: true
|
showCloseButton: true
|
||||||
visible: false
|
visible: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KQuickImageEditor.ResizeRectangle {
|
||||||
|
id: resizeRectangle
|
||||||
|
|
||||||
|
visible: rootEditorView.resizing
|
||||||
|
|
||||||
|
width: editImage.paintedWidth
|
||||||
|
height: editImage.paintedHeight
|
||||||
|
x: editImage.horizontalPadding
|
||||||
|
y: editImage.verticalPadding
|
||||||
|
|
||||||
|
insideX: 100
|
||||||
|
insideY: 100
|
||||||
|
insideWidth: 100
|
||||||
|
insideHeight: 100
|
||||||
|
|
||||||
|
onAcceptSize: rootEditorView.crop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
import QtQuick.Controls 2.12 as QQC2
|
||||||
import org.kde.kirigami 2.12 as Kirigami
|
import org.kde.kirigami 2.12 as Kirigami
|
||||||
|
|
||||||
Kirigami.Page {
|
Kirigami.Page {
|
||||||
title: i18n("Loading")
|
title: i18n("Loading")
|
||||||
|
|
||||||
Kirigami.PlaceholderMessage {
|
QQC2.BusyIndicator {
|
||||||
id: loadingIndicator
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: i18n("Loading")
|
|
||||||
QQC2.BusyIndicator {
|
|
||||||
running: loadingIndicator.visible
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,34 +17,7 @@ import NeoChat.Menu 1.0
|
|||||||
Kirigami.ScrollablePage {
|
Kirigami.ScrollablePage {
|
||||||
id: page
|
id: page
|
||||||
|
|
||||||
title: i18n("Rooms")
|
|
||||||
|
|
||||||
property var enteredRoom
|
property var enteredRoom
|
||||||
property bool collapsedMode: Config.roomListPageWidth === applicationWindow().collapsedPageWidth && applicationWindow().shouldUseSidebars
|
|
||||||
|
|
||||||
onCollapsedModeChanged: if (collapsedMode) {
|
|
||||||
sortFilterRoomListModel.filterText = "";
|
|
||||||
if (page.contentItem && page.contentItem.flickableItem && page.contentItem.flickableItem.QQC2.ScrollBar.vertical) {
|
|
||||||
page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK: the scrollbar is created with a 0 timer, so we need to set the visible flag
|
|
||||||
// after it has been created
|
|
||||||
Timer {
|
|
||||||
running: true
|
|
||||||
interval: 200
|
|
||||||
onTriggered: page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = !collapsedMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: RoomManager
|
|
||||||
function onCurrentRoomChanged() {
|
|
||||||
itemSelection.setCurrentIndex(roomListModel.index(roomListModel.indexForRoom(RoomManager.currentRoom), 0), ItemSelectionModel.SelectCurrent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToNextRoom() {
|
function goToNextRoom() {
|
||||||
do {
|
do {
|
||||||
@@ -60,56 +33,21 @@ Kirigami.ScrollablePage {
|
|||||||
listView.currentItem.action.trigger();
|
listView.currentItem.action.trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
titleDelegate: collapsedMode ? empty : searchField
|
title: i18n("Rooms")
|
||||||
|
|
||||||
Component {
|
titleDelegate: Kirigami.SearchField {
|
||||||
id: empty
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
Item {}
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
onTextChanged: sortFilterRoomListModel.filterText = text
|
||||||
|
KeyNavigation.tab: listView
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: searchField
|
|
||||||
Kirigami.SearchField {
|
|
||||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
|
||||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
onTextChanged: sortFilterRoomListModel.filterText = text
|
|
||||||
KeyNavigation.tab: listView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
header: QQC2.ItemDelegate {
|
|
||||||
visible: page.collapsedMode
|
|
||||||
action: Kirigami.Action {
|
|
||||||
id: enterRoomAction
|
|
||||||
onTriggered: quickView.item.open();
|
|
||||||
}
|
|
||||||
topPadding: Kirigami.Units.largeSpacing
|
|
||||||
leftPadding: Kirigami.Units.largeSpacing
|
|
||||||
rightPadding: Kirigami.Units.largeSpacing
|
|
||||||
bottomPadding: Kirigami.Units.largeSpacing
|
|
||||||
width: visible ? page.width : 0
|
|
||||||
height: visible ? Kirigami.Units.gridUnit * 2 : 0
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 22
|
|
||||||
height: 22
|
|
||||||
source: "search"
|
|
||||||
}
|
|
||||||
Kirigami.Separator {
|
|
||||||
width: parent.width
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
clip: accountList.count > 1
|
|
||||||
|
|
||||||
Kirigami.PlaceholderMessage {
|
Kirigami.PlaceholderMessage {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -126,7 +64,6 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ItemSelectionModel {
|
ItemSelectionModel {
|
||||||
id: itemSelection
|
id: itemSelection
|
||||||
model: roomListModel
|
model: roomListModel
|
||||||
@@ -150,7 +87,6 @@ Kirigami.ScrollablePage {
|
|||||||
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
||||||
section.delegate: Kirigami.ListSectionHeader {
|
section.delegate: Kirigami.ListSectionHeader {
|
||||||
id: sectionHeader
|
id: sectionHeader
|
||||||
height: implicitHeight
|
|
||||||
action: Kirigami.Action {
|
action: Kirigami.Action {
|
||||||
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
||||||
}
|
}
|
||||||
@@ -161,14 +97,11 @@ Kirigami.ScrollablePage {
|
|||||||
level: 3
|
level: 3
|
||||||
text: roomListModel.categoryName(section)
|
text: roomListModel.categoryName(section)
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
elide: Text.ElideRight
|
|
||||||
visible: !page.collapsedMode
|
|
||||||
}
|
}
|
||||||
Kirigami.Icon {
|
Kirigami.Icon {
|
||||||
source: page.collapsedMode ? roomListModel.categoryIconName(section) : (roomListModel.categoryVisible(section) ? "go-up" : "go-down")
|
source: roomListModel.categoryVisible(section) ? "go-up" : "go-down"
|
||||||
implicitHeight: Kirigami.Units.iconSizes.small
|
implicitHeight: Kirigami.Units.iconSizes.small
|
||||||
implicitWidth: Kirigami.Units.iconSizes.small
|
implicitWidth: Kirigami.Units.iconSizes.small
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,124 +109,87 @@ Kirigami.ScrollablePage {
|
|||||||
reuseItems: true
|
reuseItems: true
|
||||||
currentIndex: -1 // we don't want any room highlighted by default
|
currentIndex: -1 // we don't want any room highlighted by default
|
||||||
|
|
||||||
delegate: page.collapsedMode ? collapsedModeListComponent : normalModeListComponent
|
delegate: Kirigami.BasicListItem {
|
||||||
|
id: roomListItem
|
||||||
|
visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
highlighted: listView.currentIndex === index
|
||||||
|
focus: true
|
||||||
|
icon: undefined
|
||||||
|
action: Kirigami.Action {
|
||||||
|
id: enterRoomAction
|
||||||
|
onTriggered: {
|
||||||
|
RoomManager.enterRoom(currentRoom);
|
||||||
|
itemSelection.setCurrentIndex(sortFilterRoomListModel.mapToSource(
|
||||||
|
sortFilterRoomListModel.index(index, 0)), ItemSelectionModel.SelectCurrent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Keys.onEnterPressed: enterRoomAction.trigger()
|
||||||
|
Keys.onReturnPressed: enterRoomAction.trigger()
|
||||||
|
bold: unreadCount > 0
|
||||||
|
label: name ?? ""
|
||||||
|
subtitle: {
|
||||||
|
let txt = (lastEvent.length === 0 ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm, " ")
|
||||||
|
if (txt.length) {
|
||||||
|
return txt
|
||||||
|
}
|
||||||
|
return " "
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
leading: Kirigami.Avatar {
|
||||||
id: collapsedModeListComponent
|
source: avatar ? "image://mxc/" + avatar : ""
|
||||||
|
name: model.name || i18n("No Name")
|
||||||
|
implicitWidth: visible ? height : 0
|
||||||
|
visible: Config.showAvatarInTimeline
|
||||||
|
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
}
|
||||||
|
|
||||||
QQC2.ItemDelegate {
|
trailing: RowLayout {
|
||||||
action: Kirigami.Action {
|
QQC2.Label {
|
||||||
id: enterRoomAction
|
text: notificationCount
|
||||||
onTriggered: {
|
visible: notificationCount > 0
|
||||||
RoomManager.enterRoom(currentRoom);
|
padding: Kirigami.Units.smallSpacing
|
||||||
|
color: highlightCount > 0 ? "white" : Kirigami.Theme.textColor
|
||||||
|
Layout.minimumWidth: height
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
background: Rectangle {
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||||
|
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
|
||||||
|
radius: height / 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keys.onEnterPressed: enterRoomAction.trigger()
|
QQC2.Button {
|
||||||
Keys.onReturnPressed: enterRoomAction.trigger()
|
id: configButton
|
||||||
topPadding: Kirigami.Units.largeSpacing
|
visible: roomListItem.hovered || Kirigami.Settings.isMobile
|
||||||
leftPadding: Kirigami.Units.largeSpacing
|
Accessible.name: i18n("Configure room")
|
||||||
rightPadding: Kirigami.Units.largeSpacing
|
|
||||||
bottomPadding: Kirigami.Units.largeSpacing
|
|
||||||
width: ListView.view.width
|
|
||||||
height: ListView.view.width
|
|
||||||
|
|
||||||
contentItem: Kirigami.Avatar {
|
action: Kirigami.Action {
|
||||||
source: avatar ? "image://mxc/" + avatar : ""
|
id: optionAction
|
||||||
name: model.name || i18n("No Name")
|
icon.name: "configure"
|
||||||
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
onTriggered: {
|
||||||
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
const menu = roomListContextMenu.createObject(page, {"room": currentRoom})
|
||||||
}
|
configButton.visible = true
|
||||||
|
configButton.down = true
|
||||||
QQC2.ToolTip {
|
menu.closed.connect(function() {
|
||||||
enabled: text.length !== 0
|
configButton.down = undefined
|
||||||
text: name ?? ""
|
configButton.visible = Qt.binding(function() { return roomListItem.hovered || Kirigami.Settings.isMobile })
|
||||||
|
})
|
||||||
|
menu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: roomListContextMenu
|
id: roomListContextMenu
|
||||||
RoomListContextMenu {}
|
RoomListContextMenu {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: normalModeListComponent
|
|
||||||
Kirigami.BasicListItem {
|
|
||||||
id: roomListItem
|
|
||||||
visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
|
|
||||||
topPadding: Kirigami.Units.largeSpacing
|
|
||||||
bottomPadding: Kirigami.Units.largeSpacing
|
|
||||||
highlighted: listView.currentIndex === index
|
|
||||||
focus: true
|
|
||||||
icon: undefined
|
|
||||||
action: Kirigami.Action {
|
|
||||||
id: enterRoomAction
|
|
||||||
onTriggered: {
|
|
||||||
RoomManager.enterRoom(currentRoom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Keys.onEnterPressed: enterRoomAction.trigger()
|
|
||||||
Keys.onReturnPressed: enterRoomAction.trigger()
|
|
||||||
bold: unreadCount > 0
|
|
||||||
label: name ?? ""
|
|
||||||
subtitle: {
|
|
||||||
let txt = (lastEvent.length === 0 ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm, " ")
|
|
||||||
if (txt.length) {
|
|
||||||
return txt
|
|
||||||
}
|
|
||||||
return " "
|
|
||||||
}
|
|
||||||
|
|
||||||
leading: Kirigami.Avatar {
|
|
||||||
source: avatar ? "image://mxc/" + avatar : ""
|
|
||||||
name: model.name || i18n("No Name")
|
|
||||||
implicitWidth: visible ? height : 0
|
|
||||||
visible: Config.showAvatarInTimeline
|
|
||||||
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
|
||||||
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
|
||||||
}
|
|
||||||
|
|
||||||
trailing: RowLayout {
|
|
||||||
QQC2.Label {
|
|
||||||
text: notificationCount
|
|
||||||
visible: notificationCount > 0
|
|
||||||
padding: Kirigami.Units.smallSpacing
|
|
||||||
color: highlightCount > 0 ? "white" : Kirigami.Theme.textColor
|
|
||||||
Layout.minimumWidth: height
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
background: Rectangle {
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
|
||||||
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
|
|
||||||
radius: height / 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.Button {
|
|
||||||
id: configButton
|
|
||||||
visible: roomListItem.hovered || Kirigami.Settings.isMobile
|
|
||||||
Accessible.name: i18n("Configure room")
|
|
||||||
|
|
||||||
action: Kirigami.Action {
|
|
||||||
id: optionAction
|
|
||||||
icon.name: "configure"
|
|
||||||
onTriggered: {
|
|
||||||
const menu = roomListContextMenu.createObject(page, {"room": currentRoom})
|
|
||||||
configButton.visible = true
|
|
||||||
configButton.down = true
|
|
||||||
menu.closed.connect(function() {
|
|
||||||
configButton.down = undefined
|
|
||||||
configButton.visible = Qt.binding(function() { return roomListItem.hovered || Kirigami.Settings.isMobile })
|
|
||||||
})
|
|
||||||
menu.popup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: QQC2.ToolBar {
|
footer: QQC2.ToolBar {
|
||||||
visible: accountList.count > 1 && !collapsedMode
|
visible: accountList.count > 1
|
||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
leftPadding: 0
|
leftPadding: 0
|
||||||
rightPadding: 0
|
rightPadding: 0
|
||||||
|
|||||||
@@ -26,18 +26,7 @@ Kirigami.ScrollablePage {
|
|||||||
/// Used to determine if scrolling to the bottom should mark the message as unread
|
/// Used to determine if scrolling to the bottom should mark the message as unread
|
||||||
property bool hasScrolledUpBefore: false;
|
property bool hasScrolledUpBefore: false;
|
||||||
|
|
||||||
title: currentRoom.htmlSafeDisplayName
|
title: currentRoom.displayName
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: RoomManager
|
|
||||||
function onCurrentRoomChanged() {
|
|
||||||
if(!RoomManager.currentRoom) {
|
|
||||||
if(pageStack.lastItem == page) {
|
|
||||||
pageStack.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
signal switchRoomUp()
|
signal switchRoomUp()
|
||||||
signal switchRoomDown()
|
signal switchRoomDown()
|
||||||
@@ -55,8 +44,8 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Controller.activeConnection
|
target: Controller.activeConnection
|
||||||
function onJoinedRoom(room, invited) {
|
function onJoinedRoom(room) {
|
||||||
if(page.currentRoom.id === invited.id) {
|
if(room.id === invitation.id) {
|
||||||
RoomManager.enterRoom(room);
|
RoomManager.enterRoom(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +53,7 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: actionsHandler
|
target: actionsHandler
|
||||||
function onShowMessage(messageType, message) {
|
onShowMessage: {
|
||||||
page.header.contentItem.text = message;
|
page.header.contentItem.text = message;
|
||||||
page.header.contentItem.type = messageType === ActionsHandler.Error ? Kirigami.MessageType.Error : Kirigami.MessageType.Information;
|
page.header.contentItem.type = messageType === ActionsHandler.Error ? Kirigami.MessageType.Error : Kirigami.MessageType.Information;
|
||||||
page.header.contentItem.visible = true;
|
page.header.contentItem.visible = true;
|
||||||
@@ -84,6 +73,8 @@ Kirigami.ScrollablePage {
|
|||||||
Kirigami.PlaceholderMessage {
|
Kirigami.PlaceholderMessage {
|
||||||
id: invitation
|
id: invitation
|
||||||
|
|
||||||
|
property var id
|
||||||
|
|
||||||
visible: currentRoom && currentRoom.isInvite
|
visible: currentRoom && currentRoom.isInvite
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: i18n("Accept this invitation?")
|
text: i18n("Accept this invitation?")
|
||||||
@@ -92,7 +83,7 @@ Kirigami.ScrollablePage {
|
|||||||
Layout.alignment : Qt.AlignHCenter
|
Layout.alignment : Qt.AlignHCenter
|
||||||
text: i18n("Reject")
|
text: i18n("Reject")
|
||||||
|
|
||||||
onClicked: RoomManager.leaveRoom(page.currentRoom);
|
onClicked: RoomManager.leave(page.currentRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
@@ -101,6 +92,8 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
currentRoom.acceptInvitation();
|
currentRoom.acceptInvitation();
|
||||||
|
invitation.id = currentRoom.id
|
||||||
|
currentRoom = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,9 +148,9 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: hoverActions
|
id: hoverActions
|
||||||
property var event: null
|
property var event
|
||||||
property bool showEdit: event && (event.author.id === Controller.activeConnection.localUserId && (event.eventType === "emote" || event.eventType === "message"))
|
property bool showEdit: event && (event.author.id === Controller.activeConnection.localUserId && (event.eventType === "emote" || event.eventType === "message"))
|
||||||
property var bubble: null
|
property var bubble
|
||||||
property var hovered: bubble && bubble.hovered
|
property var hovered: bubble && bubble.hovered
|
||||||
property var visibleDelayed: (hovered || hoverHandler.hovered) && !Kirigami.Settings.isMobile
|
property var visibleDelayed: (hovered || hoverHandler.hovered) && !Kirigami.Settings.isMobile
|
||||||
onVisibleDelayedChanged: if (visibleDelayed) {
|
onVisibleDelayedChanged: if (visibleDelayed) {
|
||||||
@@ -172,8 +165,8 @@ Kirigami.ScrollablePage {
|
|||||||
interval: 200
|
interval: 200
|
||||||
onTriggered: hoverActions.visible = hoverActions.visibleDelayed;
|
onTriggered: hoverActions.visible = hoverActions.visibleDelayed;
|
||||||
}
|
}
|
||||||
x: bubble ? (bubble.x + Kirigami.Units.largeSpacing + Math.max(bubble.width - childWidth, 0) - (Config.compactLayout ? Kirigami.Units.gridUnit * 3 : 0)) : 0
|
x: bubble ? (bubble.x + Kirigami.Units.largeSpacing + Math.max(bubble.width - childWidth, 0)) : 0
|
||||||
y: bubble ? bubble.mapToItem(parent, 0, 0).y - hoverActions.childHeight + Kirigami.Units.smallSpacing: 0;
|
y: bubble ? bubble.mapToItem(page, 0, -Kirigami.Units.largeSpacing - hoverActions.childHeight * 1.5).y : 0
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
property var updateFunction
|
property var updateFunction
|
||||||
@@ -229,12 +222,13 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: messageListView
|
id: messageListView
|
||||||
|
pixelAligned: true
|
||||||
visible: !invitation.visible
|
visible: !invitation.visible
|
||||||
|
|
||||||
readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1
|
readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1
|
||||||
readonly property bool isLoaded: page.width * page.height > 10
|
readonly property bool isLoaded: page.width * page.height > 10
|
||||||
|
|
||||||
spacing: Config.compactLayout ? 1 : Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
reuseItems: true
|
reuseItems: true
|
||||||
|
|
||||||
verticalLayoutDirection: ListView.BottomToTop
|
verticalLayoutDirection: ListView.BottomToTop
|
||||||
@@ -266,7 +260,7 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAtYEndChanged: if (atYEnd && hasScrolledUpBefore) {
|
onAtYEndChanged: if (atYEnd && hasScrolledUpBefore) {
|
||||||
if (QQC2.ApplicationWindow.window && (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden)) {
|
if (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden) {
|
||||||
currentRoom.markAllMessagesAsRead();
|
currentRoom.markAllMessagesAsRead();
|
||||||
}
|
}
|
||||||
hasScrolledUpBefore = false;
|
hasScrolledUpBefore = false;
|
||||||
@@ -371,8 +365,19 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
innerObject: TextDelegate {
|
innerObject: TextDelegate {
|
||||||
isEmote: true
|
isEmote: true
|
||||||
|
Layout.fillWidth: !Config.showAvatarInTimeline
|
||||||
Layout.maximumWidth: emoteContainer.bubbleMaxWidth
|
Layout.maximumWidth: emoteContainer.bubbleMaxWidth
|
||||||
onRequestOpenMessageContext: openMessageContext(model, parent.selectedText)
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||||
|
Layout.bottomMargin: Kirigami.Units.largeSpacing * 2
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onTapped: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
|
||||||
|
}
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,8 +392,19 @@ Kirigami.ScrollablePage {
|
|||||||
hoverComponent: hoverActions
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
innerObject: TextDelegate {
|
innerObject: TextDelegate {
|
||||||
|
Layout.fillWidth: !Config.showAvatarInTimeline
|
||||||
Layout.maximumWidth: messageContainer.bubbleMaxWidth
|
Layout.maximumWidth: messageContainer.bubbleMaxWidth
|
||||||
onRequestOpenMessageContext: openMessageContext(model, parent.selectedText)
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onTapped: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
|
||||||
|
}
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -402,9 +418,11 @@ Kirigami.ScrollablePage {
|
|||||||
onReplyClicked: goToEvent(eventID)
|
onReplyClicked: goToEvent(eventID)
|
||||||
|
|
||||||
innerObject: TextDelegate {
|
innerObject: TextDelegate {
|
||||||
Layout.fillWidth: !Config.compactLayout
|
Layout.fillWidth: !Config.showAvatarInTimeline
|
||||||
hasContextMenu: false
|
|
||||||
Layout.maximumWidth: noticeContainer.bubbleMaxWidth
|
Layout.maximumWidth: noticeContainer.bubbleMaxWidth
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||||
|
Layout.bottomMargin: Kirigami.Units.largeSpacing * 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,23 +439,18 @@ Kirigami.ScrollablePage {
|
|||||||
innerObject: ImageDelegate {
|
innerObject: ImageDelegate {
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 15
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 15
|
||||||
Layout.maximumWidth: imageContainer.bubbleMaxWidth
|
Layout.maximumWidth: imageContainer.bubbleMaxWidth
|
||||||
|
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.preferredHeight: info.h / info.w * width
|
Layout.preferredHeight: info.h / info.w * width
|
||||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 20
|
Layout.maximumHeight: Kirigami.Units.gridUnit * 20
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.RightButton
|
acceptedButtons: Qt.RightButton
|
||||||
onTapped: openFileContext(model, parent)
|
onTapped: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
||||||
}
|
}
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
onLongPressed: openFileContext(model, parent)
|
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
||||||
onTapped: {
|
onTapped: {
|
||||||
fullScreenImage.createObject(parent, {
|
fullScreenImage.createObject(parent, {"filename": eventId, "localPath": currentRoom.urlToDownload(eventId)}).showFullScreen()
|
||||||
filename: eventId,
|
|
||||||
localPath: currentRoom.urlToDownload(eventId),
|
|
||||||
blurhash: model.content.info["xyz.amorgan.blurhash"],
|
|
||||||
imageWidth: content.info.w,
|
|
||||||
imageHeight: content.info.h
|
|
||||||
}).showFullScreen();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,11 +489,11 @@ Kirigami.ScrollablePage {
|
|||||||
Layout.maximumWidth: audioContainer.bubbleMaxWidth
|
Layout.maximumWidth: audioContainer.bubbleMaxWidth
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.RightButton
|
acceptedButtons: Qt.RightButton
|
||||||
onTapped: openFileContext(model, parent)
|
onTapped: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
||||||
}
|
}
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
onLongPressed: openFileContext(model, parent)
|
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -504,11 +517,11 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.RightButton
|
acceptedButtons: Qt.RightButton
|
||||||
onTapped: openFileContext(model, parent)
|
onTapped: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
||||||
}
|
}
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
onLongPressed: openFileContext(model, parent)
|
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -527,32 +540,16 @@ Kirigami.ScrollablePage {
|
|||||||
Layout.maximumWidth: fileContainer.bubbleMaxWidth
|
Layout.maximumWidth: fileContainer.bubbleMaxWidth
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.RightButton
|
acceptedButtons: Qt.RightButton
|
||||||
onTapped: openFileContext(model, parent)
|
onTapped: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
||||||
}
|
}
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
onLongPressed: openFileContext(model, parent)
|
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "encrypted"
|
|
||||||
delegate: TimelineContainer {
|
|
||||||
id: encryptedContainer
|
|
||||||
width: messageListView.width
|
|
||||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
|
||||||
|
|
||||||
innerObject: EncryptedDelegate {
|
|
||||||
Layout.fillWidth: Config.compactLayout
|
|
||||||
Layout.maximumWidth: encryptedContainer.bubbleMaxWidth
|
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "readMarker"
|
roleValue: "readMarker"
|
||||||
delegate: QQC2.ItemDelegate {
|
delegate: QQC2.ItemDelegate {
|
||||||
@@ -572,7 +569,7 @@ Kirigami.ScrollablePage {
|
|||||||
shadow.size: Kirigami.Units.smallSpacing
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
||||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
border.width: 1
|
border.width: Kirigami.Units.devicePixelRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -871,28 +868,27 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Open message context dialog for file and videos
|
/// Open message context dialog for file and videos
|
||||||
function openFileContext(event, file) {
|
function openFileContext(author, message, eventId, source, progressInfo, file) {
|
||||||
const contextMenu = fileDelegateContextMenu.createObject(page, {
|
const contextMenu = fileDelegateContextMenu.createObject(page, {
|
||||||
author: event.author,
|
author: author,
|
||||||
message: event.message,
|
message: message,
|
||||||
eventId: event.eventId,
|
eventId: eventId,
|
||||||
source: event.toolTip,
|
source: source,
|
||||||
file: file,
|
file: file,
|
||||||
progressInfo: event.progressInfo,
|
progressInfo: progressInfo,
|
||||||
});
|
});
|
||||||
contextMenu.open();
|
contextMenu.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open context menu for normal message
|
/// Open context menu for normal message
|
||||||
function openMessageContext(event, selectedText) {
|
function openMessageContext(author, message, eventId, source, eventType, formattedBody) {
|
||||||
const contextMenu = messageDelegateContextMenu.createObject(page, {
|
const contextMenu = messageDelegateContextMenu.createObject(page, {
|
||||||
selectedText: selectedText,
|
author: author,
|
||||||
author: event.author,
|
message: message,
|
||||||
message: event.message,
|
eventId: eventId,
|
||||||
eventId: event.eventId,
|
formattedBody: formattedBody,
|
||||||
formattedBody: event.formattedBody,
|
source: source,
|
||||||
source: event.toolTip,
|
eventType: eventType
|
||||||
eventType: event.eventType
|
|
||||||
});
|
});
|
||||||
contextMenu.open();
|
contextMenu.open();
|
||||||
}
|
}
|
||||||
|
|||||||
88
imports/NeoChat/Page/SettingsPage.qml
Normal file
88
imports/NeoChat/Page/SettingsPage.qml
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
Kirigami.ScrollablePage {
|
||||||
|
title: i18n("Settings")
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
QQC2.CheckBox {
|
||||||
|
Kirigami.FormData.label: i18n("General settings:")
|
||||||
|
text: i18n("Close to system tray")
|
||||||
|
checked: Config.systemTray
|
||||||
|
visible: Controller.supportSystemTray
|
||||||
|
onToggled: {
|
||||||
|
Config.systemTray = checked
|
||||||
|
Config.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.CheckBox {
|
||||||
|
// TODO: When there are enough notification and timeline event
|
||||||
|
// settings, make 2 separate groups with FormData labels.
|
||||||
|
Kirigami.FormData.label: i18n("Notifications and events:")
|
||||||
|
text: i18n("Show notifications")
|
||||||
|
checked: Config.showNotifications
|
||||||
|
onToggled: {
|
||||||
|
Config.showNotifications = checked
|
||||||
|
Config.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.CheckBox {
|
||||||
|
text: i18n("Show leave and join events")
|
||||||
|
checked: Config.showLeaveJoinEvent
|
||||||
|
onToggled: {
|
||||||
|
Config.showLeaveJoinEvent = checked
|
||||||
|
Config.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.RadioButton {
|
||||||
|
Kirigami.FormData.label: i18n("Rooms and private chats:")
|
||||||
|
text: i18n("Separated")
|
||||||
|
checked: !Config.mergeRoomList
|
||||||
|
onToggled: {
|
||||||
|
Config.mergeRoomList = false
|
||||||
|
Config.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.RadioButton {
|
||||||
|
text: i18n("Intermixed")
|
||||||
|
checked: Config.mergeRoomList
|
||||||
|
onToggled: {
|
||||||
|
Config.mergeRoomList = true
|
||||||
|
Config.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.CheckBox {
|
||||||
|
Kirigami.FormData.label: i18n("Timeline:")
|
||||||
|
text: i18n("Show User Avatar")
|
||||||
|
checked: Config.showAvatarInTimeline
|
||||||
|
onToggled: {
|
||||||
|
Config.showAvatarInTimeline = checked
|
||||||
|
Config.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.CheckBox {
|
||||||
|
text: i18n("Show Fancy Effects")
|
||||||
|
checked: Config.showFancyEffects
|
||||||
|
onToggled: {
|
||||||
|
Config.showFancyEffects = checked
|
||||||
|
Config.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.CheckBox {
|
||||||
|
text: i18n("Use s/text/replacement syntax to edit your last message")
|
||||||
|
checked: Config.allowQuickEdit
|
||||||
|
onToggled: {
|
||||||
|
Config.allowQuickEdit = checked
|
||||||
|
Config.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: LoginHelper
|
target: LoginHelper
|
||||||
function onErrorOccured(message) {
|
onErrorOccured: {
|
||||||
headerMessage.text = message;
|
headerMessage.text = message;
|
||||||
headerMessage.visible = true;
|
headerMessage.visible = true;
|
||||||
headerMessage.type = Kirigami.MessageType.Error;
|
headerMessage.type = Kirigami.MessageType.Error;
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ RoomPage 1.0 RoomPage.qml
|
|||||||
RoomWindow 1.0 RoomWindow.qml
|
RoomWindow 1.0 RoomWindow.qml
|
||||||
JoinRoomPage 1.0 JoinRoomPage.qml
|
JoinRoomPage 1.0 JoinRoomPage.qml
|
||||||
InviteUserPage 1.0 InviteUserPage.qml
|
InviteUserPage 1.0 InviteUserPage.qml
|
||||||
|
SettingsPage 1.0 SettingsPage.qml
|
||||||
ImageEditorPage 1.0 ImageEditorPage.qml
|
ImageEditorPage 1.0 ImageEditorPage.qml
|
||||||
|
|
||||||
|
|||||||
@@ -18,46 +18,6 @@ Kirigami.OverlayDrawer {
|
|||||||
id: roomDrawer
|
id: roomDrawer
|
||||||
readonly property var room: RoomManager.currentRoom
|
readonly property var room: RoomManager.currentRoom
|
||||||
|
|
||||||
width: modal ? undefined : actualWidth
|
|
||||||
readonly property int minWidth: Kirigami.Units.gridUnit * 15
|
|
||||||
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
|
|
||||||
readonly property int defaultWidth: Kirigami.Units.gridUnit * 20
|
|
||||||
property int actualWidth: {
|
|
||||||
if (Config.roomDrawerWidth === -1) {
|
|
||||||
return Kirigami.Units.gridUnit * 20;
|
|
||||||
} else {
|
|
||||||
return Config.roomDrawerWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.right: undefined
|
|
||||||
width: 2
|
|
||||||
z: 500
|
|
||||||
cursorShape: !Kirigami.Settings.isMobile ? Qt.SplitHCursor : undefined
|
|
||||||
enabled: true
|
|
||||||
visible: true
|
|
||||||
onPressed: _lastX = mapToGlobal(mouseX, mouseY).x
|
|
||||||
onReleased: {
|
|
||||||
Config.roomDrawerWidth = roomDrawer.actualWidth;
|
|
||||||
Config.save();
|
|
||||||
}
|
|
||||||
property real _lastX: -1
|
|
||||||
|
|
||||||
onPositionChanged: {
|
|
||||||
if (_lastX === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Qt.application.layoutDirection === Qt.RightToLeft) {
|
|
||||||
roomDrawer.actualWidth = Math.min(roomDrawer.maxWidth, Math.max(roomDrawer.minWidth, Config.roomDrawerWidth - _lastX + mapToGlobal(mouseX, mouseY).x))
|
|
||||||
} else {
|
|
||||||
roomDrawer.actualWidth = Math.min(roomDrawer.maxWidth, Math.max(roomDrawer.minWidth, Config.roomDrawerWidth + _lastX - mapToGlobal(mouseX, mouseY).x))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
|
edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
|
||||||
@@ -70,6 +30,7 @@ Kirigami.OverlayDrawer {
|
|||||||
active: roomDrawer.drawerOpen
|
active: roomDrawer.drawerOpen
|
||||||
sourceComponent: ColumnLayout {
|
sourceComponent: ColumnLayout {
|
||||||
id: columnLayout
|
id: columnLayout
|
||||||
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
Kirigami.AbstractApplicationHeader {
|
Kirigami.AbstractApplicationHeader {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -86,7 +47,7 @@ Kirigami.OverlayDrawer {
|
|||||||
icon.name: "list-add-user"
|
icon.name: "list-add-user"
|
||||||
text: i18n("Invite")
|
text: i18n("Invite")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
applicationWindow().pageStack.layers.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {room: room})
|
applicationWindow().pageStack.layers.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {"room": room})
|
||||||
roomDrawer.close();
|
roomDrawer.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +69,12 @@ Kirigami.OverlayDrawer {
|
|||||||
ToolButton {
|
ToolButton {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
icon.name: 'settings-configure'
|
icon.name: 'settings-configure'
|
||||||
onClicked: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
|
onClicked: {
|
||||||
|
roomSettingDialog.createObject(ApplicationWindow.overlay, {"room": room}).open()
|
||||||
|
if (!wideScreen) {
|
||||||
|
roomDrawer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ToolTip {
|
ToolTip {
|
||||||
text: i18n("Room settings")
|
text: i18n("Room settings")
|
||||||
@@ -117,61 +83,67 @@ Kirigami.OverlayDrawer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
Control {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: Kirigami.Units.largeSpacing
|
padding: Kirigami.Units.largeSpacing
|
||||||
Kirigami.Heading {
|
contentItem: ColumnLayout {
|
||||||
text: i18n("Room information")
|
id: infoLayout
|
||||||
level: 3
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: Kirigami.Units.largeSpacing
|
Kirigami.Heading {
|
||||||
|
text: i18n("Room information")
|
||||||
spacing: Kirigami.Units.largeSpacing
|
level: 3
|
||||||
|
|
||||||
Kirigami.Avatar {
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3.5
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3.5
|
|
||||||
|
|
||||||
name: room ? room.name : i18n("No name")
|
|
||||||
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
|
|
||||||
}
|
}
|
||||||
|
RowLayout {
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Kirigami.Heading {
|
spacing: Kirigami.Units.largeSpacing
|
||||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 9
|
|
||||||
Layout.fillWidth: true
|
Kirigami.Avatar {
|
||||||
level: 1
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 3.5
|
||||||
font.bold: true
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 3.5
|
||||||
wrapMode: Label.Wrap
|
|
||||||
text: room ? room.displayName : i18n("No name")
|
name: room ? room.name : i18n("No name")
|
||||||
|
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||||
}
|
}
|
||||||
Label {
|
|
||||||
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: room && room.canonicalAlias ? room.canonicalAlias : i18n("No Canonical Alias")
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Kirigami.Heading {
|
||||||
|
Layout.maximumWidth: Kirigami.Units.gridUnit * 9
|
||||||
|
Layout.fillWidth: true
|
||||||
|
level: 1
|
||||||
|
font.bold: true
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
text: room ? room.displayName : i18n("No name")
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: room && room.canonicalAlias ? room.canonicalAlias : i18n("No Canonical Alias")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
Layout.fillWidth: true
|
Layout.maximumWidth: Kirigami.Units.gridUnit * 13
|
||||||
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 13
|
||||||
readonly property var replaceLinks: /\(https:\/\/[^ ]*\)/
|
Layout.fillWidth: true
|
||||||
textFormat: TextEdit.MarkdownText
|
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||||
wrapMode: Text.WordWrap
|
readonly property var replaceLinks: /\(https:\/\/[^ ]*\)/
|
||||||
selectByMouse: true
|
textFormat: TextEdit.MarkdownText
|
||||||
color: Kirigami.Theme.textColor
|
wrapMode: Text.WordWrap
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
selectByMouse: true
|
||||||
readOnly: true
|
color: Kirigami.Theme.textColor
|
||||||
MouseArea {
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
anchors.fill: parent
|
readOnly: true
|
||||||
acceptedButtons: Qt.NoButton
|
MouseArea {
|
||||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +153,7 @@ Kirigami.OverlayDrawer {
|
|||||||
activeFocusOnTab: false
|
activeFocusOnTab: false
|
||||||
Label {
|
Label {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
text: room ? i18np("%1 Member", "%1 Members", room.joinedCount) : i18n("No Member Count")
|
text: room ? i18np("%1 Member", "%1 Members", room.totalMemberCount) : i18n("No Member Count")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,11 +161,6 @@ Kirigami.OverlayDrawer {
|
|||||||
padding: Kirigami.Units.smallSpacing
|
padding: Kirigami.Units.smallSpacing
|
||||||
implicitWidth: parent.width
|
implicitWidth: parent.width
|
||||||
z: 2
|
z: 2
|
||||||
background: Rectangle {
|
|
||||||
color: Kirigami.Theme.backgroundColor
|
|
||||||
Kirigami.Theme.inherit: false
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
|
||||||
}
|
|
||||||
contentItem: Kirigami.SearchField {
|
contentItem: Kirigami.SearchField {
|
||||||
id: userListSearchField
|
id: userListSearchField
|
||||||
onAccepted: sortedMessageEventModel.filterString = text;
|
onAccepted: sortedMessageEventModel.filterString = text;
|
||||||
@@ -220,7 +187,6 @@ Kirigami.OverlayDrawer {
|
|||||||
|
|
||||||
sortRole: "perm"
|
sortRole: "perm"
|
||||||
filterRole: "name"
|
filterRole: "name"
|
||||||
filterCaseSensitivity: Qt.CaseInsensitive
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: Kirigami.AbstractListItem {
|
delegate: Kirigami.AbstractListItem {
|
||||||
@@ -232,7 +198,7 @@ Kirigami.OverlayDrawer {
|
|||||||
Kirigami.Avatar {
|
Kirigami.Avatar {
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
Layout.preferredWidth: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
Layout.preferredHeight: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
visible: Config.showAvatarInRoomDrawer
|
visible: Config.showAvatarInTimeline
|
||||||
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
source: avatar ? ("image://mxc/" + avatar) : ""
|
source: avatar ? ("image://mxc/" + avatar) : ""
|
||||||
@@ -288,6 +254,12 @@ Kirigami.OverlayDrawer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: roomSettingDialog
|
||||||
|
|
||||||
|
RoomSettingsDialog {}
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: userDetailDialog
|
id: userDetailDialog
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import org.kde.kirigami 2.18 as Kirigami
|
|
||||||
import QtQuick.Controls 2.15 as Controls
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
|
|
||||||
Kirigami.CategorizedSettings {
|
|
||||||
id: root
|
|
||||||
required property var room
|
|
||||||
objectName: "settingsPage"
|
|
||||||
actions: [
|
|
||||||
Kirigami.SettingAction {
|
|
||||||
text: i18n("General")
|
|
||||||
icon.name: "settings-configure"
|
|
||||||
page: Qt.resolvedUrl("General.qml")
|
|
||||||
initialProperties: {
|
|
||||||
return {
|
|
||||||
room: root.room
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Kirigami.SettingAction {
|
|
||||||
text: i18n("Security")
|
|
||||||
icon.name: "security-low"
|
|
||||||
page: Qt.resolvedUrl("Security.qml")
|
|
||||||
initialProperties: {
|
|
||||||
return {
|
|
||||||
room: root.room
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
|
||||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
import NeoChat.Component 1.0
|
|
||||||
import NeoChat.Dialog 1.0
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property var room
|
|
||||||
|
|
||||||
readonly property bool canChangeAvatar: room.canSendState("m.room.avatar")
|
|
||||||
readonly property bool canChangeName: room.canSendState("m.room.name")
|
|
||||||
readonly property bool canChangeTopic: room.canSendState("m.room.topic")
|
|
||||||
readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias")
|
|
||||||
|
|
||||||
title: i18n('General')
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Kirigami.FormLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
Kirigami.Avatar {
|
|
||||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
name: room.name
|
|
||||||
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
|
|
||||||
|
|
||||||
RoundButton {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
height: Kirigami.Units.gridUnits
|
|
||||||
width: Kirigami.Units.gridUnits
|
|
||||||
icon.name: 'cloud-upload'
|
|
||||||
Accessible.name: i18n("Update avatar")
|
|
||||||
enabled: canChangeAvatar
|
|
||||||
onClicked: {
|
|
||||||
const fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
|
||||||
|
|
||||||
fileDialog.chosen.connect(function(path) {
|
|
||||||
if (!path) return
|
|
||||||
|
|
||||||
room.changeAvatar(path)
|
|
||||||
})
|
|
||||||
|
|
||||||
fileDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TextField {
|
|
||||||
id: roomNameField
|
|
||||||
text: room.name
|
|
||||||
Kirigami.FormData.label: i18n("Room Name:")
|
|
||||||
enabled: canChangeName
|
|
||||||
}
|
|
||||||
|
|
||||||
TextArea {
|
|
||||||
id: roomTopicField
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: room.topic
|
|
||||||
Kirigami.FormData.label: i18n("Room topic:")
|
|
||||||
enabled: canChangeTopic
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Kirigami.Separator {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
visible: canonicalAliasComboBox.visible || altAlias.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
id: canonicalAliasComboBox
|
|
||||||
visible: room.aliases && room.aliases.length
|
|
||||||
Kirigami.FormData.label: i18n("Canonical Alias:")
|
|
||||||
popup.z: 999; // HACK This is an absolute hack, but combos inside OverlaySheets have their popups show up underneath, because of fun z ordering stuff
|
|
||||||
|
|
||||||
enabled: canChangeCanonicalAlias
|
|
||||||
|
|
||||||
model: room.aliases
|
|
||||||
|
|
||||||
currentIndex: room.aliases.indexOf(room.canonicalAlias)
|
|
||||||
onCurrentIndexChanged: {
|
|
||||||
if (room.canonicalAlias != room.aliases[currentIndex]) {
|
|
||||||
room.setCanonicalAlias(room.aliases[currentIndex])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: altAlias
|
|
||||||
Kirigami.FormData.label: i18n("Other Aliases:")
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
visible: room.altAliases && room.altAliases.length
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: room.altAliases
|
|
||||||
|
|
||||||
delegate: RowLayout {
|
|
||||||
Layout.maximumWidth: parent.width
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: modelData
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolButton {
|
|
||||||
icon.name: ""
|
|
||||||
onClicked: room.removeLocalAlias(modelData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Separator {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
visible: next.visible || prev.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
Control {
|
|
||||||
id: next
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
visible: room.predecessorId && room.connection.room(room.predecessorId)
|
|
||||||
|
|
||||||
padding: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
contentItem: Kirigami.InlineMessage {
|
|
||||||
text: i18n("This room continues another conversation.")
|
|
||||||
actions: Kirigami.Action {
|
|
||||||
text: i18n("See older messages...")
|
|
||||||
onTriggered: {
|
|
||||||
roomListForm.enteredRoom = Controller.activeConnection.room(room.predecessorId)
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Control {
|
|
||||||
id: prev
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
visible: room.successorId && room.connection.room(room.successorId)
|
|
||||||
|
|
||||||
padding: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
contentItem: Kirigami.InlineMessage {
|
|
||||||
text: i18n("This room has been replaced.")
|
|
||||||
actions: Kirigami.Action {
|
|
||||||
text: i18n("See new room...")
|
|
||||||
onTriggered: {
|
|
||||||
roomListForm.enteredRoom = Controller.activeConnection.room(room.successorId)
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: openFileDialog
|
|
||||||
|
|
||||||
OpenFileDialog {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
footer: ToolBar {
|
|
||||||
contentItem: RowLayout {
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
enabled: room.name !== roomNameField.text || room.topic !== roomTopicField.text
|
|
||||||
text: i18n("Apply")
|
|
||||||
onClicked: {
|
|
||||||
if (room.name != roomNameField.text) {
|
|
||||||
room.setName(roomNameField.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (room.topic != roomTopicField.text) {
|
|
||||||
room.setTopic(roomTopicField.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
|
||||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
import NeoChat.Component 1.0
|
|
||||||
import NeoChat.Dialog 1.0
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property var room
|
|
||||||
|
|
||||||
title: i18n('Security')
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Kirigami.FormLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
text: i18nc("@option:check", "Private (invite only)")
|
|
||||||
Kirigami.FormData.label: i18nc("@option:check", "Access:")
|
|
||||||
checked: room.joinRule === "invite"
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: i18n("Only invited people can join.")
|
|
||||||
font: Kirigami.Theme.smallFont
|
|
||||||
}
|
|
||||||
CheckBox {
|
|
||||||
text: i18nc("@option:check", "Space members")
|
|
||||||
checked: room.joinRule === "restricted"
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: i18n("Anyone in a space can find and join.")
|
|
||||||
font: Kirigami.Theme.smallFont
|
|
||||||
}
|
|
||||||
CheckBox {
|
|
||||||
text: i18nc("@option:check", "Public")
|
|
||||||
checked: room.joinRule === "public"
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: i18nc("@option:check", "Anyone can find and join.") + room.joinRule
|
|
||||||
font: Kirigami.Theme.smallFont
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
footer: ToolBar {
|
|
||||||
contentItem: RowLayout {
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
enabled: false
|
|
||||||
text: i18n("Apply")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
|
||||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
Kirigami.AboutPage {
|
|
||||||
title: i18nc('@title:window', 'About NeoChat')
|
|
||||||
aboutData: Controller.aboutData
|
|
||||||
}
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
|
||||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15 as QQC2
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
import NeoChat.Settings 1.0
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
|
||||||
title: i18nc('@title:window', 'Appearance')
|
|
||||||
ColumnLayout {
|
|
||||||
RowLayout {
|
|
||||||
Layout.alignment: Qt.AlignCenter
|
|
||||||
spacing: Kirigami.Units.gridUnit * 2
|
|
||||||
QQC2.ButtonGroup { id: themeGroup }
|
|
||||||
ThemeRadioButton {
|
|
||||||
innerObject: [
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Kirigami.Avatar {
|
|
||||||
color: "#4a5bcc"
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
visible: Config.showAvatarInTimeline
|
|
||||||
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
|
|
||||||
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
|
|
||||||
}
|
|
||||||
QQC2.Control {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
font.weight: Font.Bold
|
|
||||||
font.pixelSize: 7
|
|
||||||
text: "Paul Müller"
|
|
||||||
color: "#4a5bcc"
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta mauris, quis finibus sem suscipit tincidunt."
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
font.pixelSize: 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
background: Kirigami.ShadowedRectangle {
|
|
||||||
color: Kirigami.Theme.backgroundColor
|
|
||||||
radius: Kirigami.Units.smallSpacing
|
|
||||||
shadow.size: Kirigami.Units.smallSpacing
|
|
||||||
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
|
||||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
|
||||||
border.width: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Kirigami.Avatar {
|
|
||||||
color: "#9f244b"
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
visible: Config.showAvatarInTimeline
|
|
||||||
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
|
|
||||||
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
|
|
||||||
}
|
|
||||||
QQC2.Control {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
font.weight: Font.Bold
|
|
||||||
font.pixelSize: 7
|
|
||||||
text: "Jean Paul"
|
|
||||||
color: "#9f244b"
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta , quis sem suscipit tincidunt."
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
font.pixelSize: 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
background: Kirigami.ShadowedRectangle {
|
|
||||||
color: Kirigami.Theme.backgroundColor
|
|
||||||
radius: Kirigami.Units.smallSpacing
|
|
||||||
shadow.size: Kirigami.Units.smallSpacing
|
|
||||||
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
|
||||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
|
||||||
border.width: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
text: i18n("Bubbles")
|
|
||||||
checked: !Config.compactLayout
|
|
||||||
QQC2.ButtonGroup.group: themeGroup
|
|
||||||
enabled: !Config.isCompactLayoutImmutable
|
|
||||||
|
|
||||||
onToggled: {
|
|
||||||
Config.compactLayout = !checked;
|
|
||||||
Config.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ThemeRadioButton {
|
|
||||||
innerObject: [
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Kirigami.Avatar {
|
|
||||||
color: "#4a5bcc"
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
visible: Config.showAvatarInTimeline
|
|
||||||
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
|
|
||||||
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
|
|
||||||
}
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
font.weight: Font.Bold
|
|
||||||
font.pixelSize: 7
|
|
||||||
text: "Paul Müller"
|
|
||||||
color: "#4a5bcc"
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: "Lorem ipsum dolor sit amet, consectetur elit. Vivamus facilisis porta mauris, finibus sem suscipit tincidunt."
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
font.pixelSize: 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Kirigami.Avatar {
|
|
||||||
color: "#9f244b"
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
visible: Config.showAvatarInTimeline
|
|
||||||
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
|
|
||||||
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
|
|
||||||
}
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
font.weight: Font.Bold
|
|
||||||
font.pixelSize: 7
|
|
||||||
text: "Jean Paul"
|
|
||||||
color: "#9f244b"
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta mauris, quis finibus sem suscipit tincidunt."
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
font.pixelSize: 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
text: i18n("Compact")
|
|
||||||
checked: Config.compactLayout
|
|
||||||
QQC2.ButtonGroup.group: themeGroup
|
|
||||||
enabled: !Config.isCompactLayoutImmutable
|
|
||||||
|
|
||||||
onToggled: {
|
|
||||||
Config.compactLayout = checked;
|
|
||||||
Config.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Kirigami.FormLayout {
|
|
||||||
QQC2.CheckBox {
|
|
||||||
Kirigami.FormData.label: "Show Avatar:"
|
|
||||||
text: i18n("In Chat")
|
|
||||||
checked: Config.showAvatarInTimeline
|
|
||||||
onToggled: {
|
|
||||||
Config.showAvatarInTimeline = checked
|
|
||||||
Config.save()
|
|
||||||
}
|
|
||||||
enabled: !Config.isShowAvatarInTimelineImmutable
|
|
||||||
}
|
|
||||||
QQC2.CheckBox {
|
|
||||||
text: i18n("In Sidebar")
|
|
||||||
checked: Config.showAvatarInRoomDrawer
|
|
||||||
enabled: !Config.isShowAvatarInRoomDrawerImmutable
|
|
||||||
onToggled: {
|
|
||||||
Config.showAvatarInRoomDrawer = checked
|
|
||||||
Config.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.CheckBox {
|
|
||||||
text: i18n("Show Fancy Effects")
|
|
||||||
checked: Config.showFancyEffects
|
|
||||||
enabled: !Config.isShowFancyEffectsImmutable
|
|
||||||
onToggled: {
|
|
||||||
Config.showFancyEffects = checked;
|
|
||||||
Config.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loader {
|
|
||||||
visible: item !== null
|
|
||||||
Kirigami.FormData.label: item ? i18n("Theme:") : ""
|
|
||||||
source: "qrc:/imports/NeoChat/Settings/ColorScheme.qml"
|
|
||||||
}
|
|
||||||
QQC2.CheckBox {
|
|
||||||
visible: Controller.hasWindowSystem
|
|
||||||
text: i18n("Use transparent chat page")
|
|
||||||
enabled: !Config.compactLayout && !Config.isBlurImmutable
|
|
||||||
checked: Config.blur
|
|
||||||
onToggled: {
|
|
||||||
Config.blur = checked;
|
|
||||||
Config.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
visible: Controller.hasWindowSystem && Config.blur
|
|
||||||
enabled: !Config.isTransparancyImmutable
|
|
||||||
Kirigami.FormData.label: i18n("Transparency:")
|
|
||||||
QQC2.Slider {
|
|
||||||
enabled: !Config.compactLayout && Config.blur
|
|
||||||
from: 0
|
|
||||||
to: 1
|
|
||||||
stepSize: 0.05
|
|
||||||
value: Config.transparency
|
|
||||||
onMoved: {
|
|
||||||
Config.transparency = value;
|
|
||||||
Config.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
HoverHandler { id: sliderHover }
|
|
||||||
QQC2.ToolTip.visible: sliderHover.hovered && !enabled
|
|
||||||
QQC2.ToolTip.text: i18n("Only enabled if the transparent chat page is enabled.")
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
text: Math.round(Config.transparency * 100) + "%"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.CheckBox {
|
|
||||||
text: i18n("Show your messages on the right")
|
|
||||||
checked: Config.showLocalMessagesOnRight
|
|
||||||
enabled: !Config.isShowLocalMessagesOnRightImmutable
|
|
||||||
onToggled: {
|
|
||||||
Config.showLocalMessagesOnRight = checked
|
|
||||||
Config.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15 as QQC2
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
import NeoChat.Settings 1.0
|
|
||||||
|
|
||||||
QQC2.ComboBox {
|
|
||||||
textRole: "display"
|
|
||||||
model: ColorSchemer.model
|
|
||||||
Component.onCompleted: currentIndex = ColorSchemer.indexForScheme(Config.colorScheme);
|
|
||||||
onActivated: {
|
|
||||||
ColorSchemer.apply(currentIndex);
|
|
||||||
Config.colorScheme = ColorSchemer.nameForIndex(currentIndex);
|
|
||||||
Config.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15 as Controls
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
Kirigami.Page {
|
|
||||||
title: i18n("Devices")
|
|
||||||
|
|
||||||
leftPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
|
||||||
topPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
|
||||||
bottomPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
|
||||||
rightPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: pageSettingStack
|
|
||||||
function onWideModeChanged() {
|
|
||||||
scroll.background.visible = pageSettingStack.wideMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Controls.ScrollView {
|
|
||||||
id: scroll
|
|
||||||
Component.onCompleted: background.visible = pageSettingStack.wideMode
|
|
||||||
anchors.fill: parent
|
|
||||||
ListView {
|
|
||||||
clip: true
|
|
||||||
model: DevicesModel {
|
|
||||||
id: devices
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.PlaceholderMessage {
|
|
||||||
visible: parent.model.count === 0 // We can assume 0 means loading since there is at least one device
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: i18n("Loading")
|
|
||||||
Controls.BusyIndicator {
|
|
||||||
running: parent.visible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Kirigami.SwipeListItem {
|
|
||||||
leftPadding: 0
|
|
||||||
rightPadding: 0
|
|
||||||
Kirigami.BasicListItem {
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
text: model.displayName
|
|
||||||
subtitle: model.id
|
|
||||||
icon: "network-connect"
|
|
||||||
}
|
|
||||||
actions: [
|
|
||||||
Kirigami.Action {
|
|
||||||
text: i18n("Edit device name")
|
|
||||||
iconName: "document-edit"
|
|
||||||
onTriggered: {
|
|
||||||
renameSheet.index = model.index
|
|
||||||
renameSheet.name = model.displayName
|
|
||||||
renameSheet.open()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
text: i18n("Logout device")
|
|
||||||
iconName: "edit-delete-remove"
|
|
||||||
onTriggered: {
|
|
||||||
passwordSheet.index = index
|
|
||||||
passwordSheet.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.OverlaySheet {
|
|
||||||
id: passwordSheet
|
|
||||||
|
|
||||||
property var index
|
|
||||||
|
|
||||||
title: i18n("Remove device")
|
|
||||||
Kirigami.FormLayout {
|
|
||||||
Controls.TextField {
|
|
||||||
id: passwordField
|
|
||||||
Kirigami.FormData.label: i18n("Password:")
|
|
||||||
echoMode: TextInput.Password
|
|
||||||
}
|
|
||||||
Controls.Button {
|
|
||||||
text: i18n("Confirm")
|
|
||||||
onClicked: {
|
|
||||||
devices.logout(passwordSheet.index, passwordField.text)
|
|
||||||
passwordField.text = ""
|
|
||||||
passwordSheet.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.OverlaySheet {
|
|
||||||
id: renameSheet
|
|
||||||
property int index
|
|
||||||
property string name
|
|
||||||
|
|
||||||
title: i18n("Edit device")
|
|
||||||
Kirigami.FormLayout {
|
|
||||||
Controls.TextField {
|
|
||||||
id: nameField
|
|
||||||
Kirigami.FormData.label: i18n("Name:")
|
|
||||||
text: renameSheet.name
|
|
||||||
}
|
|
||||||
Controls.Button {
|
|
||||||
text: i18n("Save")
|
|
||||||
onClicked: {
|
|
||||||
devices.setName(renameSheet.index, nameField.text)
|
|
||||||
renameSheet.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15 as QQC2
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
import NeoChat.Settings 1.0
|
|
||||||
|
|
||||||
import NeoChat.Component 1.0 as Components
|
|
||||||
import NeoChat.Dialog 1.0
|
|
||||||
|
|
||||||
Kirigami.Page {
|
|
||||||
title: i18nc('@title:window', 'Custom Emojis')
|
|
||||||
|
|
||||||
leftPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
|
||||||
topPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
|
||||||
bottomPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
|
||||||
rightPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: column
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: pageSettingStack
|
|
||||||
function onWideModeChanged() {
|
|
||||||
scroll.background.visible = pageSettingStack.wideMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.ScrollView {
|
|
||||||
id: scroll
|
|
||||||
Component.onCompleted: background.visible = pageSettingStack.wideMode
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
clip: true
|
|
||||||
model: CustomEmojiModel {
|
|
||||||
id: emojiModel
|
|
||||||
|
|
||||||
connection: Controller.activeConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.PlaceholderMessage {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: i18n("No custom inline stickers found")
|
|
||||||
visible: parent.model.count === 0
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Kirigami.BasicListItem {
|
|
||||||
id: del
|
|
||||||
|
|
||||||
required property string name
|
|
||||||
required property url imageURL
|
|
||||||
|
|
||||||
text: name
|
|
||||||
reserveSpaceForSubtitle: true
|
|
||||||
|
|
||||||
leading: Image {
|
|
||||||
width: height
|
|
||||||
sourceSize.width: width
|
|
||||||
sourceSize.height: height
|
|
||||||
source: imageURL
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: parent.status === Image.Loading
|
|
||||||
radius: height/2
|
|
||||||
gradient: Components.ShimmerGradient { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trailing: QQC2.ToolButton {
|
|
||||||
width: height
|
|
||||||
icon.name: "delete"
|
|
||||||
onClicked: emojiModel.removeEmoji(del.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
active: pageSettingStack.wideMode
|
|
||||||
sourceComponent: addEmojiComponent
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
footer: QQC2.ToolBar {
|
|
||||||
id: toolbar
|
|
||||||
width: parent.width
|
|
||||||
visible: !pageSettingStack.wideMode
|
|
||||||
height: visible ? implicitHeight : 0
|
|
||||||
contentItem: Loader {
|
|
||||||
active: !pageSettingStack.wideMode
|
|
||||||
sourceComponent: addEmojiComponent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: openFileDialog
|
|
||||||
|
|
||||||
OpenFileDialog {
|
|
||||||
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property Component addEmojiComponent: RowLayout {
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: Qt.application.layoutDirection == Qt.LeftToRight
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.TextField {
|
|
||||||
id: emojiField
|
|
||||||
placeholderText: i18n("new_emoji_name_here")
|
|
||||||
|
|
||||||
validator: RegularExpressionValidator {
|
|
||||||
regularExpression: /[a-zA-Z_0-9]*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.Button {
|
|
||||||
text: i18n("Add Emoji...")
|
|
||||||
|
|
||||||
enabled: emojiField.text != ""
|
|
||||||
property var fileDialog: null
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (this.fileDialog != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fileDialog = openFileDialog.createObject(QQC2.Overlay.overlay)
|
|
||||||
|
|
||||||
this.fileDialog.chosen.connect((url) => {
|
|
||||||
emojiModel.addEmoji(emojiField.text, url)
|
|
||||||
this.fileDialog = null
|
|
||||||
})
|
|
||||||
this.fileDialog.onRejected.connect(() => {
|
|
||||||
rej()
|
|
||||||
this.fileDialog = null
|
|
||||||
})
|
|
||||||
this.fileDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: Qt.application.layoutDirection == Qt.RightToLeft
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
|
||||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15 as QQC2
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
|
||||||
title: i18nc('@title:window', 'General')
|
|
||||||
ColumnLayout {
|
|
||||||
Kirigami.FormLayout {
|
|
||||||
QQC2.CheckBox {
|
|
||||||
Kirigami.FormData.label: i18n("General settings:")
|
|
||||||
text: i18n("Close to system tray")
|
|
||||||
checked: Config.systemTray
|
|
||||||
visible: Controller.supportSystemTray
|
|
||||||
enabled: !Config.isSystemTrayImmutable
|
|
||||||
onToggled: {
|
|
||||||
Config.systemTray = checked
|
|
||||||
Config.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.CheckBox {
|
|
||||||
// TODO: When there are enough notification and timeline event
|
|
||||||
// settings, make 2 separate groups with FormData labels.
|
|
||||||
Kirigami.FormData.label: i18n("Notifications and events:")
|
|
||||||
text: i18n("Show notifications")
|
|
||||||
checked: Config.showNotifications
|
|
||||||
enabled: !Config.isShowNotificationsImmutable
|
|
||||||
onToggled: {
|
|
||||||
Config.showNotifications = checked
|
|
||||||
Config.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.CheckBox {
|
|
||||||
text: i18n("Show leave and join events")
|
|
||||||
checked: Config.showLeaveJoinEvent
|
|
||||||
enabled: !Config.isShowLeaveJoinEventImmutable
|
|
||||||
onToggled: {
|
|
||||||
Config.showLeaveJoinEvent = checked
|
|
||||||
Config.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.RadioButton {
|
|
||||||
Kirigami.FormData.label: i18n("Rooms and private chats:")
|
|
||||||
text: i18n("Separated")
|
|
||||||
checked: !Config.mergeRoomList
|
|
||||||
enabled: !Config.isMergeRoomListImmutable
|
|
||||||
onToggled: {
|
|
||||||
Config.mergeRoomList = false
|
|
||||||
Config.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.RadioButton {
|
|
||||||
text: i18n("Intermixed")
|
|
||||||
checked: Config.mergeRoomList
|
|
||||||
enabled: !Config.isMergeRoomListImmutable
|
|
||||||
onToggled: {
|
|
||||||
Config.mergeRoomList = true
|
|
||||||
Config.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.CheckBox {
|
|
||||||
text: i18n("Use s/text/replacement syntax to edit your last message")
|
|
||||||
checked: Config.allowQuickEdit
|
|
||||||
enabled: !Config.isAllowQuickEditImmutable
|
|
||||||
onToggled: {
|
|
||||||
Config.allowQuickEdit = checked
|
|
||||||
Config.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import org.kde.kirigami 2.18 as Kirigami
|
|
||||||
import QtQuick.Controls 2.15 as Controls
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
|
|
||||||
Kirigami.CategorizedSettings {
|
|
||||||
objectName: "settingsPage"
|
|
||||||
actions: [
|
|
||||||
Kirigami.SettingAction {
|
|
||||||
text: i18n("General")
|
|
||||||
icon.name: "org.kde.neochat"
|
|
||||||
page: Qt.resolvedUrl("GeneralSettingsPage.qml")
|
|
||||||
},
|
|
||||||
Kirigami.SettingAction {
|
|
||||||
text: i18n("Appearance")
|
|
||||||
icon.name: "preferences-desktop-theme-global"
|
|
||||||
page: Qt.resolvedUrl("AppearanceSettingsPage.qml")
|
|
||||||
},
|
|
||||||
Kirigami.SettingAction {
|
|
||||||
text: i18n("Accounts")
|
|
||||||
icon.name: "preferences-system-users"
|
|
||||||
page: Qt.resolvedUrl("AccountsPage.qml")
|
|
||||||
},
|
|
||||||
Kirigami.SettingAction {
|
|
||||||
text: i18n("Custom Emoji")
|
|
||||||
icon.name: "preferences-desktop-emoticons"
|
|
||||||
page: Qt.resolvedUrl("Emoticons.qml")
|
|
||||||
},
|
|
||||||
Kirigami.SettingAction {
|
|
||||||
text: i18n("Devices")
|
|
||||||
iconName: "network-connect"
|
|
||||||
page: Qt.resolvedUrl("DevicesPage.qml")
|
|
||||||
},
|
|
||||||
Kirigami.SettingAction {
|
|
||||||
text: i18n("About NeoChat")
|
|
||||||
icon.name: "help-about"
|
|
||||||
page: Qt.resolvedUrl("About.qml")
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
// Copyright 2021 Marco Martin <mart@kde.org>
|
|
||||||
// Copyright 2018 Furkan Tokac <furkantokac34@gmail.com>
|
|
||||||
// Copyright 2019 Nate Graham <nate@kde.org>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
|
||||||
import QtQuick.Controls 2.15 as QQC2
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
|
||||||
|
|
||||||
QQC2.RadioButton {
|
|
||||||
id: delegate
|
|
||||||
|
|
||||||
implicitWidth: contentItem.implicitWidth
|
|
||||||
implicitHeight: contentItem.implicitHeight
|
|
||||||
|
|
||||||
property alias innerObject: contentLayout.children
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
Kirigami.ShadowedRectangle {
|
|
||||||
implicitWidth: implicitHeight * 1.6
|
|
||||||
implicitHeight: Kirigami.Units.gridUnit * 6
|
|
||||||
radius: Kirigami.Units.smallSpacing
|
|
||||||
Kirigami.Theme.inherit: false
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
||||||
|
|
||||||
shadow.xOffset: 0
|
|
||||||
shadow.yOffset: 2
|
|
||||||
shadow.size: 10
|
|
||||||
shadow.color: Qt.rgba(0, 0, 0, 0.3)
|
|
||||||
|
|
||||||
color: {
|
|
||||||
if (delegate.checked) {
|
|
||||||
return Kirigami.Theme.highlightColor;
|
|
||||||
} else if (delegate.hovered) {
|
|
||||||
// Match appearance of hovered list items
|
|
||||||
return Qt.rgba(Kirigami.Theme.highlightColor.r,
|
|
||||||
Kirigami.Theme.highlightColor.g,
|
|
||||||
Kirigami.Theme.highlightColor.b,
|
|
||||||
0.5);
|
|
||||||
} else {
|
|
||||||
return Kirigami.Theme.backgroundColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ColumnLayout {
|
|
||||||
id: contentLayout
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Kirigami.Units.smallSpacing
|
|
||||||
clip: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.Label {
|
|
||||||
id: label
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: delegate.text
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
indicator: Item {}
|
|
||||||
background: Item {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
module NeoChat.Settings
|
|
||||||
ThemeRadioButton 1.0 ThemeRadioButton.qml
|
|
||||||
SettingsPage 1.0 SettingsPage.qml
|
|
||||||
@@ -11,16 +11,13 @@ Name[es]=NeoChat
|
|||||||
Name[eu]=NeoChat
|
Name[eu]=NeoChat
|
||||||
Name[fi]=NeoChat
|
Name[fi]=NeoChat
|
||||||
Name[fr]=NeoChat
|
Name[fr]=NeoChat
|
||||||
Name[hu]=NeoChat
|
|
||||||
Name[ia]=Neochat
|
Name[ia]=Neochat
|
||||||
Name[it]=NeoChat
|
Name[it]=NeoChat
|
||||||
Name[ko]=NeoChat
|
Name[ko]=NeoChat
|
||||||
Name[lt]=NeoChat
|
|
||||||
Name[nl]=NeoChat
|
Name[nl]=NeoChat
|
||||||
Name[nn]=NeoChat
|
Name[nn]=NeoChat
|
||||||
Name[pa]=ਨਿਓ-ਚੈਟ
|
Name[pa]=ਨਿਓ-ਚੈਟ
|
||||||
Name[pl]=NeoChat
|
Name[pl]=NeoChat
|
||||||
Name[pt]=NeoChat
|
|
||||||
Name[pt_BR]=NeoChat
|
Name[pt_BR]=NeoChat
|
||||||
Name[ro]=NeoChat
|
Name[ro]=NeoChat
|
||||||
Name[sk]=NeoChat
|
Name[sk]=NeoChat
|
||||||
@@ -33,24 +30,22 @@ Name[zh_CN]=NeoChat
|
|||||||
DesktopEntry=org.kde.neochat
|
DesktopEntry=org.kde.neochat
|
||||||
Comment=A client for matrix, the decentralized communication protocol
|
Comment=A client for matrix, the decentralized communication protocol
|
||||||
Comment[az]=Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu
|
Comment[az]=Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu
|
||||||
Comment[ca]=Un client per a Matrix, el protocol de comunicacions descentralitzat
|
Comment[ca]=Un client per al Matrix, el protocol de comunicacions descentralitzat
|
||||||
Comment[ca@valencia]=Un client per a Matrix, el protocol de comunicacions descentralitzat
|
Comment[ca@valencia]=Un client per al Matrix, el protocol de comunicacions descentralitzat
|
||||||
|
Comment[cs]=Klient pro decentralizovaný komunikační protokol matrix
|
||||||
Comment[de]=Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll
|
Comment[de]=Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll
|
||||||
Comment[en_GB]=A client for matrix, the decentralised communication protocol
|
Comment[en_GB]=A client for matrix, the decentralised communication protocol
|
||||||
Comment[es]=Un cliente para Matrix, el protocolo de comunicaciones descentralizado
|
Comment[es]=Un cliente para Matrix, el protocolo de comunicaciones descentralizado
|
||||||
Comment[eu]=Matrix, deszentralizatutako komunikazio protokolorako, bezero bat
|
Comment[eu]=Matrix, deszentralizatutako komunikazio protokolorako, bezero bat
|
||||||
Comment[fi]=Hajautetun Matrix-viestintäyhteyskäytännön asiakasohjelma
|
Comment[fi]=Hajautetun Matrix-viestintäyhteyskäytännön asiakasohjelma
|
||||||
Comment[fr]=Un client pour « Matrix », le protocole décentralisé de communications.
|
Comment[fr]=Un client pour « Matrix », le protocole décentralisé de communications.
|
||||||
Comment[hu]=Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz
|
Comment[ia]=Un cliente per matrix, le protocollo de communication decentralisate
|
||||||
Comment[ia]=Un cliente per Matrix, le protocollo de communication decentralisate
|
|
||||||
Comment[it]=Un client per matrix, il protocollo di comunicazione decentralizzato
|
Comment[it]=Un client per matrix, il protocollo di comunicazione decentralizzato
|
||||||
Comment[ko]=Matrix, 분산 대화 프로토콜 클라이언트
|
Comment[ko]=Matrix, 분산 대화 프로토콜 클라이언트
|
||||||
Comment[lt]=Matrix decentralizuoto bendravimo protokolo kliento programa
|
|
||||||
Comment[nl]=Een client voor matrix, het gedecentraliseerde communicatieprotocol
|
Comment[nl]=Een client voor matrix, het gedecentraliseerde communicatieprotocol
|
||||||
Comment[nn]=Klient for Matrix, den desentraliserte lynmeldingsprotokollen.
|
Comment[nn]=Klient for Matrix, den desentraliserte lynmeldingsprotokollen.
|
||||||
Comment[pa]=ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ
|
Comment[pa]=ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||||
Comment[pl]=Program do obsługi matriksa, rozproszonego protokołu porozumiewania się
|
Comment[pl]=Program do obsługi matriksa, rozproszonego protokołu porozumiewania się
|
||||||
Comment[pt]=Um cliente para o Matrix, o protocolo descentralizado de comunicações
|
|
||||||
Comment[pt_BR]=Um cliente para o Matrix, o protocolo de comunicação decentralizado
|
Comment[pt_BR]=Um cliente para o Matrix, o protocolo de comunicação decentralizado
|
||||||
Comment[ro]=Client pentru Matrix, protocolul de comunicare descentralizată
|
Comment[ro]=Client pentru Matrix, protocolul de comunicare descentralizată
|
||||||
Comment[sk]=Klient pre matrix, decentralizovaný komunikačný protokol
|
Comment[sk]=Klient pre matrix, decentralizovaný komunikačný protokol
|
||||||
@@ -77,7 +72,6 @@ Name[hu]=Új üzenet
|
|||||||
Name[ia]=Nove message
|
Name[ia]=Nove message
|
||||||
Name[it]=Nuovo messaggio
|
Name[it]=Nuovo messaggio
|
||||||
Name[ko]=새 메시지
|
Name[ko]=새 메시지
|
||||||
Name[lt]=Nauja žinutė
|
|
||||||
Name[nl]=Nieuw bericht
|
Name[nl]=Nieuw bericht
|
||||||
Name[nn]=Ny melding
|
Name[nn]=Ny melding
|
||||||
Name[pa]=ਨਵਾਂ ਸੁਨੇਹਾ
|
Name[pa]=ਨਵਾਂ ਸੁਨੇਹਾ
|
||||||
@@ -103,10 +97,9 @@ Comment[eu]=Mezu berri bat dago
|
|||||||
Comment[fi]=Saapui uusi viesti
|
Comment[fi]=Saapui uusi viesti
|
||||||
Comment[fr]=Il y a un nouveau message
|
Comment[fr]=Il y a un nouveau message
|
||||||
Comment[hu]=Új üzenet érkezett
|
Comment[hu]=Új üzenet érkezett
|
||||||
Comment[ia]=Il ha un nove message
|
Comment[ia]=Isto es un nove message
|
||||||
Comment[it]=È presente un nuovo messaggio
|
Comment[it]=È presente un nuovo messaggio
|
||||||
Comment[ko]=새 메시지가 있음
|
Comment[ko]=새 메시지가 있음
|
||||||
Comment[lt]=Yra nauja žinutė
|
|
||||||
Comment[nl]=Er is een nieuw bericht
|
Comment[nl]=Er is een nieuw bericht
|
||||||
Comment[nn]=Du har ei ny melding
|
Comment[nn]=Du har ei ny melding
|
||||||
Comment[pa]=ਨਵਾਂ ਸੁਨੇਹਾ ਹੈ
|
Comment[pa]=ਨਵਾਂ ਸੁਨੇਹਾ ਹੈ
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
|
||||||
- SPDX-License-Identifier: CC0-1.0
|
|
||||||
- SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carlschwan@kde.org>
|
|
||||||
- SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
|
|
||||||
-->
|
|
||||||
<component type="desktop">
|
<component type="desktop">
|
||||||
<id>org.kde.neochat</id>
|
<id>org.kde.neochat</id>
|
||||||
<provides>
|
<provides>
|
||||||
@@ -20,16 +15,13 @@
|
|||||||
<name xml:lang="eu">NeoChat</name>
|
<name xml:lang="eu">NeoChat</name>
|
||||||
<name xml:lang="fi">NeoChat</name>
|
<name xml:lang="fi">NeoChat</name>
|
||||||
<name xml:lang="fr">NeoChat</name>
|
<name xml:lang="fr">NeoChat</name>
|
||||||
<name xml:lang="hu">NeoChat</name>
|
|
||||||
<name xml:lang="ia">Neochat</name>
|
<name xml:lang="ia">Neochat</name>
|
||||||
<name xml:lang="id">NeoChat</name>
|
|
||||||
<name xml:lang="it">NeoChat</name>
|
<name xml:lang="it">NeoChat</name>
|
||||||
<name xml:lang="ko">NeoChat</name>
|
<name xml:lang="ko">NeoChat</name>
|
||||||
<name xml:lang="nl">NeoChat</name>
|
<name xml:lang="nl">NeoChat</name>
|
||||||
<name xml:lang="nn">NeoChat</name>
|
<name xml:lang="nn">NeoChat</name>
|
||||||
<name xml:lang="pa">ਨਿਓ-ਚੈਟ</name>
|
<name xml:lang="pa">ਨਿਓ-ਚੈਟ</name>
|
||||||
<name xml:lang="pl">NeoChat</name>
|
<name xml:lang="pl">NeoChat</name>
|
||||||
<name xml:lang="pt">NeoChat</name>
|
|
||||||
<name xml:lang="pt-BR">NeoChat</name>
|
<name xml:lang="pt-BR">NeoChat</name>
|
||||||
<name xml:lang="sk">NeoChat</name>
|
<name xml:lang="sk">NeoChat</name>
|
||||||
<name xml:lang="sl">NeoChat</name>
|
<name xml:lang="sl">NeoChat</name>
|
||||||
@@ -50,7 +42,7 @@
|
|||||||
<summary xml:lang="fi">Asiakas Matrixille, hajautetulle viestintäyhteyskäytännölle</summary>
|
<summary xml:lang="fi">Asiakas Matrixille, hajautetulle viestintäyhteyskäytännölle</summary>
|
||||||
<summary xml:lang="fr">Un client pour « Matrix », le protocole décentralisé de communications.</summary>
|
<summary xml:lang="fr">Un client pour « Matrix », le protocole décentralisé de communications.</summary>
|
||||||
<summary xml:lang="hu">Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz</summary>
|
<summary xml:lang="hu">Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz</summary>
|
||||||
<summary xml:lang="ia">Un cliente per Matrix, le protocollo de communication decentralisate</summary>
|
<summary xml:lang="ia">Un cliente per matrix, le protocollo de communication decentralisate</summary>
|
||||||
<summary xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi</summary>
|
<summary xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi</summary>
|
||||||
<summary xml:lang="it">Un client per matrix, il protocollo di comunicazione decentralizzato</summary>
|
<summary xml:lang="it">Un client per matrix, il protocollo di comunicazione decentralizzato</summary>
|
||||||
<summary xml:lang="ko">Matrix, 분산 대화 프로토콜 클라이언트</summary>
|
<summary xml:lang="ko">Matrix, 분산 대화 프로토콜 클라이언트</summary>
|
||||||
@@ -77,13 +69,11 @@
|
|||||||
<p xml:lang="eu">NeoChat Matrix bezero bat da. Familiari, lankideei eta lagunei testu-mezuak, bideoak eta audio-fitxategiak bidaltzeko aukera ematen du, Matrix protokoloa erabiliz.</p>
|
<p xml:lang="eu">NeoChat Matrix bezero bat da. Familiari, lankideei eta lagunei testu-mezuak, bideoak eta audio-fitxategiak bidaltzeko aukera ematen du, Matrix protokoloa erabiliz.</p>
|
||||||
<p xml:lang="fi">NeoChat on Matrix-asiakas. Sillä voi lähettää perheelle, tuttaville ja kavereille tekstiviestejä sekä video- ja äänitiedostoja Matrix-yhteyskäytännöllä.</p>
|
<p xml:lang="fi">NeoChat on Matrix-asiakas. Sillä voi lähettää perheelle, tuttaville ja kavereille tekstiviestejä sekä video- ja äänitiedostoja Matrix-yhteyskäytännöllä.</p>
|
||||||
<p xml:lang="fr">NeoChat est un client Matrix. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis en utilisant le protocole Matrix.</p>
|
<p xml:lang="fr">NeoChat est un client Matrix. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis en utilisant le protocole Matrix.</p>
|
||||||
<p xml:lang="hu">A NeoChat egy Matrix kliens. Szöveges üzeneteket, videókat ésaudio fájlokat küldhet családjának, kollégáinak és barátainak a Matrix protokoll használatával.</p>
|
|
||||||
<p xml:lang="ia">NeoChat es un cliente de Matrix. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante le protocollo de Matrix.</p>
|
<p xml:lang="ia">NeoChat es un cliente de Matrix. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante le protocollo de Matrix.</p>
|
||||||
<p xml:lang="it">NeoChat è un client Matrix. Ti consente di inviare messaggi di testo, file video e audio a familiari, colleghi e amici utilizzando il protocollo Matrix.</p>
|
<p xml:lang="it">NeoChat è un client Matrix. Ti consente di inviare messaggi di testo, file video e audio a familiari, colleghi e amici utilizzando il protocollo Matrix.</p>
|
||||||
<p xml:lang="ko">NeoChat은 Matrix 클라이언트입니다. Matrix 프로토콜을 사용하여 가족, 동료, 친구에게 텍스트 메시지, 동영상, 오디오 파일을 전송할 수 있습니다.</p>
|
<p xml:lang="ko">NeoChat은 Matrix 클라이언트입니다. Matrix 프로토콜을 사용하여 가족, 동료, 친구에게 텍스트 메시지, 동영상, 오디오 파일을 전송할 수 있습니다.</p>
|
||||||
<p xml:lang="nl">NeoChat is een Matrix-client. Het biedt u het verzenden van tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden met het Matrix-protocol.</p>
|
<p xml:lang="nl">NeoChat is een Matrix-client. Het biedt u het verzenden van tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden met het Matrix-protocol.</p>
|
||||||
<p xml:lang="pl">NeoChat jest programem do Matrisa. Umożliwia wysyłanie wiadomości tekstowych, filmów oraz dźwięku do twojej rodziny, znajomych oraz przyjaciół poprzez protokół Matriksa.</p>
|
<p xml:lang="pl">NeoChat jest programem do Matrisa. Umożliwia wysyłanie wiadomości tekstowych, filmów oraz dźwięku do twojej rodziny, znajomych oraz przyjaciół poprzez protokół Matriksa.</p>
|
||||||
<p xml:lang="pt">O NeoChat é um cliente do Matrix. O mesmo permite-lhe enviar mensagens de texto, ficheiros de vídeo e áudio para a sua família, colegas e amigos com o protocolo Matrix.</p>
|
|
||||||
<p xml:lang="pt-BR">O NeoChat é um cliente Matrix. Ele permite a você enviar mensagens de texto, arquivos de vídeo e áudio para seus familiares, colegas e amigos usando o protocolo Matrix.</p>
|
<p xml:lang="pt-BR">O NeoChat é um cliente Matrix. Ele permite a você enviar mensagens de texto, arquivos de vídeo e áudio para seus familiares, colegas e amigos usando o protocolo Matrix.</p>
|
||||||
<p xml:lang="sk">NeoChat je Matrix klient. Umožňuje vám posielať textové správy, videá a zvukové súbory rodine, kolegom a priateľom pomocou protokolu Matrix.</p>
|
<p xml:lang="sk">NeoChat je Matrix klient. Umožňuje vám posielať textové správy, videá a zvukové súbory rodine, kolegom a priateľom pomocou protokolu Matrix.</p>
|
||||||
<p xml:lang="sl">NeoChat je odjemalec Matrixa. Dovoljuje vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, kolegom in prijateljem z uporabo protokola Matrix.</p>
|
<p xml:lang="sl">NeoChat je odjemalec Matrixa. Dovoljuje vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, kolegom in prijateljem z uporabo protokola Matrix.</p>
|
||||||
@@ -100,14 +90,12 @@
|
|||||||
<p xml:lang="es">Matrix es un protocolo de comunicaciones descentralizado, que devuelve el control al usuario. En la actualidad, NeoChat implementa gran parte del protocolo con la excepción de chats cifrados y chats de vídeo.</p>
|
<p xml:lang="es">Matrix es un protocolo de comunicaciones descentralizado, que devuelve el control al usuario. En la actualidad, NeoChat implementa gran parte del protocolo con la excepción de chats cifrados y chats de vídeo.</p>
|
||||||
<p xml:lang="eu">Matrix komunikazio-protokolo deszentralizatu bat da, erabiltzaileari kontrola itzultzen diona. Gaur egun, NeoChat-ek protokoloaren zati handi bat inplementatzen du, berriketa zifratuak eta bideo berriketak izan ezik.</p>
|
<p xml:lang="eu">Matrix komunikazio-protokolo deszentralizatu bat da, erabiltzaileari kontrola itzultzen diona. Gaur egun, NeoChat-ek protokoloaren zati handi bat inplementatzen du, berriketa zifratuak eta bideo berriketak izan ezik.</p>
|
||||||
<p xml:lang="fi">Matrix on hajautettu viestintäyhteyskäytäntö, joka antaa hallinnan takaisin käyttäjille. NeoChat tarjoaa nykyisellään valtaosan yhteyskäytännöstä salattuja keskustelu- ja videokeskusteluja lukuun ottamatta.</p>
|
<p xml:lang="fi">Matrix on hajautettu viestintäyhteyskäytäntö, joka antaa hallinnan takaisin käyttäjille. NeoChat tarjoaa nykyisellään valtaosan yhteyskäytännöstä salattuja keskustelu- ja videokeskusteluja lukuun ottamatta.</p>
|
||||||
<p xml:lang="fr">Matrix est un protocole de communication décentralisé, donnant le contrôle à l'utilisateur. Actuellement, NeoChat met en œuvre une grande partie du protocole, à l'exception des discussions cryptées et du chat vidéo.</p>
|
<p xml:lang="fr">Matrix est un protocole de communication décentralisé, donnant le contrôle à l'utilisateur. Actuellement, NeoChat met en œuvre une grande partie du protocole, à l'exception des discussions chiffrées et du chat vidéo.</p>
|
||||||
<p xml:lang="hu">A Matrix egy decentralizált kommunikációs protokoll, amely a felhasználók kezébe adja az irányítást.</p>
|
|
||||||
<p xml:lang="ia">Matrix es un protocollo de communication decentrate, ponente le usator in le controlo. Currentemente NeoChat implementa un grande parte del protocollo con le exception de conversationes cryptate e conversationes video.</p>
|
<p xml:lang="ia">Matrix es un protocollo de communication decentrate, ponente le usator in le controlo. Currentemente NeoChat implementa un grande parte del protocollo con le exception de conversationes cryptate e conversationes video.</p>
|
||||||
<p xml:lang="it">Matrix è un protocollo di comunicazione decentralizzato, che restituisce all'utente il controllo. Attualmente NeoChat implementa gran parte del protocollo ad eccezione delle chat cifrate e delle chat video.</p>
|
<p xml:lang="it">Matrix è un protocollo di comunicazione decentralizzato, che restituisce all'utente il controllo. Attualmente NeoChat implementa gran parte del protocollo ad eccezione delle chat cifrate e delle chat video.</p>
|
||||||
<p xml:lang="ko">Matrix는 사용자에게 제어권을 돌려 주는 분산 통신 프로토콜입니다. NeoChat은 암호화된 대화 및 영상 통화를 제외한 프로토콜의 대부분 기능을 구현합니다.</p>
|
<p xml:lang="ko">Matrix는 사용자에게 제어권을 돌려 주는 분산 통신 프로토콜입니다. NeoChat은 암호화된 대화 및 영상 통화를 제외한 프로토콜의 대부분 기능을 구현합니다.</p>
|
||||||
<p xml:lang="nl">Matrix is een gedecentraliseerd communicatieprotocol, dat de gebruiker de controle teruggeeft. Op dit moment implementeert NeoChat grote delen van het protocol met de uitzondering van versleutelde chats en video-chat.</p>
|
<p xml:lang="nl">Matrix is een gedecentraliseerd communicatieprotocol, dat de gebruiker de controle teruggeeft. Op dit moment implementeert NeoChat grote delen van het protocol met de uitzondering van versleutelde chats en video-chat.</p>
|
||||||
<p xml:lang="pl">Matrix jest protokołem rozproszonego porozumiewania się oddający użytkownikowi jego władzę. Obecnie NeoChat obsługuje dużą część protokołu poza szyfrowanymi rozmowami tekstowymi i z obrazem.</p>
|
<p xml:lang="pl">Matrix jest protokołem rozproszonego porozumiewania się oddający użytkownikowi jego władzę. Obecnie NeoChat obsługuje dużą część protokołu poza szyfrowanymi rozmowami tekstowymi i z obrazem.</p>
|
||||||
<p xml:lang="pt">O Matrix é um protocolo de comunicações descentralizado, colocando de novo o utilizador no poder. De momento, o NeoChat implementa uma boa parte do protocolo, com a excepção das conversas encriptadas e as conversas de vídeo.</p>
|
|
||||||
<p xml:lang="pt-BR">O Matrix é um protocolo de comunicação descentralizado, colocando o usuário de volta no controle. Atualmente o NeoChat implementa grande parte do protocolo com a exceção de bate-papos criptografados e bate-papo por vídeo.</p>
|
<p xml:lang="pt-BR">O Matrix é um protocolo de comunicação descentralizado, colocando o usuário de volta no controle. Atualmente o NeoChat implementa grande parte do protocolo com a exceção de bate-papos criptografados e bate-papo por vídeo.</p>
|
||||||
<p xml:lang="sk">Matrix je decentralizovaný komunikačný protokol, ktorý používateľovi vracia kontrolu. V súčasnosti NeoChat implementuje veľkú časť protokolu s výnimkou šifrovaných chatov a videohovorov.</p>
|
<p xml:lang="sk">Matrix je decentralizovaný komunikačný protokol, ktorý používateľovi vracia kontrolu. V súčasnosti NeoChat implementuje veľkú časť protokolu s výnimkou šifrovaných chatov a videohovorov.</p>
|
||||||
<p xml:lang="sl">Matrix je decentraliziran komunikacijski protokol, kjer ima uporabnik uporabnik kontrolo rabe. Trenutno ima NeoChat izveden velik del protokola z izjemo šifriranih klepetov in video klepetov.</p>
|
<p xml:lang="sl">Matrix je decentraliziran komunikacijski protokol, kjer ima uporabnik uporabnik kontrolo rabe. Trenutno ima NeoChat izveden velik del protokola z izjemo šifriranih klepetov in video klepetov.</p>
|
||||||
@@ -125,13 +113,11 @@
|
|||||||
<p xml:lang="eu">NeoChat mugikorretan eta mahaigainean dabil, erabiltzaile esperientzia koherentea eskainiz.</p>
|
<p xml:lang="eu">NeoChat mugikorretan eta mahaigainean dabil, erabiltzaile esperientzia koherentea eskainiz.</p>
|
||||||
<p xml:lang="fi">NeoChat toimii sekä mobiili- että työpöytäalustoilla tarjoten yhdenmukaisen käyttökokemuksen.</p>
|
<p xml:lang="fi">NeoChat toimii sekä mobiili- että työpöytäalustoilla tarjoten yhdenmukaisen käyttökokemuksen.</p>
|
||||||
<p xml:lang="fr">NeoChat fonctionne aussi bien sur les mobiles que sur les ordinateurs de bureau, tout en offrant une expérience utilisateur cohérente.</p>
|
<p xml:lang="fr">NeoChat fonctionne aussi bien sur les mobiles que sur les ordinateurs de bureau, tout en offrant une expérience utilisateur cohérente.</p>
|
||||||
<p xml:lang="hu">A NeoChat mobilon és asztali számítógépen is működik, egységes felhasználói élményt nyújtva.</p>
|
|
||||||
<p xml:lang="ia">NeoChat functiona sia sur mobile que ur scriptorio durante que forni un experientia de usator consistente.</p>
|
<p xml:lang="ia">NeoChat functiona sia sur mobile que ur scriptorio durante que forni un experientia de usator consistente.</p>
|
||||||
<p xml:lang="it">NeoChat funziona sia su dispositivi mobili che desktop, fornendo un'esperienza utente coerente.</p>
|
<p xml:lang="it">NeoChat funziona sia su dispositivi mobili che desktop, fornendo un'esperienza utente coerente.</p>
|
||||||
<p xml:lang="ko">NeoChat은 모바일과 데스크톱 모두에서 일관된 사용자 경험을 제공합니다.</p>
|
<p xml:lang="ko">NeoChat은 모바일과 데스크톱 모두에서 일관된 사용자 경험을 제공합니다.</p>
|
||||||
<p xml:lang="nl">NeoChat werkt zowel op de mobiel en het bureaublad met het leveren van een consistente gebruikerservaring.</p>
|
<p xml:lang="nl">NeoChat werkt zowel op de mobiel en het bureaublad met het leveren van een consistente gebruikerservaring.</p>
|
||||||
<p xml:lang="pl">NeoChat działa zarówno na urządzeniach przenośnych jak i biurkowych, zapewniając spójne wrażenia użytkownika</p>
|
<p xml:lang="pl">NeoChat działa zarówno na urządzeniach przenośnych jak i biurkowych, zapewniając spójne wrażenia użytkownika</p>
|
||||||
<p xml:lang="pt">O NeoChat funciona tanto em dispositivos móveis como no computador, fornecendo uma experiência de utilizador consistente.</p>
|
|
||||||
<p xml:lang="pt-BR">O NeoChat funciona tanto no celular como no computador enquanto fornece uma experiência consistente ao usuário.</p>
|
<p xml:lang="pt-BR">O NeoChat funciona tanto no celular como no computador enquanto fornece uma experiência consistente ao usuário.</p>
|
||||||
<p xml:lang="sk">NeoChat funguje na mobilných aj stolových počítačoch a poskytuje konzistentný používateľský zážitok.</p>
|
<p xml:lang="sk">NeoChat funguje na mobilných aj stolových počítačoch a poskytuje konzistentný používateľský zážitok.</p>
|
||||||
<p xml:lang="sl">NeoChat deluje tako na mobilnih kot na namiznih platformah z zagotavljanjem konsistentne uporabniške izkušnje.</p>
|
<p xml:lang="sl">NeoChat deluje tako na mobilnih kot na namiznih platformah z zagotavljanjem konsistentne uporabniške izkušnje.</p>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
# SPDX-License-Identifier: CC0-1.0
|
|
||||||
# SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=NeoChat
|
Name=NeoChat
|
||||||
Name[az]=NeoChat
|
Name[az]=NeoChat
|
||||||
@@ -12,16 +10,13 @@ Name[es]=NeoChat
|
|||||||
Name[eu]=NeoChat
|
Name[eu]=NeoChat
|
||||||
Name[fi]=NeoChat
|
Name[fi]=NeoChat
|
||||||
Name[fr]=NeoChat
|
Name[fr]=NeoChat
|
||||||
Name[hu]=NeoChat
|
|
||||||
Name[ia]=Neochat
|
Name[ia]=Neochat
|
||||||
Name[it]=NeoChat
|
Name[it]=NeoChat
|
||||||
Name[ko]=NeoChat
|
Name[ko]=NeoChat
|
||||||
Name[lt]=NeoChat
|
|
||||||
Name[nl]=NeoChat
|
Name[nl]=NeoChat
|
||||||
Name[nn]=NeoChat
|
Name[nn]=NeoChat
|
||||||
Name[pa]=ਨਿਓ-ਚੈਟ
|
Name[pa]=ਨਿਓ-ਚੈਟ
|
||||||
Name[pl]=NeoChat
|
Name[pl]=NeoChat
|
||||||
Name[pt]=NeoChat
|
|
||||||
Name[pt_BR]=NeoChat
|
Name[pt_BR]=NeoChat
|
||||||
Name[ro]=NeoChat
|
Name[ro]=NeoChat
|
||||||
Name[sk]=NeoChat
|
Name[sk]=NeoChat
|
||||||
@@ -33,8 +28,8 @@ Name[x-test]=xxNeoChatxx
|
|||||||
Name[zh_CN]=NeoChat
|
Name[zh_CN]=NeoChat
|
||||||
GenericName=Matrix Client
|
GenericName=Matrix Client
|
||||||
GenericName[az]=Matrix Müştərisi
|
GenericName[az]=Matrix Müştərisi
|
||||||
GenericName[ca]=Client de Matrix
|
GenericName[ca]=Client del Matrix
|
||||||
GenericName[ca@valencia]=Client de Matrix
|
GenericName[ca@valencia]=Client del Matrix
|
||||||
GenericName[cs]=Klient protokolu Matrix
|
GenericName[cs]=Klient protokolu Matrix
|
||||||
GenericName[de]=Matrix-Programm
|
GenericName[de]=Matrix-Programm
|
||||||
GenericName[en_GB]=Matrix Client
|
GenericName[en_GB]=Matrix Client
|
||||||
@@ -43,10 +38,9 @@ GenericName[eu]=Matrix bezeroa
|
|||||||
GenericName[fi]=Matrix-asiakas
|
GenericName[fi]=Matrix-asiakas
|
||||||
GenericName[fr]=Client « Matrix »
|
GenericName[fr]=Client « Matrix »
|
||||||
GenericName[hu]=Matrix kliens
|
GenericName[hu]=Matrix kliens
|
||||||
GenericName[ia]=Cliente de Matrice
|
GenericName[ia]=Cliente de Matrix
|
||||||
GenericName[it]=Client Matrix
|
GenericName[it]=Client Matrix
|
||||||
GenericName[ko]=Matrix 클라이언트
|
GenericName[ko]=Matrix 클라이언트
|
||||||
GenericName[lt]=Matrix kliento programą
|
|
||||||
GenericName[nl]=Matrix-client
|
GenericName[nl]=Matrix-client
|
||||||
GenericName[nn]=Matrix-klient
|
GenericName[nn]=Matrix-klient
|
||||||
GenericName[pa]=ਮੈਟਰਿਕਸ ਕਲਾਈਂਟ
|
GenericName[pa]=ਮੈਟਰਿਕਸ ਕਲਾਈਂਟ
|
||||||
@@ -75,7 +69,6 @@ Comment[hu]=Kliens a Matrix protokollhoz
|
|||||||
Comment[ia]=Cliente per le protocollo de Matrix
|
Comment[ia]=Cliente per le protocollo de Matrix
|
||||||
Comment[it]=Client per il protocollo Matrix
|
Comment[it]=Client per il protocollo Matrix
|
||||||
Comment[ko]=Matrix 프로토콜용 클라이언트
|
Comment[ko]=Matrix 프로토콜용 클라이언트
|
||||||
Comment[lt]=Matrix protokolo kliento programa
|
|
||||||
Comment[nl]=Client voor het Matrix-protocol
|
Comment[nl]=Client voor het Matrix-protocol
|
||||||
Comment[nn]=Lynmeldingsklient for Matrix-protokollen
|
Comment[nn]=Lynmeldingsklient for Matrix-protokollen
|
||||||
Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
|
Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||||
|
|||||||
193
qml/main.qml
193
qml/main.qml
@@ -27,14 +27,9 @@ Kirigami.ApplicationWindow {
|
|||||||
onClosing: Controller.saveWindowGeometry(root)
|
onClosing: Controller.saveWindowGeometry(root)
|
||||||
|
|
||||||
pageStack.initialPage: LoadingPage {}
|
pageStack.initialPage: LoadingPage {}
|
||||||
pageStack.globalToolBar.canContainHandles: true
|
|
||||||
|
|
||||||
property bool roomListLoaded: false
|
property bool roomListLoaded: false
|
||||||
|
|
||||||
property RoomPage roomPage: RoomPage {
|
|
||||||
KeyNavigation.left: pageStack.get(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.quitAction
|
target: root.quitAction
|
||||||
function onTriggered() {
|
function onTriggered() {
|
||||||
@@ -42,11 +37,6 @@ Kirigami.ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
|
||||||
active: !Kirigami.Settings.isMobile
|
|
||||||
source: Qt.resolvedUrl("qrc:/imports/NeoChat/Menu/GlobalMenu.qml")
|
|
||||||
}
|
|
||||||
|
|
||||||
// This timer allows to batch update the window size change to reduce
|
// This timer allows to batch update the window size change to reduce
|
||||||
// the io load and also work around the fact that x/y/width/height are
|
// the io load and also work around the fact that x/y/width/height are
|
||||||
// changed when loading the page and overwrite the saved geometry from
|
// changed when loading the page and overwrite the saved geometry from
|
||||||
@@ -62,35 +52,39 @@ Kirigami.ApplicationWindow {
|
|||||||
onXChanged: saveWindowGeometryTimer.restart()
|
onXChanged: saveWindowGeometryTimer.restart()
|
||||||
onYChanged: saveWindowGeometryTimer.restart()
|
onYChanged: saveWindowGeometryTimer.restart()
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Ctrl+K"
|
/// Setup keyboard navigation to the room page.
|
||||||
onActivated: {
|
function connectRoomToSignal(item) {
|
||||||
quickView.item.open()
|
if (!roomListLoaded) {
|
||||||
|
console.log("Should not happen: no room list page but room page");
|
||||||
}
|
}
|
||||||
}
|
const roomList = pageStack.get(0);
|
||||||
|
item.switchRoomUp.connect(function() {
|
||||||
|
roomList.goToNextRoom();
|
||||||
|
});
|
||||||
|
|
||||||
Loader {
|
item.switchRoomDown.connect(function() {
|
||||||
id: quickView
|
roomList.goToPreviousRoom();
|
||||||
|
});
|
||||||
active: !Kirigami.Settings.isMobile
|
item.forceActiveFocus();
|
||||||
sourceComponent: QuickSwitcher { }
|
item.KeyNavigation.left = pageStack.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: RoomManager
|
target: RoomManager
|
||||||
|
|
||||||
function onPushRoom(room, event) {
|
function onPushRoom(room, event) {
|
||||||
pageStack.push(root.roomPage);
|
const roomItem = pageStack.push("qrc:/imports/NeoChat/Page/RoomPage.qml");
|
||||||
root.roomPage.forceActiveFocus();
|
connectRoomToSignal(roomItem);
|
||||||
if (event.length > 0) {
|
if (event.length > 0) {
|
||||||
roomPage.goToEvent(event);
|
roomItem.goToEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReplaceRoom(room, event) {
|
function onReplaceRoom(room, event) {
|
||||||
const roomItem = pageStack.get(pageStack.depth - 1);
|
const roomItem = pageStack.get(pageStack.depth - 1);
|
||||||
pageStack.currentIndex = pageStack.depth - 1;
|
pageStack.currentIndex = pageStack.depth - 1;
|
||||||
root.roomPage.forceActiveFocus();
|
connectRoomToSignal(roomItem);
|
||||||
if (event.length > 0) {
|
if (event.length > 0) {
|
||||||
roomItem.goToEvent(event);
|
roomItem.goToEvent(event);
|
||||||
}
|
}
|
||||||
@@ -146,11 +140,11 @@ Kirigami.ApplicationWindow {
|
|||||||
root.show()
|
root.show()
|
||||||
root.raise()
|
root.raise()
|
||||||
root.requestActivate()
|
root.requestActivate()
|
||||||
Controller.raiseWindow(root)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contextDrawer: RoomDrawer {
|
contextDrawer: RoomDrawer {
|
||||||
id: contextDrawer
|
id: contextDrawer
|
||||||
|
contentItem.implicitWidth: columnWidth
|
||||||
edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
|
edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
|
||||||
modal: !root.wideScreen || !enabled
|
modal: !root.wideScreen || !enabled
|
||||||
onEnabledChanged: drawerOpen = enabled && !modal
|
onEnabledChanged: drawerOpen = enabled && !modal
|
||||||
@@ -159,70 +153,7 @@ Kirigami.ApplicationWindow {
|
|||||||
handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3
|
handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property int defaultPageWidth: Kirigami.Units.gridUnit * 17
|
pageStack.columnView.columnWidth: Kirigami.Units.gridUnit * 17
|
||||||
readonly property int minPageWidth: Kirigami.Units.gridUnit * 10
|
|
||||||
readonly property int collapsedPageWidth: Kirigami.Units.gridUnit * 3 - Kirigami.Units.smallSpacing * 3
|
|
||||||
readonly property bool shouldUseSidebars: RoomManager.hasOpenRoom && (Config.roomListPageWidth > minPageWidth ? root.width >= Kirigami.Units.gridUnit * 35 : root.width > Kirigami.Units.gridUnit * 27) && roomListLoaded
|
|
||||||
readonly property int pageWidth: {
|
|
||||||
if (Config.roomListPageWidth === -1) {
|
|
||||||
return defaultPageWidth;
|
|
||||||
} else if (Config.roomListPageWidth < minPageWidth) {
|
|
||||||
return collapsedPageWidth;
|
|
||||||
} else {
|
|
||||||
return Config.roomListPageWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pageStack.defaultColumnWidth: pageWidth
|
|
||||||
pageStack.columnView.columnResizeMode: shouldUseSidebars ? Kirigami.ColumnView.FixedColumns : Kirigami.ColumnView.SingleColumn
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
visible: root.pageStack.wideMode
|
|
||||||
z: 500
|
|
||||||
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
x: root.pageStack.defaultColumnWidth - (width / 2)
|
|
||||||
width: 2
|
|
||||||
|
|
||||||
property int _lastX: -1
|
|
||||||
enabled: !Kirigami.Settings.isMobile
|
|
||||||
|
|
||||||
cursorShape: !Kirigami.Settings.isMobile ? Qt.SplitHCursor : undefined
|
|
||||||
|
|
||||||
onPressed: _lastX = mouseX
|
|
||||||
onReleased: Config.save();
|
|
||||||
|
|
||||||
onPositionChanged: {
|
|
||||||
if (_lastX == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mouse.x > _lastX) {
|
|
||||||
// we moved to the right
|
|
||||||
if (Config.roomListPageWidth === root.collapsedPageWidth && root.pageWidth + (mouse.x - _lastX) >= root.minPageWidth) {
|
|
||||||
// Here we get back directly to a more wide mode.
|
|
||||||
Config.roomListPageWidth = root.minPageWidth;
|
|
||||||
if (root.width < Kirigami.Units.gridUnit * 35) {
|
|
||||||
root.width = Kirigami.Units.gridUnit * 35;
|
|
||||||
}
|
|
||||||
} else if (Config.roomListPageWidth !== root.collapsedPageWidth) {
|
|
||||||
// Increase page width
|
|
||||||
Config.roomListPageWidth = Math.min(root.defaultPageWidth, root.pageWidth + (mouse.x - _lastX));
|
|
||||||
}
|
|
||||||
} else if (mouse.x < _lastX) {
|
|
||||||
const tmpWidth = root.pageWidth - (_lastX - mouse.x);
|
|
||||||
|
|
||||||
if (tmpWidth < root.minPageWidth) {
|
|
||||||
Config.roomListPageWidth = root.collapsedPageWidth;
|
|
||||||
} else {
|
|
||||||
Config.roomListPageWidth = tmpWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
globalDrawer: Kirigami.GlobalDrawer {
|
globalDrawer: Kirigami.GlobalDrawer {
|
||||||
property bool hasLayer
|
property bool hasLayer
|
||||||
@@ -251,13 +182,32 @@ Kirigami.ApplicationWindow {
|
|||||||
shortcut: StandardKey.New
|
shortcut: StandardKey.New
|
||||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
||||||
},
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Accounts")
|
||||||
|
icon.name: "im-user"
|
||||||
|
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/AccountsPage.qml")
|
||||||
|
enabled: pageStack.layers.currentItem.title !== i18n("Accounts") && Controller.accountCount > 0
|
||||||
|
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Devices")
|
||||||
|
iconName: "network-connect"
|
||||||
|
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/DevicesPage.qml")
|
||||||
|
enabled: pageStack.layers.currentItem.title !== i18n("Devices") && Controller.accountCount > 0
|
||||||
|
},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18n("Settings")
|
text: i18n("Settings")
|
||||||
icon.name: "settings-configure"
|
icon.name: "settings-configure"
|
||||||
onTriggered: pageStack.pushDialogLayer("qrc:/imports/NeoChat/Settings/SettingsPage.qml")
|
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/SettingsPage.qml")
|
||||||
enabled: pageStack.layers.currentItem.title !== i18n("Settings")
|
enabled: pageStack.layers.currentItem.title !== i18n("Settings")
|
||||||
shortcut: StandardKey.Preferences
|
shortcut: StandardKey.Preferences
|
||||||
},
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("About NeoChat")
|
||||||
|
icon.name: "help-about"
|
||||||
|
onTriggered: pushReplaceLayer(aboutPage)
|
||||||
|
enabled: pageStack.layers.currentItem.title !== i18n("About")
|
||||||
|
},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18n("Logout")
|
text: i18n("Logout")
|
||||||
icon.name: "list-remove-user"
|
icon.name: "list-remove-user"
|
||||||
@@ -273,39 +223,27 @@ Kirigami.ApplicationWindow {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: Controller.setBlur(pageStack, Config.blur && !Config.compactLayout);
|
Component {
|
||||||
Connections {
|
id: aboutPage
|
||||||
target: Config
|
Kirigami.AboutPage {
|
||||||
function onBlurChanged() {
|
aboutData: Controller.aboutData
|
||||||
Controller.setBlur(pageStack, Config.blur && !Config.compactLayout);
|
|
||||||
}
|
}
|
||||||
function onCompactLayoutChanged() {
|
|
||||||
Controller.setBlur(pageStack, Config.blur && !Config.compactLayout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// blur effect
|
|
||||||
color: Config.blur && !Config.compactLayout ? "transparent" : Kirigami.Theme.backgroundColor
|
|
||||||
|
|
||||||
// we need to apply the translucency effect separately on top of the color
|
|
||||||
background: Rectangle {
|
|
||||||
color: Config.blur && !Config.compactLayout ? Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 1 - Config.transparency) : "transparent"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: roomListComponent
|
id: roomListComponent
|
||||||
RoomListPage {
|
RoomListPage {
|
||||||
id: roomList
|
id: roomList
|
||||||
|
}
|
||||||
Connections {
|
}
|
||||||
target: root.roomPage
|
Connections {
|
||||||
function onSwitchRoomUp() {
|
target: LoginHelper
|
||||||
roomList.goToNextRoom();
|
function onInitialSyncFinished() {
|
||||||
}
|
pageStack.replace(roomListComponent, {
|
||||||
function onSwitchRoomDown() {
|
activeConnection: Controller.activeConnection
|
||||||
roomList.goToPreviousRoom();
|
});
|
||||||
}
|
roomListLoaded = true;
|
||||||
}
|
RoomManager.loadInitialRoom();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,9 +251,12 @@ Kirigami.ApplicationWindow {
|
|||||||
target: Controller
|
target: Controller
|
||||||
|
|
||||||
function onInitiated() {
|
function onInitiated() {
|
||||||
|
if (RoomManager.hasOpenRoom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (Controller.accountCount === 0) {
|
if (Controller.accountCount === 0) {
|
||||||
pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml", {});
|
pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml", {});
|
||||||
} else if (!roomListLoaded) {
|
} else {
|
||||||
pageStack.replace(roomListComponent, {
|
pageStack.replace(roomListComponent, {
|
||||||
activeConnection: Controller.activeConnection
|
activeConnection: Controller.activeConnection
|
||||||
});
|
});
|
||||||
@@ -324,6 +265,13 @@ Kirigami.ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onBusyChanged() {
|
||||||
|
if(!Controller.busy && roomListLoaded === false) {
|
||||||
|
pageStack.replace(roomListComponent);
|
||||||
|
roomListLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onConnectionDropped() {
|
function onConnectionDropped() {
|
||||||
if (Controller.accountCount === 0) {
|
if (Controller.accountCount === 0) {
|
||||||
RoomManager.reset();
|
RoomManager.reset();
|
||||||
@@ -339,7 +287,6 @@ Kirigami.ApplicationWindow {
|
|||||||
|
|
||||||
function onShowWindow() {
|
function onShowWindow() {
|
||||||
root.showWindow()
|
root.showWindow()
|
||||||
root.raise()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUserConsentRequired(url) {
|
function onUserConsentRequired(url) {
|
||||||
@@ -350,7 +297,7 @@ Kirigami.ApplicationWindow {
|
|||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Controller.activeConnection
|
target: Controller.activeConnection
|
||||||
function onDirectChatAvailable(directChat) {
|
onDirectChatAvailable: {
|
||||||
RoomManager.enterRoom(Controller.activeConnection.room(directChat.id));
|
RoomManager.enterRoom(Controller.activeConnection.room(directChat.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,7 +307,9 @@ Kirigami.ApplicationWindow {
|
|||||||
|
|
||||||
property string url: ""
|
property string url: ""
|
||||||
|
|
||||||
title: i18n("User consent")
|
header: Kirigami.Heading {
|
||||||
|
text: i18n("User consent")
|
||||||
|
}
|
||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: label
|
id: label
|
||||||
@@ -399,7 +348,9 @@ Kirigami.ApplicationWindow {
|
|||||||
required property var user;
|
required property var user;
|
||||||
|
|
||||||
parent: QQC2.ApplicationWindow.overlay
|
parent: QQC2.ApplicationWindow.overlay
|
||||||
title: i18n("Start a chat")
|
header: Kirigami.Heading {
|
||||||
|
text: i18n("Start a chat")
|
||||||
|
}
|
||||||
contentItem: QQC2.Label {
|
contentItem: QQC2.Label {
|
||||||
text: i18n("Do you want to start a chat with %1?", user.displayName)
|
text: i18n("Do you want to start a chat with %1?", user.displayName)
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
|
|||||||
26
res.qrc
26
res.qrc
@@ -6,30 +6,24 @@
|
|||||||
<file>imports/NeoChat/Page/RoomListPage.qml</file>
|
<file>imports/NeoChat/Page/RoomListPage.qml</file>
|
||||||
<file>imports/NeoChat/Page/RoomPage.qml</file>
|
<file>imports/NeoChat/Page/RoomPage.qml</file>
|
||||||
<file>imports/NeoChat/Page/RoomWindow.qml</file>
|
<file>imports/NeoChat/Page/RoomWindow.qml</file>
|
||||||
|
<file>imports/NeoChat/Page/AccountsPage.qml</file>
|
||||||
<file>imports/NeoChat/Page/JoinRoomPage.qml</file>
|
<file>imports/NeoChat/Page/JoinRoomPage.qml</file>
|
||||||
<file>imports/NeoChat/Page/InviteUserPage.qml</file>
|
<file>imports/NeoChat/Page/InviteUserPage.qml</file>
|
||||||
|
<file>imports/NeoChat/Page/SettingsPage.qml</file>
|
||||||
<file>imports/NeoChat/Page/StartChatPage.qml</file>
|
<file>imports/NeoChat/Page/StartChatPage.qml</file>
|
||||||
<file>imports/NeoChat/Page/ImageEditorPage.qml</file>
|
<file>imports/NeoChat/Page/ImageEditorPage.qml</file>
|
||||||
|
<file>imports/NeoChat/Page/DevicesPage.qml</file>
|
||||||
<file>imports/NeoChat/Page/WelcomePage.qml</file>
|
<file>imports/NeoChat/Page/WelcomePage.qml</file>
|
||||||
<file>imports/NeoChat/RoomSettings/General.qml</file>
|
|
||||||
<file>imports/NeoChat/RoomSettings/Security.qml</file>
|
|
||||||
<file>imports/NeoChat/RoomSettings/Categories.qml</file>
|
|
||||||
<file>imports/NeoChat/Component/qmldir</file>
|
<file>imports/NeoChat/Component/qmldir</file>
|
||||||
<file>imports/NeoChat/Component/FullScreenImage.qml</file>
|
<file>imports/NeoChat/Component/FullScreenImage.qml</file>
|
||||||
<file>imports/NeoChat/Component/FancyEffectsContainer.qml</file>
|
<file>imports/NeoChat/Component/FancyEffectsContainer.qml</file>
|
||||||
<file>imports/NeoChat/Component/TypingPane.qml</file>
|
<file>imports/NeoChat/Component/TypingPane.qml</file>
|
||||||
<file>imports/NeoChat/Component/ShimmerGradient.qml</file>
|
|
||||||
<file>imports/NeoChat/Component/QuickSwitcher.qml</file>
|
|
||||||
<file>imports/NeoChat/Component/ChatBox</file>
|
<file>imports/NeoChat/Component/ChatBox</file>
|
||||||
<file>imports/NeoChat/Component/ChatBox/ChatBox.qml</file>
|
<file>imports/NeoChat/Component/ChatBox/ChatBox.qml</file>
|
||||||
<file>imports/NeoChat/Component/ChatBox/ChatBar.qml</file>
|
<file>imports/NeoChat/Component/ChatBox/ChatBar.qml</file>
|
||||||
<file>imports/NeoChat/Component/ChatBox/AttachmentPane.qml</file>
|
<file>imports/NeoChat/Component/ChatBox/AttachmentPane.qml</file>
|
||||||
<file>imports/NeoChat/Component/ChatBox/ReplyPane.qml</file>
|
<file>imports/NeoChat/Component/ChatBox/ReplyPane.qml</file>
|
||||||
<file>imports/NeoChat/Component/ChatBox/CompletionMenu.qml</file>
|
<file>imports/NeoChat/Component/ChatBox/CompletionMenu.qml</file>
|
||||||
<file>imports/NeoChat/Component/ChatBox/CursorHandle.qml</file>
|
|
||||||
<file>imports/NeoChat/Component/ChatBox/CursorDelegate.qml</file>
|
|
||||||
<file>imports/NeoChat/Component/ChatBox/MobileTextActionsToolBar.qml</file>
|
|
||||||
<file>imports/NeoChat/Component/ChatBox/TextFieldContextMenu.qml</file>
|
|
||||||
<file>imports/NeoChat/Component/ChatBox/qmldir</file>
|
<file>imports/NeoChat/Component/ChatBox/qmldir</file>
|
||||||
<file>imports/NeoChat/Component/Emoji/EmojiPicker.qml</file>
|
<file>imports/NeoChat/Component/Emoji/EmojiPicker.qml</file>
|
||||||
<file>imports/NeoChat/Component/Emoji/qmldir</file>
|
<file>imports/NeoChat/Component/Emoji/qmldir</file>
|
||||||
@@ -44,7 +38,6 @@
|
|||||||
<file>imports/NeoChat/Component/Timeline/AudioDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/AudioDelegate.qml</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/FileDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/FileDelegate.qml</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/ImageDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/ImageDelegate.qml</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/EncryptedDelegate.qml</file>
|
|
||||||
<file>imports/NeoChat/Component/Login/qmldir</file>
|
<file>imports/NeoChat/Component/Login/qmldir</file>
|
||||||
<file>imports/NeoChat/Component/Login/LoginStep.qml</file>
|
<file>imports/NeoChat/Component/Login/LoginStep.qml</file>
|
||||||
<file>imports/NeoChat/Component/Login/Login.qml</file>
|
<file>imports/NeoChat/Component/Login/Login.qml</file>
|
||||||
@@ -57,13 +50,12 @@
|
|||||||
<file>imports/NeoChat/Panel/qmldir</file>
|
<file>imports/NeoChat/Panel/qmldir</file>
|
||||||
<file>imports/NeoChat/Panel/RoomDrawer.qml</file>
|
<file>imports/NeoChat/Panel/RoomDrawer.qml</file>
|
||||||
<file>imports/NeoChat/Dialog/qmldir</file>
|
<file>imports/NeoChat/Dialog/qmldir</file>
|
||||||
|
<file>imports/NeoChat/Dialog/RoomSettingsDialog.qml</file>
|
||||||
<file>imports/NeoChat/Dialog/UserDetailDialog.qml</file>
|
<file>imports/NeoChat/Dialog/UserDetailDialog.qml</file>
|
||||||
<file>imports/NeoChat/Dialog/CreateRoomDialog.qml</file>
|
<file>imports/NeoChat/Dialog/CreateRoomDialog.qml</file>
|
||||||
<file>imports/NeoChat/Dialog/EmojiDialog.qml</file>
|
<file>imports/NeoChat/Dialog/EmojiDialog.qml</file>
|
||||||
<file>imports/NeoChat/Dialog/OpenFileDialog.qml</file>
|
<file>imports/NeoChat/Dialog/OpenFileDialog.qml</file>
|
||||||
<file>imports/NeoChat/Menu/qmldir</file>
|
<file>imports/NeoChat/Menu/qmldir</file>
|
||||||
<file>imports/NeoChat/Menu/GlobalMenu.qml</file>
|
|
||||||
<file>imports/NeoChat/Menu/EditMenu.qml</file>
|
|
||||||
<file>imports/NeoChat/Menu/Timeline/qmldir</file>
|
<file>imports/NeoChat/Menu/Timeline/qmldir</file>
|
||||||
<file>imports/NeoChat/Menu/Timeline/MessageDelegateContextMenu.qml</file>
|
<file>imports/NeoChat/Menu/Timeline/MessageDelegateContextMenu.qml</file>
|
||||||
<file>imports/NeoChat/Menu/Timeline/FileDelegateContextMenu.qml</file>
|
<file>imports/NeoChat/Menu/Timeline/FileDelegateContextMenu.qml</file>
|
||||||
@@ -72,15 +64,5 @@
|
|||||||
<file>qtquickcontrols2.conf</file>
|
<file>qtquickcontrols2.conf</file>
|
||||||
<file>imports/NeoChat/Component/glowdot.png</file>
|
<file>imports/NeoChat/Component/glowdot.png</file>
|
||||||
<file>imports/NeoChat/Component/confetti.png</file>
|
<file>imports/NeoChat/Component/confetti.png</file>
|
||||||
<file>imports/NeoChat/Settings/SettingsPage.qml</file>
|
|
||||||
<file>imports/NeoChat/Settings/ThemeRadioButton.qml</file>
|
|
||||||
<file>imports/NeoChat/Settings/ColorScheme.qml</file>
|
|
||||||
<file>imports/NeoChat/Settings/GeneralSettingsPage.qml</file>
|
|
||||||
<file>imports/NeoChat/Settings/Emoticons.qml</file>
|
|
||||||
<file>imports/NeoChat/Settings/AppearanceSettingsPage.qml</file>
|
|
||||||
<file>imports/NeoChat/Settings/AccountsPage.qml</file>
|
|
||||||
<file>imports/NeoChat/Settings/DevicesPage.qml</file>
|
|
||||||
<file>imports/NeoChat/Settings/About.qml</file>
|
|
||||||
<file>imports/NeoChat/Settings/qmldir</file>
|
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ add_executable(neochat
|
|||||||
controller.cpp
|
controller.cpp
|
||||||
actionshandler.cpp
|
actionshandler.cpp
|
||||||
emojimodel.cpp
|
emojimodel.cpp
|
||||||
customemojimodel.cpp
|
|
||||||
customemojimodel+network.cpp
|
|
||||||
clipboard.cpp
|
clipboard.cpp
|
||||||
matriximageprovider.cpp
|
matriximageprovider.cpp
|
||||||
messageeventmodel.cpp
|
messageeventmodel.cpp
|
||||||
@@ -32,21 +30,6 @@ add_executable(neochat
|
|||||||
stickerevent.cpp
|
stickerevent.cpp
|
||||||
chatboxhelper.cpp
|
chatboxhelper.cpp
|
||||||
commandmodel.cpp
|
commandmodel.cpp
|
||||||
webshortcutmodel.cpp
|
|
||||||
spellcheckhighlighter.cpp
|
|
||||||
blurhash.cpp
|
|
||||||
blurhashimageprovider.cpp
|
|
||||||
joinrulesevent.cpp
|
|
||||||
voip/audiosources.cpp
|
|
||||||
voip/devicemonitor.cpp
|
|
||||||
voip/pipelinemanager.cpp
|
|
||||||
voip/screencast.cpp
|
|
||||||
voip/videosources.cpp
|
|
||||||
voip/waylandscreencast.cpp
|
|
||||||
voip/webrtcfoo.cpp
|
|
||||||
voip/windowmodel.cpp
|
|
||||||
voip/xscreencast.cpp
|
|
||||||
voip/callhandler.cpp
|
|
||||||
../res.qrc
|
../res.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,19 +42,11 @@ ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
|
|||||||
target_sources(neochat PRIVATE ${NEOCHAT_ICON})
|
target_sources(neochat PRIVATE ${NEOCHAT_ICON})
|
||||||
|
|
||||||
if(NOT ANDROID)
|
if(NOT ANDROID)
|
||||||
target_sources(neochat PRIVATE colorschemer.cpp)
|
target_sources(neochat PRIVATE trayicon.cpp)
|
||||||
if (NOT WIN32 AND NOT APPLE)
|
|
||||||
target_sources(neochat PRIVATE trayicon_sni.cpp)
|
|
||||||
else()
|
|
||||||
target_sources(neochat PRIVATE trayicon.cpp)
|
|
||||||
endif()
|
|
||||||
target_link_libraries(neochat PRIVATE KF5::ConfigWidgets KF5::WindowSystem KF5::SonnetCore)
|
|
||||||
target_compile_definitions(neochat PRIVATE -DHAVE_COLORSCHEME)
|
|
||||||
target_compile_definitions(neochat PRIVATE -DHAVE_WINDOWSYSTEM)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
||||||
target_link_libraries(neochat PRIVATE Qt::Quick Qt::Qml Qt::Gui Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::QCoro PkgConfig::GSTREAMER)
|
target_link_libraries(neochat PRIVATE Qt::Quick Qt::Qml Qt::Gui Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES})
|
||||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||||
|
|
||||||
if(NEOCHAT_FLATPAK)
|
if(NEOCHAT_FLATPAK)
|
||||||
@@ -81,7 +56,6 @@ endif()
|
|||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
target_link_libraries(neochat PRIVATE Qt5::Svg OpenSSL::SSL)
|
target_link_libraries(neochat PRIVATE Qt5::Svg OpenSSL::SSL)
|
||||||
kirigami_package_breeze_icons(ICONS
|
kirigami_package_breeze_icons(ICONS
|
||||||
"arrow-down"
|
|
||||||
"help-about"
|
"help-about"
|
||||||
"im-user"
|
"im-user"
|
||||||
"im-invisible-user"
|
"im-invisible-user"
|
||||||
@@ -115,13 +89,9 @@ if(ANDROID)
|
|||||||
"gtk-quit"
|
"gtk-quit"
|
||||||
"compass"
|
"compass"
|
||||||
"network-connect"
|
"network-connect"
|
||||||
"list-remove-user"
|
|
||||||
"org.kde.neochat"
|
|
||||||
"preferences-system-users"
|
|
||||||
"preferences-desktop-theme-global"
|
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
target_link_libraries(neochat PRIVATE Qt5::Widgets KF5::KIOWidgets)
|
target_link_libraries(neochat PRIVATE Qt5::Widgets)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(TARGET KF5::DBusAddons)
|
if(TARGET KF5::DBusAddons)
|
||||||
@@ -129,8 +99,4 @@ if(TARGET KF5::DBusAddons)
|
|||||||
target_compile_definitions(neochat PRIVATE -DHAVE_KDBUSADDONS)
|
target_compile_definitions(neochat PRIVATE -DHAVE_KDBUSADDONS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (TARGET KF5::KIOWidgets)
|
|
||||||
target_compile_definitions(neochat PRIVATE -DHAVE_KIO)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
install(TARGETS neochat ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})
|
install(TARGETS neochat ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
|
|||||||
@@ -8,13 +8,31 @@
|
|||||||
AccountListModel::AccountListModel(QObject *parent)
|
AccountListModel::AccountListModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
connect(&Controller::instance(), &Controller::connectionAdded, this, [=]() {
|
m_connections.clear();
|
||||||
beginResetModel();
|
m_connections += Controller::instance().connections();
|
||||||
endResetModel();
|
|
||||||
|
connect(&Controller::instance(), &Controller::connectionAdded, this, [=](Connection *conn) {
|
||||||
|
if (!conn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
beginInsertRows(QModelIndex(), m_connections.count(), m_connections.count());
|
||||||
|
m_connections.append(conn);
|
||||||
|
endInsertRows();
|
||||||
});
|
});
|
||||||
connect(&Controller::instance(), &Controller::connectionDropped, this, [=]() {
|
connect(&Controller::instance(), &Controller::connectionDropped, this, [=](Connection *conn) {
|
||||||
beginResetModel();
|
if (!conn) {
|
||||||
endResetModel();
|
qDebug() << "Trying to remove null connection";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
conn->disconnect(this);
|
||||||
|
const auto it = std::find(m_connections.begin(), m_connections.end(), conn);
|
||||||
|
if (it == m_connections.end()) {
|
||||||
|
return; // Already deleted, nothing to do
|
||||||
|
}
|
||||||
|
const int row = it - m_connections.begin();
|
||||||
|
beginRemoveRows(QModelIndex(), row, row);
|
||||||
|
m_connections.erase(it);
|
||||||
|
endRemoveRows();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,17 +42,19 @@ QVariant AccountListModel::data(const QModelIndex &index, int role) const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index.row() >= Controller::instance().connections().count()) {
|
if (index.row() >= m_connections.count()) {
|
||||||
|
qDebug() << "AccountListModel, something's wrong: index.row() >= "
|
||||||
|
"m_users.count()";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto connection = Controller::instance().connections().at(index.row());
|
auto m_connection = m_connections.at(index.row());
|
||||||
|
|
||||||
if (role == UserRole) {
|
if (role == UserRole) {
|
||||||
return QVariant::fromValue(connection->user());
|
return QVariant::fromValue(m_connection->user());
|
||||||
}
|
}
|
||||||
if (role == ConnectionRole) {
|
if (role == ConnectionRole) {
|
||||||
return QVariant::fromValue(connection);
|
return QVariant::fromValue(m_connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
@@ -46,7 +66,7 @@ int AccountListModel::rowCount(const QModelIndex &parent) const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Controller::instance().connections().count();
|
return m_connections.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> AccountListModel::roleNames() const
|
QHash<int, QByteArray> AccountListModel::roleNames() const
|
||||||
|
|||||||
@@ -23,4 +23,7 @@ public:
|
|||||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QVector<Connection *> m_connections;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include <QStringBuilder>
|
#include <QStringBuilder>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "customemojimodel.h"
|
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
|
|
||||||
ActionsHandler::ActionsHandler(QObject *parent)
|
ActionsHandler::ActionsHandler(QObject *parent)
|
||||||
@@ -62,36 +61,28 @@ void ActionsHandler::setConnection(Connection *connection)
|
|||||||
|
|
||||||
void ActionsHandler::postEdit(const QString &text)
|
void ActionsHandler::postEdit(const QString &text)
|
||||||
{
|
{
|
||||||
|
|
||||||
const auto localId = Controller::instance().activeConnection()->userId();
|
const auto localId = Controller::instance().activeConnection()->userId();
|
||||||
for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); ++it) {
|
for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); ++it) {
|
||||||
const auto &evt = **it;
|
const auto &evt = **it;
|
||||||
if (const auto event = eventCast<const RoomMessageEvent>(&evt)) {
|
if (const auto event = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
if (event->senderId() == localId && event->hasTextContent()) {
|
if (event->senderId() == localId && event->hasTextContent()) {
|
||||||
static QRegularExpression re("^s/([^/]*)/([^/]*)(/g)?");
|
static QRegularExpression re("^s/([^/]*)/([^/]*)");
|
||||||
auto match = re.match(text);
|
auto match = re.match(text);
|
||||||
if (!match.hasMatch()) {
|
if (!match.hasMatch()) {
|
||||||
// should not happen but still make sure to send the message normally
|
// should not happen but still make sure to send the message normally
|
||||||
// just in case.
|
// just in case.
|
||||||
postMessage(text, QString(), QString(), QString(), QVariantMap(), nullptr);
|
postMessage(text, QString(), QString(), QString(), QVariantMap());
|
||||||
}
|
}
|
||||||
const QString regex = match.captured(1);
|
const QString regex = match.captured(1);
|
||||||
const QString replacement = match.captured(2);
|
const QString replacement = match.captured(2);
|
||||||
const QString flags = match.captured(3);
|
|
||||||
QString originalString;
|
QString originalString;
|
||||||
if (event->content()) {
|
if (event->content()) {
|
||||||
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
|
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
|
||||||
} else {
|
} else {
|
||||||
originalString = event->plainBody();
|
originalString = event->plainBody();
|
||||||
}
|
}
|
||||||
if (flags == "/g") {
|
m_room->postHtmlMessage(text, originalString.replace(regex, replacement), event->msgtype(), "", event->id());
|
||||||
m_room->postHtmlMessage(text, originalString.replace(regex, replacement), event->msgtype(), "", event->id());
|
|
||||||
} else {
|
|
||||||
m_room->postHtmlMessage(text,
|
|
||||||
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
|
|
||||||
event->msgtype(),
|
|
||||||
"",
|
|
||||||
event->id());
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,41 +90,20 @@ void ActionsHandler::postEdit(const QString &text)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ActionsHandler::postMessage(const QString &text,
|
void ActionsHandler::postMessage(const QString &text,
|
||||||
const QString &attachmentPath,
|
const QString &attachementPath,
|
||||||
const QString &replyEventId,
|
const QString &replyEventId,
|
||||||
const QString &editEventId,
|
const QString &editEventId,
|
||||||
const QVariantMap &usernames,
|
const QVariantMap &usernames)
|
||||||
CustomEmojiModel *cem)
|
|
||||||
{
|
{
|
||||||
QString rawText = text;
|
QString rawText = text;
|
||||||
auto stringList = text.split(QStringLiteral("```"));
|
QString cleanedText = text;
|
||||||
QString cleanedText;
|
|
||||||
const auto count = stringList.count();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
if (i % 2 == 0) {
|
|
||||||
if (i + 1 != count) {
|
|
||||||
cleanedText += stringList[i].toHtmlEscaped() + QStringLiteral("```");
|
|
||||||
} else {
|
|
||||||
cleanedText += stringList[i].toHtmlEscaped();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cleanedText += stringList[i] + QStringLiteral("```");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto preprocess = [cem](const QString &it) -> QString {
|
|
||||||
if (cem == nullptr) {
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
return cem->preprocessText(it);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (auto it = usernames.constBegin(); it != usernames.constEnd(); it++) {
|
for (auto it = usernames.constBegin(); it != usernames.constEnd(); it++) {
|
||||||
cleanedText = cleanedText.replace(it.key(), "[" + it.key() + "](https://matrix.to/#/" + it.value().toString() + ")");
|
cleanedText = cleanedText.replace(it.key(), "[" + it.key() + "](https://matrix.to/#/" + it.value().toString() + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attachmentPath.length() > 0) {
|
if (attachementPath.length() > 0) {
|
||||||
m_room->uploadFile(attachmentPath, cleanedText);
|
m_room->uploadFile(attachementPath, cleanedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleanedText.length() == 0) {
|
if (cleanedText.length() == 0) {
|
||||||
@@ -145,32 +115,27 @@ void ActionsHandler::postMessage(const QString &text,
|
|||||||
// Message commands
|
// Message commands
|
||||||
static const QString shrugPrefix = QStringLiteral("/shrug");
|
static const QString shrugPrefix = QStringLiteral("/shrug");
|
||||||
static const QString lennyPrefix = QStringLiteral("/lenny");
|
static const QString lennyPrefix = QStringLiteral("/lenny");
|
||||||
static const QString tableflipPrefix = QStringLiteral("/tableflip");
|
static const QString plainPrefix = QStringLiteral("/plain "); // TODO
|
||||||
static const QString unflipPrefix = QStringLiteral("/unflip");
|
static const QString htmlPrefix = QStringLiteral("/html "); // TODO
|
||||||
// static const QString plainPrefix = QStringLiteral("/plain "); // TODO
|
|
||||||
// static const QString htmlPrefix = QStringLiteral("/html "); // TODO
|
|
||||||
static const QString rainbowPrefix = QStringLiteral("/rainbow ");
|
static const QString rainbowPrefix = QStringLiteral("/rainbow ");
|
||||||
static const QString rainbowmePrefix = QStringLiteral("/rainbowme ");
|
static const QString rainbowmePrefix = QStringLiteral("/rainbowme ");
|
||||||
static const QString spoilerPrefix = QStringLiteral("/spoiler ");
|
|
||||||
static const QString mePrefix = QStringLiteral("/me ");
|
static const QString mePrefix = QStringLiteral("/me ");
|
||||||
static const QString noticePrefix = QStringLiteral("/notice ");
|
static const QString noticePrefix = QStringLiteral("/notice ");
|
||||||
|
|
||||||
// Actions commands
|
// Actions commands
|
||||||
// static const QString ddgPrefix = QStringLiteral("/ddg "); // TODO
|
static const QString ddgPrefix = QStringLiteral("/ddg "); // TODO
|
||||||
// static const QString nickPrefix = QStringLiteral("/nick "); // TODO
|
static const QString nickPrefix = QStringLiteral("/nick "); // TODO
|
||||||
// static const QString meroomnickPrefix = QStringLiteral("/myroomnick "); // TODO
|
static const QString meroomnickPrefix = QStringLiteral("/myroomnick "); // TODO
|
||||||
// static const QString roomavatarPrefix = QStringLiteral("/roomavatar "); // TODO
|
static const QString roomavatarPrefix = QStringLiteral("/roomavatar "); // TODO
|
||||||
// static const QString myroomavatarPrefix = QStringLiteral("/myroomavatar "); // TODO
|
static const QString myroomavatarPrefix = QStringLiteral("/myroomavatar "); // TODO
|
||||||
// static const QString myavatarPrefix = QStringLiteral("/myavatar "); // TODO
|
static const QString myavatarPrefix = QStringLiteral("/myavatar "); // TODO
|
||||||
static const QString invitePrefix = QStringLiteral("/invite ");
|
static const QString invitePrefix = QStringLiteral("/invite ");
|
||||||
static const QString joinPrefix = QStringLiteral("/join ");
|
static const QString joinPrefix = QStringLiteral("/join ");
|
||||||
static const QString joinShortPrefix = QStringLiteral("/j ");
|
|
||||||
static const QString partPrefix = QStringLiteral("/part");
|
static const QString partPrefix = QStringLiteral("/part");
|
||||||
static const QString leavePrefix = QStringLiteral("/leave");
|
|
||||||
static const QString ignorePrefix = QStringLiteral("/ignore ");
|
static const QString ignorePrefix = QStringLiteral("/ignore ");
|
||||||
static const QString unignorePrefix = QStringLiteral("/unignore ");
|
static const QString unignorePrefix = QStringLiteral("/unignore ");
|
||||||
// static const QString queryPrefix = QStringLiteral("/query "); // TODO
|
static const QString queryPrefix = QStringLiteral("/query "); // TODO
|
||||||
// static const QString msgPrefix = QStringLiteral("/msg "); // TODO
|
static const QString msgPrefix = QStringLiteral("/msg "); // TODO
|
||||||
static const QString reactPrefix = QStringLiteral("/react ");
|
static const QString reactPrefix = QStringLiteral("/react ");
|
||||||
|
|
||||||
// Admin commands
|
// Admin commands
|
||||||
@@ -192,33 +157,13 @@ void ActionsHandler::postMessage(const QString &text,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleanedText.indexOf(tableflipPrefix) == 0) {
|
|
||||||
cleanedText = QStringLiteral("(╯°□°)╯︵ ┻━┻") % cleanedText.remove(0, tableflipPrefix.length());
|
|
||||||
m_room->postHtmlMessage(cleanedText, cleanedText, messageEventType, replyEventId, editEventId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanedText.indexOf(unflipPrefix) == 0) {
|
|
||||||
cleanedText = QStringLiteral("┬──┬ ノ( ゜-゜ノ)") % cleanedText.remove(0, unflipPrefix.length());
|
|
||||||
m_room->postHtmlMessage(cleanedText, cleanedText, messageEventType, replyEventId, editEventId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanedText.indexOf(rainbowPrefix) == 0) {
|
if (cleanedText.indexOf(rainbowPrefix) == 0) {
|
||||||
cleanedText = cleanedText.remove(0, rainbowPrefix.length());
|
cleanedText = cleanedText.remove(0, rainbowPrefix.length());
|
||||||
QString rainbowText;
|
QString rainbowText;
|
||||||
for (int i = 0; i < cleanedText.length(); i++) {
|
for (int i = 0; i < cleanedText.length(); i++) {
|
||||||
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
||||||
}
|
}
|
||||||
m_room->postHtmlMessage(cleanedText, preprocess(rainbowText), RoomMessageEvent::MsgType::Notice, replyEventId, editEventId);
|
m_room->postHtmlMessage(cleanedText, rainbowText, RoomMessageEvent::MsgType::Notice, replyEventId, editEventId);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanedText.indexOf(spoilerPrefix) == 0) {
|
|
||||||
cleanedText = cleanedText.remove(0, spoilerPrefix.length());
|
|
||||||
const QStringList splittedText = rawText.split(" ");
|
|
||||||
QString spoilerHtml = QStringLiteral("<span data-mx-spoiler>") % preprocess(cleanedText) % QStringLiteral("</span>");
|
|
||||||
m_room->postHtmlMessage(cleanedText, spoilerHtml, RoomMessageEvent::MsgType::Notice, replyEventId, editEventId);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,16 +173,12 @@ void ActionsHandler::postMessage(const QString &text,
|
|||||||
for (int i = 0; i < cleanedText.length(); i++) {
|
for (int i = 0; i < cleanedText.length(); i++) {
|
||||||
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
||||||
}
|
}
|
||||||
m_room->postHtmlMessage(cleanedText, preprocess(rainbowText), messageEventType, replyEventId, editEventId);
|
m_room->postHtmlMessage(cleanedText, rainbowText, messageEventType, replyEventId, editEventId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawText.indexOf(joinPrefix) == 0 || rawText.indexOf(joinShortPrefix) == 0) {
|
if (rawText.indexOf(joinPrefix) == 0) {
|
||||||
if (rawText.indexOf(joinPrefix) == 0) {
|
rawText = rawText.remove(0, joinPrefix.length());
|
||||||
rawText = rawText.remove(0, joinPrefix.length());
|
|
||||||
} else {
|
|
||||||
rawText = rawText.remove(0, joinShortPrefix.length());
|
|
||||||
}
|
|
||||||
const QStringList splittedText = rawText.split(" ");
|
const QStringList splittedText = rawText.split(" ");
|
||||||
if (text.count() == 0) {
|
if (text.count() == 0) {
|
||||||
Q_EMIT showMessage(MessageType::Error, i18n("Invalid command"));
|
Q_EMIT showMessage(MessageType::Error, i18n("Invalid command"));
|
||||||
@@ -266,12 +207,8 @@ void ActionsHandler::postMessage(const QString &text,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawText.indexOf(partPrefix) == 0 || rawText.indexOf(leavePrefix) == 0) {
|
if (rawText.indexOf(partPrefix) == 0) {
|
||||||
if (rawText.indexOf(partPrefix) == 0) {
|
rawText = rawText.remove(0, partPrefix.length());
|
||||||
rawText = rawText.remove(0, partPrefix.length());
|
|
||||||
} else {
|
|
||||||
rawText = rawText.remove(0, leavePrefix.length());
|
|
||||||
}
|
|
||||||
const QStringList splittedText = rawText.split(" ");
|
const QStringList splittedText = rawText.split(" ");
|
||||||
if (splittedText.count() == 0 || splittedText[0].isEmpty()) {
|
if (splittedText.count() == 0 || splittedText[0].isEmpty()) {
|
||||||
// leave current room
|
// leave current room
|
||||||
@@ -319,18 +256,10 @@ void ActionsHandler::postMessage(const QString &text,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (rawText.indexOf(reactPrefix) == 0) {
|
if (rawText.indexOf(reactPrefix) == 0) {
|
||||||
rawText = rawText.remove(0, reactPrefix.length());
|
|
||||||
if (replyEventId.isEmpty()) {
|
if (replyEventId.isEmpty()) {
|
||||||
for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); ++it) {
|
|
||||||
const auto &evt = **it;
|
|
||||||
if (const auto event = eventCast<const RoomMessageEvent>(&evt)) {
|
|
||||||
m_room->toggleReaction(event->id(), rawText);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Q_EMIT showMessage(MessageType::Error, i18n("Couldn't find a message to react to"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
rawText = rawText.remove(0, reactPrefix.length());
|
||||||
m_room->toggleReaction(replyEventId, rawText);
|
m_room->toggleReaction(replyEventId, rawText);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -343,5 +272,5 @@ void ActionsHandler::postMessage(const QString &text,
|
|||||||
cleanedText = cleanedText.remove(0, noticePrefix.length());
|
cleanedText = cleanedText.remove(0, noticePrefix.length());
|
||||||
messageEventType = RoomMessageEvent::MsgType::Notice;
|
messageEventType = RoomMessageEvent::MsgType::Notice;
|
||||||
}
|
}
|
||||||
m_room->postMessage(rawText, preprocess(m_room->preprocessText(cleanedText)), messageEventType, replyEventId, editEventId);
|
m_room->postMessage(rawText, cleanedText, messageEventType, replyEventId, editEventId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,13 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
class CustomEmojiModel;
|
|
||||||
|
|
||||||
/// \brief Handles user interactions with NeoChat (joining room, creating room,
|
/// \brief Handles user interactions with NeoChat (joining room, creating room,
|
||||||
/// sending message). Account management is handled by Controller.
|
/// sending message). Account management is handled by Controller.
|
||||||
class ActionsHandler : public QObject
|
class ActionsHandler : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|
||||||
/// \brief The connection that will handle sending the message.
|
/// \brief The connection that will handle sending the message.
|
||||||
Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||||
|
|
||||||
@@ -34,6 +33,8 @@ public:
|
|||||||
explicit ActionsHandler(QObject *parent = nullptr);
|
explicit ActionsHandler(QObject *parent = nullptr);
|
||||||
~ActionsHandler();
|
~ActionsHandler();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[nodiscard]] Connection *connection() const;
|
[[nodiscard]] Connection *connection() const;
|
||||||
void setConnection(Connection *connection);
|
void setConnection(Connection *connection);
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ Q_SIGNALS:
|
|||||||
/// \brief Show error or information message.
|
/// \brief Show error or information message.
|
||||||
///
|
///
|
||||||
/// These messages will be displayed in the room view header.
|
/// These messages will be displayed in the room view header.
|
||||||
void showMessage(ActionsHandler::MessageType messageType, QString message);
|
void showMessage(MessageType messageType, QString message);
|
||||||
|
|
||||||
void roomChanged();
|
void roomChanged();
|
||||||
void connectionChanged();
|
void connectionChanged();
|
||||||
@@ -54,12 +55,8 @@ public Q_SLOTS:
|
|||||||
/// \brief Post a message.
|
/// \brief Post a message.
|
||||||
///
|
///
|
||||||
/// This also interprets commands if any.
|
/// This also interprets commands if any.
|
||||||
void postMessage(const QString &text,
|
void
|
||||||
const QString &attachementPath,
|
postMessage(const QString &text, const QString &attachementPath, const QString &replyEventId, const QString &editEventId, const QVariantMap &usernames);
|
||||||
const QString &replyEventId,
|
|
||||||
const QString &editEventId,
|
|
||||||
const QVariantMap &usernames,
|
|
||||||
CustomEmojiModel *cem);
|
|
||||||
|
|
||||||
/// \brief Send edit instructions (.e.g s/hallo/hello/)
|
/// \brief Send edit instructions (.e.g s/hallo/hello/)
|
||||||
///
|
///
|
||||||
|
|||||||
194
src/blurhash.cpp
194
src/blurhash.cpp
@@ -1,194 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2018 Wolt Enterprises
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#include "blurhash.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
const char chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~";
|
|
||||||
|
|
||||||
struct Color {
|
|
||||||
float r = 0;
|
|
||||||
float g = 0;
|
|
||||||
float b = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline int linearTosRGB(float value)
|
|
||||||
{
|
|
||||||
float v = fmaxf(0, fminf(1, value));
|
|
||||||
if (v <= 0.0031308)
|
|
||||||
return v * 12.92 * 255 + 0.5;
|
|
||||||
else
|
|
||||||
return (1.055 * powf(v, 1 / 2.4) - 0.055) * 255 + 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline float sRGBToLinear(int value)
|
|
||||||
{
|
|
||||||
float v = (float)value / 255;
|
|
||||||
if (v <= 0.04045)
|
|
||||||
return v / 12.92;
|
|
||||||
else
|
|
||||||
return powf((v + 0.055) / 1.055, 2.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline float signPow(float value, float exp)
|
|
||||||
{
|
|
||||||
return copysignf(powf(fabsf(value), exp), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint8_t clampToUByte(int *src)
|
|
||||||
{
|
|
||||||
if (*src >= 0 && *src <= 255) {
|
|
||||||
return *src;
|
|
||||||
}
|
|
||||||
return (*src < 0) ? 0 : 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint8_t *createByteArray(int size)
|
|
||||||
{
|
|
||||||
return (uint8_t *)malloc(size * sizeof(uint8_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
int decodeToInt(const char *string, int start, int end)
|
|
||||||
{
|
|
||||||
int value = 0;
|
|
||||||
for (int iter1 = start; iter1 < end; iter1++) {
|
|
||||||
int index = -1;
|
|
||||||
for (int iter2 = 0; iter2 < 83; iter2++) {
|
|
||||||
if (chars[iter2] == string[iter1]) {
|
|
||||||
index = iter2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (index == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
value = value * 83 + index;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void decodeDC(int value, Color *color)
|
|
||||||
{
|
|
||||||
color->r = sRGBToLinear(value >> 16);
|
|
||||||
color->g = sRGBToLinear((value >> 8) & 255);
|
|
||||||
color->b = sRGBToLinear(value & 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
void decodeAC(int value, float maximumValue, Color *color)
|
|
||||||
{
|
|
||||||
int quantR = (int)floorf(value / (19 * 19));
|
|
||||||
int quantG = (int)floorf(value / 19) % 19;
|
|
||||||
int quantB = (int)value % 19;
|
|
||||||
|
|
||||||
color->r = signPow(((float)quantR - 9) / 9, 2.0) * maximumValue;
|
|
||||||
color->g = signPow(((float)quantG - 9) / 9, 2.0) * maximumValue;
|
|
||||||
color->b = signPow(((float)quantB - 9) / 9, 2.0) * maximumValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int decodeToArray(const char *blurhash, int width, int height, int punch, int nChannels, uint8_t *pixelArray)
|
|
||||||
{
|
|
||||||
if (!isValidBlurhash(blurhash)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (punch < 1) {
|
|
||||||
punch = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sizeFlag = decodeToInt(blurhash, 0, 1);
|
|
||||||
int numY = (int)floorf(sizeFlag / 9) + 1;
|
|
||||||
int numX = (sizeFlag % 9) + 1;
|
|
||||||
int iter = 0;
|
|
||||||
|
|
||||||
Color color;
|
|
||||||
int quantizedMaxValue = decodeToInt(blurhash, 1, 2);
|
|
||||||
if (quantizedMaxValue == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float maxValue = ((float)(quantizedMaxValue + 1)) / 166;
|
|
||||||
|
|
||||||
const int colors_size = numX * numY;
|
|
||||||
|
|
||||||
std::vector<Color> colors(colors_size, {0, 0, 0});
|
|
||||||
|
|
||||||
for (iter = 0; iter < colors_size; iter++) {
|
|
||||||
if (iter == 0) {
|
|
||||||
int value = decodeToInt(blurhash, 2, 6);
|
|
||||||
if (value == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
decodeDC(value, &color);
|
|
||||||
colors[iter] = color;
|
|
||||||
} else {
|
|
||||||
int value = decodeToInt(blurhash, 4 + iter * 2, 6 + iter * 2);
|
|
||||||
if (value == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
decodeAC(value, maxValue * punch, &color);
|
|
||||||
colors[iter] = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int bytesPerRow = width * nChannels;
|
|
||||||
int x = 0, y = 0, i = 0, j = 0;
|
|
||||||
int intR = 0, intG = 0, intB = 0;
|
|
||||||
|
|
||||||
for (y = 0; y < height; y++) {
|
|
||||||
for (x = 0; x < width; x++) {
|
|
||||||
float r = 0, g = 0, b = 0;
|
|
||||||
|
|
||||||
for (j = 0; j < numY; j++) {
|
|
||||||
for (i = 0; i < numX; i++) {
|
|
||||||
float basics = cos((M_PI * x * i) / width) * cos((M_PI * y * j) / height);
|
|
||||||
int idx = i + j * numX;
|
|
||||||
r += colors[idx].r * basics;
|
|
||||||
g += colors[idx].g * basics;
|
|
||||||
b += colors[idx].b * basics;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
intR = linearTosRGB(r);
|
|
||||||
intG = linearTosRGB(g);
|
|
||||||
intB = linearTosRGB(b);
|
|
||||||
|
|
||||||
pixelArray[nChannels * x + 0 + y * bytesPerRow] = clampToUByte(&intR);
|
|
||||||
pixelArray[nChannels * x + 1 + y * bytesPerRow] = clampToUByte(&intG);
|
|
||||||
pixelArray[nChannels * x + 2 + y * bytesPerRow] = clampToUByte(&intB);
|
|
||||||
|
|
||||||
if (nChannels == 4) {
|
|
||||||
pixelArray[nChannels * x + 3 + y * bytesPerRow] = 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t *decode(const char *blurhash, int width, int height, int punch, int nChannels)
|
|
||||||
{
|
|
||||||
int bytesPerRow = width * nChannels;
|
|
||||||
uint8_t *pixelArray = createByteArray(bytesPerRow * height);
|
|
||||||
|
|
||||||
if (decodeToArray(blurhash, width, height, punch, nChannels, pixelArray) == -1) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return pixelArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isValidBlurhash(const char *blurhash)
|
|
||||||
{
|
|
||||||
const int hashLength = strlen(blurhash);
|
|
||||||
|
|
||||||
if (!blurhash || strlen(blurhash) < 6) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sizeFlag = decodeToInt(blurhash, 0, 1);
|
|
||||||
int numY = (int)floorf(sizeFlag / 9) + 1;
|
|
||||||
int numX = (sizeFlag % 9) + 1;
|
|
||||||
|
|
||||||
return hashLength == 4 + 2 * numX * numY;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2018 Wolt Enterprises
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
uint8_t *decode(const char *blurhash, int width, int height, int punch, int nChannels);
|
|
||||||
|
|
||||||
bool isValidBlurhash(const char *blurhash);
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "blurhashimageprovider.h"
|
|
||||||
|
|
||||||
#include <QImage>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include "blurhash.h"
|
|
||||||
|
|
||||||
BlurhashImageProvider::BlurhashImageProvider()
|
|
||||||
: QQuickImageProvider(QQuickImageProvider::Image)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage BlurhashImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
|
|
||||||
{
|
|
||||||
if (id.isEmpty()) {
|
|
||||||
return QImage();
|
|
||||||
}
|
|
||||||
*size = requestedSize;
|
|
||||||
if (size->width() == -1) {
|
|
||||||
size->setWidth(256);
|
|
||||||
}
|
|
||||||
if (size->height() == -1) {
|
|
||||||
size->setHeight(256);
|
|
||||||
}
|
|
||||||
auto data = decode(QUrl::fromPercentEncoding(id.toLatin1()).toLatin1().data(), size->width(), size->height(), 1, 3);
|
|
||||||
QImage image(data, size->width(), size->height(), size->width() * 3, QImage::Format_RGB888, free, data);
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QQuickImageProvider>
|
|
||||||
|
|
||||||
class BlurhashImageProvider : public QQuickImageProvider
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
BlurhashImageProvider();
|
|
||||||
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
|
|
||||||
};
|
|
||||||
@@ -16,8 +16,6 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
|
|||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_document(nullptr)
|
, m_document(nullptr)
|
||||||
, m_cursorPosition(-1)
|
, m_cursorPosition(-1)
|
||||||
, m_selectionStart(-1)
|
|
||||||
, m_selectionEnd(-1)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +123,7 @@ void ChatDocumentHandler::setRoom(NeoChatRoom *room)
|
|||||||
Q_EMIT roomChanged();
|
Q_EMIT roomChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap ChatDocumentHandler::getAutocompletionInfo(bool isAutocompleting)
|
QVariantMap ChatDocumentHandler::getAutocompletionInfo()
|
||||||
{
|
{
|
||||||
QTextCursor cursor = textCursor();
|
QTextCursor cursor = textCursor();
|
||||||
|
|
||||||
@@ -135,6 +133,9 @@ QVariantMap ChatDocumentHandler::getAutocompletionInfo(bool isAutocompleting)
|
|||||||
{"type", AutoCompletionType::Ignore},
|
{"type", AutoCompletionType::Ignore},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (m_cursorPosition != m_autoCompleteBeginPosition && m_cursorPosition != m_autoCompleteEndPosition) {
|
||||||
|
// we moved our cursor, so cancel autocompletion
|
||||||
|
}
|
||||||
|
|
||||||
QString text = cursor.block().text();
|
QString text = cursor.block().text();
|
||||||
QString textBeforeCursor = text;
|
QString textBeforeCursor = text;
|
||||||
@@ -166,17 +167,10 @@ QVariantMap ChatDocumentHandler::getAutocompletionInfo(bool isAutocompleting)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAutocompleting) {
|
return QVariantMap{
|
||||||
return QVariantMap{
|
{"keyword", autoCompletePrefix},
|
||||||
{"keyword", autoCompletePrefix},
|
{"type", AutoCompletionType::Emoji},
|
||||||
{"type", AutoCompletionType::Emoji},
|
};
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return QVariantMap{
|
|
||||||
{"type", AutoCompletionType::Ignore},
|
|
||||||
{"keyword", autoCompletePrefix},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariantMap{
|
return QVariantMap{
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public:
|
|||||||
|
|
||||||
/// This function will look at the current QTextCursor and determine if there
|
/// This function will look at the current QTextCursor and determine if there
|
||||||
/// is the possibility to autocomplete it.
|
/// is the possibility to autocomplete it.
|
||||||
Q_INVOKABLE QVariantMap getAutocompletionInfo(bool isAutocompleting);
|
Q_INVOKABLE QVariantMap getAutocompletionInfo();
|
||||||
Q_INVOKABLE void replaceAutoComplete(const QString &word);
|
Q_INVOKABLE void replaceAutoComplete(const QString &word);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
||||||
|
|
||||||
#include <KColorSchemeManager>
|
|
||||||
#include <QAbstractItemModel>
|
|
||||||
|
|
||||||
#include "colorschemer.h"
|
|
||||||
|
|
||||||
ColorSchemer::ColorSchemer(QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, c(new KColorSchemeManager(this))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorSchemer::~ColorSchemer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QAbstractItemModel *ColorSchemer::model() const
|
|
||||||
{
|
|
||||||
return c->model();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorSchemer::apply(int idx)
|
|
||||||
{
|
|
||||||
c->activateScheme(c->model()->index(idx, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorSchemer::apply(const QString &name)
|
|
||||||
{
|
|
||||||
c->activateScheme(c->indexForScheme(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
int ColorSchemer::indexForScheme(const QString &name) const
|
|
||||||
{
|
|
||||||
auto index = c->indexForScheme(name).row();
|
|
||||||
if (index == -1) {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ColorSchemer::nameForIndex(int index) const
|
|
||||||
{
|
|
||||||
return c->model()->data(c->model()->index(index, 0), Qt::DisplayRole).toString();
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QIdentityProxyModel>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
class QAbstractItemModel;
|
|
||||||
class KColorSchemeManager;
|
|
||||||
|
|
||||||
class ColorSchemer : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(QAbstractItemModel *model READ model CONSTANT)
|
|
||||||
public:
|
|
||||||
ColorSchemer(QObject *parent = nullptr);
|
|
||||||
~ColorSchemer();
|
|
||||||
|
|
||||||
QAbstractItemModel *model() const;
|
|
||||||
Q_INVOKABLE void apply(int idx);
|
|
||||||
Q_INVOKABLE void apply(const QString &name);
|
|
||||||
Q_INVOKABLE int indexForScheme(const QString &name) const;
|
|
||||||
Q_INVOKABLE QString nameForIndex(int index) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
KColorSchemeManager *c;
|
|
||||||
};
|
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "actionshandler.h"
|
#include "actionshandler.h"
|
||||||
#include "commandmodel.h"
|
#include "commandmodel.h"
|
||||||
|
|
||||||
|
|
||||||
QVariantList CommandModel::filterModel(const QString &filter)
|
QVariantList CommandModel::filterModel(const QString &filter)
|
||||||
{
|
{
|
||||||
QVariantList result;
|
QVariantList result;
|
||||||
@@ -23,47 +24,70 @@ QVariantList CommandModel::filterModel(const QString &filter)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QVariantList CommandModel::commands()
|
QVariantList CommandModel::commands()
|
||||||
{
|
{
|
||||||
QVariantList commands;
|
QVariantList commands;
|
||||||
|
|
||||||
// Messages commands
|
// Messages commands
|
||||||
commands.append(QVariant::fromValue(Command{QStringLiteral("/me "), QStringLiteral("<message>"), i18n("Displays action")}));
|
commands.append(QVariant::fromValue(Command{
|
||||||
|
QStringLiteral("/me "),
|
||||||
|
QStringLiteral("<message>"),
|
||||||
|
i18n("Displays action")}));
|
||||||
|
|
||||||
commands.append(QVariant::fromValue(Command{QStringLiteral("/shrug "), QStringLiteral("<message>"), i18n("Prepends ¯\\_(ツ)_/¯ to a plain-text message")}));
|
commands.append(QVariant::fromValue(Command{
|
||||||
|
QStringLiteral("/shrug "),
|
||||||
|
QStringLiteral("<message>"),
|
||||||
|
i18n("Prepends ¯\\_(ツ)_/¯ to a plain-text message")}));
|
||||||
|
|
||||||
commands.append(QVariant::fromValue(Command{QStringLiteral("/lenny "), QStringLiteral("<message>"), i18n("Prepends ( ͡° ͜ʖ ͡°) to a plain-text message")}));
|
commands.append(QVariant::fromValue(Command{
|
||||||
|
QStringLiteral("/lenny "),
|
||||||
|
QStringLiteral("<message>"),
|
||||||
|
i18n("Prepends ( ͡° ͜ʖ ͡°) to a plain-text message")}));
|
||||||
|
|
||||||
commands.append(
|
commands.append(QVariant::fromValue(Command{
|
||||||
QVariant::fromValue(Command{QStringLiteral("/tableflip "), QStringLiteral("<message>"), i18n("Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message")}));
|
QStringLiteral("/plain "),
|
||||||
|
QStringLiteral("<message>"),
|
||||||
|
i18n("Sends a message as plain text, without interpreting it as markdown")}));
|
||||||
|
|
||||||
commands.append(
|
commands.append(QVariant::fromValue(Command{
|
||||||
QVariant::fromValue(Command{QStringLiteral("/unflip "), QStringLiteral("<message>"), i18n("Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message")}));
|
QStringLiteral("/html "),
|
||||||
|
QStringLiteral("<message>"),
|
||||||
|
i18n("Sends a message as html, without interpreting it as markdown")}));
|
||||||
|
|
||||||
commands.append(QVariant::fromValue(
|
commands.append(QVariant::fromValue(Command{
|
||||||
Command{QStringLiteral("/plain "), QStringLiteral("<message>"), i18n("Sends a message as plain text, without interpreting it as markdown")}));
|
QStringLiteral("/rainbow "),
|
||||||
|
QStringLiteral("<message>"),
|
||||||
|
i18n("Sends the given message coloured as a rainbow")}));
|
||||||
|
|
||||||
commands.append(QVariant::fromValue(
|
commands.append(QVariant::fromValue(Command{
|
||||||
Command{QStringLiteral("/html "), QStringLiteral("<message>"), i18n("Sends a message as html, without interpreting it as markdown")}));
|
QStringLiteral("/rainbowme "),
|
||||||
|
QStringLiteral("<message>"),
|
||||||
|
i18n("Sends the given emote coloured as a rainbow")}));
|
||||||
|
|
||||||
commands.append(
|
|
||||||
QVariant::fromValue(Command{QStringLiteral("/rainbow "), QStringLiteral("<message>"), i18n("Sends the given message coloured as a rainbow")}));
|
|
||||||
|
|
||||||
commands.append(
|
|
||||||
QVariant::fromValue(Command{QStringLiteral("/rainbowme "), QStringLiteral("<message>"), i18n("Sends the given emote coloured as a rainbow")}));
|
|
||||||
|
|
||||||
commands.append(QVariant::fromValue(Command{QStringLiteral("/spoiler "), QStringLiteral("<message>"), i18n("Sends the given message as a spoiler")}));
|
|
||||||
|
|
||||||
// Actions commands
|
// Actions commands
|
||||||
commands.append(QVariant::fromValue(Command{QStringLiteral("/join "), QStringLiteral("<room-address>"), i18n("Joins room with given address")}));
|
commands.append(QVariant::fromValue(Command{
|
||||||
|
QStringLiteral("/join "), QStringLiteral("<room-address>"),
|
||||||
|
i18n("Joins room with given address")}));
|
||||||
|
|
||||||
commands.append(QVariant::fromValue(Command{QStringLiteral("/part "), QStringLiteral("[<room-address>]"), i18n("Leave room")}));
|
commands.append(QVariant::fromValue(Command{
|
||||||
|
QStringLiteral("/part "),
|
||||||
|
QStringLiteral("[<room-address>]"),
|
||||||
|
i18n("Leave room")}));
|
||||||
|
|
||||||
commands.append(QVariant::fromValue(Command{QStringLiteral("/invite "), QStringLiteral("<user-id>"), i18n("Invites user with given id to current room")}));
|
commands.append(QVariant::fromValue(Command{
|
||||||
|
QStringLiteral("/invite "),
|
||||||
|
QStringLiteral("<user-id>"),
|
||||||
|
i18n("Invites user with given id to current room")}));
|
||||||
|
|
||||||
commands.append(QVariant::fromValue(Command{QStringLiteral("/react "), QStringLiteral("<reaction text>"), i18n("React to this message with a text")}));
|
commands.append(QVariant::fromValue(Command{
|
||||||
|
QStringLiteral("/react "),
|
||||||
|
QStringLiteral("<reaction text>"),
|
||||||
|
i18n("React to this message with a text")}));
|
||||||
|
|
||||||
// TODO more see elements /help action
|
// TODO more see elements /help action
|
||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ struct Command {
|
|||||||
QString parameter;
|
QString parameter;
|
||||||
QString help;
|
QString help;
|
||||||
|
|
||||||
Q_GADGET
|
Q_GADGET
|
||||||
Q_PROPERTY(QString command MEMBER command)
|
Q_PROPERTY(QString command MEMBER command)
|
||||||
Q_PROPERTY(QString parameter MEMBER parameter)
|
Q_PROPERTY(QString parameter MEMBER parameter)
|
||||||
Q_PROPERTY(QString help MEMBER help)
|
Q_PROPERTY(QString help MEMBER help)
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(Command)
|
Q_DECLARE_METATYPE(Command)
|
||||||
|
|||||||
@@ -10,13 +10,14 @@
|
|||||||
#include <KConfigGroup>
|
#include <KConfigGroup>
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
#include <KWindowConfig>
|
#include <KWindowConfig>
|
||||||
#ifdef HAVE_WINDOWSYSTEM
|
|
||||||
#include <KWindowEffects>
|
|
||||||
#include <KWindowSystem>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <QAuthenticator>
|
#include <QAuthenticator>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
#include <QCloseEvent>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
@@ -24,7 +25,6 @@
|
|||||||
#include <QNetworkConfigurationManager>
|
#include <QNetworkConfigurationManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QQuickItem>
|
|
||||||
#include <QQuickWindow>
|
#include <QQuickWindow>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QStringBuilder>
|
#include <QStringBuilder>
|
||||||
@@ -39,18 +39,20 @@
|
|||||||
#include "csapi/joining.h"
|
#include "csapi/joining.h"
|
||||||
#include "csapi/logout.h"
|
#include "csapi/logout.h"
|
||||||
#include "csapi/profile.h"
|
#include "csapi/profile.h"
|
||||||
|
#include "csapi/registration.h"
|
||||||
|
#include "csapi/wellknown.h"
|
||||||
#include "events/eventcontent.h"
|
#include "events/eventcontent.h"
|
||||||
|
#include "events/roommessageevent.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "roommanager.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "roommanager.h"
|
||||||
#include <KStandardShortcut>
|
#include <KStandardShortcut>
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
#ifndef Q_OS_ANDROID
|
||||||
#include "trayicon.h"
|
#include "trayicon.h"
|
||||||
#elif !defined(Q_OS_ANDROID)
|
|
||||||
#include "trayicon_sni.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -62,26 +64,6 @@ Controller::Controller(QObject *parent)
|
|||||||
Connection::setRoomType<NeoChatRoom>();
|
Connection::setRoomType<NeoChatRoom>();
|
||||||
Connection::setUserType<NeoChatUser>();
|
Connection::setUserType<NeoChatUser>();
|
||||||
|
|
||||||
connect(this, &Controller::connectionAdded, this, [=, this](Connection *connection) {
|
|
||||||
connection->getTurnServers();
|
|
||||||
|
|
||||||
connect(connection, &Connection::turnServersChanged, this, [=, this](const QJsonObject &data) {
|
|
||||||
m_turnServers[connection->userId()].clear();
|
|
||||||
const auto username = QUrl::toPercentEncoding(data["username"].toString());
|
|
||||||
const auto password = QUrl::toPercentEncoding(data["password"].toString());
|
|
||||||
const auto ttl = data["ttl"].toInt();
|
|
||||||
for (const auto &uri : data["uris"].toArray()) {
|
|
||||||
const auto parts = uri.toString().split(":");
|
|
||||||
const auto port = parts[2].split("?")[0];
|
|
||||||
m_turnServers[connection->userId()] +=
|
|
||||||
parts[0] + QStringLiteral("://") + username + QStringLiteral(":") + password + QStringLiteral("@") + parts[1] + QStringLiteral(":") + port;
|
|
||||||
}
|
|
||||||
QTimer::singleShot(1000 * ttl, this, [this, connection]() {
|
|
||||||
connection->getTurnServers();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
TrayIcon *trayIcon = new TrayIcon(this);
|
TrayIcon *trayIcon = new TrayIcon(this);
|
||||||
if (NeoChatConfig::self()->systemTray()) {
|
if (NeoChatConfig::self()->systemTray()) {
|
||||||
@@ -133,7 +115,8 @@ Controller::Controller(QObject *parent)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
connect(m_mgr, &QNetworkConfigurationManager::onlineStateChanged, this, &Controller::isOnlineChanged);
|
connect(m_mgr, &QNetworkConfigurationManager::onlineStateChanged,
|
||||||
|
this, &Controller::isOnlineChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller::~Controller()
|
Controller::~Controller()
|
||||||
@@ -164,7 +147,7 @@ void Controller::loginWithAccessToken(const QString &serverAddr, const QString &
|
|||||||
|
|
||||||
QUrl serverUrl(serverAddr);
|
QUrl serverUrl(serverAddr);
|
||||||
|
|
||||||
auto conn = new Connection();
|
auto conn = new Connection(this);
|
||||||
if (serverUrl.isValid()) {
|
if (serverUrl.isValid()) {
|
||||||
conn->setHomeserver(serverUrl);
|
conn->setHomeserver(serverUrl);
|
||||||
}
|
}
|
||||||
@@ -172,6 +155,7 @@ void Controller::loginWithAccessToken(const QString &serverAddr, const QString &
|
|||||||
connect(conn, &Connection::connected, this, [=] {
|
connect(conn, &Connection::connected, this, [=] {
|
||||||
AccountSettings account(conn->userId());
|
AccountSettings account(conn->userId());
|
||||||
account.setKeepLoggedIn(true);
|
account.setKeepLoggedIn(true);
|
||||||
|
account.clearAccessToken(); // Drop the legacy - just in case
|
||||||
account.setHomeserver(conn->homeserver());
|
account.setHomeserver(conn->homeserver());
|
||||||
account.setDeviceId(conn->deviceId());
|
account.setDeviceId(conn->deviceId());
|
||||||
account.setDeviceName(deviceName);
|
account.setDeviceName(deviceName);
|
||||||
@@ -185,7 +169,7 @@ void Controller::loginWithAccessToken(const QString &serverAddr, const QString &
|
|||||||
connect(conn, &Connection::networkError, this, [=](QString error, const QString &, int, int) {
|
connect(conn, &Connection::networkError, this, [=](QString error, const QString &, int, int) {
|
||||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
||||||
});
|
});
|
||||||
conn->assumeIdentity(user, token, deviceName);
|
conn->connectWithToken(user, token, deviceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::logout(Connection *conn, bool serverSideLogout)
|
void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||||
@@ -280,13 +264,13 @@ void Controller::invokeLogin()
|
|||||||
if (!account.homeserver().isEmpty()) {
|
if (!account.homeserver().isEmpty()) {
|
||||||
auto accessToken = loadAccessTokenFromKeyChain(account);
|
auto accessToken = loadAccessTokenFromKeyChain(account);
|
||||||
|
|
||||||
auto connection = new Connection(account.homeserver());
|
auto connection = new Connection(account.homeserver(), this);
|
||||||
connect(connection, &Connection::connected, this, [=] {
|
connect(connection, &Connection::connected, this, [=] {
|
||||||
connection->loadState();
|
connection->loadState();
|
||||||
addConnection(connection);
|
addConnection(connection);
|
||||||
if (connection->userId() == id) {
|
if (connection->userId() == id) {
|
||||||
setActiveConnection(connection);
|
setActiveConnection(connection);
|
||||||
connectSingleShot(connection, &Connection::syncDone, this, &Controller::initiated);
|
Q_EMIT initiated();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(connection, &Connection::loginError, this, [=](const QString &error, const QString &) {
|
connect(connection, &Connection::loginError, this, [=](const QString &error, const QString &) {
|
||||||
@@ -302,7 +286,7 @@ void Controller::invokeLogin()
|
|||||||
connect(connection, &Connection::networkError, this, [=](const QString &error, const QString &, int, int) {
|
connect(connection, &Connection::networkError, this, [=](const QString &error, const QString &, int, int) {
|
||||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
||||||
});
|
});
|
||||||
connection->assumeIdentity(account.userId(), accessToken, account.deviceId());
|
connection->connectWithToken(account.userId(), accessToken, account.deviceId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (accounts.isEmpty()) {
|
if (accounts.isEmpty()) {
|
||||||
@@ -411,7 +395,7 @@ void Controller::changeAvatar(Connection *conn, const QUrl &localFile)
|
|||||||
{
|
{
|
||||||
auto job = conn->uploadFile(localFile.toLocalFile());
|
auto job = conn->uploadFile(localFile.toLocalFile());
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
if (isJobPending(job)) {
|
if(isJobPending(job)) {
|
||||||
#else
|
#else
|
||||||
if (isJobRunning(job)) {
|
if (isJobRunning(job)) {
|
||||||
#endif
|
#endif
|
||||||
@@ -483,11 +467,7 @@ bool Controller::setAvatar(Connection *connection, const QUrl &avatarSource)
|
|||||||
User *localUser = connection->user();
|
User *localUser = connection->user();
|
||||||
QString decoded = avatarSource.path();
|
QString decoded = avatarSource.path();
|
||||||
if (decoded.isEmpty()) {
|
if (decoded.isEmpty()) {
|
||||||
#ifdef QUOTIENT_07
|
connection->callApi<SetAvatarUrlJob>(localUser->id(), "");
|
||||||
connection->callApi<SetAvatarUrlJob>(localUser->id(), avatarSource);
|
|
||||||
#else
|
|
||||||
connection->callApi<SetAvatarUrlJob>(localUser->id(), QString());
|
|
||||||
#endif
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (QImageReader(decoded).read().isNull()) {
|
if (QImageReader(decoded).read().isNull()) {
|
||||||
@@ -498,11 +478,7 @@ bool Controller::setAvatar(Connection *connection, const QUrl &avatarSource)
|
|||||||
}
|
}
|
||||||
|
|
||||||
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth)
|
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth)
|
||||||
#ifdef QUOTIENT_07
|
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), QStringLiteral("/_matrix/client/r0") % "/account/password")
|
||||||
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), "/_matrix/client/r0/account/password")
|
|
||||||
#else
|
|
||||||
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), QStringLiteral("/_matrix/client/r0/account/password"))
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
QJsonObject _data;
|
QJsonObject _data;
|
||||||
addParam<>(_data, QStringLiteral("new_password"), newPassword);
|
addParam<>(_data, QStringLiteral("new_password"), newPassword);
|
||||||
@@ -581,11 +557,7 @@ void Controller::saveWindowGeometry(QQuickWindow *window)
|
|||||||
}
|
}
|
||||||
|
|
||||||
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth)
|
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth)
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
: Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId).toLatin1())
|
|
||||||
#else
|
|
||||||
: Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId))
|
: Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId))
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
QJsonObject _data;
|
QJsonObject _data;
|
||||||
addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth);
|
addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth);
|
||||||
@@ -605,7 +577,6 @@ bool Controller::isOnline() const
|
|||||||
return m_mgr->isOnline();
|
return m_mgr->isOnline();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove in favor of RoomManager::joinRoom
|
|
||||||
void Controller::joinRoom(const QString &alias)
|
void Controller::joinRoom(const QString &alias)
|
||||||
{
|
{
|
||||||
if (!alias.contains(":")) {
|
if (!alias.contains(":")) {
|
||||||
@@ -614,7 +585,14 @@ void Controller::joinRoom(const QString &alias)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto knownServer = alias.mid(alias.indexOf(":") + 1);
|
const auto knownServer = alias.mid(alias.indexOf(":") + 1);
|
||||||
RoomManager::instance().joinRoom(m_connection, alias, QStringList{knownServer});
|
auto joinRoomJob = m_connection->joinRoom(alias, QStringList{knownServer});
|
||||||
|
|
||||||
|
connect(joinRoomJob, &JoinRoomJob::failure, [=] {
|
||||||
|
Q_EMIT errorOccured(i18n("Server error when joining the room \"%1\": %2", joinRoomJob->errorString()));
|
||||||
|
});
|
||||||
|
connect(joinRoomJob, &JoinRoomJob::success, [this, joinRoomJob] {
|
||||||
|
Q_EMIT errorOccured(joinRoomJob->roomId());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::openOrCreateDirectChat(NeoChatUser *user)
|
void Controller::openOrCreateDirectChat(NeoChatUser *user)
|
||||||
@@ -630,43 +608,10 @@ void Controller::openOrCreateDirectChat(NeoChatUser *user)
|
|||||||
|
|
||||||
QString Controller::formatByteSize(double size, int precision) const
|
QString Controller::formatByteSize(double size, int precision) const
|
||||||
{
|
{
|
||||||
return QLocale().formattedDataSize(size, precision);
|
return KFormat().formatByteSize(size, precision);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Controller::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const
|
QString Controller::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const
|
||||||
{
|
{
|
||||||
return KFormat().formatDuration(msecs, options);
|
return KFormat().formatDuration(msecs, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::setBlur(QQuickItem *item, bool blur)
|
|
||||||
{
|
|
||||||
#ifdef HAVE_WINDOWSYSTEM
|
|
||||||
auto setWindows = [item, blur]() {
|
|
||||||
auto reg = QRect(QPoint(0, 0), item->window()->size());
|
|
||||||
KWindowEffects::enableBackgroundContrast(item->window(), blur, 1, 1, 1, reg);
|
|
||||||
KWindowEffects::enableBlurBehind(item->window(), blur, reg);
|
|
||||||
};
|
|
||||||
|
|
||||||
disconnect(item->window(), &QQuickWindow::heightChanged, this, nullptr);
|
|
||||||
disconnect(item->window(), &QQuickWindow::widthChanged, this, nullptr);
|
|
||||||
connect(item->window(), &QQuickWindow::heightChanged, this, setWindows);
|
|
||||||
connect(item->window(), &QQuickWindow::widthChanged, this, setWindows);
|
|
||||||
setWindows();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::raiseWindow(QWindow *window)
|
|
||||||
{
|
|
||||||
#ifdef HAVE_WINDOWSYSTEM
|
|
||||||
KWindowSystem::activateWindow(window->winId());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Controller::hasWindowSystem() const
|
|
||||||
{
|
|
||||||
#ifdef HAVE_WINDOWSYSTEM
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include <QMediaPlayer>
|
#include <QMediaPlayer>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QQuickItem>
|
|
||||||
|
|
||||||
#include <KAboutData>
|
#include <KAboutData>
|
||||||
#include <KFormat>
|
#include <KFormat>
|
||||||
@@ -34,7 +33,6 @@ class Controller : public QObject
|
|||||||
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
||||||
Q_PROPERTY(KAboutData aboutData READ aboutData WRITE setAboutData NOTIFY aboutDataChanged)
|
Q_PROPERTY(KAboutData aboutData READ aboutData WRITE setAboutData NOTIFY aboutDataChanged)
|
||||||
Q_PROPERTY(bool supportSystemTray READ supportSystemTray CONSTANT)
|
Q_PROPERTY(bool supportSystemTray READ supportSystemTray CONSTANT)
|
||||||
Q_PROPERTY(bool hasWindowSystem READ hasWindowSystem CONSTANT)
|
|
||||||
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -89,10 +87,6 @@ public:
|
|||||||
Q_INVOKABLE QString formatByteSize(double size, int precision = 1) const;
|
Q_INVOKABLE QString formatByteSize(double size, int precision = 1) const;
|
||||||
|
|
||||||
Q_INVOKABLE void openOrCreateDirectChat(NeoChatUser *user);
|
Q_INVOKABLE void openOrCreateDirectChat(NeoChatUser *user);
|
||||||
|
|
||||||
Q_INVOKABLE void setBlur(QQuickItem *item, bool blur);
|
|
||||||
Q_INVOKABLE void raiseWindow(QWindow *window);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit Controller(QObject *parent = nullptr);
|
explicit Controller(QObject *parent = nullptr);
|
||||||
~Controller() override;
|
~Controller() override;
|
||||||
@@ -108,7 +102,6 @@ private:
|
|||||||
void saveSettings() const;
|
void saveSettings() const;
|
||||||
|
|
||||||
KAboutData m_aboutData;
|
KAboutData m_aboutData;
|
||||||
bool hasWindowSystem() const;
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void invokeLogin();
|
void invokeLogin();
|
||||||
@@ -145,7 +138,6 @@ public Q_SLOTS:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
QNetworkConfigurationManager *m_mgr;
|
QNetworkConfigurationManager *m_mgr;
|
||||||
QHash<QString, QSet<QString>> m_turnServers; // userId -> List of TURN servers in the format that gstreamer needs
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO libQuotient 0.7: Drop
|
// TODO libQuotient 0.7: Drop
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <csapi/account-data.h>
|
|
||||||
#include <csapi/content-repo.h>
|
|
||||||
#include <csapi/profile.h>
|
|
||||||
|
|
||||||
#include "customemojimodel_p.h"
|
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#define running isJobPending
|
|
||||||
#else
|
|
||||||
#define running isJobRunning
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void CustomEmojiModel::fetchEmojies()
|
|
||||||
{
|
|
||||||
if (d->conn == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &data = d->conn->accountData("im.ponies.user_emotes");
|
|
||||||
if (data == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QJsonObject emojies = data->contentJson()["images"].toObject();
|
|
||||||
|
|
||||||
// TODO: Remove with stable migration
|
|
||||||
const auto legacyEmojies = data->contentJson()["emoticons"].toObject();
|
|
||||||
for (const auto &emoji : legacyEmojies.keys()) {
|
|
||||||
if (!emojies.contains(emoji)) {
|
|
||||||
emojies[emoji] = legacyEmojies[emoji];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
d->emojies.clear();
|
|
||||||
|
|
||||||
for (const auto &emoji : emojies.keys()) {
|
|
||||||
const auto &data = emojies[emoji];
|
|
||||||
|
|
||||||
const auto e = emoji.startsWith(":") ? emoji : (QStringLiteral(":") + emoji + QStringLiteral(":"));
|
|
||||||
|
|
||||||
d->emojies << CustomEmoji{e, data.toObject()["url"].toString(), QRegularExpression(QStringLiteral(R"((^|[^\\]))") + e)};
|
|
||||||
}
|
|
||||||
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
|
||||||
{
|
|
||||||
using namespace Quotient;
|
|
||||||
|
|
||||||
auto job = d->conn->uploadFile(location.toLocalFile());
|
|
||||||
|
|
||||||
if (running(job)) {
|
|
||||||
connect(job, &BaseJob::success, this, [this, name, job] {
|
|
||||||
const auto &data = d->conn->accountData("im.ponies.user_emotes");
|
|
||||||
auto json = data != nullptr ? data->contentJson() : QJsonObject();
|
|
||||||
auto emojiData = json["images"].toObject();
|
|
||||||
emojiData[QStringLiteral("%1").arg(name)] = QJsonObject({
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
{QStringLiteral("url"), job->contentUri().toString()}
|
|
||||||
#else
|
|
||||||
{QStringLiteral("url"), job->contentUri()}
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
json["images"] = emojiData;
|
|
||||||
d->conn->setAccountData("im.ponies.user_emotes", json);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomEmojiModel::removeEmoji(const QString &name)
|
|
||||||
{
|
|
||||||
using namespace Quotient;
|
|
||||||
|
|
||||||
const auto &data = d->conn->accountData("im.ponies.user_emotes");
|
|
||||||
Q_ASSERT(data != nullptr); // something's screwed if we get here with a nullptr
|
|
||||||
auto json = data->contentJson();
|
|
||||||
const QString _name = name.mid(1).chopped(1);
|
|
||||||
auto emojiData = json["images"].toObject();
|
|
||||||
|
|
||||||
if (emojiData.contains(name)) {
|
|
||||||
emojiData.remove(name);
|
|
||||||
json["images"] = emojiData;
|
|
||||||
}
|
|
||||||
if (emojiData.contains(_name)) {
|
|
||||||
emojiData.remove(_name);
|
|
||||||
json["images"] = emojiData;
|
|
||||||
}
|
|
||||||
emojiData = json["emoticons"].toObject();
|
|
||||||
if (emojiData.contains(name)) {
|
|
||||||
emojiData.remove(name);
|
|
||||||
json["emoticons"] = emojiData;
|
|
||||||
}
|
|
||||||
if (emojiData.contains(_name)) {
|
|
||||||
emojiData.remove(_name);
|
|
||||||
json["emoticons"] = emojiData;
|
|
||||||
}
|
|
||||||
d->conn->setAccountData("im.ponies.user_emotes", json);
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "customemojimodel_p.h"
|
|
||||||
#include "emojimodel.h"
|
|
||||||
|
|
||||||
enum Roles {
|
|
||||||
Name,
|
|
||||||
ImageURL,
|
|
||||||
ModelData, // for emulating the regular emoji model's usage, otherwise the UI code would get too complicated
|
|
||||||
};
|
|
||||||
|
|
||||||
CustomEmojiModel::CustomEmojiModel(QObject *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
, d(new Private)
|
|
||||||
{
|
|
||||||
connect(this, &CustomEmojiModel::connectionChanged, this, &CustomEmojiModel::fetchEmojies);
|
|
||||||
connect(this, &CustomEmojiModel::connectionChanged, this, [this]() {
|
|
||||||
if (!d->conn)
|
|
||||||
return;
|
|
||||||
|
|
||||||
connect(d->conn, &Connection::accountDataChanged, this, [this](const QString &id) {
|
|
||||||
if (id != QStringLiteral("im.ponies.user_emotes")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fetchEmojies();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomEmojiModel::~CustomEmojiModel()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const
|
|
||||||
{
|
|
||||||
const auto row = idx.row();
|
|
||||||
if (row >= d->emojies.length()) {
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
const auto &data = d->emojies[row];
|
|
||||||
|
|
||||||
switch (Roles(role)) {
|
|
||||||
case Roles::ModelData:
|
|
||||||
return QVariant::fromValue(Emoji(QStringLiteral("image://mxc/") + data.url.mid(6), data.name, true));
|
|
||||||
case Roles::Name:
|
|
||||||
return data.name;
|
|
||||||
case Roles::ImageURL:
|
|
||||||
return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
|
|
||||||
}
|
|
||||||
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
int CustomEmojiModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(parent)
|
|
||||||
|
|
||||||
return d->emojies.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> CustomEmojiModel::roleNames() const
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
{Name, "name"},
|
|
||||||
{ImageURL, "imageURL"},
|
|
||||||
{ModelData, "modelData"},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Connection *CustomEmojiModel::connection() const
|
|
||||||
{
|
|
||||||
return d->conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CustomEmojiModel::setConnection(Connection *it)
|
|
||||||
{
|
|
||||||
if (d->conn == it) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (d->conn != nullptr) {
|
|
||||||
disconnect(d->conn, nullptr, this, nullptr);
|
|
||||||
}
|
|
||||||
d->conn = it;
|
|
||||||
Q_EMIT connectionChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString CustomEmojiModel::preprocessText(const QString &it)
|
|
||||||
{
|
|
||||||
auto cp = it;
|
|
||||||
for (const auto &emoji : qAsConst(d->emojies)) {
|
|
||||||
cp.replace(
|
|
||||||
emoji.regexp,
|
|
||||||
QStringLiteral(R"(<img data-mx-emoticon="" src="%1" alt="%2" title="%2" height="32" vertical-align="middle" />)").arg(emoji.url, emoji.name));
|
|
||||||
}
|
|
||||||
return cp;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantList CustomEmojiModel::filterModel(const QString &filter)
|
|
||||||
{
|
|
||||||
QVariantList results;
|
|
||||||
for (const auto &emoji : qAsConst(d->emojies)) {
|
|
||||||
if (results.length() >= 10)
|
|
||||||
break;
|
|
||||||
if (!emoji.name.contains(filter, Qt::CaseInsensitive))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
results << QVariant::fromValue(Emoji(QStringLiteral("image://mxc/") + emoji.url.mid(6), emoji.name, true));
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "connection.h"
|
|
||||||
|
|
||||||
using namespace Quotient;
|
|
||||||
|
|
||||||
class CustomEmojiModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
// constructors
|
|
||||||
|
|
||||||
explicit CustomEmojiModel(QObject *parent = nullptr);
|
|
||||||
~CustomEmojiModel();
|
|
||||||
|
|
||||||
// model
|
|
||||||
|
|
||||||
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
// property setters
|
|
||||||
|
|
||||||
Connection *connection() const;
|
|
||||||
void setConnection(Connection *it);
|
|
||||||
Q_SIGNAL void connectionChanged();
|
|
||||||
|
|
||||||
// QML functions
|
|
||||||
|
|
||||||
Q_INVOKABLE QString preprocessText(const QString &it);
|
|
||||||
Q_INVOKABLE QVariantList filterModel(const QString &filter);
|
|
||||||
Q_INVOKABLE void addEmoji(const QString &name, const QUrl &location);
|
|
||||||
Q_INVOKABLE void removeEmoji(const QString &name);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Private;
|
|
||||||
std::unique_ptr<Private> d;
|
|
||||||
|
|
||||||
void fetchEmojies();
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "customemojimodel.h"
|
|
||||||
|
|
||||||
struct CustomEmoji {
|
|
||||||
QString name; // with :semicolons:
|
|
||||||
QString url; // mxc://
|
|
||||||
QRegularExpression regexp;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CustomEmojiModel::Private {
|
|
||||||
Connection *conn = nullptr;
|
|
||||||
QList<CustomEmoji> emojies;
|
|
||||||
};
|
|
||||||
@@ -10,29 +10,18 @@
|
|||||||
DevicesModel::DevicesModel(QObject *parent)
|
DevicesModel::DevicesModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &DevicesModel::fetchDevices);
|
GetDevicesJob *job = Controller::instance().activeConnection()->callApi<GetDevicesJob>();
|
||||||
|
connect(job, &BaseJob::success, this, [this, job]() {
|
||||||
fetchDevices();
|
beginResetModel();
|
||||||
}
|
m_devices = job->devices();
|
||||||
|
endResetModel();
|
||||||
void DevicesModel::fetchDevices()
|
});
|
||||||
{
|
|
||||||
if (Controller::instance().activeConnection()) {
|
|
||||||
auto job = Controller::instance().activeConnection()->callApi<GetDevicesJob>();
|
|
||||||
connect(job, &BaseJob::success, this, [this, job]() {
|
|
||||||
beginResetModel();
|
|
||||||
m_devices = job->devices();
|
|
||||||
endResetModel();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant DevicesModel::data(const QModelIndex &index, int role) const
|
QVariant DevicesModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
if (index.row() < 0 || index.row() >= rowCount(QModelIndex())) {
|
if (index.row() < 0 || index.row() >= rowCount(QModelIndex()))
|
||||||
return {};
|
return QVariant();
|
||||||
}
|
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Id:
|
case Id:
|
||||||
return m_devices[index.row()].deviceId;
|
return m_devices[index.row()].deviceId;
|
||||||
@@ -44,7 +33,7 @@ QVariant DevicesModel::data(const QModelIndex &index, int role) const
|
|||||||
if (m_devices[index.row()].lastSeenTs)
|
if (m_devices[index.row()].lastSeenTs)
|
||||||
return *m_devices[index.row()].lastSeenTs;
|
return *m_devices[index.row()].lastSeenTs;
|
||||||
}
|
}
|
||||||
return {};
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
int DevicesModel::rowCount(const QModelIndex &parent) const
|
int DevicesModel::rowCount(const QModelIndex &parent) const
|
||||||
@@ -73,9 +62,9 @@ void DevicesModel::logout(int index, const QString &password)
|
|||||||
authData["identifier"] = identifier;
|
authData["identifier"] = identifier;
|
||||||
auto *innerJob = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
auto *innerJob = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
||||||
connect(innerJob, &BaseJob::success, this, [this, index]() {
|
connect(innerJob, &BaseJob::success, this, [this, index]() {
|
||||||
beginRemoveRows(QModelIndex(), index, index);
|
Q_EMIT beginRemoveRows(QModelIndex(), index, index);
|
||||||
m_devices.remove(index);
|
m_devices.remove(index);
|
||||||
endRemoveRows();
|
Q_EMIT endRemoveRows();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -85,12 +74,12 @@ void DevicesModel::setName(int index, const QString &name)
|
|||||||
{
|
{
|
||||||
auto job = Controller::instance().activeConnection()->callApi<UpdateDeviceJob>(m_devices[index].deviceId, name);
|
auto job = Controller::instance().activeConnection()->callApi<UpdateDeviceJob>(m_devices[index].deviceId, name);
|
||||||
QString oldName = m_devices[index].displayName;
|
QString oldName = m_devices[index].displayName;
|
||||||
beginResetModel();
|
Q_EMIT beginResetModel();
|
||||||
m_devices[index].displayName = name;
|
m_devices[index].displayName = name;
|
||||||
endResetModel();
|
Q_EMIT endResetModel();
|
||||||
connect(job, &BaseJob::failure, this, [=]() {
|
connect(job, &BaseJob::failure, this, [=]() {
|
||||||
beginResetModel();
|
Q_EMIT beginResetModel();
|
||||||
m_devices[index].displayName = oldName;
|
m_devices[index].displayName = oldName;
|
||||||
endResetModel();
|
Q_EMIT endResetModel();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,5 @@ public:
|
|||||||
Q_INVOKABLE void setName(int index, const QString &name);
|
Q_INVOKABLE void setName(int index, const QString &name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void fetchDevices();
|
|
||||||
QVector<Quotient::Device> m_devices;
|
QVector<Quotient::Device> m_devices;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,10 +10,9 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
struct Emoji {
|
struct Emoji {
|
||||||
Emoji(QString u, QString s, bool isCustom = false)
|
Emoji(QString u, QString s)
|
||||||
: unicode(std::move(std::move(u)))
|
: unicode(std::move(std::move(u)))
|
||||||
, shortname(std::move(std::move(s)))
|
, shortname(std::move(std::move(s)))
|
||||||
, isCustom(isCustom)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
Emoji() = default;
|
Emoji() = default;
|
||||||
@@ -29,18 +28,15 @@ struct Emoji {
|
|||||||
{
|
{
|
||||||
arch >> object.unicode;
|
arch >> object.unicode;
|
||||||
arch >> object.shortname;
|
arch >> object.shortname;
|
||||||
object.isCustom = object.unicode.startsWith("image://");
|
|
||||||
return arch;
|
return arch;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString unicode;
|
QString unicode;
|
||||||
QString shortname;
|
QString shortname;
|
||||||
bool isCustom = false;
|
|
||||||
|
|
||||||
Q_GADGET
|
Q_GADGET
|
||||||
Q_PROPERTY(QString unicode MEMBER unicode)
|
Q_PROPERTY(QString unicode MEMBER unicode)
|
||||||
Q_PROPERTY(QString shortname MEMBER shortname)
|
Q_PROPERTY(QString shortname MEMBER shortname)
|
||||||
Q_PROPERTY(bool isCustom MEMBER isCustom)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(Emoji)
|
Q_DECLARE_METATYPE(Emoji)
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ public:
|
|||||||
enum MatchMode { MatchDefault, MatchExtension, MatchContent };
|
enum MatchMode { MatchDefault, MatchExtension, MatchContent };
|
||||||
Q_ENUM(MatchMode)
|
Q_ENUM(MatchMode)
|
||||||
|
|
||||||
Q_INVOKABLE QMimeType mimeTypeForFile(const QString &fileName, FileTypeSingleton::MatchMode mode = MatchDefault) const;
|
Q_INVOKABLE QMimeType mimeTypeForFile(const QString &fileName, MatchMode mode = MatchDefault) const;
|
||||||
Q_INVOKABLE QMimeType mimeTypeForFile(const QFileInfo &fileInfo, FileTypeSingleton::MatchMode mode = MatchDefault) const;
|
Q_INVOKABLE QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode = MatchDefault) const;
|
||||||
Q_INVOKABLE QList<QMimeType> mimeTypesForFileName(const QString &fileName) const;
|
Q_INVOKABLE QList<QMimeType> mimeTypesForFileName(const QString &fileName) const;
|
||||||
|
|
||||||
Q_INVOKABLE QMimeType mimeTypeForData(const QByteArray &data) const;
|
Q_INVOKABLE QMimeType mimeTypeForData(const QByteArray &data) const;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user