Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b0cefe7ff | ||
|
|
60e9587aa7 | ||
|
|
ae2c517940 | ||
|
|
ac38b42403 | ||
|
|
06803548fa | ||
|
|
70dba75d31 | ||
|
|
aad95d2bc3 | ||
|
|
d57b410c04 | ||
|
|
e24a1b4541 | ||
|
|
008ef38226 | ||
|
|
cae3c4ca60 | ||
|
|
71c4d7d651 | ||
|
|
d8edac696d | ||
|
|
a5570838ca | ||
|
|
2d75c34b17 | ||
|
|
cc372f67eb | ||
|
|
aaa042a2f0 | ||
|
|
6fdc8a9298 | ||
|
|
1526c81a2f | ||
|
|
03d13bb543 | ||
|
|
d8916aa133 | ||
|
|
bb3b3bc088 | ||
|
|
1234132a05 | ||
|
|
52e5ec50c6 | ||
|
|
16f661f6ae | ||
|
|
df1835de2d | ||
|
|
1432ba2368 | ||
|
|
edcbc9ab01 | ||
|
|
1afbd42523 |
@@ -1,42 +0,0 @@
|
||||
<!--
|
||||
Hello, thanks for reporting a bug. To help us with the debugging,
|
||||
please make sure to fill all the recommended information.
|
||||
Thanks!
|
||||
-->
|
||||
|
||||
## Description
|
||||
|
||||
(General description of the bug)
|
||||
|
||||
## Steps to reproduce
|
||||
|
||||
1. Open app
|
||||
2. ...
|
||||
3. ...
|
||||
|
||||
## What is the current bug behavior?
|
||||
|
||||
(What actually happens)
|
||||
|
||||
## What is the expected correct behavior?
|
||||
|
||||
(What you should see instead)
|
||||
|
||||
## Relevant logs and/or screenshots
|
||||
|
||||
(Paste any relevant logs - please use code blocks (```) to format console output, logs, and code, as
|
||||
it's very hard to read otherwise.)
|
||||
|
||||
## Possible fixes
|
||||
|
||||
(If you can, link to the line of code that might be responsible for the problem)
|
||||
|
||||
## System/Matrix Information
|
||||
|
||||
- **Distribution / Platform:** Ubuntu 20.04, openSUSE, Flatpak, Windows, MacOS, Android, ...
|
||||
- **Qt Version:** 5.15.2
|
||||
- **NeoChat version:** 1.2
|
||||
- **Quotient version:** 0.6.6
|
||||
- **Matrix server:** matrix.org, kde.org, ....
|
||||
|
||||
/label ~Bug
|
||||
15
.reuse/dep5
15
.reuse/dep5
@@ -1,15 +0,0 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: NeoChat
|
||||
Upstream-Contact: Carl Schwan <carlschwan@kde.org>
|
||||
|
||||
Files: 128-logo.png icons/* logo.png org.kde.neochat.svg org.kde.neochat-symbolic.svg android/res/drawable/neochat.png
|
||||
Copyright: 2020 Carson Black <uhhadd@gmail.com>
|
||||
License: LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
Files: qtquickcontrols2.conf
|
||||
Copyright: 2020 Tobias Fella <fella@posteo.de>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: android/res/drawable/splash.xml
|
||||
Copyright: 2020 Tobias Fella <fella@posteo.de>
|
||||
License: BSD-2-Clause
|
||||
@@ -1,12 +1,6 @@
|
||||
# SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carl@carlschwan.eu>
|
||||
# SPDX-FileCopyrightText: 2020-2021 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
|
||||
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
|
||||
project(NeoChat)
|
||||
project(Neochat)
|
||||
|
||||
set(KF5_MIN_VERSION "5.77.0")
|
||||
set(QT_MIN_VERSION "5.15.0")
|
||||
@@ -34,12 +28,12 @@ endif()
|
||||
# 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)
|
||||
|
||||
ecm_setup_version(1.2.0
|
||||
ecm_setup_version(1.1.1
|
||||
VARIABLE_PREFIX NEOCHAT
|
||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
||||
)
|
||||
|
||||
find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
|
||||
find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Widgets Core Quick Gui QuickControls2 Multimedia Svg)
|
||||
set_package_properties(Qt5 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
@@ -54,12 +48,6 @@ set_package_properties(KF5Kirigami2 PROPERTIES
|
||||
PURPOSE "Kirigami application UI framework"
|
||||
)
|
||||
|
||||
find_package(Qt5Keychain)
|
||||
set_package_properties(Qt5Keychain PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Secure storage of account secrets"
|
||||
)
|
||||
|
||||
if(ANDROID)
|
||||
find_package(OpenSSL)
|
||||
set_package_properties(OpenSSL PROPERTIES
|
||||
@@ -67,10 +55,10 @@ if(ANDROID)
|
||||
PURPOSE "Encrypted communications"
|
||||
)
|
||||
else()
|
||||
find_package(Qt5 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||
find_package(KF5QQC2DesktopStyle ${KF5_MIN_VERSION} REQUIRED)
|
||||
set_package_properties(KF5QQC2DesktopStyle PROPERTIES
|
||||
TYPE RUNTIME
|
||||
find_package(Qt5Keychain)
|
||||
set_package_properties(Qt5Keychain PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Secure storage of account secrets"
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES
|
||||
NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE
|
||||
AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION
|
||||
ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE
|
||||
OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS
|
||||
LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION
|
||||
OR WORKS PROVIDED HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer exclusive
|
||||
Copyright and Related Rights (defined below) upon the creator and subsequent
|
||||
owner(s) (each and all, an "owner") of an original work of authorship and/or
|
||||
a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||
purpose of contributing to a commons of creative, cultural and scientific
|
||||
works ("Commons") that the public can reliably and without fear of later claims
|
||||
of infringement build upon, modify, incorporate in other works, reuse and
|
||||
redistribute as freely as possible in any form whatsoever and for any purposes,
|
||||
including without limitation commercial purposes. These owners may contribute
|
||||
to the Commons to promote the ideal of a free culture and the further production
|
||||
of creative, cultural and scientific works, or to gain reputation or greater
|
||||
distribution for their Work in part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation
|
||||
of additional consideration or compensation, the person associating CC0 with
|
||||
a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
||||
and publicly distribute the Work under its terms, with knowledge of his or
|
||||
her Copyright and Related Rights in the Work and the meaning and intended
|
||||
legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be protected
|
||||
by copyright and related or neighboring rights ("Copyright and Related Rights").
|
||||
Copyright and Related Rights include, but are not limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
||||
and translate a Work;
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||
depicted in a Work;
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work, subject
|
||||
to the limitations in paragraph 4(a), below;
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal protection
|
||||
of databases, and under any national implementation thereof, including any
|
||||
amended or successor version of such directive); and
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world
|
||||
based on applicable law or treaty, and any national implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||
and Related Rights and associated claims and causes of action, whether now
|
||||
known or unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||
duration provided by applicable law or treaty (including future time extensions),
|
||||
(iii) in any current or future medium and for any number of copies, and (iv)
|
||||
for any purpose whatsoever, including without limitation commercial, advertising
|
||||
or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the
|
||||
benefit of each member of the public at large and to the detriment of Affirmer's
|
||||
heirs and successors, fully intending that such Waiver shall not be subject
|
||||
to revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||
shall be preserved to the maximum extent permitted taking into account Affirmer's
|
||||
express Statement of Purpose. In addition, to the extent the Waiver is so
|
||||
judged Affirmer hereby grants to each affected person a royalty-free, non
|
||||
transferable, non sublicensable, non exclusive, irrevocable and unconditional
|
||||
license to exercise Affirmer's Copyright and Related Rights in the Work (i)
|
||||
in all territories worldwide, (ii) for the maximum duration provided by applicable
|
||||
law or treaty (including future time extensions), (iii) in any current or
|
||||
future medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional purposes
|
||||
(the "License"). The License shall be deemed effective as of the date CC0
|
||||
was applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder of
|
||||
the License, and in such case Affirmer hereby affirms that he or she will
|
||||
not (i) exercise any of his or her remaining Copyright and Related Rights
|
||||
in the Work or (ii) assert any associated claims and causes of action with
|
||||
respect to the Work, in either case contrary to Affirmer's express Statement
|
||||
of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered,
|
||||
licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or warranties
|
||||
of any kind concerning the Work, express, implied, statutory or otherwise,
|
||||
including without limitation warranties of title, merchantability, fitness
|
||||
for a particular purpose, non infringement, or the absence of latent or other
|
||||
defects, accuracy, or the present or absence of errors, whether or not discoverable,
|
||||
all to the greatest extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without limitation
|
||||
any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims
|
||||
responsibility for obtaining any necessary consents, permissions or other
|
||||
rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a party
|
||||
to this document and has no duty or obligation with respect to this CC0 or
|
||||
use of the Work.
|
||||
@@ -1,319 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||
document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your freedom to share
|
||||
and change it. By contrast, the GNU General Public License is intended to
|
||||
guarantee your freedom to share and change free software--to make sure the
|
||||
software is free for all its users. This General Public License applies to
|
||||
most of the Free Software Foundation's software and to any other program whose
|
||||
authors commit to using it. (Some other Free Software Foundation software
|
||||
is covered by the GNU Lesser General Public License instead.) You can apply
|
||||
it to your programs, 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 software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether gratis or
|
||||
for a fee, you must give the recipients all the rights that you have. You
|
||||
must make sure that they, too, receive or can get the source code. And you
|
||||
must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and (2)
|
||||
offer you this license which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain that
|
||||
everyone understands that there is no warranty for this free software. If
|
||||
the software is modified by someone else and passed on, we want its recipients
|
||||
to know that what they have is not the original, 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 redistributors of a free program will individually
|
||||
obtain patent licenses, in effect making the program proprietary. To prevent
|
||||
this, we have made it clear that any patent must be licensed for everyone's
|
||||
free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification
|
||||
follow.
|
||||
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains a notice
|
||||
placed by the copyright holder saying it may be distributed under the terms
|
||||
of this General Public License. The "Program", below, refers to any such program
|
||||
or work, and a "work based on the Program" means either the Program or any
|
||||
derivative work under copyright law: that is to say, a work containing the
|
||||
Program or a portion of it, either verbatim or with modifications and/or translated
|
||||
into another language. (Hereinafter, translation is included without limitation
|
||||
in the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not covered
|
||||
by this License; they are outside its scope. The act of running the Program
|
||||
is not restricted, and the output from the Program is covered only if its
|
||||
contents constitute a work based on the Program (independent of having been
|
||||
made by running the Program). Whether that is true depends on what the Program
|
||||
does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's 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 give any other recipients of the Program a copy of this
|
||||
License along with the Program.
|
||||
|
||||
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 Program or any portion of it,
|
||||
thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices stating that
|
||||
you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in whole or
|
||||
in part contains or is derived from the Program or any part thereof, to be
|
||||
licensed as a whole at no charge to all third parties under the terms of this
|
||||
License.
|
||||
|
||||
c) If the modified program normally reads commands interactively when run,
|
||||
you must cause it, when started running for such interactive use in the most
|
||||
ordinary way, to print or display an announcement including an appropriate
|
||||
copyright notice and a notice that there is no warranty (or else, saying that
|
||||
you provide a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this License.
|
||||
(Exception: if the Program itself is interactive but does not normally print
|
||||
such an announcement, your work based on the Program is not required to print
|
||||
an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If identifiable
|
||||
sections of that work are not derived from the Program, 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 Program, 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 Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program with
|
||||
the Program (or with a work based on the Program) on a volume of a storage
|
||||
or distribution medium does not bring the other work under the scope of this
|
||||
License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it, under Section
|
||||
2) in object code or executable form under the terms of Sections 1 and 2 above
|
||||
provided that you also do one of the following:
|
||||
|
||||
a) 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; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three years, to give
|
||||
any third party, for a charge no more than your cost of physically performing
|
||||
source distribution, a complete machine-readable copy of the corresponding
|
||||
source code, to be distributed under the terms of Sections 1 and 2 above on
|
||||
a medium customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer to distribute
|
||||
corresponding source code. (This alternative is allowed only for noncommercial
|
||||
distribution and only if you received the program in object code or executable
|
||||
form with such an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for making
|
||||
modifications to it. For an executable work, 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 executable. 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.
|
||||
|
||||
If distribution of executable or 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 counts as distribution of the source code,
|
||||
even though third parties are not compelled to copy the source along with
|
||||
the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program except
|
||||
as expressly provided under this License. Any attempt otherwise to copy, modify,
|
||||
sublicense or distribute the Program 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.
|
||||
|
||||
5. 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
|
||||
Program or its derivative works. These actions are prohibited by law if you
|
||||
do not accept this License. Therefore, by modifying or distributing the Program
|
||||
(or any work based on the Program), you indicate your acceptance of this License
|
||||
to do so, and all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the Program),
|
||||
the recipient automatically receives a license from the original licensor
|
||||
to copy, distribute or modify the Program 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.
|
||||
|
||||
7. 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 Program at all. For example, if a
|
||||
patent license would not permit royalty-free redistribution of the Program
|
||||
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 Program.
|
||||
|
||||
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.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in certain
|
||||
countries either by patents or by copyrighted interfaces, the original copyright
|
||||
holder who places the Program 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.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions of
|
||||
the 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 Program 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
|
||||
Program does not specify a version number of this License, you may choose
|
||||
any version ever published by the Free Software Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free programs
|
||||
whose distribution conditions are different, 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
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
|
||||
THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
|
||||
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
|
||||
"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 PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 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 Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest possible
|
||||
use to the public, the best way to achieve this is to make it free software
|
||||
which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. 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 program's name and an idea of what it does.>
|
||||
|
||||
Copyright (C)< yyyy> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under
|
||||
the terms of the GNU General Public License as published by the Free Software
|
||||
Foundation; either version 2 of the License, or (at your option) any later
|
||||
version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
|
||||
Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this when
|
||||
it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
|
||||
with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software,
|
||||
and you are welcome to redistribute it under certain conditions; type `show
|
||||
c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may be
|
||||
called something other than `show w' and `show c'; they could even be mouse-clicks
|
||||
or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary. Here
|
||||
is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision'
|
||||
(which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon >, 1 April 1989 Ty Coon, President of Vice This General
|
||||
Public License does not permit incorporating your program into proprietary
|
||||
programs. If your program is a subroutine library, you may consider it more
|
||||
useful to permit linking proprietary applications with the library. If this
|
||||
is what you want to do, use the GNU Lesser General Public License instead
|
||||
of this License.
|
||||
@@ -1,468 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||
document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts as the
|
||||
successor of the GNU Library Public License, version 2, hence the version
|
||||
number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your freedom to share
|
||||
and change it. By contrast, the GNU General Public Licenses are intended to
|
||||
guarantee your freedom to share and change free software--to make sure the
|
||||
software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some specially
|
||||
designated software packages--typically libraries--of the Free Software Foundation
|
||||
and other authors who decide to use it. You can use it too, but we suggest
|
||||
you first think carefully about whether this license or the ordinary General
|
||||
Public License is the better strategy to use in any particular case, based
|
||||
on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use, not price.
|
||||
Our General Public Licenses are designed to make sure that you have the freedom
|
||||
to distribute copies of free software (and charge for this service if you
|
||||
wish); that you receive source code or can get it if you want it; that you
|
||||
can change the software and use pieces of it in new free programs; and that
|
||||
you are informed that you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid distributors
|
||||
to deny you these rights or to ask you to surrender these rights. These restrictions
|
||||
translate to certain responsibilities for you if you distribute copies of
|
||||
the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis or for
|
||||
a fee, you must give the recipients all the rights that we gave you. You must
|
||||
make sure that they, too, receive or can get the source code. If you link
|
||||
other code with the library, you must provide complete object files to the
|
||||
recipients, so that they can relink them with the library after making changes
|
||||
to the library and recompiling it. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the library,
|
||||
and (2) we offer you this license, which gives you legal permission to copy,
|
||||
distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that there is no
|
||||
warranty for the free library. Also, if the library is modified by someone
|
||||
else and passed on, the recipients should know that what they have is not
|
||||
the original version, so that the original author's reputation will not be
|
||||
affected by problems that might be introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of any free
|
||||
program. We wish to make sure that a company cannot effectively restrict the
|
||||
users of a free program by obtaining a restrictive license from a patent holder.
|
||||
Therefore, we insist that any patent license obtained for a version of the
|
||||
library must be consistent with the full freedom of use specified in this
|
||||
license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the ordinary GNU
|
||||
General Public License. This license, the GNU Lesser General Public License,
|
||||
applies to certain designated libraries, and is quite different from the ordinary
|
||||
General Public License. We use this license for certain libraries in order
|
||||
to permit linking those libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using a shared
|
||||
library, the combination of the two is legally speaking a combined work, a
|
||||
derivative of the original library. The ordinary General Public License therefore
|
||||
permits such linking only if the entire combination fits its criteria of freedom.
|
||||
The Lesser General Public License permits more lax criteria for linking other
|
||||
code with the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it does Less
|
||||
to protect the user's freedom than the ordinary General Public License. It
|
||||
also provides other free software developers Less of an advantage over competing
|
||||
non-free programs. These disadvantages are the reason we use the ordinary
|
||||
General Public License for many libraries. However, the Lesser license provides
|
||||
advantages in certain special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to encourage the
|
||||
widest possible use of a certain library, so that it becomes a de-facto standard.
|
||||
To achieve this, non-free programs must be allowed to use the library. A more
|
||||
frequent case is that a free library does the same job as widely used non-free
|
||||
libraries. In this case, there is little to gain by limiting the free library
|
||||
to free software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free programs
|
||||
enables a greater number of people to use a large body of free software. For
|
||||
example, permission to use the GNU C Library in non-free programs enables
|
||||
many more people to use the whole GNU operating system, as well as its variant,
|
||||
the GNU/Linux operating system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the users'
|
||||
freedom, it does ensure that the user of a program that is linked with the
|
||||
Library has the freedom and the wherewithal to run that program using a modified
|
||||
version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification
|
||||
follow. Pay close attention to the difference between a "work based on the
|
||||
library" and a "work that uses the library". The former contains code derived
|
||||
from the library, whereas the latter must be combined with the library in
|
||||
order to run.
|
||||
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other program
|
||||
which contains a notice placed by the copyright holder or other authorized
|
||||
party saying it may be distributed under the terms of this Lesser General
|
||||
Public License (also called "this License"). Each licensee is addressed as
|
||||
"you".
|
||||
|
||||
A "library" means a collection of software functions and/or data prepared
|
||||
so as to be conveniently linked with application programs (which use some
|
||||
of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work which has
|
||||
been distributed under these terms. A "work based on the Library" means either
|
||||
the Library or any derivative work under copyright law: that is to say, a
|
||||
work containing the Library or a portion of it, either verbatim or with modifications
|
||||
and/or translated straightforwardly into another language. (Hereinafter, translation
|
||||
is included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for making modifications
|
||||
to it. For a library, complete source code means all the source code for all
|
||||
modules it contains, plus any associated interface definition files, plus
|
||||
the scripts used to control compilation and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not covered
|
||||
by this License; they are outside its scope. The act of running a program
|
||||
using the Library is not restricted, and output from such a program is covered
|
||||
only if its contents constitute a work based on the Library (independent of
|
||||
the use of the Library in a tool for writing it). Whether that is true depends
|
||||
on what the Library does and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's complete source
|
||||
code as you receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice and disclaimer
|
||||
of warranty; keep intact all the notices that refer to this License and to
|
||||
the absence of any warranty; and distribute a copy of this License along with
|
||||
the Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and you
|
||||
may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion of it,
|
||||
thus forming a work based on the Library, and copy and distribute such modifications
|
||||
or work under the terms of Section 1 above, provided that you also meet all
|
||||
of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices stating that
|
||||
you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no charge to all
|
||||
third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a table of
|
||||
data to be supplied by an application program that uses the facility, other
|
||||
than as an argument passed when the facility is invoked, then you must make
|
||||
a good faith effort to ensure that, in the event an application does not supply
|
||||
such function or table, the facility still operates, and performs whatever
|
||||
part of its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has a purpose
|
||||
that is entirely well-defined independent of the application. Therefore, Subsection
|
||||
2d requires that any application-supplied function or table used by this function
|
||||
must be optional: if the application does not supply it, the square root function
|
||||
must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If identifiable
|
||||
sections of that work are not derived from the Library, and can be reasonably
|
||||
considered independent and separate works in themselves, then this License,
|
||||
and its terms, do not apply to those sections when you distribute them as
|
||||
separate works. But when you distribute the same sections as part of a whole
|
||||
which is a work based on the Library, the distribution of the whole must be
|
||||
on the terms of this License, whose permissions for other licensees extend
|
||||
to the entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest your
|
||||
rights to work written entirely by you; rather, the intent is to exercise
|
||||
the right to control the distribution of derivative or collective works based
|
||||
on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library with
|
||||
the Library (or with a work based on the Library) on a volume of a storage
|
||||
or distribution medium does not bring the other work under the scope of this
|
||||
License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public License
|
||||
instead of this License to a given copy of the Library. To do this, you must
|
||||
alter all the notices that refer to this License, so that they refer to the
|
||||
ordinary GNU General Public License, version 2, instead of to this License.
|
||||
(If a newer version than version 2 of the ordinary GNU General Public License
|
||||
has appeared, then you can specify that version instead if you wish.) Do not
|
||||
make any other change in these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for that copy,
|
||||
so the ordinary GNU General Public License applies to all subsequent copies
|
||||
and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of the Library
|
||||
into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or derivative of
|
||||
it, under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you accompany it with the complete corresponding
|
||||
machine-readable source code, which must be distributed under the terms of
|
||||
Sections 1 and 2 above on a medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy from a designated
|
||||
place, then offering equivalent access to copy the source code from the same
|
||||
place satisfies the requirement to distribute the source code, even though
|
||||
third parties are not compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the Library, but
|
||||
is designed to work with the Library by being compiled or linked with it,
|
||||
is called a "work that uses the Library". Such a work, in isolation, is not
|
||||
a derivative work of the Library, and therefore falls outside the scope of
|
||||
this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library creates an
|
||||
executable that is a derivative of the Library (because it contains portions
|
||||
of the Library), rather than a "work that uses the library". The executable
|
||||
is therefore covered by this License. Section 6 states terms for distribution
|
||||
of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file that
|
||||
is part of the Library, the object code for the work may be a derivative work
|
||||
of the Library even though the source code is not. Whether this is true is
|
||||
especially significant if the work can be linked without the Library, or if
|
||||
the work is itself a library. The threshold for this to be true is not precisely
|
||||
defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data structure layouts
|
||||
and accessors, and small macros and small inline functions (ten lines or less
|
||||
in length), then the use of the object file is unrestricted, regardless of
|
||||
whether it is legally a derivative work. (Executables containing this object
|
||||
code plus portions of the Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may distribute
|
||||
the object code for the work under the terms of Section 6. Any executables
|
||||
containing that work also fall under Section 6, whether or not they are linked
|
||||
directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or link a "work
|
||||
that uses the Library" with the Library to produce a work containing portions
|
||||
of the Library, and distribute that work under terms of your choice, provided
|
||||
that the terms permit modification of the work for the customer's own use
|
||||
and reverse engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the Library
|
||||
is used in it and that the Library and its use are covered by this License.
|
||||
You must supply a copy of this License. If the work during execution displays
|
||||
copyright notices, you must include the copyright notice for the Library among
|
||||
them, as well as a reference directing the user to the copy of this License.
|
||||
Also, you must do one of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding machine-readable source
|
||||
code for the Library including whatever changes were used in the work (which
|
||||
must be distributed under Sections 1 and 2 above); and, if the work is an
|
||||
executable linked with the Library, with the complete machine-readable "work
|
||||
that uses the Library", as object code and/or source code, so that the user
|
||||
can modify the Library and then relink to produce a modified executable containing
|
||||
the modified Library. (It is understood that the user who changes the contents
|
||||
of definitions files in the Library will not necessarily be able to recompile
|
||||
the application to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the Library. A
|
||||
suitable mechanism is one that (1) uses at run time a copy of the library
|
||||
already present on the user's computer system, rather than copying library
|
||||
functions into the executable, and (2) will operate properly with a modified
|
||||
version of the library, if the user installs one, as long as the modified
|
||||
version is interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at least three years,
|
||||
to give the same user the materials specified in Subsection 6a, above, for
|
||||
a charge no more than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy from a designated
|
||||
place, offer equivalent access to copy the above specified materials from
|
||||
the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these materials or
|
||||
that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the Library" must
|
||||
include any data and utility programs needed for reproducing the executable
|
||||
from it. However, as a special exception, the materials to be distributed
|
||||
need not include anything that is normally distributed (in either source or
|
||||
binary form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component itself
|
||||
accompanies the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license restrictions of
|
||||
other proprietary libraries that do not normally accompany the operating system.
|
||||
Such a contradiction means you cannot use both them and the Library together
|
||||
in an executable that you distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the Library side-by-side
|
||||
in a single library together with other library facilities not covered by
|
||||
this License, and distribute such a combined library, provided that the separate
|
||||
distribution of the work based on the Library and of the other library facilities
|
||||
is otherwise permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based on the
|
||||
Library, uncombined with any other library facilities. This must be distributed
|
||||
under the terms of the Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact that part of
|
||||
it is a work based on the Library, and explaining where to find the accompanying
|
||||
uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute 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 with this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent infringement
|
||||
or for any other reason (not limited to patent issues), conditions are imposed
|
||||
on you (whether by court order, agreement or otherwise) that contradict the
|
||||
conditions of this License, they do not excuse you from the conditions of
|
||||
this License. If you cannot distribute so as to satisfy simultaneously your
|
||||
obligations under this License and any other pertinent obligations, then as
|
||||
a consequence you may not distribute the Library at all. For example, if a
|
||||
patent license would not permit royalty-free redistribution of the Library
|
||||
by all those who receive copies directly or indirectly through you, then the
|
||||
only way you could satisfy both it and this License would be to refrain entirely
|
||||
from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any patents
|
||||
or other property right claims or to contest validity of any such claims;
|
||||
this section has the sole purpose of protecting the integrity of the free
|
||||
software distribution system which is implemented by public license practices.
|
||||
Many people have made generous contributions to the wide range of software
|
||||
distributed through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing to
|
||||
distribute software through any other system and a licensee cannot impose
|
||||
that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to be a
|
||||
consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in certain
|
||||
countries either by patents or by copyrighted interfaces, the original copyright
|
||||
holder who places the Library under this License may add an explicit geographical
|
||||
distribution limitation excluding those countries, so that distribution is
|
||||
permitted only in or among countries not thus excluded. In such case, this
|
||||
License incorporates the limitation as if written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new versions of
|
||||
the Lesser General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to address
|
||||
new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library specifies
|
||||
a version number of this License which applies to it and "any later version",
|
||||
you have the option of following the terms and conditions either of that version
|
||||
or of any later version published by the Free Software Foundation. If the
|
||||
Library does not specify a license version number, you may choose any version
|
||||
ever published by the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free programs
|
||||
whose distribution conditions are incompatible with these, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free Software
|
||||
Foundation, write to the Free Software Foundation; we sometimes make exceptions
|
||||
for this. Our decision will be guided by the two goals of preserving the free
|
||||
status of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
|
||||
THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
|
||||
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY
|
||||
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
|
||||
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
|
||||
OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
|
||||
THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
|
||||
OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
|
||||
OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
|
||||
OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
|
||||
HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
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 Lesser General Public License as published by the Free
|
||||
Software Foundation; either version 2.1 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 Lesser General Public License for more
|
||||
details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this library; if not, write to the Free Software Foundation, Inc., 51
|
||||
Franklin Street, 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,163 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||
document, but changing it is not allowed.
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates the terms
|
||||
and conditions of version 3 of the GNU General Public License, supplemented
|
||||
by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser General
|
||||
Public License, and the "GNU GPL" refers to version 3 of the GNU General Public
|
||||
License.
|
||||
|
||||
|
||||
|
||||
"The Library" refers to a covered work governed by this License, other than
|
||||
an Application or a Combined Work as defined below.
|
||||
|
||||
|
||||
|
||||
An "Application" is any work that makes use of an interface provided by the
|
||||
Library, but which is not otherwise based on the Library. Defining a subclass
|
||||
of a class defined by the Library is deemed a mode of using an interface provided
|
||||
by the Library.
|
||||
|
||||
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an Application
|
||||
with the Library. The particular version of the Library with which the Combined
|
||||
Work was made is also called the "Linked Version".
|
||||
|
||||
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the Corresponding
|
||||
Source for the Combined Work, excluding any source code for portions of the
|
||||
Combined Work that, considered in isolation, are based on the Application,
|
||||
and not on the Linked Version.
|
||||
|
||||
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the object
|
||||
code and/or source code for the Application, including any data and utility
|
||||
programs needed for reproducing the Combined Work from the Application, but
|
||||
excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License without
|
||||
being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a facility
|
||||
refers to a function or data to be supplied by an Application that uses the
|
||||
facility (other than as an argument passed when the facility is invoked),
|
||||
then you may convey a copy of the modified version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to ensure
|
||||
that, in the event an Application does not supply the function or data, the
|
||||
facility still operates, and performs whatever part of its purpose remains
|
||||
meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of this License
|
||||
applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from a header
|
||||
file that is part of the Library. You may convey such object code under terms
|
||||
of your choice, provided that, if the incorporated material is not limited
|
||||
to numerical parameters, data structure layouts and accessors, or small macros,
|
||||
inline functions and templates (ten or fewer lines in length), you do both
|
||||
of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the Library
|
||||
is used in it and that the Library and its use are covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that, taken together,
|
||||
effectively do not restrict modification of the portions of the Library contained
|
||||
in the Combined Work and reverse engineering for debugging such modifications,
|
||||
if you also do each of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that the Library
|
||||
is used in it and that the Library and its use are covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during execution, include
|
||||
the copyright notice for the Library among these notices, as well as a reference
|
||||
directing the user to the copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this License,
|
||||
and the Corresponding Application Code in a form suitable for, and under terms
|
||||
that permit, the user to recombine or relink the Application with a modified
|
||||
version of the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the Library. A
|
||||
suitable mechanism is one that (a) uses at run time a copy of the Library
|
||||
already present on the user's computer system, and (b) will operate properly
|
||||
with a modified version of the Library that is interface-compatible with the
|
||||
Linked Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise be required
|
||||
to provide such information under section 6 of the GNU GPL, and only to the
|
||||
extent that such information is necessary to install and execute a modified
|
||||
version of the Combined Work produced by recombining or relinking the Application
|
||||
with a modified version of the Linked Version. (If you use option 4d0, the
|
||||
Installation Information must accompany the Minimal Corresponding Source and
|
||||
Corresponding Application Code. If you use option 4d1, you must provide the
|
||||
Installation Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the Library side
|
||||
by side in a single library together with other library facilities that are
|
||||
not Applications and are not covered by this License, and convey such a combined
|
||||
library under terms of your choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based on the
|
||||
Library, uncombined with any other library facilities, conveyed under the
|
||||
terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it is a work
|
||||
based on the Library, and explaining where to find the accompanying uncombined
|
||||
form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of the
|
||||
GNU Lesser General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to address
|
||||
new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library as you
|
||||
received it specifies that a certain numbered version of the GNU Lesser General
|
||||
Public License "or any later version" applies to it, you have the option of
|
||||
following the terms and conditions either of that published version or of
|
||||
any later version published by the Free Software Foundation. If the Library
|
||||
as you received it does not specify a version number of the GNU Lesser General
|
||||
Public License, you may choose any version of the GNU Lesser General Public
|
||||
License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide whether
|
||||
future versions of the GNU Lesser General Public License shall apply, that
|
||||
proxy's public statement of acceptance of any version is permanent authorization
|
||||
for you to choose that version for the Library.
|
||||
@@ -1,12 +1,9 @@
|
||||
# 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
|
||||
messaging. It is a fork of Spectral, using KDE frameworks, most notably Kirigami,
|
||||
KConfig and KI18n.
|
||||
|
||||
<a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a>
|
||||
|
||||
|
||||
## Get it
|
||||
|
||||
A stable release [is available](https://apps.kde.org/en/neochat) for download for Linux distributions.
|
||||
@@ -26,7 +23,7 @@ and can also directly be downloaded from the [binary factory](https://binary-fac
|
||||
|
||||
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).
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
android:versionName="0.0.1"
|
||||
android:versionCode="1604412458"
|
||||
android:installLocation="auto">
|
||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat">
|
||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="Neochat" android:icon="@drawable/neochat">
|
||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
||||
android:label="NeoChat"
|
||||
android:label="Neochat"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:launchMode="singleTop">
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
||||
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
# SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#
|
||||
# CMake module to search for the cmark library
|
||||
#
|
||||
|
||||
# first try to find cmark-config.cmake
|
||||
# path to a file not in the search path can be set with 'cmake -Dcmark_DIR=some/path/'
|
||||
find_package(cmark CONFIG QUIET)
|
||||
find_package(cmark CONFIG)
|
||||
if(cmark_FOUND AND TARGET cmark::cmark)
|
||||
# found it!
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_CMARK QUIET cmark)
|
||||
endif()
|
||||
include(FindPkgConfig)
|
||||
pkg_check_modules(PC_CMARK QUIET cmark)
|
||||
|
||||
if(NOT CMARK_INCLUDE_DIR)
|
||||
find_path(CMARK_INCLUDE_DIR
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Include FontConfig config which uses the Emoji One font from the
|
||||
|
||||
18
imports/NeoChat/Component/AutoMouseArea.qml
Normal file
18
imports/NeoChat/Component/AutoMouseArea.qml
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
MouseArea {
|
||||
signal primaryClicked()
|
||||
signal secondaryClicked()
|
||||
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: mouse.button == Qt.RightButton ? secondaryClicked() : primaryClicked()
|
||||
onPressAndHold: secondaryClicked()
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Page 1.0
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
|
||||
property var attachmentMimetype: FileType.mimeTypeForUrl(ChatBoxHelper.attachmentPath)
|
||||
readonly property bool hasImage: attachmentMimetype.valid && FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix)
|
||||
|
||||
active: visible
|
||||
sourceComponent: Component {
|
||||
Pane {
|
||||
id: attachmentPane
|
||||
property string baseFileName: ChatBoxHelper.attachmentPath.toString().substring(ChatBoxHelper.attachmentPath.toString().lastIndexOf('/') + 1, ChatBoxHelper.attachmentPath.length)
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
contentItem: Item {
|
||||
property real spacing: attachmentPane.spacing > 0 ? attachmentPane.spacing : toolBar.spacing
|
||||
implicitWidth: Math.max(image.implicitWidth, imageBusyIndicator.implicitWidth, fileInfoLayout.implicitWidth, toolBar.implicitWidth)
|
||||
implicitHeight: Math.max(
|
||||
(hasImage ? Math.max(image.preferredHeight, imageBusyIndicator.implicitHeight) + spacing : 0)
|
||||
+ fileInfoLayout.implicitHeight,
|
||||
toolBar.implicitHeight
|
||||
)
|
||||
|
||||
Image {
|
||||
id: image
|
||||
property real preferredHeight: Math.min(implicitHeight, Kirigami.Units.gridUnit * 8)
|
||||
height: preferredHeight
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: fileInfoLayout.top
|
||||
bottomMargin: parent.spacing
|
||||
}
|
||||
width: Math.min(implicitWidth, attachmentPane.availableWidth)
|
||||
asynchronous: true
|
||||
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
||||
smooth: height == preferredHeight && parent.height == parent.implicitHeight // Don't smooth until height animation stops
|
||||
source: hasImage ? ChatBoxHelper.attachmentPath : ""
|
||||
visible: hasImage
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
onSourceChanged: {
|
||||
// Reset source size height, which affect implicitHeight
|
||||
sourceSize.height = -1
|
||||
}
|
||||
|
||||
onSourceSizeChanged: {
|
||||
if (implicitHeight > Kirigami.Units.gridUnit * 8) {
|
||||
// This can save a lot of RAM when loading large images.
|
||||
// It also improves visual quality for large images.
|
||||
sourceSize.height = Kirigami.Units.gridUnit * 8
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
id: imageBusyIndicator
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.top
|
||||
bottom: fileInfoLayout.top
|
||||
bottomMargin: parent.spacing
|
||||
}
|
||||
visible: running
|
||||
running: image.visible && image.progress < 1
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: fileInfoLayout
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: undefined
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: parent.spacing
|
||||
|
||||
Kirigami.Icon {
|
||||
id: mimetypeIcon
|
||||
implicitHeight: Kirigami.Units.fontMetrics.roundedIconSize(fileLabel.implicitHeight)
|
||||
implicitWidth: implicitHeight
|
||||
source: attachmentMimetype.iconName
|
||||
}
|
||||
|
||||
Label {
|
||||
id: fileLabel
|
||||
text: baseFileName
|
||||
}
|
||||
|
||||
states: State {
|
||||
when: !hasImage
|
||||
AnchorChanges {
|
||||
target: fileInfoLayout
|
||||
anchors.bottom: undefined
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Using a toolbar to get a button spacing consistent with what the QQC2 style normally has
|
||||
// Also has some accessibility info
|
||||
ToolBar {
|
||||
id: toolBar
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
Kirigami.Theme.inherit: true
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: parent.spacing
|
||||
Label {
|
||||
Layout.leftMargin: -attachmentPane.leftPadding
|
||||
Layout.topMargin: -attachmentPane.topPadding
|
||||
leftPadding: cancelAttachmentButton.leftPadding + 1 + attachmentPane.leftPadding
|
||||
rightPadding: cancelAttachmentButton.rightPadding + 1
|
||||
topPadding: cancelAttachmentButton.topPadding + attachmentPane.topPadding
|
||||
bottomPadding: cancelAttachmentButton.bottomPadding
|
||||
text: i18n("Attachment:")
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
property real cornerRadius: cancelAttachmentButton.background.hasOwnProperty("radius") ?
|
||||
Math.min(cancelAttachmentButton.background.radius, height/2) : 0
|
||||
corners.bottomLeftRadius: toolBar.mirrored ? cornerRadius : 0
|
||||
corners.bottomRightRadius: toolBar.mirrored ? 0 : cornerRadius
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
opacity: 0.75
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Button {
|
||||
id: editImageButton
|
||||
visible: hasImage
|
||||
icon.name: "document-edit"
|
||||
text: i18n("Edit")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
Component {
|
||||
id: imageEditorPage
|
||||
ImageEditorPage {
|
||||
imagePath: ChatBoxHelper.attachmentPath
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
|
||||
imageEditor.newPathChanged.connect(function(newPath) {
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
ChatBoxHelper.attachmentPath = newPath;
|
||||
});
|
||||
}
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
Button {
|
||||
id: cancelAttachmentButton
|
||||
icon.name: "dialog-cancel"
|
||||
text: i18n("Cancel")
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: ChatBoxHelper.clearAttachment();
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
background: null
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
ToolBar {
|
||||
id: chatBar
|
||||
property string replyEventId: ""
|
||||
property string editEventId: ""
|
||||
property alias inputFieldText: inputField.text
|
||||
property alias textField: inputField
|
||||
property alias emojiPaneOpened: emojiButton.checked
|
||||
|
||||
// store each user we autoComplete here, this will be helpful later to generate
|
||||
// the matrix.to links.
|
||||
// This use an hack to define: https://doc.qt.io/qt-5/qml-var.html#property-value-initialization-semantics
|
||||
property var userAutocompleted: ({})
|
||||
|
||||
signal closeAllTriggered()
|
||||
signal inputFieldForceActiveFocusTriggered()
|
||||
signal messageSent()
|
||||
signal pasteImageTriggered()
|
||||
signal editLastUserMessage()
|
||||
signal replyPreviousUserMessage()
|
||||
|
||||
property alias isCompleting: completionMenu.visible
|
||||
|
||||
onInputFieldForceActiveFocusTriggered: {
|
||||
inputField.forceActiveFocus();
|
||||
// set the cursor to the end of the text
|
||||
inputField.cursorPosition = inputField.length;
|
||||
}
|
||||
|
||||
position: ToolBar.Footer
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
// Using a custom background because some styles like Material
|
||||
// or Fusion might have ugly colors for a TextArea placed inside
|
||||
// of a toolbar. ToolBar is otherwise the closest QQC2 component
|
||||
// to what we want because of the padding and spacing values.
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: chatBar.spacing
|
||||
|
||||
ScrollView {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: inputField.implicitHeight
|
||||
// lineSpacing is height+leading, so subtract leading once since leading only exists between lines.
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||
+ inputField.topPadding + inputField.bottomPadding
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: inputField.font
|
||||
}
|
||||
TextArea {
|
||||
id: inputField
|
||||
focus: true
|
||||
/* Some QQC2 styles will have their own predefined backgrounds for TextAreas.
|
||||
* Make sure there is no background since we are using the ToolBar background.
|
||||
*
|
||||
* This could cause a problem if the QQC2 style was designed around TextArea
|
||||
* background colors being very different from the QPalette::Base color.
|
||||
* Luckily, none of the Qt QQC2 styles do that and neither do KDE's QQC2 styles.
|
||||
*/
|
||||
background: MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: Qt.IBeamCursor
|
||||
z: 1
|
||||
}
|
||||
leftPadding: mirrored ? 0 : Kirigami.Units.largeSpacing
|
||||
rightPadding: !mirrored ? 0 : Kirigami.Units.largeSpacing
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
property real progress: 0
|
||||
property bool autoAppeared: false
|
||||
//property int lineHeight: contentHeight / lineCount
|
||||
|
||||
text: inputFieldText
|
||||
placeholderText: currentRoom.usesEncryption ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : editEventId.length > 0 ? i18n("Edit Message") : i18n("Write your message...")
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
horizontalAlignment: TextEdit.AlignLeft
|
||||
wrapMode: Text.Wrap
|
||||
readOnly: currentRoom.usesEncryption
|
||||
|
||||
|
||||
ChatDocumentHandler {
|
||||
id: documentHandler
|
||||
document: inputField.textDocument
|
||||
cursorPosition: inputField.cursorPosition
|
||||
selectionStart: inputField.selectionStart
|
||||
selectionEnd: inputField.selectionEnd
|
||||
room: currentRoom ?? null
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timeoutTimer
|
||||
repeat: false
|
||||
interval: 2000
|
||||
onTriggered: {
|
||||
repeatTimer.stop()
|
||||
currentRoom.sendTypingNotification(false)
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: repeatTimer
|
||||
repeat: true
|
||||
interval: 5000
|
||||
triggeredOnStart: true
|
||||
onTriggered: currentRoom.sendTypingNotification(true)
|
||||
}
|
||||
|
||||
function sendMessage(event) {
|
||||
if (isCompleting) {
|
||||
chatBar.complete();
|
||||
|
||||
isCompleting = false;
|
||||
return;
|
||||
}
|
||||
if (event.modifiers & Qt.ShiftModifier) {
|
||||
inputField.insert(cursorPosition, "\n")
|
||||
} else {
|
||||
chatBar.postMessage()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: { sendMessage(event) }
|
||||
Keys.onEnterPressed: { sendMessage(event) }
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
closeAllTriggered()
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
switchRoomDown();
|
||||
} else if (event.key === Qt.Key_PageUp) {
|
||||
switchRoomUp();
|
||||
} else if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
|
||||
chatBar.pasteImage();
|
||||
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
|
||||
replyPreviousUserMessage();
|
||||
} else if (event.key === Qt.Key_Up && inputField.text.length === 0) {
|
||||
editLastUserMessage();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onBacktabPressed: {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switchRoomUp();
|
||||
return;
|
||||
}
|
||||
if (!isCompleting) {
|
||||
nextItemInFocusChain(false).forceActiveFocus(Qt.TabFocusReason)
|
||||
return
|
||||
}
|
||||
if (!autoAppeared) {
|
||||
let decrementedIndex = completionMenu.currentIndex - 1
|
||||
// Wrap around to the last item
|
||||
if (decrementedIndex < 0) {
|
||||
decrementedIndex = Math.max(completionMenu.count - 1, 0) // 0 if count == 0
|
||||
}
|
||||
completionMenu.currentIndex = decrementedIndex
|
||||
} else {
|
||||
autoAppeared = false;
|
||||
}
|
||||
|
||||
chatBar.complete();
|
||||
}
|
||||
|
||||
Keys.onTabPressed: {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switchRoomDown();
|
||||
return;
|
||||
}
|
||||
if (!isCompleting) {
|
||||
nextItemInFocusChain().forceActiveFocus(Qt.TabFocusReason);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO detect moved cursor
|
||||
|
||||
// ignore first time tab was clicked so that user can select
|
||||
// first emoji/user
|
||||
if (!autoAppeared) {
|
||||
let incrementedIndex = completionMenu.currentIndex + 1;
|
||||
// Wrap around to the first item
|
||||
if (incrementedIndex > completionMenu.count - 1) {
|
||||
incrementedIndex = 0
|
||||
}
|
||||
completionMenu.currentIndex = incrementedIndex;
|
||||
} else {
|
||||
autoAppeared = false;
|
||||
}
|
||||
|
||||
chatBar.complete();
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
timeoutTimer.restart()
|
||||
repeatTimer.start()
|
||||
currentRoom.cachedInput = text
|
||||
autoAppeared = false;
|
||||
|
||||
const completionInfo = documentHandler.getAutocompletionInfo();
|
||||
|
||||
if (completionInfo.type === ChatDocumentHandler.Ignore) {
|
||||
return;
|
||||
}
|
||||
if (completionInfo.type === ChatDocumentHandler.None) {
|
||||
isCompleting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
completionMenu.completionType = completionInfo.type
|
||||
if (completionInfo.type === ChatDocumentHandler.User) {
|
||||
completionMenu.model = currentRoom.getUsers(completionInfo.keyword);
|
||||
} else if (completionInfo.type === ChatDocumentHandler.Command) {
|
||||
completionMenu.model = CommandModel.filterModel(completionInfo.keyword);
|
||||
} else {
|
||||
completionMenu.model = EmojiModel.filterModel(completionInfo.keyword);
|
||||
}
|
||||
|
||||
if (completionMenu.model.length === 0) {
|
||||
isCompleting = false;
|
||||
return;
|
||||
}
|
||||
isCompleting = true
|
||||
autoAppeared = true;
|
||||
completionMenu.endPosition = cursorPosition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: !ChatBoxHelper.isReplying && (!ChatBoxHelper.hasAttachment || uploadingBusySpinner.running)
|
||||
implicitWidth: uploadButton.implicitWidth
|
||||
implicitHeight: uploadButton.implicitHeight
|
||||
ToolButton {
|
||||
id: uploadButton
|
||||
anchors.fill: parent
|
||||
// Matrix does not allow sending attachments in replies
|
||||
visible: !ChatBoxHelper.isReplying && !ChatBoxHelper.hasAttachment && !uploadingBusySpinner.running
|
||||
icon.name: "mail-attachment"
|
||||
text: i18n("Attach an image or file")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
onClicked: {
|
||||
if (Clipboard.hasImage) {
|
||||
attachDialog.open()
|
||||
} else {
|
||||
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
||||
fileDialog.chosen.connect((path) => {
|
||||
if (!path) { return }
|
||||
ChatBoxHelper.attachmentPath = path;
|
||||
})
|
||||
fileDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
BusyIndicator {
|
||||
id: uploadingBusySpinner
|
||||
anchors.fill: parent
|
||||
visible: running
|
||||
running: currentRoom && currentRoom.hasFileUploading
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: emojiButton
|
||||
icon.name: "preferences-desktop-emoticons"
|
||||
text: i18n("Add an Emoji")
|
||||
display: AbstractButton.IconOnly
|
||||
checkable: true
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: sendButton
|
||||
icon.name: "document-send"
|
||||
text: i18n("Send message")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
onClicked: {
|
||||
chatBar.postMessage()
|
||||
}
|
||||
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: pasteAction
|
||||
shortcut: StandardKey.Paste
|
||||
onTriggered: {
|
||||
if (Clipboard.hasImage) {
|
||||
pasteImageTriggered();
|
||||
}
|
||||
activeFocusItem.paste();
|
||||
}
|
||||
}
|
||||
|
||||
CompletionMenu {
|
||||
id: completionMenu
|
||||
width: parent.width
|
||||
//height: 80 //Math.min(implicitHeight, delegate.implicitHeight * 6)
|
||||
height: implicitHeight
|
||||
y: -height - 1
|
||||
z: 1
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
onCompleteTriggered: {
|
||||
complete()
|
||||
isCompleting = false;
|
||||
}
|
||||
}
|
||||
|
||||
function pasteImage() {
|
||||
let localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png";
|
||||
if (!Clipboard.saveImage(localPath)) {
|
||||
return;
|
||||
}
|
||||
ChatBoxHelper.attachmentPath = localPath;
|
||||
}
|
||||
|
||||
function postMessage() {
|
||||
checkForFancyEffectsReason();
|
||||
|
||||
if (ChatBoxHelper.hasAttachment) {
|
||||
// send attachment but don't reset the text
|
||||
actionsHandler.postMessage("", ChatBoxHelper.attachmentPath,
|
||||
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, {});
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
messageSent();
|
||||
return;
|
||||
}
|
||||
|
||||
const re = /^s\/([^\/]*)\/([^\/]*)/;
|
||||
if (Config.allowQuickEdit && re.test(inputField.text)) {
|
||||
// send edited messages
|
||||
actionsHandler.postEdit(inputField.text);
|
||||
} else {
|
||||
// send normal message
|
||||
actionsHandler.postMessage(inputField.text.trim(), ChatBoxHelper.attachmentPath,
|
||||
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, userAutocompleted);
|
||||
}
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
inputField.clear();
|
||||
inputField.text = Qt.binding(function() {
|
||||
return currentRoom ? currentRoom.cachedInput : "";
|
||||
});
|
||||
messageSent()
|
||||
}
|
||||
|
||||
function complete() {
|
||||
documentHandler.replaceAutoComplete(completionMenu.currentDisplayText);
|
||||
if (completionMenu.completionType === ChatDocumentHandler.User
|
||||
&& completionMenu.currentDisplayText.length > 0
|
||||
&& completionMenu.currentItem.userId.length > 0) {
|
||||
userAutocompleted[completionMenu.currentDisplayText] = completionMenu.currentItem.userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component.ChatBox 1.0
|
||||
import NeoChat.Component.Emoji 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property alias inputFieldText: chatBar.inputFieldText
|
||||
|
||||
signal fancyEffectsReasonFound(string fancyEffect)
|
||||
signal messageSent()
|
||||
signal editLastUserMessage()
|
||||
signal replyPreviousUserMessage()
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
implicitWidth: {
|
||||
let w = 0
|
||||
for(let i = 0; i < visibleChildren.length; ++i) {
|
||||
w = Math.max(w, Math.ceil(visibleChildren[i].implicitWidth))
|
||||
}
|
||||
return w
|
||||
}
|
||||
implicitHeight: {
|
||||
let h = 0
|
||||
for(let i = 0; i < visibleChildren.length; ++i) {
|
||||
h += Math.ceil(visibleChildren[i].implicitHeight)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// For some reason, this is needed to make the height animation work even though
|
||||
// it used to work and height should be directly affected by implicitHeight
|
||||
height: implicitHeight
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
id: connectionPaneSeparator
|
||||
visible: connectionPane.visible
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: connectionPane.top
|
||||
z: 1
|
||||
}
|
||||
|
||||
QQC2.Pane {
|
||||
id: connectionPane
|
||||
padding: fontMetrics.lineSpacing * 0.25
|
||||
spacing: 0
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
visible: !Controller.isOnline
|
||||
width: parent.width
|
||||
QQC2.Label {
|
||||
text: i18n("NeoChat is offline. Please check your network connection.")
|
||||
}
|
||||
anchors.bottom: emojiPickerLoaderSeparator.top
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
id: emojiPickerLoaderSeparator
|
||||
visible: emojiPickerLoader.visible
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: emojiPickerLoader.top
|
||||
z: 1
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: emojiPickerLoader
|
||||
active: visible
|
||||
visible: chatBar.emojiPaneOpened
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: replySeparator.top
|
||||
sourceComponent: EmojiPicker{
|
||||
textArea: chatBar.textField
|
||||
onChosen: addText(emoji)
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
id: replySeparator
|
||||
visible: replyPane.visible
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: replyPane.top
|
||||
}
|
||||
|
||||
|
||||
ReplyPane {
|
||||
id: replyPane
|
||||
visible: ChatBoxHelper.isReplying || ChatBoxHelper.isEditing
|
||||
user: ChatBoxHelper.replyUser
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: attachmentSeparator.top
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
onReplyCancelled: {
|
||||
root.focusInputField()
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
id: attachmentSeparator
|
||||
visible: attachmentPane.visible
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: attachmentPane.top
|
||||
}
|
||||
|
||||
AttachmentPane {
|
||||
id: attachmentPane
|
||||
visible: ChatBoxHelper.hasAttachment
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: chatBarSeparator.top
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
id: chatBarSeparator
|
||||
visible: chatBar.visible
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: chatBar.top
|
||||
}
|
||||
|
||||
ChatBar {
|
||||
id: chatBar
|
||||
visible: currentRoom.canSendEvent("m.room.message")
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
property: "height"
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
onCloseAllTriggered: closeAll()
|
||||
onMessageSent: {
|
||||
closeAll()
|
||||
checkForFancyEffectsReason()
|
||||
root.messageSent();
|
||||
}
|
||||
onEditLastUserMessage: {
|
||||
root.editLastUserMessage();
|
||||
}
|
||||
onReplyPreviousUserMessage: {
|
||||
root.replyPreviousUserMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function checkForFancyEffectsReason() {
|
||||
if (!Config.showFancyEffects) {
|
||||
return
|
||||
}
|
||||
|
||||
let text = root.inputFieldText.trim()
|
||||
if (text.includes('\u{2744}')) {
|
||||
root.fancyEffectsReasonFound("snowflake")
|
||||
}
|
||||
if (text.includes('\u{1F386}')) {
|
||||
root.fancyEffectsReasonFound("fireworks")
|
||||
}
|
||||
if (text.includes('\u{1F387}')) {
|
||||
root.fancyEffectsReasonFound("fireworks")
|
||||
}
|
||||
if (text.includes('\u{1F389}')) {
|
||||
root.fancyEffectsReasonFound("confetti")
|
||||
}
|
||||
if (text.includes('\u{1F38A}')) {
|
||||
root.fancyEffectsReasonFound("confetti")
|
||||
}
|
||||
}
|
||||
|
||||
function addText(text) {
|
||||
root.inputFieldText = inputFieldText + text
|
||||
}
|
||||
|
||||
function insertText(str) {
|
||||
root.inputFieldText = inputFieldText.substr(0, inputField.cursorPosition) + str + inputFieldText.substr(inputField.cursorPosition)
|
||||
}
|
||||
|
||||
function focusInputField() {
|
||||
chatBar.inputFieldForceActiveFocusTriggered()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: RoomManager
|
||||
|
||||
function onCurrentRoomChanged() {
|
||||
chatBar.userAutocompleted = {};
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ChatBoxHelper
|
||||
|
||||
function onShouldClearText() {
|
||||
root.inputFieldText = "";
|
||||
}
|
||||
|
||||
function onEditing(editContent, editFormatedContent) {
|
||||
// Set the input field in edit mode
|
||||
root.inputFieldText = editContent;
|
||||
|
||||
// clean autocompletion list
|
||||
chatBar.userAutocompleted = {};
|
||||
|
||||
// Fill autocompletion list with values extracted from message.
|
||||
// We can't just iterate on every user in the list and try to
|
||||
// find matching display name since some users have display name
|
||||
// matching frequent words and this will marks too many words as
|
||||
// mentions.
|
||||
const regex = /<a href=\"https:\/\/matrix.to\/#\/(@[a-zA-Z09]*:[a-zA-Z09.]*)\">([^<]*)<\/a>/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(editFormatedContent.toString())) !== null) {
|
||||
chatBar.userAutocompleted[match[2]] = match[1];
|
||||
}
|
||||
chatBox.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
ChatBoxHelper.clear();
|
||||
chatBar.emojiPaneOpened = false;
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
import Qt.labs.qmlmodels 1.0
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Popup {
|
||||
id: control
|
||||
|
||||
// Expose internal ListView properties.
|
||||
property alias model: completionListView.model
|
||||
property alias currentIndex: completionListView.currentIndex
|
||||
property alias currentItem: completionListView.currentItem
|
||||
property alias count: completionListView.count
|
||||
property alias delegate: completionListView.delegate
|
||||
|
||||
// Autocomplee text
|
||||
property string currentDisplayText: currentItem && (currentItem.displayName ?? "")
|
||||
|
||||
property int completionType: ChatDocumentHandler.Emoji
|
||||
property int beginPosition: 0
|
||||
property int endPosition: 0
|
||||
|
||||
signal completeTriggered()
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
bottomPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
clip: true
|
||||
|
||||
onVisibleChanged: if (!visible) {
|
||||
completionListView.currentIndex = 0;
|
||||
}
|
||||
|
||||
implicitHeight: Math.min(completionListView.contentHeight, Kirigami.Units.gridUnit * 5)
|
||||
|
||||
contentItem: ScrollView {
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ListView {
|
||||
id: completionListView
|
||||
implicitWidth: contentWidth
|
||||
delegate: {
|
||||
if (completionType === ChatDocumentHandler.Emoji) {
|
||||
emojiDelegate
|
||||
} else if (completionType === ChatDocumentHandler.Command) {
|
||||
commandDelegate
|
||||
} else if (completionType === ChatDocumentHandler.User) {
|
||||
usernameDelegate
|
||||
}
|
||||
}
|
||||
|
||||
keyNavigationWraps: true
|
||||
|
||||
//interactive: Window.window ? contentHeight + control.topPadding + control.bottomPadding > Window.window.height : false
|
||||
clip: true
|
||||
currentIndex: control.currentIndex || 0
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
|
||||
Component {
|
||||
id: usernameDelegate
|
||||
Kirigami.BasicListItem {
|
||||
id: usernameItem
|
||||
width: ListView.view.width ?? implicitWidth
|
||||
property string displayName: modelData.displayName
|
||||
property string userId: modelData.id
|
||||
leading: Kirigami.Avatar {
|
||||
implicitHeight: Kirigami.Units.gridUnit
|
||||
implicitWidth: implicitHeight
|
||||
source: modelData.avatarMediaId ? ("image://mxc/" + modelData.avatarMediaId) : ""
|
||||
color: modelData.color ? Qt.darker(modelData.color, 1.1) : null
|
||||
}
|
||||
text: modelData.displayName
|
||||
onClicked: completeTriggered();
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: emojiDelegate
|
||||
Kirigami.BasicListItem {
|
||||
id: emojiItem
|
||||
width: ListView.view.width ?? implicitWidth
|
||||
property string displayName: modelData.unicode
|
||||
text: modelData.unicode + " " + modelData.shortname
|
||||
|
||||
leading: Label {
|
||||
id: unicodeLabel
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||
Layout.preferredWidth: textMetrics.tightBoundingRect.width
|
||||
font.pointSize: Kirigami.Units.gridUnit * 0.75
|
||||
text: modelData.unicode
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
TextMetrics {
|
||||
id: textMetrics
|
||||
text: modelData.unicode
|
||||
font: unicodeLabel.font
|
||||
}
|
||||
onClicked: completeTriggered();
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: commandDelegate
|
||||
Kirigami.BasicListItem {
|
||||
id: commandItem
|
||||
width: ListView.view.width ?? implicitWidth
|
||||
text: "<i>" + modelData.parameter.replace("<", "<").replace(">", ">") + "</i> " + modelData.help
|
||||
property string displayName: modelData.command
|
||||
|
||||
leading: Label {
|
||||
id: commandLabel
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||
Layout.preferredWidth: textMetrics.tightBoundingRect.width
|
||||
text: modelData.command
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
TextMetrics {
|
||||
id: textMetrics
|
||||
text: modelData.command
|
||||
font: commandLabel.font
|
||||
}
|
||||
onClicked: completeTriggered();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
readonly property bool isEdit: ChatBoxHelper.isEditing
|
||||
property var user: null
|
||||
property string avatarMediaUrl: user ? "image://mxc/" + user.avatarMediaId : ""
|
||||
|
||||
signal replyCancelled()
|
||||
|
||||
active: visible
|
||||
sourceComponent: Pane {
|
||||
id: replyPane
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
spacing: leftPadding
|
||||
|
||||
contentItem: RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: replyPane.spacing
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: textArea.font
|
||||
}
|
||||
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
Layout.alignment: textContentLayout.height > avatar.height ? Qt.AlignHCenter | Qt.AlignTop : Qt.AlignCenter
|
||||
Layout.preferredWidth: Layout.preferredHeight
|
||||
Layout.preferredHeight: fontMetrics.lineSpacing * 2 - fontMetrics.leading
|
||||
source: root.avatarMediaUrl
|
||||
name: user ? user.displayName : ""
|
||||
color: user ? user.color : "transparent"
|
||||
visible: Boolean(user)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: textContentLayout
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.fillWidth: true
|
||||
spacing: fontMetrics.leading
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
textFormat: Text.StyledText
|
||||
elide: Text.ElideRight
|
||||
text: {
|
||||
let heading = "<b>%1</b>"
|
||||
let userName = user ? "<font color=\""+ user.color +"\">" + user.displayName + "</font>" : ""
|
||||
if (isEdit) {
|
||||
heading = heading.arg(i18n("Editing message:")) + "<br/>"
|
||||
} else {
|
||||
heading = heading.arg(i18n("Replying to %1:", userName))
|
||||
}
|
||||
|
||||
return heading
|
||||
}
|
||||
}
|
||||
ScrollView {
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||
TextArea {
|
||||
id: textArea
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
text: {
|
||||
const stylesheet = "<style> a{color:"+Kirigami.Theme.linkColor+";}.user-pill{}</style>";
|
||||
const content = ChatBoxHelper.isReplying ? ChatBoxHelper.replyEventContent : ChatBoxHelper.editContent;
|
||||
return stylesheet + content;
|
||||
}
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
readOnly: true
|
||||
wrapMode: Label.Wrap
|
||||
textFormat: TextEdit.RichText
|
||||
background: null
|
||||
HoverHandler {
|
||||
cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: cancelReplyButton
|
||||
Layout.alignment: avatar.Layout.alignment
|
||||
icon.name: "dialog-cancel"
|
||||
text: i18n("Cancel")
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: {
|
||||
ChatBoxHelper.clearEditReply();
|
||||
root.replyCancelled();
|
||||
}
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module NeoChat.Component.ChatBox
|
||||
ChatBox 1.0 ChatBox.qml
|
||||
ChatBar 1.0 ChatBar.qml
|
||||
ReplyPane 1.0 ReplyPane.qml
|
||||
AttachmentPane 1.0 AttachmentPane.qml
|
||||
CompletionMenu 1.0 CompletionMenu.qml
|
||||
EmojiPickerPane 1.0 EmojiPickerPane.qml
|
||||
648
imports/NeoChat/Component/ChatTextInput.qml
Normal file
648
imports/NeoChat/Component/ChatTextInput.qml
Normal file
@@ -0,0 +1,648 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import Qt.labs.platform 1.0 as Platform
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Component.Emoji 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Page 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
ToolBar {
|
||||
id: root
|
||||
|
||||
property alias isReply: replyItem.visible
|
||||
property bool isReaction: false
|
||||
property var replyUser
|
||||
property string replyEventID: ""
|
||||
property string replyContent: ""
|
||||
|
||||
property string editEventId
|
||||
|
||||
property alias isAutoCompleting: autoCompleteListView.visible
|
||||
property var autoCompleteModel
|
||||
property int autoCompleteBeginPosition
|
||||
property int autoCompleteEndPosition
|
||||
|
||||
property bool hasAttachment: false
|
||||
property url attachmentPath: ""
|
||||
property var attachmentMimetype: FileType.mimeTypeForUrl(attachmentPath)
|
||||
property bool hasImageAttachment: hasAttachment && attachmentMimetype.valid
|
||||
&& FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix)
|
||||
|
||||
position: ToolBar.Footer
|
||||
|
||||
function addText(text) {
|
||||
inputField.insert(inputField.length, text)
|
||||
}
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
Action {
|
||||
id: pasteAction
|
||||
shortcut: StandardKey.Paste
|
||||
onTriggered: {
|
||||
if (Clipboard.hasImage) {
|
||||
root.pasteImage();
|
||||
}
|
||||
activeFocusItem.paste();
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: layout
|
||||
spacing: 0
|
||||
EmojiPicker {
|
||||
id: emojiPicker
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
visible: false
|
||||
|
||||
textArea: inputField
|
||||
emojiModel: EmojiModel { id: emojiModel }
|
||||
onChosen: {
|
||||
textArea.insert(textArea.cursorPosition, emoji);
|
||||
textArea.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 8
|
||||
|
||||
id: replyItem
|
||||
|
||||
visible: false
|
||||
|
||||
spacing: 8
|
||||
|
||||
Control {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
padding: 4
|
||||
|
||||
contentItem: RowLayout {
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||
|
||||
source: replyUser ? ("image://mxc/" + replyUser.avatarMediaId) : ""
|
||||
name: replyUser ? replyUser.name : i18n("No name")
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: replyUser ? replyUser.displayName : i18n("No name")
|
||||
rightPadding: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: "<style>a{color: " + color + ";} .user-pill{}</style>" + replyContent
|
||||
color: Kirigami.Theme.textColor
|
||||
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
wrapMode: Label.Wrap
|
||||
textFormat: Text.RichText
|
||||
selectedTextColor: "white"
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 36
|
||||
Layout.margins: 8
|
||||
|
||||
id: autoCompleteListView
|
||||
|
||||
visible: false
|
||||
|
||||
model: autoCompleteModel
|
||||
|
||||
clip: true
|
||||
spacing: 4
|
||||
orientation: ListView.Horizontal
|
||||
highlightFollowsCurrentItem: true
|
||||
keyNavigationWraps: true
|
||||
|
||||
delegate: Control {
|
||||
readonly property string userId: modelData.id ?? ""
|
||||
readonly property string displayText: modelData.displayName ?? modelData.unicode
|
||||
readonly property bool isEmoji: modelData.unicode != null
|
||||
readonly property bool highlighted: autoCompleteListView.currentIndex == index
|
||||
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Label {
|
||||
width: Kirigami.Units.gridUnit
|
||||
height: Kirigami.Units.gridUnit
|
||||
visible: isEmoji
|
||||
text: displayText
|
||||
font.family: "Emoji"
|
||||
font.pointSize: 20
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||
source: modelData.avatarMediaId ? ("image://mxc/" + modelData.avatarMediaId) : ""
|
||||
color: modelData.color ? Qt.darker(modelData.color, 1.1) : null
|
||||
visible: !isEmoji
|
||||
}
|
||||
Label {
|
||||
Layout.fillHeight: true
|
||||
|
||||
visible: !isEmoji
|
||||
text: displayText
|
||||
color: highlighted ? Kirigami.Theme.highlightTextColor : Kirigami.Theme.textColor
|
||||
font.underline: highlighted
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
autoCompleteListView.currentIndex = index
|
||||
inputField.autoComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
visible: emojiPicker.visible || replyItem.visible || autoCompleteListView.visible
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 10
|
||||
source: attachmentPath
|
||||
visible: hasImageAttachment
|
||||
fillMode: Image.PreserveAspectFit
|
||||
Layout.preferredWidth: paintedWidth
|
||||
RowLayout {
|
||||
anchors.right: parent.right
|
||||
Button {
|
||||
icon.name: "document-edit"
|
||||
|
||||
// HACK: Use a component because an url doesn't work
|
||||
Component {
|
||||
id: imageEditorPage
|
||||
ImageEditorPage {
|
||||
imagePath: attachmentPath
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage, {
|
||||
imagePath: attachmentPath
|
||||
});
|
||||
imageEditor.newPathChanged.connect(function(newPath) {
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
attachmentPath = newPath;
|
||||
});
|
||||
}
|
||||
ToolTip {
|
||||
text: i18n("Edit")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
icon.name: "dialog-cancel"
|
||||
onClicked: {
|
||||
hasAttachment = false;
|
||||
attachmentPath = "";
|
||||
}
|
||||
ToolTip {
|
||||
text: i18n("Cancel")
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
color: Qt.rgba(255, 255, 255, 40)
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
implicitHeight: fileLabel.implicitHeight
|
||||
|
||||
Label {
|
||||
id: fileLabel
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: attachmentPath !== "" ? attachmentPath.toString().substring(attachmentPath.toString().lastIndexOf('/') + 1, attachmentPath.length) : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: hasAttachment && !hasImageAttachment
|
||||
ToolButton {
|
||||
icon.name: "dialog-cancel"
|
||||
onClicked: {
|
||||
hasAttachment = false;
|
||||
attachmentPath = "";
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Icon {
|
||||
id: mimetypeIcon
|
||||
implicitHeight: Kirigami.Units.fontMetrics.roundedIconSize(horizontalFileLabel.implicitHeight)
|
||||
implicitWidth: implicitHeight
|
||||
source: attachmentMimetype.iconName
|
||||
}
|
||||
|
||||
Label {
|
||||
id: horizontalFileLabel
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: attachmentPath !== "" ? attachmentPath.toString().substring(attachmentPath.toString().lastIndexOf('/') + 1, attachmentPath.length) : ""
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: editEventId.length > 0
|
||||
ToolButton {
|
||||
icon.name: "dialog-cancel"
|
||||
onClicked: clearEditReply();
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: i18n("Edit Message")
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
visible: hasAttachment
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
spacing: 0 //Kirigami.Units.smallSpacing
|
||||
|
||||
Button {
|
||||
id: cancelReplyButton
|
||||
|
||||
visible: isReply
|
||||
|
||||
icon.name: "dialog-cancel"
|
||||
|
||||
onClicked: clearEditReply()
|
||||
}
|
||||
|
||||
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: inputField.lineHeight * 8
|
||||
TextArea {
|
||||
id: inputField
|
||||
property real progress: 0
|
||||
property bool autoAppeared: false
|
||||
|
||||
// store each user we autoComplete here, this will be helpful later to generate
|
||||
// the matrix.to links.
|
||||
// This use an hack to define: https://doc.qt.io/qt-5/qml-var.html#property-value-initialization-semantics
|
||||
property var userAutocompleted: ({})
|
||||
|
||||
ChatDocumentHandler {
|
||||
id: documentHandler
|
||||
document: inputField.textDocument
|
||||
cursorPosition: inputField.cursorPosition
|
||||
selectionStart: inputField.selectionStart
|
||||
selectionEnd: inputField.selectionEnd
|
||||
room: currentRoom ?? null
|
||||
}
|
||||
|
||||
property int lineHeight: contentHeight / lineCount
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
placeholderText: i18n("Write your message...")
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
leftPadding: Kirigami.Units.smallSpacing
|
||||
selectByMouse: true
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
|
||||
text: currentRoom != null ? currentRoom.cachedInput : ""
|
||||
|
||||
background: MouseArea {
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: Qt.IBeamCursor
|
||||
z: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: currentRoom && currentRoom.hasFileUploading ? parent.width * currentRoom.fileUploadingProgress / 100 : 0
|
||||
height: parent.height
|
||||
|
||||
opacity: 0.2
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timeoutTimer
|
||||
|
||||
repeat: false
|
||||
interval: 2000
|
||||
onTriggered: {
|
||||
repeatTimer.stop()
|
||||
currentRoom.sendTypingNotification(false)
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: repeatTimer
|
||||
|
||||
repeat: true
|
||||
interval: 5000
|
||||
triggeredOnStart: true
|
||||
onTriggered: currentRoom.sendTypingNotification(true)
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
if (isAutoCompleting) {
|
||||
inputField.autoComplete();
|
||||
|
||||
isAutoCompleting = false;
|
||||
return;
|
||||
}
|
||||
if (event.modifiers & Qt.ShiftModifier) {
|
||||
insert(cursorPosition, "\n")
|
||||
} else {
|
||||
postMessage()
|
||||
text = ""
|
||||
clearEditReply()
|
||||
closeAll()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
clearEditReply();
|
||||
closeAll();
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
switchRoomDown();
|
||||
} else if (event.key === Qt.Key_PageUp) {
|
||||
switchRoomUp();
|
||||
} else if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
|
||||
root.pasteImage();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onBacktabPressed: {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switchRoomUp();
|
||||
return;
|
||||
}
|
||||
if (isAutoCompleting) {
|
||||
autoCompleteListView.decrementCurrentIndex();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onTabPressed: {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switchRoomDown();
|
||||
return;
|
||||
}
|
||||
if (!isAutoCompleting) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO detect moved cursor
|
||||
|
||||
// ignore first time tab was clicked so that user can select
|
||||
// first emoji/user
|
||||
if (autoAppeared === false) {
|
||||
autoCompleteListView.incrementCurrentIndex()
|
||||
} else {
|
||||
autoAppeared = false;
|
||||
}
|
||||
|
||||
inputField.autoComplete();
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
timeoutTimer.restart()
|
||||
repeatTimer.start()
|
||||
currentRoom.cachedInput = text
|
||||
autoAppeared = false;
|
||||
|
||||
const autocompletionInfo = documentHandler.getAutocompletionInfo();
|
||||
|
||||
if (autocompletionInfo.type === ChatDocumentHandler.Ignore) {
|
||||
return;
|
||||
}
|
||||
if (autocompletionInfo.type === ChatDocumentHandler.None) {
|
||||
isAutoCompleting = false;
|
||||
autoCompleteListView.currentIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (autocompletionInfo.type === ChatDocumentHandler.User) {
|
||||
autoCompleteModel = currentRoom.getUsers(autocompletionInfo.keyword);
|
||||
} else {
|
||||
autoCompleteModel = emojiModel.filterModel(autocompletionInfo.keyword);
|
||||
}
|
||||
|
||||
if (autoCompleteModel.length === 0) {
|
||||
isAutoCompleting = false;
|
||||
autoCompleteListView.currentIndex = 0;
|
||||
return;
|
||||
}
|
||||
isAutoCompleting = true
|
||||
autoAppeared = true;
|
||||
autoCompleteEndPosition = cursorPosition
|
||||
}
|
||||
|
||||
function postMessage() {
|
||||
roomManager.actionsHandler.postMessage(inputField.text.trim(), attachmentPath,
|
||||
replyEventID, editEventId, inputField.userAutocompleted);
|
||||
clearAttachment();
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
clear();
|
||||
text = Qt.binding(function() {
|
||||
return currentRoom != null ? currentRoom.cachedInput : "";
|
||||
});
|
||||
}
|
||||
|
||||
function autoComplete() {
|
||||
documentHandler.replaceAutoComplete(autoCompleteListView.currentItem.displayText)
|
||||
if (!autoCompleteListView.currentItem.isEmoji) {
|
||||
inputField.userAutocompleted[autoCompleteListView.currentItem.displayText] = autoCompleteListView.currentItem.userId;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ToolButton {
|
||||
id: emojiButton
|
||||
icon.name: "preferences-desktop-emoticons"
|
||||
icon.color: "transparent"
|
||||
|
||||
checkable: true
|
||||
checked: emojiPicker.visible
|
||||
onToggled: emojiPicker.visible = !emojiPicker.visible
|
||||
|
||||
ToolTip {
|
||||
text: i18n("Add an Emoji")
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: uploadButton
|
||||
|
||||
visible: !isReply && !hasAttachment
|
||||
|
||||
icon.name: "mail-attachment"
|
||||
|
||||
onClicked: {
|
||||
if (Clipboard.hasImage) {
|
||||
attachDialog.open()
|
||||
} else {
|
||||
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
||||
|
||||
fileDialog.chosen.connect(function(path) {
|
||||
if (!path) return
|
||||
|
||||
root.attach(path)
|
||||
})
|
||||
|
||||
fileDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
text: i18n("Attach an image or file")
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.fill: parent
|
||||
|
||||
running: currentRoom && currentRoom.hasFileUploading
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
icon.name: "document-send"
|
||||
icon.color: "transparent"
|
||||
|
||||
enabled: inputField.length > 0 || hasAttachment
|
||||
|
||||
onClicked: {
|
||||
inputField.postMessage()
|
||||
inputField.text = ""
|
||||
root.clearEditReply()
|
||||
root.closeAll()
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
text: i18n("Send message")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitHeight: 40
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
Kirigami.Separator {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function insert(str) {
|
||||
inputField.insert(inputField.cursorPosition, str)
|
||||
}
|
||||
|
||||
function clear() {
|
||||
inputField.clear();
|
||||
inputField.userAutocompleted = {};
|
||||
}
|
||||
|
||||
function clearEditReply() {
|
||||
isReply = false;
|
||||
replyUser = null;
|
||||
clear();
|
||||
root.replyContent = "";
|
||||
root.replyEventID = "";
|
||||
root.editEventId = "";
|
||||
focus();
|
||||
}
|
||||
|
||||
function focus() {
|
||||
inputField.forceActiveFocus()
|
||||
}
|
||||
|
||||
function edit(editContent, editFormatedContent, editEventId) {
|
||||
console.log("Editing ", editContent, "html:", editFormatedContent)
|
||||
// Set the input field in edit mode
|
||||
inputField.text = editContent;
|
||||
root.editEventId = editEventId
|
||||
|
||||
// clean autocompletion list
|
||||
inputField.userAutocompleted = {};
|
||||
|
||||
// Fill autocompletion list with values extracted from message.
|
||||
// We can't just iterate on every user in the list and try to
|
||||
// find matching display name since some users have display name
|
||||
// matching frequent words and this will marks too many words as
|
||||
// mentions.
|
||||
const regex = /<a href=\"https:\/\/matrix.to\/#\/(@[a-zA-Z09]*:[a-zA-Z09.]*)\">([^<]*)<\/a>/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(editFormatedContent.toString())) !== null) {
|
||||
inputField.userAutocompleted[match[2]] = match[1];
|
||||
}
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
replyItem.visible = false
|
||||
autoCompleteListView.visible = false
|
||||
emojiPicker.visible = false
|
||||
}
|
||||
|
||||
function attach(localPath) {
|
||||
hasAttachment = true
|
||||
attachmentPath = localPath
|
||||
}
|
||||
|
||||
function clearAttachment() {
|
||||
hasAttachment = false
|
||||
attachmentPath = ""
|
||||
}
|
||||
|
||||
function pasteImage() {
|
||||
var localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png";
|
||||
if (!Clipboard.saveImage(localPath)) {
|
||||
return;
|
||||
}
|
||||
root.attach(localPath);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0 as NeoChat
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
ColumnLayout {
|
||||
property string emojiCategory: "history"
|
||||
property var textArea
|
||||
readonly property var emojiModel: NeoChat.EmojiModel
|
||||
property var emojiModel
|
||||
|
||||
signal chosen(string emoji)
|
||||
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Particles 2.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
Item {
|
||||
id: item
|
||||
property bool enabled: false
|
||||
property int effectInterval: Kirigami.Units.veryLongDuration*10;
|
||||
property color darkSnowColor: "grey"
|
||||
property bool isThemeDark: Kirigami.Theme.backgroundColor.hslLightness <= darkSnowColor.hslLightness
|
||||
|
||||
function showConfettiEffect() {
|
||||
confettiTimer.start()
|
||||
}
|
||||
|
||||
function showSnowEffect() {
|
||||
snowTimer.start()
|
||||
}
|
||||
|
||||
function showFireworksEffect() {
|
||||
fireworksTimer.start()
|
||||
}
|
||||
|
||||
// Confetti
|
||||
|
||||
Timer {
|
||||
id: confettiTimer
|
||||
interval: item.effectInterval;
|
||||
running: false;
|
||||
repeat: false;
|
||||
triggeredOnStart: true;
|
||||
onTriggered: {
|
||||
if (item.enabled) {
|
||||
confettiSystem.running = !confettiSystem.running
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParticleSystem {
|
||||
id: confettiSystem
|
||||
anchors.fill: parent
|
||||
|
||||
running: false
|
||||
onRunningChanged: {
|
||||
if (running) {
|
||||
opacity = 1
|
||||
} else {
|
||||
opacity = 0
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
SequentialAnimation {
|
||||
NumberAnimation { duration: Kirigami.Units.longDuration }
|
||||
}
|
||||
}
|
||||
|
||||
ImageParticle {
|
||||
source: "qrc:/imports/NeoChat/Component/confetti.png"
|
||||
entryEffect: ImageParticle.Scale
|
||||
rotationVariation: 360
|
||||
rotationVelocity: 90
|
||||
color: Qt.hsla(Math.random(), 0.5, 0.6, 1)
|
||||
colorVariation: 1
|
||||
}
|
||||
|
||||
Emitter {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
|
||||
sizeVariation: Kirigami.Units.iconSizes.small/2
|
||||
lifeSpan: Kirigami.Units.veryLongDuration*10
|
||||
size: Kirigami.Units.iconSizes.small
|
||||
|
||||
velocity: AngleDirection {
|
||||
angle: 90
|
||||
angleVariation: 42
|
||||
magnitude: 500
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Snow
|
||||
|
||||
Timer {
|
||||
id: snowTimer
|
||||
interval: item.effectInterval;
|
||||
running: false;
|
||||
repeat: false;
|
||||
triggeredOnStart: true;
|
||||
onTriggered: {
|
||||
if (item.enabled) {
|
||||
snowSystem.running = !snowSystem.running
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ParticleSystem {
|
||||
id: snowSystem
|
||||
anchors.fill: parent
|
||||
|
||||
running: false
|
||||
onRunningChanged: {
|
||||
if (running) {
|
||||
opacity = 1
|
||||
} else {
|
||||
opacity = 0
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
SequentialAnimation {
|
||||
NumberAnimation { duration: Kirigami.Units.longDuration }
|
||||
}
|
||||
}
|
||||
|
||||
ItemParticle {
|
||||
delegate: Rectangle {
|
||||
width: 10
|
||||
height: width
|
||||
radius: width
|
||||
color: item.isThemeDark ? "white" : darkSnowColor
|
||||
scale: Math.random()
|
||||
opacity: Math.random()
|
||||
}
|
||||
}
|
||||
|
||||
Emitter {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
|
||||
sizeVariation: Kirigami.Units.iconSizes.medium
|
||||
lifeSpan: Kirigami.Units.veryLongDuration*10
|
||||
size: Kirigami.Units.iconSizes.large
|
||||
emitRate: 42
|
||||
|
||||
velocity: AngleDirection {
|
||||
angle: 90
|
||||
angleVariation: 10
|
||||
magnitude: 300
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fireworks
|
||||
|
||||
Timer {
|
||||
id: fireworksTimer
|
||||
interval: item.effectInterval;
|
||||
running: false;
|
||||
repeat: false;
|
||||
triggeredOnStart: true;
|
||||
onTriggered: {
|
||||
if (item.enabled) {
|
||||
fireworksInternalTimer.running = !fireworksInternalTimer.running
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: fireworksInternalTimer
|
||||
interval: 300
|
||||
triggeredOnStart: true
|
||||
running: false
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
var x = Math.random() * parent.width
|
||||
var y = Math.random() * parent.height
|
||||
customEmit(x, y)
|
||||
customEmit(x, y)
|
||||
customEmit(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
ParticleSystem {
|
||||
id: fireworksSystem
|
||||
anchors.fill: parent
|
||||
running: fireworksInternalTimer.running
|
||||
onRunningChanged: {
|
||||
if (running) {
|
||||
opacity = 1
|
||||
} else {
|
||||
opacity = 0
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
SequentialAnimation {
|
||||
NumberAnimation { duration: Kirigami.Units.longDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageParticle {
|
||||
id: fireworksParticleA
|
||||
system: fireworksSystem
|
||||
source: "qrc:/imports/NeoChat/Component/glowdot.png"
|
||||
alphaVariation: item.isThemeDark ? 0.1 : 0.1
|
||||
alpha: item.isThemeDark ? 0.5 : 1
|
||||
groups: ["a"]
|
||||
opacity: fireworksSystem.opacity
|
||||
entryEffect: ImageParticle.Scale
|
||||
rotationVariation: 360
|
||||
}
|
||||
|
||||
ImageParticle {
|
||||
system: fireworksSystem
|
||||
source: "qrc:/imports/NeoChat/Component/glowdot.png"
|
||||
color: item.isThemeDark ? "white" : "gold"
|
||||
alphaVariation: item.isThemeDark ? 0.1 : 0.1
|
||||
alpha: item.isThemeDark ? 0.5 : 1
|
||||
groups: ["light"]
|
||||
opacity: fireworksSystem.opacity
|
||||
entryEffect: ImageParticle.Scale
|
||||
rotationVariation: 360
|
||||
}
|
||||
|
||||
ImageParticle {
|
||||
id: fireworksParticleB
|
||||
system: fireworksSystem
|
||||
source: "qrc:/imports/NeoChat/Component/glowdot.png"
|
||||
alphaVariation: item.isThemeDark ? 0.1 : 0.1
|
||||
alpha: item.isThemeDark ? 0.5 : 1
|
||||
groups: ["b"]
|
||||
opacity: fireworksSystem.opacity
|
||||
entryEffect: ImageParticle.Scale
|
||||
rotationVariation: 360
|
||||
}
|
||||
|
||||
Component {
|
||||
id: emitterComp
|
||||
Emitter {
|
||||
id: container
|
||||
property int life: 23
|
||||
property real targetX: 0
|
||||
property real targetY: 0
|
||||
width: 1
|
||||
height: 1
|
||||
system: fireworksSystem
|
||||
size: 16
|
||||
endSize: 8
|
||||
sizeVariation: 5
|
||||
Timer {
|
||||
interval: life
|
||||
running: true
|
||||
onTriggered: {
|
||||
container.destroy();
|
||||
var randomHue = Math.random()
|
||||
var lightness = item.isThemeDark ? 0.8 : 0.7
|
||||
fireworksParticleA.color = Qt.hsla(randomHue, 0.8, lightness, 1)
|
||||
fireworksParticleB.color = Qt.hsla(1-randomHue, 0.8, lightness, 1)
|
||||
}
|
||||
}
|
||||
velocity: AngleDirection {angleVariation:360; magnitude: 200}
|
||||
}
|
||||
}
|
||||
|
||||
function customEmit(x,y) {
|
||||
var currentSize = Math.round(Math.random() * 200) + 40
|
||||
var currentLifeSpan = Math.round(Math.random() * 1000) + 100
|
||||
for (var i=0; i<8; i++) {
|
||||
var obj = emitterComp.createObject(parent);
|
||||
obj.x = x
|
||||
obj.y = y
|
||||
obj.targetX = Math.random() * currentSize - currentSize/2 + obj.x
|
||||
obj.targetY = Math.random() * currentSize - currentSize/2 + obj.y
|
||||
obj.life = Math.round(Math.random() * 23) + 150
|
||||
obj.emitRate = Math.round(Math.random() * 32) + 5
|
||||
obj.lifeSpan = currentLifeSpan
|
||||
const group = Math.round(Math.random() * 3);
|
||||
switch (group) {
|
||||
case 0:
|
||||
obj.group = "light";
|
||||
break;
|
||||
case 1:
|
||||
obj.group = "a";
|
||||
break;
|
||||
case 2:
|
||||
obj.group = "b";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
@@ -28,14 +31,7 @@ ApplicationWindow {
|
||||
onClicked: root.destroy()
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
visible: image.status !== Image.Ready
|
||||
anchors.centerIn: parent
|
||||
running: visible
|
||||
}
|
||||
|
||||
AnimatedImage {
|
||||
id: image
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: Math.min(sourceSize.width, root.width)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as QQC2
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
@@ -15,7 +18,7 @@ LoginStep {
|
||||
readonly property var homeserver: customHomeserver.visible ? customHomeserver.text : serverCombo.currentText
|
||||
property bool loading: false
|
||||
|
||||
title: i18nc("@title", "Select a Homeserver")
|
||||
title: i18n("@title", "Select a Homeserver")
|
||||
|
||||
action: Kirigami.Action {
|
||||
enabled: LoginHelper.homeserverReachable && !customHomeserver.visible || customHomeserver.acceptableInput
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
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 QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
QQC2.BusyIndicator {
|
||||
|
||||
property var showContinueButton: false
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as Controls
|
||||
import QtQuick.Layouts 1.14
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component.Login 1.0
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as Controls
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
import NeoChat.Component.Login 1.0
|
||||
|
||||
LoginStep {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.14
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
|
||||
LoginStep {
|
||||
id: password
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtMultimedia 5.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtGraphicalEffects 1.0
|
||||
import Qt.labs.platform 1.0 as Platform
|
||||
import QtMultimedia 5.12
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Menu.Timeline 1.0
|
||||
@@ -26,6 +29,27 @@ Control {
|
||||
autoLoad: false
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
id: saveFileAction
|
||||
onTriggered: {
|
||||
let contextMenu = fileDelegateContextMenu.createObject(root, {'room': currentRoom, 'author': author});
|
||||
contextMenu.viewSource.connect(function() {
|
||||
messagerSourceSheet.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
||||
})
|
||||
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
||||
contextMenu.saveFileAs.connect(saveFileAs)
|
||||
contextMenu.reply.connect(function() {
|
||||
roomPanelInput.replyModel = Object.assign({}, model)
|
||||
roomPanelInput.isReply = true
|
||||
roomPanelInput.focus()
|
||||
})
|
||||
contextMenu.redact.connect(function() {
|
||||
currentRoom.redactEvent(eventId)
|
||||
})
|
||||
contextMenu.popup()
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
RowLayout {
|
||||
ToolButton {
|
||||
@@ -45,8 +69,6 @@ Control {
|
||||
}
|
||||
RowLayout {
|
||||
visible: audio.hasAudio
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
// Server doesn't support seeking, so use ProgressBar instead of Slider :(
|
||||
ProgressBar {
|
||||
from: 0
|
||||
@@ -55,8 +77,72 @@ Control {
|
||||
}
|
||||
|
||||
Label {
|
||||
text: Controller.formatDuration(audio.position) + "/" + Controller.formatDuration(audio.duration)
|
||||
text: humanSize(audio.position) + "/" + humanSize(audio.duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: AutoMouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
id: messageMouseArea
|
||||
|
||||
onSecondaryClicked: saveFileAction.trigger()
|
||||
|
||||
Component {
|
||||
id: messagerSourceSheet
|
||||
|
||||
MessageSourceSheet {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: openFolderDialog
|
||||
|
||||
OpenFolderDialog {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDelegateContextMenu
|
||||
|
||||
FileDelegateContextMenu {}
|
||||
}
|
||||
}
|
||||
|
||||
function saveFileAs() {
|
||||
var folderDialog = openFolderDialog.createObject(ApplicationWindow.overlay)
|
||||
|
||||
folderDialog.chosen.connect(function(path) {
|
||||
if (!path) return
|
||||
|
||||
currentRoom.downloadFile(eventId, path + "/" + currentRoom.fileNameToDownload(eventId))
|
||||
})
|
||||
|
||||
folderDialog.open()
|
||||
}
|
||||
|
||||
function downloadAndOpen() {
|
||||
if (downloaded) {
|
||||
openSavedFile()
|
||||
} else {
|
||||
openOnFinished = true
|
||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||
}
|
||||
}
|
||||
|
||||
function openSavedFile() {
|
||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||
}
|
||||
|
||||
function humanSize(duration) {
|
||||
if (!duration) {
|
||||
return i18n("Unknown duration")
|
||||
}
|
||||
|
||||
if (duration > 1000 * 60 * 60) {
|
||||
return new Date(duration).toLocaleTimeString(Qt.locale(), "hh:mm:ss")
|
||||
}
|
||||
|
||||
return new Date(duration).toLocaleTimeString(Qt.locale(), "mm:ss")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,105 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtGraphicalEffects 1.0
|
||||
import Qt.labs.platform 1.0
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Menu.Timeline 1.0
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
property bool openOnFinished: false
|
||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
id: root
|
||||
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
spacing: 4
|
||||
|
||||
onDownloadedChanged: if (downloaded && openOnFinished) {
|
||||
openSavedFile();
|
||||
}
|
||||
onDownloadedChanged: if (downloaded && openOnFinished) openSavedFile()
|
||||
|
||||
ToolButton {
|
||||
icon.name: progressInfo.completed ? "document-open" : "document-save"
|
||||
onClicked: progressInfo.completed ? openSavedFile() : saveFileAs()
|
||||
}
|
||||
z: -5
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
level: 4
|
||||
text: model.display
|
||||
wrapMode: Label.Wrap
|
||||
Control {
|
||||
contentItem: RowLayout {
|
||||
ToolButton {
|
||||
icon.name: progressInfo.completed ? "document-open" : "document-save"
|
||||
onClicked: progressInfo.completed ? openSavedFile() : saveFileAs()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
level: 4
|
||||
text: display
|
||||
wrapMode: Label.Wrap
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: !progressInfo.completed && progressInfo.active ? (humanSize(progressInfo.progress) + "/" + humanSize(progressInfo.total)) : humanSize(content.info ? content.info.size : 0)
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
wrapMode: Label.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
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
|
||||
}
|
||||
}
|
||||
background: Item {
|
||||
MouseArea {
|
||||
id: messageMouseArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
var contextMenu = fileDelegateContextMenu.createObject(root, {'room': currentRoom, 'author': author});
|
||||
contextMenu.viewSource.connect(function() {
|
||||
messageSourceSheet.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
||||
})
|
||||
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
||||
contextMenu.saveFileAs.connect(saveFileAs)
|
||||
contextMenu.reply.connect(function() {
|
||||
roomPanelInput.replyModel = Object.assign({}, model)
|
||||
roomPanelInput.isReply = true
|
||||
roomPanelInput.focus()
|
||||
})
|
||||
contextMenu.redact.connect(function() {
|
||||
currentRoom.redactEvent(eventId)
|
||||
})
|
||||
contextMenu.popup()
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDialog
|
||||
Component {
|
||||
id: messageSourceSheet
|
||||
|
||||
FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||
onAccepted: {
|
||||
currentRoom.downloadFile(eventId, file)
|
||||
MessageSourceSheet {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDialog
|
||||
|
||||
FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||
onAccepted: {
|
||||
currentRoom.downloadFile(eventId, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDelegateContextMenu
|
||||
|
||||
FileDelegateContextMenu {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,18 +110,34 @@ RowLayout {
|
||||
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 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.localDir)) return;
|
||||
}
|
||||
|
||||
function humanSize(bytes)
|
||||
{
|
||||
if (!bytes)
|
||||
return i18nc("Unknown attachment size", "Unknown")
|
||||
if (bytes < 4000)
|
||||
return i18np("%1 byte", "%1 bytes", bytes)
|
||||
bytes = Math.round(bytes / 100) / 10
|
||||
if (bytes < 2000)
|
||||
return i18nc("KB as in kilobytes", "%1 KB", bytes)
|
||||
bytes = Math.round(bytes / 100) / 10
|
||||
if (bytes < 2000)
|
||||
return i18nc("MB as in megabytes", "%1 MB", bytes)
|
||||
return i18nc("GB as in gigabytes", "%1 GB", Math.round(bytes / 100) / 10)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtGraphicalEffects 1.0
|
||||
import Qt.labs.platform 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Menu.Timeline 1.0
|
||||
@@ -15,7 +19,6 @@ import NeoChat.Menu.Timeline 1.0
|
||||
Image {
|
||||
id: img
|
||||
|
||||
property var content: model.content
|
||||
readonly property bool isAnimated: contentType === "image/gif"
|
||||
|
||||
property bool openOnFinished: false
|
||||
@@ -29,6 +32,9 @@ Image {
|
||||
|
||||
source: "image://mxc/" + mediaId
|
||||
|
||||
sourceSize.width: info.w
|
||||
sourceSize.height: info.h
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
ToolTip.text: display
|
||||
@@ -57,6 +63,66 @@ Image {
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: messageMouseArea
|
||||
|
||||
enabled: !img.readonly
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: {
|
||||
if(mouse.button === Qt.LeftButton) {
|
||||
fullScreenImage.createObject(parent, {"filename": eventId, "localPath": currentRoom.urlToDownload(eventId)}).showFullScreen()
|
||||
} else {
|
||||
openContextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
function openContextMenu() {
|
||||
var contextMenu = imageDelegateContextMenu.createObject(root, {'room': currentRoom, 'author': author});
|
||||
contextMenu.viewSource.connect(function() {
|
||||
messageSourceSheet.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
||||
})
|
||||
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
||||
contextMenu.saveFileAs.connect(saveFileAs)
|
||||
contextMenu.reply.connect(function() {
|
||||
roomPanelInput.replyModel = Object.assign({}, model)
|
||||
roomPanelInput.isReply = true
|
||||
roomPanelInput.focus()
|
||||
})
|
||||
contextMenu.redact.connect(function() {
|
||||
currentRoom.redactEvent(eventId)
|
||||
})
|
||||
contextMenu.popup()
|
||||
}
|
||||
|
||||
Component {
|
||||
id: messageSourceSheet
|
||||
|
||||
MessageSourceSheet {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: openFolderDialog
|
||||
|
||||
OpenFolderDialog {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageDelegateContextMenu
|
||||
|
||||
FileDelegateContextMenu {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fullScreenImage
|
||||
|
||||
FullScreenImage {}
|
||||
}
|
||||
}
|
||||
|
||||
function saveFileAs() {
|
||||
var dialog = fileDialog.createObject(ApplicationWindow.overlay)
|
||||
dialog.open()
|
||||
|
||||
158
imports/NeoChat/Component/Timeline/MessageDelegate.qml
Normal file
158
imports/NeoChat/Component/Timeline/MessageDelegate.qml
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtGraphicalEffects 1.12
|
||||
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
RowLayout {
|
||||
default property alias innerObject : column.children
|
||||
|
||||
readonly property bool sentByMe: author.isLocalUser
|
||||
readonly property bool darkBackground: !sentByMe
|
||||
readonly property bool replyVisible: reply ?? false
|
||||
readonly property bool failed: marks == EventStatus.SendingFailed
|
||||
readonly property color authorColor: eventType == "notice" ? Kirigami.Theme.activeTextColor : author.color
|
||||
readonly property color replyAuthorColor: replyVisible ? reply.author.color : Kirigami.Theme.focusColor
|
||||
|
||||
property alias mouseArea: controlContainer.children
|
||||
property bool isEmote: false
|
||||
|
||||
signal saveFileAs()
|
||||
signal openExternally()
|
||||
signal replyClicked(string eventID)
|
||||
signal replyToMessageClicked(var replyUser, string replyContent, string eventID)
|
||||
|
||||
id: root
|
||||
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: 0
|
||||
Layout.topMargin: showAuthor ? Kirigami.Units.smallSpacing : 0
|
||||
|
||||
Kirigami.Avatar {
|
||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 2
|
||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
visible: showAuthor && Config.showAvatarInTimeline
|
||||
name: author.name ?? author.displayName
|
||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||
color: author.color
|
||||
|
||||
Component {
|
||||
id: userDetailDialog
|
||||
|
||||
UserDetailDialog {}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open()
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: 1
|
||||
visible: !showAuthor && Config.showAvatarInTimeline
|
||||
}
|
||||
|
||||
|
||||
QQC2.Control {
|
||||
id: controlContainer
|
||||
Layout.fillWidth: true
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
hoverEnabled: true
|
||||
contentItem: ColumnLayout {
|
||||
id: column
|
||||
spacing: showAuthor ? Kirigami.Units.smallSpacing : 0
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
Layout.fillWidth: true
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
topInset: 0
|
||||
|
||||
visible: showAuthor && !isEmote
|
||||
|
||||
text: author.displayName
|
||||
font.weight: Font.Bold
|
||||
color: author.color
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
QQC2.Label {
|
||||
visible: showAuthor && !isEmote
|
||||
text: time.toLocaleTimeString(Locale.ShortFormat)
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
id: replyLoader
|
||||
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
|
||||
active: replyVisible
|
||||
}
|
||||
Connections {
|
||||
target: replyLoader.item
|
||||
function onClicked() {
|
||||
replyClicked(reply.eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
z: 2
|
||||
anchors.bottom: controlContainer.top
|
||||
anchors.bottomMargin: -Kirigami.Units.gridUnit
|
||||
anchors.right: controlContainer.right
|
||||
spacing: 0
|
||||
QQC2.Button {
|
||||
QQC2.ToolTip.text: i18n("React")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
visible: controlContainer.hovered
|
||||
icon.name: "preferences-desktop-emoticons"
|
||||
onClicked: emojiDialog.open();
|
||||
EmojiDialog {
|
||||
id: emojiDialog
|
||||
onReact: currentRoom.toggleReaction(eventId, emoji)
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
QQC2.ToolTip.text: i18n("Edit")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
visible: controlContainer.hovered && author.id === Controller.activeConnection.localUserId && (model.eventType === "emote" || model.eventType === "message")
|
||||
icon.name: "document-edit"
|
||||
onClicked: chatTextInput.edit(message, model.formattedBody, eventId)
|
||||
}
|
||||
QQC2.Button {
|
||||
QQC2.ToolTip.text: i18n("Reply")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
visible: controlContainer.hovered
|
||||
icon.name: "mail-replied-symbolic"
|
||||
onClicked: replyToMessage(author, message, eventId)
|
||||
}
|
||||
}
|
||||
background: Rectangle {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
color: !model.isHighlighted ? Kirigami.Theme.backgroundColor : Kirigami.Theme.positiveBackgroundColor
|
||||
opacity: controlContainer.hovered || model.isHighlighted ? 1 : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,21 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
Flow {
|
||||
visible: (reaction && reaction.length > 0) ?? false
|
||||
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Repeater {
|
||||
model: reaction ?? null
|
||||
model: reaction
|
||||
|
||||
delegate: AbstractButton {
|
||||
width: Math.max(implicitWidth, height)
|
||||
@@ -23,43 +27,35 @@ Flow {
|
||||
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
color: checked ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
||||
background: Rectangle {
|
||||
radius: height / 2
|
||||
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: Kirigami.Units.devicePixelRatio
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||
color: checked ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
||||
border.color: checked ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
|
||||
checkable: true
|
||||
|
||||
checked: modelData.hasLocalUser
|
||||
|
||||
onToggled: currentRoom.toggleReaction(eventId, modelData.reaction)
|
||||
|
||||
hoverEnabled: true
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: {
|
||||
var text = "";
|
||||
|
||||
for (var i = 0; i < modelData.authors.length && i < 3; i++) {
|
||||
if (i !== 0) {
|
||||
if (i < modelData.authors.length - 1) {
|
||||
text += ", "
|
||||
} else {
|
||||
text += i18nc("Separate the usernames of users", " and ")
|
||||
}
|
||||
for (var i = 0; i < modelData.authors.length; i++) {
|
||||
if (i === modelData.authors.length - 1 && i !== 0) {
|
||||
text += i18nc("Separate the usernames of users", " and ")
|
||||
} else if (i !== 0) {
|
||||
text += ", "
|
||||
}
|
||||
|
||||
text += modelData.authors[i].displayName
|
||||
}
|
||||
if (modelData.authors.length > 3) {
|
||||
text += i18ncp("%1 is the number of other users", " and %1 other", " and %1 others", modelData.authors.length - 3)
|
||||
}
|
||||
|
||||
text = i18ncp("%2 is the users who reacted and %3 the emoji that was given", "%2 reacted with %3", "%2 reacted with %3", modelData.authors.length, text, modelData.reaction)
|
||||
text = i18ncp("%1 is the users who reacted and %2 the emoji that was given", "%2 reacted with %3", "%2 reacted with %3", modelData.authors.length, text, modelData.reaction)
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
@@ -1,102 +1,56 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
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
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
import NeoChat.Component.Timeline 1.0
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
MouseArea {
|
||||
id: replyButton
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: replyName.implicitHeight + (loader.item ? loader.item.height : 0) + Kirigami.Units.largeSpacing
|
||||
implicitWidth: Math.min(bubbleMaxWidth, Math.max((loader.item ? loader.item.width + Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
|
||||
Component.onCompleted: {
|
||||
parent.Layout.fillWidth = true;
|
||||
parent.Layout.preferredWidth = Qt.binding(function() { return implicitWidth; })
|
||||
parent.Layout.maximumWidth = Qt.binding(function() { return bubbleMaxWidth + Kirigami.Units.largeSpacing * 2; })
|
||||
}
|
||||
Rectangle {
|
||||
id: replyLeftBorder
|
||||
width: Kirigami.Units.smallSpacing
|
||||
height: parent.height
|
||||
color: Kirigami.Theme.highlightColor
|
||||
}
|
||||
QQC2.AbstractButton {
|
||||
visible: replyVisible
|
||||
Component.onCompleted: parent.Layout.fillWidth = true
|
||||
|
||||
Kirigami.Avatar {
|
||||
id: avatatReply
|
||||
anchors.left: replyLeftBorder.right
|
||||
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||
width: visible ? Kirigami.Units.gridUnit : 0
|
||||
height: Kirigami.Units.gridUnit
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
Layout.alignment: Qt.AlignTop
|
||||
visible: Config.showAvatarInTimeline
|
||||
source: reply.author.avatarMediaId ? ("image://mxc/" + reply.author.avatarMediaId) : ""
|
||||
name: reply.author.name || ""
|
||||
color: reply.author.color
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
QQC2.Label {
|
||||
id: replyName
|
||||
anchors {
|
||||
left: avatatReply.right
|
||||
leftMargin: Kirigami.Units.smallSpacing
|
||||
right: parent.right
|
||||
rightMargin: Kirigami.Units.smallSpacing
|
||||
Rectangle {
|
||||
Layout.preferredWidth: Kirigami.Units.smallSpacing
|
||||
Layout.fillHeight: true
|
||||
|
||||
color: Kirigami.Theme.highlightColor
|
||||
}
|
||||
text: reply.author.displayName
|
||||
color: reply.author.color
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.top: replyName.bottom
|
||||
sourceComponent: {
|
||||
switch (reply.type) {
|
||||
case "image":
|
||||
case "sticker":
|
||||
return imageComponent;
|
||||
case "message":
|
||||
return textComponent;
|
||||
// TODO support more types
|
||||
default:
|
||||
return textComponent;
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||
Layout.alignment: Qt.AlignTop
|
||||
visible: Config.showAvatarInTimeline
|
||||
source: replyVisible && reply.author.avatarMediaId ? ("image://mxc/" + reply.author.avatarMediaId) : ""
|
||||
name: replyVisible ? reply.author.name : "H"
|
||||
color: replyVisible ? reply.author.color : Kirigami.Theme.highlightColor
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: replyLayout
|
||||
Layout.fillWidth: true
|
||||
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: replyVisible ? reply.author.displayName : ""
|
||||
color: replyVisible ? reply.author.color: null
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: textComponent
|
||||
TextDelegate {
|
||||
id: replyText
|
||||
textMessage: reply.display
|
||||
Layout.fillWidth: true
|
||||
text: replyVisible ? ("<style>pre {white-space: pre-wrap} a{color: " + Kirigami.Theme.linkColor + ";} .user-pill{}</style>" + reply.display) : ""
|
||||
textFormat: Text.RichText
|
||||
wrapMode: Text.WordWrap
|
||||
width: Math.min(implicitWidth, bubbleMaxWidth - Kirigami.Units.largeSpacing * 3)
|
||||
x: Kirigami.Units.smallSpacing * 3 + avatatReply.width
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageComponent
|
||||
Image {
|
||||
readonly property var content: reply.content
|
||||
readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null)
|
||||
// readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info
|
||||
readonly property var info: content.info
|
||||
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
|
||||
source: "image://mxc/" + mediaId
|
||||
|
||||
width: bubbleMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - avatatReply.width
|
||||
height: reply.content.info.h / reply.content.info.w * width
|
||||
x: Kirigami.Units.smallSpacing * 3 + avatatReply.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
|
||||
import QtQuick 2.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
Kirigami.Heading {
|
||||
level: 4
|
||||
text: model.showSection ? section : ""
|
||||
text: section
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
topPadding: Kirigami.Units.largeSpacing * 2
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
|
||||
Item {
|
||||
Layout.minimumWidth: Kirigami.Units.iconSizes.medium
|
||||
Layout.preferredHeight: 1
|
||||
}
|
||||
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
name: author.displayName
|
||||
name: author.name
|
||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||
color: author.color
|
||||
|
||||
@@ -36,10 +43,18 @@ RowLayout {
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: author.displayName
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.RichText
|
||||
text: "<style>a {text-decoration: none;}</style><a href=\"https://matrix.to/#/" + author.id + "\" style='color: " + author.color + "'>" + author.displayName + "</a> " + display
|
||||
|
||||
text: display
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
font.weight: Font.Medium
|
||||
|
||||
wrapMode: Label.Wrap
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
// SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.4 as Kirigami
|
||||
|
||||
TextEdit {
|
||||
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: /^(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+$/
|
||||
|
||||
property bool isEmote: false
|
||||
property string textMessage: model.display
|
||||
|
||||
text: "<style>
|
||||
table {
|
||||
@@ -38,22 +34,19 @@ a{
|
||||
color: " + Kirigami.Theme.linkColor + ";
|
||||
text-decoration: none;
|
||||
}
|
||||
</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>") : "")
|
||||
|
||||
.user-pill{}
|
||||
</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + display + (isEdited ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + i18n("(edited)") + "</span>") : "")
|
||||
|
||||
color: Kirigami.Theme.textColor
|
||||
font.pointSize: model.reply === undefined && isEmoji.test(model.display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
||||
font.pointSize: isEmoji.test(display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
||||
selectByMouse: !Kirigami.Settings.isMobile
|
||||
readOnly: true
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.RichText
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
onLinkActivated: RoomManager.openResource(link)
|
||||
onHoveredLinkChanged: if (hoveredLink.length > 0) {
|
||||
applicationWindow().hoverLinkIndicator.text = hoveredLink;
|
||||
} else {
|
||||
applicationWindow().hoverLinkIndicator.text = "";
|
||||
onLinkActivated: {
|
||||
applicationWindow().handleLink(link, currentRoom)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -1,199 +1,38 @@
|
||||
// SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12 as Controls
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.12
|
||||
import org.kde.kirigami 2.4 as Kirigami
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
QQC2.ItemDelegate {
|
||||
id: messageDelegate
|
||||
Item {
|
||||
default property alias innerObject : column.children
|
||||
// readonly property bool failed: marks == EventStatus.SendingFailed
|
||||
property bool isLoaded
|
||||
|
||||
property bool isEmote: false
|
||||
property bool cardBackground: true
|
||||
height: column.implicitHeight + (readMarker ? 2 * Kirigami.Units.smallSpacing : 0)
|
||||
|
||||
readonly property int bubbleMaxWidth: !Config.showAvatarInTimeline ? width : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 4, Kirigami.Units.gridUnit * 20)
|
||||
|
||||
signal saveFileAs()
|
||||
signal openExternally()
|
||||
signal replyClicked(string eventID)
|
||||
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
background: null
|
||||
property Item hoverComponent
|
||||
|
||||
// show hover actions
|
||||
onHoveredChanged: {
|
||||
if (hovered && !Kirigami.Settings.isMobile) {
|
||||
updateHoverComponent();
|
||||
}
|
||||
}
|
||||
|
||||
// updates the global hover component to point to this delegate, and update its position
|
||||
function updateHoverComponent() {
|
||||
if (hoverComponent) {
|
||||
hoverComponent.bubble = bubble
|
||||
hoverComponent.updateFunction = updateHoverComponent;
|
||||
hoverComponent.event = model
|
||||
}
|
||||
}
|
||||
|
||||
height: sectionDelegate.height + Math.max(avatar.height, bubble.implicitHeight) + loader.height + (model.showAuthor ? Kirigami.Units.smallSpacing : 0) - (Config.showAvatarInTimeline ? 0 : Kirigami.Units.largeSpacing)
|
||||
|
||||
SectionDelegate {
|
||||
id: sectionDelegate
|
||||
ColumnLayout {
|
||||
id: column
|
||||
width: parent.width
|
||||
anchors.left: avatar.left
|
||||
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||
visible: model.showSection
|
||||
height: visible ? implicitHeight : 0
|
||||
}
|
||||
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
width: visible || Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0
|
||||
height: width
|
||||
sourceSize.width: width
|
||||
sourceSize.height: width
|
||||
anchors {
|
||||
top: sectionDelegate.bottom
|
||||
topMargin: model.showAuthor ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.smallSpacing
|
||||
left: parent.left
|
||||
leftMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
SectionDelegate {
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
visible: model.showAuthor && Config.showAvatarInTimeline
|
||||
name: model.author.name ?? model.author.displayName
|
||||
source: visible && model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : ""
|
||||
color: model.author.color
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: currentRoom,
|
||||
user: author.object,
|
||||
displayName: author.displayName,
|
||||
avatarMediaId: author.avatarMediaId,
|
||||
avatarUrl: author.avatarUrl
|
||||
}).open();
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
visible: showSection
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Control {
|
||||
id: bubble
|
||||
topPadding: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||
bottomPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
hoverEnabled: true
|
||||
anchors {
|
||||
top: avatar.top
|
||||
left: avatar.right
|
||||
leftMargin: Kirigami.Units.smallSpacing
|
||||
right: Config.showAvatarInTimeline ? undefined : parent.right
|
||||
rightMargin: Config.showAvatarInTimeline ? undefined : Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: column
|
||||
spacing: 0
|
||||
Item {
|
||||
id: rowLayout
|
||||
visible: model.showAuthor && !isEmote
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.preferredWidth: nameLabel.implicitWidth + timeLabel.implicitWidth + Kirigami.Units.largeSpacing
|
||||
Layout.maximumWidth: bubbleMaxWidth
|
||||
implicitHeight: visible ? nameLabel.implicitHeight : 0
|
||||
|
||||
QQC2.Label {
|
||||
id: nameLabel
|
||||
topInset: 0
|
||||
|
||||
visible: model.showAuthor && !isEmote
|
||||
anchors.left: rowLayout.left
|
||||
anchors.right: timeLabel.left
|
||||
anchors.rightMargin: Kirigami.Units.smallSpacing
|
||||
|
||||
text: visible ? author.displayName : ""
|
||||
font.weight: Font.Bold
|
||||
color: author.color
|
||||
wrapMode: Text.Wrap
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: currentRoom,
|
||||
user: author.object,
|
||||
displayName: author.displayName,
|
||||
avatarMediaId: author.avatarMediaId,
|
||||
avatarUrl: author.avatarUrl
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
}
|
||||
QQC2.Label {
|
||||
id: timeLabel
|
||||
anchors.right: rowLayout.right
|
||||
visible: model.showAuthor && !isEmote
|
||||
text: visible ? time.toLocaleTimeString(Locale.ShortFormat) : ""
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
id: replyLoader
|
||||
active: model.reply !== undefined
|
||||
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
|
||||
visible: active
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
|
||||
Connections {
|
||||
target: replyLoader.item
|
||||
function onClicked() {
|
||||
replyClicked(reply.eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
visible: cardBackground && Config.showAvatarInTimeline
|
||||
color: model.isHighlighted ? Kirigami.Theme.positiveBackgroundColor : 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: Kirigami.Units.devicePixelRatio
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors {
|
||||
left: bubble.left
|
||||
right: parent.right
|
||||
top: bubble.bottom
|
||||
topMargin: active && Config.showAvatarInTimeline ? Kirigami.Units.smallSpacing : 0
|
||||
}
|
||||
height: active ? item.implicitHeight : 0
|
||||
//Layout.bottomMargin: readMarker ? Kirigami.Units.smallSpacing : 0
|
||||
active: eventType !== "state" && eventType !== "notice" && reaction != undefined && reaction.length > 0
|
||||
visible: active
|
||||
sourceComponent: ReactionDelegate { }
|
||||
Rectangle {
|
||||
width: parent.width * 0.9
|
||||
x: parent.width * 0.05
|
||||
height: Kirigami.Units.smallSpacing
|
||||
anchors.top: column.bottom
|
||||
anchors.topMargin: Kirigami.Units.smallSpacing
|
||||
visible: readMarker
|
||||
color: Kirigami.Theme.positiveTextColor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtMultimedia 5.15
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtMultimedia 5.12
|
||||
import Qt.labs.platform 1.0 as Platform
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Menu.Timeline 1.0
|
||||
@@ -18,6 +21,7 @@ import NeoChat.Menu.Timeline 1.0
|
||||
Video {
|
||||
id: vid
|
||||
|
||||
property bool openOnFinished: false
|
||||
property bool playOnFinished: false
|
||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||
|
||||
@@ -28,6 +32,10 @@ Video {
|
||||
vid.source = progressInfo.localPath
|
||||
}
|
||||
|
||||
if (downloaded && openOnFinished) {
|
||||
openSavedFile()
|
||||
openOnFinished = false
|
||||
}
|
||||
if (downloaded && playOnFinished) {
|
||||
playSavedFile()
|
||||
playOnFinished = false
|
||||
@@ -97,6 +105,36 @@ Video {
|
||||
}
|
||||
}
|
||||
|
||||
Control {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
|
||||
horizontalPadding: 8
|
||||
verticalPadding: 4
|
||||
|
||||
contentItem: RowLayout {
|
||||
Label {
|
||||
text: Qt.formatTime(time)
|
||||
color: "white"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
|
||||
Label {
|
||||
text: author.displayName
|
||||
color: "white"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
radius: height / 2
|
||||
color: "black"
|
||||
opacity: 0.3
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -115,29 +153,100 @@ Video {
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onTapped: if (supportStreaming || progressInfo.completed) {
|
||||
if (vid.playbackState == MediaPlayer.PlayingState) {
|
||||
vid.pause()
|
||||
AutoMouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
id: messageMouseArea
|
||||
|
||||
onPrimaryClicked: {
|
||||
if (supportStreaming || progressInfo.completed) {
|
||||
if (vid.playbackState == MediaPlayer.PlayingState) {
|
||||
vid.pause()
|
||||
} else {
|
||||
vid.play()
|
||||
}
|
||||
} else {
|
||||
vid.play()
|
||||
downloadAndPlay()
|
||||
}
|
||||
} else {
|
||||
downloadAndPlay()
|
||||
}
|
||||
|
||||
onSecondaryClicked: {
|
||||
var contextMenu = imageDelegateContextMenu.createObject(vid, {'room': currentRoom, 'author': author})
|
||||
contextMenu.viewSource.connect(function() {
|
||||
messageSourceSheet.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
||||
})
|
||||
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
||||
contextMenu.saveFileAs.connect(saveFileAs)
|
||||
contextMenu.reply.connect(function() {
|
||||
roomPanelInput.replyModel = Object.assign({}, model)
|
||||
roomPanelInput.isReply = true
|
||||
roomPanelInput.focus()
|
||||
})
|
||||
contextMenu.redact.connect(function() {
|
||||
currentRoom.redactEvent(eventId)
|
||||
})
|
||||
contextMenu.popup()
|
||||
}
|
||||
|
||||
Component {
|
||||
id: messageSourceSheet
|
||||
|
||||
MessageSourceSheet {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: openFolderDialog
|
||||
|
||||
OpenFolderDialog {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageDelegateContextMenu
|
||||
|
||||
FileDelegateContextMenu {}
|
||||
}
|
||||
}
|
||||
|
||||
function downloadAndPlay() {
|
||||
if (downloaded) {
|
||||
playSavedFile()
|
||||
} else {
|
||||
function saveFileAs() {
|
||||
var folderDialog = openFolderDialog.createObject(ApplicationWindow.overlay)
|
||||
|
||||
folderDialog.chosen.connect(function(path) {
|
||||
if (!path) return
|
||||
|
||||
currentRoom.downloadFile(eventId, path + "/" + currentRoom.fileNameToDownload(eventId))
|
||||
})
|
||||
|
||||
folderDialog.open()
|
||||
}
|
||||
|
||||
function downloadAndOpen()
|
||||
{
|
||||
if (downloaded) openSavedFile()
|
||||
else
|
||||
{
|
||||
openOnFinished = true
|
||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||
}
|
||||
}
|
||||
|
||||
function downloadAndPlay()
|
||||
{
|
||||
if (downloaded) playSavedFile()
|
||||
else
|
||||
{
|
||||
playOnFinished = true
|
||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||
}
|
||||
}
|
||||
|
||||
function playSavedFile() {
|
||||
function openSavedFile()
|
||||
{
|
||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||
}
|
||||
|
||||
function playSavedFile()
|
||||
{
|
||||
vid.stop()
|
||||
vid.play()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module NeoChat.Component.Timeline
|
||||
TimelineContainer 1.0 TimelineContainer.qml
|
||||
MessageDelegate 1.0 MessageDelegate.qml
|
||||
TextDelegate 1.0 TextDelegate.qml
|
||||
StateDelegate 1.0 StateDelegate.qml
|
||||
SectionDelegate 1.0 SectionDelegate.qml
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
* SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2021 Srevin Saju <srevinsaju@sugarlabs.org>
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
property string labelText: ""
|
||||
|
||||
active: visible
|
||||
sourceComponent: Pane {
|
||||
id: typingPane
|
||||
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: typingPane.spacing
|
||||
Row {
|
||||
id: dotRow
|
||||
property int duration: 400
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
Repeater {
|
||||
model: 3
|
||||
delegate: Rectangle {
|
||||
id: dot
|
||||
color: Kirigami.Theme.textColor
|
||||
radius: height/2
|
||||
implicitWidth: fontMetrics.xHeight
|
||||
implicitHeight: fontMetrics.xHeight
|
||||
// rotating 45 degrees makes the dots look a bit smoother when scaled up
|
||||
rotation: 45
|
||||
opacity: 0.5
|
||||
scale: 1
|
||||
// FIXME: Sometimes the animation timings for each
|
||||
// dot drift slightly reletative to each other.
|
||||
// Not everyone can see this, but I'm pretty sure it's there.
|
||||
SequentialAnimation {
|
||||
running: true
|
||||
PauseAnimation { duration: dotRow.duration * index / 2 }
|
||||
SequentialAnimation {
|
||||
loops: Animation.Infinite
|
||||
ParallelAnimation {
|
||||
// Animators unfortunately sync up instead of being
|
||||
// staggered, so I'm using NumberAnimations instead.
|
||||
NumberAnimation {
|
||||
target: dot; property: "scale";
|
||||
from: 1; to: 1.33
|
||||
duration: dotRow.duration
|
||||
}
|
||||
NumberAnimation {
|
||||
target: dot; property: "opacity"
|
||||
from: 0.5; to: 1
|
||||
duration: dotRow.duration
|
||||
}
|
||||
}
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: dot; property: "scale"
|
||||
from: 1.33; to: 1
|
||||
duration: dotRow.duration
|
||||
}
|
||||
NumberAnimation {
|
||||
target: dot; property: "opacity"
|
||||
from: 1; to: 0.5
|
||||
duration: dotRow.duration
|
||||
}
|
||||
}
|
||||
PauseAnimation { duration: dotRow.duration }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Label {
|
||||
id: typingLabel
|
||||
elide: Text.ElideRight
|
||||
text: root.labelText
|
||||
}
|
||||
}
|
||||
|
||||
leftInset: !mirrored ? 0 : -background.radius
|
||||
rightInset: mirrored ? 0 : -background.radius
|
||||
bottomInset: -background.radius
|
||||
background: Rectangle {
|
||||
radius: 3
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
border.color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,5 +1,4 @@
|
||||
module NeoChat.Component
|
||||
AutoMouseArea 1.0 AutoMouseArea.qml
|
||||
FullScreenImage 1.0 FullScreenImage.qml
|
||||
ChatTextInput 1.0 ChatTextInput.qml
|
||||
FancyEffectsContainer 1.0 FancyEffectsContainer.qml
|
||||
TypingPane 1.0 TypingPane.qml
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: root
|
||||
@@ -36,7 +39,7 @@ Kirigami.OverlaySheet {
|
||||
|
||||
text: i18nc("@action:button", "Ok")
|
||||
onClicked: {
|
||||
Controller.createRoom(roomNameField.text, roomTopicField.text);
|
||||
roomManager.actionsHandler.createRoom(roomNameField.text, roomTopicField.text);
|
||||
root.close();
|
||||
root.destroy();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as QQC2
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component.Emoji 1.0
|
||||
@@ -24,5 +25,6 @@ QQC2.Popup {
|
||||
|
||||
contentItem: EmojiPicker {
|
||||
onChosen: react(emoji)
|
||||
emojiModel: EmojiModel {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
FileDialog {
|
||||
|
||||
17
imports/NeoChat/Dialog/OpenFolderDialog.qml
Normal file
17
imports/NeoChat/Dialog/OpenFolderDialog.qml
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
FolderDialog {
|
||||
signal chosen(string path)
|
||||
|
||||
id: root
|
||||
|
||||
title: i18n("Please choose a folder")
|
||||
|
||||
onAccepted: chosen(folder)
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: root
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: root
|
||||
@@ -29,7 +32,7 @@ Kirigami.OverlaySheet {
|
||||
|
||||
header: Kirigami.Heading {
|
||||
id: heading
|
||||
text: i18nc("@title:menu Account detail dialog", "Account detail")
|
||||
text: i18nc("Account detail dialog", "Account detail - %1", displayName)
|
||||
elide: Text.ElideRight
|
||||
QQC2.ToolTip.visible: truncated && hovered
|
||||
QQC2.ToolTip.text: text
|
||||
@@ -51,7 +54,6 @@ Kirigami.OverlaySheet {
|
||||
|
||||
name: displayName
|
||||
source: avatarMediaId ? ("image://mxc/" + avatarMediaId) : ""
|
||||
color: user.color
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -104,7 +106,7 @@ Kirigami.OverlaySheet {
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
visible: user !== room.localUser && room.canSendState("kick") && room.containsUser(user.id)
|
||||
visible: user !== room.localUser && room.canSendState("kick")
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Kick this user")
|
||||
@@ -116,14 +118,14 @@ Kirigami.OverlaySheet {
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
visible: user !== room.localUser && room.canSendState("ban") && !room.isUserBanned(user.id)
|
||||
visible: user !== room.localUser && room.canSendState("ban")
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Ban this user")
|
||||
icon.name: "im-ban-user"
|
||||
icon.color: Kirigami.Theme.negativeTextColor
|
||||
onTriggered: {
|
||||
room.ban(user.id)
|
||||
room.banMember(user.id)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
@@ -133,7 +135,7 @@ Kirigami.OverlaySheet {
|
||||
text: i18n("Open a private chat")
|
||||
icon.name: "document-send"
|
||||
onTriggered: {
|
||||
Controller.openOrCreateDirectChat(user);
|
||||
Controller.activeConnection.requestDirectChat(user)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ LoginDialog 1.0 LoginDialog.qml
|
||||
CreateRoomDialog 1.0 CreateRoomDialog.qml
|
||||
AcceptInvitationDialog 1.0 AcceptInvitationDialog.qml
|
||||
OpenFileDialog 1.0 OpenFileDialog.qml
|
||||
OpenFolderDialog 1.0 OpenFolderDialog.qml
|
||||
ImageClipboardDialog 1.0 ImageClipboardDialog.qml
|
||||
StartChatDialog 1.0 StartChatDialog.qml
|
||||
EmojiDialog 1.0 EmojiDialog.qml
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import NeoChat.Page 1.0
|
||||
|
||||
/**
|
||||
@@ -17,7 +17,7 @@ Menu {
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Open in new window")
|
||||
onTriggered: RoomManager.openWindow(room);
|
||||
onTriggered: roomManager.openWindow(room);
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
@@ -44,7 +44,7 @@ Menu {
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Leave Room")
|
||||
onTriggered: RoomManager.leaveRoom(room)
|
||||
onTriggered: room.forget()
|
||||
}
|
||||
|
||||
onClosed: destroy()
|
||||
|
||||
@@ -1,85 +1,54 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Menu 1.0
|
||||
|
||||
MessageDelegateContextMenu {
|
||||
Menu {
|
||||
id: root
|
||||
|
||||
required property var file
|
||||
required property var progressInfo
|
||||
required property var room
|
||||
required property var author
|
||||
|
||||
property list<Kirigami.Action> actions: [
|
||||
Kirigami.Action {
|
||||
text: i18n("Open Externally")
|
||||
icon.name: "document-open"
|
||||
onTriggered: {
|
||||
if (file.downloaded) {
|
||||
if (!Qt.openUrlExternally(progressInfo.localPath)) {
|
||||
Qt.openUrlExternally(progressInfo.localDir);
|
||||
}
|
||||
} else {
|
||||
file.onDownloadedChanged.connect(function() {
|
||||
if (!Qt.openUrlExternally(progressInfo.localPath)) {
|
||||
Qt.openUrlExternally(progressInfo.localDir);
|
||||
}
|
||||
});
|
||||
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||
}
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Save As")
|
||||
icon.name: "document-save"
|
||||
onTriggered: {
|
||||
var dialog = saveAsDialog.createObject(ApplicationWindow.overlay)
|
||||
dialog.open()
|
||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Reply")
|
||||
icon.name: "mail-replied-symbolic"
|
||||
onTriggered: {
|
||||
ChatBoxHelper.replyToMessage(eventId, message, author);
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
||||
text: i18n("Remove")
|
||||
icon.name: "edit-delete-remove"
|
||||
icon.color: "red"
|
||||
onTriggered: {
|
||||
currentRoom.redactEvent(eventId);
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("View Source")
|
||||
icon.name: "code-context"
|
||||
onTriggered: {
|
||||
messageSourceSheet.createObject(root, {'sourceText': root.source}).open();
|
||||
}
|
||||
}
|
||||
]
|
||||
Component {
|
||||
id: saveAsDialog
|
||||
FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||
onAccepted: {
|
||||
if (!currentFile) {
|
||||
return;
|
||||
}
|
||||
currentRoom.downloadFile(eventId, currentFile)
|
||||
}
|
||||
}
|
||||
signal viewSource()
|
||||
signal downloadAndOpen()
|
||||
signal saveFileAs()
|
||||
signal reply()
|
||||
signal redact()
|
||||
|
||||
MenuItem {
|
||||
text: i18n("View Source")
|
||||
|
||||
onTriggered: viewSource()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Open Externally")
|
||||
|
||||
onTriggered: downloadAndOpen()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Save As")
|
||||
|
||||
onTriggered: saveFileAs()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Reply")
|
||||
|
||||
onTriggered: reply()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
visible: room.canSendState("redact") || room.localUser.id === author.id
|
||||
text: i18n("Redact")
|
||||
onTriggered: redact()
|
||||
}
|
||||
|
||||
onClosed: destroy()
|
||||
}
|
||||
|
||||
@@ -1,267 +1,121 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
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.Dialog 1.0
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Loader {
|
||||
id: loadRoot
|
||||
Kirigami.OverlaySheet {
|
||||
id: root
|
||||
|
||||
required property var author
|
||||
required property string message
|
||||
required property string eventId
|
||||
property string eventType: ""
|
||||
property string formattedBody: ""
|
||||
required property string source
|
||||
|
||||
property list<Kirigami.Action> actions: [
|
||||
Kirigami.Action {
|
||||
text: i18n("Edit")
|
||||
icon.name: "document-edit"
|
||||
onTriggered: ChatBoxHelper.edit(message, formattedBody, eventId);
|
||||
visible: eventType.length > 0 && author.id === Controller.activeConnection.localUserId && (eventType === "emote" || eventType === "message")
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Reply")
|
||||
icon.name: "mail-replied-symbolic"
|
||||
onTriggered: ChatBoxHelper.replyToMessage(eventId, message, author);
|
||||
},
|
||||
Kirigami.Action {
|
||||
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
||||
text: i18n("Remove")
|
||||
icon.name: "edit-delete-remove"
|
||||
icon.color: "red"
|
||||
onTriggered: currentRoom.redactEvent(eventId);
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Copy")
|
||||
icon.name: "edit-copy"
|
||||
onTriggered: Clipboard.saveText(message)
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("View Source")
|
||||
icon.name: "code-context"
|
||||
onTriggered: {
|
||||
messageSourceSheet.createObject(page, {'sourceText': loadRoot.source}).open();
|
||||
signal viewSource()
|
||||
signal reply(var author, string message)
|
||||
signal remove()
|
||||
|
||||
parent: applicationWindow().overlay
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
id: headerLayout
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: 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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Component {
|
||||
id: regularMenu
|
||||
|
||||
QQC2.Menu {
|
||||
Repeater {
|
||||
model: loadRoot.actions
|
||||
QQC2.MenuItem {
|
||||
visible: modelData.visible
|
||||
action: modelData
|
||||
onClicked: loadRoot.item.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
Kirigami.OverlaySheet {
|
||||
id: root
|
||||
|
||||
parent: applicationWindow().overlay
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
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
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
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 {
|
||||
id: mobileMenu
|
||||
|
||||
Kirigami.OverlayDrawer {
|
||||
id: drawer
|
||||
height: popupContent.implicitHeight
|
||||
edge: Qt.BottomEdge
|
||||
padding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
|
||||
parent: applicationWindow().overlay
|
||||
|
||||
ColumnLayout {
|
||||
id: popupContent
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
id: headerLayout
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
level: 3
|
||||
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
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
level: 3
|
||||
Layout.fillWidth: true
|
||||
text: author.displayName
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
QQC2.Label {
|
||||
text: message
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
onLinkActivated: RoomManager.openResource(link);
|
||||
}
|
||||
}
|
||||
text: author.displayName
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
Kirigami.Separator {
|
||||
QQC2.Label {
|
||||
text: message
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||
Repeater {
|
||||
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
|
||||
delegate: QQC2.ItemDelegate {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
contentItem: Kirigami.Heading {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
font.family: "emoji"
|
||||
text: modelData
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
currentRoom.toggleReaction(eventId, modelData);
|
||||
loadRoot.item.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Repeater {
|
||||
id: listViewAction
|
||||
model: loadRoot.actions
|
||||
|
||||
Kirigami.BasicListItem {
|
||||
icon: modelData.icon.name
|
||||
iconColor: modelData.icon.color ?? undefined
|
||||
enabled: modelData.enabled
|
||||
visible: modelData.visible
|
||||
text: modelData.text
|
||||
onClicked: {
|
||||
modelData.triggered()
|
||||
loadRoot.item.close();
|
||||
}
|
||||
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
|
||||
onLinkActivated: {
|
||||
applicationWindow().handleLink(link, currentRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Row {
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
|
||||
delegate: QQC2.ItemDelegate {
|
||||
width: 32
|
||||
height: 32
|
||||
|
||||
asynchronous: true
|
||||
sourceComponent: Kirigami.Settings.isMobile ? mobileMenu : regularMenu
|
||||
contentItem: QQC2.Label {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
function open() {
|
||||
active = true;
|
||||
}
|
||||
font.pixelSize: 16
|
||||
font.family: "emoji"
|
||||
text: modelData
|
||||
|
||||
onStatusChanged: if (status == Loader.Ready) {
|
||||
if (Kirigami.Settings.isMobile) {
|
||||
item.open();
|
||||
} else {
|
||||
item.popup();
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
currentRoom.toggleReaction(eventId, modelData)
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Reply")
|
||||
icon.name: "mail-replied-symbolic"
|
||||
onTriggered: reply(author, message)
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Remove")
|
||||
icon.name: "edit-delete-remove"
|
||||
icon.color: "red"
|
||||
onTriggered: remove()
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Copy")
|
||||
icon.name: "edit-copy"
|
||||
onTriggered: Clipboard.saveText(message)
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
action: Kirigami.Action {
|
||||
text: i18n("View Source")
|
||||
icon.name: "code-context"
|
||||
onTriggered: viewSource()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as Controls
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18n("Accounts")
|
||||
@@ -28,7 +29,7 @@ Kirigami.ScrollablePage {
|
||||
|
||||
text: model.user.displayName
|
||||
subtitle: model.user.id
|
||||
icon: model.user.avatarMediaId ? ("image://mxc/" + model.user.avatarMediaId) : "im-user"
|
||||
icon: model.connection.user.avatarMediaId ? "image://mxc/" + model.connection.user.avatarMediaId : "im-user"
|
||||
|
||||
onClicked: {
|
||||
Controller.activeConnection = model.connection
|
||||
@@ -79,14 +80,6 @@ Kirigami.ScrollablePage {
|
||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/WelcomePage.qml")
|
||||
}
|
||||
|
||||
Component {
|
||||
id: openFileDialog
|
||||
|
||||
OpenFileDialog {
|
||||
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: userEditSheet
|
||||
|
||||
@@ -98,44 +91,6 @@ Kirigami.ScrollablePage {
|
||||
|
||||
Kirigami.FormLayout {
|
||||
anchors.top: passwordsMessage.bottom
|
||||
RowLayout {
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
source: userEditSheet.connection.localUser.avatarMediaId ? ("image://mxc/" + userEditSheet.connection.localUser.avatarMediaId) : ""
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
property var fileDialog: null;
|
||||
onClicked: {
|
||||
if (fileDialog != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileDialog = openFileDialog.createObject(Controls.ApplicationWindow.Overlay)
|
||||
|
||||
fileDialog.chosen.connect(function(receivedSource) {
|
||||
mouseArea.fileDialog = null;
|
||||
if (!receivedSource) {
|
||||
return;
|
||||
}
|
||||
parent.source = receivedSource;
|
||||
});
|
||||
fileDialog.onRejected.connect(function() {
|
||||
mouseArea.fileDialog = null;
|
||||
});
|
||||
fileDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
visible: avatar.source.length !== 0
|
||||
icon.name: "edit-clear"
|
||||
|
||||
onClicked: avatar.source = ""
|
||||
}
|
||||
Kirigami.FormData.label: i18n("Avatar:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: name
|
||||
text: userEditSheet.connection.localUser.displayName
|
||||
@@ -158,37 +113,23 @@ Kirigami.ScrollablePage {
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Controls.Button {
|
||||
text: i18n("Save")
|
||||
onClicked: {
|
||||
if(!Controller.setAvatar(userEditSheet.connection, avatar.source))
|
||||
showPassiveNotification("The Avatar could not be set")
|
||||
if(userEditSheet.connection.localUser.displayName !== name.text)
|
||||
userEditSheet.connection.localUser.rename(name.text)
|
||||
if(currentPassword.text !== "" && newPassword.text !== "" && confirmPassword.text !== "") {
|
||||
if(newPassword.text === confirmPassword.text) {
|
||||
Controller.changePassword(userEditSheet.connection, currentPassword.text, newPassword.text)
|
||||
} else {
|
||||
showPassiveNotification(i18n("Passwords do not match"))
|
||||
return
|
||||
}
|
||||
Controls.Button {
|
||||
text: i18n("Save")
|
||||
onClicked: {
|
||||
if(userEditSheet.connection.localUser.displayName !== name.text)
|
||||
userEditSheet.connection.localUser.rename(name.text)
|
||||
if(currentPassword.text !== "" && newPassword.text !== "" && confirmPassword.text !== "") {
|
||||
if(newPassword.text === confirmPassword.text) {
|
||||
Controller.changePassword(userEditSheet.connection, currentPassword.text, newPassword.text)
|
||||
} else {
|
||||
showPassiveNotification(i18n("Passwords do not match"))
|
||||
return
|
||||
}
|
||||
userEditSheet.close()
|
||||
currentPassword.text = ""
|
||||
newPassword.text = ""
|
||||
confirmPassword.text = ""
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
text: i18n("Cancel")
|
||||
onClicked: {
|
||||
userEditSheet.close()
|
||||
avatar.source = userEditSheet.connection.localUser.avatarMediaId ? ("image://mxc/" + userEditSheet.connection.localUser.avatarMediaId) : ""
|
||||
currentPassword.text = ""
|
||||
newPassword.text = ""
|
||||
confirmPassword.text = ""
|
||||
}
|
||||
userEditSheet.close()
|
||||
currentPassword.text = ""
|
||||
newPassword.text = ""
|
||||
confirmPassword.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-LicenseIdentifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as Controls
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
* SPDX-FileCopyrightText: (C) 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.1 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kquickimageeditor 1.0 as KQuickImageEditor
|
||||
import QtGraphicalEffects 1.12
|
||||
import Qt.labs.platform 1.0 as Platform
|
||||
|
||||
Kirigami.Page {
|
||||
id: rootEditorView
|
||||
@@ -92,18 +94,7 @@ Kirigami.Page {
|
||||
Kirigami.Action {
|
||||
iconName: rootEditorView.resizing ? "dialog-cancel" : "transform-crop"
|
||||
text: rootEditorView.resizing ? i18n("Cancel") : i18nc("@action:button Crop an image", "Crop");
|
||||
onTriggered: {
|
||||
resizeRectangle.width = editImage.paintedWidth
|
||||
resizeRectangle.height = editImage.paintedHeight
|
||||
resizeRectangle.x = editImage.horizontalPadding
|
||||
resizeRectangle.y = editImage.verticalPadding
|
||||
resizeRectangle.insideX = 100
|
||||
resizeRectangle.insideY = 100
|
||||
resizeRectangle.insideWidth = 100
|
||||
resizeRectangle.insideHeight = 100
|
||||
|
||||
rootEditorView.resizing = !rootEditorView.resizing;
|
||||
}
|
||||
onTriggered: rootEditorView.resizing = !rootEditorView.resizing;
|
||||
},
|
||||
Kirigami.Action {
|
||||
iconName: "dialog-ok"
|
||||
@@ -153,7 +144,7 @@ Kirigami.Page {
|
||||
|
||||
width: editImage.paintedWidth
|
||||
height: editImage.paintedHeight
|
||||
x: editImage.horizontalPadding
|
||||
x: 0
|
||||
y: editImage.verticalPadding
|
||||
|
||||
insideX: 100
|
||||
@@ -162,5 +153,23 @@ Kirigami.Page {
|
||||
insideHeight: 100
|
||||
|
||||
onAcceptSize: rootEditorView.crop();
|
||||
|
||||
//resizeHandle: KQuickImageEditor.BasicResizeHandle { }
|
||||
|
||||
/*Rectangle {
|
||||
radius: 2
|
||||
width: Kirigami.Units.gridUnit * 8
|
||||
height: Kirigami.Units.gridUnit * 3
|
||||
anchors.centerIn: parent
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
QQC2.Label {
|
||||
anchors.centerIn: parent
|
||||
text: "x: " + (resizeRectangle.x - rootEditorView.contentItem.width + editImage.paintedWidth)
|
||||
+ " y: " + (resizeRectangle.y - rootEditorView.contentItem.height + editImage.paintedHeight)
|
||||
+ "\nwidth: " + resizeRectangle.width
|
||||
+ " height: " + resizeRectangle.height
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
@@ -19,19 +24,18 @@ Kirigami.ScrollablePage {
|
||||
|
||||
title: i18n("Explore Rooms")
|
||||
|
||||
Component.onCompleted: identifierField.forceActiveFocus()
|
||||
|
||||
header: Control {
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
contentItem: RowLayout {
|
||||
Kirigami.SearchField {
|
||||
id: identifierField
|
||||
property bool isRoomAlias: text.match(/#(.+):(.+)/g)
|
||||
property var room: isRoomAlias ? connection.roomByAlias(text) : null
|
||||
property bool isJoined: room != null
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
id: identifierField
|
||||
|
||||
placeholderText: i18n("Find a room...")
|
||||
}
|
||||
|
||||
@@ -45,7 +49,7 @@ Kirigami.ScrollablePage {
|
||||
|
||||
onClicked: {
|
||||
if (!identifierField.isJoined) {
|
||||
Controller.joinRoom(identifierField.text);
|
||||
roomManager.actionsHandler.joinRoom(identifierField.text);
|
||||
// When joining the room, the room will be opened
|
||||
}
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
@@ -98,17 +102,17 @@ Kirigami.ScrollablePage {
|
||||
width: publicRoomsListView.width
|
||||
onClicked: {
|
||||
if (!isJoined) {
|
||||
Controller.joinRoom(roomID)
|
||||
roomManager.actionsHandler.joinRoom(connection, roomID)
|
||||
justJoined = true;
|
||||
} else {
|
||||
RoomManager.enterRoom(connection.room(roomID))
|
||||
roomManager.enterRoom(connection.room(roomID))
|
||||
}
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.normal
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.normal
|
||||
|
||||
source: model.avatar ? ("image://mxc/" + model.avatar) : ""
|
||||
name: name
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
|
||||
Kirigami.Page {
|
||||
title: i18n("Loading")
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQml.Models 2.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Menu 1.0
|
||||
|
||||
@@ -18,6 +19,7 @@ Kirigami.ScrollablePage {
|
||||
id: page
|
||||
|
||||
property var enteredRoom
|
||||
required property var activeConnection
|
||||
|
||||
function goToNextRoom() {
|
||||
do {
|
||||
@@ -46,9 +48,6 @@ Kirigami.ScrollablePage {
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
||||
@@ -57,31 +56,16 @@ Kirigami.ScrollablePage {
|
||||
helpfulAction: Kirigami.Action {
|
||||
icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
|
||||
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
|
||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {
|
||||
connection: Controller.activeConnection,
|
||||
keyword: sortFilterRoomListModel.filterText
|
||||
})
|
||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": activeConnection, "keyword": sortFilterRoomListModel.filterText})
|
||||
}
|
||||
}
|
||||
|
||||
ItemSelectionModel {
|
||||
id: itemSelection
|
||||
model: roomListModel
|
||||
onCurrentChanged: {
|
||||
listView.currentIndex = sortFilterRoomListModel.mapFromSource(current).row
|
||||
}
|
||||
}
|
||||
|
||||
model: SortFilterRoomListModel {
|
||||
model: SortFilterRoomListModel {
|
||||
id: sortFilterRoomListModel
|
||||
sourceModel: RoomListModel {
|
||||
id: roomListModel
|
||||
connection: Controller.activeConnection
|
||||
connection: page.activeConnection
|
||||
}
|
||||
roomSortOrder: Config.mergeRoomList ? SortFilterRoomListModel.LastActivity : SortFilterRoomListModel.Categories
|
||||
onLayoutChanged: {
|
||||
listView.currentIndex = sortFilterRoomListModel.mapFromSource(itemSelection.currentIndex).row
|
||||
}
|
||||
}
|
||||
|
||||
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
||||
@@ -106,31 +90,23 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
reuseItems: true
|
||||
currentIndex: -1 // we don't want any room highlighted by default
|
||||
|
||||
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)
|
||||
var roomItem = roomManager.enterRoom(currentRoom)
|
||||
roomListItem.KeyNavigation.right = roomItem
|
||||
roomItem.focus = true;
|
||||
}
|
||||
}
|
||||
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, " ")
|
||||
let txt = (lastEvent == "" ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm," ")
|
||||
if (txt.length) {
|
||||
return txt
|
||||
}
|
||||
@@ -140,10 +116,7 @@ Kirigami.ScrollablePage {
|
||||
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
|
||||
implicitWidth: height
|
||||
}
|
||||
|
||||
trailing: RowLayout {
|
||||
@@ -188,28 +161,18 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
footer: QQC2.ToolBar {
|
||||
visible: accountList.count > 1
|
||||
height: visible ? implicitHeight : 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
contentItem: RowLayout {
|
||||
spacing: 0
|
||||
Repeater {
|
||||
id: accountList
|
||||
model: AccountListModel {
|
||||
id: accountListModel
|
||||
}
|
||||
delegate: Kirigami.BasicListItem {
|
||||
checkable: true
|
||||
checked: Controller.activeConnection && Controller.activeConnection.localUser.id === model.user.id
|
||||
onClicked: Controller.activeConnection = model.connection
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
text: model.user.id
|
||||
}
|
||||
footer: RowLayout {
|
||||
visible: accountListTab.count > 1
|
||||
height: visible ? accountListTab.implicitHeight : 0
|
||||
Repeater {
|
||||
id: accountListTab
|
||||
model: AccountListModel { }
|
||||
delegate: QQC2.TabButton {
|
||||
checkable: true
|
||||
checked: Controller.activeConnection.user.id === model.connection.user.id
|
||||
onClicked: Controller.activeConnection = model.connection
|
||||
Layout.fillWidth: true
|
||||
text: model.user.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,13 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.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
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
Kirigami.ApplicationWindow {
|
||||
id: window
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as QQC2
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
@@ -17,7 +20,6 @@ Kirigami.ScrollablePage {
|
||||
Kirigami.FormData.label: i18n("General settings:")
|
||||
text: i18n("Close to system tray")
|
||||
checked: Config.systemTray
|
||||
visible: Controller.supportSystemTray
|
||||
onToggled: {
|
||||
Config.systemTray = checked
|
||||
Config.save()
|
||||
@@ -68,21 +70,5 @@ Kirigami.ScrollablePage {
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Show Fancy Effects")
|
||||
checked: Config.showFancyEffects
|
||||
onToggled: {
|
||||
Config.showFancyEffects = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Use s/text/replacement syntax to edit your last message")
|
||||
checked: Config.allowQuickEdit
|
||||
onToggled: {
|
||||
Config.allowQuickEdit = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as Controls
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
import NeoChat.Component.Login 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.OverlayDrawer {
|
||||
id: roomDrawer
|
||||
readonly property var room: RoomManager.currentRoom
|
||||
property var room
|
||||
|
||||
enabled: true
|
||||
|
||||
@@ -26,222 +30,214 @@ Kirigami.OverlayDrawer {
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
contentItem: Loader {
|
||||
active: roomDrawer.drawerOpen
|
||||
sourceComponent: ColumnLayout {
|
||||
id: columnLayout
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
Kirigami.AbstractApplicationHeader {
|
||||
Layout.fillWidth: true
|
||||
topPadding: Kirigami.Units.smallSpacing / 2;
|
||||
bottomPadding: Kirigami.Units.smallSpacing / 2;
|
||||
rightPadding: Kirigami.Units.smallSpacing
|
||||
leftPadding: Kirigami.Units.smallSpacing
|
||||
contentItem: ColumnLayout {
|
||||
id: columnLayout
|
||||
spacing: 0
|
||||
Kirigami.AbstractApplicationHeader {
|
||||
Layout.fillWidth: true
|
||||
topPadding: Kirigami.Units.smallSpacing / 2;
|
||||
bottomPadding: Kirigami.Units.smallSpacing / 2;
|
||||
rightPadding: Kirigami.Units.smallSpacing
|
||||
leftPadding: Kirigami.Units.smallSpacing
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
ToolButton {
|
||||
icon.name: "list-add-user"
|
||||
text: i18n("Invite")
|
||||
onClicked: {
|
||||
applicationWindow().pageStack.layers.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {"room": room})
|
||||
ToolButton {
|
||||
icon.name: "list-add-user"
|
||||
text: i18n("Invite")
|
||||
onClicked: {
|
||||
applicationWindow().pageStack.layers.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {"room": room})
|
||||
roomDrawer.close();
|
||||
}
|
||||
}
|
||||
Item {
|
||||
// HACK otherwise rating item is not right aligned
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: room.isFavourite ? "rating" : "rating-unrated"
|
||||
checkable: true
|
||||
checked: room.isFavourite
|
||||
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
||||
ToolTip {
|
||||
text: room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: 'settings-configure'
|
||||
onClicked: {
|
||||
roomSettingDialog.createObject(ApplicationWindow.overlay, {"room": room}).open()
|
||||
if (!wideScreen) {
|
||||
roomDrawer.close();
|
||||
}
|
||||
}
|
||||
Item {
|
||||
// HACK otherwise rating item is not right aligned
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: room && room.isFavourite ? "rating" : "rating-unrated"
|
||||
checkable: true
|
||||
checked: room && room.isFavourite
|
||||
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
||||
ToolTip {
|
||||
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: 'settings-configure'
|
||||
onClicked: {
|
||||
roomSettingDialog.createObject(ApplicationWindow.overlay, {"room": room}).open()
|
||||
if (!wideScreen) {
|
||||
roomDrawer.close();
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
text: i18n("Room settings")
|
||||
}
|
||||
ToolTip {
|
||||
text: i18n("Room settings")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Control {
|
||||
Control {
|
||||
Layout.fillWidth: true
|
||||
bottomPadding: Kirigami.Units.largeSpacing
|
||||
contentItem: ColumnLayout {
|
||||
id: infoLayout
|
||||
Layout.fillWidth: true
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
contentItem: ColumnLayout {
|
||||
id: infoLayout
|
||||
Kirigami.Heading {
|
||||
text: i18n("Room information")
|
||||
level: 3
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
text: i18n("Room information")
|
||||
level: 3
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3.5
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3.5
|
||||
|
||||
name: room ? room.name : i18n("No name")
|
||||
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||
}
|
||||
RowLayout {
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: 0
|
||||
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3.5
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3.5
|
||||
|
||||
name: room ? room.name : i18n("No name")
|
||||
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.Heading {
|
||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 9
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 9
|
||||
Layout.fillWidth: true
|
||||
level: 1
|
||||
font.bold: true
|
||||
wrapMode: Label.Wrap
|
||||
text: room ? room.displayName : i18n("No name")
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: room && room.canonicalAlias ? room.canonicalAlias : i18n("No Canonical Alias")
|
||||
}
|
||||
level: 1
|
||||
font.bold: true
|
||||
wrapMode: Label.Wrap
|
||||
text: room ? room.displayName : i18n("No name")
|
||||
}
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 13
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 13
|
||||
Layout.fillWidth: true
|
||||
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||
readonly property var replaceLinks: /\(https:\/\/[^ ]*\)/
|
||||
textFormat: TextEdit.MarkdownText
|
||||
wrapMode: Text.WordWrap
|
||||
selectByMouse: true
|
||||
color: Kirigami.Theme.textColor
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
readOnly: true
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: room && room.canonicalAlias ? room.canonicalAlias : i18n("No Canonical Alias")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.ListSectionHeader {
|
||||
label: i18n("Members")
|
||||
activeFocusOnTab: false
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: room ? i18np("%1 Member", "%1 Members", room.totalMemberCount) : i18n("No Member Count")
|
||||
TextEdit {
|
||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 13
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 13
|
||||
Layout.fillWidth: true
|
||||
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||
readonly property var replaceLinks: /\(https:\/\/[^ ]*\)/
|
||||
textFormat: TextEdit.MarkdownText
|
||||
wrapMode: Text.WordWrap
|
||||
selectByMouse: true
|
||||
color: Kirigami.Theme.textColor
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
readOnly: true
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Pane {
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
implicitWidth: parent.width
|
||||
z: 2
|
||||
contentItem: Kirigami.SearchField {
|
||||
id: userListSearchField
|
||||
onAccepted: sortedMessageEventModel.filterString = text;
|
||||
}
|
||||
Kirigami.ListSectionHeader {
|
||||
label: i18n("Members")
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: room ? i18np("%1 Member", "%1 Members", room.totalMemberCount) : i18n("No Member Count")
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ListView {
|
||||
id: userListView
|
||||
clip: true
|
||||
headerPositioning: ListView.OverlayHeader
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
activeFocusOnTab: true
|
||||
ListView {
|
||||
id: userListView
|
||||
clip: true
|
||||
headerPositioning: ListView.OverlayHeader
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
|
||||
model: KSortFilterProxyModel {
|
||||
id: sortedMessageEventModel
|
||||
header: Pane {
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
implicitWidth: parent.width
|
||||
z: 2
|
||||
contentItem: Kirigami.SearchField {
|
||||
id: userListSearchField
|
||||
onTextChanged: sortedMessageEventModel.filterString = text;
|
||||
}
|
||||
}
|
||||
|
||||
sourceModel: UserListModel {
|
||||
room: roomDrawer.room
|
||||
}
|
||||
model: KSortFilterProxyModel {
|
||||
id: sortedMessageEventModel
|
||||
|
||||
sortRole: "perm"
|
||||
filterRole: "name"
|
||||
sourceModel: UserListModel {
|
||||
room: roomDrawer.room
|
||||
}
|
||||
|
||||
delegate: Kirigami.AbstractListItem {
|
||||
width: userListView.width
|
||||
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||
z: 1
|
||||
sortRole: "perm"
|
||||
filterRole: "name"
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
visible: Config.showAvatarInTimeline
|
||||
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
source: avatar ? ("image://mxc/" + avatar) : ""
|
||||
name: name
|
||||
}
|
||||
delegate: Kirigami.AbstractListItem {
|
||||
width: userListView.width
|
||||
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||
z: 1
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
contentItem: RowLayout {
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: height
|
||||
Layout.fillHeight: true
|
||||
|
||||
text: name
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
source: avatar ? ("image://mxc/" + avatar) : ""
|
||||
name: name
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: perm != UserType.Member
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: {
|
||||
if (perm == UserType.Owner) {
|
||||
return i18n("Owner")
|
||||
}
|
||||
if (perm == UserType.Admin) {
|
||||
return i18n("Admin")
|
||||
}
|
||||
if (perm == UserType.Moderator) {
|
||||
return i18n("Mod")
|
||||
}
|
||||
if (perm == UserType.Muted) {
|
||||
return i18n("Muted")
|
||||
}
|
||||
return ""
|
||||
text: name
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: perm != UserType.Member
|
||||
|
||||
text: {
|
||||
if (perm == UserType.Owner) {
|
||||
return i18n("Owner")
|
||||
}
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
font.pixelSize: 12
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.NoWrap
|
||||
if (perm == UserType.Admin) {
|
||||
return i18n("Admin")
|
||||
}
|
||||
if (perm == UserType.Moderator) {
|
||||
return i18n("Mod")
|
||||
}
|
||||
if (perm == UserType.Muted) {
|
||||
return i18n("Muted")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
font.pixelSize: 12
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
|
||||
action: Kirigami.Action {
|
||||
onTriggered: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": room, "user": user, "displayName": name, "avatarMediaId": avatar}).open()
|
||||
}
|
||||
action: Kirigami.Action {
|
||||
onTriggered: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": room, "user": user, "displayName": name, "avatarMediaId": avatar}).open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
imports/NeoChat/Setting/Palette.qml
Normal file
19
imports/NeoChat/Setting/Palette.qml
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
pragma Singleton
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
|
||||
QtObject {
|
||||
readonly property int theme: MSettings.darkTheme ? Material.Dark : Material.Light
|
||||
|
||||
readonly property color primary: "#344955"
|
||||
readonly property color accent: "#4286F5"
|
||||
readonly property color foreground: MSettings.darkTheme ? "#FFFFFF" : "#1D333E"
|
||||
readonly property color background: MSettings.darkTheme ? "#303030" : "#FFFFFF"
|
||||
readonly property color lighter: MSettings.darkTheme ? "#FFFFFF" : "#5B7480"
|
||||
readonly property color banner: MSettings.darkTheme ? "#404040" : "#F2F3F4"
|
||||
}
|
||||
18
imports/NeoChat/Setting/Setting.qml
Normal file
18
imports/NeoChat/Setting/Setting.qml
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
pragma Singleton
|
||||
import QtQuick 2.12
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
Settings {
|
||||
property bool showNotification: true
|
||||
|
||||
property bool showTray: true
|
||||
|
||||
property bool darkTheme
|
||||
|
||||
property string fontFamily: "Roboto,Noto Sans,Noto Color Emoji"
|
||||
}
|
||||
3
imports/NeoChat/Setting/qmldir
Normal file
3
imports/NeoChat/Setting/qmldir
Normal file
@@ -0,0 +1,3 @@
|
||||
module NeoChat.Setting
|
||||
singleton MSettings 1.0 Setting.qml
|
||||
singleton MPalette 1.0 Palette.qml
|
||||
@@ -1,38 +1,35 @@
|
||||
[Global]
|
||||
IconName=org.kde.neochat
|
||||
Name=NeoChat
|
||||
Name[az]=NeoChat
|
||||
Name[ca]=NeoChat
|
||||
Name[ca@valencia]=NeoChat
|
||||
Name[cs]=NeoChat
|
||||
Name[de]=NeoChat
|
||||
Name[en_GB]=NeoChat
|
||||
Name[es]=NeoChat
|
||||
Name[eu]=NeoChat
|
||||
Name[fi]=NeoChat
|
||||
Name[fr]=NeoChat
|
||||
Name=Neochat
|
||||
Name[ca]=Neochat
|
||||
Name[ca@valencia]=Neochat
|
||||
Name[cs]=Neochat
|
||||
Name[da]=Neochat
|
||||
Name[de]=Neochat
|
||||
Name[en_GB]=Neochat
|
||||
Name[es]=Neochat
|
||||
Name[eu]=Neochat
|
||||
Name[fi]=Neochat
|
||||
Name[fr]=Neochat
|
||||
Name[hu]=Neochat
|
||||
Name[ia]=Neochat
|
||||
Name[it]=NeoChat
|
||||
Name[ko]=NeoChat
|
||||
Name[nl]=NeoChat
|
||||
Name[nn]=NeoChat
|
||||
Name[pa]=ਨਿਓ-ਚੈਟ
|
||||
Name[pl]=NeoChat
|
||||
Name[pt_BR]=NeoChat
|
||||
Name[ro]=NeoChat
|
||||
Name[sk]=NeoChat
|
||||
Name[sl]=NeoChat
|
||||
Name[sv]=NeoChat
|
||||
Name[ta]=நியோச்சாட்
|
||||
Name[uk]=NeoChat
|
||||
Name[x-test]=xxNeoChatxx
|
||||
Name[zh_CN]=NeoChat
|
||||
Name[it]=Neochat
|
||||
Name[nl]=Neochat
|
||||
Name[nn]=Neochat
|
||||
Name[pl]=Neochat
|
||||
Name[pt]=Neochat
|
||||
Name[pt_BR]=Neochat
|
||||
Name[ro]=Neochat
|
||||
Name[sk]=Neochat
|
||||
Name[sl]=Neochat
|
||||
Name[sv]=Neochat
|
||||
Name[uk]=Neochat
|
||||
Name[x-test]=xxNeochatxx
|
||||
Name[zh_CN]=Neochat
|
||||
DesktopEntry=org.kde.neochat
|
||||
Comment=A client for matrix, the decentralized communication protocol
|
||||
Comment[az]=Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu
|
||||
Comment[ca]=Un client per al 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[en_GB]=A client for matrix, the decentralised communication protocol
|
||||
Comment[es]=Un cliente para Matrix, el protocolo de comunicaciones descentralizado
|
||||
@@ -41,14 +38,11 @@ Comment[fi]=Hajautetun Matrix-viestintäyhteyskäytännön asiakasohjelma
|
||||
Comment[fr]=Un client pour « Matrix », le protocole décentralisé de communications.
|
||||
Comment[ia]=Un cliente per matrix, le protocollo de communication decentralisate
|
||||
Comment[it]=Un client per matrix, il protocollo di comunicazione decentralizzato
|
||||
Comment[ko]=Matrix, 분산 대화 프로토콜 클라이언트
|
||||
Comment[nl]=Een client voor matrix, het gedecentraliseerde communicatieprotocol
|
||||
Comment[nn]=Klient for Matrix, den desentraliserte lynmeldingsprotokollen.
|
||||
Comment[pa]=ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||
Comment[pl]=Program do obsługi matriksa, rozproszonego protokołu porozumiewania się
|
||||
Comment[pt_BR]=Um cliente para o Matrix, o protocolo de comunicação decentralizado
|
||||
Comment[ro]=Client pentru Matrix, protocolul de comunicare descentralizată
|
||||
Comment[sk]=Klient pre matrix, decentralizovaný komunikačný protokol
|
||||
Comment[sl]=Odjemalec za decentralizirani komunikacijski protokol matrix
|
||||
Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokollet
|
||||
Comment[uk]=Клієнт matrix, децентралізованого протоколу обміну даними
|
||||
@@ -58,7 +52,6 @@ Comment[zh_CN]=分布式通讯协议 Matrix 的客户端
|
||||
|
||||
[Event/message]
|
||||
Name=New message
|
||||
Name[az]=Yeni ismarıc
|
||||
Name[ca]=Missatge nou
|
||||
Name[ca@valencia]=Missatge nou
|
||||
Name[cs]=Nová zpráva
|
||||
@@ -71,10 +64,8 @@ Name[fr]=Nouveau message
|
||||
Name[hu]=Új üzenet
|
||||
Name[ia]=Nove message
|
||||
Name[it]=Nuovo messaggio
|
||||
Name[ko]=새 메시지
|
||||
Name[nl]=Nieuw bericht
|
||||
Name[nn]=Ny melding
|
||||
Name[pa]=ਨਵਾਂ ਸੁਨੇਹਾ
|
||||
Name[pl]=Nowa wiadomość
|
||||
Name[pt]=Nova mensagem
|
||||
Name[pt_BR]=Nova mensagem
|
||||
@@ -82,12 +73,10 @@ Name[ro]=Mesaj nou
|
||||
Name[sk]=Nová správa
|
||||
Name[sl]=Novo sporočilo
|
||||
Name[sv]=Nytt meddelande
|
||||
Name[ta]=புதிய செய்தி
|
||||
Name[uk]=Нове повідомлення
|
||||
Name[x-test]=xxNew messagexx
|
||||
Name[zh_CN]=新消息
|
||||
Comment=There is a new message
|
||||
Comment[az]=Yeni ismarıc var
|
||||
Comment[ca]=Hi ha un missatge nou
|
||||
Comment[ca@valencia]=Hi ha un missatge nou
|
||||
Comment[de]=Es ist eine neue Nachricht vorhanden
|
||||
@@ -99,10 +88,8 @@ Comment[fr]=Il y a un nouveau message
|
||||
Comment[hu]=Új üzenet érkezett
|
||||
Comment[ia]=Isto es un nove message
|
||||
Comment[it]=È presente un nuovo messaggio
|
||||
Comment[ko]=새 메시지가 있음
|
||||
Comment[nl]=Er is een nieuw bericht
|
||||
Comment[nn]=Du har ei ny melding
|
||||
Comment[pa]=ਨਵਾਂ ਸੁਨੇਹਾ ਹੈ
|
||||
Comment[pl]=Dostępna jest nowa wiadomość
|
||||
Comment[pt]=Tem uma mensagem nova
|
||||
Comment[pt_BR]=Existe uma nova mensagem
|
||||
@@ -110,7 +97,6 @@ Comment[ro]=Este un mesaj nou
|
||||
Comment[sk]=Je nová správa
|
||||
Comment[sl]=Prišlo je novo sporočilo
|
||||
Comment[sv]=Det finns ett nytt meddelande
|
||||
Comment[ta]=ஒரு புதிய செய்தி உள்ளது
|
||||
Comment[uk]=Надійшло нове повідомлення
|
||||
Comment[x-test]=xxThere is a new messagexx
|
||||
Comment[zh_CN]=有新消息
|
||||
|
||||
@@ -4,34 +4,32 @@
|
||||
<provides>
|
||||
<binary>neochat</binary>
|
||||
</provides>
|
||||
<name>NeoChat</name>
|
||||
<name xml:lang="az">NeoChat</name>
|
||||
<name xml:lang="ca">NeoChat</name>
|
||||
<name xml:lang="ca-valencia">NeoChat</name>
|
||||
<name xml:lang="cs">NeoChat</name>
|
||||
<name xml:lang="de">NeoChat</name>
|
||||
<name xml:lang="en-GB">NeoChat</name>
|
||||
<name xml:lang="es">NeoChat</name>
|
||||
<name xml:lang="eu">NeoChat</name>
|
||||
<name xml:lang="fi">NeoChat</name>
|
||||
<name xml:lang="fr">NeoChat</name>
|
||||
<name>Neochat</name>
|
||||
<name xml:lang="ca">Neochat</name>
|
||||
<name xml:lang="ca-valencia">Neochat</name>
|
||||
<name xml:lang="cs">Neochat</name>
|
||||
<name xml:lang="da">Neochat</name>
|
||||
<name xml:lang="de">Neochat</name>
|
||||
<name xml:lang="en-GB">Neochat</name>
|
||||
<name xml:lang="es">Neochat</name>
|
||||
<name xml:lang="eu">Neochat</name>
|
||||
<name xml:lang="fi">Neochat</name>
|
||||
<name xml:lang="fr">Neochat</name>
|
||||
<name xml:lang="hu">Neochat</name>
|
||||
<name xml:lang="ia">Neochat</name>
|
||||
<name xml:lang="it">NeoChat</name>
|
||||
<name xml:lang="ko">NeoChat</name>
|
||||
<name xml:lang="nl">NeoChat</name>
|
||||
<name xml:lang="nn">NeoChat</name>
|
||||
<name xml:lang="pa">ਨਿਓ-ਚੈਟ</name>
|
||||
<name xml:lang="pl">NeoChat</name>
|
||||
<name xml:lang="pt-BR">NeoChat</name>
|
||||
<name xml:lang="sk">NeoChat</name>
|
||||
<name xml:lang="sl">NeoChat</name>
|
||||
<name xml:lang="sv">NeoChat</name>
|
||||
<name xml:lang="ta">நியோச்சாட்</name>
|
||||
<name xml:lang="uk">NeoChat</name>
|
||||
<name xml:lang="x-test">xxNeoChatxx</name>
|
||||
<name xml:lang="zh-CN">NeoChat</name>
|
||||
<name xml:lang="id">Neochat</name>
|
||||
<name xml:lang="it">Neochat</name>
|
||||
<name xml:lang="nl">Neochat</name>
|
||||
<name xml:lang="nn">Neochat</name>
|
||||
<name xml:lang="pl">Neochat</name>
|
||||
<name xml:lang="pt">Neochat</name>
|
||||
<name xml:lang="pt-BR">Neochat</name>
|
||||
<name xml:lang="sk">Neochat</name>
|
||||
<name xml:lang="sl">Neochat</name>
|
||||
<name xml:lang="sv">Neochat</name>
|
||||
<name xml:lang="uk">Neochat</name>
|
||||
<name xml:lang="x-test">xxNeochatxx</name>
|
||||
<summary>A client for matrix, the decentralized communication protocol</summary>
|
||||
<summary xml:lang="az">Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu</summary>
|
||||
<summary xml:lang="ca">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="ca-valencia">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary>
|
||||
@@ -45,10 +43,8 @@
|
||||
<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="it">Un client per matrix, il protocollo di comunicazione decentralizzato</summary>
|
||||
<summary xml:lang="ko">Matrix, 분산 대화 프로토콜 클라이언트</summary>
|
||||
<summary xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol</summary>
|
||||
<summary xml:lang="nn">Ein klient for Matrix, den desentraliserte lynmeldingsprotokollen</summary>
|
||||
<summary xml:lang="pa">ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ</summary>
|
||||
<summary xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się</summary>
|
||||
<summary xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado</summary>
|
||||
<summary xml:lang="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado</summary>
|
||||
@@ -57,82 +53,38 @@
|
||||
<summary xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet</summary>
|
||||
<summary xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними</summary>
|
||||
<summary xml:lang="x-test">xxA client for matrix, the decentralized communication protocolxx</summary>
|
||||
<summary xml:lang="zh-CN">分布式通讯协议 Matrix 的客户端</summary>
|
||||
<description>
|
||||
<p>NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p>
|
||||
<p xml:lang="az">NeoChat Mtrix müştərisidir. O, Matrix protokolundan istifadə edərək, ailənizə, dostlarınıza, iş yoldaşlarınıza mətn, səsli və görüntülü ismarıclar göndərməyə imkan verir.</p>
|
||||
<p xml:lang="ca">El NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
|
||||
<p xml:lang="ca-valencia">El NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
|
||||
<p xml:lang="de">NeoChat ist ein Matrix-Client. Er ermöglicht Ihnen das Senden von Textnachrichten, Videos und Audiodateien an Ihre Familie, Kollegen und Freunde unter Verwendung des Matrix-Protokolls.</p>
|
||||
<p xml:lang="en-GB">NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p>
|
||||
<p xml:lang="es">NeoChat es un cliente para Matrix. Le permite enviar mensajes de texto, vídeos y archivos de sonido a su familia, compañeros de trabajo y amigos usando el protocolo Matrix.</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="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="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="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="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-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="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="sv">NeoChat är en Matrix-klient. Den låter dig skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner med användning av Matrix-protokollet.</p>
|
||||
<p xml:lang="uk">NeoChat — клієнт мережі обміну повідомленнями Matrix. За допомогою цієї програми ви зможете надсилати текстові повідомлення, відео та звукові файли вашій родині, колегам та друзям за допомогою протоколу Matrix.</p>
|
||||
<p xml:lang="x-test">xxNeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.xx</p>
|
||||
<p xml:lang="zh-CN">NeoChat 是一个 Matrix 客户端。 它允许您使用 Matrix 协议向您的家人、同事和朋友发送文本消息、视频和音频文件。</p>
|
||||
<p>Matrix is a decentralized communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
|
||||
<p xml:lang="az">Matrix, istifadəçini nəzarətdə saxlayan, mərkəzləşməmişi rabitə protokoludur. NeoChat, söhbətin və video əlaqəsinin şifrələnməsindən başqa bir çox protokolları həyata keçirə bilir.</p>
|
||||
<p xml:lang="ca">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="de">Matrix ist ein dezentralisiertes Kommunikationsprotokoll, das dem Benutzer wieder die Kontrolle zurückgibt. Derzeit implementiert NeoChat einen großen Teil des Protokolls mit der Ausnahme von verschlüsselten Chats und Video-Chat.</p>
|
||||
<p xml:lang="en-GB">Matrix is a decentralised communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</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="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 chiffrées et du chat vidéo.</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="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="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-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="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="sv">Matrix är ett decentraliserat kommunikationsprotokoll, som ger tillbaka kontrollen till användaren. För närvarande implementerar NeoChat en stor del av protokollet, med undantag för krypterad chatt och videochatt.</p>
|
||||
<p xml:lang="uk">Matrix — протокол децентралізованого спілкування, який передає контроль над даними користувачеві. У поточній версії NeoChat реалізовано більшу частину протоколу, окрім зашифрованого спілкування та відеоспілкування.</p>
|
||||
<p xml:lang="x-test">xxMatrix is a decentralized communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.xx</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 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 els mòbils i a l'escriptori, proporcionant una 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="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="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="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="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="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="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-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="sl">NeoChat deluje tako na mobilnih kot na namiznih platformah z zagotavljanjem konsistentne uporabniške izkušnje.</p>
|
||||
<p xml:lang="sv">NeoChat fungerar både på mobil och skrivbord och tillhandahåller en konsekvent användarupplevelse.</p>
|
||||
<p xml:lang="uk">NeoChat працює на мобільних пристроях та звичайних комп'ютерах, маючи однорідний інтерфейс на усіх підтримуваних пристроях.</p>
|
||||
<p xml:lang="x-test">xxNeoChat works both on mobile and desktop while providing a consistent user experience.xx</p>
|
||||
<p xml:lang="zh-CN">NeoChat 在移动设备和桌面上均可用,并提供一致的用户体验。</p>
|
||||
<p>A client for matrix, the decentralized communication protocol.</p>
|
||||
<p xml:lang="ca">Un client per al Matrix, el protocol de comunicacions descentralitzat.</p>
|
||||
<p xml:lang="ca-valencia">Un client per al Matrix, el protocol de comunicacions descentralitzat.</p>
|
||||
<p xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix.</p>
|
||||
<p xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll.</p>
|
||||
<p xml:lang="en-GB">A client for matrix, the decentralised communication protocol.</p>
|
||||
<p xml:lang="es">Un cliente para Matrix, el protocolo de comunicaciones descentralizado.</p>
|
||||
<p xml:lang="eu">Matrix, deszentralizatutako komunikazio protokolorako bezero bat.</p>
|
||||
<p xml:lang="fi">Asiakas Matrixille, hajautetulle viestintäyhteyskäytännölle.</p>
|
||||
<p xml:lang="fr">Un client « Matrix », le protocole décentralisé de communications.</p>
|
||||
<p xml:lang="hu">Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz.</p>
|
||||
<p xml:lang="ia">Un cliente per matrix, le protocollo de communication decentralisate.</p>
|
||||
<p xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi.</p>
|
||||
<p xml:lang="it">Un client per matrix, il protocollo di comunicazione decentralizzato.</p>
|
||||
<p xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol.</p>
|
||||
<p xml:lang="nn">Ein klient for Matrix, den desentraliserte lynmeldingsprotokollen.</p>
|
||||
<p xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się.</p>
|
||||
<p xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado.</p>
|
||||
<p xml:lang="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado.</p>
|
||||
<p xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol.</p>
|
||||
<p xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol.</p>
|
||||
<p xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet.</p>
|
||||
<p xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними.</p>
|
||||
<p xml:lang="x-test">xxA client for matrix, the decentralized communication protocol.xx</p>
|
||||
</description>
|
||||
<url type="homepage">https://apps.kde.org/neochat/</url>
|
||||
<url type="bugtracker">https://invent.kde.org/network/neochat/-/issues</url>
|
||||
<url type="homepage">https://kde.org</url>
|
||||
<url type="bugtracker">https://bugs.kde.org</url>
|
||||
<categories>
|
||||
<category>Network</category>
|
||||
</categories>
|
||||
<developer_name>The KDE Community</developer_name>
|
||||
<developer_name xml:lang="az">KDE Cəmiyyəti</developer_name>
|
||||
<developer_name xml:lang="ca">La comunitat KDE</developer_name>
|
||||
<developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name>
|
||||
<developer_name xml:lang="cs">Komunita KDE</developer_name>
|
||||
@@ -146,10 +98,8 @@
|
||||
<developer_name xml:lang="ia">Le communitate de KDE</developer_name>
|
||||
<developer_name xml:lang="id">Komunitas KDE</developer_name>
|
||||
<developer_name xml:lang="it">La comunità KDE</developer_name>
|
||||
<developer_name xml:lang="ko">KDE 커뮤니티</developer_name>
|
||||
<developer_name xml:lang="nl">De KDE gemeenschap</developer_name>
|
||||
<developer_name xml:lang="nn">KDE-fellesskapet</developer_name>
|
||||
<developer_name xml:lang="pa">ਕੇਡੀਈ ਕਮਿਊਨਟੀ</developer_name>
|
||||
<developer_name xml:lang="pl">Społeczność KDE</developer_name>
|
||||
<developer_name xml:lang="pt">A Comunidade do KDE</developer_name>
|
||||
<developer_name xml:lang="pt-BR">A comunidade KDE</developer_name>
|
||||
@@ -158,30 +108,21 @@
|
||||
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
|
||||
<developer_name xml:lang="uk">Спільнота KDE</developer_name>
|
||||
<developer_name xml:lang="x-test">xxThe KDE Communityxx</developer_name>
|
||||
<developer_name xml:lang="zh-CN">KDE 社区</developer_name>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<value key="KDE::matrix">#neochat:kde.org</value>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/application-mobile.png</image>
|
||||
</screenshot>
|
||||
<screenshot type="default">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/application.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://cdn.kde.org/screenshots/neochat/application-mobile.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="1.2.0" date="2021-06-01">
|
||||
<description>
|
||||
<p>NeoChat 1.2 brings a major redesign of the user interface. The chat page is now using bubbles for the messages and the input component was completely rewritten with a nicer look as well.</p>
|
||||
<p>It's now possible to send custom reactions by replying to a comment with /react <message>.</p>
|
||||
<p>NeoChat now supports opening Matrix URIs from your browser.</p>
|
||||
</description>
|
||||
<url>https://carlschwan.eu/2021/06/01/neochat-1.2/</url>
|
||||
</release>
|
||||
<release urgency="critical" version="1.1.1" date="2021-02-23"/>
|
||||
<release version="1.1.0" date="2021-02-22">
|
||||
<description>
|
||||
@@ -191,7 +132,7 @@
|
||||
<p>We added a few commands to NeoChat (/shrug, /lenny, /join, /ignore, ...).</p>
|
||||
<p>We improved the Plasma integration a bit. Now the number of unread messages is displayed in the Plasma Taskbar.</p>
|
||||
</description>
|
||||
<url>https://carlschwan.eu/2021/02/22/neochat-1.1/</url>
|
||||
<url>https://carlschwan.eu/2020/02/22/neochat-1.1/</url>
|
||||
</release>
|
||||
<release version="1.0.1" date="2021-01-13">
|
||||
<description>
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
[Desktop Entry]
|
||||
Name=NeoChat
|
||||
Name[az]=NeoChat
|
||||
Name[ca]=NeoChat
|
||||
Name[ca@valencia]=NeoChat
|
||||
Name[cs]=NeoChat
|
||||
Name[de]=NeoChat
|
||||
Name[en_GB]=NeoChat
|
||||
Name[es]=NeoChat
|
||||
Name[eu]=NeoChat
|
||||
Name[fi]=NeoChat
|
||||
Name[fr]=NeoChat
|
||||
Name=Neochat
|
||||
Name[ca]=Neochat
|
||||
Name[ca@valencia]=Neochat
|
||||
Name[cs]=Neochat
|
||||
Name[da]=Neochat
|
||||
Name[de]=Neochat
|
||||
Name[en_GB]=Neochat
|
||||
Name[es]=Neochat
|
||||
Name[eu]=Neochat
|
||||
Name[fi]=Neochat
|
||||
Name[fr]=Neochat
|
||||
Name[hu]=Neochat
|
||||
Name[ia]=Neochat
|
||||
Name[it]=NeoChat
|
||||
Name[ko]=NeoChat
|
||||
Name[nl]=NeoChat
|
||||
Name[nn]=NeoChat
|
||||
Name[pa]=ਨਿਓ-ਚੈਟ
|
||||
Name[pl]=NeoChat
|
||||
Name[pt_BR]=NeoChat
|
||||
Name[ro]=NeoChat
|
||||
Name[sk]=NeoChat
|
||||
Name[sl]=NeoChat
|
||||
Name[sv]=NeoChat
|
||||
Name[ta]=நியோச்சாட்
|
||||
Name[uk]=NeoChat
|
||||
Name[x-test]=xxNeoChatxx
|
||||
Name[zh_CN]=NeoChat
|
||||
Name[it]=Neochat
|
||||
Name[nl]=Neochat
|
||||
Name[nn]=Neochat
|
||||
Name[pl]=Neochat
|
||||
Name[pt]=Neochat
|
||||
Name[pt_BR]=Neochat
|
||||
Name[ro]=Neochat
|
||||
Name[sk]=Neochat
|
||||
Name[sl]=Neochat
|
||||
Name[sv]=Neochat
|
||||
Name[uk]=Neochat
|
||||
Name[x-test]=xxNeochatxx
|
||||
Name[zh_CN]=Neochat
|
||||
GenericName=Matrix Client
|
||||
GenericName[az]=Matrix Müştərisi
|
||||
GenericName[ca]=Client del Matrix
|
||||
GenericName[ca@valencia]=Client del Matrix
|
||||
GenericName[cs]=Klient protokolu Matrix
|
||||
@@ -40,10 +38,8 @@ GenericName[fr]=Client « Matrix »
|
||||
GenericName[hu]=Matrix kliens
|
||||
GenericName[ia]=Cliente de Matrix
|
||||
GenericName[it]=Client Matrix
|
||||
GenericName[ko]=Matrix 클라이언트
|
||||
GenericName[nl]=Matrix-client
|
||||
GenericName[nn]=Matrix-klient
|
||||
GenericName[pa]=ਮੈਟਰਿਕਸ ਕਲਾਈਂਟ
|
||||
GenericName[pl]=Program Matriksa
|
||||
GenericName[pt]=Cliente de Matrix
|
||||
GenericName[pt_BR]=Cliente Matrix
|
||||
@@ -51,12 +47,10 @@ GenericName[ro]=Client Matrix
|
||||
GenericName[sk]=Matrix Client
|
||||
GenericName[sl]=Odjemalec Matrix
|
||||
GenericName[sv]=Matrix-klient
|
||||
GenericName[ta]=Matrix வாங்கி
|
||||
GenericName[uk]=Клієнт Matrix
|
||||
GenericName[x-test]=xxMatrix Clientxx
|
||||
GenericName[zh_CN]=Matrix 客户端
|
||||
Comment=Client for the Matrix protocol
|
||||
Comment[az]=Matrix protokolu üçün müştəri
|
||||
Comment[ca]=Client per al protocol Matrix
|
||||
Comment[ca@valencia]=Client per al protocol Matrix
|
||||
Comment[de]=Programm für das Matrix-Protokoll
|
||||
@@ -68,10 +62,8 @@ Comment[fr]=Client pour le protocole « Matrix »
|
||||
Comment[hu]=Kliens a Matrix protokollhoz
|
||||
Comment[ia]=Cliente per le protocollo de Matrix
|
||||
Comment[it]=Client per il protocollo Matrix
|
||||
Comment[ko]=Matrix 프로토콜용 클라이언트
|
||||
Comment[nl]=Client voor het Matrix-protocol
|
||||
Comment[nn]=Lynmeldingsklient for Matrix-protokollen
|
||||
Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||
Comment[pl]=Program obsługi protokołu Matriksa
|
||||
Comment[pt]=Cliente para o protocolo Matrix
|
||||
Comment[pt_BR]=Cliente para o protocolo Matrix
|
||||
@@ -79,12 +71,10 @@ Comment[ro]=Client pentru protocolul Matrix
|
||||
Comment[sk]=Klient protokolu Matrix
|
||||
Comment[sl]=Odjemalec za protokol Matrix
|
||||
Comment[sv]=Klient för protokollet Matrix
|
||||
Comment[ta]=Matrix நெறிமுறைக்கான வாங்கி
|
||||
Comment[uk]=Клієнт протоколу Matrix
|
||||
Comment[x-test]=xxClient for the Matrix protocolxx
|
||||
Comment[zh_CN]=为 Matrix 协议打造的客户端
|
||||
MimeType=x-scheme-handler/matrix;
|
||||
Exec=neochat %u
|
||||
Exec=neochat
|
||||
Terminal=false
|
||||
Icon=org.kde.neochat
|
||||
Type=Application
|
||||
|
||||
262
qml/main.qml
262
qml/main.qml
@@ -1,18 +1,21 @@
|
||||
// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as QQC2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
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.kirigami 2.12 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Panel 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Page 1.0
|
||||
import NeoChat.Panel 1.0
|
||||
|
||||
Kirigami.ApplicationWindow {
|
||||
id: root
|
||||
@@ -28,8 +31,6 @@ Kirigami.ApplicationWindow {
|
||||
|
||||
pageStack.initialPage: LoadingPage {}
|
||||
|
||||
property bool roomListLoaded: false
|
||||
|
||||
Connections {
|
||||
target: root.quitAction
|
||||
function onTriggered() {
|
||||
@@ -52,79 +53,86 @@ Kirigami.ApplicationWindow {
|
||||
onXChanged: saveWindowGeometryTimer.restart()
|
||||
onYChanged: saveWindowGeometryTimer.restart()
|
||||
|
||||
/**
|
||||
* Manage opening and close rooms
|
||||
* TODO this should probably be moved to C++
|
||||
*/
|
||||
QtObject {
|
||||
id: roomManager
|
||||
|
||||
/// Setup keyboard navigation to the room page.
|
||||
function connectRoomToSignal(item) {
|
||||
if (!roomListLoaded) {
|
||||
console.log("Should not happen: no room list page but room page");
|
||||
property var actionsHandler: ActionsHandler {
|
||||
room: roomManager.currentRoom
|
||||
connection: Controller.activeConnection
|
||||
onRoomJoined: {
|
||||
roomManager.enterRoom(Controller.activeConnection.room(roomName))
|
||||
}
|
||||
}
|
||||
const roomList = pageStack.get(0);
|
||||
item.switchRoomUp.connect(function() {
|
||||
roomList.goToNextRoom();
|
||||
});
|
||||
|
||||
item.switchRoomDown.connect(function() {
|
||||
roomList.goToPreviousRoom();
|
||||
});
|
||||
item.forceActiveFocus();
|
||||
item.KeyNavigation.left = pageStack.get(0);
|
||||
}
|
||||
property var currentRoom: null
|
||||
property alias pageStack: root.pageStack
|
||||
property var roomList: null
|
||||
property Item roomItem: null
|
||||
|
||||
Connections {
|
||||
target: RoomManager
|
||||
readonly property bool hasOpenRoom: currentRoom !== null
|
||||
|
||||
function onPushRoom(room, event) {
|
||||
const roomItem = pageStack.push("qrc:/imports/NeoChat/Page/RoomPage.qml");
|
||||
signal leaveRoom(string room);
|
||||
signal openRoom(string room);
|
||||
|
||||
function roomByAliasOrId(aliasOrId) {
|
||||
return Controller.activeConnection.room(aliasOrId)
|
||||
}
|
||||
|
||||
function openRoomAndEvent(room, event) {
|
||||
enterRoom(room)
|
||||
roomItem.goToEvent(event)
|
||||
}
|
||||
|
||||
function loadInitialRoom() {
|
||||
if (Config.openRoom) {
|
||||
const room = Controller.activeConnection.room(Config.openRoom);
|
||||
currentRoom = room;
|
||||
roomItem = pageStack.push("qrc:/imports/NeoChat/Page/RoomPage.qml", { 'currentRoom': room, });
|
||||
connectRoomToSignal(roomItem);
|
||||
} else {
|
||||
// TODO create welcome page
|
||||
}
|
||||
}
|
||||
|
||||
function enterRoom(room) {
|
||||
if (currentRoom != null) {
|
||||
roomItem.currentRoom = room;
|
||||
pageStack.currentIndex = pageStack.depth - 1;
|
||||
} else {
|
||||
roomItem = pageStack.push("qrc:/imports/NeoChat/Page/RoomPage.qml", { 'currentRoom': room, });
|
||||
}
|
||||
currentRoom = room;
|
||||
Config.openRoom = room.id;
|
||||
Config.save();
|
||||
connectRoomToSignal(roomItem);
|
||||
if (event.length > 0) {
|
||||
roomItem.goToEvent(event);
|
||||
}
|
||||
return roomItem;
|
||||
}
|
||||
|
||||
function onReplaceRoom(room, event) {
|
||||
const roomItem = pageStack.get(pageStack.depth - 1);
|
||||
pageStack.currentIndex = pageStack.depth - 1;
|
||||
connectRoomToSignal(roomItem);
|
||||
if (event.length > 0) {
|
||||
roomItem.goToEvent(event);
|
||||
}
|
||||
function getBack() {
|
||||
pageStack.replace("qrc:/imports/NeoChat/Page/RoomPage.qml", { 'currentRoom': currentRoom, });
|
||||
}
|
||||
|
||||
function goToEvent(event) {
|
||||
if (event.length > 0) {
|
||||
roomItem.goToEvent(event);
|
||||
}
|
||||
roomItem.forceActiveFocus();
|
||||
}
|
||||
|
||||
function onPushWelcomePage() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
function onOpenRoomInNewWindow(room) {
|
||||
function openWindow(room) {
|
||||
const secondayWindow = roomWindow.createObject(applicationWindow(), {currentRoom: room});
|
||||
secondayWindow.width = root.width - pageStack.get(0).width;
|
||||
secondayWindow.width = root.width - roomList.width;
|
||||
secondayWindow.show();
|
||||
}
|
||||
|
||||
function onShowUserDetail(user) {
|
||||
const roomItem = pageStack.get(pageStack.depth - 1);
|
||||
roomItem.showUserDetail(user);
|
||||
}
|
||||
|
||||
function onAskDirectChatConfirmation(user) {
|
||||
askDirectChatConfirmationComponent.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
user: user,
|
||||
}).open();
|
||||
}
|
||||
|
||||
function onWarning(title, message) {
|
||||
if (RoomManager.currentRoom) {
|
||||
const roomItem = pageStack.get(pageStack.depth - 1);
|
||||
roomItem.warning(title, message);
|
||||
} else {
|
||||
showPassiveNotification(i18n("Warning: %1", message));
|
||||
function connectRoomToSignal(item) {
|
||||
if (!roomList) {
|
||||
console.log("Should not happen: no room list page but room page");
|
||||
}
|
||||
item.switchRoomUp.connect(function() {
|
||||
roomList.goToNextRoom();
|
||||
});
|
||||
|
||||
item.switchRoomDown.connect(function() {
|
||||
roomList.goToPreviousRoom();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,12 +157,11 @@ Kirigami.ApplicationWindow {
|
||||
modal: !root.wideScreen || !enabled
|
||||
onEnabledChanged: drawerOpen = enabled && !modal
|
||||
onModalChanged: drawerOpen = !modal
|
||||
enabled: RoomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3
|
||||
enabled: roomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3
|
||||
room: roomManager.currentRoom
|
||||
handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3
|
||||
}
|
||||
|
||||
pageStack.columnView.columnWidth: Kirigami.Units.gridUnit * 17
|
||||
|
||||
globalDrawer: Kirigami.GlobalDrawer {
|
||||
property bool hasLayer
|
||||
contentItem.implicitWidth: columnWidth
|
||||
@@ -192,7 +199,7 @@ Kirigami.ApplicationWindow {
|
||||
Kirigami.Action {
|
||||
text: i18n("Devices")
|
||||
iconName: "network-connect"
|
||||
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/DevicesPage.qml")
|
||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/DevicesPage.qml")
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Devices") && Controller.accountCount > 0
|
||||
},
|
||||
Kirigami.Action {
|
||||
@@ -203,7 +210,7 @@ Kirigami.ApplicationWindow {
|
||||
shortcut: StandardKey.Preferences
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("About NeoChat")
|
||||
text: i18n("About Neochat")
|
||||
icon.name: "help-about"
|
||||
onTriggered: pushReplaceLayer(aboutPage)
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("About")
|
||||
@@ -234,16 +241,13 @@ Kirigami.ApplicationWindow {
|
||||
id: roomListComponent
|
||||
RoomListPage {
|
||||
id: roomList
|
||||
activeConnection: Controller.activeConnection
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onInitialSyncFinished() {
|
||||
pageStack.replace(roomListComponent, {
|
||||
activeConnection: Controller.activeConnection
|
||||
});
|
||||
roomListLoaded = true;
|
||||
RoomManager.loadInitialRoom();
|
||||
roomManager.roomList = pageStack.replace(roomListComponent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,44 +255,47 @@ Kirigami.ApplicationWindow {
|
||||
target: Controller
|
||||
|
||||
function onInitiated() {
|
||||
if (RoomManager.hasOpenRoom) {
|
||||
if (roomManager.hasOpenRoom) {
|
||||
return;
|
||||
}
|
||||
if (Controller.accountCount === 0) {
|
||||
pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml", {});
|
||||
} else {
|
||||
pageStack.replace(roomListComponent, {
|
||||
activeConnection: Controller.activeConnection
|
||||
});
|
||||
roomListLoaded = true;
|
||||
RoomManager.loadInitialRoom();
|
||||
roomManager.roomList = pageStack.replace(roomListComponent, {'activeConnection': Controller.activeConnection});
|
||||
roomManager.loadInitialRoom();
|
||||
}
|
||||
}
|
||||
|
||||
function onBusyChanged() {
|
||||
if(!Controller.busy && roomListLoaded === false) {
|
||||
pageStack.replace(roomListComponent);
|
||||
roomListLoaded = true;
|
||||
if(!Controller.busy && roomManager.roomList === null) {
|
||||
roomManager.roomList = pageStack.replace(roomListComponent);
|
||||
}
|
||||
}
|
||||
|
||||
function onRoomJoined(roomId) {
|
||||
const room = Controller.activeConnection.room(roomId);
|
||||
return roomManager.enterRoom(room);
|
||||
}
|
||||
|
||||
function onConnectionDropped() {
|
||||
if (Controller.accountCount === 0) {
|
||||
RoomManager.reset();
|
||||
pageStack.clear();
|
||||
roomListLoaded = false;
|
||||
pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml");
|
||||
}
|
||||
}
|
||||
|
||||
function onGlobalErrorOccured(error, detail) {
|
||||
showPassiveNotification(i18nc("%1: %2", error, detail));
|
||||
showPassiveNotification(error + ": " + detail)
|
||||
}
|
||||
|
||||
function onShowWindow() {
|
||||
root.showWindow()
|
||||
}
|
||||
|
||||
function onOpenRoom(room) {
|
||||
roomManager.enterRoom(room)
|
||||
}
|
||||
|
||||
function onUserConsentRequired(url) {
|
||||
consentSheet.url = url
|
||||
consentSheet.open()
|
||||
@@ -298,7 +305,7 @@ Kirigami.ApplicationWindow {
|
||||
Connections {
|
||||
target: Controller.activeConnection
|
||||
onDirectChatAvailable: {
|
||||
RoomManager.enterRoom(Controller.activeConnection.room(directChat.id));
|
||||
roomManager.enterRoom(Controller.activeConnection.room(directChat.id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,51 +342,36 @@ Kirigami.ApplicationWindow {
|
||||
RoomWindow {}
|
||||
}
|
||||
|
||||
function handleLink(link, currentRoom) {
|
||||
if (link.startsWith("https://matrix.to/")) {
|
||||
var content = link.replace("https://matrix.to/#/", "").replace(/\?.*/, "")
|
||||
if(content.match("^[#!]")) {
|
||||
if(content.includes("/")) {
|
||||
var result = content.match("([!#].*:.*)/(\\$.*)")
|
||||
if(!result) {
|
||||
return
|
||||
}
|
||||
if(result[1] == currentRoom.id) {
|
||||
roomManager.roomItem.goToEvent(result[2])
|
||||
} else {
|
||||
roomManager.openRoomAndEvent(roomManager.roomByAliasOrId(result[1]), result[2])
|
||||
}
|
||||
} else {
|
||||
roomManager.enterRoom(roomManager.roomByAliasOrId(content))
|
||||
}
|
||||
} else if(content.match("^@")) {
|
||||
let dialog = userDialog.createObject(root.overlay, {room: currentRoom, user: currentRoom.user(content)})
|
||||
dialog.open()
|
||||
console.log(dialog.user)
|
||||
}
|
||||
} else {
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userDialog
|
||||
UserDetailDialog {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: askDirectChatConfirmationComponent
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: askDirectChatConfirmation
|
||||
required property var user;
|
||||
|
||||
parent: QQC2.ApplicationWindow.overlay
|
||||
header: Kirigami.Heading {
|
||||
text: i18n("Start a chat")
|
||||
}
|
||||
contentItem: QQC2.Label {
|
||||
text: i18n("Do you want to start a chat with %1?", user.displayName)
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
footer: QQC2.DialogButtonBox {
|
||||
standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel
|
||||
onAccepted: {
|
||||
user.requestDirectChat();
|
||||
askDirectChatConfirmation.close();
|
||||
}
|
||||
onRejected: askDirectChatConfirmation.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Item hoverLinkIndicator: QQC2.Control {
|
||||
parent: overlay.parent
|
||||
property alias text: linkText.text
|
||||
opacity: text.length > 0 ? 1 : 0
|
||||
|
||||
z: 20
|
||||
x: 0
|
||||
y: parent.height - implicitHeight
|
||||
contentItem: QQC2.Label {
|
||||
id: linkText
|
||||
}
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
UserDetailDialog {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
res.qrc
18
res.qrc
@@ -15,19 +15,13 @@
|
||||
<file>imports/NeoChat/Page/DevicesPage.qml</file>
|
||||
<file>imports/NeoChat/Page/WelcomePage.qml</file>
|
||||
<file>imports/NeoChat/Component/qmldir</file>
|
||||
<file>imports/NeoChat/Component/ChatTextInput.qml</file>
|
||||
<file>imports/NeoChat/Component/AutoMouseArea.qml</file>
|
||||
<file>imports/NeoChat/Component/FullScreenImage.qml</file>
|
||||
<file>imports/NeoChat/Component/FancyEffectsContainer.qml</file>
|
||||
<file>imports/NeoChat/Component/TypingPane.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/ChatBox.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/ChatBar.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/AttachmentPane.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/ReplyPane.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/CompletionMenu.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/qmldir</file>
|
||||
<file>imports/NeoChat/Component/Emoji/EmojiPicker.qml</file>
|
||||
<file>imports/NeoChat/Component/Emoji/qmldir</file>
|
||||
<file>imports/NeoChat/Component/Timeline/qmldir</file>
|
||||
<file>imports/NeoChat/Component/Timeline/MessageDelegate.qml</file>
|
||||
<file>imports/NeoChat/Component/Timeline/ReplyComponent.qml</file>
|
||||
<file>imports/NeoChat/Component/Timeline/StateDelegate.qml</file>
|
||||
<file>imports/NeoChat/Component/Timeline/TextDelegate.qml</file>
|
||||
@@ -47,6 +41,9 @@
|
||||
<file>imports/NeoChat/Component/Login/Homeserver.qml</file>
|
||||
<file>imports/NeoChat/Component/Login/LoginMethod.qml</file>
|
||||
<file>imports/NeoChat/Component/Login/Sso.qml</file>
|
||||
<file>imports/NeoChat/Setting/Setting.qml</file>
|
||||
<file>imports/NeoChat/Setting/qmldir</file>
|
||||
<file>imports/NeoChat/Setting/Palette.qml</file>
|
||||
<file>imports/NeoChat/Panel/qmldir</file>
|
||||
<file>imports/NeoChat/Panel/RoomDrawer.qml</file>
|
||||
<file>imports/NeoChat/Dialog/qmldir</file>
|
||||
@@ -55,6 +52,7 @@
|
||||
<file>imports/NeoChat/Dialog/CreateRoomDialog.qml</file>
|
||||
<file>imports/NeoChat/Dialog/EmojiDialog.qml</file>
|
||||
<file>imports/NeoChat/Dialog/OpenFileDialog.qml</file>
|
||||
<file>imports/NeoChat/Dialog/OpenFolderDialog.qml</file>
|
||||
<file>imports/NeoChat/Menu/qmldir</file>
|
||||
<file>imports/NeoChat/Menu/Timeline/qmldir</file>
|
||||
<file>imports/NeoChat/Menu/Timeline/MessageDelegateContextMenu.qml</file>
|
||||
@@ -62,7 +60,5 @@
|
||||
<file>imports/NeoChat/Menu/Timeline/MessageSourceSheet.qml</file>
|
||||
<file>imports/NeoChat/Menu/RoomListContextMenu.qml</file>
|
||||
<file>qtquickcontrols2.conf</file>
|
||||
<file>imports/NeoChat/Component/glowdot.png</file>
|
||||
<file>imports/NeoChat/Component/confetti.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
# SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carl@carlschwan.eu>
|
||||
# SPDX-FileCopyrightText: 2020-2021 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
add_executable(neochat
|
||||
accountlistmodel.cpp
|
||||
controller.cpp
|
||||
@@ -13,7 +8,6 @@ add_executable(neochat
|
||||
messageeventmodel.cpp
|
||||
messagefiltermodel.cpp
|
||||
roomlistmodel.cpp
|
||||
roommanager.cpp
|
||||
neochatroom.cpp
|
||||
neochatuser.cpp
|
||||
userlistmodel.cpp
|
||||
@@ -28,15 +22,9 @@ add_executable(neochat
|
||||
filetypesingleton.cpp
|
||||
login.cpp
|
||||
stickerevent.cpp
|
||||
chatboxhelper.cpp
|
||||
commandmodel.cpp
|
||||
../res.qrc
|
||||
)
|
||||
|
||||
if(Quotient_VERSION_MINOR GREATER 6)
|
||||
target_compile_definitions(neochat PRIVATE QUOTIENT_07)
|
||||
endif()
|
||||
|
||||
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
|
||||
|
||||
target_sources(neochat PRIVATE ${NEOCHAT_ICON})
|
||||
@@ -46,7 +34,7 @@ if(NOT ANDROID)
|
||||
endif()
|
||||
|
||||
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})
|
||||
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)
|
||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
@@ -91,7 +79,7 @@ if(ANDROID)
|
||||
"network-connect"
|
||||
)
|
||||
else()
|
||||
target_link_libraries(neochat PRIVATE Qt5::Widgets)
|
||||
target_link_libraries(neochat PRIVATE Qt5::Widgets ${QTKEYCHAIN_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(TARGET KF5::DBusAddons)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
#include "accountlistmodel.h"
|
||||
|
||||
#include "room.h"
|
||||
@@ -20,6 +22,7 @@ AccountListModel::AccountListModel(QObject *parent)
|
||||
endInsertRows();
|
||||
});
|
||||
connect(&Controller::instance(), &Controller::connectionDropped, this, [=](Connection *conn) {
|
||||
qDebug() << "Dropping connection" << conn->userId();
|
||||
if (!conn) {
|
||||
qDebug() << "Trying to remove null connection";
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2018 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// SPDX-License-Identifier: GPl-3.0-or-later
|
||||
|
||||
#include "actionshandler.h"
|
||||
#include "controller.h"
|
||||
|
||||
#include <csapi/joining.h>
|
||||
#include <events/roommessageevent.h>
|
||||
#include "csapi/joining.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <QDebug>
|
||||
#include <QStringBuilder>
|
||||
|
||||
#include "controller.h"
|
||||
#include "roommanager.h"
|
||||
#include <QDebug>
|
||||
|
||||
ActionsHandler::ActionsHandler(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
ActionsHandler::~ActionsHandler(){};
|
||||
ActionsHandler::~ActionsHandler()
|
||||
{};
|
||||
|
||||
NeoChatRoom *ActionsHandler::room() const
|
||||
{
|
||||
@@ -51,55 +48,126 @@ void ActionsHandler::setConnection(Connection *connection)
|
||||
}
|
||||
m_connection = connection;
|
||||
if (m_connection != nullptr) {
|
||||
connect(m_connection, &Connection::directChatAvailable, this, [this](Quotient::Room *room) {
|
||||
room->setDisplayed(true);
|
||||
RoomManager::instance().enterRoom(qobject_cast<NeoChatRoom *>(room));
|
||||
});
|
||||
connect(m_connection, &Connection::directChatAvailable,
|
||||
this, [this](Quotient::Room *room) {
|
||||
room->setDisplayed(true);
|
||||
Q_EMIT roomJoined(room->id());
|
||||
});
|
||||
}
|
||||
Q_EMIT connectionChanged();
|
||||
}
|
||||
|
||||
void ActionsHandler::postEdit(const QString &text)
|
||||
QVariantList ActionsHandler::commands() const
|
||||
{
|
||||
QVariantList commands;
|
||||
// Messages commands
|
||||
commands.append({
|
||||
QStringLiteral("prefix"), QStringLiteral("/shrug "),
|
||||
QStringLiteral("parameter"), i18nc("@label Parameter of a command", "<message>"),
|
||||
QStringLiteral("help"), i18n("Prepends ¯\\_(ツ)_/¯ to a plain-text message")
|
||||
});
|
||||
|
||||
const auto localId = Controller::instance().activeConnection()->userId();
|
||||
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)) {
|
||||
if (event->senderId() == localId && event->hasTextContent()) {
|
||||
static QRegularExpression re("^s/([^/]*)/([^/]*)");
|
||||
auto match = re.match(text);
|
||||
if (!match.hasMatch()) {
|
||||
// should not happen but still make sure to send the message normally
|
||||
// just in case.
|
||||
postMessage(text, QString(), QString(), QString(), QVariantMap());
|
||||
}
|
||||
const QString regex = match.captured(1);
|
||||
const QString replacement = match.captured(2);
|
||||
QString originalString;
|
||||
if (event->content()) {
|
||||
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
|
||||
} else {
|
||||
originalString = event->plainBody();
|
||||
}
|
||||
m_room->postHtmlMessage(text, originalString.replace(regex, replacement), event->msgtype(), "", event->id());
|
||||
return;
|
||||
}
|
||||
}
|
||||
commands.append({
|
||||
QStringLiteral("prefix"), QStringLiteral("/lenny "),
|
||||
QStringLiteral("parameter"), i18nc("@label Parameter of a command", "<message>"),
|
||||
QStringLiteral("help"), i18n("Prepends ( ͡° ͜ʖ ͡°) to a plain-text message")
|
||||
});
|
||||
|
||||
commands.append({
|
||||
QStringLiteral("prefix"), QStringLiteral("/plain "),
|
||||
QStringLiteral("parameter"), i18nc("@label Parameter of a command", "<message>"),
|
||||
QStringLiteral("help"), i18n("Sends a message as plain text, without interpreting it as markdown")
|
||||
});
|
||||
|
||||
commands.append({
|
||||
QStringLiteral("prefix"), QStringLiteral("/html "),
|
||||
QStringLiteral("parameter"), i18nc("@label Parameter of a command", "<message>"),
|
||||
QStringLiteral("help"), i18n("Sends a message as html, without interpreting it as markdown")
|
||||
});
|
||||
|
||||
commands.append({
|
||||
QStringLiteral("prefix"), QStringLiteral("/rainbow "),
|
||||
QStringLiteral("parameter"), i18nc("@label Parameter of a command", "<message>"),
|
||||
QStringLiteral("help"), i18n("Sends the given message coloured as a rainbow")
|
||||
});
|
||||
|
||||
commands.append({
|
||||
QStringLiteral("prefix"), QStringLiteral("/rainbowme "),
|
||||
QStringLiteral("parameter"), i18nc("@label Parameter of a command", "<message>"),
|
||||
QStringLiteral("help"), i18n("Sends the given emote coloured as a rainbow")
|
||||
});
|
||||
|
||||
commands.append({
|
||||
QStringLiteral("prefix"), QStringLiteral("/me "),
|
||||
QStringLiteral("parameter"), i18nc("@label Parameter of a command", "<message>"),
|
||||
QStringLiteral("help"), i18n("Displays action")
|
||||
});
|
||||
|
||||
// Actions commands
|
||||
commands.append({
|
||||
QStringLiteral("prefix"), QStringLiteral("/join "),
|
||||
QStringLiteral("parameter"), i18nc("@label Parameter of a command", "<room-address>"),
|
||||
QStringLiteral("help"), i18n("Joins room with given address")
|
||||
});
|
||||
|
||||
commands.append({
|
||||
QStringLiteral("prefix"), QStringLiteral("/part "),
|
||||
QStringLiteral("parameter"), i18nc("@label Parameter of a command", "[<room-address>]"),
|
||||
QStringLiteral("help"), i18n("Leave room")
|
||||
});
|
||||
|
||||
commands.append({
|
||||
QStringLiteral("prefix"), QStringLiteral("/invite "),
|
||||
QStringLiteral("parameter"), i18nc("@label Parameter of a command", "<user-id>"),
|
||||
QStringLiteral("help"), i18n("Invites user with given id to current room")
|
||||
});
|
||||
|
||||
// TODO more see elements /help action
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
void ActionsHandler::joinRoom(const QString &alias)
|
||||
{
|
||||
if (!alias.contains(":")) {
|
||||
Q_EMIT showMessage(MessageType::Error, i18n("The room id you are trying to join is not valid"));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto knownServer = alias.mid(alias.indexOf(":") + 1);
|
||||
auto joinRoomJob = m_connection->joinRoom(alias, QStringList{knownServer});
|
||||
|
||||
Quotient::JoinRoomJob::connect(joinRoomJob, &JoinRoomJob::failure, [=] {
|
||||
Q_EMIT showMessage(MessageType::Error, i18n("Server error when joining the room \"%1\": %2",
|
||||
joinRoomJob->errorString()));
|
||||
});
|
||||
Quotient::JoinRoomJob::connect(joinRoomJob, &JoinRoomJob::success, [this, joinRoomJob] {
|
||||
Q_EMIT roomJoined(joinRoomJob->roomId());
|
||||
});
|
||||
}
|
||||
|
||||
void ActionsHandler::createRoom(const QString &name, const QString &topic)
|
||||
{
|
||||
auto createRoomJob = m_connection->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
|
||||
Quotient::CreateRoomJob::connect(createRoomJob, &CreateRoomJob::failure, [=] {
|
||||
Q_EMIT showMessage(MessageType::Error, i18n("Room creation failed: \"%1\"", createRoomJob->errorString()));
|
||||
});
|
||||
Quotient::CreateRoomJob::connect(createRoomJob, &CreateRoomJob::success, [=] {
|
||||
Q_EMIT roomJoined(createRoomJob->roomId());
|
||||
});
|
||||
}
|
||||
|
||||
void ActionsHandler::postMessage(const QString &text,
|
||||
const QString &attachementPath,
|
||||
const QString &replyEventId,
|
||||
const QString &editEventId,
|
||||
const QVariantMap &usernames)
|
||||
const QString &attachementPath, const QString &replyEventId, const QString &editEventId,
|
||||
const QVariantMap &usernames)
|
||||
{
|
||||
QString rawText = text;
|
||||
QString cleanedText = text;
|
||||
|
||||
|
||||
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 (attachementPath.length() > 0) {
|
||||
@@ -113,8 +181,8 @@ void ActionsHandler::postMessage(const QString &text,
|
||||
auto messageEventType = RoomMessageEvent::MsgType::Text;
|
||||
|
||||
// Message commands
|
||||
static const QString shrugPrefix = QStringLiteral("/shrug");
|
||||
static const QString lennyPrefix = QStringLiteral("/lenny");
|
||||
static const QString shrugPrefix = QStringLiteral("/shrug ");
|
||||
static const QString lennyPrefix = QStringLiteral("/lenny ");
|
||||
static const QString plainPrefix = QStringLiteral("/plain "); // TODO
|
||||
static const QString htmlPrefix = QStringLiteral("/html "); // TODO
|
||||
static const QString rainbowPrefix = QStringLiteral("/rainbow ");
|
||||
@@ -136,17 +204,17 @@ void ActionsHandler::postMessage(const QString &text,
|
||||
static const QString unignorePrefix = QStringLiteral("/unignore ");
|
||||
static const QString queryPrefix = QStringLiteral("/query "); // TODO
|
||||
static const QString msgPrefix = QStringLiteral("/msg "); // TODO
|
||||
static const QString reactPrefix = QStringLiteral("/react ");
|
||||
|
||||
// Admin commands
|
||||
// Admin commands
|
||||
|
||||
static QStringList rainbowColors{"#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00",
|
||||
"#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff",
|
||||
"#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff",
|
||||
"#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"};
|
||||
static QStringList rainbowColors{"#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500",
|
||||
"#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b",
|
||||
"#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff",
|
||||
"#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff",
|
||||
"#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"};
|
||||
|
||||
if (cleanedText.indexOf(shrugPrefix) == 0) {
|
||||
cleanedText = QStringLiteral("¯\\_(ツ)_/¯") % cleanedText.remove(0, shrugPrefix.length());
|
||||
cleanedText = QStringLiteral("¯\\\\_(ツ)\\_/¯") % cleanedText.remove(0, shrugPrefix.length());
|
||||
m_room->postHtmlMessage(cleanedText, cleanedText, messageEventType, replyEventId, editEventId);
|
||||
return;
|
||||
}
|
||||
@@ -163,7 +231,7 @@ void ActionsHandler::postMessage(const QString &text,
|
||||
for (int i = 0; i < cleanedText.length(); i++) {
|
||||
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
||||
}
|
||||
m_room->postHtmlMessage(cleanedText, rainbowText, RoomMessageEvent::MsgType::Notice, replyEventId, editEventId);
|
||||
m_room->postHtmlMessage(cleanedText, rainbowText, RoomMessageEvent::MsgType::Notice, replyEventId, editEventId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,13 +253,11 @@ void ActionsHandler::postMessage(const QString &text,
|
||||
return;
|
||||
}
|
||||
if (splittedText.count() > 1) {
|
||||
Controller::instance().joinRoom(splittedText[0] + ":" + splittedText[1]);
|
||||
joinRoom(splittedText[0] + ":" + splittedText[1]);
|
||||
return;
|
||||
} else if (splittedText[0].indexOf(":") != -1) {
|
||||
Controller::instance().joinRoom(splittedText[0]);
|
||||
return;
|
||||
} else {
|
||||
Controller::instance().joinRoom(splittedText[0] + ":matrix.org");
|
||||
}
|
||||
else {
|
||||
joinRoom(splittedText[0] + ":matrix.org");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -255,19 +321,9 @@ void ActionsHandler::postMessage(const QString &text,
|
||||
return;
|
||||
}
|
||||
|
||||
if (rawText.indexOf(reactPrefix) == 0) {
|
||||
if (replyEventId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
rawText = rawText.remove(0, reactPrefix.length());
|
||||
m_room->toggleReaction(replyEventId, rawText);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cleanedText.indexOf(mePrefix) == 0) {
|
||||
cleanedText = cleanedText.remove(0, mePrefix.length());
|
||||
messageEventType = RoomMessageEvent::MsgType::Emote;
|
||||
rawText = rawText.remove(0, mePrefix.length());
|
||||
} else if (cleanedText.indexOf(noticePrefix) == 0) {
|
||||
cleanedText = cleanedText.remove(0, noticePrefix.length());
|
||||
messageEventType = RoomMessageEvent::MsgType::Notice;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
//
|
||||
// SPDX-License-Identifier: GPl-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -16,6 +17,9 @@ class ActionsHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/// \brief List of command definition. Useful for building an autocompletion
|
||||
/// engine or an help dialog.
|
||||
Q_PROPERTY(QVariantList commands READ commands CONSTANT)
|
||||
|
||||
/// \brief The connection that will handle sending the message.
|
||||
Q_PROPERTY(Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
@@ -33,7 +37,7 @@ public:
|
||||
explicit ActionsHandler(QObject *parent = nullptr);
|
||||
~ActionsHandler();
|
||||
|
||||
|
||||
QVariantList commands() const;
|
||||
|
||||
[[nodiscard]] Connection *connection() const;
|
||||
void setConnection(Connection *connection);
|
||||
@@ -47,22 +51,28 @@ Q_SIGNALS:
|
||||
/// These messages will be displayed in the room view header.
|
||||
void showMessage(MessageType messageType, QString message);
|
||||
|
||||
/// \brief Emitted when an action made the user join a room.
|
||||
///
|
||||
/// Either when a new room was created, a direct chat was started
|
||||
/// or a group chat was joined. The UI will react to this signal
|
||||
/// and switch to the newly joined room.
|
||||
void roomJoined(QString roomName);
|
||||
|
||||
void roomChanged();
|
||||
void connectionChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
/// \brief Create new room for a group chat.
|
||||
void createRoom(const QString &name, const QString &topic);
|
||||
|
||||
/// \brief Join a room.
|
||||
void joinRoom(const QString &alias);
|
||||
|
||||
/// \brief Post a message.
|
||||
///
|
||||
/// This also interprets commands if any.
|
||||
void
|
||||
postMessage(const QString &text, const QString &attachementPath, const QString &replyEventId, const QString &editEventId, const QVariantMap &usernames);
|
||||
|
||||
/// \brief Send edit instructions (.e.g s/hallo/hello/)
|
||||
///
|
||||
/// This will automatically edit the last message posted and send the sed
|
||||
/// instruction to IRC.
|
||||
void postEdit(const QString &text);
|
||||
void postMessage(const QString &text, const QString &attachementPath,
|
||||
const QString &replyEventId, const QString &editEventId, const QVariantMap &usernames);
|
||||
|
||||
private:
|
||||
Connection *m_connection = nullptr;
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "chatboxhelper.h"
|
||||
#include <QDebug>
|
||||
|
||||
ChatBoxHelper::ChatBoxHelper(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool ChatBoxHelper::isEditing() const
|
||||
{
|
||||
return !m_editEventId.isEmpty();
|
||||
}
|
||||
|
||||
QString ChatBoxHelper::editEventId() const
|
||||
{
|
||||
return m_editEventId;
|
||||
}
|
||||
|
||||
void ChatBoxHelper::setEditEventId(const QString &editEventId)
|
||||
{
|
||||
if (m_editEventId == editEventId) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_editEventId = editEventId;
|
||||
Q_EMIT editEventIdChanged(m_editEventId);
|
||||
Q_EMIT isEditingChanged(!m_editEventId.isEmpty());
|
||||
}
|
||||
|
||||
QString ChatBoxHelper::editContent() const
|
||||
{
|
||||
return m_editContent;
|
||||
}
|
||||
|
||||
void ChatBoxHelper::setEditContent(const QString &editContent)
|
||||
{
|
||||
if (m_editContent == editContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_editContent = editContent;
|
||||
Q_EMIT editContentChanged();
|
||||
}
|
||||
|
||||
QString ChatBoxHelper::replyEventId() const
|
||||
{
|
||||
return m_replyEventId;
|
||||
}
|
||||
|
||||
void ChatBoxHelper::setReplyEventId(const QString &replyEventId)
|
||||
{
|
||||
if (m_replyEventId == replyEventId) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_replyEventId = replyEventId;
|
||||
Q_EMIT replyEventIdChanged(m_replyEventId);
|
||||
}
|
||||
|
||||
QString ChatBoxHelper::replyEventContent() const
|
||||
{
|
||||
return m_replyEventContent;
|
||||
}
|
||||
|
||||
void ChatBoxHelper::setReplyEventContent(const QString &replyEventContent)
|
||||
{
|
||||
if (m_replyEventContent == replyEventContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_replyEventContent = replyEventContent;
|
||||
Q_EMIT replyEventContentChanged(m_replyEventContent);
|
||||
Q_EMIT isReplyingChanged(!m_replyEventContent.isEmpty());
|
||||
}
|
||||
|
||||
bool ChatBoxHelper::isReplying() const
|
||||
{
|
||||
return !m_replyEventId.isEmpty();
|
||||
}
|
||||
|
||||
QString ChatBoxHelper::attachmentPath() const
|
||||
{
|
||||
return m_attachmentPath;
|
||||
}
|
||||
|
||||
void ChatBoxHelper::setAttachmentPath(const QString &attachmentPath)
|
||||
{
|
||||
if (m_attachmentPath == attachmentPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_attachmentPath = attachmentPath;
|
||||
Q_EMIT attachmentPathChanged(m_attachmentPath);
|
||||
Q_EMIT hasAttachmentChanged(!m_attachmentPath.isEmpty());
|
||||
}
|
||||
|
||||
bool ChatBoxHelper::hasAttachment() const
|
||||
{
|
||||
return !m_attachmentPath.isEmpty();
|
||||
}
|
||||
|
||||
void ChatBoxHelper::replyToMessage(const QString &replyEventId, const QString &replyEvent, const QVariant &replyUser)
|
||||
{
|
||||
setEditEventId(QString());
|
||||
setEditContent(QString());
|
||||
setReplyEventId(replyEventId);
|
||||
setReplyEventContent(replyEvent);
|
||||
setReplyUser(replyUser);
|
||||
}
|
||||
|
||||
QVariant ChatBoxHelper::replyUser() const
|
||||
{
|
||||
return m_replyUser;
|
||||
}
|
||||
|
||||
void ChatBoxHelper::setReplyUser(const QVariant &replyUser)
|
||||
{
|
||||
if (m_replyUser == replyUser) {
|
||||
return;
|
||||
}
|
||||
m_replyUser = replyUser;
|
||||
Q_EMIT replyUserChanged();
|
||||
}
|
||||
|
||||
void ChatBoxHelper::clear()
|
||||
{
|
||||
setEditEventId(QString());
|
||||
setEditContent(QString());
|
||||
setReplyEventId(QString());
|
||||
setReplyEventContent(QString());
|
||||
setAttachmentPath(QString());
|
||||
setReplyUser(QVariant());
|
||||
}
|
||||
|
||||
void ChatBoxHelper::edit(const QString &message, const QString &formattedBody, const QString &eventId)
|
||||
{
|
||||
setEditEventId(eventId);
|
||||
setEditContent(message);
|
||||
Q_EMIT editing(message, formattedBody);
|
||||
}
|
||||
|
||||
void ChatBoxHelper::clearEditReply()
|
||||
{
|
||||
setEditEventId(QString());
|
||||
setEditContent(QString());
|
||||
setReplyEventId(QString());
|
||||
setReplyEventContent(QString());
|
||||
setReplyUser(QVariant());
|
||||
Q_EMIT shouldClearText();
|
||||
}
|
||||
|
||||
void ChatBoxHelper::clearAttachment()
|
||||
{
|
||||
setAttachmentPath(QString());
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
|
||||
/// Helper singleton for keeping the chatbar state in sync in the application.
|
||||
class ChatBoxHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
/// True, iff the user is currently editing one of their previous message.
|
||||
Q_PROPERTY(bool isEditing READ isEditing NOTIFY isEditingChanged)
|
||||
Q_PROPERTY(QString editEventId READ editEventId WRITE setEditEventId NOTIFY editEventIdChanged)
|
||||
Q_PROPERTY(QString editContent READ editContent WRITE setEditContent NOTIFY editContentChanged)
|
||||
|
||||
Q_PROPERTY(bool isReplying READ isReplying NOTIFY isReplyingChanged)
|
||||
Q_PROPERTY(QString replyEventId READ replyEventId WRITE setReplyEventId NOTIFY replyEventIdChanged)
|
||||
Q_PROPERTY(QString replyEventContent READ replyEventContent WRITE setReplyEventContent NOTIFY replyEventContentChanged)
|
||||
Q_PROPERTY(QVariant replyUser READ replyUser WRITE setReplyUser NOTIFY replyUserChanged)
|
||||
|
||||
Q_PROPERTY(QString attachmentPath READ attachmentPath WRITE setAttachmentPath NOTIFY attachmentPathChanged)
|
||||
Q_PROPERTY(bool hasAttachment READ hasAttachment NOTIFY hasAttachmentChanged)
|
||||
|
||||
public:
|
||||
ChatBoxHelper(QObject *parent = nullptr);
|
||||
~ChatBoxHelper() = default;
|
||||
|
||||
bool isEditing() const;
|
||||
QString editEventId() const;
|
||||
QString editContent() const;
|
||||
|
||||
QString replyEventId() const;
|
||||
QString replyEventContent() const;
|
||||
QVariant replyUser() const;
|
||||
bool isReplying() const;
|
||||
|
||||
QString attachmentPath() const;
|
||||
bool hasAttachment() const;
|
||||
|
||||
void setEditEventId(const QString &editEventId);
|
||||
void setEditContent(const QString &editContent);
|
||||
void setReplyEventId(const QString &replyEventId);
|
||||
void setReplyEventContent(const QString &replyEventContent);
|
||||
void setAttachmentPath(const QString &attachmentPath);
|
||||
void setReplyUser(const QVariant &replyUser);
|
||||
|
||||
Q_INVOKABLE void replyToMessage(const QString &replyEventid, const QString &replyEvent, const QVariant &replyUser);
|
||||
Q_INVOKABLE void edit(const QString &message, const QString &formattedBody, const QString &eventId);
|
||||
Q_INVOKABLE void clear();
|
||||
Q_INVOKABLE void clearEditReply();
|
||||
Q_INVOKABLE void clearAttachment();
|
||||
|
||||
Q_SIGNALS:
|
||||
void isEditingChanged(bool isEditing);
|
||||
void editEventIdChanged(const QString &editEventId);
|
||||
void editContentChanged();
|
||||
void replyEventIdChanged(const QString &replyEventId);
|
||||
void replyEventContentChanged(const QString &replyEventContent);
|
||||
void replyUserChanged();
|
||||
void isReplyingChanged(bool isReplying);
|
||||
void attachmentPathChanged(const QString &attachmentPath);
|
||||
void hasAttachmentChanged(bool hasAttachment);
|
||||
void editing(const QString &message, const QString &formattedBody);
|
||||
void shouldClearText();
|
||||
|
||||
private:
|
||||
QString m_editEventId;
|
||||
QString m_editContent;
|
||||
QString m_replyEventId;
|
||||
QString m_replyEventContent;
|
||||
QVariant m_replyUser;
|
||||
QString m_attachmentPath;
|
||||
};
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "chatdocumenthandler.h"
|
||||
|
||||
#include <QQmlFile>
|
||||
@@ -149,7 +146,7 @@ QVariantMap ChatDocumentHandler::getAutocompletionInfo()
|
||||
};
|
||||
}
|
||||
|
||||
if (autoCompletePrefix.startsWith("@") || autoCompletePrefix.startsWith(":") || autoCompletePrefix.startsWith("/")) {
|
||||
if (autoCompletePrefix.startsWith("@") || autoCompletePrefix.startsWith(":")) {
|
||||
m_autoCompleteBeginPosition = textBeforeCursor.lastIndexOf(" ") + 1; // 1 == space
|
||||
|
||||
if (autoCompletePrefix.startsWith("@")) {
|
||||
@@ -159,14 +156,6 @@ QVariantMap ChatDocumentHandler::getAutocompletionInfo()
|
||||
{"type", AutoCompletionType::User},
|
||||
};
|
||||
}
|
||||
|
||||
if (autoCompletePrefix.startsWith("/")) {
|
||||
return QVariantMap{
|
||||
{"keyword", autoCompletePrefix},
|
||||
{"type", AutoCompletionType::Command},
|
||||
};
|
||||
}
|
||||
|
||||
return QVariantMap{
|
||||
{"keyword", autoCompletePrefix},
|
||||
{"type", AutoCompletionType::Emoji},
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -27,7 +30,6 @@ public:
|
||||
enum AutoCompletionType {
|
||||
User,
|
||||
Emoji,
|
||||
Command,
|
||||
None,
|
||||
Ignore,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
#include "clipboard.h"
|
||||
|
||||
#include <QClipboard>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Srevin Saju <srevinsaju@sugarlabs.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "actionshandler.h"
|
||||
#include "commandmodel.h"
|
||||
|
||||
|
||||
QVariantList CommandModel::filterModel(const QString &filter)
|
||||
{
|
||||
QVariantList result;
|
||||
|
||||
for (const QVariant &e : CommandModel::commands()) {
|
||||
auto command = qvariant_cast<Command>(e);
|
||||
if (command.command.startsWith(filter)) {
|
||||
result.append(e);
|
||||
if (result.length() > 10) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
QVariantList CommandModel::commands()
|
||||
{
|
||||
QVariantList commands;
|
||||
|
||||
// Messages commands
|
||||
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("/lenny "),
|
||||
QStringLiteral("<message>"),
|
||||
i18n("Prepends ( ͡° ͜ʖ ͡°) to a plain-text message")}));
|
||||
|
||||
commands.append(QVariant::fromValue(Command{
|
||||
QStringLiteral("/plain "),
|
||||
QStringLiteral("<message>"),
|
||||
i18n("Sends a message as plain text, without interpreting it as markdown")}));
|
||||
|
||||
commands.append(QVariant::fromValue(Command{
|
||||
QStringLiteral("/html "),
|
||||
QStringLiteral("<message>"),
|
||||
i18n("Sends a message as html, without interpreting it as markdown")}));
|
||||
|
||||
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")}));
|
||||
|
||||
|
||||
// Actions commands
|
||||
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("/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")}));
|
||||
|
||||
// TODO more see elements /help action
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Srevin Saju <srevinsaju@sugarlabs.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
#include <utility>
|
||||
|
||||
struct Command {
|
||||
Command(const QString &p, const QString &a, const QString &h)
|
||||
: command(p)
|
||||
, parameter(a)
|
||||
, help(h)
|
||||
{
|
||||
}
|
||||
Command() = default;
|
||||
|
||||
QString command;
|
||||
QString parameter;
|
||||
QString help;
|
||||
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString command MEMBER command)
|
||||
Q_PROPERTY(QString parameter MEMBER parameter)
|
||||
Q_PROPERTY(QString help MEMBER help)
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Command)
|
||||
|
||||
class CommandModel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CommandModel(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
Q_INVOKABLE QVariantList filterModel(const QString &filter);
|
||||
static QVariantList commands();
|
||||
};
|
||||
@@ -1,42 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
#include "controller.h"
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <qt5keychain/keychain.h>
|
||||
|
||||
#endif
|
||||
#include <KConfig>
|
||||
#include <KConfigGroup>
|
||||
#include <KLocalizedString>
|
||||
#include <KWindowConfig>
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <QAuthenticator>
|
||||
#include <QClipboard>
|
||||
#include <QCloseEvent>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QQuickWindow>
|
||||
#include <QDir>
|
||||
#include <QElapsedTimer>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QGuiApplication>
|
||||
#include <QMovie>
|
||||
#include <QNetworkConfigurationManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QPixmap>
|
||||
#include <QQuickWindow>
|
||||
#include <QStandardPaths>
|
||||
#include <QStringBuilder>
|
||||
#include <QSysInfo>
|
||||
#include <QTimer>
|
||||
#include <QCloseEvent>
|
||||
#include <QDesktopServices>
|
||||
#include <QMovie>
|
||||
#include <QPixmap>
|
||||
#include <QAuthenticator>
|
||||
#include <QNetworkReply>
|
||||
#include <QStringBuilder>
|
||||
#include <utility>
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include "csapi/account-data.h"
|
||||
#include "csapi/content-repo.h"
|
||||
#include "csapi/joining.h"
|
||||
#include "csapi/logout.h"
|
||||
#include "csapi/profile.h"
|
||||
#include "csapi/registration.h"
|
||||
@@ -48,75 +46,40 @@
|
||||
#include "neochatuser.h"
|
||||
#include "settings.h"
|
||||
#include "utils.h"
|
||||
#include "roommanager.h"
|
||||
#include <KStandardShortcut>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include "trayicon.h"
|
||||
#endif
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
Controller::Controller(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_mgr(new QNetworkConfigurationManager(this))
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
Connection::setUserType<NeoChatUser>();
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
TrayIcon *trayIcon = new TrayIcon(this);
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
if(NeoChatConfig::self()->systemTray()) {
|
||||
trayIcon->show();
|
||||
connect(trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||
QApplication::setQuitOnLastWindowClosed(false);
|
||||
}
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, [=]() {
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, [=](){
|
||||
if(NeoChatConfig::self()->systemTray()) {
|
||||
trayIcon->show();
|
||||
connect(trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
} else {
|
||||
trayIcon->hide();
|
||||
disconnect(trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
}
|
||||
QGuiApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
|
||||
QApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
|
||||
});
|
||||
#endif
|
||||
|
||||
QTimer::singleShot(0, this, [=] {
|
||||
invokeLogin();
|
||||
});
|
||||
|
||||
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [] {
|
||||
NeoChatConfig::self()->save();
|
||||
});
|
||||
|
||||
#ifndef Q_OS_WINDOWS
|
||||
// Setup Unix signal handlers
|
||||
const auto unixExitHandler = [](int /*sig*/) -> void {
|
||||
QCoreApplication::quit();
|
||||
};
|
||||
|
||||
const int quitSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP};
|
||||
|
||||
sigset_t blockingMask;
|
||||
sigemptyset(&blockingMask);
|
||||
for (const auto sig : quitSignals) {
|
||||
sigaddset(&blockingMask, sig);
|
||||
}
|
||||
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = unixExitHandler;
|
||||
sa.sa_mask = blockingMask;
|
||||
sa.sa_flags = 0;
|
||||
|
||||
for (auto sig : quitSignals) {
|
||||
sigaction(sig, &sa, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
connect(m_mgr, &QNetworkConfigurationManager::onlineStateChanged,
|
||||
this, &Controller::isOnlineChanged);
|
||||
}
|
||||
|
||||
Controller::~Controller()
|
||||
@@ -167,7 +130,7 @@ void Controller::loginWithAccessToken(const QString &serverAddr, const QString &
|
||||
setActiveConnection(conn);
|
||||
});
|
||||
connect(conn, &Connection::networkError, this, [=](QString error, const QString &, int, int) {
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
||||
Q_EMIT errorOccured("Network Error", std::move(error));
|
||||
});
|
||||
conn->connectWithToken(user, token, deviceName);
|
||||
}
|
||||
@@ -182,6 +145,7 @@ void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||
SettingsGroup("Accounts").remove(conn->userId());
|
||||
QFile(accessTokenFileName(AccountSettings(conn->userId()))).remove();
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
QKeychain::DeletePasswordJob job(qAppName());
|
||||
job.setAutoDelete(true);
|
||||
job.setKey(conn->userId());
|
||||
@@ -189,7 +153,11 @@ void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||
job.start();
|
||||
loop.exec();
|
||||
|
||||
#else
|
||||
KConfig config("neochat_tokens");
|
||||
KConfigGroup tokensGroup(&config, "Tokens");
|
||||
tokensGroup.deleteEntry(conn->userId());
|
||||
#endif
|
||||
conn->stopSync();
|
||||
Q_EMIT conn->stateChanged();
|
||||
Q_EMIT conn->loggedOut();
|
||||
@@ -203,7 +171,7 @@ void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||
}
|
||||
auto logoutJob = conn->callApi<LogoutJob>();
|
||||
connect(logoutJob, &LogoutJob::failure, this, [=] {
|
||||
Q_EMIT errorOccured(i18n("Server-side Logout Failed: %1", logoutJob->errorString()));
|
||||
Q_EMIT errorOccured("Server-side Logout Failed", logoutJob->errorString());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -227,8 +195,8 @@ void Controller::addConnection(Connection *c)
|
||||
dropConnection(c);
|
||||
});
|
||||
|
||||
connect(c, &Connection::requestFailed, this, [=](BaseJob *job) {
|
||||
if (job->error() == BaseJob::UserConsentRequiredError) {
|
||||
connect(c, &Connection::requestFailed, this, [=] (BaseJob *job) {
|
||||
if(job->error() == BaseJob::UserConsentRequiredError) {
|
||||
Q_EMIT userConsentRequired(job->errorUrl());
|
||||
}
|
||||
});
|
||||
@@ -275,16 +243,16 @@ void Controller::invokeLogin()
|
||||
});
|
||||
connect(connection, &Connection::loginError, this, [=](const QString &error, const QString &) {
|
||||
if (error == "Unrecognised access token") {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
|
||||
Q_EMIT errorOccured(i18n("Login Failed"), i18n("Access Token invalid or revoked"));
|
||||
logout(connection, false);
|
||||
} else {
|
||||
Q_EMIT errorOccured(i18n("Login Failed", error));
|
||||
Q_EMIT errorOccured(i18n("Login Failed"), error);
|
||||
logout(connection, true);
|
||||
}
|
||||
Q_EMIT initiated();
|
||||
});
|
||||
connect(connection, &Connection::networkError, this, [=](const QString &error, const QString &, int, int) {
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
||||
Q_EMIT errorOccured("Network Error", error);
|
||||
});
|
||||
connection->connectWithToken(account.userId(), accessToken, account.deviceId());
|
||||
}
|
||||
@@ -311,6 +279,7 @@ QByteArray Controller::loadAccessTokenFromFile(const AccountSettings &account)
|
||||
|
||||
QByteArray Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
|
||||
{
|
||||
#ifndef Q_OS_ANDROID
|
||||
qDebug() << "Read the access token from the keychain for " << account.userId();
|
||||
QKeychain::ReadPasswordJob job(qAppName());
|
||||
job.setAutoDelete(false);
|
||||
@@ -344,6 +313,13 @@ QByteArray Controller::loadAccessTokenFromKeyChain(const AccountSettings &accoun
|
||||
}
|
||||
|
||||
return accessToken;
|
||||
#else
|
||||
qDebug() << "Saving access token to KConfig";
|
||||
KConfig config("neochat_tokens");
|
||||
KConfigGroup tokensGroup(&config, "Tokens");
|
||||
QString token = tokensGroup.readEntry(account.userId(), QString());
|
||||
return token.toLatin1();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Controller::saveAccessTokenToFile(const AccountSettings &account, const QByteArray &accessToken)
|
||||
@@ -354,7 +330,7 @@ bool Controller::saveAccessTokenToFile(const AccountSettings &account, const QBy
|
||||
|
||||
auto fileDir = QFileInfo(accountTokenFile).dir();
|
||||
if (!((fileDir.exists() || fileDir.mkpath(".")) && accountTokenFile.open(QFile::WriteOnly))) {
|
||||
Q_EMIT errorOccured("I/O Denied: Cannot save access token.");
|
||||
Q_EMIT errorOccured("I/O Denied", "Cannot save access token.");
|
||||
} else {
|
||||
accountTokenFile.write(accessToken);
|
||||
return true;
|
||||
@@ -364,6 +340,7 @@ bool Controller::saveAccessTokenToFile(const AccountSettings &account, const QBy
|
||||
|
||||
bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken)
|
||||
{
|
||||
#ifndef Q_OS_ANDROID
|
||||
qDebug() << "Save the access token to the keychain for " << account.userId();
|
||||
QKeychain::WritePasswordJob job(qAppName());
|
||||
job.setAutoDelete(false);
|
||||
@@ -378,9 +355,16 @@ bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const
|
||||
qWarning() << "Could not save access token to the keychain: " << qPrintable(job.errorString());
|
||||
return saveAccessTokenToFile(account, accessToken);
|
||||
}
|
||||
|
||||
#else
|
||||
KConfig config("neochat_tokens");
|
||||
KConfigGroup tokensGroup(&config, "Tokens");
|
||||
tokensGroup.writeEntry(account.userId(), accessToken);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Controller::playAudio(const QUrl &localFile)
|
||||
{
|
||||
auto player = new QMediaPlayer;
|
||||
@@ -394,11 +378,7 @@ void Controller::playAudio(const QUrl &localFile)
|
||||
void Controller::changeAvatar(Connection *conn, const QUrl &localFile)
|
||||
{
|
||||
auto job = conn->uploadFile(localFile.toLocalFile());
|
||||
#ifdef QUOTIENT_07
|
||||
if(isJobPending(job)) {
|
||||
#else
|
||||
if (isJobRunning(job)) {
|
||||
#endif
|
||||
connect(job, &BaseJob::success, this, [conn, job] {
|
||||
conn->callApi<SetAvatarUrlJob>(conn->userId(), job->contentUri());
|
||||
});
|
||||
@@ -424,16 +404,6 @@ KAboutData Controller::aboutData() const
|
||||
return m_aboutData;
|
||||
}
|
||||
|
||||
bool Controller::supportSystemTray() const
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
return false;
|
||||
#else
|
||||
QString de = getenv("XDG_CURRENT_DESKTOP");
|
||||
return de != QStringLiteral("GNOME") && de != QStringLiteral("Pantheon");
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::changePassword(Connection *connection, const QString ¤tPassword, const QString &newPassword)
|
||||
{
|
||||
NeochatChangePasswordJob *job = connection->callApi<NeochatChangePasswordJob>(newPassword, false);
|
||||
@@ -462,21 +432,6 @@ void Controller::changePassword(Connection *connection, const QString ¤tPa
|
||||
});
|
||||
}
|
||||
|
||||
bool Controller::setAvatar(Connection *connection, const QUrl &avatarSource)
|
||||
{
|
||||
User *localUser = connection->user();
|
||||
QString decoded = avatarSource.path();
|
||||
if (decoded.isEmpty()) {
|
||||
connection->callApi<SetAvatarUrlJob>(localUser->id(), "");
|
||||
return true;
|
||||
}
|
||||
if (QImageReader(decoded).read().isNull()) {
|
||||
return false;
|
||||
} else {
|
||||
return localUser->setAvatar(decoded);
|
||||
}
|
||||
}
|
||||
|
||||
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), QStringLiteral("/_matrix/client/r0") % "/account/password")
|
||||
{
|
||||
@@ -499,13 +454,13 @@ int Controller::accountCount() const
|
||||
|
||||
bool Controller::quitOnLastWindowClosed()
|
||||
{
|
||||
return QGuiApplication::quitOnLastWindowClosed();
|
||||
return QApplication::quitOnLastWindowClosed();
|
||||
}
|
||||
|
||||
void Controller::setQuitOnLastWindowClosed(bool value)
|
||||
{
|
||||
if (quitOnLastWindowClosed() != value) {
|
||||
QGuiApplication::setQuitOnLastWindowClosed(value);
|
||||
QApplication::setQuitOnLastWindowClosed(value);
|
||||
Q_EMIT quitOnLastWindowClosedChanged();
|
||||
}
|
||||
}
|
||||
@@ -556,62 +511,12 @@ void Controller::saveWindowGeometry(QQuickWindow *window)
|
||||
dataResource.sync();
|
||||
}
|
||||
|
||||
|
||||
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth)
|
||||
: Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId))
|
||||
{
|
||||
QJsonObject _data;
|
||||
addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth);
|
||||
setRequestData(std::move(_data));
|
||||
}
|
||||
|
||||
void Controller::createRoom(const QString &name, const QString &topic)
|
||||
{
|
||||
auto createRoomJob = m_connection->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
|
||||
Quotient::CreateRoomJob::connect(createRoomJob, &CreateRoomJob::failure, [=] {
|
||||
Q_EMIT errorOccured(i18n("Room creation failed: \"%1\"", createRoomJob->errorString()));
|
||||
});
|
||||
}
|
||||
|
||||
bool Controller::isOnline() const
|
||||
{
|
||||
return m_mgr->isOnline();
|
||||
}
|
||||
|
||||
void Controller::joinRoom(const QString &alias)
|
||||
{
|
||||
if (!alias.contains(":")) {
|
||||
Q_EMIT errorOccured(i18n("The room id you are trying to join is not valid"));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto knownServer = alias.mid(alias.indexOf(":") + 1);
|
||||
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)
|
||||
{
|
||||
const auto existing = activeConnection()->directChats();
|
||||
|
||||
if (existing.contains(user)) {
|
||||
RoomManager::instance().enterRoom(static_cast<NeoChatRoom *>(activeConnection()->room(existing.value(user))));
|
||||
return;
|
||||
}
|
||||
activeConnection()->requestDirectChat(user);
|
||||
}
|
||||
|
||||
QString Controller::formatByteSize(double size, int precision) const
|
||||
{
|
||||
return KFormat().formatByteSize(size, precision);
|
||||
}
|
||||
|
||||
QString Controller::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const
|
||||
{
|
||||
return KFormat().formatDuration(msecs, options);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMediaPlayer>
|
||||
#include <QMenu>
|
||||
#include <QObject>
|
||||
|
||||
#include <KAboutData>
|
||||
#include <KFormat>
|
||||
|
||||
class QKeySequences;
|
||||
class QNetworkConfigurationManager;
|
||||
|
||||
#include "connection.h"
|
||||
#include "csapi/list_public_rooms.h"
|
||||
@@ -19,7 +21,6 @@ class QNetworkConfigurationManager;
|
||||
#include "user.h"
|
||||
|
||||
class NeoChatRoom;
|
||||
class NeoChatUser;
|
||||
class QQuickWindow;
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -32,8 +33,6 @@ class Controller : public QObject
|
||||
Q_PROPERTY(Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
||||
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
||||
Q_PROPERTY(KAboutData aboutData READ aboutData WRITE setAboutData NOTIFY aboutDataChanged)
|
||||
Q_PROPERTY(bool supportSystemTray READ supportSystemTray CONSTANT)
|
||||
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
||||
|
||||
public:
|
||||
static Controller &instance();
|
||||
@@ -50,8 +49,6 @@ public:
|
||||
|
||||
Q_INVOKABLE void changePassword(Quotient::Connection *connection, const QString ¤tPassword, const QString &newPassword);
|
||||
|
||||
Q_INVOKABLE bool setAvatar(Quotient::Connection *connection, const QUrl &avatarSource);
|
||||
|
||||
[[nodiscard]] int accountCount() const;
|
||||
|
||||
[[nodiscard]] static bool quitOnLastWindowClosed();
|
||||
@@ -63,8 +60,6 @@ public:
|
||||
void setAboutData(const KAboutData &aboutData);
|
||||
[[nodiscard]] KAboutData aboutData() const;
|
||||
|
||||
[[nodiscard]] bool supportSystemTray() const;
|
||||
|
||||
bool saveAccessTokenToFile(const AccountSettings &account, const QByteArray &accessToken);
|
||||
bool saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken);
|
||||
|
||||
@@ -75,18 +70,6 @@ public:
|
||||
};
|
||||
Q_ENUM(PasswordStatus);
|
||||
|
||||
/// \brief Create new room for a group chat.
|
||||
Q_INVOKABLE void createRoom(const QString &name, const QString &topic);
|
||||
|
||||
/// \brief Join a room.
|
||||
Q_INVOKABLE void joinRoom(const QString &alias);
|
||||
|
||||
bool isOnline() const;
|
||||
|
||||
Q_INVOKABLE QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options = KFormat::DefaultDuration) const;
|
||||
Q_INVOKABLE QString formatByteSize(double size, int precision = 1) const;
|
||||
|
||||
Q_INVOKABLE void openOrCreateDirectChat(NeoChatUser *user);
|
||||
private:
|
||||
explicit Controller(QObject *parent = nullptr);
|
||||
~Controller() override;
|
||||
@@ -109,7 +92,7 @@ private Q_SLOTS:
|
||||
Q_SIGNALS:
|
||||
void busyChanged();
|
||||
/// Error occurred because of user inputs
|
||||
void errorOccured(const QString &error);
|
||||
void errorOccured(QString error, QString detail);
|
||||
|
||||
/// Error occurred because of server or bug in NeoChat
|
||||
void globalErrorOccured(QString error, QString detail);
|
||||
@@ -125,9 +108,9 @@ Q_SIGNALS:
|
||||
void aboutDataChanged();
|
||||
void passwordStatus(Controller::PasswordStatus _t1);
|
||||
void showWindow();
|
||||
void openRoom(NeoChatRoom *room);
|
||||
void userConsentRequired(QUrl url);
|
||||
void testConnectionResult(const QString &connection, bool usable);
|
||||
void isOnlineChanged(bool isOnline);
|
||||
|
||||
public Q_SLOTS:
|
||||
void logout(Quotient::Connection *conn, bool serverSideLogout);
|
||||
@@ -135,9 +118,6 @@ public Q_SLOTS:
|
||||
void changeAvatar(Quotient::Connection *conn, const QUrl &localFile);
|
||||
static void markAllMessagesAsRead(Quotient::Connection *conn);
|
||||
void saveWindowGeometry(QQuickWindow *);
|
||||
|
||||
private:
|
||||
QNetworkConfigurationManager *m_mgr;
|
||||
};
|
||||
|
||||
// TODO libQuotient 0.7: Drop
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-LicenseIdentifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "devicesmodel.h"
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-LicenseIdentifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
@@ -19,36 +22,24 @@ QVariantList EmojiModel::filterModel(const QString &filter)
|
||||
auto emoji = qvariant_cast<Emoji>(e);
|
||||
if (emoji.shortname.startsWith(filter)) {
|
||||
result.append(e);
|
||||
if (result.length() > 10) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const QVariant &e : nature) {
|
||||
auto emoji = qvariant_cast<Emoji>(e);
|
||||
if (emoji.shortname.startsWith(filter)) {
|
||||
result.append(e);
|
||||
if (result.length() > 10) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const QVariant &e : food) {
|
||||
auto emoji = qvariant_cast<Emoji>(e);
|
||||
if (emoji.shortname.startsWith(filter)) {
|
||||
result.append(e);
|
||||
if (result.length() > 10) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const QVariant &e : activity) {
|
||||
auto emoji = qvariant_cast<Emoji>(e);
|
||||
if (emoji.shortname.startsWith(filter)) {
|
||||
result.append(e);
|
||||
if (result.length() > 10) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const QVariant &e : travel) {
|
||||
@@ -61,27 +52,18 @@ QVariantList EmojiModel::filterModel(const QString &filter)
|
||||
auto emoji = qvariant_cast<Emoji>(e);
|
||||
if (emoji.shortname.startsWith(filter)) {
|
||||
result.append(e);
|
||||
if (result.length() > 10) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const QVariant &e : symbols) {
|
||||
auto emoji = qvariant_cast<Emoji>(e);
|
||||
if (emoji.shortname.startsWith(filter)) {
|
||||
result.append(e);
|
||||
if (result.length() > 10) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const QVariant &e : flags) {
|
||||
auto emoji = qvariant_cast<Emoji>(e);
|
||||
if (emoji.shortname.startsWith(filter)) {
|
||||
result.append(e);
|
||||
if (result.length() > 10) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1293,261 +1275,133 @@ const QVariantList EmojiModel::symbols = {
|
||||
};
|
||||
|
||||
const QVariantList EmojiModel::flags = {
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xa8"), ":flag_ac:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xa9"), ":flag_ad:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xaa"), ":flag_ae:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xab"), ":flag_af:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xac"), ":flag_ag:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xae"), ":flag_ai:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb1"), ":flag_al:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb2"), ":flag_am:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb4"), ":flag_ao:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb6"), ":flag_aq:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb7"), ":flag_ar:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb8"), ":flag_as:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb9"), ":flag_at:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xba"), ":flag_au:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xbc"), ":flag_aw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xbd"), ":flag_ax:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xbf"), ":flag_az:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xa6"), ":flag_ba:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xa7"), ":flag_bb:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xa9"), ":flag_bd:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xaa"), ":flag_be:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xab"), ":flag_bf:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xac"), ":flag_bg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xad"), ":flag_bh:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xae"), ":flag_bi:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xaf"), ":flag_bj:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb1"), ":flag_bl:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb2"), ":flag_bm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb3"), ":flag_bn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb4"), ":flag_bo:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb6"), ":flag_bq:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb7"), ":flag_br:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb8"), ":flag_bs:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb9"), ":flag_bt:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbb"), ":flag_bv:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbc"), ":flag_bw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbe"), ":flag_by:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbf"), ":flag_bz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xa6"), ":flag_ca:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xa8"), ":flag_cc:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xa9"), ":flag_cd:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xab"), ":flag_cf:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xac"), ":flag_cg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xad"), ":flag_ch:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xae"), ":flag_ci:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb0"), ":flag_ck:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb1"), ":flag_cl:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb2"), ":flag_cm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb3"), ":flag_cn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb4"), ":flag_co:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb5"), ":flag_cp:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb7"), ":flag_cr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xba"), ":flag_cu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbb"), ":flag_cv:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbc"), ":flag_cw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbd"), ":flag_cx:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbe"), ":flag_cy:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbf"), ":flag_cz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xaa"), ":flag_de:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xac"), ":flag_dg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xaf"), ":flag_dj:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xb0"), ":flag_dk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xb2"), ":flag_dm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xb4"), ":flag_do:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xbf"), ":flag_dz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xa6"), ":flag_ea:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xa8"), ":flag_ec:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xaa"), ":flag_ee:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xac"), ":flag_eg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xad"), ":flag_eh:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xb7"), ":flag_er:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xb8"), ":flag_es:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xb9"), ":flag_et:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xba"), ":flag_eu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xae"), ":flag_fi:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xaf"), ":flag_fj:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb0"), ":flag_fk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb2"), ":flag_fm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb4"), ":flag_fo:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb7"), ":flag_fr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xa6"), ":flag_ga:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xa7"), ":flag_gb:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xa9"), ":flag_gd:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xaa"), ":flag_ge:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xab"), ":flag_gf:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xac"), ":flag_gg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xad"), ":flag_gh:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xae"), ":flag_gi:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb1"), ":flag_gl:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb2"), ":flag_gm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb3"), ":flag_gn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb5"), ":flag_gp:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb6"), ":flag_gq:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb7"), ":flag_gr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb8"), ":flag_gs:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb9"), ":flag_gt:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xba"), ":flag_gu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xbc"), ":flag_gw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xbe"), ":flag_gy:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb0"), ":flag_hk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb2"), ":flag_hm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb3"), ":flag_hn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb7"), ":flag_hr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb9"), ":flag_ht:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xba"), ":flag_hu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xa8"), ":flag_ic:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xa9"), ":flag_id:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xaa"), ":flag_ie:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb1"), ":flag_il:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb2"), ":flag_im:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb3"), ":flag_in:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb4"), ":flag_io:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb6"), ":flag_iq:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb7"), ":flag_ir:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb8"), ":flag_is:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb9"), ":flag_it:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xaa"), ":flag_je:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xb2"), ":flag_jm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xb4"), ":flag_jo:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xb5"), ":flag_jp:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xaa"), ":flag_ke:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xac"), ":flag_kg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xad"), ":flag_kh:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xae"), ":flag_ki:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb2"), ":flag_km:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb3"), ":flag_kn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb5"), ":flag_kp:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb7"), ":flag_kr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xbc"), ":flag_kw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xbe"), ":flag_ky:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xbf"), ":flag_kz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xa6"), ":flag_la:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xa7"), ":flag_lb:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xa8"), ":flag_lc:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xae"), ":flag_li:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb0"), ":flag_lk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb7"), ":flag_lr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb8"), ":flag_ls:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb9"), ":flag_lt:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xba"), ":flag_lu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xbb"), ":flag_lv:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xbe"), ":flag_ly:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xa6"), ":flag_ma:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xa8"), ":flag_mc:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xa9"), ":flag_md:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xaa"), ":flag_me:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xab"), ":flag_mf:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xac"), ":flag_mg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xad"), ":flag_mh:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb0"), ":flag_mk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb1"), ":flag_ml:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb2"), ":flag_mm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb3"), ":flag_mn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb4"), ":flag_mo:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb5"), ":flag_mp:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb6"), ":flag_mq:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb7"), ":flag_mr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb8"), ":flag_ms:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb9"), ":flag_mt:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xba"), ":flag_mu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbb"), ":flag_mv:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbc"), ":flag_mw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbd"), ":flag_mx:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbe"), ":flag_my:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbf"), ":flag_mz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xa6"), ":flag_na:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xa8"), ":flag_nc:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xaa"), ":flag_ne:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xab"), ":flag_nf:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xac"), ":flag_ng:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xae"), ":flag_ni:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb1"), ":flag_nl:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb4"), ":flag_no:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb5"), ":flag_np:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb7"), ":flag_nr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xba"), ":flag_nu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xbf"), ":flag_nz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb4\xf0\x9f\x87\xb2"), ":flag_om:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xa6"), ":flag_pa:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xaa"), ":flag_pe:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xab"), ":flag_pf:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xac"), ":flag_pg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xad"), ":flag_ph:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb0"), ":flag_pk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb1"), ":flag_pl:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb2"), ":flag_pm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb3"), ":flag_pn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb7"), ":flag_pr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb8"), ":flag_ps:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb9"), ":flag_pt:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xbc"), ":flag_pw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xbe"), ":flag_py:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb6\xf0\x9f\x87\xa6"), ":flag_qa:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xaa"), ":flag_re:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xb4"), ":flag_ro:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xb8"), ":flag_rs:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xba"), ":flag_ru:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xbc"), ":flag_rw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa6"), ":flag_sa:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa7"), ":flag_sb:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa8"), ":flag_sc:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa9"), ":flag_sd:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xaa"), ":flag_se:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xac"), ":flag_sg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xad"), ":flag_sh:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xae"), ":flag_si:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xaf"), ":flag_sj:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb0"), ":flag_sk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb1"), ":flag_sl:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb2"), ":flag_sm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb3"), ":flag_sn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb4"), ":flag_so:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb7"), ":flag_sr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb8"), ":flag_ss:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb9"), ":flag_st:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbb"), ":flag_sv:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbd"), ":flag_sx:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbe"), ":flag_sy:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbf"), ":flag_sz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xa6"), ":flag_ta:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xa8"), ":flag_tc:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xa9"), ":flag_td:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xab"), ":flag_tf:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xac"), ":flag_tg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xad"), ":flag_th:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xaf"), ":flag_tj:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb0"), ":flag_tk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb1"), ":flag_tl:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb2"), ":flag_tm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb3"), ":flag_tn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb4"), ":flag_to:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb7"), ":flag_tr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb9"), ":flag_tt:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xbb"), ":flag_tv:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xbc"), ":flag_tw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xbf"), ":flag_tz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xa6"), ":flag_ua:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xac"), ":flag_ug:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xb2"), ":flag_um:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xb8"), ":flag_us:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xbe"), ":flag_uy:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xbf"), ":flag_uz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xa6"), ":flag_va:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xa8"), ":flag_vc:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xaa"), ":flag_ve:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xac"), ":flag_vg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xae"), ":flag_vi:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xb3"), ":flag_vn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xba"), ":flag_vu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbc\xf0\x9f\x87\xab"), ":flag_wf:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbc\xf0\x9f\x87\xb8"), ":flag_ws:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbd\xf0\x9f\x87\xb0"), ":flag_xk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbe\xf0\x9f\x87\xaa"), ":flag_ye:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbe\xf0\x9f\x87\xb9"), ":flag_yt:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbf\xf0\x9f\x87\xa6"), ":flag_za:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbf\xf0\x9f\x87\xb2"), ":flag_zm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xa8"), ":flag_ac:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xa9"), ":flag_ad:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xaa"), ":flag_ae:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xab"), ":flag_af:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xac"), ":flag_ag:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xae"), ":flag_ai:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb1"), ":flag_al:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb2"), ":flag_am:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb4"), ":flag_ao:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb6"), ":flag_aq:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb7"), ":flag_ar:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb8"), ":flag_as:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb9"), ":flag_at:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xba"), ":flag_au:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xbc"), ":flag_aw:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xbd"), ":flag_ax:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xbf"), ":flag_az:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xa6"), ":flag_ba:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xa7"), ":flag_bb:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xa9"), ":flag_bd:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xaa"), ":flag_be:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xab"), ":flag_bf:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xac"), ":flag_bg:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xad"), ":flag_bh:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xae"), ":flag_bi:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xaf"), ":flag_bj:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb1"), ":flag_bl:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb2"), ":flag_bm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb3"), ":flag_bn:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb4"), ":flag_bo:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb6"), ":flag_bq:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb7"), ":flag_br:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb8"), ":flag_bs:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb9"), ":flag_bt:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbb"), ":flag_bv:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbc"), ":flag_bw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbe"), ":flag_by:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbf"), ":flag_bz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xa6"), ":flag_ca:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xa8"), ":flag_cc:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xa9"), ":flag_cd:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xab"), ":flag_cf:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xac"), ":flag_cg:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xad"), ":flag_ch:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xae"), ":flag_ci:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb0"), ":flag_ck:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb1"), ":flag_cl:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb2"), ":flag_cm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb3"), ":flag_cn:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb4"), ":flag_co:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb5"), ":flag_cp:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb7"), ":flag_cr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xba"), ":flag_cu:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbb"), ":flag_cv:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbc"), ":flag_cw:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbd"), ":flag_cx:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbe"), ":flag_cy:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbf"), ":flag_cz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xaa"), ":flag_de:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xac"), ":flag_dg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xaf"), ":flag_dj:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xb0"), ":flag_dk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xb2"), ":flag_dm:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xb4"), ":flag_do:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xbf"), ":flag_dz:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xa6"), ":flag_ea:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xa8"), ":flag_ec:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xaa"), ":flag_ee:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xac"), ":flag_eg:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xad"), ":flag_eh:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xb7"), ":flag_er:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xb8"), ":flag_es:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xb9"), ":flag_et:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xba"), ":flag_eu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xae"), ":flag_fi:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xaf"), ":flag_fj:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb0"), ":flag_fk:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb2"), ":flag_fm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb4"), ":flag_fo:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb7"), ":flag_fr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xa6"), ":flag_ga:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xa7"), ":flag_gb:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xa9"), ":flag_gd:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xaa"), ":flag_ge:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xab"), ":flag_gf:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xac"), ":flag_gg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xad"), ":flag_gh:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xae"), ":flag_gi:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb1"), ":flag_gl:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb2"), ":flag_gm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb3"), ":flag_gn:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb5"), ":flag_gp:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb6"), ":flag_gq:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb7"), ":flag_gr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb8"), ":flag_gs:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb9"), ":flag_gt:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xba"), ":flag_gu:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xbc"), ":flag_gw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xbe"), ":flag_gy:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb0"), ":flag_hk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb2"), ":flag_hm:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb3"), ":flag_hn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb7"), ":flag_hr:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb9"), ":flag_ht:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xba"), ":flag_hu:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xa8"), ":flag_ic:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xa9"), ":flag_id:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xaa"), ":flag_ie:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb1"), ":flag_il:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb2"), ":flag_im:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb3"), ":flag_in:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb4"), ":flag_io:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb6"), ":flag_iq:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb7"), ":flag_ir:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb8"), ":flag_is:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb9"), ":flag_it:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xaa"), ":flag_je:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xb2"), ":flag_jm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xb4"), ":flag_jo:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xb5"), ":flag_jp:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xaa"), ":flag_ke:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xac"), ":flag_kg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xad"), ":flag_kh:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xae"), ":flag_ki:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb2"), ":flag_km:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb3"), ":flag_kn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb5"), ":flag_kp:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb7"), ":flag_kr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xbc"), ":flag_kw:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xbe"), ":flag_ky:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xbf"), ":flag_kz:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xa6"), ":flag_la:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xa7"), ":flag_lb:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xa8"), ":flag_lc:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xae"), ":flag_li:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb0"), ":flag_lk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb7"), ":flag_lr:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb8"), ":flag_ls:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb9"), ":flag_lt:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xba"), ":flag_lu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xbb"), ":flag_lv:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xbe"), ":flag_ly:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xa6"), ":flag_ma:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xa8"), ":flag_mc:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xa9"), ":flag_md:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xaa"), ":flag_me:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xab"), ":flag_mf:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xac"), ":flag_mg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xad"), ":flag_mh:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb0"), ":flag_mk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb1"), ":flag_ml:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb2"), ":flag_mm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb3"), ":flag_mn:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb4"), ":flag_mo:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb5"), ":flag_mp:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb6"), ":flag_mq:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb7"), ":flag_mr:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb8"), ":flag_ms:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb9"), ":flag_mt:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xba"), ":flag_mu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbb"), ":flag_mv:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbc"), ":flag_mw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbd"), ":flag_mx:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbe"), ":flag_my:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbf"), ":flag_mz:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xa6"), ":flag_na:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xa8"), ":flag_nc:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xaa"), ":flag_ne:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xab"), ":flag_nf:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xac"), ":flag_ng:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xae"), ":flag_ni:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb1"), ":flag_nl:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb4"), ":flag_no:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb5"), ":flag_np:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb7"), ":flag_nr:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xba"), ":flag_nu:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xbf"), ":flag_nz:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb4\xf0\x9f\x87\xb2"), ":flag_om:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xa6"), ":flag_pa:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xaa"), ":flag_pe:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xab"), ":flag_pf:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xac"), ":flag_pg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xad"), ":flag_ph:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb0"), ":flag_pk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb1"), ":flag_pl:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb2"), ":flag_pm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb3"), ":flag_pn:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb7"), ":flag_pr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb8"), ":flag_ps:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb9"), ":flag_pt:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xbc"), ":flag_pw:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xbe"), ":flag_py:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb6\xf0\x9f\x87\xa6"), ":flag_qa:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xaa"), ":flag_re:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xb4"), ":flag_ro:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xb8"), ":flag_rs:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xba"), ":flag_ru:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xbc"), ":flag_rw:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa6"), ":flag_sa:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa7"), ":flag_sb:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa8"), ":flag_sc:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa9"), ":flag_sd:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xaa"), ":flag_se:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xac"), ":flag_sg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xad"), ":flag_sh:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xae"), ":flag_si:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xaf"), ":flag_sj:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb0"), ":flag_sk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb1"), ":flag_sl:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb2"), ":flag_sm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb3"), ":flag_sn:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb4"), ":flag_so:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb7"), ":flag_sr:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb8"), ":flag_ss:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb9"), ":flag_st:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbb"), ":flag_sv:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbd"), ":flag_sx:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbe"), ":flag_sy:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbf"), ":flag_sz:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xa6"), ":flag_ta:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xa8"), ":flag_tc:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xa9"), ":flag_td:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xab"), ":flag_tf:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xac"), ":flag_tg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xad"), ":flag_th:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xaf"), ":flag_tj:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb0"), ":flag_tk:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb1"), ":flag_tl:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb2"), ":flag_tm:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb3"), ":flag_tn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb4"), ":flag_to:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb7"), ":flag_tr:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb9"), ":flag_tt:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xbb"), ":flag_tv:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xbc"), ":flag_tw:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xbf"), ":flag_tz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xa6"), ":flag_ua:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xac"), ":flag_ug:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xb2"), ":flag_um:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xb8"), ":flag_us:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xbe"), ":flag_uy:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xbf"), ":flag_uz:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xa6"), ":flag_va:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xa8"), ":flag_vc:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xaa"), ":flag_ve:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xac"), ":flag_vg:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xae"), ":flag_vi:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xb3"), ":flag_vn:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xba"), ":flag_vu:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbc\xf0\x9f\x87\xab"), ":flag_wf:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbc\xf0\x9f\x87\xb8"), ":flag_ws:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbd\xf0\x9f\x87\xb0"), ":flag_xk:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbe\xf0\x9f\x87\xaa"), ":flag_ye:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbe\xf0\x9f\x87\xb9"), ":flag_yt:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbf\xf0\x9f\x87\xa6"), ":flag_za:"}), QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbf\xf0\x9f\x87\xb2"), ":flag_zm:"}),
|
||||
QVariant::fromValue(Emoji{QString::fromUtf8("\xf0\x9f\x87\xbf\xf0\x9f\x87\xbc"), ":flag_zw:"}),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2018 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2018 Black Hat <bhat@encom.eu.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-KDE-Accepted-LGPL
|
||||
/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
||||
* SPDX-License-Identifier: LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "filetypesingleton.h"
|
||||
#include <QImageReader>
|
||||
@@ -8,7 +9,7 @@
|
||||
static QStringList byteArrayListToStringList(const QByteArrayList &byteArrayList)
|
||||
{
|
||||
QStringList stringList;
|
||||
for (const QByteArray &byteArray : byteArrayList) {
|
||||
for(const QByteArray &byteArray : byteArrayList) {
|
||||
stringList.append(QString::fromLocal8Bit(byteArray));
|
||||
}
|
||||
return stringList;
|
||||
@@ -20,18 +21,17 @@ class FileTypeSingletonPrivate
|
||||
Q_DISABLE_COPY(FileTypeSingletonPrivate)
|
||||
public:
|
||||
FileTypeSingletonPrivate(FileTypeSingleton *qq);
|
||||
FileTypeSingleton *const q_ptr;
|
||||
FileTypeSingleton * const q_ptr;
|
||||
QMimeDatabase mimetypeDatabase;
|
||||
QStringList supportedImageFormats = byteArrayListToStringList(QImageReader::supportedImageFormats());
|
||||
QStringList supportedAnimatedImageFormats = byteArrayListToStringList(QMovie::supportedFormats());
|
||||
};
|
||||
|
||||
FileTypeSingletonPrivate::FileTypeSingletonPrivate(FileTypeSingleton *qq)
|
||||
: q_ptr(qq)
|
||||
FileTypeSingletonPrivate::FileTypeSingletonPrivate(FileTypeSingleton* qq) : q_ptr(qq)
|
||||
{
|
||||
}
|
||||
|
||||
FileTypeSingleton::FileTypeSingleton(QObject *parent)
|
||||
FileTypeSingleton::FileTypeSingleton(QObject* parent)
|
||||
: QObject(parent)
|
||||
, d_ptr(new FileTypeSingletonPrivate(this))
|
||||
{
|
||||
@@ -41,61 +41,61 @@ FileTypeSingleton::~FileTypeSingleton() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
QMimeType FileTypeSingleton::mimeTypeForName(const QString &nameOrAlias) const
|
||||
QMimeType FileTypeSingleton::mimeTypeForName(const QString& nameOrAlias) const
|
||||
{
|
||||
Q_D(const FileTypeSingleton);
|
||||
return d->mimetypeDatabase.mimeTypeForName(nameOrAlias);
|
||||
}
|
||||
|
||||
QMimeType FileTypeSingleton::mimeTypeForFile(const QString &fileName, MatchMode mode) const
|
||||
QMimeType FileTypeSingleton::mimeTypeForFile(const QString& fileName, MatchMode mode) const
|
||||
{
|
||||
Q_D(const FileTypeSingleton);
|
||||
return d->mimetypeDatabase.mimeTypeForFile(fileName, static_cast<QMimeDatabase::MatchMode>(mode));
|
||||
}
|
||||
|
||||
QMimeType FileTypeSingleton::mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const
|
||||
QMimeType FileTypeSingleton::mimeTypeForFile(const QFileInfo& fileInfo, MatchMode mode) const
|
||||
{
|
||||
Q_D(const FileTypeSingleton);
|
||||
return d->mimetypeDatabase.mimeTypeForFile(fileInfo, static_cast<QMimeDatabase::MatchMode>(mode));
|
||||
}
|
||||
|
||||
QList<QMimeType> FileTypeSingleton::mimeTypesForFileName(const QString &fileName) const
|
||||
QList<QMimeType> FileTypeSingleton::mimeTypesForFileName(const QString& fileName) const
|
||||
{
|
||||
Q_D(const FileTypeSingleton);
|
||||
return d->mimetypeDatabase.mimeTypesForFileName(fileName);
|
||||
}
|
||||
|
||||
QMimeType FileTypeSingleton::mimeTypeForData(const QByteArray &data) const
|
||||
QMimeType FileTypeSingleton::mimeTypeForData(const QByteArray& data) const
|
||||
{
|
||||
Q_D(const FileTypeSingleton);
|
||||
return d->mimetypeDatabase.mimeTypeForData(data);
|
||||
}
|
||||
|
||||
QMimeType FileTypeSingleton::mimeTypeForData(QIODevice *device) const
|
||||
QMimeType FileTypeSingleton::mimeTypeForData(QIODevice* device) const
|
||||
{
|
||||
Q_D(const FileTypeSingleton);
|
||||
return d->mimetypeDatabase.mimeTypeForData(device);
|
||||
}
|
||||
|
||||
QMimeType FileTypeSingleton::mimeTypeForUrl(const QUrl &url) const
|
||||
QMimeType FileTypeSingleton::mimeTypeForUrl(const QUrl& url) const
|
||||
{
|
||||
Q_D(const FileTypeSingleton);
|
||||
return d->mimetypeDatabase.mimeTypeForUrl(url);
|
||||
}
|
||||
|
||||
QMimeType FileTypeSingleton::mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device) const
|
||||
QMimeType FileTypeSingleton::mimeTypeForFileNameAndData(const QString& fileName, QIODevice* device) const
|
||||
{
|
||||
Q_D(const FileTypeSingleton);
|
||||
return d->mimetypeDatabase.mimeTypeForFileNameAndData(fileName, device);
|
||||
}
|
||||
|
||||
QMimeType FileTypeSingleton::mimeTypeForFileNameAndData(const QString &fileName, const QByteArray &data) const
|
||||
QMimeType FileTypeSingleton::mimeTypeForFileNameAndData(const QString& fileName, const QByteArray& data) const
|
||||
{
|
||||
Q_D(const FileTypeSingleton);
|
||||
return d->mimetypeDatabase.mimeTypeForFileNameAndData(fileName, data);
|
||||
}
|
||||
|
||||
QString FileTypeSingleton::suffixForFileName(const QString &fileName) const
|
||||
QString FileTypeSingleton::suffixForFileName(const QString& fileName) const
|
||||
{
|
||||
Q_D(const FileTypeSingleton);
|
||||
return d->mimetypeDatabase.suffixForFileName(fileName);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2015 Klaralvdalens Datakonsult AB
|
||||
// SPDX-FileCopyrightText: 2016 The Qt Company Ltd.
|
||||
// SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: LicenseRef-KDE-Accepted-LGPL
|
||||
/* SPDX-FileCopyrightText: 2015 Klaralvdalens Datakonsult AB
|
||||
* SPDX-FileCopyrightText: 2016 The Qt Company Ltd.
|
||||
* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
||||
* SPDX-License-Identifier: LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMimeDatabase>
|
||||
#include <QObject>
|
||||
#include <qqml.h>
|
||||
#include <QMimeDatabase>
|
||||
|
||||
class FileTypeSingletonPrivate;
|
||||
|
||||
@@ -26,7 +27,11 @@ public:
|
||||
// Most of the code in this public section was copy/pasted from qmimedatabase.h
|
||||
Q_INVOKABLE QMimeType mimeTypeForName(const QString &nameOrAlias) const;
|
||||
|
||||
enum MatchMode { MatchDefault, MatchExtension, MatchContent };
|
||||
enum MatchMode {
|
||||
MatchDefault,
|
||||
MatchExtension,
|
||||
MatchContent
|
||||
};
|
||||
Q_ENUM(MatchMode)
|
||||
|
||||
Q_INVOKABLE QMimeType mimeTypeForFile(const QString &fileName, MatchMode mode = MatchDefault) const;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "login.h"
|
||||
#include "connection.h"
|
||||
@@ -26,7 +29,7 @@ void Login::init()
|
||||
m_supportsPassword = false;
|
||||
m_ssoUrl = QUrl();
|
||||
|
||||
connect(this, &Login::matrixIdChanged, this, [=]() {
|
||||
connect(this, &Login::matrixIdChanged, this, [=](){
|
||||
setHomeserverReachable(false);
|
||||
|
||||
if (m_connection) {
|
||||
@@ -34,7 +37,7 @@ void Login::init()
|
||||
m_connection = nullptr;
|
||||
}
|
||||
|
||||
if (m_matrixId == "@") {
|
||||
if(m_matrixId == "@") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,7 +45,7 @@ void Login::init()
|
||||
Q_EMIT testingChanged();
|
||||
m_connection = new Connection(this);
|
||||
m_connection->resolveServer(m_matrixId);
|
||||
connect(m_connection, &Connection::loginFlowsChanged, this, [=]() {
|
||||
connect(m_connection, &Connection::loginFlowsChanged, this, [=](){
|
||||
setHomeserverReachable(true);
|
||||
m_testing = false;
|
||||
Q_EMIT testingChanged();
|
||||
@@ -72,7 +75,7 @@ QString Login::matrixId() const
|
||||
void Login::setMatrixId(const QString &matrixId)
|
||||
{
|
||||
m_matrixId = matrixId;
|
||||
if (!m_matrixId.startsWith('@')) {
|
||||
if(!m_matrixId.startsWith('@')) {
|
||||
m_matrixId.prepend('@');
|
||||
}
|
||||
Q_EMIT matrixIdChanged();
|
||||
@@ -105,8 +108,7 @@ void Login::login()
|
||||
m_isLoggingIn = true;
|
||||
Q_EMIT isLoggingInChanged();
|
||||
|
||||
setDeviceName("NeoChat " + QSysInfo::machineHostName() + " " + QSysInfo::productType() + " " + QSysInfo::productVersion() + " "
|
||||
+ QSysInfo::currentCpuArchitecture());
|
||||
setDeviceName("NeoChat " + QSysInfo::machineHostName() + " " + QSysInfo::productType() + " " + QSysInfo::productVersion() + " " + QSysInfo::currentCpuArchitecture());
|
||||
|
||||
m_connection = new Connection(this);
|
||||
m_connection->resolveServer(m_matrixId);
|
||||
@@ -169,24 +171,23 @@ QUrl Login::ssoUrl() const
|
||||
|
||||
void Login::loginWithSso()
|
||||
{
|
||||
SsoSession *session = m_connection->prepareForSso("NeoChat " + QSysInfo::machineHostName() + " " + QSysInfo::productType() + " "
|
||||
+ QSysInfo::productVersion() + " " + QSysInfo::currentCpuArchitecture());
|
||||
SsoSession *session = m_connection->prepareForSso("NeoChat " + QSysInfo::machineHostName() + " " + QSysInfo::productType() + " " + QSysInfo::productVersion() + " " + QSysInfo::currentCpuArchitecture());
|
||||
m_ssoUrl = session->ssoUrl();
|
||||
Q_EMIT ssoUrlChanged();
|
||||
connect(m_connection, &Connection::connected, [=]() {
|
||||
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);
|
||||
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();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user