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.86.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,10 +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()
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -131,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).
|
||||||
|
|
||||||
|
|||||||
@@ -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,8 +78,9 @@ MouseArea {
|
|||||||
id: replyText
|
id: replyText
|
||||||
textMessage: reply.display
|
textMessage: reply.display
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,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,13 +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)
|
|
||||||
ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage))
|
|
||||||
|
|
||||||
text: "<style>
|
text: "<style>
|
||||||
table {
|
table {
|
||||||
@@ -38,19 +38,13 @@ 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
|
Layout.fillWidth: true
|
||||||
@@ -62,12 +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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ Kirigami.OverlaySheet {
|
|||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: altAlias
|
id: altAlias
|
||||||
Kirigami.FormData.label: i18n("Other Aliases:")
|
Kirigami.FormData.label: i18n("Alt Aliases")
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
visible: room.altAliases && room.altAliases.length
|
visible: room.altAliases && room.altAliases.length
|
||||||
@@ -139,6 +139,7 @@ Kirigami.OverlaySheet {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: modelData
|
text: modelData
|
||||||
|
color: Kirigami.Theme.disabledColor
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
|
|||||||
@@ -74,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 {
|
||||||
@@ -129,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,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: pushReplaceLayer("qrc:/imports/NeoChat/Page/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/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {
|
||||||
@@ -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,7 +5,6 @@
|
|||||||
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 {
|
||||||
@@ -17,16 +16,9 @@ Kirigami.OverlaySheet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -138,10 +97,11 @@ Kirigami.Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -178,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 {
|
||||||
|
|||||||
@@ -9,71 +9,43 @@ import org.kde.kirigami 2.15 as Kirigami
|
|||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
Kirigami.Page {
|
Kirigami.ScrollablePage {
|
||||||
title: i18n("Devices")
|
title: i18n("Devices")
|
||||||
|
|
||||||
leftPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
ListView {
|
||||||
topPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
model: DevicesModel {
|
||||||
bottomPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
id: devices
|
||||||
rightPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: pageSettingStack
|
|
||||||
function onWideModeChanged() {
|
|
||||||
scroll.background.visible = pageSettingStack.wideMode
|
|
||||||
}
|
}
|
||||||
}
|
delegate: Kirigami.SwipeListItem {
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
Controls.ScrollView {
|
text: model.displayName
|
||||||
id: scroll
|
subtitle: model.id
|
||||||
Component.onCompleted: background.visible = pageSettingStack.wideMode
|
icon: "network-connect"
|
||||||
anchors.fill: parent
|
|
||||||
ListView {
|
|
||||||
clip: true
|
|
||||||
model: DevicesModel {
|
|
||||||
id: devices
|
|
||||||
}
|
}
|
||||||
|
actions: [
|
||||||
Kirigami.PlaceholderMessage {
|
Kirigami.Action {
|
||||||
visible: parent.model.count === 0 // We can assume 0 means loading since there is at least one device
|
text: i18n("Edit device name")
|
||||||
anchors.centerIn: parent
|
iconName: "document-edit"
|
||||||
text: i18n("Loading")
|
onTriggered: {
|
||||||
Controls.BusyIndicator {
|
renameSheet.index = model.index
|
||||||
running: parent.visible
|
renameSheet.name = model.displayName
|
||||||
}
|
renameSheet.open()
|
||||||
}
|
|
||||||
|
|
||||||
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.Action {
|
||||||
|
text: i18n("Logout device")
|
||||||
|
iconName: "edit-delete-remove"
|
||||||
|
onTriggered: {
|
||||||
|
passwordSheet.index = index
|
||||||
|
passwordSheet.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,39 +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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.main: Kirigami.Action {
|
|
||||||
text: i18n("New room")
|
|
||||||
icon.name: "list-add"
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToNextRoom() {
|
function goToNextRoom() {
|
||||||
do {
|
do {
|
||||||
@@ -65,55 +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.margins: 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
|
||||||
@@ -130,7 +64,6 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ItemSelectionModel {
|
ItemSelectionModel {
|
||||||
id: itemSelection
|
id: itemSelection
|
||||||
model: roomListModel
|
model: roomListModel
|
||||||
@@ -154,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))
|
||||||
}
|
}
|
||||||
@@ -165,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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,125 +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")
|
|
||||||
flat: true
|
|
||||||
|
|
||||||
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
|
||||||
@@ -235,7 +228,7 @@ Kirigami.ScrollablePage {
|
|||||||
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
|
||||||
@@ -267,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;
|
||||||
@@ -372,17 +365,18 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
innerObject: TextDelegate {
|
innerObject: TextDelegate {
|
||||||
isEmote: true
|
isEmote: true
|
||||||
Layout.fillWidth: Config.compactLayout
|
Layout.fillWidth: !Config.showAvatarInTimeline
|
||||||
Layout.maximumWidth: emoteContainer.bubbleMaxWidth
|
Layout.maximumWidth: emoteContainer.bubbleMaxWidth
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||||
|
Layout.bottomMargin: Kirigami.Units.largeSpacing * 2
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.RightButton
|
acceptedButtons: Qt.RightButton
|
||||||
onTapped: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody, parent.selectedText)
|
onTapped: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
|
||||||
}
|
}
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
onLongPressed: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody, parent.selectedText)
|
onLongPressed: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -398,17 +392,18 @@ Kirigami.ScrollablePage {
|
|||||||
hoverComponent: hoverActions
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
innerObject: TextDelegate {
|
innerObject: TextDelegate {
|
||||||
Layout.fillWidth: Config.compactLayout
|
Layout.fillWidth: !Config.showAvatarInTimeline
|
||||||
Layout.maximumWidth: messageContainer.bubbleMaxWidth
|
Layout.maximumWidth: messageContainer.bubbleMaxWidth
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.RightButton
|
acceptedButtons: Qt.RightButton
|
||||||
onTapped: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody, parent.selectedText)
|
onTapped: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
|
||||||
}
|
}
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
onLongPressed: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody, parent.selectedText)
|
onLongPressed: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,10 +418,11 @@ Kirigami.ScrollablePage {
|
|||||||
onReplyClicked: goToEvent(eventID)
|
onReplyClicked: goToEvent(eventID)
|
||||||
|
|
||||||
innerObject: TextDelegate {
|
innerObject: TextDelegate {
|
||||||
Layout.fillWidth: !Config.compactLayout
|
Layout.fillWidth: !Config.showAvatarInTimeline
|
||||||
Layout.maximumWidth: noticeContainer.bubbleMaxWidth
|
Layout.maximumWidth: noticeContainer.bubbleMaxWidth
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||||
|
Layout.bottomMargin: Kirigami.Units.largeSpacing * 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,6 +439,7 @@ 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 {
|
||||||
@@ -453,7 +450,7 @@ Kirigami.ScrollablePage {
|
|||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
||||||
onTapped: {
|
onTapped: {
|
||||||
fullScreenImage.createObject(parent, {"filename": eventId, "localPath": currentRoom.urlToDownload(eventId), "blurhash": model.content.info["xyz.amorgan.blurhash"], "imageWidth": content.info.w, "imageHeight": content.info.h}).showFullScreen()
|
fullScreenImage.createObject(parent, {"filename": eventId, "localPath": currentRoom.urlToDownload(eventId)}).showFullScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -553,22 +550,6 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -588,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 {
|
||||||
@@ -900,9 +881,8 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Open context menu for normal message
|
/// Open context menu for normal message
|
||||||
function openMessageContext(author, message, eventId, source, eventType, formattedBody, selectedText) {
|
function openMessageContext(author, message, eventId, source, eventType, formattedBody) {
|
||||||
const contextMenu = messageDelegateContextMenu.createObject(page, {
|
const contextMenu = messageDelegateContextMenu.createObject(page, {
|
||||||
selectedText: selectedText,
|
|
||||||
author: author,
|
author: author,
|
||||||
message: message,
|
message: message,
|
||||||
eventId: eventId,
|
eventId: eventId,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15 as QQC2
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
@@ -9,80 +8,81 @@ import QtQuick.Layouts 1.15
|
|||||||
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.Settings 1.0
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
Kirigami.ScrollablePage {
|
||||||
title: i18n("Settings")
|
title: i18n("Settings")
|
||||||
bottomPadding: 0
|
|
||||||
leftPadding: 0
|
|
||||||
rightPadding: 0
|
|
||||||
topPadding: 0
|
|
||||||
|
|
||||||
onBackRequested: {
|
Kirigami.FormLayout {
|
||||||
if (pageSettingStack.depth > 1 && !pageSettingStack.wideMode && pageSettingStack.currentIndex !== 0) {
|
QQC2.CheckBox {
|
||||||
event.accepted = true;
|
Kirigami.FormData.label: i18n("General settings:")
|
||||||
pageSettingStack.pop();
|
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
|
||||||
Kirigami.PageRow {
|
// settings, make 2 separate groups with FormData labels.
|
||||||
id: pageSettingStack
|
Kirigami.FormData.label: i18n("Notifications and events:")
|
||||||
anchors.fill: parent
|
text: i18n("Show notifications")
|
||||||
columnView.columnWidth: Kirigami.Units.gridUnit * 12
|
checked: Config.showNotifications
|
||||||
initialPage: Kirigami.ScrollablePage {
|
onToggled: {
|
||||||
bottomPadding: 0
|
Config.showNotifications = checked
|
||||||
leftPadding: 0
|
Config.save()
|
||||||
rightPadding: 0
|
}
|
||||||
topPadding: 0
|
}
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
QQC2.CheckBox {
|
||||||
ListView {
|
text: i18n("Show leave and join events")
|
||||||
Component.onCompleted: if (pageSettingStack.wideMode) {
|
checked: Config.showLeaveJoinEvent
|
||||||
actions[0].trigger();
|
onToggled: {
|
||||||
}
|
Config.showLeaveJoinEvent = checked
|
||||||
property list<Kirigami.Action> actions: [
|
Config.save()
|
||||||
Kirigami.Action {
|
}
|
||||||
text: i18n("General")
|
}
|
||||||
icon.name: "org.kde.neochat"
|
QQC2.RadioButton {
|
||||||
onTriggered: pageSettingStack.push("qrc:/imports/NeoChat/Settings/GeneralSettingsPage.qml")
|
Kirigami.FormData.label: i18n("Rooms and private chats:")
|
||||||
},
|
text: i18n("Separated")
|
||||||
Kirigami.Action {
|
checked: !Config.mergeRoomList
|
||||||
text: i18n("Appearance")
|
onToggled: {
|
||||||
icon.name: "preferences-desktop-theme-global"
|
Config.mergeRoomList = false
|
||||||
onTriggered: pageSettingStack.push("qrc:/imports/NeoChat/Settings/AppearanceSettingsPage.qml")
|
Config.save()
|
||||||
},
|
}
|
||||||
Kirigami.Action {
|
}
|
||||||
text: i18n("Accounts")
|
QQC2.RadioButton {
|
||||||
icon.name: "preferences-system-users"
|
text: i18n("Intermixed")
|
||||||
onTriggered: pageSettingStack.push("qrc:/imports/NeoChat/Page/AccountsPage.qml")
|
checked: Config.mergeRoomList
|
||||||
},
|
onToggled: {
|
||||||
Kirigami.Action {
|
Config.mergeRoomList = true
|
||||||
text: i18n("Custom Emoji")
|
Config.save()
|
||||||
icon.name: "preferences-desktop-emoticons"
|
}
|
||||||
onTriggered: pageSettingStack.push("qrc:/imports/NeoChat/Settings/Emoticons.qml")
|
}
|
||||||
},
|
QQC2.CheckBox {
|
||||||
Kirigami.Action {
|
Kirigami.FormData.label: i18n("Timeline:")
|
||||||
text: i18n("Devices")
|
text: i18n("Show User Avatar")
|
||||||
iconName: "network-connect"
|
checked: Config.showAvatarInTimeline
|
||||||
onTriggered: pageSettingStack.push("qrc:/imports/NeoChat/Page/DevicesPage.qml")
|
onToggled: {
|
||||||
},
|
Config.showAvatarInTimeline = checked
|
||||||
Kirigami.Action {
|
Config.save()
|
||||||
text: i18n("About NeoChat")
|
}
|
||||||
icon.name: "help-about"
|
}
|
||||||
onTriggered: pageSettingStack.push(aboutPage)
|
QQC2.CheckBox {
|
||||||
}
|
text: i18n("Show Fancy Effects")
|
||||||
]
|
checked: Config.showFancyEffects
|
||||||
model: actions
|
onToggled: {
|
||||||
delegate: Kirigami.BasicListItem {
|
Config.showFancyEffects = checked
|
||||||
action: modelData
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: aboutPage
|
|
||||||
Kirigami.AboutPage {
|
|
||||||
aboutData: Controller.aboutData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -198,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) : ""
|
||||||
|
|||||||
@@ -1,254 +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 {
|
|
||||||
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,155 +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 {
|
|
||||||
|
|
||||||
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,78 +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 {
|
|
||||||
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,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,2 +0,0 @@
|
|||||||
module NeoChat.Settings
|
|
||||||
ThemeRadioButton 1.0 ThemeRadioButton.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>
|
||||||
@@ -117,21 +105,19 @@
|
|||||||
<p xml:lang="zh-CN">Matrix 是一个分布式通讯协议,使用户重新得到控制权。 目前,NeoChat 实现了协议的大部分,除了加密聊天和视频聊天。</p>
|
<p xml:lang="zh-CN">Matrix 是一个分布式通讯协议,使用户重新得到控制权。 目前,NeoChat 实现了协议的大部分,除了加密聊天和视频聊天。</p>
|
||||||
<p>NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
<p>NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
||||||
<p xml:lang="az">Vahid istifadəçi interfeysi ilə təmin olunan NeoChat, həm mobil telefonda həm də kompyuterlərdə işləyir.</p>
|
<p xml:lang="az">Vahid istifadəçi interfeysi ilə təmin olunan NeoChat, həm mobil telefonda həm də kompyuterlərdə işləyir.</p>
|
||||||
<p xml:lang="ca">El NeoChat funciona en el mòbils i a l'escriptori, proporcionant un experiència d'usuari coherent.</p>
|
<p xml:lang="ca">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||||
<p xml:lang="ca-valencia">El NeoChat funciona en el mòbils i a l'escriptori, proporcionant un experiència d'usuari coherent.</p>
|
<p xml:lang="ca-valencia">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||||
<p xml:lang="de">NeoChat funktioniert sowohl auf dem Mobiltelefon als auch auf dem Arbeitsfläche und bietet ein einheitliches Benutzererlebnis. </p>
|
<p xml:lang="de">NeoChat funktioniert sowohl auf dem Mobiltelefon als auch auf dem Arbeitsfläche und bietet ein einheitliches Benutzererlebnis. </p>
|
||||||
<p xml:lang="en-GB">NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
<p xml:lang="en-GB">NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
||||||
<p xml:lang="es">NeoChat funciona en móviles y en el escritorio a la vez que proporciona una experiencia de usuario consistente.</p>
|
<p xml:lang="es">NeoChat funciona en móviles y en el escritorio a la vez que proporciona una experiencia de usuario consistente.</p>
|
||||||
<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]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||||
|
|||||||
284
qml/main.qml
284
qml/main.qml
@@ -6,7 +6,7 @@ import QtQuick 2.15
|
|||||||
import QtQuick.Controls 2.15 as QQC2
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
import org.kde.kirigami 2.19 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
|
import NeoChat.Component 1.0
|
||||||
@@ -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,7 +140,6 @@ Kirigami.ApplicationWindow {
|
|||||||
root.show()
|
root.show()
|
||||||
root.raise()
|
root.raise()
|
||||||
root.requestActivate()
|
root.requestActivate()
|
||||||
Controller.raiseWindow(root)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contextDrawer: RoomDrawer {
|
contextDrawer: RoomDrawer {
|
||||||
@@ -160,96 +153,110 @@ 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
|
globalDrawer: Kirigami.GlobalDrawer {
|
||||||
readonly property bool shouldUseSidebars: RoomManager.hasOpenRoom && (Config.roomListPageWidth > minPageWidth ? root.width >= Kirigami.Units.gridUnit * 35 : root.width > Kirigami.Units.gridUnit * 27) && roomListLoaded
|
property bool hasLayer
|
||||||
readonly property int pageWidth: {
|
contentItem.implicitWidth: columnWidth
|
||||||
if (Config.roomListPageWidth === -1) {
|
isMenu: true
|
||||||
return defaultPageWidth;
|
actions: [
|
||||||
} else if (Config.roomListPageWidth < minPageWidth) {
|
Kirigami.Action {
|
||||||
return collapsedPageWidth;
|
text: i18n("Explore rooms")
|
||||||
} else {
|
icon.name: "compass"
|
||||||
return Config.roomListPageWidth;
|
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": Controller.activeConnection})
|
||||||
|
enabled: pageStack.layers.currentItem.title !== i18n("Explore Rooms") && Controller.accountCount > 0
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Start a Chat")
|
||||||
|
icon.name: "irc-join-channel"
|
||||||
|
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {"connection": Controller.activeConnection})
|
||||||
|
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Create a Room")
|
||||||
|
icon.name: "irc-join-channel"
|
||||||
|
onTriggered: {
|
||||||
|
let dialog = createRoomDialog.createObject(root.overlay);
|
||||||
|
dialog.open();
|
||||||
|
}
|
||||||
|
shortcut: StandardKey.New
|
||||||
|
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 {
|
||||||
|
text: i18n("Settings")
|
||||||
|
icon.name: "settings-configure"
|
||||||
|
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/SettingsPage.qml")
|
||||||
|
enabled: pageStack.layers.currentItem.title !== i18n("Settings")
|
||||||
|
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 {
|
||||||
|
text: i18n("Logout")
|
||||||
|
icon.name: "list-remove-user"
|
||||||
|
enabled: Controller.accountCount > 0
|
||||||
|
onTriggered: Controller.logout(Controller.activeConnection, true)
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Quit")
|
||||||
|
icon.name: "gtk-quit"
|
||||||
|
shortcut: StandardKey.Quit
|
||||||
|
onTriggered: Qt.quit()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: aboutPage
|
||||||
|
Kirigami.AboutPage {
|
||||||
|
aboutData: Controller.aboutData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pageStack.defaultColumnWidth: pageWidth
|
Component {
|
||||||
pageStack.columnView.columnResizeMode: shouldUseSidebars ? Kirigami.ColumnView.FixedColumns : Kirigami.ColumnView.SingleColumn
|
id: roomListComponent
|
||||||
|
RoomListPage {
|
||||||
MouseArea {
|
id: roomList
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: Controller.setBlur(pageStack, Config.blur && !Config.compactLayout);
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Config
|
target: LoginHelper
|
||||||
function onBlurChanged() {
|
function onInitialSyncFinished() {
|
||||||
Controller.setBlur(pageStack, Config.blur && !Config.compactLayout);
|
pageStack.replace(roomListComponent, {
|
||||||
|
activeConnection: Controller.activeConnection
|
||||||
|
});
|
||||||
|
roomListLoaded = true;
|
||||||
|
RoomManager.loadInitialRoom();
|
||||||
}
|
}
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
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
|
||||||
});
|
});
|
||||||
@@ -258,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();
|
||||||
@@ -273,7 +287,6 @@ Kirigami.ApplicationWindow {
|
|||||||
|
|
||||||
function onShowWindow() {
|
function onShowWindow() {
|
||||||
root.showWindow()
|
root.showWindow()
|
||||||
root.raise()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUserConsentRequired(url) {
|
function onUserConsentRequired(url) {
|
||||||
@@ -284,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,69 +382,4 @@ Kirigami.ApplicationWindow {
|
|||||||
color: Kirigami.Theme.backgroundColor
|
color: Kirigami.Theme.backgroundColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: settingsPageComponent
|
|
||||||
SettingsPage {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: roomListComponent
|
|
||||||
RoomListPage {
|
|
||||||
id: roomList
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root.roomPage
|
|
||||||
function onSwitchRoomUp() {
|
|
||||||
roomList.goToNextRoom();
|
|
||||||
}
|
|
||||||
function onSwitchRoomDown() {
|
|
||||||
roomList.goToPreviousRoom();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
footer: Kirigami.NavigationTabBar {
|
|
||||||
actions: [
|
|
||||||
Kirigami.Action {
|
|
||||||
icon.name: "globe"
|
|
||||||
text: "Spaces"
|
|
||||||
checked: fooPage.visble
|
|
||||||
onTriggered: {
|
|
||||||
console.warn("foo")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
icon.name: "dialog-messages"
|
|
||||||
text: "Rooms"
|
|
||||||
checked: fooPage.visble
|
|
||||||
onTriggered: {
|
|
||||||
while (pageStack.depth > 0) {
|
|
||||||
pageStack.pop();
|
|
||||||
}
|
|
||||||
pageStack.push(roomListComponent);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
icon.name: "document-open-recent-symbolic"
|
|
||||||
text: "Recent"
|
|
||||||
checked: fooPage.visble
|
|
||||||
onTriggered: {
|
|
||||||
console.warn("foo")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
icon.name: "settings-configure"
|
|
||||||
text: "Settings"
|
|
||||||
checked: fooPage.visble
|
|
||||||
onTriggered: {
|
|
||||||
while (pageStack.depth > 0) {
|
|
||||||
pageStack.pop();
|
|
||||||
}
|
|
||||||
pageStack.push(settingsPageComponent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
15
res.qrc
15
res.qrc
@@ -18,18 +18,12 @@
|
|||||||
<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>
|
||||||
@@ -63,8 +56,6 @@
|
|||||||
<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>
|
||||||
@@ -73,11 +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/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/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,10 +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
|
|
||||||
../res.qrc
|
../res.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,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)
|
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)
|
||||||
@@ -70,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"
|
||||||
@@ -104,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)
|
||||||
@@ -118,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,28 +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;
|
||||||
QString cleanedText = text;
|
QString cleanedText = text;
|
||||||
|
|
||||||
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) {
|
||||||
@@ -132,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
|
||||||
@@ -179,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,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"));
|
||||||
@@ -253,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
|
||||||
@@ -306,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;
|
||||||
}
|
}
|
||||||
@@ -330,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;
|
||||||
@@ -113,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()
|
||||||
@@ -144,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);
|
||||||
}
|
}
|
||||||
@@ -152,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);
|
||||||
@@ -165,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)
|
||||||
@@ -260,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 &) {
|
||||||
@@ -282,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()) {
|
||||||
@@ -391,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
|
||||||
@@ -463,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()) {
|
||||||
@@ -478,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);
|
||||||
@@ -561,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);
|
||||||
@@ -585,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(":")) {
|
||||||
@@ -594,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)
|
||||||
@@ -610,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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
125
src/login.cpp
125
src/login.cpp
@@ -18,11 +18,10 @@ Login::Login(QObject *parent)
|
|||||||
void Login::init()
|
void Login::init()
|
||||||
{
|
{
|
||||||
m_homeserverReachable = false;
|
m_homeserverReachable = false;
|
||||||
m_connection = new Connection();
|
m_connection = nullptr;
|
||||||
m_matrixId = QString();
|
m_matrixId = QString();
|
||||||
m_password = QString();
|
m_password = QString();
|
||||||
m_deviceName = QStringLiteral("NeoChat %1 %2 %3 %4")
|
m_deviceName = QString();
|
||||||
.arg(QSysInfo::machineHostName(), QSysInfo::productType(), QSysInfo::productVersion(), QSysInfo::currentCpuArchitecture());
|
|
||||||
m_supportsSso = false;
|
m_supportsSso = false;
|
||||||
m_supportsPassword = false;
|
m_supportsPassword = false;
|
||||||
m_ssoUrl = QUrl();
|
m_ssoUrl = QUrl();
|
||||||
@@ -30,17 +29,20 @@ void Login::init()
|
|||||||
connect(this, &Login::matrixIdChanged, this, [=]() {
|
connect(this, &Login::matrixIdChanged, this, [=]() {
|
||||||
setHomeserverReachable(false);
|
setHomeserverReachable(false);
|
||||||
|
|
||||||
|
if (m_connection) {
|
||||||
|
delete m_connection;
|
||||||
|
m_connection = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_matrixId == "@") {
|
if (m_matrixId == "@") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_testing = true;
|
m_testing = true;
|
||||||
Q_EMIT testingChanged();
|
Q_EMIT testingChanged();
|
||||||
if (!m_connection) {
|
m_connection = new Connection(this);
|
||||||
m_connection = new Connection();
|
|
||||||
}
|
|
||||||
m_connection->resolveServer(m_matrixId);
|
m_connection->resolveServer(m_matrixId);
|
||||||
connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [=]() {
|
connect(m_connection, &Connection::loginFlowsChanged, this, [=]() {
|
||||||
setHomeserverReachable(true);
|
setHomeserverReachable(true);
|
||||||
m_testing = false;
|
m_testing = false;
|
||||||
Q_EMIT testingChanged();
|
Q_EMIT testingChanged();
|
||||||
@@ -49,42 +51,6 @@ void Login::init()
|
|||||||
Q_EMIT loginFlowsChanged();
|
Q_EMIT loginFlowsChanged();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
connect(m_connection, &Connection::connected, this, [=] {
|
|
||||||
Q_EMIT connected();
|
|
||||||
m_isLoggingIn = false;
|
|
||||||
Q_EMIT isLoggingInChanged();
|
|
||||||
Q_ASSERT(m_connection);
|
|
||||||
AccountSettings account(m_connection->userId());
|
|
||||||
account.setKeepLoggedIn(true);
|
|
||||||
account.setHomeserver(m_connection->homeserver());
|
|
||||||
account.setDeviceId(m_connection->deviceId());
|
|
||||||
account.setDeviceName(m_deviceName);
|
|
||||||
if (!Controller::instance().saveAccessTokenToKeyChain(account, m_connection->accessToken())) {
|
|
||||||
qWarning() << "Couldn't save access token";
|
|
||||||
}
|
|
||||||
account.sync();
|
|
||||||
Controller::instance().addConnection(m_connection);
|
|
||||||
Controller::instance().setActiveConnection(m_connection);
|
|
||||||
m_connection = nullptr;
|
|
||||||
});
|
|
||||||
connect(m_connection, &Connection::networkError, this, [=](QString error, const QString &, int, int) {
|
|
||||||
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
|
|
||||||
m_isLoggingIn = false;
|
|
||||||
Q_EMIT isLoggingInChanged();
|
|
||||||
});
|
|
||||||
connect(m_connection, &Connection::loginError, this, [=](QString error, const QString &) {
|
|
||||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
|
||||||
m_isLoggingIn = false;
|
|
||||||
Q_EMIT isLoggingInChanged();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_connection, &Connection::resolveError, this, [=](QString error) {
|
|
||||||
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
|
|
||||||
});
|
|
||||||
|
|
||||||
connectSingleShot(m_connection, &Connection::syncDone, this, [=]() {
|
|
||||||
Q_EMIT Controller::instance().initiated();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Login::setHomeserverReachable(bool reachable)
|
void Login::setHomeserverReachable(bool reachable)
|
||||||
@@ -139,7 +105,51 @@ void Login::login()
|
|||||||
m_isLoggingIn = true;
|
m_isLoggingIn = true;
|
||||||
Q_EMIT isLoggingInChanged();
|
Q_EMIT isLoggingInChanged();
|
||||||
|
|
||||||
m_connection->loginWithPassword(m_matrixId, m_password, m_deviceName, QString());
|
setDeviceName("NeoChat " + QSysInfo::machineHostName() + " " + QSysInfo::productType() + " " + QSysInfo::productVersion() + " "
|
||||||
|
+ QSysInfo::currentCpuArchitecture());
|
||||||
|
|
||||||
|
m_connection = new Connection(this);
|
||||||
|
m_connection->resolveServer(m_matrixId);
|
||||||
|
|
||||||
|
connect(m_connection, &Connection::loginFlowsChanged, this, [=]() {
|
||||||
|
m_connection->loginWithPassword(m_matrixId, m_password, m_deviceName, QString());
|
||||||
|
connect(m_connection, &Connection::connected, this, [=] {
|
||||||
|
Q_EMIT connected();
|
||||||
|
m_isLoggingIn = false;
|
||||||
|
Q_EMIT isLoggingInChanged();
|
||||||
|
AccountSettings account(m_connection->userId());
|
||||||
|
account.setKeepLoggedIn(true);
|
||||||
|
account.clearAccessToken(); // Drop the legacy - just in case
|
||||||
|
account.setHomeserver(m_connection->homeserver());
|
||||||
|
account.setDeviceId(m_connection->deviceId());
|
||||||
|
account.setDeviceName(m_deviceName);
|
||||||
|
if (!Controller::instance().saveAccessTokenToKeyChain(account, m_connection->accessToken())) {
|
||||||
|
qWarning() << "Couldn't save access token";
|
||||||
|
}
|
||||||
|
account.sync();
|
||||||
|
Controller::instance().addConnection(m_connection);
|
||||||
|
Controller::instance().setActiveConnection(m_connection);
|
||||||
|
});
|
||||||
|
connect(m_connection, &Connection::networkError, [=](QString error, const QString &, int, int) {
|
||||||
|
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
|
||||||
|
m_isLoggingIn = false;
|
||||||
|
Q_EMIT isLoggingInChanged();
|
||||||
|
});
|
||||||
|
connect(m_connection, &Connection::loginError, [=](QString error, const QString &) {
|
||||||
|
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||||
|
m_isLoggingIn = false;
|
||||||
|
Q_EMIT isLoggingInChanged();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_connection, &Connection::resolveError, this, [=](QString error) {
|
||||||
|
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_connection, &Connection::syncDone, this, [=]() {
|
||||||
|
Q_EMIT initialSyncFinished();
|
||||||
|
disconnect(m_connection, &Connection::syncDone, this, nullptr);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Login::supportsPassword() const
|
bool Login::supportsPassword() const
|
||||||
@@ -159,12 +169,29 @@ QUrl Login::ssoUrl() const
|
|||||||
|
|
||||||
void Login::loginWithSso()
|
void Login::loginWithSso()
|
||||||
{
|
{
|
||||||
m_connection->resolveServer(m_matrixId);
|
SsoSession *session = m_connection->prepareForSso("NeoChat " + QSysInfo::machineHostName() + " " + QSysInfo::productType() + " "
|
||||||
connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [=]() {
|
+ QSysInfo::productVersion() + " " + QSysInfo::currentCpuArchitecture());
|
||||||
SsoSession *session = m_connection->prepareForSso(m_deviceName);
|
m_ssoUrl = session->ssoUrl();
|
||||||
m_ssoUrl = session->ssoUrl();
|
|
||||||
});
|
|
||||||
Q_EMIT ssoUrlChanged();
|
Q_EMIT ssoUrlChanged();
|
||||||
|
connect(m_connection, &Connection::connected, [=]() {
|
||||||
|
Q_EMIT connected();
|
||||||
|
AccountSettings account(m_connection->userId());
|
||||||
|
account.setKeepLoggedIn(true);
|
||||||
|
account.clearAccessToken(); // Drop the legacy - just in case
|
||||||
|
account.setHomeserver(m_connection->homeserver());
|
||||||
|
account.setDeviceId(m_connection->deviceId());
|
||||||
|
account.setDeviceName(m_deviceName);
|
||||||
|
if (!Controller::instance().saveAccessTokenToKeyChain(account, m_connection->accessToken())) {
|
||||||
|
qWarning() << "Couldn't save access token";
|
||||||
|
}
|
||||||
|
account.sync();
|
||||||
|
Controller::instance().addConnection(m_connection);
|
||||||
|
Controller::instance().setActiveConnection(m_connection);
|
||||||
|
});
|
||||||
|
connect(m_connection, &Connection::syncDone, this, [=]() {
|
||||||
|
Q_EMIT initialSyncFinished();
|
||||||
|
disconnect(m_connection, &Connection::syncDone, this, nullptr);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Login::testing() const
|
bool Login::testing() const
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ Q_SIGNALS:
|
|||||||
void matrixIdChanged();
|
void matrixIdChanged();
|
||||||
void passwordChanged();
|
void passwordChanged();
|
||||||
void deviceNameChanged();
|
void deviceNameChanged();
|
||||||
|
void initialSyncFinished();
|
||||||
void loginFlowsChanged();
|
void loginFlowsChanged();
|
||||||
void ssoUrlChanged();
|
void ssoUrlChanged();
|
||||||
void connected();
|
void connected();
|
||||||
@@ -76,6 +77,6 @@ private:
|
|||||||
bool m_supportsPassword = false;
|
bool m_supportsPassword = false;
|
||||||
Connection *m_connection = nullptr;
|
Connection *m_connection = nullptr;
|
||||||
QUrl m_ssoUrl;
|
QUrl m_ssoUrl;
|
||||||
bool m_testing = false;
|
bool m_testing;
|
||||||
bool m_isLoggingIn = false;
|
bool m_isLoggingIn = false;
|
||||||
};
|
};
|
||||||
|
|||||||
88
src/main.cpp
88
src/main.cpp
@@ -2,16 +2,15 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
#include <QDebug>
|
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QIcon>
|
|
||||||
#include <QNetworkProxy>
|
#include <QNetworkProxy>
|
||||||
#include <QNetworkProxyFactory>
|
#include <QNetworkProxyFactory>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QQuickStyle>
|
#include <QQuickStyle>
|
||||||
#include <QQuickWindow>
|
#include <QQuickWindow>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
@@ -22,7 +21,6 @@
|
|||||||
#include <KAboutData>
|
#include <KAboutData>
|
||||||
#ifdef HAVE_KDBUSADDONS
|
#ifdef HAVE_KDBUSADDONS
|
||||||
#include <KDBusService>
|
#include <KDBusService>
|
||||||
#include <KWindowSystem>
|
|
||||||
#endif
|
#endif
|
||||||
#include <KLocalizedContext>
|
#include <KLocalizedContext>
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
@@ -32,7 +30,6 @@
|
|||||||
|
|
||||||
#include "accountlistmodel.h"
|
#include "accountlistmodel.h"
|
||||||
#include "actionshandler.h"
|
#include "actionshandler.h"
|
||||||
#include "blurhashimageprovider.h"
|
|
||||||
#include "chatboxhelper.h"
|
#include "chatboxhelper.h"
|
||||||
#include "chatdocumenthandler.h"
|
#include "chatdocumenthandler.h"
|
||||||
#include "clipboard.h"
|
#include "clipboard.h"
|
||||||
@@ -40,7 +37,6 @@
|
|||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "csapi/joining.h"
|
#include "csapi/joining.h"
|
||||||
#include "csapi/leaving.h"
|
#include "csapi/leaving.h"
|
||||||
#include "customemojimodel.h"
|
|
||||||
#include "devicesmodel.h"
|
#include "devicesmodel.h"
|
||||||
#include "emojimodel.h"
|
#include "emojimodel.h"
|
||||||
#include "filetypesingleton.h"
|
#include "filetypesingleton.h"
|
||||||
@@ -53,32 +49,15 @@
|
|||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "notificationsmanager.h"
|
#include "notificationsmanager.h"
|
||||||
#include "publicroomlistmodel.h"
|
#include "publicroomlistmodel.h"
|
||||||
|
#include <room.h>
|
||||||
#include "roomlistmodel.h"
|
#include "roomlistmodel.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include "sortfilterroomlistmodel.h"
|
#include "sortfilterroomlistmodel.h"
|
||||||
#include "spellcheckhighlighter.h"
|
|
||||||
#include "userdirectorylistmodel.h"
|
#include "userdirectorylistmodel.h"
|
||||||
#include "userlistmodel.h"
|
#include "userlistmodel.h"
|
||||||
#include "webshortcutmodel.h"
|
|
||||||
#include <room.h>
|
|
||||||
#ifdef HAVE_COLORSCHEME
|
|
||||||
#include "colorschemer.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
#ifdef HAVE_KDBUSADDONS
|
|
||||||
static void raiseWindow(QWindow *window)
|
|
||||||
{
|
|
||||||
if (KWindowSystem::isPlatformWayland()) {
|
|
||||||
KWindowSystem::setCurrentXdgActivationToken(qEnvironmentVariable("XDG_ACTIVATION_TOKEN"));
|
|
||||||
KWindowSystem::activateWindow(window->winId());
|
|
||||||
} else {
|
|
||||||
window->raise();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
Q_DECL_EXPORT
|
Q_DECL_EXPORT
|
||||||
#endif
|
#endif
|
||||||
@@ -90,9 +69,8 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
QGuiApplication app(argc, argv);
|
QGuiApplication app(argc, argv);
|
||||||
QQuickStyle::setStyle(QStringLiteral("org.kde.breeze"));
|
QQuickStyle::setStyle(QStringLiteral("Material"));
|
||||||
#else
|
#else
|
||||||
QIcon::setFallbackThemeName("breeze");
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
// Default to org.kde.desktop style unless the user forces another style
|
// Default to org.kde.desktop style unless the user forces another style
|
||||||
if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
|
if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
|
||||||
@@ -119,34 +97,28 @@ int main(int argc, char *argv[])
|
|||||||
QStringLiteral(NEOCHAT_VERSION_STRING),
|
QStringLiteral(NEOCHAT_VERSION_STRING),
|
||||||
i18n("Matrix client"),
|
i18n("Matrix client"),
|
||||||
KAboutLicense::GPL_V3,
|
KAboutLicense::GPL_V3,
|
||||||
i18n("© 2018-2020 Black Hat, 2020-2021 KDE Community"));
|
i18n("© 2018-2020 Black Hat, 2020 KDE Community"));
|
||||||
about.addAuthor(i18n("Black Hat"), QString(), QStringLiteral("bhat@encom.eu.org"));
|
about.addAuthor(i18n("Black Hat"), QString(), QStringLiteral("bhat@encom.eu.org"));
|
||||||
about.addAuthor(i18n("Carl Schwan"), QString(), QStringLiteral("carl@carlschwan.eu"));
|
about.addAuthor(i18n("Carl Schwan"), QString(), QStringLiteral("carl@carlschwan.eu"));
|
||||||
about.addAuthor(i18n("Tobias Fella"), QString(), QStringLiteral("fella@posteo.de"));
|
about.addAuthor(i18n("Tobias Fella"), QString(), QStringLiteral("fella@posteo.de"));
|
||||||
about.setOrganizationDomain("kde.org");
|
about.setOrganizationDomain("kde.org");
|
||||||
|
|
||||||
about.addComponent(QStringLiteral("libQuotient"),
|
|
||||||
i18n("A Qt5 library to write cross-platform clients for Matrix"),
|
|
||||||
QString(),
|
|
||||||
QStringLiteral("https://github.com/quotient-im/libquotient"),
|
|
||||||
KAboutLicense::LGPL_V2_1);
|
|
||||||
|
|
||||||
KAboutData::setApplicationData(about);
|
KAboutData::setApplicationData(about);
|
||||||
QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.kde.neochat")));
|
QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.kde.neochat")));
|
||||||
|
|
||||||
#ifdef HAVE_KDBUSADDONS
|
#ifdef HAVE_KDBUSADDONS
|
||||||
KDBusService service(KDBusService::Unique);
|
KDBusService service(KDBusService::Unique);
|
||||||
service.connect(&service, &KDBusService::activateRequested, &RoomManager::instance(), [](const QStringList &arguments, const QString &workingDirectory) {
|
service.connect(&service,
|
||||||
Q_UNUSED(workingDirectory);
|
&KDBusService::activateRequested,
|
||||||
if (arguments.isEmpty()) {
|
&RoomManager::instance(),
|
||||||
return;
|
[](const QStringList &arguments, const QString &workingDirectory) {
|
||||||
}
|
Q_UNUSED(workingDirectory);
|
||||||
auto args = arguments;
|
auto args = arguments;
|
||||||
args.removeFirst();
|
args.removeFirst();
|
||||||
for (const auto &arg : args) {
|
for (const auto &arg : args) {
|
||||||
RoomManager::instance().openResource(arg);
|
RoomManager::instance().openResource(arg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef NEOCHAT_FLATPAK
|
#ifdef NEOCHAT_FLATPAK
|
||||||
@@ -162,39 +134,36 @@ int main(int argc, char *argv[])
|
|||||||
Login *login = new Login();
|
Login *login = new Login();
|
||||||
ChatBoxHelper chatBoxHelper;
|
ChatBoxHelper chatBoxHelper;
|
||||||
|
|
||||||
#ifdef HAVE_COLORSCHEME
|
|
||||||
ColorSchemer colorScheme;
|
|
||||||
qmlRegisterSingletonInstance<ColorSchemer>("org.kde.neochat", 1, 0, "ColorSchemer", &colorScheme);
|
|
||||||
if (!config->colorScheme().isEmpty()) {
|
|
||||||
colorScheme.apply(config->colorScheme());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Controller", &Controller::instance());
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Controller", &Controller::instance());
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Clipboard", &clipboard);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Clipboard", &clipboard);
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Config", config);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Config", config);
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "RoomManager", &RoomManager::instance());
|
qmlRegisterSingletonInstance<RoomManager>("org.kde.neochat", 1, 0, "RoomManager", &RoomManager::instance());
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "FileType", &fileTypeSingleton);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "FileType", &fileTypeSingleton);
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login);
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "ChatBoxHelper", &chatBoxHelper);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "ChatBoxHelper", &chatBoxHelper);
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "EmojiModel", new EmojiModel(&app));
|
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "CommandModel", new CommandModel(&app));
|
|
||||||
qmlRegisterType<AccountListModel>("org.kde.neochat", 1, 0, "AccountListModel");
|
qmlRegisterType<AccountListModel>("org.kde.neochat", 1, 0, "AccountListModel");
|
||||||
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
|
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
|
||||||
qmlRegisterType<ChatDocumentHandler>("org.kde.neochat", 1, 0, "ChatDocumentHandler");
|
qmlRegisterType<ChatDocumentHandler>("org.kde.neochat", 1, 0, "ChatDocumentHandler");
|
||||||
qmlRegisterType<SpellcheckHighlighter>("org.kde.neochat", 1, 0, "SpellcheckHighlighter");
|
|
||||||
qmlRegisterType<RoomListModel>("org.kde.neochat", 1, 0, "RoomListModel");
|
qmlRegisterType<RoomListModel>("org.kde.neochat", 1, 0, "RoomListModel");
|
||||||
qmlRegisterType<KWebShortcutModel>("org.kde.neochat", 1, 0, "WebShortcutModel");
|
|
||||||
qmlRegisterType<UserListModel>("org.kde.neochat", 1, 0, "UserListModel");
|
qmlRegisterType<UserListModel>("org.kde.neochat", 1, 0, "UserListModel");
|
||||||
qmlRegisterType<CustomEmojiModel>("org.kde.neochat", 1, 0, "CustomEmojiModel");
|
|
||||||
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
||||||
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
|
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
|
||||||
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
|
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
|
||||||
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
|
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
|
||||||
|
qmlRegisterSingletonType<EmojiModel>("org.kde.neochat", 1, 0, "EmojiModel", [](QQmlEngine *engine2, QJSEngine *scriptEngine) -> QObject * {
|
||||||
|
Q_UNUSED(scriptEngine);
|
||||||
|
Q_UNUSED(engine2);
|
||||||
|
return new EmojiModel();
|
||||||
|
});
|
||||||
|
qmlRegisterSingletonType<CommandModel>("org.kde.neochat", 1, 0, "CommandModel", [](QQmlEngine *engine2, QJSEngine *scriptEngine) -> QObject * {
|
||||||
|
Q_UNUSED(scriptEngine);
|
||||||
|
Q_UNUSED(engine2);
|
||||||
|
return new CommandModel();
|
||||||
|
});
|
||||||
qmlRegisterType<SortFilterRoomListModel>("org.kde.neochat", 1, 0, "SortFilterRoomListModel");
|
qmlRegisterType<SortFilterRoomListModel>("org.kde.neochat", 1, 0, "SortFilterRoomListModel");
|
||||||
qmlRegisterType<DevicesModel>("org.kde.neochat", 1, 0, "DevicesModel");
|
qmlRegisterType<DevicesModel>("org.kde.neochat", 1, 0, "DevicesModel");
|
||||||
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
||||||
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");
|
qmlRegisterUncreatableType<RoomType>("org.kde.neochat", 1, 0, "RoomType", "ENUM");
|
||||||
qmlRegisterUncreatableType<UserType>("org.kde.neochat", 1, 0, "UserType", "ENUM");
|
qmlRegisterUncreatableType<UserType>("org.kde.neochat", 1, 0, "UserType", "ENUM");
|
||||||
|
|
||||||
qRegisterMetaType<User *>("User*");
|
qRegisterMetaType<User *>("User*");
|
||||||
@@ -227,7 +196,6 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
engine.addImportPath("qrc:/imports");
|
engine.addImportPath("qrc:/imports");
|
||||||
engine.addImageProvider(QLatin1String("mxc"), new MatrixImageProvider);
|
engine.addImageProvider(QLatin1String("mxc"), new MatrixImageProvider);
|
||||||
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
|
|
||||||
|
|
||||||
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
|
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
|
||||||
if (engine.rootObjects().isEmpty()) {
|
if (engine.rootObjects().isEmpty()) {
|
||||||
@@ -245,7 +213,7 @@ int main(int argc, char *argv[])
|
|||||||
auto view = qobject_cast<QQuickWindow *>(obj);
|
auto view = qobject_cast<QQuickWindow *>(obj);
|
||||||
if (view) {
|
if (view) {
|
||||||
view->show();
|
view->show();
|
||||||
raiseWindow(view);
|
view->raise();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ void ThumbnailResponse::prepareResult()
|
|||||||
errorStr.clear();
|
errorStr.clear();
|
||||||
} else if (job->error() == BaseJob::Abandoned) {
|
} else if (job->error() == BaseJob::Abandoned) {
|
||||||
errorStr = i18n("Image request has been cancelled");
|
errorStr = i18n("Image request has been cancelled");
|
||||||
// qDebug() << "ThumbnailResponse: cancelled for" << mediaId;
|
//qDebug() << "ThumbnailResponse: cancelled for" << mediaId;
|
||||||
} else {
|
} else {
|
||||||
errorStr = job->errorString();
|
errorStr = job->errorString();
|
||||||
qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-" << errorStr;
|
qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-" << errorStr;
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
#include <QQmlEngine> // for qmlRegisterType()
|
#include <QQmlEngine> // for qmlRegisterType()
|
||||||
#include <QTimeZone>
|
#include <QTimeZone>
|
||||||
|
|
||||||
#include <KFormat>
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
#include <KFormat>
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
@@ -37,7 +37,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[HighlightRole] = "isHighlighted";
|
roles[HighlightRole] = "isHighlighted";
|
||||||
roles[SpecialMarksRole] = "marks";
|
roles[SpecialMarksRole] = "marks";
|
||||||
roles[LongOperationRole] = "progressInfo";
|
roles[LongOperationRole] = "progressInfo";
|
||||||
roles[FileMimetypeIcon] = "fileMimetypeIcon";
|
|
||||||
roles[AnnotationRole] = "annotation";
|
roles[AnnotationRole] = "annotation";
|
||||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||||
roles[ReplyRole] = "reply";
|
roles[ReplyRole] = "reply";
|
||||||
@@ -57,6 +56,7 @@ MessageEventModel::MessageEventModel(QObject *parent)
|
|||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
qmlRegisterAnonymousType<FileTransferInfo>("org.kde.neochat", 1);
|
qmlRegisterAnonymousType<FileTransferInfo>("org.kde.neochat", 1);
|
||||||
qRegisterMetaType<FileTransferInfo>();
|
qRegisterMetaType<FileTransferInfo>();
|
||||||
|
qmlRegisterUncreatableType<EventStatus>("org.kde.neochat", 1, 0, "EventStatus", "EventStatus is not an creatable type");
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [=]() {
|
QTimer::singleShot(0, this, [=]() {
|
||||||
if (!m_currentRoom) {
|
if (!m_currentRoom) {
|
||||||
@@ -68,7 +68,7 @@ MessageEventModel::MessageEventModel(QObject *parent)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto it = m_currentRoom->findInTimeline(m_currentRoom->readMarkerEventId());
|
const auto it = m_currentRoom->findInTimeline(m_currentRoom->readMarkerEventId());
|
||||||
if (it == m_currentRoom->historyEdge()) {
|
if (it == m_currentRoom->timelineEdge()) {
|
||||||
m_currentRoom->getPreviousContent(50);
|
m_currentRoom->getPreviousContent(50);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -144,7 +144,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
endInsertRows();
|
endInsertRows();
|
||||||
if (!m_lastReadEventIndex.isValid()) {
|
if (!m_lastReadEventIndex.isValid()) {
|
||||||
// no read marker, so see if we need to create one.
|
// no read marker, so see if we need to create one.
|
||||||
moveReadMarker(m_currentRoom->readMarkerEventId());
|
moveReadMarker(QString(), m_currentRoom->readMarkerEventId());
|
||||||
}
|
}
|
||||||
if (biggest < m_currentRoom->maxTimelineIndex()) {
|
if (biggest < m_currentRoom->maxTimelineIndex()) {
|
||||||
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1;
|
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1;
|
||||||
@@ -166,7 +166,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
movingEvent = true;
|
movingEvent = true;
|
||||||
// Reverse i because row 0 is bottommost in the model
|
// Reverse i because row 0 is bottommost in the model
|
||||||
const auto row = timelineBaseIndex() - i - 1;
|
const auto row = timelineBaseIndex() - i - 1;
|
||||||
beginMoveRows({}, row, row, {}, timelineBaseIndex());
|
Q_ASSERT(beginMoveRows({}, row, row, {}, timelineBaseIndex()));
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::pendingEventMerged, this, [this] {
|
connect(m_currentRoom, &Room::pendingEventMerged, this, [this] {
|
||||||
if (movingEvent) {
|
if (movingEvent) {
|
||||||
@@ -184,10 +184,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
beginRemoveRows({}, i, i);
|
beginRemoveRows({}, i, i);
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::pendingEventDiscarded, this, &MessageEventModel::endRemoveRows);
|
connect(m_currentRoom, &Room::pendingEventDiscarded, this, &MessageEventModel::endRemoveRows);
|
||||||
connect(m_currentRoom, &Room::readMarkerMoved, this, [=](const QString &fromEventId, const QString &toEventId) {
|
connect(m_currentRoom, &Room::readMarkerMoved, this, &MessageEventModel::moveReadMarker);
|
||||||
Q_UNUSED(fromEventId);
|
|
||||||
moveReadMarker(toEventId);
|
|
||||||
});
|
|
||||||
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
|
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
|
||||||
refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
|
refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
|
||||||
});
|
});
|
||||||
@@ -200,9 +197,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent);
|
connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent);
|
||||||
connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent);
|
connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent);
|
||||||
connect(m_currentRoom, &Room::fileTransferFailed, this, &MessageEventModel::refreshEvent);
|
connect(m_currentRoom, &Room::fileTransferFailed, this, &MessageEventModel::refreshEvent);
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
connect(m_currentRoom, &Room::fileTransferCancelled, this, &MessageEventModel::refreshEvent);
|
connect(m_currentRoom, &Room::fileTransferCancelled, this, &MessageEventModel::refreshEvent);
|
||||||
#endif
|
|
||||||
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [=] {
|
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [=] {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
@@ -235,10 +230,10 @@ void MessageEventModel::refreshEventRoles(int row, const QVector<int> &roles)
|
|||||||
Q_EMIT dataChanged(idx, idx, roles);
|
Q_EMIT dataChanged(idx, idx, roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageEventModel::moveReadMarker(const QString &toEventId)
|
void MessageEventModel::moveReadMarker(const QString &fromEventId, const QString &toEventId)
|
||||||
{
|
{
|
||||||
const auto timelineIt = m_currentRoom->findInTimeline(toEventId);
|
const auto timelineIt = m_currentRoom->findInTimeline(toEventId);
|
||||||
if (timelineIt == m_currentRoom->historyEdge()) {
|
if (timelineIt == m_currentRoom->timelineEdge()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int newRow = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex();
|
int newRow = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex();
|
||||||
@@ -283,7 +278,7 @@ int MessageEventModel::refreshEventRoles(const QString &id, const QVector<int> &
|
|||||||
row = int(pendingIt - m_currentRoom->pendingEvents().begin());
|
row = int(pendingIt - m_currentRoom->pendingEvents().begin());
|
||||||
} else {
|
} else {
|
||||||
const auto timelineIt = m_currentRoom->findInTimeline(id);
|
const auto timelineIt = m_currentRoom->findInTimeline(id);
|
||||||
if (timelineIt == m_currentRoom->historyEdge()) {
|
if (timelineIt == m_currentRoom->timelineEdge()) {
|
||||||
qWarning() << "Trying to refresh inexistent event:" << id;
|
qWarning() << "Trying to refresh inexistent event:" << id;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -372,6 +367,7 @@ int MessageEventModel::rowCount(const QModelIndex &parent) const
|
|||||||
} else {
|
} else {
|
||||||
return m_currentRoom->timelineSize();
|
return m_currentRoom->timelineSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MessageEventModel::canFetchMore(const QModelIndex &parent) const
|
bool MessageEventModel::canFetchMore(const QModelIndex &parent) const
|
||||||
@@ -389,6 +385,7 @@ void MessageEventModel::fetchMore(const QModelIndex &parent)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline QVariantMap userAtEvent(NeoChatUser *user, NeoChatRoom *room, const RoomEvent &evt)
|
inline QVariantMap userAtEvent(NeoChatUser *user, NeoChatRoom *room, const RoomEvent &evt)
|
||||||
{
|
{
|
||||||
Q_UNUSED(evt)
|
Q_UNUSED(evt)
|
||||||
@@ -415,22 +412,22 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
bool isPending = row < timelineBaseIndex();
|
bool isPending = row < timelineBaseIndex();
|
||||||
|
|
||||||
if (m_lastReadEventIndex.row() == row) {
|
if (m_lastReadEventIndex.row() == row) {
|
||||||
switch (role) {
|
switch(role) {
|
||||||
case EventTypeRole:
|
case EventTypeRole:
|
||||||
return QStringLiteral("readMarker");
|
return QStringLiteral("readMarker");
|
||||||
case TimeRole: {
|
case TimeRole:
|
||||||
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime();
|
{
|
||||||
const KFormat format;
|
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime();
|
||||||
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
|
const KFormat format;
|
||||||
}
|
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
|
||||||
|
}
|
||||||
case SpecialMarksRole:
|
case SpecialMarksRole:
|
||||||
return EventStatus::Hidden;
|
return EventStatus::Hidden;
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto timelineIt = m_currentRoom->messageEvents().crbegin()
|
const auto timelineIt = m_currentRoom->messageEvents().crbegin() + std::max(0, row - timelineBaseIndex() - (m_lastReadEventIndex.isValid() && m_lastReadEventIndex.row() < row ? 1 : 0));
|
||||||
+ std::max(0, row - timelineBaseIndex() - (m_lastReadEventIndex.isValid() && m_lastReadEventIndex.row() < row ? 1 : 0));
|
|
||||||
const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + std::min(row, timelineBaseIndex());
|
const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + std::min(row, timelineBaseIndex());
|
||||||
const auto &evt = isPending ? **pendingIt : **timelineIt;
|
const auto &evt = isPending ? **pendingIt : **timelineIt;
|
||||||
|
|
||||||
@@ -438,7 +435,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
if (evt.isRedacted()) {
|
if (evt.isRedacted()) {
|
||||||
auto reason = evt.redactedBecause()->reason();
|
auto reason = evt.redactedBecause()->reason();
|
||||||
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>")
|
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>")
|
||||||
: i18n("<i>[This message was deleted: %1]</i>", evt.redactedBecause()->reason());
|
: i18n("<i>[This message was deleted: %1]</i>").arg(evt.redactedBecause()->reason());
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_currentRoom->eventToString(evt, Qt::RichText);
|
return m_currentRoom->eventToString(evt, Qt::RichText);
|
||||||
@@ -490,9 +487,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
if (evt.isStateEvent()) {
|
if (evt.isStateEvent()) {
|
||||||
return "state";
|
return "state";
|
||||||
}
|
}
|
||||||
if (is<const EncryptedEvent>(evt)) {
|
|
||||||
return "encrypted";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "other";
|
return "other";
|
||||||
}
|
}
|
||||||
@@ -536,15 +530,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return m_currentRoom->isEventHighlighted(&evt);
|
return m_currentRoom->isEventHighlighted(&evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == FileMimetypeIcon) {
|
|
||||||
auto e = eventCast<const RoomMessageEvent>(&evt);
|
|
||||||
if (!e || !e->hasFileContent()) {
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
return e->content()->fileInfo()->mimeType.iconName();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == SpecialMarksRole) {
|
if (role == SpecialMarksRole) {
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return pendingIt->deliveryStatus();
|
return pendingIt->deliveryStatus();
|
||||||
@@ -635,7 +620,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
|
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
|
||||||
if (replyIt == m_currentRoom->historyEdge()) {
|
if (replyIt == m_currentRoom->timelineEdge()) {
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
const auto &replyEvt = **replyIt;
|
const auto &replyEvt = **replyIt;
|
||||||
@@ -684,11 +669,12 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
content = QVariant::fromValue(e->image().originalJson);
|
content = QVariant::fromValue(e->image().originalJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariantMap{{"eventId", replyEventId},
|
return QVariantMap{
|
||||||
{"display", m_currentRoom->eventToString(replyEvt, Qt::RichText)},
|
{"eventId", replyEventId},
|
||||||
{"content", content},
|
{"display", m_currentRoom->eventToString(replyEvt, Qt::RichText)},
|
||||||
{"type", type},
|
{"content", content},
|
||||||
{"author", userAtEvent(static_cast<NeoChatUser *>(m_currentRoom->user(replyEvt.senderId())), m_currentRoom, evt)}};
|
{"type", type},
|
||||||
|
{"author", userAtEvent(static_cast<NeoChatUser *>(m_currentRoom->user(replyEvt.senderId())), m_currentRoom, evt)}};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == ShowAuthorRole) {
|
if (role == ShowAuthorRole) {
|
||||||
@@ -757,8 +743,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
int MessageEventModel::eventIDToIndex(const QString &eventID) const
|
int MessageEventModel::eventIDToIndex(const QString &eventID) const
|
||||||
{
|
{
|
||||||
const auto it = m_currentRoom->findInTimeline(eventID);
|
const auto it = m_currentRoom->findInTimeline(eventID);
|
||||||
if (it == m_currentRoom->historyEdge()) {
|
if (it == m_currentRoom->timelineEdge()) {
|
||||||
// qWarning() << "Trying to find inexistent event:" << eventID;
|
//qWarning() << "Trying to find inexistent event:" << eventID;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
|
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ public:
|
|||||||
UserMarkerRole,
|
UserMarkerRole,
|
||||||
FormattedBodyRole,
|
FormattedBodyRole,
|
||||||
|
|
||||||
FileMimetypeIcon,
|
|
||||||
|
|
||||||
ReplyRole,
|
ReplyRole,
|
||||||
|
|
||||||
ShowAuthorRole,
|
ShowAuthorRole,
|
||||||
@@ -84,7 +82,7 @@ private:
|
|||||||
void refreshLastUserEvents(int baseTimelineRow);
|
void refreshLastUserEvents(int baseTimelineRow);
|
||||||
void refreshEventRoles(int row, const QVector<int> &roles = {});
|
void refreshEventRoles(int row, const QVector<int> &roles = {});
|
||||||
int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {});
|
int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {});
|
||||||
void moveReadMarker(const QString &toEventId);
|
void moveReadMarker(const QString &fromEventId, const QString &toEventId);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void roomChanged();
|
void roomChanged();
|
||||||
|
|||||||
@@ -14,17 +14,6 @@
|
|||||||
<entry name="ActiveConnection" type="String">
|
<entry name="ActiveConnection" type="String">
|
||||||
<label>Latest active connection</label>
|
<label>Latest active connection</label>
|
||||||
</entry>
|
</entry>
|
||||||
<entry name="ColorScheme" type="String">
|
|
||||||
<label>Color scheme</label>
|
|
||||||
</entry>
|
|
||||||
<entry name="Blur" type="bool">
|
|
||||||
<label>Make NeoChat blurry</label>
|
|
||||||
<default>false</default>
|
|
||||||
</entry>
|
|
||||||
<entry name="Transparency" type="Double">
|
|
||||||
<label>Background transparency value</label>
|
|
||||||
<default>0.3</default>
|
|
||||||
</entry>
|
|
||||||
<entry name="ShowNotifications" type="bool">
|
<entry name="ShowNotifications" type="bool">
|
||||||
<label>Show notifications</label>
|
<label>Show notifications</label>
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
@@ -41,23 +30,12 @@
|
|||||||
<label>Use s/text/replacement syntax to edit your last message.</label>
|
<label>Use s/text/replacement syntax to edit your last message.</label>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
</entry>
|
</entry>
|
||||||
<entry name="ShowLocalMessagesOnRight" type="bool">
|
|
||||||
<label>"Show your messages on the right</label>
|
|
||||||
<default>true</default>
|
|
||||||
</entry>
|
|
||||||
<entry name="RoomListPageWidth" type="int">
|
|
||||||
<default>-1</default>
|
|
||||||
</entry>
|
|
||||||
</group>
|
</group>
|
||||||
<group name="Timeline">
|
<group name="Timeline">
|
||||||
<entry name="ShowAvatarInTimeline" type="bool">
|
<entry name="ShowAvatarInTimeline" type="bool">
|
||||||
<label>Show avatar in the timeline</label>
|
<label>Show avatar in the timeline</label>
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
</entry>
|
</entry>
|
||||||
<entry name="CompactLayout" type="bool">
|
|
||||||
<label>Use a compact layout</label>
|
|
||||||
<default>false</default>
|
|
||||||
</entry>
|
|
||||||
<entry name="ShowRename" type="bool">
|
<entry name="ShowRename" type="bool">
|
||||||
<label>Show rename events in the timeline</label>
|
<label>Show rename events in the timeline</label>
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
@@ -75,11 +53,5 @@
|
|||||||
<default>true</default>
|
<default>true</default>
|
||||||
</entry>
|
</entry>
|
||||||
</group>
|
</group>
|
||||||
<group name="RoomDrawer">
|
|
||||||
<entry name="ShowAvatarInRoomDrawer" type="bool">
|
|
||||||
<label>Show avatar in the room drawer</label>
|
|
||||||
<default>true</default>
|
|
||||||
</entry>
|
|
||||||
</group>
|
|
||||||
</kcfg>
|
</kcfg>
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,10 @@
|
|||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <qcoro/qcorosignal.h>
|
|
||||||
#include <qcoro/task.h>
|
|
||||||
|
|
||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
#include "csapi/account-data.h"
|
#include "csapi/account-data.h"
|
||||||
#include "csapi/content-repo.h"
|
#include "csapi/content-repo.h"
|
||||||
#include "csapi/leaving.h"
|
#include "csapi/leaving.h"
|
||||||
#include "csapi/redaction.h"
|
|
||||||
#include "csapi/room_state.h"
|
#include "csapi/room_state.h"
|
||||||
#include "csapi/rooms.h"
|
#include "csapi/rooms.h"
|
||||||
#include "csapi/typing.h"
|
#include "csapi/typing.h"
|
||||||
@@ -57,7 +53,6 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
Q_EMIT isInviteChanged();
|
Q_EMIT isInviteChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(this, &Room::displaynameChanged, this, &NeoChatRoom::displayNameChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::uploadFile(const QUrl &url, const QString &body)
|
void NeoChatRoom::uploadFile(const QUrl &url, const QString &body)
|
||||||
@@ -152,23 +147,10 @@ const RoomMessageEvent *NeoChatRoom::lastEvent(bool ignoreStateEvent) const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::lastEventIsSpoiler() const
|
|
||||||
{
|
|
||||||
if (auto event = lastEvent()) {
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(event)) {
|
|
||||||
if (e->hasTextContent() && e->content() && e->mimeType().name() == "text/html") {
|
|
||||||
auto htmlBody = static_cast<const Quotient::EventContent::TextContent *>(e->content())->body;
|
|
||||||
return htmlBody.contains("data-mx-spoiler");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString NeoChatRoom::lastEventToString() const
|
QString NeoChatRoom::lastEventToString() const
|
||||||
{
|
{
|
||||||
if (auto event = lastEvent()) {
|
if (auto event = lastEvent()) {
|
||||||
return htmlSafeMemberName(event->senderId()) + (event->isStateEvent() ? " " : ": ") + eventToString(*event);
|
return user(event->senderId())->displayname(this) + (event->isStateEvent() ? " " : ": ") + eventToString(*event);
|
||||||
}
|
}
|
||||||
return QLatin1String("");
|
return QLatin1String("");
|
||||||
}
|
}
|
||||||
@@ -239,12 +221,12 @@ QDateTime NeoChatRoom::lastActiveTime()
|
|||||||
|
|
||||||
int NeoChatRoom::savedTopVisibleIndex() const
|
int NeoChatRoom::savedTopVisibleIndex() const
|
||||||
{
|
{
|
||||||
return firstDisplayedMarker() == historyEdge() ? 0 : int(firstDisplayedMarker() - messageEvents().rbegin());
|
return firstDisplayedMarker() == timelineEdge() ? 0 : int(firstDisplayedMarker() - messageEvents().rbegin());
|
||||||
}
|
}
|
||||||
|
|
||||||
int NeoChatRoom::savedBottomVisibleIndex() const
|
int NeoChatRoom::savedBottomVisibleIndex() const
|
||||||
{
|
{
|
||||||
return lastDisplayedMarker() == historyEdge() ? 0 : int(lastDisplayedMarker() - messageEvents().rbegin());
|
return lastDisplayedMarker() == timelineEdge() ? 0 : int(lastDisplayedMarker() - messageEvents().rbegin());
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::saveViewport(int topIndex, int bottomIndex)
|
void NeoChatRoom::saveViewport(int topIndex, int bottomIndex)
|
||||||
@@ -280,13 +262,15 @@ QVariantList NeoChatRoom::getUsers(const QString &keyword) const
|
|||||||
return matchedList;
|
return matchedList;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap NeoChatRoom::getUser(const QString &userID) const
|
QVariantMap NeoChatRoom::getUser(const QString& userID) const
|
||||||
{
|
{
|
||||||
NeoChatUser user(userID, connection());
|
NeoChatUser user(userID, connection());
|
||||||
return QVariantMap{{QStringLiteral("id"), user.id()},
|
return QVariantMap {
|
||||||
{QStringLiteral("displayName"), user.displayname(this)},
|
{ QStringLiteral("id"), user.id() },
|
||||||
{QStringLiteral("avatarMediaId"), user.avatarMediaId(this)},
|
{ QStringLiteral("displayName"), user.displayname(this) },
|
||||||
{QStringLiteral("color"), user.color()}};
|
{ QStringLiteral("avatarMediaId"), user.avatarMediaId(this) },
|
||||||
|
{ QStringLiteral("color"), user.color() }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl NeoChatRoom::urlToMxcUrl(const QUrl &mxcUrl)
|
QUrl NeoChatRoom::urlToMxcUrl(const QUrl &mxcUrl)
|
||||||
@@ -318,7 +302,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
return visit(
|
return visit(
|
||||||
evt,
|
evt,
|
||||||
[this, prettyPrint, removeReply](const RoomMessageEvent &e) {
|
[prettyPrint, removeReply](const RoomMessageEvent &e) {
|
||||||
using namespace MessageEventContent;
|
using namespace MessageEventContent;
|
||||||
|
|
||||||
// 1. prettyPrint/HTML
|
// 1. prettyPrint/HTML
|
||||||
@@ -330,10 +314,6 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
htmlBody.replace(utils::userPillRegExp, R"(<b class="user-pill">\1</b>)");
|
htmlBody.replace(utils::userPillRegExp, R"(<b class="user-pill">\1</b>)");
|
||||||
htmlBody.replace(utils::strikethroughRegExp, "<s>\\1</s>");
|
htmlBody.replace(utils::strikethroughRegExp, "<s>\\1</s>");
|
||||||
|
|
||||||
auto url = connection()->homeserver();
|
|
||||||
auto base = url.scheme() + QStringLiteral("://") + url.host() + (url.port() != -1 ? ':' + QString::number(url.port()) : QString());
|
|
||||||
htmlBody.replace(utils::mxcImageRegExp, QStringLiteral(R"(<img \1 src="%1/_matrix/media/r0/download/\2/\3" \4 > )").arg(base));
|
|
||||||
|
|
||||||
return htmlBody;
|
return htmlBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,7 +351,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
},
|
},
|
||||||
[this](const RoomMemberEvent &e) {
|
[this](const RoomMemberEvent &e) {
|
||||||
// FIXME: Rewind to the name that was at the time of this event
|
// FIXME: Rewind to the name that was at the time of this event
|
||||||
auto subjectName = this->htmlSafeMemberName(e.userId());
|
auto subjectName = this->user(e.userId())->displayname();
|
||||||
// The below code assumes senderName output in AuthorRole
|
// The below code assumes senderName output in AuthorRole
|
||||||
switch (e.membership()) {
|
switch (e.membership()) {
|
||||||
case MembershipType::Invite:
|
case MembershipType::Invite:
|
||||||
@@ -399,7 +379,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
}
|
}
|
||||||
// Part 2: profile changes of joined members
|
// Part 2: profile changes of joined members
|
||||||
if (e.isRename() && NeoChatConfig::self()->showRename()) {
|
if (e.isRename() && NeoChatConfig::self()->showRename()) {
|
||||||
if (e.displayName().isEmpty()) {
|
if (!e.displayName().isEmpty()) {
|
||||||
text = i18nc("their refers to a singular user", "cleared their display name");
|
text = i18nc("their refers to a singular user", "cleared their display name");
|
||||||
} else {
|
} else {
|
||||||
text = i18nc("their refers to a singular user", "changed their display name to %1", e.displayName().toHtmlEscaped());
|
text = i18nc("their refers to a singular user", "changed their display name to %1", e.displayName().toHtmlEscaped());
|
||||||
@@ -438,7 +418,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
return (e.senderId() != e.userId()) ? i18n("banned %1 from the room: %2", subjectName, e.contentJson()["reason"_ls].toString().toHtmlEscaped())
|
return (e.senderId() != e.userId()) ? i18n("banned %1 from the room: %2", subjectName, e.contentJson()["reason"_ls].toString().toHtmlEscaped())
|
||||||
: i18n("self-banned from the room");
|
: i18n("self-banned from the room");
|
||||||
case MembershipType::Knock:
|
case MembershipType::Knock:
|
||||||
return i18n("requested an invite");
|
return i18n("knocked");
|
||||||
default:;
|
default:;
|
||||||
}
|
}
|
||||||
return i18n("made something unknown");
|
return i18n("made something unknown");
|
||||||
@@ -463,10 +443,9 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
: i18n("created the room, version %1", e.version().isEmpty() ? "1" : e.version().toHtmlEscaped());
|
: i18n("created the room, version %1", e.version().isEmpty() ? "1" : e.version().toHtmlEscaped());
|
||||||
},
|
},
|
||||||
[](const StateEventBase &e) {
|
[](const StateEventBase &e) {
|
||||||
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
|
// A small hack for state events from TWIM bot
|
||||||
return i18n("changed the server access control lists for this room");
|
return e.stateKey() == "twim" ? i18n("updated the database")
|
||||||
}
|
: e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
|
||||||
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
|
|
||||||
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
|
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
|
||||||
},
|
},
|
||||||
i18n("Unknown event"));
|
i18n("Unknown event"));
|
||||||
@@ -476,16 +455,12 @@ void NeoChatRoom::changeAvatar(const QUrl &localFile)
|
|||||||
{
|
{
|
||||||
const auto job = connection()->uploadFile(localFile.toLocalFile());
|
const auto job = connection()->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
|
||||||
connect(job, &BaseJob::success, this, [this, job] {
|
connect(job, &BaseJob::success, this, [this, job] {
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar", localUser()->id(), QJsonObject{{"url", job->contentUri().toString()}});
|
|
||||||
#else
|
|
||||||
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar", localUser()->id(), QJsonObject{{"url", job->contentUri()}});
|
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar", localUser()->id(), QJsonObject{{"url", job->contentUri()}});
|
||||||
#endif
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -557,14 +532,12 @@ QString msgTypeToString(MessageEventType msgType)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NeoChatRoom::preprocessText(const QString &text)
|
|
||||||
{
|
|
||||||
return markdownToHTML(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::postMessage(const QString &rawText, const QString &text, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
|
void NeoChatRoom::postMessage(const QString &rawText, const QString &text, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
|
||||||
{
|
{
|
||||||
postHtmlMessage(rawText, text, type, replyEventId, relateToEventId);
|
const auto html = markdownToHTML(text);
|
||||||
|
QString cleanText(text);
|
||||||
|
cleanText.replace(QRegularExpression("\\[(.+)\\]\\(.+\\)"), "\\1");
|
||||||
|
postHtmlMessage(rawText, html, type, replyEventId, relateToEventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
|
void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
|
||||||
@@ -573,7 +546,7 @@ void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, Mess
|
|||||||
bool isReply = !replyEventId.isEmpty();
|
bool isReply = !replyEventId.isEmpty();
|
||||||
bool isEdit = !relateToEventId.isEmpty();
|
bool isEdit = !relateToEventId.isEmpty();
|
||||||
const auto replyIt = findInTimeline(replyEventId);
|
const auto replyIt = findInTimeline(replyEventId);
|
||||||
if (replyIt == historyEdge()) {
|
if (replyIt == timelineEdge()) {
|
||||||
isReply = false;
|
isReply = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -639,7 +612,7 @@ void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto eventIt = findInTimeline(eventId);
|
const auto eventIt = findInTimeline(eventId);
|
||||||
if (eventIt == historyEdge()) {
|
if (eventIt == timelineEdge()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,7 +677,7 @@ bool NeoChatRoom::canSendState(const QString &eventType) const
|
|||||||
bool NeoChatRoom::readMarkerLoaded() const
|
bool NeoChatRoom::readMarkerLoaded() const
|
||||||
{
|
{
|
||||||
const auto it = findInTimeline(readMarkerEventId());
|
const auto it = findInTimeline(readMarkerEventId());
|
||||||
return it != historyEdge();
|
return it != timelineEdge();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::isInvite() const
|
bool NeoChatRoom::isInvite() const
|
||||||
@@ -716,36 +689,3 @@ bool NeoChatRoom::isUserBanned(const QString &user) const
|
|||||||
{
|
{
|
||||||
return getCurrentState<RoomMemberEvent>(user)->membership() == MembershipType::Ban;
|
return getCurrentState<RoomMemberEvent>(user)->membership() == MembershipType::Ban;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NeoChatRoom::htmlSafeName() const
|
|
||||||
{
|
|
||||||
return name().toHtmlEscaped();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString NeoChatRoom::htmlSafeDisplayName() const
|
|
||||||
{
|
|
||||||
return displayName().toHtmlEscaped();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::deleteMessagesByUser(const QString &user)
|
|
||||||
{
|
|
||||||
doDeleteMessagesByUser(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user)
|
|
||||||
{
|
|
||||||
QStringList events;
|
|
||||||
for (const auto &event : messageEvents()) {
|
|
||||||
if (event->senderId() == user && !event->isRedacted() && !event.viewAs<RedactionEvent>() && !event->isStateEvent()) {
|
|
||||||
events += event->id();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const auto &e : events) {
|
|
||||||
auto job = connection()->callApi<RedactEventJob>(id(), QUrl::toPercentEncoding(e), connection()->generateTxnId());
|
|
||||||
co_await qCoro(job, &BaseJob::finished);
|
|
||||||
if (job->error() != BaseJob::Success) {
|
|
||||||
qWarning() << "Error: \"" << job->error() << "\" while deleting messages. Aborting";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include <qcoro/task.h>
|
|
||||||
|
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "room.h"
|
#include "room.h"
|
||||||
|
|
||||||
@@ -33,7 +31,6 @@ class NeoChatRoom : public Room
|
|||||||
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
||||||
Q_PROPERTY(QDateTime lastActiveTime READ lastActiveTime NOTIFY lastActiveTimeChanged)
|
Q_PROPERTY(QDateTime lastActiveTime READ lastActiveTime NOTIFY lastActiveTimeChanged)
|
||||||
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
|
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
|
||||||
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit NeoChatRoom(Connection *connection, QString roomId, JoinState joinState = {});
|
explicit NeoChatRoom(Connection *connection, QString roomId, JoinState joinState = {});
|
||||||
@@ -50,15 +47,8 @@ public:
|
|||||||
/// Convenient way to get the last event but in a string format.
|
/// Convenient way to get the last event but in a string format.
|
||||||
///
|
///
|
||||||
/// \see lastEvent
|
/// \see lastEvent
|
||||||
/// \see lastEventIsSpoiler
|
|
||||||
[[nodiscard]] QString lastEventToString() const;
|
[[nodiscard]] QString lastEventToString() const;
|
||||||
|
|
||||||
/// Convenient way to check if the last event looks like it has spoilers.
|
|
||||||
///
|
|
||||||
/// \see lastEvent
|
|
||||||
/// \see lastEventToString
|
|
||||||
[[nodiscard]] bool lastEventIsSpoiler() const;
|
|
||||||
|
|
||||||
/// Convenient way to get the QDateTime of the last event.
|
/// Convenient way to get the QDateTime of the last event.
|
||||||
///
|
///
|
||||||
/// \see lastEvent
|
/// \see lastEvent
|
||||||
@@ -115,16 +105,6 @@ public:
|
|||||||
|
|
||||||
bool isInvite() const;
|
bool isInvite() const;
|
||||||
|
|
||||||
Q_INVOKABLE QString htmlSafeName() const;
|
|
||||||
Q_INVOKABLE QString htmlSafeDisplayName() const;
|
|
||||||
|
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
Q_INVOKABLE QString htmlSafeMemberName(const QString &userId) const
|
|
||||||
{
|
|
||||||
return safeMemberName(userId).toHtmlEscaped();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_cachedInput;
|
QString m_cachedInput;
|
||||||
QSet<const Quotient::RoomEvent *> highlights;
|
QSet<const Quotient::RoomEvent *> highlights;
|
||||||
@@ -139,7 +119,6 @@ private:
|
|||||||
void onRedaction(const RoomEvent &prevEvent, const RoomEvent &after) override;
|
void onRedaction(const RoomEvent &prevEvent, const RoomEvent &after) override;
|
||||||
|
|
||||||
static QString markdownToHTML(const QString &markdown);
|
static QString markdownToHTML(const QString &markdown);
|
||||||
QCoro::Task<void> doDeleteMessagesByUser(const QString &user);
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void countChanged();
|
void countChanged();
|
||||||
@@ -153,15 +132,12 @@ Q_SIGNALS:
|
|||||||
void readMarkerLoadedChanged();
|
void readMarkerLoadedChanged();
|
||||||
void lastActiveTimeChanged();
|
void lastActiveTimeChanged();
|
||||||
void isInviteChanged();
|
void isInviteChanged();
|
||||||
void displayNameChanged();
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void uploadFile(const QUrl &url, const QString &body = QString());
|
void uploadFile(const QUrl &url, const QString &body = QString());
|
||||||
void acceptInvitation();
|
void acceptInvitation();
|
||||||
void forget();
|
void forget();
|
||||||
void sendTypingNotification(bool isTyping);
|
void sendTypingNotification(bool isTyping);
|
||||||
QString preprocessText(const QString &text);
|
|
||||||
|
|
||||||
/// @param rawText The text as it was typed.
|
/// @param rawText The text as it was typed.
|
||||||
/// @param cleanedText The text with link to the users.
|
/// @param cleanedText The text with link to the users.
|
||||||
void postMessage(const QString &rawText,
|
void postMessage(const QString &rawText,
|
||||||
@@ -178,5 +154,4 @@ public Q_SLOTS:
|
|||||||
void addLocalAlias(const QString &alias);
|
void addLocalAlias(const QString &alias);
|
||||||
void removeLocalAlias(const QString &alias);
|
void removeLocalAlias(const QString &alias);
|
||||||
void toggleReaction(const QString &eventId, const QString &reaction);
|
void toggleReaction(const QString &eventId, const QString &reaction);
|
||||||
void deleteMessagesByUser(const QString &user);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,13 +3,24 @@
|
|||||||
|
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <PlatformTheme> // Kirigami
|
||||||
#include <QPalette>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "csapi/profile.h"
|
||||||
|
|
||||||
|
#include "controller.h"
|
||||||
|
|
||||||
|
static Kirigami::PlatformTheme *s_theme = nullptr;
|
||||||
|
|
||||||
NeoChatUser::NeoChatUser(QString userId, Connection *connection)
|
NeoChatUser::NeoChatUser(QString userId, Connection *connection)
|
||||||
: User(std::move(userId), connection)
|
: User(std::move(userId), connection)
|
||||||
{
|
{
|
||||||
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, &NeoChatUser::polishColor);
|
if (!s_theme) {
|
||||||
|
s_theme = static_cast<Kirigami::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::PlatformTheme>(&Controller::instance(), true));
|
||||||
|
Q_ASSERT(s_theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(s_theme, &Kirigami::PlatformTheme::colorsChanged, this, &NeoChatUser::polishColor);
|
||||||
polishColor();
|
polishColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +41,9 @@ void NeoChatUser::setColor(const QColor &color)
|
|||||||
|
|
||||||
void NeoChatUser::polishColor()
|
void NeoChatUser::polishColor()
|
||||||
{
|
{
|
||||||
const auto lightness = static_cast<QGuiApplication *>(QGuiApplication::instance())->palette().color(QPalette::Active, QPalette::Window).lightnessF();
|
|
||||||
// https://github.com/quotient-im/libQuotient/wiki/User-color-coding-standard-draft-proposal
|
// https://github.com/quotient-im/libQuotient/wiki/User-color-coding-standard-draft-proposal
|
||||||
setColor(QColor::fromHslF(hueF(), 1, -0.7 * lightness + 0.9, 1));
|
setColor(QColor::fromHslF(hueF(),
|
||||||
|
1 - s_theme->alternateBackgroundColor().saturationF(),
|
||||||
|
-0.7 * s_theme->alternateBackgroundColor().lightnessF() + 0.9,
|
||||||
|
s_theme->textColor().alphaF()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,14 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "room.h"
|
||||||
#include "user.h"
|
#include "user.h"
|
||||||
|
|
||||||
|
namespace Kirigami
|
||||||
|
{
|
||||||
|
class PlatformTheme;
|
||||||
|
}
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
class NeoChatUser : public User
|
class NeoChatUser : public User
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "neochatconfig.h"
|
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
|
#include "neochatconfig.h"
|
||||||
|
|
||||||
NotificationsManager &NotificationsManager::instance()
|
NotificationsManager &NotificationsManager::instance()
|
||||||
{
|
{
|
||||||
@@ -55,17 +55,19 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
|||||||
notification->setPixmap(img);
|
notification->setPixmap(img);
|
||||||
|
|
||||||
notification->setDefaultAction(i18n("Open NeoChat in this room"));
|
notification->setDefaultAction(i18n("Open NeoChat in this room"));
|
||||||
connect(notification, &KNotification::defaultActivated, this, [=]() {
|
connect(notification, &KNotification::defaultActivated, this, [this, room]() {
|
||||||
RoomManager::instance().enterRoom(room);
|
Q_EMIT RoomManager::instance().enterRoom(room);
|
||||||
Q_EMIT Controller::instance().showWindow();
|
Q_EMIT Controller::instance().showWindow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#if KNOTIFICATIONS_VERSION >= QT_VERSION_CHECK(5, 81, 0)
|
||||||
std::unique_ptr<KNotificationReplyAction> replyAction(new KNotificationReplyAction(i18n("Reply")));
|
std::unique_ptr<KNotificationReplyAction> replyAction(new KNotificationReplyAction(i18n("Reply")));
|
||||||
replyAction->setPlaceholderText(i18n("Reply..."));
|
replyAction->setPlaceholderText(i18n("Reply..."));
|
||||||
connect(replyAction.get(), &KNotificationReplyAction::replied, this, [room, replyEventId](const QString &text) {
|
QObject::connect(replyAction.get(), &KNotificationReplyAction::replied, [room, replyEventId](const QString &text) {
|
||||||
room->postMessage(text, room->preprocessText(text), RoomMessageEvent::MsgType::Text, replyEventId, QString());
|
room->postMessage(text, text, RoomMessageEvent::MsgType::Text, replyEventId, QString());
|
||||||
});
|
});
|
||||||
notification->setReplyAction(std::move(replyAction));
|
notification->setReplyAction(std::move(replyAction));
|
||||||
|
#endif
|
||||||
|
|
||||||
notification->sendEvent();
|
notification->sendEvent();
|
||||||
|
|
||||||
|
|||||||
@@ -173,11 +173,8 @@ QVariant PublicRoomListModel::data(const QModelIndex &index, int role) const
|
|||||||
if (avatarUrl.isEmpty()) {
|
if (avatarUrl.isEmpty()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return avatarUrl.url().remove(0, 6);
|
|
||||||
#else
|
|
||||||
return avatarUrl.remove(0, 6);
|
return avatarUrl.remove(0, 6);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
if (role == TopicRole) {
|
if (role == TopicRole) {
|
||||||
return room.topic;
|
return room.topic;
|
||||||
|
|||||||
@@ -26,8 +26,6 @@
|
|||||||
#include "notificationsmanager.h"
|
#include "notificationsmanager.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(Quotient::JoinState)
|
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
bool useUnityCounter()
|
bool useUnityCounter()
|
||||||
{
|
{
|
||||||
@@ -291,6 +289,7 @@ void RoomListModel::updateRoom(Room *room, Room *prev)
|
|||||||
} else {
|
} else {
|
||||||
beginInsertRows(QModelIndex(), m_rooms.count(), m_rooms.count());
|
beginInsertRows(QModelIndex(), m_rooms.count(), m_rooms.count());
|
||||||
doAddRoom(newRoom);
|
doAddRoom(newRoom);
|
||||||
|
RoomManager::instance().enterRoom(qobject_cast<NeoChatRoom *>(newRoom));
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,7 +328,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
NeoChatRoom *room = m_rooms.at(index.row());
|
NeoChatRoom *room = m_rooms.at(index.row());
|
||||||
if (role == NameRole) {
|
if (role == NameRole) {
|
||||||
return !room->name().isEmpty() ? room->htmlSafeName() : room->htmlSafeDisplayName();
|
return !room->name().isEmpty() ? room->name() : room->displayName();
|
||||||
}
|
}
|
||||||
if (role == DisplayNameRole) {
|
if (role == DisplayNameRole) {
|
||||||
return room->displayName();
|
return room->displayName();
|
||||||
@@ -342,26 +341,18 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
if (role == CategoryRole) {
|
if (role == CategoryRole) {
|
||||||
if (room->joinState() == JoinState::Invite) {
|
if (room->joinState() == JoinState::Invite) {
|
||||||
return NeoChatRoomType::Invited;
|
return RoomType::Invited;
|
||||||
}
|
}
|
||||||
if (room->isFavourite()) {
|
if (room->isFavourite()) {
|
||||||
return NeoChatRoomType::Favorite;
|
return RoomType::Favorite;
|
||||||
}
|
}
|
||||||
if (room->isLowPriority()) {
|
if (room->isLowPriority()) {
|
||||||
return NeoChatRoomType::Deprioritized;
|
return RoomType::Deprioritized;
|
||||||
}
|
}
|
||||||
if (room->isDirectChat()) {
|
if (room->isDirectChat()) {
|
||||||
return NeoChatRoomType::Direct;
|
return RoomType::Direct;
|
||||||
}
|
}
|
||||||
const RoomCreateEvent *creationEvent = room->creation();
|
return RoomType::Normal;
|
||||||
QJsonObject contentJson = creationEvent->contentJson();
|
|
||||||
QJsonObject::const_iterator typeIter = contentJson.find("type");
|
|
||||||
if (typeIter != contentJson.end()) {
|
|
||||||
if (typeIter.value().toString() == "m.space") {
|
|
||||||
return NeoChatRoomType::Space;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NeoChatRoomType::Normal;
|
|
||||||
}
|
}
|
||||||
if (role == UnreadCountRole) {
|
if (role == UnreadCountRole) {
|
||||||
return room->unreadCount();
|
return room->unreadCount();
|
||||||
@@ -373,9 +364,6 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
|||||||
return room->highlightCount();
|
return room->highlightCount();
|
||||||
}
|
}
|
||||||
if (role == LastEventRole) {
|
if (role == LastEventRole) {
|
||||||
if (room->lastEventIsSpoiler()) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
return room->lastEventToString();
|
return room->lastEventToString();
|
||||||
}
|
}
|
||||||
if (role == LastActiveTimeRole) {
|
if (role == LastActiveTimeRole) {
|
||||||
@@ -385,7 +373,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
|||||||
if (!room->successorId().isEmpty()) {
|
if (!room->successorId().isEmpty()) {
|
||||||
return QStringLiteral("upgraded");
|
return QStringLiteral("upgraded");
|
||||||
}
|
}
|
||||||
return QVariant::fromValue(room->joinState());
|
return toCString(room->joinState());
|
||||||
}
|
}
|
||||||
if (role == CurrentRoomRole) {
|
if (role == CurrentRoomRole) {
|
||||||
return QVariant::fromValue(room);
|
return QVariant::fromValue(room);
|
||||||
@@ -439,33 +427,11 @@ QString RoomListModel::categoryName(int section)
|
|||||||
return i18n("Normal");
|
return i18n("Normal");
|
||||||
case 5:
|
case 5:
|
||||||
return i18n("Low priority");
|
return i18n("Low priority");
|
||||||
case 6:
|
|
||||||
return i18n("Spaces");
|
|
||||||
default:
|
default:
|
||||||
return "Deadbeef";
|
return "Deadbeef";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString RoomListModel::categoryIconName(int section)
|
|
||||||
{
|
|
||||||
switch (section) {
|
|
||||||
case 1:
|
|
||||||
return QStringLiteral("user-invisible");
|
|
||||||
case 2:
|
|
||||||
return QStringLiteral("favorite");
|
|
||||||
case 3:
|
|
||||||
return QStringLiteral("dialog-messages");
|
|
||||||
case 4:
|
|
||||||
return QStringLiteral("group");
|
|
||||||
case 5:
|
|
||||||
return QStringLiteral("object-order-lower");
|
|
||||||
case 6:
|
|
||||||
return QStringLiteral("group");
|
|
||||||
default:
|
|
||||||
return QStringLiteral("tools-report-bug");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomListModel::setCategoryVisible(int category, bool visible)
|
void RoomListModel::setCategoryVisible(int category, bool visible)
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
@@ -489,15 +455,10 @@ bool RoomListModel::categoryVisible(int category) const
|
|||||||
|
|
||||||
NeoChatRoom *RoomListModel::roomByAliasOrId(const QString &aliasOrId)
|
NeoChatRoom *RoomListModel::roomByAliasOrId(const QString &aliasOrId)
|
||||||
{
|
{
|
||||||
for (const auto &room : qAsConst(m_rooms)) {
|
for (const auto &room : m_rooms) {
|
||||||
if (room->aliases().contains(aliasOrId) || room->id() == aliasOrId) {
|
if (room->aliases().contains(aliasOrId) || room->id() == aliasOrId) {
|
||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RoomListModel::indexForRoom(NeoChatRoom *room) const
|
|
||||||
{
|
|
||||||
return m_rooms.indexOf(room);
|
|
||||||
}
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user