This commit is contained in:
cutefishd 2021-03-16 15:02:20 +08:00
commit 6d82581776
85 changed files with 14538 additions and 0 deletions

54
.gitignore vendored Normal file
View file

@ -0,0 +1,54 @@
# C++ objects and libs
*.slo
*.lo
*.o
*.a
*.la
*.lai
*.so
*.so.*
*.dll
*.dylib
# Qt-es
object_script.*.Release
object_script.*.Debug
*_plugin_import.cpp
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
Makefile*
*build-*
*.qm
*.prl
# Qt unit tests
target_wrapper.*
# QtCreator
*.autosave
# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*
# QtCreator CMake
CMakeLists.txt.user*
# QtCreator 4.8< compilation database
compile_commands.json
# QtCreator local machine specific files for imported projects
*creator.user*
/build/*

69
CMakeLists.txt Normal file
View file

@ -0,0 +1,69 @@
cmake_minimum_required(VERSION 3.14)
project(cutefish-filemanager LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt5 COMPONENTS Core Quick Concurrent DBus LinguistTools REQUIRED)
find_package(KF5KIO)
find_package(MeuiKit REQUIRED)
add_executable(cutefish-filemanager
src/main.cpp
src/fm.cpp
src/fmh.cpp
src/fmstatic.cpp
src/fmlist.cpp
src/handy.cpp
src/placeslist.cpp
src/pathlist.cpp
src/baselist.cpp
src/basemodel.cpp
src/rubberband.cpp
src/iconthemeprovider.cpp
src/lib/foldermodel.cpp
src/lib/positioner.cpp
src/lib/itemviewadapter.cpp
src/lib/fileitemactions.cpp
src/lib/placesmodel.cpp
src/lib/placesitem.cpp
src/dialogs/propertiesdialog.cpp
src/desktop/desktopview.cpp
src/desktop/desktopsettings.cpp
qml.qrc
)
file(GLOB TS_FILES translations/*.ts)
qt5_create_translation(QM_FILES ${TS_FILES})
add_custom_target(translations DEPENDS ${QM_FILES} SOURCES ${TS_FILES})
add_dependencies(${PROJECT_NAME} translations)
target_link_libraries(cutefish-filemanager
PRIVATE
Qt5::Core
Qt5::GuiPrivate
Qt5::Quick
Qt5::Concurrent
Qt5::DBus
KF5::KIOCore
KF5::KIOFileWidgets
KF5::KIOWidgets
MeuiKit
)
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION /usr/bin)
install(FILES cutefish-filemanager.desktop DESTINATION "/usr/share/applications")
install(FILES ${QM_FILES} DESTINATION /usr/share/cutefish-filemanager/translations)

674
LICENSE Normal file
View file

@ -0,0 +1,674 @@
GNU 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.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. 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
them 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 prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. 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.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey 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;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If 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 convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU 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 that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
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.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
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.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
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
state 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 a brief idea of what it does.>
Copyright (C) <year> <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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program 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, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU 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. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

28
README.md Normal file
View file

@ -0,0 +1,28 @@
# File Manager
Cutefish File Manager
## Dependencies
```shell
sudo pacman -S extra-cmake-modules qt5-base qt5-quickcontrols2 taglib kio
```
## Build
```shell
mkdir build
cd build
cmake ..
make
```
## Install
```shell
sudo make install
```
## License
This project has been licensed by GPLv3.

View file

@ -0,0 +1,11 @@
[Desktop Entry]
Type=Application
Name=File Manager
Name[zh_CN]=
GenericName=File Manager
Comment=Cutefish File Manager
Exec=cutefish-fm %U
MimeType=inode/directory;
Icon=file-system-manager
Categories=FileManager;Utility;Core;Qt;
StartupNotify=true

14
images/dark/go-next.svg Normal file
View file

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#FFFFFF;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m7.707031 3l-.707031.707031 6.125 6.125 1.167969 1.167969-1.167969 1.167969-6.125 6.125.707031.707031 6.125-6.125 1.875-1.875-1.875-1.875-6.125-6.125"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 500 B

View file

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#FFFFFF;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m14.292969 3l-6.125 6.125-1.875 1.875 1.875 1.875 6.125 6.125.707031-.707031-6.125-6.125-1.167969-1.167969 1.167969-1.167969 6.125-6.125-.707031-.707031"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 503 B

13
images/dark/grid.svg Normal file
View file

@ -0,0 +1,13 @@
<svg version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
<defs>
<style id="current-color-scheme" type="text/css">.ColorScheme-Text {
color:#FFFFFF;
}</style>
</defs>
<g class="ColorScheme-Text" fill="currentColor">
<path d="m5 3c-1.108 0-2 0.892-2 2v3c0 1.108 0.892 2 2 2h3c1.108 0 2-0.892 2-2v-3c0-1.108-0.892-2-2-2h-3zm0 1h3c0.554 0 1 0.446 1 1v3c0 0.554-0.446 1-1 1h-3c-0.554 0-1-0.446-1-1v-3c0-0.554 0.446-1 1-1z"/>
<path d="m14 3c-1.108 0-2 0.892-2 2v3c0 1.108 0.892 2 2 2h3c1.108 0 2-0.892 2-2v-3c0-1.108-0.892-2-2-2zm0 1h3c0.554 0 1 0.446 1 1v3c0 0.554-0.446 1-1 1h-3c-0.554 0-1-0.446-1-1v-3c0-0.554 0.446-1 1-1z"/>
<path d="m5 12c-1.108 0-2 0.892-2 2v3c0 1.108 0.892 2 2 2h3c1.108 0 2-0.892 2-2v-3c0-1.108-0.892-2-2-2zm0 1h3c0.554 0 1 0.446 1 1v3c0 0.554-0.446 1-1 1h-3c-0.554 0-1-0.446-1-1v-3c0-0.554 0.446-1 1-1z"/>
<path d="m14 12c-1.108 0-2 0.892-2 2v3c0 1.108 0.892 2 2 2h3c1.108 0 2-0.892 2-2v-3c0-1.108-0.892-2-2-2zm0 1h3c0.554 0 1 0.446 1 1v3c0 0.554-0.446 1-1 1h-3c-0.554 0-1-0.446-1-1v-3c0-0.554 0.446-1 1-1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

105
images/dark/list.svg Normal file
View file

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
viewBox="0 0 22 22"
id="svg17"
sodipodi:docname="view-list-tree.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
<metadata
id="metadata21">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2160"
inkscape:window-height="1312"
id="namedview19"
showgrid="false"
inkscape:zoom="33.409091"
inkscape:cx="11"
inkscape:cy="11"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:window-maximized="1"
inkscape:current-layer="svg17" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">.ColorScheme-Text {
color:#FFFFFF;
}</style>
</defs>
<rect
class="ColorScheme-Text"
x="7"
y="5"
width="12"
height="1"
ry="0"
fill="currentColor"
id="rect5" />
<rect
class="ColorScheme-Text"
x="7"
y="11"
width="12"
height="1"
ry="0"
fill="currentColor"
id="rect9" />
<rect
class="ColorScheme-Text"
x="7"
y="17"
width="12"
height="1"
ry="0"
fill="currentColor"
id="rect13" />
<rect
style="fill:#FFFFFF;fill-opacity:1;stroke-width:0.768904"
id="rect855"
width="3"
height="3"
x="3"
y="4"
ry="1.5" />
<rect
style="fill:#FFFFFF;fill-opacity:1;stroke-width:0.768904"
id="rect855-3"
width="3"
height="3"
x="3"
y="10"
ry="1.5" />
<rect
style="fill:#FFFFFF;fill-opacity:1;stroke-width:0.768904"
id="rect855-3-6"
width="3"
height="3"
x="3"
y="16"
ry="1.5" />
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

6
images/folder-desktop.svg Executable file
View file

@ -0,0 +1,6 @@
<svg width="22" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style id="current-color-scheme" type="text/css">.ColorScheme-Text { color:#363636; }</style>
</defs>
<path class="ColorScheme-Text" d="m5 5c-1.108 0-2 0.892-2 2v9c0 1.108 0.892 2 2 2h12c1.108 0 2-0.892 2-2v-9c0-1.108-0.892-2-2-2zm0 1h12c0.554 0 1 0.446 1 1h-14c0-0.554 0.446-1 1-1zm-1 2h14v8c0 0.554-0.446 1-1 1h-12c-0.554 0-1-0.446-1-1zm3 6c-0.554 0-1 0.446-1 1s0.446 1 1 1h8c0.554 0 1-0.446 1-1s-0.446-1-1-1z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 532 B

6
images/folder-document.svg Executable file
View file

@ -0,0 +1,6 @@
<svg width="22" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style id="current-color-scheme" type="text/css">.ColorScheme-Text { color:#363636; }</style>
</defs>
<path class="ColorScheme-Text" d="m10 3c-1.108 0-2 0.892-2 2v1h-2c-1.108 0-2 0.892-2 2v9c0 1.108 0.892 2 2 2h6c1.108 0 2-0.892 2-2v-1h2c1.108 0 2-0.892 2-2v-6l-5-5h-1zm0 1h2v5h5v5c0 0.554-0.446 1-1 1h-2v-4l-5-5v-1c0-0.1385 0.027656-0.27091 0.078125-0.39062 0.15141-0.35916 0.50638-0.60938 0.92188-0.60938zm3 0.41406 3.5859 3.5859h-3.5859zm-7 2.5859h2v5h5v5c0 0.554-0.446 1-1 1h-6c-0.554 0-1-0.446-1-1v-9c0-0.1385 0.027656-0.27091 0.078125-0.39062 0.15141-0.35916 0.50638-0.60938 0.92188-0.60938zm3 0.41406 3.5859 3.5859h-3.5859z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 751 B

59
images/folder-download.svg Executable file
View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
version="1.1"
id="svg7"
sodipodi:docname="folder-download.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
<metadata
id="metadata11">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2160"
inkscape:window-height="1304"
id="namedview9"
showgrid="false"
inkscape:zoom="33.409091"
inkscape:cx="11"
inkscape:cy="11"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg7" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">.ColorScheme-Text { color:#363636; } .ColorScheme-Text { color:#363636; }</style>
</defs>
<path
class="ColorScheme-Text"
d="m 11,3 a 8,8 0 0 0 -8,8 8,8 0 0 0 8,8 8,8 0 0 0 8,-8 8,8 0 0 0 -8,-8 z m 0,1.0666667 A 6.9333333,6.9333333 0 0 1 17.933333,11 6.9333333,6.9333333 0 0 1 11,17.933333 6.9333333,6.9333333 0 0 1 4.0666667,11 6.9333333,6.9333333 0 0 1 11,4.0666667 Z m 0,3.2 c -0.295467,0 -0.533333,0.2378666 -0.533333,0.5333333 v 5.112533 L 9.11456,11.560427 c -0.208928,-0.208928 -0.5452373,-0.208928 -0.7541653,0 -0.208928,0.208928 -0.208928,0.545237 0,0.754165 l 2.2625063,2.262507 c 0.05014,0.05014 0.110123,0.08968 0.177088,0.116672 0.128363,0.05223 0.271638,0.05223 0.4,0 0.06697,-0.02699 0.126934,-0.06652 0.177088,-0.116672 l 2.262507,-2.262507 c 0.208928,-0.208928 0.208928,-0.545237 0,-0.754165 -0.208928,-0.208928 -0.545237,-0.208928 -0.754165,0 l -1.352107,1.352106 V 7.8 c 0,-0.2954667 -0.237867,-0.5333333 -0.533333,-0.5333333 z"
fill="currentColor"
id="path5"
style="stroke-width:1.06667" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

6
images/folder-home.svg Executable file
View file

@ -0,0 +1,6 @@
<svg width="22" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style id="current-color-scheme" type="text/css">.ColorScheme-Text { color:#363636; }</style>
</defs>
<path class="ColorScheme-Text" d="m11 4.0058c-0.12781 0-0.25558 0.04855-0.35352 0.14648l-7.0703 7.0723c-0.19587 0.19587-0.19587 0.51116 0 0.70703s0.51116 0.19587 0.70703 0l0.7168-0.7168v5.7852c0 1.108 0.892 2 2 2h8c1.108 0 2-0.892 2-2v-5.7852l0.7168 0.7168c0.19587 0.19587 0.51116 0.19587 0.70703 0s0.19587-0.51116 0-0.70703l-7.0703-7.0723c-0.09793-0.097934-0.22571-0.14648-0.35352-0.14648zm0 1.209 5 5v6.7852c0 0.554-0.446 1-1 1h-2v-3c0-1.108-0.892-2-2-2s-2 0.892-2 2v3h-2c-0.554 0-1-0.446-1-1v-6.7852z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 726 B

6
images/folder-music.svg Executable file
View file

@ -0,0 +1,6 @@
<svg width="22" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style id="current-color-scheme" type="text/css">.ColorScheme-Text { color:#363636; } .ColorScheme-Highlight { color:#5294e2; }</style>
</defs>
<path class="ColorScheme-Text" fill="currentColor" d="m16.375 3-7.3774 1.9053c-0.55422 0.14836-1.0009 0.73837-1.0009 1.3116v9.0892c-0.562-0.294-1.2839-0.41299-2.0322-0.21874-1.3503 0.35052-2.1966 1.4838-1.9071 2.5303 0.28945 1.0465 1.6203 1.6309 2.9706 1.2804 1.0773-0.27964 1.82-1.071 1.9384-1.9053l0.031278-8.5892 7.0021-1.8741v6.7776c-0.562-0.294-1.2831-0.41299-2.0314-0.21874-1.3503 0.35052-2.1966 1.4838-1.9071 2.5303 0.28945 1.0465 1.6194 1.6309 2.9697 1.2804 1.0773-0.27964 1.82-1.071 1.9384-1.9053l0.03128-11.181c0-0.42993-0.26627-0.752-0.62557-0.81248z"/>
</svg>

After

Width:  |  Height:  |  Size: 806 B

6
images/folder-picture.svg Executable file
View file

@ -0,0 +1,6 @@
<svg width="22" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style id="current-color-scheme" type="text/css">.ColorScheme-Text { color:#363636; } .ColorScheme-Text { color:#363636; }</style>
</defs>
<path class="ColorScheme-Text" d="m5 5c-1.108 0-2 0.892-2 2v9c0 1.108 0.892 2 2 2h12c1.108 0 2-0.892 2-2v-9c0-1.108-0.892-2-2-2zm0 1h12c0.554 0 1 0.446 1 1v9l-5-5-3.4794 4-2.5206-2-3 3v-9c0-0.554 0.446-1 1-1zm2.5 2c-0.82843 0-1.5 0.67157-1.5 1.5s0.67157 1.5 1.5 1.5 1.5-0.67157 1.5-1.5-0.67157-1.5-1.5-1.5z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 566 B

6
images/folder-video.svg Executable file
View file

@ -0,0 +1,6 @@
<svg width="22" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style id="current-color-scheme" type="text/css">.ColorScheme-Text { color:#363636; }</style>
</defs>
<path class="ColorScheme-Text" d="m5 5c-1.108 0-2 0.892-2 2v9c0 1.108 0.892 2 2 2h12c1.108 0 2-0.892 2-2v-9c0-1.108-0.892-2-2-2zm0 1h1v1h-2c0-0.554 0.446-1 1-1zm2 0h8v5h-8zm9 0h1c0.554 0 1 0.446 1 1h-2zm-12 2h2v1h-2zm12 0h2v1h-2zm-12 2h2v1h-2zm12 0h2v1h-2zm-12 2h2v1h-2zm3 0h8v5h-8zm9 0h2v1h-2zm-12 2h2v1h-2zm12 0h2v1h-2zm-12 2h2v1h-1c-0.554 0-1-0.446-1-1zm12 0h2c0 0.554-0.446 1-1 1h-1z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 610 B

14
images/light/go-next.svg Normal file
View file

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#363636;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m7.707031 3l-.707031.707031 6.125 6.125 1.167969 1.167969-1.167969 1.167969-6.125 6.125.707031.707031 6.125-6.125 1.875-1.875-1.875-1.875-6.125-6.125"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 500 B

View file

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#363636;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m14.292969 3l-6.125 6.125-1.875 1.875 1.875 1.875 6.125 6.125.707031-.707031-6.125-6.125-1.167969-1.167969 1.167969-1.167969 6.125-6.125-.707031-.707031"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 503 B

13
images/light/grid.svg Normal file
View file

@ -0,0 +1,13 @@
<svg version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
<defs>
<style id="current-color-scheme" type="text/css">.ColorScheme-Text {
color:#363636;
}</style>
</defs>
<g class="ColorScheme-Text" fill="currentColor">
<path d="m5 3c-1.108 0-2 0.892-2 2v3c0 1.108 0.892 2 2 2h3c1.108 0 2-0.892 2-2v-3c0-1.108-0.892-2-2-2h-3zm0 1h3c0.554 0 1 0.446 1 1v3c0 0.554-0.446 1-1 1h-3c-0.554 0-1-0.446-1-1v-3c0-0.554 0.446-1 1-1z"/>
<path d="m14 3c-1.108 0-2 0.892-2 2v3c0 1.108 0.892 2 2 2h3c1.108 0 2-0.892 2-2v-3c0-1.108-0.892-2-2-2zm0 1h3c0.554 0 1 0.446 1 1v3c0 0.554-0.446 1-1 1h-3c-0.554 0-1-0.446-1-1v-3c0-0.554 0.446-1 1-1z"/>
<path d="m5 12c-1.108 0-2 0.892-2 2v3c0 1.108 0.892 2 2 2h3c1.108 0 2-0.892 2-2v-3c0-1.108-0.892-2-2-2zm0 1h3c0.554 0 1 0.446 1 1v3c0 0.554-0.446 1-1 1h-3c-0.554 0-1-0.446-1-1v-3c0-0.554 0.446-1 1-1z"/>
<path d="m14 12c-1.108 0-2 0.892-2 2v3c0 1.108 0.892 2 2 2h3c1.108 0 2-0.892 2-2v-3c0-1.108-0.892-2-2-2zm0 1h3c0.554 0 1 0.446 1 1v3c0 0.554-0.446 1-1 1h-3c-0.554 0-1-0.446-1-1v-3c0-0.554 0.446-1 1-1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

105
images/light/list.svg Normal file
View file

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
viewBox="0 0 22 22"
id="svg17"
sodipodi:docname="view-list-tree.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
<metadata
id="metadata21">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2160"
inkscape:window-height="1312"
id="namedview19"
showgrid="false"
inkscape:zoom="33.409091"
inkscape:cx="11"
inkscape:cy="11"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:window-maximized="1"
inkscape:current-layer="svg17" />
<defs
id="defs3">
<style
id="current-color-scheme"
type="text/css">.ColorScheme-Text {
color:#363636;
}</style>
</defs>
<rect
class="ColorScheme-Text"
x="7"
y="5"
width="12"
height="1"
ry="0"
fill="currentColor"
id="rect5" />
<rect
class="ColorScheme-Text"
x="7"
y="11"
width="12"
height="1"
ry="0"
fill="currentColor"
id="rect9" />
<rect
class="ColorScheme-Text"
x="7"
y="17"
width="12"
height="1"
ry="0"
fill="currentColor"
id="rect13" />
<rect
style="fill:#363636;fill-opacity:1;stroke-width:0.768904"
id="rect855"
width="3"
height="3"
x="3"
y="4"
ry="1.5" />
<rect
style="fill:#363636;fill-opacity:1;stroke-width:0.768904"
id="rect855-3"
width="3"
height="3"
x="3"
y="10"
ry="1.5" />
<rect
style="fill:#363636;fill-opacity:1;stroke-width:0.768904"
id="rect855-3-6"
width="3"
height="3"
x="3"
y="16"
ry="1.5" />
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

6
images/user-trash.svg Executable file
View file

@ -0,0 +1,6 @@
<svg width="22" height="22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style id="current-color-scheme" type="text/css">.ColorScheme-Text { color:#363636; }</style>
</defs>
<path class="ColorScheme-Text" d="m9 2.9998c-0.554 0-1 0.446-1 1v1h-4.5c-0.277 0-0.5 0.223-0.5 0.5s0.223 0.5 0.5 0.5h0.49805l0.54688 11.061c0.0612 1.0155 0.90615 1.9395 1.998 1.9395h7.9141c1.0919 0 1.9368-0.92391 1.998-1.9395l0.54688-11.061h0.49805c0.277 0 0.5-0.223 0.5-0.5s-0.223-0.5-0.5-0.5h-4.5v-1c0-0.554-0.446-1-1-1h-3zm0 1h3v1h-3zm-4 2h11l-0.54297 11c-0.03333 0.553-0.446 1-1 1h-7.9141c-0.554 0-0.96667-0.447-1-1l-0.54297-11zm2.5 2c-0.277 0-0.5 0.223-0.5 0.5v7c0 0.277 0.223 0.5 0.5 0.5s0.5-0.223 0.5-0.5v-7c0-0.277-0.223-0.5-0.5-0.5zm3 0c-0.277 0-0.5 0.223-0.5 0.5v7c0 0.277 0.223 0.5 0.5 0.5s0.5-0.223 0.5-0.5v-7c0-0.277-0.223-0.5-0.5-0.5zm3 0c-0.277 0-0.5 0.223-0.5 0.5v7c0 0.277 0.223 0.5 0.5 0.5s0.5-0.223 0.5-0.5v-7c0-0.277-0.223-0.5-0.5-0.5z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 978 B

40
qml.qrc Normal file
View file

@ -0,0 +1,40 @@
<RCC>
<qresource prefix="/">
<file>qml/main.qml</file>
<file>qml/SideBar.qml</file>
<file>qml/SidebarItem.qml</file>
<file>qml/FolderListView.qml</file>
<file>qml/PathBar.qml</file>
<file>qml/FolderListDelegate.qml</file>
<file>images/dark/go-next.svg</file>
<file>images/dark/go-previous.svg</file>
<file>images/dark/grid.svg</file>
<file>images/dark/list.svg</file>
<file>images/light/go-next.svg</file>
<file>images/light/go-previous.svg</file>
<file>images/light/grid.svg</file>
<file>images/light/list.svg</file>
<file>qml/IconButton.qml</file>
<file>images/folder-download.svg</file>
<file>images/folder-home.svg</file>
<file>images/folder-music.svg</file>
<file>images/folder-picture.svg</file>
<file>images/folder-video.svg</file>
<file>images/folder-document.svg</file>
<file>qml/GlobalSettings.qml</file>
<file>qml/FolderIconView.qml</file>
<file>qml/FolderIconDelegate.qml</file>
<file>qml/BrowserView.qml</file>
<file>qml/ItemMenu.qml</file>
<file>qml/BrowserMenu.qml</file>
<file>qml/Desktop/DesktopFolderView.qml</file>
<file>qml/Desktop/FolderViewDropArea.qml</file>
<file>qml/Desktop/FolderItemDelegate.qml</file>
<file>qml/Desktop/Desktop.qml</file>
<file>qml/Desktop/FolderTools.js</file>
<file>qml/Dialogs/PropertiesDialog.qml</file>
<file>qml/IconDelegate.qml</file>
<file>images/folder-desktop.svg</file>
<file>images/user-trash.svg</file>
</qresource>
</RCC>

61
qml/BrowserMenu.qml Normal file
View file

@ -0,0 +1,61 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import Cutefish.FileManager 1.0
import MeuiKit 1.0 as Meui
Menu {
id: control
property FMList currentList
signal emptyTrashClicked()
signal propertiesClicked()
signal selectAllClicked()
MenuItem {
id: newFolderItem
text: qsTr("New Folder")
enabled: currentList.pathType !== FMList.TRASH_PATH
}
MenuSeparator {
visible: newFolderItem.visible && pasteItem.visible
}
MenuItem {
id: pasteItem
text: qsTr("Paste")
onTriggered: paste()
enabled: currentList.pathType !== FMList.TRASH_PATH
}
MenuItem {
text: qsTr("Select All")
onTriggered: control.selectAllClicked()
}
MenuItem {
id: terminal
text: qsTr("Open in Terminal")
}
MenuItem {
id: properties
text: qsTr("Properties")
onTriggered: {
propertiesClicked()
close()
}
}
MenuItem {
id: emptyItem
text: qsTr("Empty Trash")
visible: currentList.pathType === FMList.TRASH_PATH
onTriggered: control.emptyTrashClicked()
}
function show(parent = control, x, y) {
popup(parent, x, y)
}
}

107
qml/BrowserView.qml Normal file
View file

@ -0,0 +1,107 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.0
import MeuiKit 1.0 as Meui
import Cutefish.FileManager 1.0 as FM
Item {
id: control
property alias model: dirModel
property alias url: dirModel.url
property alias currentView: viewLoader.item
signal openPathBar
FM.FolderModel {
id: dirModel
sortDirsFirst: true
parseDesktopFiles: true
url: dirModel.homePath()
previews: true
previewPlugins: []
}
FM.Positioner {
id: positioner
folderModel: dirModel
enabled: true
}
Rectangle {
anchors.fill: parent
anchors.topMargin: 0
anchors.leftMargin: Meui.Theme.smallRadius / 2
anchors.rightMargin: Meui.Theme.smallRadius
anchors.bottomMargin: Meui.Theme.smallRadius
radius: Meui.Theme.smallRadius
color: Meui.Theme.backgroundColor
Label {
anchors.centerIn: parent
text: qsTr("No Files")
font.pointSize: 20
visible: dirModel.status === FM.FolderModel.Ready && currentView.count === 0
}
}
Loader {
id: viewLoader
anchors.fill: parent
anchors.bottomMargin: Meui.Units.largeSpacing
sourceComponent: switch (settings.viewMethod) {
case 0: return listViewBrowser
case 1: return gridViewBrowser
}
}
Component {
id: listViewBrowser
FolderListView {
id: _listViewBrowser
anchors.fill: parent
model: dirModel
leftMargin: Meui.Units.largeSpacing + Meui.Units.smallSpacing
rightMargin: Meui.Units.largeSpacing + Meui.Units.smallSpacing
topMargin: Meui.Units.smallSpacing
bottomMargin: Meui.Units.largeSpacing * 2
delegate: FolderListDelegate {
id: listDelegate
width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin
height: 48
}
}
}
Component {
id: gridViewBrowser
FolderIconView {
id: _gridViewBrowser
anchors.fill: parent
model: dirModel
leftMargin: Meui.Units.largeSpacing
rightMargin: Meui.Units.largeSpacing
delegate: FolderIconDelegate {
id: iconDelegate
height: _gridViewBrowser.cellHeight
width: _gridViewBrowser.cellWidth
}
}
}
Component.onCompleted: {
control.currentView.forceActiveFocus()
}
function openFolder(url) {
dirModel.url = url
}
}

97
qml/Desktop/Desktop.qml Normal file
View file

@ -0,0 +1,97 @@
import QtQuick 2.4
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import Qt.labs.platform 1.0
import Cutefish.FileManager 1.0
import MeuiKit 1.0 as Meui
FolderViewDropArea {
id: root
visible: true
preventStealing: true
property bool containsDrag: false
folderView: folderViewLayer.item
function isDrag(fromX, fromY, toX, toY) {
var length = Math.abs(fromX - toX) + Math.abs(fromY - toY);
return length >= Qt.styleHints.startDragDistance;
}
function isFileDrag(event) {
var taskUrl = event.mimeData.formats.indexOf("text/x-orgkdeplasmataskmanager_taskurl") !== -1;
var arkService = event.mimeData.formats.indexOf("application/x-kde-ark-dndextract-service") !== -1;
var arkPath = event.mimeData.formats.indexOf("application/x-kde-ark-dndextract-path") !== -1;
return (event.mimeData.hasUrls || taskUrl || (arkService && arkPath));
}
onDragEnter: {
if (!isFileDrag(event))
event.ignore();
// Firefox tabs are regular drags. Since all of our drop handling is asynchronous
// we would accept this drop and have Firefox not spawn a new window. (Bug 337711)
if (event.mimeData.formats.indexOf("application/x-moz-tabbrowser-tab") > -1) {
event.ignore();
}
}
onDragMove: {
}
onDragLeave: {
}
onDrop: {
}
DesktopSettings {
id: settings
}
Image {
id: wallpaper
anchors.fill: parent
source: "file://" + settings.wallpaper
sourceSize: Qt.size(width, height)
fillMode: Image.PreserveAspectCrop
clip: true
cache: false
ColorOverlay {
id: dimsWallpaper
anchors.fill: wallpaper
source: wallpaper
color: "#000000"
opacity: Meui.Theme.darkMode && settings.dimsWallpaper ? 0.4 : 0.0
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
}
Loader {
id: folderViewLayer
anchors.fill: parent
property bool ready: status == Loader.Ready
property Item view: item ? item : null
property QtObject model: item ? item.model : null
focus: true
active: true
asynchronous: false
source: "DesktopFolderView.qml"
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,116 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.0
import MeuiKit 1.0 as Meui
import org.kde.plasma.core 2.0 as PlasmaCore
Item {
id: main
property int index: model.index
property string name: model.blank ? "" : model.display
property bool blank: model.blank
property bool isDir: model.blank ? false : model.isDir
property bool selected: model.blank ? false : model.selected
property Item frame: contentItem
property Item iconArea: icon
property Item labelArea: label
property bool hovered: (main.GridView.view.hoveredItem === main)
property color hoveredColor: Qt.rgba(Meui.Theme.highlightColor.r,
Meui.Theme.highlightColor.g,
Meui.Theme.highlightColor.b, 0.1)
property color selectedColor: Qt.rgba(Meui.Theme.highlightColor.r,
Meui.Theme.highlightColor.g,
Meui.Theme.highlightColor.b, 0.9)
Accessible.name: name
Accessible.role: Accessible.Canvas
onSelectedChanged: {
if (selected && !blank) {
contentItem.grabToImage(function(result) {
dir.addItemDragImage(positioner.map(index), main.x + contentItem.x, main.y + contentItem.y, contentItem.width, contentItem.height, result.image);
});
}
}
Rectangle {
anchors.fill: parent
anchors.margins: Meui.Units.largeSpacing
radius: Meui.Theme.bigRadius
color: selected ? selectedColor : main.hovered ? hoveredColor : "transparent"
border.color: Qt.rgba(Meui.Theme.highlightColor.r,
Meui.Theme.highlightColor.g,
Meui.Theme.highlightColor.b, 0.3)
border.width: main.hovered || selected ? 1 : 0
}
Item {
id: contentItem
anchors.fill: parent
anchors.margins: Meui.Units.largeSpacing
PlasmaCore.IconItem {
id: icon
z: 2
anchors.top: parent.top
anchors.topMargin: Meui.Units.smallSpacing
anchors.horizontalCenter: parent.horizontalCenter
height: main.height * 0.55
width: height
animated: false
usesPlasmaTheme: false
smooth: true
source: model.blank ? "" : model.decoration
overlays: model.blank ? "" : model.overlays
}
Label {
id: label
z: 2
anchors.top: icon.bottom
anchors.topMargin: Meui.Units.smallSpacing
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
width: parent.width
textFormat: Text.PlainText
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignTop
wrapMode: Text.Wrap
elide: Text.ElideRight
color: "#FFFFFF"
opacity: model.isHidden ? 0.6 : 1
text: model.blank ? "" : model.display
font.italic: model.isLink
}
DropShadow {
anchors.fill: label
z: 1
horizontalOffset: 1
verticalOffset: 1
radius: Math.round(4 * Meui.Units.devicePixelRatio)
samples: radius * 2 + 1
spread: 0.35
color: "black"
opacity: model.isHidden ? 0.4 : 0.5
source: label
visible: !selected
}
}
}

View file

@ -0,0 +1,43 @@
function effectiveNavDirection(flow, layoutDirection, direction) {
if (direction === Qt.LeftArrow) {
if (flow === GridView.FlowLeftToRight) {
if (layoutDirection === Qt.LeftToRight) {
return Qt.LeftArrow;
} else {
return Qt.RightArrow;
}
} else {
if (layoutDirection === Qt.LeftToRight) {
return Qt.UpArrow;
} else {
return Qt.DownArrow;
}
}
} else if (direction === Qt.RightArrow) {
if (flow === GridView.FlowLeftToRight) {
if (layoutDirection === Qt.LeftToRight) {
return Qt.RightArrow;
} else {
return Qt.LeftArrow;
}
} else {
if (layoutDirection === Qt.LeftToRight) {
return Qt.DownArrow;
} else {
return Qt.UpArrow;
}
}
} else if (direction === Qt.UpArrow) {
if (flow === GridView.FlowLeftToRight) {
return Qt.UpArrow;
} else {
return Qt.LeftArrow;
}
} else if (direction === Qt.DownArrow) {
if (flow === GridView.FlowLeftToRight) {
return Qt.DownArrow;
} else {
return Qt.RightArrow
}
}
}

View file

@ -0,0 +1,53 @@
import QtQuick 2.12
import MeuiKit 1.0 as Meui
import org.kde.draganddrop 2.0 as DragDrop
DragDrop.DropArea {
id: dropArea
property Item folderView: null
function handleDragMove(folderView, pos) {
// Trigger autoscroll.
folderView.scrollLeft = (pos.x < (Meui.Units.largeSpacing * 3));
folderView.scrollRight = (pos.x > width - (Meui.Units.largeSpacing * 3));
folderView.scrollUp = (pos.y < (Meui.Units.largeSpacing * 3));
folderView.scrollDown = (pos.y > height - (Meui.Units.largeSpacing * 3));
folderView.handleDragMove(pos.x, pos.y);
}
function handleDragEnd(folderView) {
// Cancel autoscroll.
folderView.scrollLeft = false;
folderView.scrollRight = false;
folderView.scrollUp = false;
folderView.scrollDown = false;
folderView.endDragMove();
}
onDragMove: {
// TODO: We should reject drag moves onto file items that don't accept drops
// (cf. QAbstractItemModel::flags() here, but DeclarativeDropArea currently
// is currently incapable of rejecting drag events.
if (folderView) {
handleDragMove(folderView, mapToItem(folderView, event.x, event.y));
}
}
onDragLeave: {
if (folderView) {
handleDragEnd(folderView);
}
}
onDrop: {
if (folderView) {
handleDragEnd(folderView);
folderView.drop(folderView, event, mapToItem(folderView, event.x, event.y));
}
}
}

View file

@ -0,0 +1,174 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import MeuiKit 1.0 as Meui
Window {
id: control
title: qsTr("Properties")
flags: Qt.Dialog | Qt.WindowStaysOnTopHint
visible: true
onVisibleChanged: {
if (visible) updateWindowSize()
}
Component.onCompleted: {
updateWindowSize()
}
function updateWindowSize() {
if (visible) {
control.width = _mainLayout.implicitWidth + _mainLayout.anchors.leftMargin + _mainLayout.anchors.rightMargin
control.height = _mainLayout.implicitHeight + _mainLayout.anchors.topMargin + _mainLayout.anchors.bottomMargin
control.minimumWidth = control.width
control.minimumHeight = control.height
control.maximumWidth = control.width
control.maximumHeight = control.height
if (_textField.enabled)
_textField.forceActiveFocus()
}
}
Item {
id: _contentItem
anchors.fill: parent
focus: true
Keys.enabled: true
Keys.onEscapePressed: control.close()
ColumnLayout {
id: _mainLayout
anchors.fill: parent
anchors.leftMargin: Meui.Units.largeSpacing * 2
anchors.rightMargin: Meui.Units.largeSpacing * 2
anchors.topMargin: Meui.Units.largeSpacing
anchors.bottomMargin: Meui.Units.largeSpacing
spacing: Meui.Units.largeSpacing
RowLayout {
spacing: Meui.Units.largeSpacing * 2
Image {
width: 64
height: width
sourceSize: Qt.size(width, height)
source: "image://icontheme/" + main.iconName
}
TextField {
id: _textField
text: main.fileName
focus: true
Layout.fillWidth: true
Keys.onEscapePressed: control.close()
enabled: !main.multiple
}
}
GridLayout {
columns: 2
columnSpacing: Meui.Units.largeSpacing
rowSpacing: Meui.Units.largeSpacing
Layout.alignment: Qt.AlignTop
onHeightChanged: updateWindowSize()
Label {
text: qsTr("Type:")
Layout.alignment: Qt.AlignRight
visible: mimeType.visible
}
Label {
id: mimeType
text: main.mimeType
visible: text
}
Label {
text: qsTr("Location:")
Layout.alignment: Qt.AlignRight
}
Label {
id: location
text: main.location
}
Label {
text: qsTr("Size:")
Layout.alignment: Qt.AlignRight
visible: size.visible
}
Label {
id: size
text: main.size
visible: text
}
Label {
text: qsTr("Created:")
Layout.alignment: Qt.AlignRight
visible: creationTime.visible
}
Label {
id: creationTime
text: main.creationTime
visible: text
}
Label {
text: qsTr("Modified:")
Layout.alignment: Qt.AlignRight
visible: modifiedTime.visible
}
Label {
id: modifiedTime
text: main.modifiedTime
visible: text
}
Label {
text: qsTr("Accessed:")
Layout.alignment: Qt.AlignRight
visible: accessTime.visible
}
Label {
id: accessTime
text: main.accessedTime
visible: text
}
}
Item {
height: Meui.Units.largeSpacing
}
RowLayout {
Layout.alignment: Qt.AlignRight
spacing: Meui.Units.largeSpacing
Button {
text: qsTr("Cancel")
Layout.fillWidth: true
onClicked: control.close()
}
Button {
text: qsTr("OK")
Layout.fillWidth: true
onClicked: control.close()
}
}
}
}
}

102
qml/FolderIconDelegate.qml Normal file
View file

@ -0,0 +1,102 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.0
import MeuiKit 1.0 as Meui
import org.kde.plasma.core 2.0 as PlasmaCore
Item {
id: main
property int index: model.index
property string name: model.blank ? "" : model.display
property bool blank: model.blank
property bool isDir: model.blank ? false : model.isDir
property bool selected: model.blank ? false : model.selected
property Item frame: contentItem
property Item iconArea: icon
property Item labelArea: label
property bool hovered: (main.GridView.view.hoveredItem === main)
property color hoveredColor: Qt.rgba(Meui.Theme.highlightColor.r,
Meui.Theme.highlightColor.g,
Meui.Theme.highlightColor.b, 0.1)
property color selectedColor: Qt.rgba(Meui.Theme.highlightColor.r,
Meui.Theme.highlightColor.g,
Meui.Theme.highlightColor.b, 0.9)
Accessible.name: name
Accessible.role: Accessible.Canvas
onSelectedChanged: {
if (selected && !blank) {
contentItem.grabToImage(function(result) {
dirModel.addItemDragImage(positioner.map(index), main.x + contentItem.x, main.y + contentItem.y, contentItem.width, contentItem.height, result.image);
});
}
}
Rectangle {
anchors.fill: parent
anchors.margins: Meui.Units.largeSpacing
radius: Meui.Theme.bigRadius
color: selected ? selectedColor : main.hovered ? hoveredColor : "transparent"
border.color: Qt.rgba(Meui.Theme.highlightColor.r,
Meui.Theme.highlightColor.g,
Meui.Theme.highlightColor.b, 0.3)
border.width: main.hovered || selected ? 1 : 0
}
Item {
id: contentItem
anchors.fill: parent
anchors.margins: Meui.Units.largeSpacing
PlasmaCore.IconItem {
id: icon
z: 2
anchors.top: parent.top
anchors.topMargin: Meui.Units.smallSpacing
anchors.horizontalCenter: parent.horizontalCenter
height: main.height * 0.55
width: height
animated: false
usesPlasmaTheme: false
smooth: true
source: model.blank ? "" : model.decoration
overlays: model.blank ? "" : model.overlays
}
Label {
id: label
z: 2
anchors.top: icon.bottom
anchors.topMargin: Meui.Units.smallSpacing
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
width: parent.width
textFormat: Text.PlainText
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignTop
wrapMode: Text.Wrap
elide: Text.ElideRight
color: selected ? Meui.Theme.highlightedTextColor : Meui.Theme.textColor
opacity: model.isHidden ? 0.6 : 1
text: model.blank ? "" : model.display
font.italic: model.isLink
}
}
}

381
qml/FolderIconView.qml Normal file
View file

@ -0,0 +1,381 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import MeuiKit 1.0 as Meui
import Cutefish.FileManager 1.0 as FM
GridView {
id: control
// XXXX
property var iconSize: 130 + Meui.Units.largeSpacing
cellWidth: {
var extraWidth = calcExtraSpacing(iconSize, control.width - leftMargin - rightMargin);
return iconSize + extraWidth;
}
cellHeight: {
var extraHeight = calcExtraSpacing(iconSize, control.height - topMargin - bottomMargin);
return iconSize + extraHeight;
}
ScrollBar.vertical: ScrollBar {}
clip: true
property Item rubberBand: null
property Item hoveredItem: null
property Item pressedItem: null
property int pressX: -1
property int pressY: -1
property int dragX: -1
property int dragY: -1
property variant cPress: null
property bool doubleClickInProgress: false
property int anchorIndex: 0
property bool ctrlPressed: false
property bool shiftPressed: false
property bool overflowing: (visibleArea.heightRatio < 1.0 || visibleArea.widthRatio < 1.0)
property bool scrollLeft: false
property bool scrollRight: false
property bool scrollUp: false
property bool scrollDown: false
property variant cachedRectangleSelection: null
property int previouslySelectedItemIndex: -1
property int verticalDropHitscanOffset: 0
flow: GridView.FlowLeftToRight
currentIndex: -1
onPressXChanged: {
cPress = mapToItem(control.contentItem, pressX, pressY);
}
onPressYChanged: {
cPress = mapToItem(control.contentItem, pressX, pressY);
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
// propagateComposedEvents: true
hoverEnabled: true
z: -1
onPressed: {
control.forceActiveFocus()
if (mouse.source === Qt.MouseEventSynthesizedByQt) {
var index = control.indexAt(mouse.x, mouse.y)
var indexItem = control.itemAtIndex(index)
if (indexItem && indexItem.iconArea) {
control.currentIndex = index
hoveredItem = indexItem
} else {
hoveredItem = null
}
}
pressX = mouse.x
pressY = mouse.y
if (!hoveredItem || hoveredItem.blank) {
if (!control.ctrlPressed) {
control.currentIndex = -1;
previouslySelectedItemIndex = -1;
dirModel.clearSelection();
}
if (mouse.buttons & Qt.RightButton) {
clearPressState()
dirModel.openContextMenu(null, mouse.modifiers)
mouse.accepted = true
}
} else {
pressedItem = hoveredItem;
if (control.shiftPressed && control.currentIndex !== -1) {
positioner.setRangeSelected(control.anchorIndex, hoveredItem.index);
} else {
if (!control.ctrlPressed && !dirModel.isSelected(positioner.map(hoveredItem.index))) {
previouslySelectedItemIndex = -1;
dirModel.clearSelection();
}
if (control.ctrlPressed) {
dirModel.toggleSelected(positioner.map(hoveredItem.index));
} else {
dirModel.setSelected(positioner.map(hoveredItem.index));
}
control.currentIndex = hoveredItem.index;
if (mouse.buttons & Qt.RightButton) {
clearPressState();
dirModel.openContextMenu(null, mouse.modifiers);
mouse.accepted = true;
}
}
}
}
onPositionChanged: {
control.ctrlPressed = (mouse.modifiers & Qt.ControlModifier);
control.shiftPressed = (mouse.modifiers & Qt.ShiftModifier);
var cPos = mapToItem(control.contentItem, mouse.x, mouse.y);
var item = control.itemAt(cPos.x, cPos.y);
var leftEdge = Math.min(control.contentX, control.originX);
if (!item || item.blank) {
if (control.hoveredItem && !root.containsDrag) {
control.hoveredItem = null;
}
} else {
var fPos = mapToItem(item, mouse.x, mouse.y);
if (fPos.x < 0 || fPos.y < 0 || fPos.x > item.width || fPos.y > item.height) {
control.hoveredItem = null;
} else {
control.hoveredItem = item
}
}
// Trigger autoscroll.
if (pressX != -1) {
control.scrollLeft = (mouse.x <= 0 && control.contentX > leftEdge);
control.scrollRight = (mouse.x >= control.width
&& control.contentX < control.contentItem.width - control.width);
control.scrollUp = (mouse.y <= 0 && control.contentY > 0);
control.scrollDown = (mouse.y >= control.height
&& control.contentY < control.contentItem.height - control.height);
}
// Update rubberband geometry.
if (control.rubberBand) {
var rB = control.rubberBand;
if (cPos.x < cPress.x) {
rB.x = Math.max(leftEdge, cPos.x);
rB.width = Math.abs(rB.x - cPress.x);
} else {
rB.x = cPress.x;
var ceil = Math.max(control.width, control.contentItem.width) + leftEdge;
rB.width = Math.min(ceil - rB.x, Math.abs(rB.x - cPos.x));
}
if (cPos.y < cPress.y) {
rB.y = Math.max(0, cPos.y);
rB.height = Math.abs(rB.y - cPress.y);
} else {
rB.y = cPress.y;
var ceil = Math.max(control.height, control.contentItem.height);
rB.height = Math.min(ceil - rB.y, Math.abs(rB.y - cPos.y));
}
// Ensure rubberband is at least 1px in size or else it will become
// invisible and not match any items.
rB.width = Math.max(1, rB.width);
rB.height = Math.max(1, rB.height);
control.rectangleSelect(rB.x, rB.y, rB.width, rB.height);
return;
}
// Drag initiation.
if (pressX != -1 /*&& root.isDrag(pressX, pressY, mouse.x, mouse.y)*/) {
if (pressedItem != null && dirModel.isSelected(positioner.map(pressedItem.index))) {
dragX = mouse.x;
dragY = mouse.y;
control.verticalDropHitscanOffset = pressedItem.iconArea.y + (pressedItem.iconArea.height / 2)
dirModel.dragSelected(mouse.x, mouse.y);
dragX = -1;
dragY = -1;
clearPressState();
} else {
dirModel.pinSelection();
control.rubberBand = rubberBandObject.createObject(control.contentItem, {x: cPress.x, y: cPress.y})
control.interactive = false;
}
}
}
onContainsMouseChanged: {
if (!containsMouse && !control.rubberBand) {
clearPressState();
if (control.hoveredItem) {
control.hoveredItem = null;
}
}
}
onCanceled: pressCanceled()
onReleased: pressCanceled()
}
function calcExtraSpacing(cellSize, containerSize) {
var availableColumns = Math.floor(containerSize / cellSize);
var extraSpacing = 0;
if (availableColumns > 0) {
var allColumnSize = availableColumns * cellSize;
var extraSpace = Math.max(containerSize - allColumnSize, 0);
extraSpacing = extraSpace / availableColumns;
}
return Math.floor(extraSpacing);
}
function clearPressState() {
pressedItem = null;
pressX = -1;
pressY = -1;
}
function rectangleSelect(x, y, width, height) {
var rows = (control.flow === GridView.FlowLeftToRight);
var axis = rows ? control.width : control.height;
var step = rows ? cellWidth : cellHeight;
var perStripe = Math.floor(axis / step);
var stripes = Math.ceil(control.count / perStripe);
var cWidth = control.cellWidth - (2 * Meui.Units.smallSpacing);
var cHeight = control.cellHeight - (2 * Meui.Units.smallSpacing);
var midWidth = control.cellWidth / 2;
var midHeight = control.cellHeight / 2;
var indices = [];
for (var s = 0; s < stripes; s++) {
for (var i = 0; i < perStripe; i++) {
var index = (s * perStripe) + i;
if (index >= control.count) {
break;
}
if (positioner.isBlank(index)) {
continue;
}
var itemX = ((rows ? i : s) * control.cellWidth);
var itemY = ((rows ? s : i) * control.cellHeight);
if (control.effectiveLayoutDirection == Qt.RightToLeft) {
itemX -= (rows ? control.contentX : control.originX);
itemX += cWidth;
itemX = (rows ? control.width : control.contentItem.width) - itemX;
}
// Check if the rubberband intersects this cell first to avoid doing more
// expensive work.
if (control.rubberBand.intersects(Qt.rect(itemX + Meui.Units.smallSpacing, itemY + Meui.Units.smallSpacing,
cWidth, cHeight))) {
var item = control.contentItem.childAt(itemX + midWidth, itemY + midHeight);
// If this is a visible item, check for intersection with the actual
// icon or label rects for better feel.
if (item && item.iconArea) {
var iconRect = Qt.rect(itemX + item.iconArea.x, itemY + item.iconArea.y,
item.iconArea.width, item.iconArea.height);
if (control.rubberBand.intersects(iconRect)) {
indices.push(index);
continue;
}
var labelRect = Qt.rect(itemX + item.labelArea.x, itemY + item.labelArea.y,
item.labelArea.width, item.labelArea.height);
if (control.rubberBand.intersects(labelRect)) {
indices.push(index);
continue;
}
} else {
// Otherwise be content with the cell intersection.
indices.push(index);
}
}
}
}
control.cachedRectangleSelection = indices;
}
onCachedRectangleSelectionChanged: {
if (cachedRectangleSelection == null) {
return;
}
if (cachedRectangleSelection.length) {
// Set current index to start of selection.
// cachedRectangleSelection is pre-sorted.
currentIndex = cachedRectangleSelection[0];
}
dirModel.updateSelection(cachedRectangleSelection.map(positioner.map), control.ctrlPressed);
}
function pressCanceled() {
if (control.rubberBand) {
control.rubberBand.close()
control.rubberBand = null
control.interactive = true;
control.cachedRectangleSelection = null;
dirModel.unpinSelection();
}
clearPressState();
control.cancelAutoscroll();
}
function cancelAutoscroll() {
scrollLeft = false;
scrollRight = false;
scrollUp = false;
scrollDown = false;
}
Component {
id: rubberBandObject
FM.RubberBand {
id: rubberBand
width: 0
height: 0
z: 99999
color: Meui.Theme.highlightColor
function close() {
opacityAnimation.restart()
}
OpacityAnimator {
id: opacityAnimation
target: rubberBand
to: 0
from: 1
duration: 150
easing {
bezierCurve: [0.4, 0.0, 1, 1]
type: Easing.Bezier
}
onFinished: {
rubberBand.visible = false
rubberBand.enabled = false
rubberBand.destroy()
}
}
}
}
}

View file

@ -0,0 +1,79 @@
import QtQuick 2.4
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import Cutefish.FileManager 1.0
import MeuiKit 1.0 as Meui
import org.kde.plasma.core 2.0 as PlasmaCore
Item {
id: control
property int index: model.index
property string name: model.blank ? "" : model.display
property bool blank: model.blank
property bool isDir: model.blank ? false : model.isDir
property bool selected: model.blank ? false : model.selected
property Item frame: contentItem
property Item iconArea: iconItem
property Item labelArea: label1
property color hoveredColor: Qt.rgba(Meui.Theme.textColor.r,
Meui.Theme.textColor.g,
Meui.Theme.textColor.b, 0.1)
Accessible.name: name
Accessible.role: Accessible.Canvas
MouseArea {
id: _mouseArea
anchors.fill: parent
hoverEnabled: true
}
Rectangle {
z: -1
anchors.fill: parent
radius: Meui.Theme.bigRadius
color: selected ? Meui.Theme.highlightColor : _mouseArea.containsMouse ? control.hoveredColor : "transparent"
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: Meui.Units.smallSpacing
anchors.rightMargin: Meui.Units.smallSpacing
spacing: Meui.Units.largeSpacing
Item {
id: iconItem
Layout.fillHeight: true
width: parent.height * 0.8
PlasmaCore.IconItem {
id: icon
z: 2
anchors.fill: parent
animated: false
usesPlasmaTheme: false
smooth: true
source: model.blank ? "" : model.decoration
overlays: model.blank ? "" : model.overlays
}
}
Label {
id: label1
Layout.fillWidth: true
text: name
color: selected ? Meui.Theme.highlightedTextColor : Meui.Theme.textColor
}
Label {
id: label2
color: selected ? Meui.Theme.highlightedTextColor : Meui.Theme.textColor
}
}
}

38
qml/FolderListView.qml Normal file
View file

@ -0,0 +1,38 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import MeuiKit 1.0 as Meui
import Cutefish.FileManager 1.0 as FM
ListView {
id: control
signal clicked(var mouse)
signal positionChanged(var mouse)
signal pressed(var mouse)
signal released(var mouse)
ScrollBar.vertical: ScrollBar {}
spacing: Meui.Units.largeSpacing
clip: true
snapMode: ListView.NoSnap
highlightFollowsCurrentItem: true
highlightMoveDuration: 0
highlightResizeDuration : 0
MouseArea {
anchors.fill: parent
z: -1
hoverEnabled: true
propagateComposedEvents: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: parent.clicked(mouse)
onPositionChanged: parent.positionChanged(mouse)
onPressed: parent.pressed(mouse)
onReleased: parent.released(mouse)
Keys.forwardTo: _listViewBrowser
}
}

9
qml/GlobalSettings.qml Normal file
View file

@ -0,0 +1,9 @@
import QtQuick 2.0
import Qt.labs.settings 1.0
Settings {
property int viewMethod: 0 // 0 = Grid, 1 = List
property bool showHidden: false
property int width: 1080
property int height: 645
}

39
qml/IconButton.qml Normal file
View file

@ -0,0 +1,39 @@
import QtQuick 2.12
import MeuiKit 1.0 as Meui
Item {
id: control
width: 24
height: 24
property alias source: _image.source
property color hoveredColor: Meui.Theme.darkMode ? Qt.lighter(Meui.Theme.backgroundColor, 1.1)
: Qt.darker(Meui.Theme.backgroundColor, 1.2)
property color pressedColor: Meui.Theme.darkMode ? Qt.lighter(Meui.Theme.backgroundColor, 1.2)
: Qt.darker(Meui.Theme.backgroundColor, 1.3)
signal clicked()
Rectangle {
id: _background
anchors.fill: parent
radius: Meui.Theme.smallRadius
color: _mouseArea.pressed ? pressedColor : _mouseArea.containsMouse ? control.hoveredColor : Meui.Theme.backgroundColor
}
Image {
id: _image
anchors.centerIn: parent
width: control.height * 0.64
height: width
sourceSize: Qt.size(width, height)
}
MouseArea {
id: _mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton
onClicked: control.clicked()
}
}

102
qml/IconDelegate.qml Normal file
View file

@ -0,0 +1,102 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.0
import MeuiKit 1.0 as Meui
import org.kde.plasma.core 2.0 as PlasmaCore
Item {
id: main
property int index: model.index
property string name: model.blank ? "" : model.display
property bool blank: model.blank
property bool isDir: model.blank ? false : model.isDir
property bool selected: model.blank ? false : model.selected
property Item frame: contentItem
property Item iconArea: icon
property Item labelArea: label
property bool hovered: (main.GridView.view.hoveredItem === main)
property color hoveredColor: Qt.rgba(Meui.Theme.highlightColor.r,
Meui.Theme.highlightColor.g,
Meui.Theme.highlightColor.b, 0.1)
property color selectedColor: Qt.rgba(Meui.Theme.highlightColor.r,
Meui.Theme.highlightColor.g,
Meui.Theme.highlightColor.b, 0.9)
Accessible.name: name
Accessible.role: Accessible.Canvas
onSelectedChanged: {
if (selected && !blank) {
contentItem.grabToImage(function(result) {
dir.addItemDragImage(positioner.map(index), main.x + contentItem.x, main.y + contentItem.y, contentItem.width, contentItem.height, result.image);
});
}
}
Rectangle {
anchors.fill: parent
anchors.margins: Meui.Units.largeSpacing
radius: Meui.Theme.bigRadius
color: selected ? selectedColor : main.hovered ? hoveredColor : "transparent"
border.color: Qt.rgba(Meui.Theme.highlightColor.r,
Meui.Theme.highlightColor.g,
Meui.Theme.highlightColor.b, 0.3)
border.width: main.hovered || selected ? 1 : 0
}
Item {
id: contentItem
anchors.fill: parent
anchors.margins: Meui.Units.largeSpacing
PlasmaCore.IconItem {
id: icon
z: 2
anchors.top: parent.top
anchors.topMargin: Meui.Units.smallSpacing
anchors.horizontalCenter: parent.horizontalCenter
height: main.height * 0.55
width: height
animated: false
usesPlasmaTheme: false
smooth: true
source: model.blank ? "" : model.decoration
overlays: model.blank ? "" : model.overlays
}
Label {
id: label
z: 2
anchors.top: icon.bottom
anchors.topMargin: Meui.Units.smallSpacing
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
width: parent.width
textFormat: Text.PlainText
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignTop
wrapMode: Text.Wrap
elide: Text.ElideRight
color: Meui.Theme.textColor
opacity: model.isHidden ? 0.6 : 1
text: model.blank ? "" : model.display
font.italic: model.isLink
}
}
}

101
qml/ItemMenu.qml Normal file
View file

@ -0,0 +1,101 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import Cutefish.FileManager 1.0
import MeuiKit 1.0 as Meui
Menu {
id: control
implicitWidth: 200
property var item : ({})
property int index : -1
property bool isDir : false
property bool isExec : false
signal openClicked(var item)
signal removeClicked(var item)
signal copyClicked(var item)
signal cutClicked(var item)
signal renameClicked(var item)
signal wallpaperClicked(var item)
signal propertiesClicked(var item)
MenuItem {
text: qsTr("Open")
onTriggered: {
openClicked(control.item)
close()
}
}
MenuItem {
text: qsTr("Copy")
onTriggered: {
copyClicked(control.item)
close()
}
}
MenuItem {
text: qsTr("Cut")
onTriggered: {
cutClicked(control.item)
close()
}
}
MenuItem {
text: qsTr("Move to Trash")
onTriggered: {
removeClicked(control.item)
close()
}
}
MenuSeparator {}
MenuItem {
text: qsTr("Rename")
onTriggered: {
renameClicked(control.item)
close()
}
}
MenuItem {
text: qsTr("Open in Terminal")
}
MenuItem {
id: wallpaperItem
text: qsTr("Set As Wallpaper")
visible: false
onTriggered: {
wallpaperClicked(control.item)
close()
}
}
MenuItem {
id: properties
text: qsTr("Properties")
onTriggered: {
propertiesClicked(control.item)
close()
}
}
function show(index) {
control.item = currentFMModel.get(index)
if (item) {
control.index = index
control.isDir = item.isdir === true || item.isdir === "true"
control.isExec = item.executable === true || item.executable === "true"
wallpaperItem.visible = item.img === "true"
popup()
}
}
}

138
qml/PathBar.qml Normal file
View file

@ -0,0 +1,138 @@
import QtQuick 2.4
import QtQuick.Controls 2.4
import QtGraphicalEffects 1.0
import MeuiKit 1.0 as Meui
import Cutefish.FileManager 1.0
Item {
id: control
property string url: ""
signal placeClicked(string path)
signal pathChanged(string path)
onUrlChanged: {
_pathList.path = control.url
}
BaseModel {
id: _pathModel
list: _pathList
}
PathList {
id: _pathList
}
Rectangle {
anchors.fill: parent
radius: Meui.Theme.smallRadius
color: Meui.Theme.backgroundColor
}
ListView {
id: listView
anchors.fill: parent
model: _pathModel
orientation: Qt.Horizontal
layoutDirection: Qt.LeftToRight
clip: true
spacing: Meui.Units.smallSpacing
onCountChanged: {
currentIndex = listView.count - 1
listView.positionViewAtEnd()
}
delegate: MouseArea {
id: pathBarItem
height: listView.height
width: label.width + Meui.Units.largeSpacing * 2
property bool selected: index === listView.count - 1
property color pressedColor: Qt.rgba(Meui.Theme.textColor.r,
Meui.Theme.textColor.g,
Meui.Theme.textColor.b, 0.5)
onClicked: control.placeClicked(model.path)
Rectangle {
anchors.fill: parent
anchors.margins: 2
color: Meui.Theme.highlightColor
radius: Meui.Theme.smallRadius
visible: selected
layer.enabled: true
layer.effect: DropShadow {
transparentBorder: true
radius: 2
samples: 2
horizontalOffset: 0
verticalOffset: 0
color: Qt.rgba(0, 0, 0, 0.1)
}
}
Label {
id: label
text: model.label
anchors.centerIn: parent
color: selected ? Meui.Theme.highlightedTextColor : pathBarItem.pressed
? pressedColor : Meui.Theme.textColor
}
}
MouseArea {
anchors.fill: parent
z: -1
onClicked: {
if (!addressEdit.visible) {
openEditor()
}
}
}
}
TextField {
id: addressEdit
anchors.centerIn: parent
width: parent.width
height: parent.height
visible: false
selectByMouse: true
inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoAutoUppercase
text: control.url
onAccepted: {
control.pathChanged(text)
closeEditor()
}
Keys.onPressed: {
if (event.key === Qt.Key_Escape)
focus = false
}
onActiveFocusChanged: {
if (!activeFocus) {
closeEditor()
}
}
}
function closeEditor() {
addressEdit.visible = false
listView.visible = true
}
function openEditor() {
addressEdit.visible = true
addressEdit.forceActiveFocus()
addressEdit.selectAll()
listView.visible = false
}
}

74
qml/SideBar.qml Normal file
View file

@ -0,0 +1,74 @@
import QtQuick 2.4
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.4
import MeuiKit 1.0 as Meui
import Cutefish.FileManager 1.0
ListView {
id: control
implicitWidth: 200
property string currentUrl
signal placeClicked(string path)
signal itemClicked(int index)
onItemClicked: {
var item = placesModel.get(index)
control.placeClicked(item.url)
}
onCurrentUrlChanged: {
syncIndex(currentUrl)
}
Component.onCompleted: {
syncIndex(currentUrl)
}
function syncIndex(path) {
control.currentIndex = -1
for (var i = 0; i < control.count; ++i) {
if (path === control.model.get(i).url) {
control.currentIndex = i
break
}
}
}
PlacesModel {
id: placesModel
}
clip: true
spacing: Meui.Units.smallSpacing
leftMargin: Meui.Units.smallSpacing
rightMargin: Meui.Units.smallSpacing
model: placesModel
ScrollBar.vertical: ScrollBar {}
flickableDirection: Flickable.VerticalFlick
highlightFollowsCurrentItem: true
highlightMoveDuration: 0
highlightResizeDuration : 0
highlight: Rectangle {
radius: Meui.Theme.smallRadius
color: Meui.Theme.highlightColor
}
delegate: SidebarItem {
id: listItem
checked: control.currentIndex === index
width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin
height: 40
onClicked: {
control.currentIndex = index
control.itemClicked(index)
}
}
}

69
qml/SidebarItem.qml Normal file
View file

@ -0,0 +1,69 @@
import QtQuick 2.4
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.1
import QtGraphicalEffects 1.0
import MeuiKit 1.0 as Meui
Item {
id: item
property bool checked: false
signal clicked
Rectangle {
id: rect
anchors.fill: parent
radius: Meui.Theme.smallRadius
color: item.checked ? "transparent"
: mouseArea.containsMouse ? Qt.rgba(Meui.Theme.textColor.r,
Meui.Theme.textColor.g,
Meui.Theme.textColor.b, 0.1) : "transparent"
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton
onClicked: item.clicked()
}
RowLayout {
anchors.fill: rect
anchors.leftMargin: Meui.Units.largeSpacing
anchors.rightMargin: Meui.Units.largeSpacing
spacing: Meui.Units.largeSpacing
Item {
id: iconItem
height: item.height * 0.55
width: height
Image {
id: image
anchors.fill: parent
sourceSize: Qt.size(width, height)
source: model.iconPath ? model.iconPath : "image://icontheme/" + model.iconName
Layout.alignment: Qt.AlignVCenter
}
ColorOverlay {
anchors.fill: parent
source: parent
color: itemTitle.color
visible: Meui.Theme.darkMode && model.iconPath || checked
}
}
Label {
id: itemTitle
text: model.name
color: item.checked ? Meui.Theme.highlightedTextColor : Meui.Theme.textColor
elide: Text.ElideRight
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
}
}
}

106
qml/main.qml Normal file
View file

@ -0,0 +1,106 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import Cutefish.FileManager 1.0
import MeuiKit 1.0 as Meui
Meui.Window {
id: root
width: settings.width
height: settings.height
minimumWidth: 900
minimumHeight: 600
visible: true
title: qsTr("File Manager")
hideHeaderOnMaximize: false
headerBarHeight: 35 + Meui.Units.largeSpacing
backgroundColor: Meui.Theme.secondBackgroundColor
property QtObject settings: GlobalSettings { }
onClosing: {
settings.width = root.width
settings.height = root.height
}
headerBar: Item {
RowLayout {
anchors.fill: parent
anchors.leftMargin: Meui.Units.largeSpacing
anchors.rightMargin: Meui.Units.smallSpacing
anchors.topMargin: Meui.Units.largeSpacing
spacing: Meui.Units.smallSpacing
IconButton {
Layout.fillHeight: true
implicitWidth: height
source: Meui.Theme.darkMode ? "qrc:/images/dark/go-previous.svg" : "qrc:/images/light/go-previous.svg"
onClicked: _browserView.goBack()
}
IconButton {
Layout.fillHeight: true
implicitWidth: height
source: Meui.Theme.darkMode ? "qrc:/images/dark/go-next.svg" : "qrc:/images/light/go-next.svg"
onClicked: _browserView.goForward()
}
PathBar {
id: pathBar
Layout.fillWidth: true
Layout.fillHeight: true
url: _browserView.url
onPlaceClicked: _browserView.openFolder(path)
onPathChanged: _browserView.openFolder(path)
}
IconButton {
Layout.fillHeight: true
implicitWidth: height
source: Meui.Theme.darkMode ? "qrc:/images/dark/grid.svg" : "qrc:/images/light/grid.svg"
onClicked: settings.viewMethod = 1
}
IconButton {
Layout.fillHeight: true
implicitWidth: height
source: Meui.Theme.darkMode ? "qrc:/images/dark/list.svg" : "qrc:/images/light/list.svg"
onClicked: settings.viewMethod = 0
}
}
}
ColumnLayout {
anchors.fill: parent
spacing: Meui.Units.largeSpacing
Item {
id: bottomControls
Layout.fillWidth: true
Layout.fillHeight: true
RowLayout {
anchors.fill: parent
anchors.topMargin: Meui.Units.largeSpacing
spacing: 0
SideBar {
Layout.fillHeight: true
currentUrl: _browserView.model.url
onPlaceClicked: _browserView.model.url = path
}
BrowserView {
id: _browserView
Layout.fillWidth: true
Layout.fillHeight: true
// onOpenPathBar: pathBar.openEditor()
}
}
}
}
}

86
src/baselist.cpp Normal file
View file

@ -0,0 +1,86 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2019 camilo <chiguitar@unal.edu.co>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "baselist.h"
#include "basemodel.h"
BaseList::BaseList(QObject *parent)
: QObject(parent)
, m_model(nullptr)
{
}
int BaseList::getCount() const
{
return this->items().count();
}
QVariantMap BaseList::get(const int &index) const
{
if (this->m_model) {
return this->m_model->get(index);
}
if (index >= 0 && this->items().size() > 0 && index < this->items().size()) {
return FMH::toMap(this->items()[index]);
}
return QVariantMap();
}
FMH::MODEL_LIST BaseList::getItems() const
{
if (this->m_model && !this->m_model->getFilter().isEmpty()) {
return FMH::toModelList(this->m_model->getAll());
}
return this->items();
}
int BaseList::mappedIndex(const int &index) const
{
if (this->m_model)
return this->m_model->mappedToSource(index);
return index;
}
int BaseList::mappedIndexFromSource(const int &index) const
{
if (this->m_model)
return this->m_model->mappedFromSource(index);
return index;
}
bool BaseList::exists(const FMH::MODEL_KEY &key, const QString &value) const
{
return this->indexOf(key, value) >= 0;
}
int BaseList::indexOf(const FMH::MODEL_KEY &key, const QString &value) const
{
const auto it = std::find_if(this->items().constBegin(), this->items().constEnd(), [&](const FMH::MODEL &item) -> bool {
return item[key] == value;
});
if (it != this->items().constEnd())
return this->mappedIndexFromSource(std::distance(this->items().constBegin(), it));
else
return -1;
}

89
src/baselist.h Normal file
View file

@ -0,0 +1,89 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2019 camilo <chiguitar@unal.edu.co>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef BASELIST_H
#define BASELIST_H
#include "fmh.h"
#include <QQmlParserStatus>
/**
* @todo write docs
*/
#include <QObject>
class BaseModel;
class BaseList : public QObject, public QQmlParserStatus
{
Q_INTERFACES(QQmlParserStatus)
Q_OBJECT
Q_PROPERTY(int count READ getCount NOTIFY countChanged FINAL)
public:
/**
* Default constructor
*/
explicit BaseList(QObject *parent = nullptr);
virtual const FMH::MODEL_LIST &items() const = 0;
virtual void classBegin() override
{
}
virtual void componentComplete() override
{
}
virtual void modelHooked() {};
int getCount() const;
/**
* @brief getItems
* Get all the items in the list model. If the model has been filtered or sorted those are the items that are returned
* @param index
* @return
*/
FMH::MODEL_LIST getItems() const;
const BaseModel *m_model; // becarefull this is owned by qml engine, this is only supossed to be a viewer
public slots:
int mappedIndex(const int &index) const;
int mappedIndexFromSource(const int &index) const;
QVariantMap get(const int &index) const;
protected:
bool exists(const FMH::MODEL_KEY &key, const QString &value) const;
int indexOf(const FMH::MODEL_KEY &key, const QString &value) const;
signals:
void preItemAppended();
void preItemsAppended(uint count);
void postItemAppended();
void preItemAppendedAt(int index);
void preItemRemoved(int index);
void postItemRemoved();
void updateModel(int index, QVector<int> roles);
void preListChanged();
void postListChanged();
void countChanged();
};
#endif // BASELIST_H

333
src/basemodel.cpp Normal file
View file

@ -0,0 +1,333 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2019 camilo <chiguitar@unal.edu.co>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "basemodel.h"
#include "baselist.h"
BaseModel::BaseModel(QObject *parent)
: QSortFilterProxyModel(parent)
, m_model(new PrivateAbstractListModel(this))
{
this->setSourceModel(this->m_model);
this->setDynamicSortFilter(true);
}
void BaseModel::setFilterString(const QString &string)
{
this->setFilterCaseSensitivity(Qt::CaseInsensitive);
this->setFilterFixedString(string);
// this->setFilterRegExp(QRegExp(string, Qt::CaseInsensitive));
}
void BaseModel::setSortOrder(const int &sortOrder)
{
this->sort(0, static_cast<Qt::SortOrder>(sortOrder));
}
QVariantMap BaseModel::get(const int &index) const
{
QVariantMap res;
if (index >= this->rowCount() || index < 0)
return res;
const auto roleNames = this->roleNames();
for (const auto &role : roleNames)
res.insert(role, this->index(index, 0).data(FMH::MODEL_NAME_KEY[role]).toString());
return res;
}
QVariantList BaseModel::getAll() const
{
QVariantList res;
for (auto i = 0; i < this->rowCount(); i++)
res << this->get(i);
return res;
}
void BaseModel::setFilter(const QString &filter)
{
if (this->m_filter == filter)
return;
this->m_filter = filter;
emit this->filterChanged(this->m_filter);
this->setFilterFixedString(this->m_filter);
}
const QString BaseModel::getFilter() const
{
return this->m_filter;
}
void BaseModel::setSortOrder(const Qt::SortOrder &sortOrder)
{
if (this->m_sortOrder == sortOrder)
return;
this->m_sortOrder = sortOrder;
emit this->sortOrderChanged(this->m_sortOrder);
this->sort(0, this->m_sortOrder);
}
Qt::SortOrder BaseModel::getSortOrder() const
{
return this->m_sortOrder;
}
void BaseModel::setSort(const QString &sort)
{
if (this->m_sort == sort)
return;
this->m_sort = sort;
emit this->sortChanged(this->m_sort);
this->setSortRole(FMH::MODEL_NAME_KEY[sort]);
this->sort(0, this->m_sortOrder);
}
QString BaseModel::getSort() const
{
return this->m_sort;
}
int BaseModel::mappedFromSource(const int &index) const
{
return this->mapFromSource(this->m_model->index(index, 0)).row();
}
int BaseModel::mappedToSource(const int &index) const
{
return this->mapToSource(this->index(index, 0)).row();
}
bool BaseModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
if (this->filterRole() != Qt::DisplayRole) {
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
const auto data = this->sourceModel()->data(index, this->filterRole()).toString();
return data.contains(this->filterRegExp());
}
const auto roleNames = this->sourceModel()->roleNames();
for (const auto &role : roleNames) {
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
const auto data = this->sourceModel()->data(index, FMH::MODEL_NAME_KEY[role]).toString();
if (data.contains(this->filterRegExp()))
return true;
else
continue;
}
return false;
}
BaseList *BaseModel::getList() const
{
return this->m_model->getList();
}
BaseList *BaseModel::PrivateAbstractListModel::getList() const
{
return this->list;
}
void BaseModel::PrivateAbstractListModel::setList(BaseList *value)
{
beginResetModel();
if (this->list)
this->list->disconnect(this);
this->list = value;
if (this->list) {
connect(
this->list,
&BaseList::preItemAppendedAt,
this,
[=](int index) {
beginInsertRows(QModelIndex(), index, index);
},
Qt::DirectConnection);
connect(
this->list,
&BaseList::preItemAppended,
this,
[=]() {
const int index = this->list->items().size();
beginInsertRows(QModelIndex(), index, index);
},
Qt::DirectConnection);
connect(
this->list,
&BaseList::preItemsAppended,
this,
[=](uint count) {
const int index = this->list->items().size();
beginInsertRows(QModelIndex(), index, index + count - 1);
},
Qt::DirectConnection);
connect(
this->list,
&BaseList::postItemAppended,
this,
[=]() {
endInsertRows();
},
Qt::DirectConnection);
connect(
this->list,
&BaseList::preItemRemoved,
this,
[=](int index) {
beginRemoveRows(QModelIndex(), index, index);
},
Qt::DirectConnection);
connect(
this->list,
&BaseList::postItemRemoved,
this,
[=]() {
endRemoveRows();
},
Qt::DirectConnection);
connect(
this->list,
&BaseList::updateModel,
this,
[=](int index, QVector<int> roles) {
emit this->dataChanged(this->index(index), this->index(index), roles);
},
Qt::DirectConnection);
connect(
this->list,
&BaseList::preListChanged,
this,
[=]() {
beginResetModel();
},
Qt::DirectConnection);
connect(
this->list,
&BaseList::postListChanged,
this,
[=]() {
endResetModel();
},
Qt::DirectConnection);
}
endResetModel();
}
void BaseModel::setList(BaseList *value)
{
value->modelHooked();
this->m_model->setList(value);
this->getList()->m_model = this;
emit this->listChanged();
}
BaseModel::PrivateAbstractListModel::PrivateAbstractListModel(BaseModel *model)
: QAbstractListModel(model)
, list(nullptr)
, m_model(model)
{
connect(
this,
&QAbstractListModel::rowsInserted,
this,
[this](QModelIndex, int, int) {
if (this->list) {
emit this->list->countChanged();
}
},
Qt::DirectConnection);
connect(
this,
&QAbstractListModel::rowsRemoved,
this,
[this](QModelIndex, int, int) {
if (this->list) {
emit this->list->countChanged();
}
},
Qt::DirectConnection);
}
int BaseModel::PrivateAbstractListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid() || !list)
return 0;
return list->items().size();
}
QVariant BaseModel::PrivateAbstractListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || !list)
return QVariant();
auto value = list->items().at(index.row())[static_cast<FMH::MODEL_KEY>(role)];
if (role == FMH::MODEL_KEY::ADDDATE || role == FMH::MODEL_KEY::DATE || role == FMH::MODEL_KEY::MODIFIED || role == FMH::MODEL_KEY::RELEASEDATE) {
const auto date = QDateTime::fromString(value, Qt::TextDate);
if (date.isValid())
return date;
}
return value;
}
bool BaseModel::PrivateAbstractListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
Q_UNUSED(index);
Q_UNUSED(value);
Q_UNUSED(role);
return false;
}
Qt::ItemFlags BaseModel::PrivateAbstractListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable; // FIXME: Implement me!
}
QHash<int, QByteArray> BaseModel::PrivateAbstractListModel::roleNames() const
{
QHash<int, QByteArray> names;
for (const auto &key : FMH::MODEL_NAME.keys())
names[key] = QString(FMH::MODEL_NAME[key]).toUtf8();
return names;
}

181
src/basemodel.h Normal file
View file

@ -0,0 +1,181 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2019 camilo <chiguitar@unal.edu.co>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef BASEMODEL_H
#define BASEMODEL_H
#include <QAbstractListModel>
#include <QList>
#include <QObject>
#include <QSortFilterProxyModel>
class BaseList;
/**
* @brief The BaseModel class
* The BaseModel is a template model to use with a BaseList, it aims to be a generic and simple data model to quickly model string based models using the FMH::MODEL_LIST and FMH::MODEL_KEY types.
*
* This type is exposed to QML to quickly create a modle that can be filtered, sorted and has another usefull functionalities.
*/
class BaseModel : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(BaseList *list READ getList WRITE setList NOTIFY listChanged)
Q_PROPERTY(QString filter READ getFilter WRITE setFilter NOTIFY filterChanged)
Q_PROPERTY(Qt::SortOrder sortOrder READ getSortOrder WRITE setSortOrder NOTIFY sortOrderChanged)
Q_PROPERTY(QString sort READ getSort WRITE setSort NOTIFY sortChanged)
public:
BaseModel(QObject *parent = nullptr);
/**
* @brief getList
* The list being handled by the model
* @return
*/
BaseList *getList() const;
/**
* @brief setList
* For the model to work you need to set a BaseList, by subclassing it and exposing it to the QML engine
* @param value
*/
void setList(BaseList *value);
/**
* @brief getSortOrder
* The current sort order being applied
* @return
*/
Qt::SortOrder getSortOrder() const;
/**
* @brief getSort
* The current sorting key
* @return
*/
QString getSort() const;
/**
* @brief getFilter
* The filter being applied to the model
* @return
*/
const QString getFilter() const;
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
private:
class PrivateAbstractListModel;
PrivateAbstractListModel *m_model;
QString m_filter;
Qt::SortOrder m_sortOrder;
QString m_sort;
[[deprecated]] void setFilterString(const QString &string);
[[deprecated]] void setSortOrder(const int &sortOrder);
public slots:
/**
* @brief setFilter
* Filter the model using a simple string, to clear the filter just set it to a empty string
* @param filter
* Simple filter string
*/
void setFilter(const QString &filter);
/**
* @brief setSortOrder
* Set the sort order, asc or dec
* @param sortOrder
*/
void setSortOrder(const Qt::SortOrder &sortOrder);
/**
* @brief setSort
* Set the sort key. The sort keys can be found in the FMH::MODEL_KEY keys
* @param sort
*/
void setSort(const QString &sort);
/**
* @brief get
* Returns an item in the model/list. This method correctly maps the given index in case the modle has been sorted or filtered
* @param index
* Index of the item in the list
* @return
*/
QVariantMap get(const int &index) const;
/**
* @brief getAll
* Returns all the items in the list represented as a QVariantList to be able to be used in QML. This operation performs a transformation from FMH::MODEL_LIST to QVariantList
* @return
* All the items in the list
*/
QVariantList getAll() const;
/**
* @brief mappedFromSource
* Maps an index from the base list to the model, incase the modle has been filtered or sorted, this gives you the right mapped index
* @param index
* @return
*/
int mappedFromSource(const int &index) const;
/**
* @brief mappedToSource
* given an index from the filtered or sorted model it return the mapped index to the original list index
* @param index
* @return
*/
int mappedToSource(const int &index) const;
signals:
void listChanged();
void filterChanged(QString filter);
void sortOrderChanged(Qt::SortOrder sortOrder);
void sortChanged(QString sort);
};
class BaseModel::PrivateAbstractListModel : public QAbstractListModel
{
Q_OBJECT
public:
PrivateAbstractListModel(BaseModel *model);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// Editable:
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
virtual QHash<int, QByteArray> roleNames() const override;
BaseList *getList() const;
void setList(BaseList *value);
private:
BaseList *list;
BaseModel *m_model;
};
#endif // BASEMODEL_H

View file

@ -0,0 +1,55 @@
#include "desktopsettings.h"
#include <QDBusServiceWatcher>
#include <QProcess>
DesktopSettings::DesktopSettings(QObject *parent)
: QObject(parent)
, m_interface("org.cutefish.Settings",
"/Theme", "org.cutefish.Theme",
QDBusConnection::sessionBus(), this)
{
QDBusServiceWatcher *watcher = new QDBusServiceWatcher(this);
watcher->setConnection(QDBusConnection::sessionBus());
watcher->addWatchedService("org.cutefish.Settings");
connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &DesktopSettings::init);
init();
}
QString DesktopSettings::wallpaper() const
{
return m_wallpaper;
}
bool DesktopSettings::dimsWallpaper() const
{
return m_interface.property("darkModeDimsWallpaer").toBool();
}
void DesktopSettings::launch(const QString &command, const QStringList &args)
{
QProcess process;
process.setProgram(command);
process.setArguments(args);
process.startDetached();
}
void DesktopSettings::init()
{
if (m_interface.isValid()) {
connect(&m_interface, SIGNAL(wallpaperChanged(QString)), this, SLOT(onWallpaperChanged(QString)));
connect(&m_interface, SIGNAL(darkModeDimsWallpaerChanged()), this, SIGNAL(dimsWallpaperChanged()));
m_wallpaper = m_interface.property("wallpaper").toString();
emit wallpaperChanged();
}
}
void DesktopSettings::onWallpaperChanged(QString path)
{
if (path != m_wallpaper) {
m_wallpaper = path;
emit wallpaperChanged();
}
}

View file

@ -0,0 +1,34 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#include <QObject>
#include <QDBusInterface>
class DesktopSettings : public QObject
{
Q_OBJECT
Q_PROPERTY(QString wallpaper READ wallpaper NOTIFY wallpaperChanged)
Q_PROPERTY(bool dimsWallpaper READ dimsWallpaper NOTIFY dimsWallpaperChanged)
public:
explicit DesktopSettings(QObject *parent = nullptr);
QString wallpaper() const;
bool dimsWallpaper() const;
Q_INVOKABLE void launch(const QString &command, const QStringList &args);
signals:
void wallpaperChanged();
void dimsWallpaperChanged();
private slots:
void init();
void onWallpaperChanged(QString);
private:
QDBusInterface m_interface;
QString m_wallpaper;
};
#endif // SETTINGS_H

View file

@ -0,0 +1,59 @@
#include "desktopview.h"
#include <QQmlEngine>
#include <QQmlContext>
#include <QDebug>
#include <QApplication>
#include <QScreen>
#include <KWindowSystem>
DesktopView::DesktopView(QQuickView *parent)
: QQuickView(parent)
{
m_screenRect = qApp->primaryScreen()->geometry();
m_screenAvailableRect = qApp->primaryScreen()->availableGeometry();
// setFlags(Qt::Window | Qt::FramelessWindowHint);
setTitle(tr("Desktop"));
KWindowSystem::setType(winId(), NET::Desktop);
KWindowSystem::setState(winId(), NET::KeepBelow);
engine()->rootContext()->setContextProperty("desktopView", this);
setScreen(qApp->primaryScreen());
setResizeMode(QQuickView::SizeRootObjectToView);
setSource(QStringLiteral("qrc:/qml/Desktop/Desktop.qml"));
onGeometryChanged();
connect(qApp->primaryScreen(), &QScreen::virtualGeometryChanged, this, &DesktopView::onGeometryChanged);
connect(qApp->primaryScreen(), &QScreen::geometryChanged, this, &DesktopView::onGeometryChanged);
connect(qApp->primaryScreen(), &QScreen::availableGeometryChanged, this, &DesktopView::onAvailableGeometryChanged);
}
QRect DesktopView::screenRect()
{
return m_screenRect;
}
QRect DesktopView::screenAvailableRect()
{
return m_screenAvailableRect;
}
void DesktopView::onGeometryChanged()
{
m_screenRect = qApp->primaryScreen()->geometry();
setGeometry(qApp->primaryScreen()->geometry());
emit screenRectChanged();
}
void DesktopView::onAvailableGeometryChanged(const QRect &geometry)
{
m_screenAvailableRect = geometry;
emit screenAvailableGeometryChanged();
}

31
src/desktop/desktopview.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef DESKTOPVIEW_H
#define DESKTOPVIEW_H
#include <QQuickView>
class DesktopView : public QQuickView
{
Q_OBJECT
Q_PROPERTY(QRect screenRect READ screenRect NOTIFY screenRectChanged)
Q_PROPERTY(QRect screenAvailableRect READ screenAvailableRect NOTIFY screenAvailableGeometryChanged)
public:
explicit DesktopView(QQuickView *parent = nullptr);
QRect screenRect();
QRect screenAvailableRect();
signals:
void screenRectChanged();
void screenAvailableGeometryChanged();
private slots:
void onGeometryChanged();
void onAvailableGeometryChanged(const QRect &geometry);
private:
QRect m_screenRect;
QRect m_screenAvailableRect;
};
#endif // DESKTOPVIEW_H

View file

@ -0,0 +1,146 @@
#include "propertiesdialog.h"
#include "../iconthemeprovider.h"
#include <QDir>
#include <QFileInfo>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQmlContext>
#include <QDebug>
PropertiesDialog::PropertiesDialog(const KFileItem &item, QObject *parent)
: QObject(parent)
{
m_items.append(item);
init();
}
PropertiesDialog::PropertiesDialog(const KFileItemList &items, QObject *parent)
: QObject(parent)
{
m_items = items;
init();
}
PropertiesDialog::PropertiesDialog(const QUrl &url, QObject *parent)
: QObject(parent)
{
m_items.append(KFileItem(url));
init();
}
PropertiesDialog::~PropertiesDialog()
{
if (m_dirSizeJob)
m_dirSizeJob->kill();
}
void PropertiesDialog::showDialog(const KFileItem &item)
{
PropertiesDialog *dlg = new PropertiesDialog(item);
QQmlApplicationEngine *engine = new QQmlApplicationEngine;
engine->addImageProvider(QStringLiteral("icontheme"), new IconThemeProvider());
engine->rootContext()->setContextProperty("main", dlg);
engine->load(QUrl("qrc:/qml/Dialogs/PropertiesDialog.qml"));
}
void PropertiesDialog::showDialog(const KFileItemList &items)
{
PropertiesDialog *dlg = new PropertiesDialog(items);
QQmlApplicationEngine *engine = new QQmlApplicationEngine;
engine->addImageProvider(QStringLiteral("icontheme"), new IconThemeProvider());
engine->rootContext()->setContextProperty("main", dlg);
engine->load(QUrl("qrc:/qml/Dialogs/PropertiesDialog.qml"));
}
bool PropertiesDialog::multiple() const
{
return m_multiple;
}
QString PropertiesDialog::location() const
{
return m_location;
}
QString PropertiesDialog::fileName() const
{
return m_fileName;
}
QString PropertiesDialog::iconName() const
{
return m_iconName;
}
QString PropertiesDialog::mimeType() const
{
return m_mimeType;
}
QString PropertiesDialog::size() const
{
return m_size;
}
QString PropertiesDialog::creationTime() const
{
return m_creationTime;
}
QString PropertiesDialog::modifiedTime() const
{
return m_modifiedTime;
}
QString PropertiesDialog::accessedTime() const
{
return m_accessedTime;
}
void PropertiesDialog::init()
{
m_multiple = m_items.count() > 1;
m_dirSizeJob = KIO::directorySize(m_items);
connect(m_dirSizeJob, &KIO::DirectorySizeJob::result, this, &PropertiesDialog::slotDirSizeFinished);
if (!m_multiple) {
KFileItem item = m_items.first();
QString path;
m_fileName = m_items.first().name();
if (item.isDir())
m_iconName = "folder";
else
m_iconName = m_items.first().iconName();
m_mimeType = m_items.first().mimetype();
m_size = KIO::convertSize(m_items.first().size());
m_location = QFileInfo(m_items.first().localPath()).dir().path();
qDebug() << m_items.first().mimetype() << " ???";
m_creationTime = item.time(KFileItem::CreationTime).toString();
m_modifiedTime = item.time(KFileItem::ModificationTime).toString();
m_accessedTime = item.time(KFileItem::AccessTime).toString();
} else {
m_fileName = QString("%1 files").arg(m_items.count());
m_location = QFileInfo(m_items.first().localPath()).dir().path();
}
}
void PropertiesDialog::slotDirSizeFinished(KJob *job)
{
if (job->error())
return;
m_size = KIO::convertSize(m_dirSizeJob->totalSize());
m_dirSizeJob = 0;
emit sizeChanged();
}

View file

@ -0,0 +1,71 @@
#ifndef PROPERTIESDIALOG_H
#define PROPERTIESDIALOG_H
#include <QObject>
#include <QUrl>
#include <KFileItem>
#include <KIO/DirectorySizeJob>
class PropertiesDialog : public QObject
{
Q_OBJECT
Q_PROPERTY(QString location READ location CONSTANT)
Q_PROPERTY(QString fileName READ fileName NOTIFY fileNameChanged)
Q_PROPERTY(QString iconName READ iconName NOTIFY iconNameChanged)
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
Q_PROPERTY(QString size READ size NOTIFY sizeChanged)
Q_PROPERTY(QString creationTime READ creationTime CONSTANT)
Q_PROPERTY(QString modifiedTime READ modifiedTime CONSTANT)
Q_PROPERTY(QString accessedTime READ accessedTime CONSTANT)
Q_PROPERTY(bool multiple READ multiple CONSTANT)
public:
explicit PropertiesDialog(const KFileItem &item, QObject *parent = nullptr);
explicit PropertiesDialog(const KFileItemList &items, QObject *parent = nullptr);
explicit PropertiesDialog(const QUrl &url, QObject *parent = nullptr);
~PropertiesDialog();
static void showDialog(const KFileItem &item);
static void showDialog(const KFileItemList &items);
bool multiple() const;
QString location() const;
QString fileName() const;
QString iconName() const;
QString mimeType() const;
QString size() const;
QString creationTime() const;
QString modifiedTime() const;
QString accessedTime() const;
signals:
void fileNameChanged();
void iconNameChanged();
void sizeChanged();
private:
void init();
private slots:
void slotDirSizeFinished(KJob *job);
private:
KFileItemList m_items;
QString m_location;
QString m_fileName;
QString m_iconName;
QString m_mimeType;
QString m_size;
QString m_creationTime;
QString m_modifiedTime;
QString m_accessedTime;
KIO::DirectorySizeJob *m_dirSizeJob;
bool m_multiple;
};
#endif // PROPERTIESDIALOG_H

239
src/fm.cpp Normal file
View file

@ -0,0 +1,239 @@
/*
* Copyright 2018 Camilo Higuita <milo.h@aol.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, 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 Library 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.
*/
#include "fm.h"
#include <QDateTime>
#include <QFileInfo>
#include <QLocale>
#include <QRegularExpression>
#include <QUrl>
#include <KCoreDirLister>
#include <KFileItem>
#include <KFilePlacesModel>
#include <KIO/CopyJob>
#include <KIO/DeleteJob>
#include <KIO/MkdirJob>
#include <KIO/SimpleJob>
#include <QIcon>
FM::FM(QObject *parent)
: QObject(parent)
#ifdef COMPONENT_SYNCING
, sync(new Syncing(this))
#endif
, dirLister(new KCoreDirLister(this))
{
this->dirLister->setAutoUpdate(true);
const static auto packItems = [](const KFileItemList &items) -> FMH::MODEL_LIST {
return std::accumulate(items.constBegin(), items.constEnd(), FMH::MODEL_LIST(), [](FMH::MODEL_LIST &res, const KFileItem &item) -> FMH::MODEL_LIST {
res << FMH::getFileInfo(item);
return res;
});
};
connect(dirLister, static_cast<void (KCoreDirLister::*)(const QUrl &)>(&KCoreDirLister::completed), this, [&](QUrl url) {
qDebug() << "PATH CONTENT READY" << url;
emit this->pathContentReady(url);
});
connect(dirLister, static_cast<void (KCoreDirLister::*)(const QUrl &, const KFileItemList &items)>(&KCoreDirLister::itemsAdded), this, [&](QUrl dirUrl, KFileItemList items) {
qDebug() << "MORE ITEMS WERE ADDED";
emit this->pathContentItemsReady({dirUrl, packItems(items)});
});
// connect(dirLister, static_cast<void (KCoreDirLister::*)(const KFileItemList &items)>(&KCoreDirLister::newItems), [&](KFileItemList items)
// {
// qDebug()<< "MORE NEW ITEMS WERE ADDED";
// for(const auto &item : items)
// qDebug()<< "MORE <<" << item.url();
//
// emit this->pathContentChanged(dirLister->url());
// });
connect(dirLister, static_cast<void (KCoreDirLister::*)(const KFileItemList &items)>(&KCoreDirLister::itemsDeleted), this, [&](KFileItemList items) {
qDebug() << "ITEMS WERE DELETED";
emit this->pathContentItemsRemoved({dirLister->url(), packItems(items)});
});
connect(dirLister, static_cast<void (KCoreDirLister::*)(const QList<QPair<KFileItem, KFileItem>> &items)>(&KCoreDirLister::refreshItems), this, [&](QList<QPair<KFileItem, KFileItem>> items) {
qDebug() << "ITEMS WERE REFRESHED";
const auto res = std::accumulate(
items.constBegin(), items.constEnd(), QVector<QPair<FMH::MODEL, FMH::MODEL>>(), [](QVector<QPair<FMH::MODEL, FMH::MODEL>> &list, const QPair<KFileItem, KFileItem> &pair) -> QVector<QPair<FMH::MODEL, FMH::MODEL>> {
list << QPair<FMH::MODEL, FMH::MODEL> {FMH::getFileInfo(pair.first), FMH::getFileInfo(pair.second)};
return list;
});
emit this->pathContentItemsChanged(res);
});
#ifdef COMPONENT_SYNCING
connect(this->sync, &Syncing::listReady, [this](const FMH::MODEL_LIST &list, const QUrl &url) {
emit this->cloudServerContentReady(list, url);
});
connect(this->sync, &Syncing::itemReady, [this](const FMH::MODEL &item, const QUrl &url, const Syncing::SIGNAL_TYPE &signalType) {
switch (signalType) {
case Syncing::SIGNAL_TYPE::OPEN:
FMStatic::openUrl(item[FMH::MODEL_KEY::PATH]);
break;
case Syncing::SIGNAL_TYPE::DOWNLOAD:
emit this->cloudItemReady(item, url);
break;
case Syncing::SIGNAL_TYPE::COPY: {
QVariantMap data;
const auto keys = item.keys();
for (auto key : keys)
data.insert(FMH::MODEL_NAME[key], item[key]);
// this->copy(QVariantList {data}, this->sync->getCopyTo());
break;
}
default:
return;
}
});
connect(this->sync, &Syncing::error, [this](const QString &message) {
emit this->warningMessage(message);
});
connect(this->sync, &Syncing::progress, [this](const int &percent) {
emit this->loadProgress(percent);
});
connect(this->sync, &Syncing::dirCreated, [this](const FMH::MODEL &dir, const QUrl &url) {
emit this->newItem(dir, url);
});
connect(this->sync, &Syncing::uploadReady, [this](const FMH::MODEL &item, const QUrl &url) {
emit this->newItem(item, url);
});
#endif
}
void FM::getPathContent(const QUrl &path, const bool &hidden, const bool &onlyDirs, const QStringList &filters, const QDirIterator::IteratorFlags &iteratorFlags)
{
qDebug() << "Getting async path contents";
Q_UNUSED(iteratorFlags)
this->dirLister->setShowingDotFiles(hidden);
this->dirLister->setDirOnlyMode(onlyDirs);
this->dirLister->setNameFilter(filters.join(" "));
if (this->dirLister->openUrl(path))
qDebug() << "GETTING PATH CONTENT" << path;
}
FMH::MODEL_LIST FM::getAppsPath()
{
return FMH::MODEL_LIST {FMH::MODEL {{FMH::MODEL_KEY::ICON, "system-run"},
{FMH::MODEL_KEY::LABEL, FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::APPS_PATH]},
{FMH::MODEL_KEY::PATH, FMH::PATHTYPE_URI[FMH::PATHTYPE_KEY::APPS_PATH]},
{FMH::MODEL_KEY::TYPE, FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::PLACES_PATH]}}};
}
bool FM::getCloudServerContent(const QUrl &path, const QStringList &filters, const int &depth)
{
#ifdef COMPONENT_SYNCING
const auto __list = path.toString().replace("cloud:///", "/").split("/");
if (__list.isEmpty() || __list.size() < 2) {
qWarning() << "Could not parse username to get cloud server content";
return false;
}
auto user = __list[1];
// auto data = this->get(QString("select * from clouds where user = '%1'").arg(user));
QVariantList data;
if (data.isEmpty())
return false;
auto map = data.first().toMap();
user = map[FMH::MODEL_NAME[FMH::MODEL_KEY::USER]].toString();
auto server = map[FMH::MODEL_NAME[FMH::MODEL_KEY::SERVER]].toString();
auto password = map[FMH::MODEL_NAME[FMH::MODEL_KEY::PASSWORD]].toString();
this->sync->setCredentials(server, user, password);
this->sync->listContent(path, filters, depth);
return true;
#else
Q_UNUSED(path)
Q_UNUSED(filters)
Q_UNUSED(depth)
return false;
#endif
}
void FM::createCloudDir(const QString &path, const QString &name)
{
#ifdef COMPONENT_SYNCING
this->sync->createDir(path, name);
#endif
}
void FM::openCloudItem(const QVariantMap &item)
{
#ifdef COMPONENT_SYNCING
FMH::MODEL data;
const auto keys = item.keys();
for (const auto &key : keys)
data.insert(FMH::MODEL_NAME_KEY[key], item[key].toString());
this->sync->resolveFile(data, Syncing::SIGNAL_TYPE::OPEN);
#endif
}
void FM::getCloudItem(const QVariantMap &item)
{
#ifdef COMPONENT_SYNCING
this->sync->resolveFile(FMH::toModel(item), Syncing::SIGNAL_TYPE::DOWNLOAD);
#endif
}
QString FM::resolveUserCloudCachePath(const QString &server, const QString &user)
{
Q_UNUSED(server)
return FMH::CloudCachePath + "opendesktop/" + user;
}
QString FM::resolveLocalCloudPath(const QString &path)
{
#ifdef COMPONENT_SYNCING
return QString(path).replace(FMH::PATHTYPE_URI[FMH::PATHTYPE_KEY::CLOUD_PATH] + this->sync->getUser(), "");
#else
return QString();
#endif
}
bool FM::cut(const QList<QUrl> &urls, const QUrl &where)
{
return FMStatic::cut(urls, where);
}
bool FM::copy(const QList<QUrl> &urls, const QUrl &where)
{
return FMStatic::copy(urls, where);
}

206
src/fm.h Normal file
View file

@ -0,0 +1,206 @@
#ifndef FM_H
#define FM_H
#include <QHash>
#include <QObject>
#include <QStorageInfo>
#include <QStringList>
#include <QVariantList>
#include <QVector>
#include "fmh.h"
#include "fmstatic.h"
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
class KCoreDirLister;
#else
class QFileSystemWatcher;
namespace FMH
{
class FileLoader;
}
/**
* @brief The QDirLister class
* Placeholder for the KCoreDirLister for other system other than GNU Linux
*/
class QDirLister : public QObject
{
Q_OBJECT
public:
explicit QDirLister(QObject *parent = nullptr);
public slots:
/**
* @brief openUrl
* @param url
* @return
*/
bool openUrl(const QUrl &url);
/**
* @brief setNameFilter
* @param filters
*/
void setNameFilter(QString filters);
/**
* @brief setDirOnlyMode
* @param value
*/
void setDirOnlyMode(bool value);
/**
* @brief setShowingDotFiles
* @param value
*/
void setShowingDotFiles(bool value);
signals:
void itemsReady(FMH::MODEL_LIST items, QUrl url);
void itemReady(FMH::MODEL item, QUrl url);
void completed(QUrl url);
void itemsAdded(FMH::MODEL_LIST items, QUrl url);
void itemsDeleted(FMH::MODEL_LIST items, QUrl url);
void newItems(FMH::MODEL_LIST items, QUrl url);
void refreshItems(QVector<QPair<FMH::MODEL, FMH::MODEL>> items, QUrl url);
private:
FMH::FileLoader *m_loader;
QFileSystemWatcher *m_watcher;
FMH::MODEL_LIST m_list;
QString m_nameFilters;
QUrl m_url;
bool m_dirOnly = false;
bool m_showDotFiles = false;
bool m_checking = false;
void reviewChanges();
bool includes(const QUrl &url);
int indexOf(const FMH::MODEL_KEY &key, const QString &value) const;
};
#endif
class Syncing;
class Tagging;
/**
* @brief The FM class
* File management methods with syncing and tagging integration if such components were enabled with COMPONENT_SYNCING and COMPONENT_TAGGING
*/
class FM : public QObject
{
Q_OBJECT
public:
FM(QObject *parent = nullptr);
/** Syncing **/
/**
* @brief getCloudServerContent
* Given a server URL address return the contents. This only works if the syncing component has been enabled COMPONENT_SYNCING
* @param server
* Server URL
* @param filters
* Filters to be applied
* @param depth
* How deep in the directory three go, for example 1 keeps the retrieval in the first level
* @return
*/
bool getCloudServerContent(const QUrl &server, const QStringList &filters = QStringList(), const int &depth = 0);
/**
* @brief createCloudDir
* Creates a directory in the server. This only works if the syncing component has been enabled COMPONENT_SYNCING
* @param path
* Server address URL
* @param name
* Directory name
*/
Q_INVOKABLE void createCloudDir(const QString &path, const QString &name);
/**
* @brief getPathContent
* Given a path URL extract the contents and return the information packaged as a model. This method is asyncronous and once items are ready signals are emitted, such as: pathContentItemsReady or pathContentReady
* @param path
* The directory path
* @param hidden
* If shoudl also pack hidden files
* @param onlyDirs
* Should only pack directories
* @param filters
* Filters to be applied to the retrieval
* @param iteratorFlags
* Directory iterator flags, for reference check QDirIterator documentation
*/
void getPathContent(const QUrl &path, const bool &hidden = false, const bool &onlyDirs = false, const QStringList &filters = QStringList(), const QDirIterator::IteratorFlags &iteratorFlags = QDirIterator::NoIteratorFlags);
/**
* @brief resolveLocalCloudPath
* Given a server address URL resolve it to the local cache URL. This only works if the syncing component has been enabled COMPONENT_SYNCING
* @param path
* Server address
* @return
*/
QString resolveLocalCloudPath(const QString &path);
/**
* @brief getAppsPath
* Gives the path to the applications directory. Missing integration with other system other than GNU Linux
* @return
*/
static FMH::MODEL_LIST getAppsPath();
/**
* @brief resolveUserCloudCachePath
* @param server
* @param user
* @return
*/
static QString resolveUserCloudCachePath(const QString &server, const QString &user);
#ifdef COMPONENT_SYNCING
Syncing *sync;
#endif
private:
#ifdef COMPONENT_TAGGING
Tagging *tag;
#endif
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
KCoreDirLister *dirLister;
#else
QDirLister *dirLister;
#endif
signals:
void cloudServerContentReady(FMH::MODEL_LIST list, const QUrl &url);
void cloudItemReady(FMH::MODEL item, QUrl path); // when a item is downloaded and ready
void pathContentReady(QUrl path);
void pathContentItemsReady(FMH::PATH_CONTENT list);
void pathContentChanged(QUrl path);
void pathContentItemsChanged(QVector<QPair<FMH::MODEL, FMH::MODEL>> items);
void pathContentItemsRemoved(FMH::PATH_CONTENT list);
void warningMessage(QString message);
void loadProgress(int percent);
void dirCreated(FMH::MODEL dir);
void newItem(FMH::MODEL item, QUrl path); // when a new item is created
public slots:
void openCloudItem(const QVariantMap &item);
void getCloudItem(const QVariantMap &item);
/* ACTIONS */
bool copy(const QList<QUrl> &urls, const QUrl &where);
bool cut(const QList<QUrl> &urls, const QUrl &where);
friend class FMStatic;
};
#endif // FM_H

337
src/fmh.cpp Normal file
View file

@ -0,0 +1,337 @@
#include "fmh.h"
namespace FMH
{
const QVector<int> modelRoles(const FMH::MODEL &model)
{
const auto keys = model.keys();
return std::accumulate(keys.begin(), keys.end(), QVector<int>(), [](QVector<int> &res, const FMH::MODEL_KEY &key) {
res.append(key);
return res;
});
}
const QString mapValue(const QVariantMap &map, const FMH::MODEL_KEY &key)
{
return map[FMH::MODEL_NAME[key]].toString();
}
const QVariantMap toMap(const FMH::MODEL &model)
{
QVariantMap map;
for (const auto &key : model.keys())
map.insert(FMH::MODEL_NAME[key], model[key]);
return map;
}
const FMH::MODEL toModel(const QVariantMap &map)
{
FMH::MODEL model;
for (const auto &key : map.keys())
model.insert(FMH::MODEL_NAME_KEY[key], map[key].toString());
return model;
}
const FMH::MODEL_LIST toModelList(const QVariantList &list)
{
FMH::MODEL_LIST res;
return std::accumulate(list.constBegin(), list.constEnd(), res, [](FMH::MODEL_LIST &res, const QVariant &item) -> FMH::MODEL_LIST {
res << FMH::toModel(item.toMap());
return res;
});
}
const QVariantList toMapList(const FMH::MODEL_LIST &list)
{
QVariantList res;
return std::accumulate(list.constBegin(), list.constEnd(), res, [](QVariantList &res, const FMH::MODEL &item) -> QVariantList {
res << FMH::toMap(item);
return res;
});
}
const FMH::MODEL filterModel(const FMH::MODEL &model, const QVector<FMH::MODEL_KEY> &keys)
{
FMH::MODEL res;
return std::accumulate(keys.constBegin(), keys.constEnd(), res, [=](FMH::MODEL &res, const FMH::MODEL_KEY &key) -> FMH::MODEL {
if (model.contains(key))
res[key] = model[key];
return res;
});
}
const QStringList modelToList(const FMH::MODEL_LIST &list, const FMH::MODEL_KEY &key)
{
QStringList res;
return std::accumulate(list.constBegin(), list.constEnd(), res, [key](QStringList &res, const FMH::MODEL &item) -> QStringList {
if (item.contains(key))
res << item[key];
return res;
});
}
bool fileExists(const QUrl &path)
{
if (!path.isLocalFile()) {
qWarning() << "URL recived is not a local file" << path;
return false;
}
return QFileInfo::exists(path.toLocalFile());
}
const QString fileDir(const QUrl &path) // the directory path of the file
{
QString res = path.toString();
if (path.isLocalFile()) {
const QFileInfo file(path.toLocalFile());
if (file.isDir())
res = path.toString();
else
res = QUrl::fromLocalFile(file.dir().absolutePath()).toString();
} else
qWarning() << "The path is not a local one. FM::fileDir";
return res;
}
const QUrl parentDir(const QUrl &path)
{
if (!path.isLocalFile()) {
qWarning() << "URL recived is not a local file, FM::parentDir" << path;
return path;
}
QDir dir(path.toLocalFile());
dir.cdUp();
return QUrl::fromLocalFile(dir.absolutePath());
}
const QVariantMap dirConf(const QUrl &path)
{
if (!path.isLocalFile()) {
qWarning() << "URL recived is not a local file" << path;
return QVariantMap();
}
if (!fileExists(path))
return QVariantMap();
QString icon, iconsize, hidden, detailview, showthumbnail, showterminal;
uint count = 0, sortby = MODEL_KEY::MODIFIED, viewType = 0;
bool foldersFirst = false;
#if defined Q_OS_ANDROID || defined Q_OS_WIN || defined Q_OS_MACOS || defined Q_OS_IOS
QSettings file(path.toLocalFile(), QSettings::Format::NativeFormat);
file.beginGroup(QString("Desktop Entry"));
icon = file.value("Icon").toString();
file.endGroup();
file.beginGroup(QString("Settings"));
hidden = file.value("HiddenFilesShown").toString();
file.endGroup();
file.beginGroup(QString("MAUIFM"));
iconsize = file.value("IconSize").toString();
detailview = file.value("DetailView").toString();
showthumbnail = file.value("ShowThumbnail").toString();
showterminal = file.value("ShowTerminal").toString();
count = file.value("Count").toInt();
sortby = file.value("SortBy").toInt();
foldersFirst = file.value("FoldersFirst").toBool();
viewType = file.value("ViewType").toInt();
file.endGroup();
#else
KConfig file(path.toLocalFile());
icon = file.entryMap(QString("Desktop Entry"))["Icon"];
hidden = file.entryMap(QString("Settings"))["HiddenFilesShown"];
iconsize = file.entryMap(QString("MAUIFM"))["IconSize"];
detailview = file.entryMap(QString("MAUIFM"))["DetailView"];
showthumbnail = file.entryMap(QString("MAUIFM"))["ShowThumbnail"];
showterminal = file.entryMap(QString("MAUIFM"))["ShowTerminal"];
count = file.entryMap(QString("MAUIFM"))["Count"].toInt();
sortby = file.entryMap(QString("MAUIFM"))["SortBy"].toInt();
foldersFirst = file.entryMap(QString("MAUIFM"))["FoldersFirst"] == "true" ? true : false;
viewType = file.entryMap(QString("MAUIFM"))["ViewType"].toInt();
#endif
return QVariantMap({{MODEL_NAME[MODEL_KEY::ICON], icon.isEmpty() ? "folder" : icon},
{MODEL_NAME[MODEL_KEY::ICONSIZE], iconsize},
{MODEL_NAME[MODEL_KEY::COUNT], count},
{MODEL_NAME[MODEL_KEY::SHOWTERMINAL], showterminal.isEmpty() ? "false" : showterminal},
{MODEL_NAME[MODEL_KEY::SHOWTHUMBNAIL], showthumbnail.isEmpty() ? "false" : showthumbnail},
{MODEL_NAME[MODEL_KEY::DETAILVIEW], detailview.isEmpty() ? "false" : detailview},
{MODEL_NAME[MODEL_KEY::HIDDEN], hidden.isEmpty() ? false : (hidden == "true" ? true : false)},
{MODEL_NAME[MODEL_KEY::SORTBY], sortby},
{MODEL_NAME[MODEL_KEY::FOLDERSFIRST], foldersFirst},
{MODEL_NAME[MODEL_KEY::VIEWTYPE], viewType}});
}
void setDirConf(const QUrl &path, const QString &group, const QString &key, const QVariant &value)
{
if (!path.isLocalFile()) {
qWarning() << "URL recived is not a local file" << path;
return;
}
#if defined Q_OS_ANDROID || defined Q_OS_WIN || defined Q_OS_MACOS || defined Q_OS_IOS
QSettings file(path.toLocalFile(), QSettings::Format::IniFormat);
file.beginGroup(group);
file.setValue(key, value);
file.endGroup();
file.sync();
#else
KConfig file(path.toLocalFile(), KConfig::SimpleConfig);
auto kgroup = file.group(group);
kgroup.writeEntry(key, value);
// file.reparseConfiguration();
file.sync();
#endif
}
const QString getIconName(const QUrl &path)
{
if (path.isLocalFile() && QFileInfo(path.toLocalFile()).isDir()) {
if (folderIcon.contains(path.toString()))
return folderIcon[path.toString()];
else {
const auto icon = dirConf(QString(path.toString() + "/%1").arg(".directory"))[MODEL_NAME[MODEL_KEY::ICON]].toString();
return icon.isEmpty() ? "folder" : icon;
}
} else {
QMimeDatabase mime;
const auto type = mime.mimeTypeForFile(path.toString());
return type.iconName();
// KFileItem mime(path);
// return mime.iconName();
}
}
const QString getMime(const QUrl &path)
{
if (!path.isLocalFile()) {
qWarning() << "URL recived is not a local file, getMime" << path;
return QString();
}
const QMimeDatabase mimedb;
return mimedb.mimeTypeForFile(path.toLocalFile()).name();
}
const QUrl thumbnailUrl(const QUrl &url, const QString &mimetype)
{
#if defined Q_OS_LINUX && !defined Q_OS_ANDROID
if (checkFileType(FILTER_TYPE::DOCUMENT, mimetype) || checkFileType(FILTER_TYPE::VIDEO, mimetype)) {
return QUrl("image://thumbnailer/" + url.toString());
}
#endif
if (checkFileType(FILTER_TYPE::IMAGE, mimetype)) {
return url;
}
return QUrl();
}
#if (!defined Q_OS_ANDROID && defined Q_OS_LINUX) || defined Q_OS_WIN
const FMH::MODEL getFileInfo(const KFileItem &kfile)
{
return MODEL {{MODEL_KEY::LABEL, kfile.name()},
{MODEL_KEY::NAME, kfile.name().remove(kfile.name().lastIndexOf("."), kfile.name().size())},
{MODEL_KEY::DATE, kfile.time(KFileItem::FileTimes::CreationTime).toString(Qt::TextDate)},
{MODEL_KEY::MODIFIED, kfile.time(KFileItem::FileTimes::ModificationTime).toString(Qt::TextDate)},
{MODEL_KEY::LAST_READ, kfile.time(KFileItem::FileTimes::AccessTime).toString(Qt::TextDate)},
{MODEL_KEY::PATH, kfile.mostLocalUrl().toString()},
{MODEL_KEY::URL, kfile.mostLocalUrl().toString()},
{MODEL_KEY::THUMBNAIL, thumbnailUrl(kfile.mostLocalUrl(), kfile.mimetype()).toString()},
{MODEL_KEY::SYMLINK, kfile.linkDest()},
{MODEL_KEY::IS_SYMLINK, QVariant(kfile.isLink()).toString()},
{MODEL_KEY::HIDDEN, QVariant(kfile.isHidden()).toString()},
{MODEL_KEY::IS_DIR, QVariant(kfile.isDir()).toString()},
{MODEL_KEY::IS_FILE, QVariant(kfile.isFile()).toString()},
{MODEL_KEY::WRITABLE, QVariant(kfile.isWritable()).toString()},
{MODEL_KEY::READABLE, QVariant(kfile.isReadable()).toString()},
{MODEL_KEY::EXECUTABLE, QVariant(kfile.isDesktopFile()).toString()},
{MODEL_KEY::MIME, kfile.mimetype()},
{MODEL_KEY::GROUP, kfile.group()},
{MODEL_KEY::ICON, kfile.iconName()},
// for set wallpaper.
{MODEL_KEY::IMG, QVariant(kfile.mimetype().startsWith("image/")).toString()},
{MODEL_KEY::SIZE, QString::number(kfile.size())},
{MODEL_KEY::OWNER, kfile.user()},
{MODEL_KEY::COUNT, kfile.isLocalFile() && kfile.isDir() ? QString::number(QDir(kfile.localPath()).count()) : "0"}};
}
#endif
const FMH::MODEL getFileInfoModel(const QUrl &path)
{
MODEL res;
#if defined Q_OS_ANDROID || defined Q_OS_MACOS || defined Q_OS_IOS || defined Q_OS_WIN
const QFileInfo file(path.toLocalFile());
if (!file.exists())
return MODEL();
const auto mime = getMime(path);
res = MODEL {{MODEL_KEY::GROUP, file.group()},
{MODEL_KEY::OWNER, file.owner()},
{MODEL_KEY::SUFFIX, file.completeSuffix()},
{MODEL_KEY::LABEL, /*file.isDir() ? file.baseName() :*/ path == HomePath ? QStringLiteral("Home") : file.fileName()},
{MODEL_KEY::NAME, file.fileName()},
{MODEL_KEY::DATE, file.birthTime().toString(Qt::TextDate)},
{MODEL_KEY::MODIFIED, file.lastModified().toString(Qt::TextDate)},
{MODEL_KEY::LAST_READ, file.lastRead().toString(Qt::TextDate)},
{MODEL_KEY::MIME, mime},
{MODEL_KEY::SYMLINK, file.symLinkTarget()},
{MODEL_KEY::IS_SYMLINK, QVariant(file.isSymLink()).toString()},
{MODEL_KEY::IS_FILE, QVariant(file.isFile()).toString()},
{MODEL_KEY::HIDDEN, QVariant(file.isHidden()).toString()},
{MODEL_KEY::IS_DIR, QVariant(file.isDir()).toString()},
{MODEL_KEY::WRITABLE, QVariant(file.isWritable()).toString()},
{MODEL_KEY::READABLE, QVariant(file.isReadable()).toString()},
{MODEL_KEY::EXECUTABLE, QVariant(file.suffix().endsWith(".desktop")).toString()},
{MODEL_KEY::ICON, getIconName(path)},
{MODEL_KEY::SIZE, QString::number(file.size()) /*locale.formattedDataSize(file.size())*/},
{MODEL_KEY::PATH, path.toString()},
{MODEL_KEY::URL, path.toString()},
{MODEL_KEY::THUMBNAIL, thumbnailUrl(path, mime).toString()},
{MODEL_KEY::COUNT, file.isDir() ? QString::number(QDir(path.toLocalFile()).count()) : "0"}};
#else
res = getFileInfo(KFileItem(path, KFileItem::MimeTypeDetermination::NormalMimeTypeDetermination));
#endif
return res;
}
const QVariantMap getFileInfo(const QUrl &path)
{
return toMap(getFileInfoModel(path));
}
const MODEL getDirInfoModel(const QUrl &path, const QString &type)
{
auto res = getFileInfoModel(path);
res[MODEL_KEY::TYPE] = type;
return res;
}
const QVariantMap getDirInfo(const QUrl &path)
{
return toMap(getDirInfoModel(path));
}
PATHTYPE_KEY getPathType(const QUrl &url)
{
return PATHTYPE_SCHEME_NAME[url.scheme()];
}
bool checkFileType(const FMH::FILTER_TYPE &type, const QString &mimeTypeName)
{
return SUPPORTED_MIMETYPES[type].contains(mimeTypeName);
}
}

1019
src/fmh.h Normal file

File diff suppressed because it is too large Load diff

626
src/fmlist.cpp Normal file
View file

@ -0,0 +1,626 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2018 camilo higuita <milo.h@aol.com>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "fmlist.h"
#include "fm.h"
#if defined Q_OS_LINUX && !defined Q_OS_ANDROID
#include <KIO/EmptyTrashJob>
#endif
#include <QFuture>
#include <QObject>
#include <QThread>
#include <QtConcurrent/QtConcurrentRun>
#include <QtConcurrent>
FMList::FMList(QObject *parent)
: BaseList(parent)
, fm(new FM(this))
{
qRegisterMetaType<FMList *>("const FMList*"); // this is needed for QML to know of FMList in the search method
connect(this->fm, &FM::cloudServerContentReady, [&](const FMH::MODEL_LIST &list, const QUrl &url) {
if (this->path == url) {
this->assignList(list);
}
});
connect(this->fm, &FM::pathContentReady, [&](QUrl) {
emit this->preListChanged();
this->sortList();
this->setStatus({STATUS_CODE::READY, this->list.isEmpty() ? "Nothing here!" : "", this->list.isEmpty() ? "This place seems to be empty" : "", this->list.isEmpty() ? "folder-add" : "", this->list.isEmpty(), true});
emit this->postListChanged();
});
connect(this->fm, &FM::pathContentItemsChanged, [&](QVector<QPair<FMH::MODEL, FMH::MODEL>> res) {
for (const auto &item : qAsConst(res)) {
const auto index = this->indexOf(FMH::MODEL_KEY::PATH, item.first[FMH::MODEL_KEY::PATH]);
if (index >= this->list.size() || index < 0)
return;
this->list[index] = item.second;
emit this->updateModel(index, FMH::modelRoles(item.second));
}
});
connect(this->fm, &FM::pathContentItemsReady, [&](FMH::PATH_CONTENT res) {
if (res.path != this->path)
return;
this->appendToList(res.content);
});
connect(this->fm, &FM::pathContentItemsRemoved, [&](FMH::PATH_CONTENT res) {
if (res.path != this->path)
return;
if (!FMH::fileExists(res.path)) {
this->setStatus({STATUS_CODE::ERROR, "Error", "This URL cannot be listed", "documentinfo", true, false});
return;
}
for (const auto &item : qAsConst(res.content)) {
const auto index = this->indexOf(FMH::MODEL_KEY::PATH, item[FMH::MODEL_KEY::PATH]);
qDebug() << "SUPOSSED TO REMOVED THIS FORM THE LIST" << index << this->list.count() << item[FMH::MODEL_KEY::PATH];
this->remove(index);
}
this->setStatus({STATUS_CODE::READY, this->list.isEmpty() ? "Nothing here!" : "", this->list.isEmpty() ? "This place seems to be empty" : "", this->list.isEmpty() ? "folder-add" : "", this->list.isEmpty(), true});
});
connect(this->fm, &FM::warningMessage, [&](const QString &message) {
emit this->warning(message);
});
connect(this->fm, &FM::loadProgress, [&](const int &percent) {
emit this->progress(percent);
});
connect(this->fm, &FM::pathContentChanged, [&](const QUrl &path) {
qDebug() << "FOLDER PATH CHANGED" << path;
if (path != this->path)
return;
this->sortList();
});
connect(this->fm, &FM::newItem, [&](const FMH::MODEL &item, const QUrl &url) {
if (this->path == url) {
emit this->preItemAppended();
this->list << item;
emit this->postItemAppended();
}
});
}
void FMList::assignList(const FMH::MODEL_LIST &list)
{
emit this->preListChanged();
this->list = list;
this->sortList();
this->setStatus({STATUS_CODE::READY, this->list.isEmpty() ? "Nothing here!" : "", this->list.isEmpty() ? "This place seems to be empty" : "", this->list.isEmpty() ? "folder-add" : "", this->list.isEmpty(), true});
emit this->postListChanged();
}
void FMList::appendToList(const FMH::MODEL_LIST &list)
{
emit this->preItemsAppended(list.size());
this->list << list;
emit this->postItemAppended();
}
void FMList::clear()
{
emit this->preListChanged();
this->list.clear();
emit this->postListChanged();
}
void FMList::setList()
{
qDebug() << "PATHTYPE FOR URL" << pathType << this->path.toString() << this->filters << this;
this->clear();
switch (this->pathType) {
case FMList::PATHTYPE::CLOUD_PATH:
this->fm->getCloudServerContent(this->path.toString(), this->filters, this->cloudDepth);
break; // ASYNC
default: {
const bool exists = this->path.isLocalFile() ? FMH::fileExists(this->path) : true;
if (!exists)
this->setStatus({STATUS_CODE::ERROR, "Error", "This URL cannot be listed", "documentinfo", this->list.isEmpty(), exists});
else {
this->fm->getPathContent(this->path, this->hidden, this->onlyDirs, QStringList() << this->filters << FMH::FILTER_LIST[static_cast<FMH::FILTER_TYPE>(this->filterType)]);
}
break; // ASYNC
}
}
}
void FMList::reset()
{
this->setList();
}
const FMH::MODEL_LIST &FMList::items() const
{
return this->list;
}
FMList::SORTBY FMList::getSortBy() const
{
return this->sort;
}
void FMList::setSortBy(const FMList::SORTBY &key)
{
if (this->sort == key)
return;
emit this->preListChanged();
this->sort = key;
this->sortList();
emit this->sortByChanged();
emit this->postListChanged();
}
void FMList::sortList()
{
const FMH::MODEL_KEY key = static_cast<FMH::MODEL_KEY>(this->sort);
auto index = 0;
if (this->foldersFirst) {
qSort(this->list.begin(), this->list.end(), [](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool {
Q_UNUSED(e2)
const auto key = FMH::MODEL_KEY::MIME;
return e1[key] == "inode/directory";
});
for (const auto &item : qAsConst(this->list))
if (item[FMH::MODEL_KEY::MIME] == "inode/directory")
index++;
else
break;
std::sort(this->list.begin(), this->list.begin() + index, [&key](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool {
switch (key) {
case FMH::MODEL_KEY::SIZE: {
if (e1[key].toDouble() > e2[key].toDouble())
return true;
break;
}
case FMH::MODEL_KEY::MODIFIED:
case FMH::MODEL_KEY::DATE: {
auto currentTime = QDateTime::currentDateTime();
auto date1 = QDateTime::fromString(e1[key], Qt::TextDate);
auto date2 = QDateTime::fromString(e2[key], Qt::TextDate);
if (date1.secsTo(currentTime) < date2.secsTo(currentTime))
return true;
break;
}
case FMH::MODEL_KEY::LABEL: {
const auto str1 = QString(e1[key]).toLower();
const auto str2 = QString(e2[key]).toLower();
if (str1 < str2)
return true;
break;
}
default:
if (e1[key] < e2[key])
return true;
}
return false;
});
}
std::sort(this->list.begin() + index, this->list.end(), [key](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool {
switch (key) {
case FMH::MODEL_KEY::MIME:
if (e1[key] == "inode/directory")
return true;
break;
case FMH::MODEL_KEY::SIZE: {
if (e1[key].toDouble() > e2[key].toDouble())
return true;
break;
}
case FMH::MODEL_KEY::MODIFIED:
case FMH::MODEL_KEY::DATE: {
auto currentTime = QDateTime::currentDateTime();
auto date1 = QDateTime::fromString(e1[key], Qt::TextDate);
auto date2 = QDateTime::fromString(e2[key], Qt::TextDate);
if (date1.secsTo(currentTime) < date2.secsTo(currentTime))
return true;
break;
}
case FMH::MODEL_KEY::LABEL: {
const auto str1 = QString(e1[key]).toLower();
const auto str2 = QString(e2[key]).toLower();
if (str1 < str2)
return true;
break;
}
default:
if (e1[key] < e2[key])
return true;
}
return false;
});
}
QString FMList::getPathName() const
{
return this->pathName;
}
QUrl FMList::getPath() const
{
return this->path;
}
void FMList::setPath(const QUrl &path)
{
QUrl path_ = QUrl::fromUserInput(path.toString().trimmed());
if (this->path == path_)
return;
this->path = path_;
m_navHistory.appendPath(this->path);
this->setStatus({STATUS_CODE::LOADING, "Loading content", "Almost ready!", "view-refresh", true, false});
const auto __scheme = this->path.scheme();
this->pathName = QDir(this->path.toLocalFile()).dirName();
if (__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::CLOUD_PATH]) {
this->pathType = FMList::PATHTYPE::CLOUD_PATH;
} else if (__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::APPS_PATH]) {
this->pathType = FMList::PATHTYPE::APPS_PATH;
} else if (__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::TAGS_PATH]) {
this->pathType = FMList::PATHTYPE::TAGS_PATH;
this->pathName = this->path.path();
} else if (__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::TRASH_PATH]) {
this->pathType = FMList::PATHTYPE::TRASH_PATH;
this->pathName = "Trash";
} else if (__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::PLACES_PATH]) {
this->pathType = FMList::PATHTYPE::PLACES_PATH;
} else if (__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::MTP_PATH]) {
this->pathType = FMList::PATHTYPE::MTP_PATH;
} else if (__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::FISH_PATH]) {
this->pathType = FMList::PATHTYPE::FISH_PATH;
} else if (__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::REMOTE_PATH]) {
this->pathType = FMList::PATHTYPE::REMOTE_PATH;
} else if (__scheme == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::DRIVES_PATH]) {
this->pathType = FMList::PATHTYPE::DRIVES_PATH;
} else {
this->pathType = FMList::PATHTYPE::OTHER_PATH;
}
emit this->pathNameChanged();
emit this->pathTypeChanged();
emit this->pathChanged();
}
FMList::PATHTYPE FMList::getPathType() const
{
return this->pathType;
}
QStringList FMList::getFilters() const
{
return this->filters;
}
void FMList::setFilters(const QStringList &filters)
{
if (this->filters == filters)
return;
this->filters = filters;
emit this->filtersChanged();
}
FMList::FILTER FMList::getFilterType() const
{
return this->filterType;
}
void FMList::setFilterType(const FMList::FILTER &type)
{
if (this->filterType == type)
return;
this->filterType = type;
emit this->filterTypeChanged();
}
bool FMList::getHidden() const
{
return this->hidden;
}
void FMList::setHidden(const bool &state)
{
if (this->hidden == state)
return;
this->hidden = state;
emit this->hiddenChanged();
}
bool FMList::getOnlyDirs() const
{
return this->onlyDirs;
}
void FMList::setOnlyDirs(const bool &state)
{
if (this->onlyDirs == state)
return;
this->onlyDirs = state;
emit this->onlyDirsChanged();
}
void FMList::refresh()
{
emit this->pathChanged();
}
void FMList::createDir(const QString &name)
{
if (this->pathType == FMList::PATHTYPE::CLOUD_PATH) {
#ifdef COMPONENT_SYNCING
this->fm->createCloudDir(QString(this->path.toString()).replace(FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::CLOUD_PATH] + "/" + this->fm->sync->getUser(), ""), name);
#endif
} else {
FMStatic::createDir(this->path, name);
}
}
void FMList::copyInto(const QStringList &urls)
{
this->fm->copy(QUrl::fromStringList(urls), this->path);
}
void FMList::cutInto(const QStringList &urls)
{
this->fm->cut(QUrl::fromStringList(urls), this->path);
// else if(this->pathType == FMList::PATHTYPE::CLOUD_PATH)
// {
// this->fm->createCloudDir(QString(this->path).replace(FMH::PATHTYPE_NAME[FMList::PATHTYPE::CLOUD_PATH]+"/"+this->fm->sync->getUser(), ""), name);
// }
}
void FMList::setDirIcon(const int &index, const QString &iconName)
{
if (index >= this->list.size() || index < 0)
return;
// const auto index_ = this->mappedIndex(index);
const auto path = QUrl(this->list.at(index)[FMH::MODEL_KEY::PATH]);
if (!FMStatic::isDir(path))
return;
FMH::setDirConf(path.toString() + "/.directory", "Desktop Entry", "Icon", iconName);
this->list[index][FMH::MODEL_KEY::ICON] = iconName;
emit this->updateModel(index, QVector<int> {FMH::MODEL_KEY::ICON});
}
const QUrl FMList::getParentPath()
{
switch (this->pathType) {
case FMList::PATHTYPE::PLACES_PATH:
return FMStatic::parentDir(this->path).toString();
default:
return this->previousPath();
}
}
const QUrl FMList::posteriorPath()
{
const auto url = m_navHistory.getPosteriorPath();
if (url.isEmpty())
return this->path;
return url;
}
const QUrl FMList::previousPath()
{
const auto url = m_navHistory.getPreviousPath();
if (url.isEmpty())
return this->path;
return url;
}
bool FMList::getFoldersFirst() const
{
return this->foldersFirst;
}
void FMList::setFoldersFirst(const bool &value)
{
if (this->foldersFirst == value)
return;
emit this->preListChanged();
this->foldersFirst = value;
emit this->foldersFirstChanged();
this->sortList();
emit this->postListChanged();
}
void FMList::search(const QString &query, const FMList *currentFMList)
{
this->search(query, currentFMList->getPath(), currentFMList->getHidden(), currentFMList->getOnlyDirs(), currentFMList->getFilters());
}
void FMList::componentComplete()
{
connect(this, &FMList::pathChanged, this, &FMList::setList);
connect(this, &FMList::filtersChanged, this, &FMList::setList);
connect(this, &FMList::filterTypeChanged, this, &FMList::setList);
connect(this, &FMList::hiddenChanged, this, &FMList::setList);
connect(this, &FMList::onlyDirsChanged, this, &FMList::setList);
this->setList();
}
void FMList::search(const QString &query, const QUrl &path, const bool &hidden, const bool &onlyDirs, const QStringList &filters)
{
qDebug() << "SEARCHING FOR" << query << path;
if (!path.isLocalFile()) {
qWarning() << "URL recived is not a local file. So search will only filter the content" << path;
this->filterContent(query, path);
return;
}
QFutureWatcher<FMH::PATH_CONTENT> *watcher = new QFutureWatcher<FMH::PATH_CONTENT>;
connect(watcher, &QFutureWatcher<FMH::MODEL_LIST>::finished, [=]() {
const auto res = watcher->future().result();
this->assignList(res.content);
emit this->searchResultReady();
watcher->deleteLater();
});
QFuture<FMH::PATH_CONTENT> t1 = QtConcurrent::run([=]() -> FMH::PATH_CONTENT {
FMH::PATH_CONTENT res;
res.path = path.toString();
res.content = FMStatic::search(query, path, hidden, onlyDirs, filters);
return res;
});
watcher->setFuture(t1);
}
void FMList::filterContent(const QString &query, const QUrl &path)
{
if (this->list.isEmpty()) {
qDebug() << "Can not filter content. List is empty";
return;
}
QFutureWatcher<FMH::PATH_CONTENT> *watcher = new QFutureWatcher<FMH::PATH_CONTENT>;
connect(watcher, &QFutureWatcher<FMH::MODEL_LIST>::finished, [=]() {
const auto res = watcher->future().result();
this->assignList(res.content);
emit this->searchResultReady();
watcher->deleteLater();
});
QFuture<FMH::PATH_CONTENT> t1 = QtConcurrent::run([=]() -> FMH::PATH_CONTENT {
FMH::MODEL_LIST m_content;
FMH::PATH_CONTENT res;
for (const auto &item : qAsConst(this->list)) {
if (item[FMH::MODEL_KEY::LABEL].contains(query, Qt::CaseInsensitive) || item[FMH::MODEL_KEY::SUFFIX].contains(query, Qt::CaseInsensitive) || item[FMH::MODEL_KEY::MIME].contains(query, Qt::CaseInsensitive)) {
m_content << item;
}
}
res.path = path.toString();
res.content = m_content;
return res;
});
watcher->setFuture(t1);
}
int FMList::getCloudDepth() const
{
return this->cloudDepth;
}
void FMList::setCloudDepth(const int &value)
{
if (this->cloudDepth == value)
return;
this->cloudDepth = value;
emit this->cloudDepthChanged();
}
PathStatus FMList::getStatus() const
{
return this->m_status;
}
void FMList::setStatus(const PathStatus &status)
{
this->m_status = status;
emit this->statusChanged();
}
void FMList::remove(const int &index)
{
if (index >= this->list.size() || index < 0)
return;
emit this->preItemRemoved(index);
this->list.remove(index);
emit this->postItemRemoved();
}

450
src/fmlist.h Normal file
View file

@ -0,0 +1,450 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2018 Camilo Higuita <email>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef FMLIST_H
#define FMLIST_H
#include "fmh.h"
#include "baselist.h"
#include <QObject>
class FM;
enum STATUS_CODE : uint_fast8_t { LOADING, ERROR, READY };
/**
* @brief The PathStatus class
* Represents the status of a directory, be it non existance, loading or empty.
*/
class PathStatus
{
Q_GADGET
Q_PROPERTY(STATUS_CODE code MEMBER m_code)
Q_PROPERTY(QString title MEMBER m_title)
Q_PROPERTY(QString message MEMBER m_message)
Q_PROPERTY(QString icon MEMBER m_icon)
Q_PROPERTY(bool empty MEMBER m_empty)
Q_PROPERTY(bool exists MEMBER m_exists)
public:
STATUS_CODE m_code;
QString m_title;
QString m_message;
QString m_icon;
bool m_empty = false;
bool m_exists = false;
};
Q_DECLARE_METATYPE(PathStatus)
struct NavHistory {
void appendPath(const QUrl &path)
{
this->prev_history.append(path);
}
QUrl getPosteriorPath()
{
if (this->post_history.isEmpty())
return QUrl();
return this->post_history.takeLast();
}
QUrl getPreviousPath()
{
if (this->prev_history.isEmpty())
return QUrl();
if (this->prev_history.length() < 2)
return this->prev_history.at(0);
this->post_history.append(this->prev_history.takeLast());
return this->prev_history.takeLast();
}
private:
QVector<QUrl> prev_history;
QVector<QUrl> post_history;
};
/**
* @brief The FMList class
* Model for listing the file system files and directories and perfom relevant actions upon it
*/
class FMList : public BaseList
{
Q_OBJECT
// writable
Q_PROPERTY(QUrl path READ getPath WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(bool hidden READ getHidden WRITE setHidden NOTIFY hiddenChanged)
Q_PROPERTY(bool onlyDirs READ getOnlyDirs WRITE setOnlyDirs NOTIFY onlyDirsChanged)
Q_PROPERTY(bool foldersFirst READ getFoldersFirst WRITE setFoldersFirst NOTIFY foldersFirstChanged)
Q_PROPERTY(int cloudDepth READ getCloudDepth WRITE setCloudDepth NOTIFY cloudDepthChanged)
Q_PROPERTY(QStringList filters READ getFilters WRITE setFilters NOTIFY filtersChanged)
Q_PROPERTY(FMList::FILTER filterType READ getFilterType WRITE setFilterType NOTIFY filterTypeChanged)
Q_PROPERTY(FMList::SORTBY sortBy READ getSortBy WRITE setSortBy NOTIFY sortByChanged)
// readonly
Q_PROPERTY(QString pathName READ getPathName NOTIFY pathNameChanged FINAL)
Q_PROPERTY(FMList::PATHTYPE pathType READ getPathType NOTIFY pathTypeChanged FINAL)
Q_PROPERTY(PathStatus status READ getStatus NOTIFY statusChanged FINAL)
Q_PROPERTY(QUrl parentPath READ getParentPath NOTIFY pathChanged)
public:
enum SORTBY : uint_fast8_t {
SIZE = FMH::MODEL_KEY::SIZE,
MODIFIED = FMH::MODEL_KEY::MODIFIED,
DATE = FMH::MODEL_KEY::DATE,
LABEL = FMH::MODEL_KEY::LABEL,
MIME = FMH::MODEL_KEY::MIME,
ADDDATE = FMH::MODEL_KEY::MIME,
TITLE = FMH::MODEL_KEY::TITLE,
PLACE = FMH::MODEL_KEY::PLACE,
FORMAT = FMH::MODEL_KEY::FORMAT
};
Q_ENUM(SORTBY)
enum FILTER : uint_fast8_t {
AUDIO = FMH::FILTER_TYPE::AUDIO,
VIDEO = FMH::FILTER_TYPE::VIDEO,
TEXT = FMH::FILTER_TYPE::TEXT,
IMAGE = FMH::FILTER_TYPE::IMAGE,
DOCUMENT = FMH::FILTER_TYPE::DOCUMENT,
COMPRESSED = FMH::FILTER_TYPE::COMPRESSED,
FONT = FMH::FILTER_TYPE::FONT,
NONE = FMH::FILTER_TYPE::NONE
};
Q_ENUM(FILTER)
enum PATHTYPE : uint_fast8_t {
PLACES_PATH = FMH::PATHTYPE_KEY::PLACES_PATH,
FISH_PATH = FMH::PATHTYPE_KEY::FISH_PATH,
MTP_PATH = FMH::PATHTYPE_KEY::MTP_PATH,
REMOTE_PATH = FMH::PATHTYPE_KEY::REMOTE_PATH,
DRIVES_PATH = FMH::PATHTYPE_KEY::DRIVES_PATH,
REMOVABLE_PATH = FMH::PATHTYPE_KEY::REMOVABLE_PATH,
TAGS_PATH = FMH::PATHTYPE_KEY::TAGS_PATH,
APPS_PATH = FMH::PATHTYPE_KEY::APPS_PATH,
TRASH_PATH = FMH::PATHTYPE_KEY::TRASH_PATH,
CLOUD_PATH = FMH::PATHTYPE_KEY::CLOUD_PATH,
QUICK_PATH = FMH::PATHTYPE_KEY::QUICK_PATH,
OTHER_PATH = FMH::PATHTYPE_KEY::OTHER_PATH
};
Q_ENUM(PATHTYPE)
enum VIEW_TYPE : uint_fast8_t {
ICON_VIEW,
LIST_VIEW,
MILLERS_VIEW
};
Q_ENUM(VIEW_TYPE)
Q_ENUM(STATUS_CODE)
/**
* @brief FMList
* @param parent
*/
FMList(QObject *parent = nullptr);
/**
* @brief items
* @return
*/
const FMH::MODEL_LIST &items() const final override;
/**
* @brief getSortBy
* @return
*/
FMList::SORTBY getSortBy() const;
/**
* @brief setSortBy
* @param key
*/
void setSortBy(const FMList::SORTBY &key);
/**
* @brief componentComplete
*/
void componentComplete() override final;
/**
* @brief getPath
* Current path being watched and model
* @return
* Directory URL
*/
QUrl getPath() const;
/**
* @brief setPath
* Set the directory path to be model
* @param path
* Directory URL
*/
void setPath(const QUrl &path);
/**
* @brief getPathName
* The short name of the current directory
* @return
*/
QString getPathName() const;
/**
* @brief getPathType
* The type of the current path, be it LOCAl, TAGS, CLOUD, APPS, DEVICE or others
* @return
* Path type value
*/
FMList::PATHTYPE getPathType() const;
/**
* @brief getFilters
* The filters being applied to the current directory
* @return
* List of filters
*/
QStringList getFilters() const;
/**
* @brief setFilters
* FIlters to be applied as regular expressions
* @param filters
*/
void setFilters(const QStringList &filters);
/**
* @brief getFilterType
* Filter typebeing applied, for example, filtering by AUDIO or IMAGES etc...
* @return
*/
FMList::FILTER getFilterType() const;
/**
* @brief setFilterType
* Apply a filter type, this a quick shortcut for applying a filter on a file type such as AUDIO, IMAGE, DOCUMENT
* @param type
*/
void setFilterType(const FMList::FILTER &type);
/**
* @brief getHidden
* Returns if the current model is including hidden files
* @return
*/
bool getHidden() const;
/**
* @brief setHidden
* List hidden files in the model
* @param state
*/
void setHidden(const bool &state);
/**
* @brief getOnlyDirs
* Returns if the current model is including only directories or not
* @return
*/
bool getOnlyDirs() const;
/**
* @brief setOnlyDirs
* Only list directories when modeling a directory
* @param state
*/
void setOnlyDirs(const bool &state);
/**
* @brief getParentPath
* Returns a URL to the parent directory of the current directory being modeled or the previous directory if the current URL is not a local file
* @return
*/
const QUrl getParentPath();
/**
* @brief getFoldersFirst
* Returns whether directories are listed first before other files
* @return
*/
bool getFoldersFirst() const;
/**
* @brief setFoldersFirst
* List directories first
* @param value
*/
void setFoldersFirst(const bool &value);
/**
* @brief getCloudDepth
* @return
*/
int getCloudDepth() const;
/**
* @brief setCloudDepth
* @param value
*/
void setCloudDepth(const int &value);
/**
* @brief getStatus
* Get the current status of the current path
* @return
*/
PathStatus getStatus() const;
private:
FM *fm;
void clear();
void reset();
void setList();
void assignList(const FMH::MODEL_LIST &list);
void appendToList(const FMH::MODEL_LIST &list);
void sortList();
void search(const QString &query, const QUrl &path, const bool &hidden = false, const bool &onlyDirs = false, const QStringList &filters = QStringList());
void filterContent(const QString &query, const QUrl &path);
void setStatus(const PathStatus &status);
FMH::MODEL_LIST list = {{}};
QUrl path;
QString pathName = QString();
QStringList filters = {};
bool onlyDirs = false;
bool hidden = false;
bool foldersFirst = false;
int cloudDepth = 1;
PathStatus m_status;
FMList::SORTBY sort = FMList::SORTBY::MODIFIED;
FMList::FILTER filterType = FMList::FILTER::NONE;
FMList::PATHTYPE pathType = FMList::PATHTYPE::PLACES_PATH;
NavHistory m_navHistory;
public slots:
/**
* @brief refresh
* Refresh the model for new changes
*/
void refresh();
/**
* @brief createDir
* Create a new directory within the current directory
* @param name
* Name of the directory
*/
void createDir(const QString &name);
/**
* @brief copyInto
* Copy a list of file URls into the current directory
* @param urls
* List of files
*/
void copyInto(const QStringList &urls);
/**
* @brief cutInto
* Cut/move a list of file URLs to the current directory
* @param urls
* List of files
*/
void cutInto(const QStringList &urls);
/**
* @brief setDirIcon
* Changes the icon of a directory by making use of the directory config file
* @param index
* Index of the directory in the model
* @param iconName
* Name of the new icon
*/
void setDirIcon(const int &index, const QString &iconName);
/**
* @brief remove
* Remove an item from the model, this does not remove the file from the file system
* @param index
*/
void remove(const int &index);
/**
* @brief search
* Perform a search on the current directory. The search is perfrom in another model than the current one
* @param query
* Query for the search
* @param currentFMList
* The information of the model where the search is going to be performed
*/
void search(const QString &query, const FMList *currentFMList);
/**
* @brief previousPath
* Inmediate previous path
* @return
*/
const QUrl previousPath();
/**
* @brief posteriorPath
* Inmediate posterior path
* @return
*/
const QUrl posteriorPath();
signals:
void pathChanged();
void pathNameChanged();
void pathTypeChanged();
void filtersChanged();
void filterTypeChanged();
void hiddenChanged();
void onlyDirsChanged();
void sortByChanged();
void foldersFirstChanged();
void statusChanged();
void cloudDepthChanged();
void warning(QString message);
void progress(int percent);
void searchResultReady();
};
#endif // FMLIST_H

312
src/fmstatic.cpp Normal file
View file

@ -0,0 +1,312 @@
#include "fmstatic.h"
#include <QDesktopServices>
#include <KRun>
#include <KCoreDirLister>
#include <KFileItem>
#include <KFilePlacesModel>
#include <KIO/CopyJob>
#include <KIO/DeleteJob>
#include <KIO/EmptyTrashJob>
#include <KIO/MkdirJob>
#include <KIO/SimpleJob>
#include <QIcon>
FMStatic::FMStatic(QObject *parent)
: QObject(parent)
{
}
FMH::MODEL_LIST FMStatic::packItems(const QStringList &items, const QString &type)
{
FMH::MODEL_LIST data;
for (const auto &path : items) {
if (QUrl(path).isLocalFile() && !FMH::fileExists(path))
continue;
auto model = FMH::getFileInfoModel(path);
model.insert(FMH::MODEL_KEY::TYPE, type);
data << model;
}
return data;
}
FMH::MODEL_LIST FMStatic::getDefaultPaths()
{
return FMStatic::packItems(FMH::defaultPaths, FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::PLACES_PATH]);
}
FMH::MODEL_LIST FMStatic::search(const QString &query, const QUrl &path, const bool &hidden, const bool &onlyDirs, const QStringList &filters)
{
FMH::MODEL_LIST content;
if (!path.isLocalFile()) {
qWarning() << "URL recived is not a local file. FM::search" << path;
return content;
}
if (FMStatic::isDir(path)) {
QDir::Filters dirFilter;
dirFilter = (onlyDirs ? QDir::AllDirs | QDir::NoDotDot | QDir::NoDot : QDir::Files | QDir::AllDirs | QDir::NoDotDot | QDir::NoDot);
if (hidden)
dirFilter = dirFilter | QDir::Hidden | QDir::System;
QDirIterator it(path.toLocalFile(), filters, dirFilter, QDirIterator::Subdirectories);
while (it.hasNext()) {
auto url = it.next();
if (it.fileName().contains(query, Qt::CaseInsensitive)) {
content << FMH::getFileInfoModel(QUrl::fromLocalFile(url));
}
}
} else
qWarning() << "Search path does not exists" << path;
qDebug() << content;
return content;
}
FMH::MODEL_LIST FMStatic::getDevices()
{
FMH::MODEL_LIST drives;
return drives;
}
QVariantMap FMStatic::getDirInfo(const QUrl &path)
{
return FMH::getDirInfo(path);
}
QVariantMap FMStatic::getFileInfo(const QUrl &path)
{
return FMH::getFileInfo(path);
}
bool FMStatic::isDefaultPath(const QString &path)
{
return FMH::defaultPaths.contains(path);
}
QUrl FMStatic::parentDir(const QUrl &path)
{
return FMH::parentDir(path);
}
bool FMStatic::isDir(const QUrl &path)
{
if (!path.isLocalFile()) {
// qWarning() << "URL recived is not a local file. FM::isDir" << path;
return false;
}
const QFileInfo file(path.toLocalFile());
return file.isDir();
}
bool FMStatic::isCloud(const QUrl &path)
{
return path.scheme() == FMH::PATHTYPE_SCHEME[FMH::PATHTYPE_KEY::CLOUD_PATH];
}
bool FMStatic::fileExists(const QUrl &path)
{
return FMH::fileExists(path);
}
QString FMStatic::fileDir(const QUrl &path) // the directory path of the file
{
return FMH::fileDir(path);
}
QString FMStatic::formatSize(const int &size)
{
const QLocale locale;
return locale.formattedDataSize(size);
}
QString FMStatic::formatDate(const QString &dateStr, const QString &format, const QString &initFormat)
{
if (initFormat.isEmpty())
return QDateTime::fromString(dateStr, Qt::TextDate).toString(format);
else
return QDateTime::fromString(dateStr, initFormat).toString(format);
}
QString FMStatic::systemFormatDate(const QString &dateStr)
{
return QLocale::system().toString(QDateTime::fromString(dateStr, Qt::TextDate),
QLocale::ShortFormat);
}
QString FMStatic::formatTime(const qint64 &value)
{
QString tStr;
if (value) {
QTime time((value / 3600) % 60, (value / 60) % 60, value % 60, (value * 1000) % 1000);
QString format = "mm:ss";
if (value > 3600)
format = "hh:mm:ss";
tStr = time.toString(format);
}
return tStr.isEmpty() ? "00:00" : tStr;
}
QString FMStatic::homePath()
{
return FMH::HomePath;
}
bool FMStatic::copy(const QList<QUrl> &urls, const QUrl &destinationDir)
{
auto job = KIO::copy(urls, destinationDir);
job->start();
return true;
}
bool FMStatic::cut(const QList<QUrl> &urls, const QUrl &where)
{
return FMStatic::cut(urls, where, QString());
}
bool FMStatic::cut(const QList<QUrl> &urls, const QUrl &where, const QString &name)
{
QUrl _where = where;
if (!name.isEmpty())
_where = QUrl(where.toString() + "/" + name);
auto job = KIO::move(urls, _where, KIO::HideProgressInfo);
job->start();
return true;
}
bool FMStatic::removeFiles(const QList<QUrl> &urls)
{
auto job = KIO::del(urls);
job->start();
return true;
}
void FMStatic::moveToTrash(const QList<QUrl> &urls)
{
auto job = KIO::trash(urls);
job->start();
}
void FMStatic::emptyTrash()
{
auto job = KIO::emptyTrash();
job->start();
}
bool FMStatic::removeDir(const QUrl &path)
{
bool result = true;
QDir dir(path.toLocalFile());
qDebug() << "TRYING TO REMOVE DIR" << path << path.toLocalFile();
if (dir.exists()) {
Q_FOREACH (QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
if (info.isDir()) {
result = removeDir(QUrl::fromLocalFile(info.absoluteFilePath()));
} else {
result = QFile::remove(info.absoluteFilePath());
}
if (!result) {
return result;
}
}
result = dir.rmdir(path.toLocalFile());
}
return result;
}
bool FMStatic::rename(const QUrl &url, const QString &name)
{
return FMStatic::cut({url}, QUrl(url.toString().left(url.toString().lastIndexOf("/"))), name);
}
bool FMStatic::createDir(const QUrl &path, const QString &name)
{
auto job = KIO::mkdir(name.isEmpty() ? path : QUrl(path.toString() + "/" + name));
job->start();
return true;
}
bool FMStatic::createFile(const QUrl &path, const QString &name)
{
QFile file(path.toLocalFile() + "/" + name);
if (file.open(QIODevice::ReadWrite)) {
file.close();
return true;
}
return false;
}
bool FMStatic::createSymlink(const QUrl &path, const QUrl &where)
{
qDebug() << "trying to create symlink" << path << where;
const auto job = KIO::link({path}, where);
job->start();
return true;
}
bool FMStatic::openUrl(const QUrl &url)
{
KRun::runUrl(url, FMH::getFileInfoModel(url)[FMH::MODEL_KEY::MIME], nullptr, false, KRun::RunFlag::DeleteTemporaryFiles);
return true;
}
void FMStatic::openLocation(const QStringList &urls)
{
for (const auto &url : qAsConst(urls))
QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(url).dir().absolutePath()));
}
const QVariantMap FMStatic::dirConf(const QUrl &path)
{
return FMH::dirConf(path);
}
void FMStatic::setDirConf(const QUrl &path, const QString &group, const QString &key, const QVariant &value)
{
FMH::setDirConf(path, group, key, value);
}
bool FMStatic::checkFileType(const int &type, const QString &mimeTypeName)
{
return FMH::checkFileType(static_cast<FMH::FILTER_TYPE>(type), mimeTypeName);
}
static bool doNameFilter(const QString &name, const QStringList &filters)
{
const auto filtersAccumulate = std::accumulate(filters.constBegin(), filters.constEnd(), QVector<QRegExp> {}, [](QVector<QRegExp> &res, const QString &filter) -> QVector<QRegExp> {
res.append(QRegExp(filter, Qt::CaseInsensitive, QRegExp::Wildcard));
return res;
});
for (const auto &filter : filtersAccumulate) {
if (filter.exactMatch(name)) {
return true;
}
}
return false;
}
QStringList FMStatic::nameFilters(const int &type)
{
return FMH::FILTER_LIST[static_cast<FMH::FILTER_TYPE>(type)];
}
QString FMStatic::iconName(const QString &value)
{
return FMH::getIconName(value);
}

353
src/fmstatic.h Normal file
View file

@ -0,0 +1,353 @@
#ifndef FMSTATIC_H
#define FMSTATIC_H
#include "fmh.h"
#include <QObject>
/**
* @brief The FMStatic class
* STatic file management methods, this class has a constructor only to register to QML, however all methods are static.
*/
class FMStatic : public QObject
{
Q_OBJECT
public:
explicit FMStatic(QObject *parent = nullptr);
public slots:
/**
* @brief search
* Search for files in a path using filters
* @param query
* Term to be searched, such as ".qml" or "music"
* @param path
* The path to perform the search upon
* @param hidden
* If should also search for hidden files
* @param onlyDirs
* If only searching for directories and not files
* @param filters
* List of filter patterns such as {"*.qml"}, it can use regular expressions
* @return
* The search results are returned as a FMH::MODEL_LIST
*/
static FMH::MODEL_LIST search(const QString &query, const QUrl &path, const bool &hidden = false, const bool &onlyDirs = false, const QStringList &filters = QStringList());
/**
* @brief getDevices
* Devices mounted to the file system
* @return
* Represented as a FMH::MODEL_LIST
*/
static FMH::MODEL_LIST getDevices();
/**
* @brief getDefaultPaths
* A model list of the default paths in most systems, such as Home, Pictures, Video, Downloads, Music and Documents folders
* @return
*/
static FMH::MODEL_LIST getDefaultPaths();
/**
* @brief packItems
* Given a list of path URLs pack all the info of such files as a FMH::MODEL_LIST
* @param items
* List of local URLs
* @param type
* The type of the list of urls, such as local, remote etc. This value is inserted with the key FMH::MODEL_KEY::TYPE
* @return
*/
static FMH::MODEL_LIST packItems(const QStringList &items, const QString &type);
/**
* @brief copy
* Perfom a copy of the files to the passed destination
* @param urls
* List of URLs to be copy
* @param destinationDir
* Destination
* @return
* Return if the operation has been succesfull
*/
static bool copy(const QList<QUrl> &urls, const QUrl &destinationDir);
/**
* @brief cut
* Perform a move/cut of a list of files to a destination. This function also moves the associated tags if the tags component has been enabled COMPONENT_TAGGING
* @param urls
* List of URLs to be moved
* @param where
* Destination path
* @return
* If the operation has been sucessfull
*/
static bool cut(const QList<QUrl> &urls, const QUrl &where);
/**
* @brief cut
* @param urls
* @param where
* @param name
* New name of the files to be moved
* @return
*/
static bool cut(const QList<QUrl> &urls, const QUrl &where, const QString &name);
/**
* @brief removeFiles
* List of files to be removed completely. This function also removes the assciated tags to the files if the tagging component has been enabled COMPONENT_TAGGING
* @param urls
* @return
* If the operation has been sucessfull
*/
static bool removeFiles(const QList<QUrl> &urls);
/**
* @brief removeDir
* Remove a directory recursively
* @param path
* Path URL to be rmeoved
* @return
* If the operation has been sucessfull
*/
static bool removeDir(const QUrl &path);
/**
* @brief formatSize
* Format a file size
* @param size
* size in bytes
* @return
* Formated into a readable string
*/
static QString formatSize(const int &size);
/**
* @brief formatTime
* Format a milliseconds value to a readable format
* @param value
* Milliseconds
* @return
* Readable formated value
*/
static QString formatTime(const qint64 &value);
/**
* @brief formatDate
* Given a date string, a format and a intended format return a readable string
* @param dateStr
* Date format
* @param format
* Intended format, by default "dd/MM/yyyy"
* @param initFormat
* Date format
* @return
*/
static QString formatDate(const QString &dateStr, const QString &format = QString("dd/MM/yyyy"), const QString &initFormat = QString());
static QString systemFormatDate(const QString &dateStr);
/**
* @brief homePath
* The default home path in different systems
* @return
*/
static QString homePath();
/**
* @brief parentDir
* Given a file url return its parent directory
* @param path
* The file URL
* @return
* The parent directory URL if it exists otherwise returns the passed URL
*/
static QUrl parentDir(const QUrl &path);
/**
* @brief getDirInfo
* Get info of a directory packed as a QVariantMap model
* @param path
* Path URL
* @return
*/
static QVariantMap getDirInfo(const QUrl &path);
/**
* @brief getFileInfo
* Get file info
* @param path
* @return
* File info packed as a QVariantMap model
*/
static QVariantMap getFileInfo(const QUrl &path);
/**
* @brief isDefaultPath
* Checks if a given path URL is a default path as in returned by the defaultPaths method
* @param path
* @return
*/
static bool isDefaultPath(const QString &path);
/**
* @brief isDir
* If a local file URL is a directory
* @param path
* File URL
* @return
*/
static bool isDir(const QUrl &path);
/**
* @brief isCloud
* If a path is a URL server instead of a local file
* @param path
* @return
*/
static bool isCloud(const QUrl &path);
/**
* @brief fileExists
* Checks if a local file exists in the file system
* @param path
* File URL
* @return
* Existance
*/
static bool fileExists(const QUrl &path);
/**
* if the url is a file path then it returns its directory
* and if it is a directory returns the same path
* */
/**
* @brief fileDir
* Gives the directory URL path of a file, and if it is a directory returns the same path
* @param path
* File path URL
* @return
* The directory URL
*/
static QString fileDir(const QUrl &path);
/**
* @brief dirConf
* The config values of a directory, such values can be any from iconname to specific ones. The config file is stored in the directory as .dir
* @param path
* @return
*/
static const QVariantMap dirConf(const QUrl &path);
/**
* @brief setDirConf
* Write a config key-value to the directory config file
* @param path
* @param group
* @param key
* @param value
*/
static void setDirConf(const QUrl &path, const QString &group, const QString &key, const QVariant &value);
/**
* @brief checkFileType
* Checks if a mimetype belongs to a file type, for example image/jpg belong to the type FMH::FILTER_TYPE
* @param type
* FMH::FILTER_TYPE value
* @param mimeTypeName
* @return
*/
static bool checkFileType(const int &type, const QString &mimeTypeName);
/**
* @brief moveToTrash
* Moves to the trash can the file URLs. The associated tags are kept in case the files are restored.
* @param urls
*/
static void moveToTrash(const QList<QUrl> &urls);
/**
* @brief emptyTrash
* Empty the trash casn
*/
static void emptyTrash();
/**
* @brief rename
* Rename a file to a new name
* @param url
* File URL to be renamed
* @param name
* The short new name of the file, not the new URL, for setting a new URl use cut instead.
* @return
*/
static bool rename(const QUrl &url, const QString &name);
/**
* @brief createDir
* Creates a directory given a base path and a directory name
* @param path
* Base directory path
* @param name
* New directory name
* @return
* If the operation was sucessfull
*/
static bool createDir(const QUrl &path, const QString &name);
/**
* @brief createFile
* Creates a file given the base directory path and a short file name
* @param path
* Base directory path
* @param name
* Name of the new file to be created with the extension
* @return
*/
static bool createFile(const QUrl &path, const QString &name);
/**
* @brief createSymlink
* Creates a symlink
* @param path
* File to be symlinked
* @param where
* Destination of the symlink
* @return
*/
static bool createSymlink(const QUrl &path, const QUrl &where);
/**
* @brief openUrl
* Given a URL it tries to open it using the default app associated to it
* @param url
* The URL to be open
* @return
*/
static bool openUrl(const QUrl &url);
/**
* @brief openLocation
* Open with the default file manager a list of URLs
* @param urls
*/
static void openLocation(const QStringList &urls);
/**
* @brief nameFilters
* Given a filter type return a list of associated name filters, as in suffixes.
* @param type
* The filter type to be mapped to a FMH::FILTER_TYPE
*/
static QStringList nameFilters(const int &type);
/**
* @brief iconName
* Get the icon name associated to the file or name.
* @param value
* The file path or file name
*/
static QString iconName(const QString &value);
};
#endif // FMSTATIC_H

113
src/handy.cpp Normal file
View file

@ -0,0 +1,113 @@
/*
* Copyright 2018 Camilo Higuita <milo.h@aol.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, 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 Library 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.
*/
#include "handy.h"
#include "fmh.h"
#include <QApplication>
#include <QClipboard>
#include <QDebug>
#include <QIcon>
#include <QMimeData>
#include <QOperatingSystemVersion>
#include <QDBusInterface>
Handy::Handy(QObject *parent)
: QObject(parent)
{
}
QVariantMap Handy::userInfo()
{
QString name = qgetenv("USER");
if (name.isEmpty())
name = qgetenv("USERNAME");
return QVariantMap({{FMH::MODEL_NAME[FMH::MODEL_KEY::NAME], name}});
}
QString Handy::getClipboardText()
{
auto clipbopard = QApplication::clipboard();
auto mime = clipbopard->mimeData();
if (mime->hasText())
return clipbopard->text();
return QString();
}
QVariantMap Handy::getClipboard()
{
QVariantMap res;
auto clipboard = QApplication::clipboard();
auto mime = clipboard->mimeData();
if (mime->hasUrls())
res.insert("urls", QUrl::toStringList(mime->urls()));
if (mime->hasText())
res.insert("text", mime->text());
const QByteArray a = mime->data(QStringLiteral("application/x-kde-cutselection"));
res.insert("cut", (!a.isEmpty() && a.at(0) == '1'));
return res;
}
bool Handy::copyToClipboard(const QVariantMap &value, const bool &cut)
{
auto clipboard = QApplication::clipboard();
QMimeData *mimeData = new QMimeData();
if (value.contains("urls"))
mimeData->setUrls(QUrl::fromStringList(value["urls"].toStringList()));
if (value.contains("text"))
mimeData->setText(value["text"].toString());
mimeData->setData(QStringLiteral("application/x-kde-cutselection"), cut ? "1" : "0");
clipboard->setMimeData(mimeData);
return true;
}
void Handy::setAsWallpaper(const QUrl &url)
{
if (!url.isLocalFile())
return;
QDBusInterface iface("org.cutefish.Settings", "/Theme",
"org.cutefish.Theme",
QDBusConnection::sessionBus(), nullptr);
if (iface.isValid())
iface.call("setWallpaper", url.toLocalFile());
}
bool Handy::copyTextToClipboard(const QString &text)
{
QApplication::clipboard()->setText(text);
return true;
}
int Handy::version()
{
return QOperatingSystemVersion::current().majorVersion();
}

81
src/handy.h Normal file
View file

@ -0,0 +1,81 @@
/*
* Copyright 2018 Camilo Higuita <milo.h@aol.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, 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 Library 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.
*/
#ifndef HANDY_H
#define HANDY_H
#include <QObject>
#include <QVariantMap>
/*!
* \brief The Handy class
* Contains useful static methods to be used as an attached property to the Maui application
*/
class Handy : public QObject
{
Q_OBJECT
public:
Handy(QObject *parent = nullptr);
public slots:
/*!
* \brief Returns the major version of the current OS
*
* This function is static.
* \return Major OS version
*/
static int version();
/*!
* \brief Returns a QVariantMap containing basic information about the current user
*
* The pairs keys for the information returned are:
* "name"
* \return QVariantMap with user info
*/
static QVariantMap userInfo();
/*!
* \brief Returns the text contained in the clipboard
* \return QString containing clipboard text
*/
static QString getClipboardText();
static QVariantMap getClipboard();
/*!
* \brief Copies text to the clipboard
* \param text text to be copied to the clipboard
* \return
*/
static bool copyTextToClipboard(const QString &text);
/**
* @brief copyToClipboard
* @param value
* @param cut
* @return
*/
static bool copyToClipboard(const QVariantMap &value, const bool &cut = false);
static void setAsWallpaper(const QUrl &url);
};
#endif // HANDY_H

33
src/iconthemeprovider.cpp Normal file
View file

@ -0,0 +1,33 @@
#include "iconthemeprovider.h"
#include <QIcon>
IconThemeProvider::IconThemeProvider()
: QQuickImageProvider(QQuickImageProvider::Pixmap)
{
}
QPixmap IconThemeProvider::requestPixmap(const QString &id, QSize *realSize,
const QSize &requestedSize)
{
// Sanitize requested size
QSize size(requestedSize);
if (size.width() < 1)
size.setWidth(1);
if (size.height() < 1)
size.setHeight(1);
// Return real size
if (realSize)
*realSize = size;
// Is it a path?
if (id.startsWith(QLatin1Char('/')))
return QPixmap(id).scaled(size);
// Return icon from theme or fallback to a generic icon
QIcon icon = QIcon::fromTheme(id);
if (icon.isNull())
icon = QIcon::fromTheme(QLatin1String("application-x-desktop"));
return icon.pixmap(size);
}

14
src/iconthemeprovider.h Normal file
View file

@ -0,0 +1,14 @@
#ifndef ICONTHEMEPROVIDER_H
#define ICONTHEMEPROVIDER_H
#include <QtQuick/QQuickImageProvider>
class IconThemeProvider : public QQuickImageProvider
{
public:
IconThemeProvider();
QPixmap requestPixmap(const QString &id, QSize *realSize, const QSize &requestedSize);
};
#endif // ICONTHEMEPROVIDER_H

View file

@ -0,0 +1,20 @@
#include "fileitemactions.h"
#include <KMimeTypeTrader>
FileItemActions::FileItemActions(QObject *parent)
: QObject(parent)
{
}
KService::List FileItemActions::associatedApplications(const QStringList &mimeTypeList, const QString &traderConstraint)
{
const KService::List firstOffers = KMimeTypeTrader::self()->query(mimeTypeList.first(), "Application", traderConstraint);
QStringList serviceList;
for (int i = 0; i < firstOffers.count(); ++i) {
}
return KService::List();
}

18
src/lib/fileitemactions.h Normal file
View file

@ -0,0 +1,18 @@
#ifndef FILEITEMACTIONS_H
#define FILEITEMACTIONS_H
#include <QObject>
#include <KService>
class FileItemActions : public QObject
{
Q_OBJECT
public:
explicit FileItemActions(QObject *parent = nullptr);
static KService::List associatedApplications(const QStringList& mimeTypeList, const QString& traderConstraint);
};
#endif // FILEITEMACTIONS_H

2156
src/lib/foldermodel.cpp Normal file

File diff suppressed because it is too large Load diff

372
src/lib/foldermodel.h Normal file
View file

@ -0,0 +1,372 @@
/***************************************************************************
* Copyright (C) 2008 Fredrik Höglund <fredrik@kde.org> *
* Copyright (C) 2011 Marco Martin <mart@kde.org> *
* Copyright (C) 2014 by Eike Hein <hein@kde.org> *
* *
* 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 . *
***************************************************************************/
#ifndef FOLDERMODEL_H
#define FOLDERMODEL_H
#include <QImage>
#include <QItemSelection>
#include <QPointer>
#include <QQmlParserStatus>
#include <QRegExp>
#include <QSet>
#include <QSortFilterProxyModel>
#include <QStringList>
#include <KAbstractViewAdapter>
#include <KActionCollection>
#include <KDirLister>
#include <KFilePreviewGenerator>
#include <KNewFileMenu>
class QDrag;
class QItemSelectionModel;
class QQuickItem;
class KFileCopyToMenu;
class KActionCollection;
class KDirModel;
class KDirWatch;
class KFileItem;
class KFileItemActions;
class KJob;
class KNewFileMenu;
namespace KIO
{
class DropJob;
class StatJob;
}
class ScreenMapper;
class DirLister : public KDirLister
{
Q_OBJECT
public:
explicit DirLister(QObject *parent = nullptr);
~DirLister() override;
Q_SIGNALS:
void error(const QString &string);
protected:
void handleError(KIO::Job *job) override;
};
class FolderModel : public QSortFilterProxyModel, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY(QString iconName READ iconName NOTIFY iconNameChanged)
Q_PROPERTY(QUrl resolvedUrl READ resolvedUrl NOTIFY resolvedUrlChanged)
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
Q_PROPERTY(bool usedByContainment READ usedByContainment WRITE setUsedByContainment NOTIFY usedByContainmentChanged)
Q_PROPERTY(bool locked READ locked WRITE setLocked NOTIFY lockedChanged)
Q_PROPERTY(int sortMode READ sortMode WRITE setSortMode NOTIFY sortModeChanged)
Q_PROPERTY(bool sortDesc READ sortDesc WRITE setSortDesc NOTIFY sortDescChanged)
Q_PROPERTY(bool sortDirsFirst READ sortDirsFirst WRITE setSortDirsFirst NOTIFY sortDirsFirstChanged)
Q_PROPERTY(bool parseDesktopFiles READ parseDesktopFiles WRITE setParseDesktopFiles NOTIFY parseDesktopFilesChanged)
Q_PROPERTY(QObject *viewAdapter READ viewAdapter WRITE setViewAdapter NOTIFY viewAdapterChanged)
Q_PROPERTY(bool previews READ previews WRITE setPreviews NOTIFY previewsChanged)
Q_PROPERTY(QStringList previewPlugins READ previewPlugins WRITE setPreviewPlugins NOTIFY previewPluginsChanged)
Q_PROPERTY(int filterMode READ filterMode WRITE setFilterMode NOTIFY filterModeChanged)
Q_PROPERTY(QString filterPattern READ filterPattern WRITE setFilterPattern NOTIFY filterPatternChanged)
Q_PROPERTY(QStringList filterMimeTypes READ filterMimeTypes WRITE setFilterMimeTypes NOTIFY filterMimeTypesChanged)
Q_PROPERTY(QObject *newMenu READ newMenu CONSTANT)
Q_PROPERTY(bool desktopView READ desktopView WRITE setDesktopView NOTIFY desktopViewChanged)
public:
enum DataRole {
BlankRole = Qt::UserRole + 1,
OverlaysRole,
SelectedRole,
IsDirRole,
IsLinkRole,
IsHiddenRole,
UrlRole,
LinkDestinationUrl,
SizeRole,
TypeRole,
FileNameRole,
};
enum FilterMode {
NoFilter = 0,
FilterShowMatches,
FilterHideMatches,
};
enum Status {
None,
Ready,
Listing,
Canceled,
};
Q_ENUM(Status)
explicit FolderModel(QObject *parent = nullptr);
~FolderModel() override;
QHash<int, QByteArray> roleNames() const override;
static QHash<int, QByteArray> staticRoleNames();
void classBegin() override;
void componentComplete() override;
QString url() const;
void setUrl(const QString &url);
QString iconName() const;
QUrl resolvedUrl() const;
Q_INVOKABLE QUrl resolve(const QString &url);
Status status() const;
QString errorString() const;
bool dragging() const;
bool usedByContainment() const;
void setUsedByContainment(bool used);
bool locked() const;
void setLocked(bool locked);
int sortMode() const;
void setSortMode(int mode);
bool sortDesc() const;
void setSortDesc(bool desc);
bool sortDirsFirst() const;
void setSortDirsFirst(bool enable);
bool parseDesktopFiles() const;
void setParseDesktopFiles(bool enable);
QObject *viewAdapter() const;
void setViewAdapter(QObject *adapter);
bool previews() const;
void setPreviews(bool previews);
QStringList previewPlugins() const;
void setPreviewPlugins(const QStringList &previewPlugins);
int filterMode() const;
void setFilterMode(int filterMode);
QString filterPattern() const;
void setFilterPattern(const QString &pattern);
QStringList filterMimeTypes() const;
void setFilterMimeTypes(const QStringList &mimeList);
KFileItem rootItem() const;
Q_INVOKABLE void up();
Q_INVOKABLE void cd(int row);
Q_INVOKABLE void run(int row);
Q_INVOKABLE void runSelected();
Q_INVOKABLE void rename(int row, const QString &name);
Q_INVOKABLE int fileExtensionBoundary(int row);
Q_INVOKABLE bool hasSelection() const;
Q_INVOKABLE bool isSelected(int row);
Q_INVOKABLE void setSelected(int row);
Q_INVOKABLE void selectAll();
Q_INVOKABLE void toggleSelected(int row);
Q_INVOKABLE void setRangeSelected(int anchor, int to);
Q_INVOKABLE void updateSelection(const QVariantList &rows, bool toggle);
Q_INVOKABLE void clearSelection();
Q_INVOKABLE void pinSelection();
Q_INVOKABLE void unpinSelection();
Q_INVOKABLE void addItemDragImage(int row, int x, int y, int width, int height, const QVariant &image);
Q_INVOKABLE void clearDragImages();
Q_INVOKABLE void setDragHotSpotScrollOffset(int x, int y); // FIXME TODO: Propify.
Q_INVOKABLE QPoint dragCursorOffset(int row);
Q_INVOKABLE void dragSelected(int x, int y);
Q_INVOKABLE void drop(QQuickItem *target, QObject *dropEvent, int row, bool showMenuManually = false);
Q_INVOKABLE void dropCwd(QObject *dropEvent);
Q_INVOKABLE bool isBlank(int row) const;
Q_INVOKABLE QAction *action(const QString &name) const;
QObject *newMenu() const;
Q_INVOKABLE void updateActions();
Q_INVOKABLE void openContextMenu(QQuickItem *visualParent = nullptr, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
Q_INVOKABLE void linkHere(const QUrl &sourceUrl);
Q_INVOKABLE void openPropertiesDialog();
Q_INVOKABLE QString desktopPath() const;
Q_INVOKABLE QString homePath() const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int indexForUrl(const QUrl &url) const;
KFileItem itemForIndex(const QModelIndex &index) const;
bool isDir(const QModelIndex &index, const KDirModel *dirModel) const;
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
Qt::DropActions supportedDragActions() const override;
Qt::DropActions supportedDropActions() const override;
Q_INVOKABLE void paste();
Q_INVOKABLE void copy();
Q_INVOKABLE void cut();
Q_INVOKABLE void deleteSelected();
Q_INVOKABLE void openSelected();
Q_INVOKABLE void undo();
Q_INVOKABLE void refresh();
Q_INVOKABLE void createFolder();
Q_INVOKABLE void setAsWallpaper();
Q_INVOKABLE void openSettings(const QString &itemName);
bool desktopView() const;
void setDesktopView(bool value);
void setScreen(int screen);
bool eventFilter(QObject *watched, QEvent *event) override;
Q_SIGNALS:
void urlChanged() const;
void listingCompleted() const;
void listingCanceled() const;
void iconNameChanged() const;
void resolvedUrlChanged() const;
void statusChanged() const;
void errorStringChanged() const;
void draggingChanged() const;
void usedByContainmentChanged() const;
void lockedChanged() const;
void sortModeChanged() const;
void sortDescChanged() const;
void sortDirsFirstChanged() const;
void parseDesktopFilesChanged() const;
void viewAdapterChanged();
void previewsChanged() const;
void previewPluginsChanged() const;
void filterModeChanged() const;
void filterPatternChanged() const;
void filterMimeTypesChanged() const;
void screenChanged() const;
void requestRename() const;
void move(int x, int y, QList<QUrl> urls);
void popupMenuAboutToShow(KIO::DropJob *dropJob, QMimeData *mimeData, int x, int y);
void desktopViewChanged();
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool matchMimeType(const KFileItem &item) const;
bool matchPattern(const KFileItem &item) const;
private Q_SLOTS:
void dragSelectedInternal(int x, int y);
void dirListFailed(const QString &error);
void statResult(KJob *job);
void evictFromIsDirCache(const KFileItemList &items);
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
void pasteTo();
void moveSelectedToTrash();
void emptyTrashBin();
void restoreSelectedFromTrash();
void undoTextChanged(const QString &text);
void invalidateIfComplete();
void invalidateFilterIfComplete();
void newFileMenuItemCreated(const QUrl &url);
private:
struct DragImage {
int row;
QRect rect;
QPoint cursorOffset;
QImage image;
bool blank;
};
void createActions();
void addDragImage(QDrag *drag, int x, int y);
void setStatus(Status status);
static bool isTrashEmpty();
QList<QUrl> selectedUrls() const;
KDirModel *m_dirModel;
KDirWatch *m_dirWatch;
QString m_url;
mutable QHash<QUrl, bool> m_isDirCache;
mutable QHash<QUrl, KIO::StatJob *> m_isDirJobs;
QItemSelectionModel *m_selectionModel;
QItemSelection m_pinnedSelection;
QModelIndexList m_dragIndexes;
QHash<int, DragImage *> m_dragImages;
QPoint m_dragHotSpotScrollOffset;
bool m_dragInProgress;
bool m_urlChangedWhileDragging;
// target filename to target position of a drop event, note that this deliberately
// is not using the URL to easily support desktop:/ URL schemes
QHash<QString, QPoint> m_dropTargetPositions;
QTimer *m_dropTargetPositionsCleanup;
QPointer<KFilePreviewGenerator> m_previewGenerator;
QPointer<KAbstractViewAdapter> m_viewAdapter;
KActionCollection m_actionCollection;
KNewFileMenu *m_newMenu;
KFileItemActions *m_fileItemActions;
KFileCopyToMenu *m_copyToMenu;
Status m_status = Status::None;
QString m_errorString;
bool m_usedByContainment;
bool m_locked;
int m_sortMode; // FIXME TODO: Enumify.
bool m_sortDesc;
bool m_sortDirsFirst;
bool m_parseDesktopFiles;
bool m_previews;
// An empty previewPlugin list means use default.
// We don't want to leak that fact to the QML side, however, so the property stays empty
// and internally we operate on effectivePreviewPlugins instead.
QStringList m_previewPlugins;
QStringList m_effectivePreviewPlugins;
FilterMode m_filterMode;
QString m_filterPattern;
bool m_filterPatternMatchAll;
QSet<QString> m_mimeSet;
QList<QRegExp> m_regExps;
int m_screen = -1;
bool m_screenUsed;
bool m_complete;
QPoint m_menuPosition;
bool m_isDesktopView;
};
#endif

126
src/lib/itemviewadapter.cpp Normal file
View file

@ -0,0 +1,126 @@
/*
* Copyright © 2008 Fredrik Höglund <fredrik@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "itemviewadapter.h"
#include <QModelIndex>
#include <QPalette>
#include <QSize>
ItemViewAdapter::ItemViewAdapter(QObject *parent)
: KAbstractViewAdapter(parent)
, m_adapterView(nullptr)
, m_adapterModel(nullptr)
, m_adapterIconSize(-1)
{
}
QAbstractItemModel *ItemViewAdapter::model() const
{
return m_adapterModel;
}
QSize ItemViewAdapter::iconSize() const
{
return QSize(m_adapterIconSize, m_adapterIconSize);
}
QPalette ItemViewAdapter::palette() const
{
return QPalette();
}
QRect ItemViewAdapter::visibleArea() const
{
return m_adapterVisibleArea;
}
QRect ItemViewAdapter::visualRect(const QModelIndex &index) const
{
// FIXME TODO: Implemented on DND branch.
Q_UNUSED(index)
return QRect();
}
void ItemViewAdapter::connect(Signal signal, QObject *receiver, const char *slot)
{
if (signal == ScrollBarValueChanged) {
QObject::connect(this, SIGNAL(viewScrolled()), receiver, slot);
} else if (signal == IconSizeChanged) {
QObject::connect(this, SIGNAL(adapterIconSizeChanged()), receiver, slot);
}
}
QAbstractItemModel *ItemViewAdapter::adapterModel() const
{
return m_adapterModel;
}
QObject *ItemViewAdapter::adapterView() const
{
return m_adapterView;
}
void ItemViewAdapter::setAdapterView(QObject *view)
{
if (m_adapterView != view) {
m_adapterView = view;
emit adapterViewChanged();
}
}
void ItemViewAdapter::setAdapterModel(QAbstractItemModel *model)
{
if (m_adapterModel != model) {
m_adapterModel = model;
emit adapterModelChanged();
}
}
int ItemViewAdapter::adapterIconSize() const
{
return m_adapterIconSize;
}
void ItemViewAdapter::setAdapterIconSize(int size)
{
if (m_adapterIconSize != size) {
m_adapterIconSize = size;
emit adapterIconSizeChanged();
}
}
QRect ItemViewAdapter::adapterVisibleArea() const
{
return m_adapterVisibleArea;
}
void ItemViewAdapter::setAdapterVisibleArea(QRect rect)
{
if (m_adapterVisibleArea != rect) {
m_adapterVisibleArea = rect;
emit adapterVisibleAreaChanged();
}
}

72
src/lib/itemviewadapter.h Normal file
View file

@ -0,0 +1,72 @@
/***************************************************************************
* Copyright (C) 2014 by Eike Hein <hein@kde.org> *
* *
* 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 . *
***************************************************************************/
#ifndef ITEMVIEWADAPTER_H
#define ITEMVIEWADAPTER_H
#include <QRect>
#include <KAbstractViewAdapter>
class ItemViewAdapter : public KAbstractViewAdapter
{
Q_OBJECT
Q_PROPERTY(QObject *adapterView READ adapterView WRITE setAdapterView NOTIFY adapterViewChanged)
Q_PROPERTY(QAbstractItemModel *adapterModel READ adapterModel WRITE setAdapterModel NOTIFY adapterModelChanged)
Q_PROPERTY(int adapterIconSize READ adapterIconSize WRITE setAdapterIconSize NOTIFY adapterIconSizeChanged)
Q_PROPERTY(QRect adapterVisibleArea READ adapterVisibleArea WRITE setAdapterVisibleArea NOTIFY adapterVisibleAreaChanged)
public:
explicit ItemViewAdapter(QObject *parent = nullptr);
QAbstractItemModel *model() const override;
QSize iconSize() const override;
QPalette palette() const override;
QRect visibleArea() const override;
QRect visualRect(const QModelIndex &index) const override;
void connect(Signal signal, QObject *receiver, const char *slot) override;
QObject *adapterView() const;
void setAdapterView(QObject *view);
QAbstractItemModel *adapterModel() const;
void setAdapterModel(QAbstractItemModel *model);
int adapterIconSize() const;
void setAdapterIconSize(int size);
QRect adapterVisibleArea() const;
void setAdapterVisibleArea(QRect rect);
Q_SIGNALS:
void viewScrolled() const;
void adapterViewChanged() const;
void adapterModelChanged() const;
void adapterIconSizeChanged() const;
void adapterVisibleAreaChanged() const;
private:
QObject *m_adapterView;
QAbstractItemModel *m_adapterModel;
int m_adapterIconSize;
QRect m_adapterVisibleArea;
};
#endif

58
src/lib/placesitem.cpp Normal file
View file

@ -0,0 +1,58 @@
#include "placesitem.h"
#include <QDebug>
PlacesItem::PlacesItem(const QString &displayName,
const QString &iconName,
QUrl url,
QObject *parent)
: QObject(parent)
, m_displayName(displayName)
, m_iconName(iconName)
, m_url(url)
{
}
QString PlacesItem::displayName() const
{
return m_displayName;
}
void PlacesItem::setDisplayName(const QString &name)
{
m_displayName = name;
}
QString PlacesItem::iconName() const
{
return m_iconName;
}
void PlacesItem::setIconName(const QString &name)
{
m_iconName = name;
}
QString PlacesItem::iconPath() const
{
return m_iconPath;
}
void PlacesItem::setIconPath(const QString &path)
{
m_iconPath = path;
}
QUrl PlacesItem::url() const
{
return m_url;
}
void PlacesItem::setUrl(const QUrl &url)
{
m_url = url;
}
QString PlacesItem::path() const
{
return m_url.toString();
}

38
src/lib/placesitem.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef PLACESITEM_H
#define PLACESITEM_H
#include <QObject>
#include <QUrl>
class PlacesItem : public QObject
{
Q_OBJECT
public:
explicit PlacesItem(const QString &displayName = QString(),
const QString &iconName = QString(),
QUrl url = QUrl(),
QObject *parent = nullptr);
QString displayName() const;
void setDisplayName(const QString &name);
QString iconName() const;
void setIconName(const QString &name);
QString iconPath() const;
void setIconPath(const QString &path);
QUrl url() const;
void setUrl(const QUrl &url);
QString path() const;
private:
QString m_displayName;
QString m_iconName;
QString m_iconPath;
QUrl m_url;
};
#endif // PLACESITEM_H

156
src/lib/placesmodel.cpp Normal file
View file

@ -0,0 +1,156 @@
#include "placesmodel.h"
#include <QStandardPaths>
#include <QDir>
PlacesModel::PlacesModel(QObject *parent)
: QAbstractItemModel(parent)
{
const QString homePath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
if (QDir(homePath).exists()) {
PlacesItem *item = new PlacesItem(tr("Home"), "", QUrl::fromLocalFile(homePath));
item->setIconPath("qrc:/images/folder-home.svg");
m_items.append(item);
}
const QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
if (QDir(desktopPath).exists()) {
PlacesItem *item = new PlacesItem(tr("Desktop"), "", QUrl::fromLocalFile(desktopPath));
item->setIconPath("qrc:/images/folder-desktop.svg");
m_items.append(item);
}
const QString documentsPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
if (QDir(documentsPath).exists()) {
PlacesItem *item = new PlacesItem(tr("Documents"), "folder-documents", QUrl::fromLocalFile(documentsPath));
item->setIconPath("qrc:/images/folder-document.svg");
m_items.append(item);
}
const QString downloadPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
if (QDir(downloadPath).exists()) {
PlacesItem *item = new PlacesItem(tr("Downloads"), "folder-downloads", QUrl::fromLocalFile(downloadPath));
item->setIconPath("qrc:/images/folder-download.svg");
m_items.append(item);
}
const QString musicPath = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
if (QDir(musicPath).exists()) {
PlacesItem *item = new PlacesItem(tr("Music"), "folder-music", QUrl::fromLocalFile(musicPath));
item->setIconPath("qrc:/images/folder-music.svg");
m_items.append(item);
}
const QString picturePath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
if (QDir(picturePath).exists()) {
PlacesItem *item = new PlacesItem(tr("Pictures"), "folder-pictures", QUrl::fromLocalFile(picturePath));
item->setIconPath("qrc:/images/folder-picture.svg");
m_items.append(item);
}
const QString videoPath = QStandardPaths::writableLocation(QStandardPaths::MoviesLocation);
if (QDir(videoPath).exists()) {
PlacesItem *item = new PlacesItem(tr("Videos"), "folder-videos", QUrl::fromLocalFile(videoPath));
item->setIconPath("qrc:/images/folder-video.svg");
m_items.append(item);
}
PlacesItem *trashItem = new PlacesItem(tr("Trash"), "", QUrl(QStringLiteral("trash:/")));
trashItem->setIconPath("qrc:/images/user-trash.svg");
m_items.append(trashItem);
}
PlacesModel::~PlacesModel()
{
}
QHash<int, QByteArray> PlacesModel::roleNames() const
{
QHash<int, QByteArray> roleNames; // = QAbstractItemModel::roleNames();
roleNames[PlacesModel::NameRole] = "name";
roleNames[PlacesModel::IconNameRole] = "icon";
roleNames[PlacesModel::IconPathRole] = "iconPath";
roleNames[PlacesModel::UrlRole] = "url";
roleNames[PlacesModel::PathRole] = "path";
return roleNames;
}
int PlacesModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_items.size();
}
int PlacesModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 1;
}
QVariant PlacesModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
PlacesItem *item = m_items.at(index.row());
switch (role) {
case PlacesModel::NameRole:
return item->displayName();
break;
case PlacesModel::IconNameRole:
return item->iconName();
break;
case PlacesModel::IconPathRole:
return item->iconPath();
break;
case PlacesModel::UrlRole:
return item->url();
break;
case PlacesModel::PathRole:
return item->path();
break;
default:
break;
}
return QVariant();
}
QModelIndex PlacesModel::index(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column != 0 || row >= m_items.size()) {
return QModelIndex();
}
if (parent.isValid()) {
return QModelIndex();
}
return createIndex(row, column, m_items.at(row));
}
QModelIndex PlacesModel::parent(const QModelIndex &child) const
{
Q_UNUSED(child);
return QModelIndex();
}
QVariantMap PlacesModel::get(const int &index) const
{
QVariantMap res;
if (index >= this->rowCount() || index < 0)
return res;
const auto roleNames = this->roleNames();
for (auto i = roleNames.begin(); i != roleNames.end(); ++i) {
res.insert(i.value(), this->index(index, 0).data(i.key()).toString());
}
return res;
}

39
src/lib/placesmodel.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef PLACESMODEL_H
#define PLACESMODEL_H
#include <QAbstractItemModel>
#include "placesitem.h"
class PlacesModel : public QAbstractItemModel
{
Q_OBJECT
public:
enum DataRole {
NameRole = Qt::UserRole + 1,
IconNameRole,
IconPathRole,
UrlRole,
PathRole
};
Q_ENUMS(DataRole);
explicit PlacesModel(QObject *parent = nullptr);
~PlacesModel() override;
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
Q_INVOKABLE QVariantMap get(const int &index) const;
private:
QList<PlacesItem *> m_items;
};
#endif

951
src/lib/positioner.cpp Normal file
View file

@ -0,0 +1,951 @@
/***************************************************************************
* Copyright (C) 2014 by Eike Hein <hein@kde.org> *
* *
* 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 . *
***************************************************************************/
#include "positioner.h"
#include "foldermodel.h"
#include <QDebug>
#include <QTimer>
#include <cstdlib>
Positioner::Positioner(QObject *parent)
: QAbstractItemModel(parent)
, m_enabled(false)
, m_folderModel(nullptr)
, m_perStripe(0)
, m_ignoreNextTransaction(false)
, m_deferApplyPositions(false)
, m_updatePositionsTimer(new QTimer(this))
{
m_updatePositionsTimer->setSingleShot(true);
m_updatePositionsTimer->setInterval(0);
connect(m_updatePositionsTimer, &QTimer::timeout, this, &Positioner::updatePositions);
}
Positioner::~Positioner()
{
}
bool Positioner::enabled() const
{
return m_enabled;
}
void Positioner::setEnabled(bool enabled)
{
if (m_enabled != enabled) {
m_enabled = enabled;
beginResetModel();
if (enabled && m_folderModel) {
initMaps();
}
endResetModel();
emit enabledChanged();
if (!enabled) {
m_updatePositionsTimer->start();
}
}
}
FolderModel *Positioner::folderModel() const
{
return m_folderModel;
}
void Positioner::setFolderModel(QObject *folderModel)
{
if (m_folderModel != folderModel) {
beginResetModel();
if (m_folderModel) {
disconnectSignals(m_folderModel);
}
m_folderModel = qobject_cast<FolderModel *>(folderModel);
if (m_folderModel) {
connectSignals(m_folderModel);
if (m_enabled) {
initMaps();
}
}
endResetModel();
emit folderModelChanged();
}
}
int Positioner::perStripe() const
{
return m_perStripe;
}
void Positioner::setPerStripe(int perStripe)
{
if (m_perStripe != perStripe) {
m_perStripe = perStripe;
emit perStripeChanged();
if (m_enabled && perStripe > 0 && !m_proxyToSource.isEmpty()) {
applyPositions();
}
}
}
QStringList Positioner::positions() const
{
return m_positions;
}
void Positioner::setPositions(const QStringList &positions)
{
if (m_positions != positions) {
m_positions = positions;
emit positionsChanged();
// Defer applying positions until listing completes.
if (m_folderModel->status() == FolderModel::Listing) {
m_deferApplyPositions = true;
} else {
applyPositions();
}
}
}
int Positioner::map(int row) const
{
if (m_enabled && m_folderModel) {
return m_proxyToSource.value(row, -1);
}
return row;
}
int Positioner::nearestItem(int currentIndex, Qt::ArrowType direction)
{
if (!m_enabled || currentIndex >= rowCount()) {
return -1;
}
if (currentIndex < 0) {
return firstRow();
}
int hDirection = 0;
int vDirection = 0;
switch (direction) {
case Qt::LeftArrow:
hDirection = -1;
break;
case Qt::RightArrow:
hDirection = 1;
break;
case Qt::UpArrow:
vDirection = -1;
break;
case Qt::DownArrow:
vDirection = 1;
break;
default:
return -1;
}
QList<int> rows(m_proxyToSource.keys());
std::sort(rows.begin(), rows.end());
int nearestItem = -1;
const QPoint currentPos(currentIndex % m_perStripe, currentIndex / m_perStripe);
int lastDistance = -1;
int distance = 0;
foreach (int row, rows) {
const QPoint pos(row % m_perStripe, row / m_perStripe);
if (row == currentIndex) {
continue;
}
if (hDirection == 0) {
if (vDirection * pos.y() > vDirection * currentPos.y()) {
distance = (pos - currentPos).manhattanLength();
if (nearestItem == -1 || distance < lastDistance || (distance == lastDistance && pos.x() == currentPos.x())) {
nearestItem = row;
lastDistance = distance;
}
}
} else if (vDirection == 0) {
if (hDirection * pos.x() > hDirection * currentPos.x()) {
distance = (pos - currentPos).manhattanLength();
if (nearestItem == -1 || distance < lastDistance || (distance == lastDistance && pos.y() == currentPos.y())) {
nearestItem = row;
lastDistance = distance;
}
}
}
}
return nearestItem;
}
bool Positioner::isBlank(int row) const
{
if (!m_enabled && m_folderModel) {
return m_folderModel->isBlank(row);
}
if (m_proxyToSource.contains(row) && m_folderModel && !m_folderModel->isBlank(m_proxyToSource.value(row))) {
return false;
}
return true;
}
int Positioner::indexForUrl(const QUrl &url) const
{
if (!m_folderModel) {
return -1;
}
const QString &name = url.fileName();
int sourceIndex = -1;
// TODO Optimize.
for (int i = 0; i < m_folderModel->rowCount(); ++i) {
if (m_folderModel->data(m_folderModel->index(i, 0), FolderModel::FileNameRole).toString() == name) {
sourceIndex = i;
break;
}
}
return m_sourceToProxy.value(sourceIndex, -1);
}
void Positioner::setRangeSelected(int anchor, int to)
{
if (!m_folderModel) {
return;
}
if (m_enabled) {
QVariantList indices;
for (int i = qMin(anchor, to); i <= qMax(anchor, to); ++i) {
if (m_proxyToSource.contains(i)) {
indices.append(m_proxyToSource.value(i));
}
}
if (!indices.isEmpty()) {
m_folderModel->updateSelection(indices, false);
}
} else {
m_folderModel->setRangeSelected(anchor, to);
}
}
QHash<int, QByteArray> Positioner::roleNames() const
{
return FolderModel::staticRoleNames();
}
QModelIndex Positioner::index(int row, int column, const QModelIndex &parent) const
{
if (parent.isValid()) {
return QModelIndex();
}
return createIndex(row, column);
}
QModelIndex Positioner::parent(const QModelIndex &index) const
{
if (m_folderModel) {
m_folderModel->parent(index);
}
return QModelIndex();
}
QVariant Positioner::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (m_folderModel) {
if (m_enabled) {
if (m_proxyToSource.contains(index.row())) {
return m_folderModel->data(m_folderModel->index(m_proxyToSource.value(index.row()), 0), role);
} else if (role == FolderModel::BlankRole) {
return true;
}
} else {
return m_folderModel->data(m_folderModel->index(index.row(), 0), role);
}
}
return QVariant();
}
int Positioner::rowCount(const QModelIndex &parent) const
{
if (m_folderModel) {
if (m_enabled) {
if (parent.isValid()) {
return 0;
} else {
return lastRow() + 1;
}
} else {
return m_folderModel->rowCount(parent);
}
}
return 0;
}
int Positioner::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
if (m_folderModel) {
return 1;
}
return 0;
}
void Positioner::reset()
{
beginResetModel();
initMaps();
endResetModel();
m_positions = QStringList();
emit positionsChanged();
}
void Positioner::move(const QVariantList &moves)
{
// Don't allow moves while listing.
if (m_folderModel->status() == FolderModel::Listing) {
m_deferMovePositions = moves;
return;
}
QVector<int> fromIndices;
QVector<int> toIndices;
QVector<int> sourceRows;
for (int i = 0; i < moves.count(); ++i) {
const int isFrom = (i % 2 == 0);
const int v = moves[i].toInt();
if (isFrom) {
if (m_proxyToSource.contains(v)) {
sourceRows.append(m_proxyToSource.value(v));
} else {
sourceRows.append(-1);
}
}
(isFrom ? fromIndices : toIndices).append(v);
}
const int oldCount = rowCount();
for (int i = 0; i < fromIndices.count(); ++i) {
const int from = fromIndices[i];
int to = toIndices[i];
const int sourceRow = sourceRows[i];
if (sourceRow == -1 || from == to) {
continue;
}
if (to == -1) {
to = firstFreeRow();
if (to == -1) {
to = lastRow() + 1;
}
}
if (!fromIndices.contains(to) && !isBlank(to)) {
/* find the next blank space
* we won't be happy if we're moving two icons to the same place
*/
while ((!isBlank(to) && from != to) || toIndices.contains(to)) {
to++;
}
}
toIndices[i] = to;
if (!toIndices.contains(from)) {
m_proxyToSource.remove(from);
}
updateMaps(to, sourceRow);
const QModelIndex &fromIdx = index(from, 0);
emit dataChanged(fromIdx, fromIdx);
if (to < oldCount) {
const QModelIndex &toIdx = index(to, 0);
emit dataChanged(toIdx, toIdx);
}
}
const int newCount = rowCount();
if (newCount > oldCount) {
if (m_beginInsertRowsCalled) {
endInsertRows();
m_beginInsertRowsCalled = false;
}
beginInsertRows(QModelIndex(), oldCount, newCount - 1);
endInsertRows();
}
if (newCount < oldCount) {
beginRemoveRows(QModelIndex(), newCount, oldCount - 1);
endRemoveRows();
}
m_updatePositionsTimer->start();
}
void Positioner::updatePositions()
{
QStringList positions;
if (m_enabled && !m_proxyToSource.isEmpty() && m_perStripe > 0) {
positions.append(QString::number((1 + ((rowCount() - 1) / m_perStripe))));
positions.append(QString::number(m_perStripe));
QHashIterator<int, int> it(m_proxyToSource);
while (it.hasNext()) {
it.next();
const QString &name = m_folderModel->data(m_folderModel->index(it.value(), 0), FolderModel::UrlRole).toString();
if (name.isEmpty()) {
qDebug() << this << it.value() << "Source model doesn't know this index!";
return;
}
positions.append(name);
positions.append(QString::number(qMax(0, it.key() / m_perStripe)));
positions.append(QString::number(qMax(0, it.key() % m_perStripe)));
}
}
if (positions != m_positions) {
m_positions = positions;
emit positionsChanged();
}
}
void Positioner::sourceStatusChanged()
{
if (m_deferApplyPositions && m_folderModel->status() != FolderModel::Listing) {
applyPositions();
}
if (m_deferMovePositions.count() && m_folderModel->status() != FolderModel::Listing) {
move(m_deferMovePositions);
m_deferMovePositions.clear();
}
}
void Positioner::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
if (m_enabled) {
int start = topLeft.row();
int end = bottomRight.row();
for (int i = start; i <= end; ++i) {
if (m_sourceToProxy.contains(i)) {
const QModelIndex &idx = index(m_sourceToProxy.value(i), 0);
emit dataChanged(idx, idx);
}
}
} else {
emit dataChanged(topLeft, bottomRight, roles);
}
}
void Positioner::sourceModelAboutToBeReset()
{
emit beginResetModel();
}
void Positioner::sourceModelReset()
{
if (m_enabled) {
initMaps();
}
emit endResetModel();
}
void Positioner::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
{
if (m_enabled) {
// Don't insert yet if we're waiting for listing to complete to apply
// initial positions;
if (m_deferApplyPositions) {
return;
} else if (m_proxyToSource.isEmpty()) {
beginInsertRows(parent, start, end);
m_beginInsertRowsCalled = true;
initMaps(end + 1);
return;
}
// When new rows are inserted, they might go in the beginning or in the middle.
// In this case we must update first the existing proxy->source and source->proxy
// mapping, otherwise the proxy items will point to the wrong source item.
int count = end - start + 1;
m_sourceToProxy.clear();
for (auto it = m_proxyToSource.begin(); it != m_proxyToSource.end(); ++it) {
int sourceIdx = *it;
if (sourceIdx >= start) {
*it += count;
}
m_sourceToProxy[*it] = it.key();
}
int free = -1;
int rest = -1;
for (int i = start; i <= end; ++i) {
free = firstFreeRow();
if (free != -1) {
updateMaps(free, i);
m_pendingChanges << createIndex(free, 0);
} else {
rest = i;
break;
}
}
if (rest != -1) {
int firstNew = lastRow() + 1;
int remainder = (end - rest);
beginInsertRows(parent, firstNew, firstNew + remainder);
m_beginInsertRowsCalled = true;
for (int i = 0; i <= remainder; ++i) {
updateMaps(firstNew + i, rest + i);
}
} else {
m_ignoreNextTransaction = true;
}
} else {
emit beginInsertRows(parent, start, end);
beginInsertRows(parent, start, end);
m_beginInsertRowsCalled = true;
}
}
void Positioner::sourceRowsAboutToBeMoved(const QModelIndex &sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex &destinationParent,
int destinationRow)
{
emit beginMoveRows(sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow);
}
void Positioner::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
{
if (m_enabled) {
int oldLast = lastRow();
for (int i = first; i <= last; ++i) {
int proxyRow = m_sourceToProxy.take(i);
m_proxyToSource.remove(proxyRow);
m_pendingChanges << createIndex(proxyRow, 0);
}
QHash<int, int> newProxyToSource;
QHash<int, int> newSourceToProxy;
QHashIterator<int, int> it(m_sourceToProxy);
int delta = std::abs(first - last) + 1;
while (it.hasNext()) {
it.next();
if (it.key() > last) {
newProxyToSource.insert(it.value(), it.key() - delta);
newSourceToProxy.insert(it.key() - delta, it.value());
} else {
newProxyToSource.insert(it.value(), it.key());
newSourceToProxy.insert(it.key(), it.value());
}
}
m_proxyToSource = newProxyToSource;
m_sourceToProxy = newSourceToProxy;
int newLast = lastRow();
if (oldLast > newLast) {
int diff = oldLast - newLast;
beginRemoveRows(QModelIndex(), ((oldLast - diff) + 1), oldLast);
} else {
m_ignoreNextTransaction = true;
}
} else {
emit beginRemoveRows(parent, first, last);
}
}
void Positioner::sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
{
Q_UNUSED(parents)
emit layoutAboutToBeChanged(QList<QPersistentModelIndex>(), hint);
}
void Positioner::sourceRowsInserted(const QModelIndex &parent, int first, int last)
{
Q_UNUSED(parent)
Q_UNUSED(first)
Q_UNUSED(last)
if (!m_ignoreNextTransaction) {
if (m_beginInsertRowsCalled) {
endInsertRows();
m_beginInsertRowsCalled = false;
}
} else {
m_ignoreNextTransaction = false;
}
flushPendingChanges();
// Don't generate new positions data if we're waiting for listing to
// complete to apply initial positions.
if (!m_deferApplyPositions) {
m_updatePositionsTimer->start();
}
}
void Positioner::sourceRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow)
{
Q_UNUSED(sourceParent)
Q_UNUSED(sourceStart)
Q_UNUSED(sourceEnd)
Q_UNUSED(destinationParent)
Q_UNUSED(destinationRow)
emit endMoveRows();
}
void Positioner::sourceRowsRemoved(const QModelIndex &parent, int first, int last)
{
Q_UNUSED(parent)
Q_UNUSED(first)
Q_UNUSED(last)
if (!m_ignoreNextTransaction) {
emit endRemoveRows();
} else {
m_ignoreNextTransaction = false;
}
flushPendingChanges();
m_updatePositionsTimer->start();
}
void Positioner::sourceLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
{
Q_UNUSED(parents)
if (m_enabled) {
initMaps();
}
emit layoutChanged(QList<QPersistentModelIndex>(), hint);
}
void Positioner::initMaps(int size)
{
m_proxyToSource.clear();
m_sourceToProxy.clear();
if (size == -1) {
size = m_folderModel->rowCount();
}
if (!size) {
return;
}
for (int i = 0; i < size; ++i) {
updateMaps(i, i);
}
}
void Positioner::updateMaps(int proxyIndex, int sourceIndex)
{
m_proxyToSource.insert(proxyIndex, sourceIndex);
m_sourceToProxy.insert(sourceIndex, proxyIndex);
}
int Positioner::firstRow() const
{
if (!m_proxyToSource.isEmpty()) {
QList<int> keys(m_proxyToSource.keys());
std::sort(keys.begin(), keys.end());
return keys.first();
}
return -1;
}
int Positioner::lastRow() const
{
if (!m_proxyToSource.isEmpty()) {
QList<int> keys(m_proxyToSource.keys());
std::sort(keys.begin(), keys.end());
return keys.last();
}
return 0;
}
int Positioner::firstFreeRow() const
{
if (!m_proxyToSource.isEmpty()) {
int last = lastRow();
for (int i = 0; i <= last; ++i) {
if (!m_proxyToSource.contains(i)) {
return i;
}
}
}
return -1;
}
void Positioner::applyPositions()
{
// We were called while the source model is listing. Defer applying positions
// until listing completes.
if (m_folderModel->status() == FolderModel::Listing) {
m_deferApplyPositions = true;
return;
}
if (m_positions.size() < 5) {
// We were waiting for listing to complete before proxying source rows,
// but we don't have positions to apply. Reset to populate.
if (m_deferApplyPositions) {
m_deferApplyPositions = false;
reset();
}
return;
}
beginResetModel();
m_proxyToSource.clear();
m_sourceToProxy.clear();
const QStringList &positions = m_positions.mid(2);
if (positions.count() % 3 != 0) {
return;
}
QHash<QString, int> sourceIndices;
for (int i = 0; i < m_folderModel->rowCount(); ++i) {
sourceIndices.insert(m_folderModel->data(m_folderModel->index(i, 0), FolderModel::UrlRole).toString(), i);
}
QString name;
int stripe = -1;
int pos = -1;
int sourceIndex = -1;
int index = -1;
bool ok = false;
int offset = 0;
// Restore positions for items that still fit.
for (int i = 0; i < positions.count() / 3; ++i) {
offset = i * 3;
pos = positions.at(offset + 2).toInt(&ok);
if (!ok) {
return;
}
if (pos <= m_perStripe) {
name = positions.at(offset);
stripe = positions.at(offset + 1).toInt(&ok);
if (!ok) {
return;
}
if (!sourceIndices.contains(name)) {
continue;
} else {
sourceIndex = sourceIndices.value(name);
}
index = (stripe * m_perStripe) + pos;
if (m_proxyToSource.contains(index)) {
continue;
}
updateMaps(index, sourceIndex);
sourceIndices.remove(name);
}
}
// Find new positions for items that didn't fit.
for (int i = 0; i < positions.count() / 3; ++i) {
offset = i * 3;
pos = positions.at(offset + 2).toInt(&ok);
if (!ok) {
return;
}
if (pos > m_perStripe) {
name = positions.at(offset);
if (!sourceIndices.contains(name)) {
continue;
} else {
sourceIndex = sourceIndices.take(name);
}
index = firstFreeRow();
if (index == -1) {
index = lastRow() + 1;
}
updateMaps(index, sourceIndex);
}
}
QHashIterator<QString, int> it(sourceIndices);
// Find positions for new source items we don't have records for.
while (it.hasNext()) {
it.next();
index = firstFreeRow();
if (index == -1) {
index = lastRow() + 1;
}
updateMaps(index, it.value());
}
endResetModel();
m_deferApplyPositions = false;
m_updatePositionsTimer->start();
}
void Positioner::flushPendingChanges()
{
if (m_pendingChanges.isEmpty()) {
return;
}
int last = lastRow();
foreach (const QModelIndex &idx, m_pendingChanges) {
if (idx.row() <= last) {
emit dataChanged(idx, idx);
}
}
m_pendingChanges.clear();
}
void Positioner::connectSignals(FolderModel *model)
{
connect(model, &QAbstractItemModel::dataChanged, this, &Positioner::sourceDataChanged, Qt::UniqueConnection);
connect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &Positioner::sourceRowsAboutToBeInserted, Qt::UniqueConnection);
connect(model, &QAbstractItemModel::rowsAboutToBeMoved, this, &Positioner::sourceRowsAboutToBeMoved, Qt::UniqueConnection);
connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &Positioner::sourceRowsAboutToBeRemoved, Qt::UniqueConnection);
connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &Positioner::sourceLayoutAboutToBeChanged, Qt::UniqueConnection);
connect(model, &QAbstractItemModel::rowsInserted, this, &Positioner::sourceRowsInserted, Qt::UniqueConnection);
connect(model, &QAbstractItemModel::rowsMoved, this, &Positioner::sourceRowsMoved, Qt::UniqueConnection);
connect(model, &QAbstractItemModel::rowsRemoved, this, &Positioner::sourceRowsRemoved, Qt::UniqueConnection);
connect(model, &QAbstractItemModel::layoutChanged, this, &Positioner::sourceLayoutChanged, Qt::UniqueConnection);
connect(m_folderModel, &FolderModel::urlChanged, this, &Positioner::reset, Qt::UniqueConnection);
connect(m_folderModel, &FolderModel::statusChanged, this, &Positioner::sourceStatusChanged, Qt::UniqueConnection);
}
void Positioner::disconnectSignals(FolderModel *model)
{
disconnect(model, &QAbstractItemModel::dataChanged, this, &Positioner::sourceDataChanged);
disconnect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &Positioner::sourceRowsAboutToBeInserted);
disconnect(model, &QAbstractItemModel::rowsAboutToBeMoved, this, &Positioner::sourceRowsAboutToBeMoved);
disconnect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &Positioner::sourceRowsAboutToBeRemoved);
disconnect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &Positioner::sourceLayoutAboutToBeChanged);
disconnect(model, &QAbstractItemModel::rowsInserted, this, &Positioner::sourceRowsInserted);
disconnect(model, &QAbstractItemModel::rowsMoved, this, &Positioner::sourceRowsMoved);
disconnect(model, &QAbstractItemModel::rowsRemoved, this, &Positioner::sourceRowsRemoved);
disconnect(model, &QAbstractItemModel::layoutChanged, this, &Positioner::sourceLayoutChanged);
disconnect(m_folderModel, &FolderModel::urlChanged, this, &Positioner::reset);
disconnect(m_folderModel, &FolderModel::statusChanged, this, &Positioner::sourceStatusChanged);
}

140
src/lib/positioner.h Normal file
View file

@ -0,0 +1,140 @@
/***************************************************************************
* Copyright (C) 2014 by Eike Hein <hein@kde.org> *
* *
* 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 . *
***************************************************************************/
#ifndef POSITIONER_H
#define POSITIONER_H
#include <QAbstractItemModel>
class FolderModel;
class QTimer;
class Positioner : public QAbstractItemModel
{
Q_OBJECT
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(FolderModel *folderModel READ folderModel WRITE setFolderModel NOTIFY folderModelChanged)
Q_PROPERTY(int perStripe READ perStripe WRITE setPerStripe NOTIFY perStripeChanged)
Q_PROPERTY(QStringList positions READ positions WRITE setPositions NOTIFY positionsChanged)
public:
explicit Positioner(QObject *parent = nullptr);
~Positioner() override;
bool enabled() const;
void setEnabled(bool enabled);
FolderModel *folderModel() const;
void setFolderModel(QObject *folderModel);
int perStripe() const;
void setPerStripe(int perStripe);
QStringList positions() const;
void setPositions(const QStringList &positions);
Q_INVOKABLE int map(int row) const;
Q_INVOKABLE int nearestItem(int currentIndex, Qt::ArrowType direction);
Q_INVOKABLE bool isBlank(int row) const;
Q_INVOKABLE int indexForUrl(const QUrl &url) const;
Q_INVOKABLE void setRangeSelected(int anchor, int to);
Q_INVOKABLE void reset();
Q_INVOKABLE void move(const QVariantList &moves);
QHash<int, QByteArray> roleNames() const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
#ifdef BUILD_TESTING
QHash<int, int> proxyToSourceMapping() const
{
return m_proxyToSource;
}
QHash<int, int> sourceToProxyMapping() const
{
return m_sourceToProxy;
}
#endif
Q_SIGNALS:
void enabledChanged() const;
void folderModelChanged() const;
void perStripeChanged() const;
void positionsChanged() const;
private Q_SLOTS:
void updatePositions();
void sourceStatusChanged();
void sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);
void sourceModelAboutToBeReset();
void sourceModelReset();
void sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end);
void sourceRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow);
void sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
void sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint);
void sourceRowsInserted(const QModelIndex &parent, int first, int last);
void sourceRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow);
void sourceRowsRemoved(const QModelIndex &parent, int first, int last);
void sourceLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint);
private:
void initMaps(int size = -1);
void updateMaps(int proxyIndex, int sourceIndex);
int firstRow() const;
int lastRow() const;
int firstFreeRow() const;
void applyPositions();
void flushPendingChanges();
void connectSignals(FolderModel *model);
void disconnectSignals(FolderModel *model);
bool m_enabled;
FolderModel *m_folderModel;
int m_perStripe;
int m_lastRow;
QModelIndexList m_pendingChanges;
bool m_ignoreNextTransaction;
QStringList m_positions;
bool m_deferApplyPositions;
QVariantList m_deferMovePositions;
QTimer *m_updatePositionsTimer;
QHash<int, int> m_proxyToSource;
QHash<int, int> m_sourceToProxy;
bool m_beginInsertRowsCalled = false; // used to sync the amount of begin/endInsertRows calls
};
#endif

118
src/main.cpp Normal file
View file

@ -0,0 +1,118 @@
/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: rekols <revenmartin@gmail.com>
*
* 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 3 of the License, or
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QTranslator>
#include <QLocale>
#include <QAction>
#include <QCommandLineParser>
#include "fmlist.h"
#include "fm.h"
#include "basemodel.h"
#include "baselist.h"
#include "handy.h"
#include "placeslist.h"
#include "pathlist.h"
#include "desktop/desktopsettings.h"
#include "desktop/desktopview.h"
#include "rubberband.h"
#include "lib/foldermodel.h"
#include "lib/placesmodel.h"
#include "lib/itemviewadapter.h"
#include "lib/positioner.h"
int main(int argc, char *argv[])
{
const char *uri = "Cutefish.FileManager";
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
app.setOrganizationName("cutefishos");
// Translations
QLocale locale;
QString qmFilePath = QString("%1/%2.qm").arg("/usr/share/cutefish-filemanager/translations/").arg(locale.name());
if (QFile::exists(qmFilePath)) {
QTranslator *translator = new QTranslator(app.instance());
if (translator->load(qmFilePath)) {
app.installTranslator(translator);
} else {
translator->deleteLater();
}
}
QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("File Manager"));
parser.addHelpOption();
QCommandLineOption desktopOption(QStringList() << "d" << "desktop" << "Desktop Mode");
parser.addOption(desktopOption);
parser.process(app);
qmlRegisterAnonymousType<QAction>(uri, 1);
qmlRegisterType<DesktopSettings>(uri, 1, 0, "DesktopSettings");
qmlRegisterType<RubberBand>(uri, 1, 0, "RubberBand");
qmlRegisterType<FolderModel>(uri, 1, 0, "FolderModel");
qmlRegisterType<ItemViewAdapter>(uri, 1, 0, "ItemViewAdapter");
qmlRegisterType<Positioner>(uri, 1, 0, "Positioner");
qmlRegisterType<PlacesModel>(uri, 1, 0, "PlacesModel");
if (parser.isSet(desktopOption)) {
DesktopView view;
view.show();
return app.exec();
}
qmlRegisterAnonymousType<BaseList>(uri, 1); // ABSTRACT BASE LIST
qmlRegisterType<BaseModel>(uri, 1, 0, "BaseModel"); // BASE MODEL
qmlRegisterType<PlacesList>(uri, 1, 0, "PlacesList");
qmlRegisterType<PathList>(uri, 1, 0, "PathList");
qmlRegisterType<FMList>(uri, 1, 0, "FMList");
qmlRegisterSingletonType<FMStatic>(uri, 1, 0, "FM", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * {
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return new FMStatic;
});
qmlRegisterSingletonType<Handy>(uri, 1, 0, "Handy", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject * {
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return new Handy;
});
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/qml/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}

126
src/pathlist.cpp Normal file
View file

@ -0,0 +1,126 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2019 camilo <chiguitar@unal.edu.co>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "pathlist.h"
PathList::PathList(QObject *parent)
: BaseList(parent)
{
}
QVariantMap PathList::get(const int &index) const
{
if (this->list.isEmpty() || index >= this->list.size() || index < 0) {
return QVariantMap();
}
const auto model = this->list.at(index);
return FMH::toMap(model);
}
QString PathList::getPath() const
{
return this->m_path;
}
const FMH::MODEL_LIST &PathList::items() const
{
return this->list;
}
void PathList::setList()
{
const auto paths = PathList::splitPath(m_path);
if (this->list.isEmpty()) {
emit this->preListChanged();
this->list << paths;
emit this->postListChanged();
} else {
const int index = [&]() -> int {
int i = 0;
for (const auto &item : qAsConst(list)) {
if (i < paths.size()) {
if (item[FMH::MODEL_KEY::PATH] != paths[i][FMH::MODEL_KEY::PATH]) {
break;
} else
i++;
} else
break;
}
return i;
}();
for (auto i = this->list.size() - 1; i >= index; i--) {
emit preItemRemoved(i);
this->list.removeAt(i);
emit postItemRemoved();
}
for (auto i = index; i < paths.size(); i++) {
emit preItemAppended();
this->list << paths[i];
emit postItemAppended();
}
}
}
void PathList::setPath(const QString &path)
{
if (path == this->m_path)
return;
this->m_path = path;
this->setList();
emit this->pathChanged();
qDebug() << this->list;
}
FMH::MODEL_LIST PathList::splitPath(const QString &path)
{
FMH::MODEL_LIST res;
QString _url = path;
while (_url.endsWith("/"))
_url.chop(1);
_url += "/";
const auto count = _url.count("/");
for (auto i = 0; i < count; i++) {
_url = QString(_url).left(_url.lastIndexOf("/"));
auto label = QString(_url).right(_url.length() - _url.lastIndexOf("/") - 1);
if (label.isEmpty())
continue;
if (label.contains(":") && i == count - 1) // handle the protocol
{
res << FMH::MODEL {{FMH::MODEL_KEY::LABEL, "/"}, {FMH::MODEL_KEY::PATH, _url + "///"}};
break;
}
res << FMH::MODEL {{FMH::MODEL_KEY::LABEL, label}, {FMH::MODEL_KEY::PATH, _url}};
}
std::reverse(res.begin(), res.end());
return res;
}

71
src/pathlist.h Normal file
View file

@ -0,0 +1,71 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2019 camilo <chiguitar@unal.edu.co>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef PATHLIST_H
#define PATHLIST_H
#include "baselist.h"
/**
* @brief The PathList class
*/
class PathList : public BaseList
{
Q_OBJECT
Q_PROPERTY(QString path READ getPath WRITE setPath NOTIFY pathChanged)
public:
PathList(QObject *parent = nullptr);
const FMH::MODEL_LIST &items() const override;
/**
* @brief setPath
* @param path
*/
void setPath(const QString &path);
/**
* @brief getPath
* @return
*/
QString getPath() const;
/**
* @brief get
* @param index
* @return
*/
QVariantMap get(const int &index) const;
private:
FMH::MODEL_LIST list;
QString m_path;
static FMH::MODEL_LIST splitPath(const QString &path);
void setList();
signals:
/**
* @brief pathChanged
*/
void pathChanged();
};
#endif // PATHLIST_H

233
src/placeslist.cpp Normal file
View file

@ -0,0 +1,233 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2018 camilo <email>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "placeslist.h"
#include "fm.h"
#include <QEventLoop>
#include <QFileSystemWatcher>
#include <QIcon>
#include <QTimer>
#include <KFilePlacesModel>
PlacesList::PlacesList(QObject *parent)
: BaseList(parent)
, fm(new FM(this))
, model(new KFilePlacesModel(this))
, watcher(new QFileSystemWatcher(this))
{
/*
* The watcher signal returns a local file URL withouth a scheme, and the model is using a local file URL with file:// scheme.
* So those need to be correctly mapped
* */
connect(watcher, &QFileSystemWatcher::directoryChanged, [&](const QString &path) {
if (this->count.contains(QUrl::fromLocalFile(path).toString())) {
const auto oldCount = this->count[QUrl::fromLocalFile(path).toString()];
const auto index = this->indexOf(FMH::MODEL_KEY::PATH, QUrl::fromLocalFile(path).toString());
const QDir dir(path);
const auto newCount = dir.count();
int count = newCount - oldCount;
this->list[index][FMH::MODEL_KEY::COUNT] = QString::number(std::max(0, count));
emit this->updateModel(index, {FMH::MODEL_KEY::COUNT});
}
});
connect(this->model, &KFilePlacesModel::reloaded, [this]() {
this->setList();
});
connect(this->model, &KFilePlacesModel::rowsInserted, [this](const QModelIndex, int, int) {
this->setList();
emit this->bookmarksChanged();
/*emit this->preListChanged();
for (int i = first; i <= last; i++)
{
const QModelIndex index = model->index(i, 0);
if(this->groups.contains(model->groupType(index)))
{
this->list << getGroup(*this->model, static_cast<FMH::PATHTYPE_KEY>(model->groupType(index)));
}
}
emit this->postListChanged(); */
}); // TODO improve the usage of the model
}
void PlacesList::watchPath(const QString &path)
{
if (path.isEmpty() || !FMH::fileExists(path) || !QUrl(path).isLocalFile())
return;
this->watcher->addPath(QUrl(path).toLocalFile());
}
void PlacesList::componentComplete()
{
connect(this, &PlacesList::groupsChanged, this, &PlacesList::setList);
this->setList();
}
const FMH::MODEL_LIST &PlacesList::items() const
{
return this->list;
}
FMH::MODEL_LIST PlacesList::getGroup(const KFilePlacesModel &model, const FMH::PATHTYPE_KEY &type)
{
FMH::MODEL_LIST res;
if (type == FMH::PATHTYPE_KEY::QUICK_PATH) {
res << FMH::MODEL {{FMH::MODEL_KEY::PATH, FMH::PATHTYPE_URI[FMH::PATHTYPE_KEY::TAGS_PATH] + "fav"}, {FMH::MODEL_KEY::ICON, "love"}, {FMH::MODEL_KEY::LABEL, "Favorite"}, {FMH::MODEL_KEY::TYPE, "Quick"}};
#if defined Q_OS_LINUX && !defined Q_OS_ANDROID
res << FMH::MODEL {{FMH::MODEL_KEY::PATH, "recentdocuments:///"}, {FMH::MODEL_KEY::ICON, "view-media-recent"}, {FMH::MODEL_KEY::LABEL, "Recent"}, {FMH::MODEL_KEY::TYPE, "Quick"}};
#endif
return res;
}
if (type == FMH::PATHTYPE_KEY::PLACES_PATH) {
res << FMStatic::getDefaultPaths();
}
const auto group = model.groupIndexes(static_cast<KFilePlacesModel::GroupType>(type));
res << std::accumulate(group.constBegin(), group.constEnd(), FMH::MODEL_LIST(), [&model, &type](FMH::MODEL_LIST &list, const QModelIndex &index) -> FMH::MODEL_LIST {
const QUrl url = model.url(index);
if (type == FMH::PATHTYPE_KEY::PLACES_PATH && FMH::defaultPaths.contains(url.toString()))
return list;
if (type == FMH::PATHTYPE_KEY::PLACES_PATH && url.isLocalFile() && !FMH::fileExists(url))
return list;
list << FMH::MODEL {{FMH::MODEL_KEY::PATH, url.toString()},
{FMH::MODEL_KEY::URL, url.toString()},
{FMH::MODEL_KEY::ICON, model.icon(index).name()},
{FMH::MODEL_KEY::LABEL, model.text(index)},
{FMH::MODEL_KEY::NAME, model.text(index)},
{FMH::MODEL_KEY::TYPE, type == FMH::PATHTYPE_KEY::PLACES_PATH ? FMH::PATHTYPE_LABEL[FMH::PATHTYPE_KEY::BOOKMARKS_PATH] : FMH::PATHTYPE_LABEL[type]}};
return list;
});
return res;
}
void PlacesList::setList()
{
if (this->groups.isEmpty())
return;
emit this->preListChanged();
this->list.clear();
for (const auto &group : qAsConst(this->groups)) {
switch (group) {
case FMH::PATHTYPE_KEY::PLACES_PATH:
this->list << getGroup(*this->model, FMH::PATHTYPE_KEY::PLACES_PATH);
break;
case FMH::PATHTYPE_KEY::QUICK_PATH:
this->list << getGroup(*this->model, FMH::PATHTYPE_KEY::QUICK_PATH);
break;
case FMH::PATHTYPE_KEY::APPS_PATH:
this->list << FM::getAppsPath();
break;
case FMH::PATHTYPE_KEY::DRIVES_PATH:
this->list << getGroup(*this->model, FMH::PATHTYPE_KEY::DRIVES_PATH);
break;
case FMH::PATHTYPE_KEY::REMOTE_PATH:
this->list << getGroup(*this->model, FMH::PATHTYPE_KEY::REMOTE_PATH);
break;
case FMH::PATHTYPE_KEY::REMOVABLE_PATH:
this->list << getGroup(*this->model, FMH::PATHTYPE_KEY::REMOVABLE_PATH);
break;
}
}
this->setCount();
emit this->postListChanged();
}
void PlacesList::setCount()
{
this->watcher->removePaths(this->watcher->directories());
for (auto &data : this->list) {
const auto path = data[FMH::MODEL_KEY::URL];
if (FMStatic::isDir(path)) {
data.insert(FMH::MODEL_KEY::COUNT, "0");
QDir dir(QUrl(path).toLocalFile());
const auto count = dir.count();
this->count.insert(path, count);
this->watchPath(path);
}
}
}
QList<int> PlacesList::getGroups() const
{
return this->groups;
}
void PlacesList::setGroups(const QList<int> &value)
{
if (this->groups == value)
return;
this->groups = value;
emit this->groupsChanged();
}
QVariantMap PlacesList::get(const int &index) const
{
if (index >= this->list.size() || index < 0)
return QVariantMap();
const auto model = this->list.at(index);
return FMH::toMap(model);
}
void PlacesList::clearBadgeCount(const int &index)
{
this->list[index][FMH::MODEL_KEY::COUNT] = "0";
emit this->updateModel(index, {FMH::MODEL_KEY::COUNT});
}
void PlacesList::removePlace(const int &index)
{
if (index >= this->list.size() || index < 0)
return;
emit this->preItemRemoved(index);
this->model->removePlace(this->model->closestItem(this->list.at(index)[FMH::MODEL_KEY::PATH]));
this->list.removeAt(index);
emit this->postItemRemoved();
}
bool PlacesList::contains(const QUrl &path)
{
return this->exists(FMH::MODEL_KEY::PATH, path.toString());
}

102
src/placeslist.h Normal file
View file

@ -0,0 +1,102 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2018 camilo <email>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef PLACESLIST_H
#define PLACESLIST_H
#include "baselist.h"
#include <QObject>
class FM;
class KFilePlacesModel;
class QFileSystemWatcher;
class PlacesList : public BaseList
{
Q_OBJECT
Q_PROPERTY(QList<int> groups READ getGroups WRITE setGroups NOTIFY groupsChanged)
public:
PlacesList(QObject *parent = nullptr);
const FMH::MODEL_LIST &items() const override;
QList<int> getGroups() const;
void setGroups(const QList<int> &value);
void componentComplete() override final;
/**
* @brief get
* Gets a item in the model.
* @param index
* Index of the item in the model. The given index is not mapped to a filtered or sorted model
* @return
* The data of the place
*/
QVariantMap get(const int &index) const;
protected:
void setList();
void reset();
public slots:
/**
* @brief clearBadgeCount
* Clears the count associated to a place at a given index in the model
* @param index
*/
void clearBadgeCount(const int &index);
/**
* @brief removePlace
* Removes a place from the model and if the data at the given index is a file URL bookmark then it gets removed from the bookmarks.
* @param index
* Index of the item to be removed in the model
*/
void removePlace(const int &index);
/**
* @brief contains
* Checks of a file URL exists in the places model
* @param path
* File URL to be checked
* @return
* True if it exists otherwise false
*/
bool contains(const QUrl &path);
private:
FM *fm;
FMH::MODEL_LIST list;
KFilePlacesModel *model;
QHash<QString, int> count;
QList<int> groups;
QFileSystemWatcher *watcher;
void watchPath(const QString &path);
void setCount();
static FMH::MODEL_LIST getGroup(const KFilePlacesModel &model, const FMH::PATHTYPE_KEY &type);
signals:
void groupsChanged();
void bookmarksChanged();
};
#endif // PLACESLIST_H

64
src/rubberband.cpp Normal file
View file

@ -0,0 +1,64 @@
#include "rubberband.h"
#include <QApplication>
#include <QPainter>
#include <QStyleOptionRubberBand>
RubberBand::RubberBand(QQuickItem *parent)
: QQuickPaintedItem(parent)
{
}
RubberBand::~RubberBand()
{
}
void RubberBand::paint(QPainter *painter)
{
if (!qApp) {
return;
}
QPalette palette;
palette.setColor(QPalette::Highlight, m_color);
QStyleOptionRubberBand opt;
opt.state = QStyle::State_None;
opt.direction = qApp->layoutDirection();
opt.styleObject = this;
opt.palette = palette;
opt.shape = QRubberBand::Rectangle;
opt.opaque = false;
opt.rect = contentsBoundingRect().toRect();
qApp->style()->drawControl(QStyle::CE_RubberBand, &opt, painter);
}
bool RubberBand::intersects(const QRectF &rect) const
{
return m_geometry.intersects(rect);
}
QColor RubberBand::color() const
{
return m_color;
}
void RubberBand::setColor(QColor color)
{
if (m_color != color) {
m_color = color;
update();
emit colorChanged();
}
}
void RubberBand::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
Q_UNUSED(oldGeometry);
m_geometry = newGeometry;
update();
QQuickItem::geometryChanged(newGeometry, oldGeometry);
}

33
src/rubberband.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef RUBBERBAND_H
#define RUBBERBAND_H
#include <QQuickPaintedItem>
class RubberBand : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
public:
explicit RubberBand(QQuickItem *parent = nullptr);
~RubberBand() override;
void paint(QPainter *painter) override;
Q_INVOKABLE bool intersects(const QRectF &rect) const;
QColor color() const;
void setColor(QColor color);
signals:
void colorChanged();
protected:
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
private:
QRectF m_geometry;
QColor m_color;
};
#endif

283
translations/en_US.ts Normal file
View file

@ -0,0 +1,283 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>BrowserMenu</name>
<message>
<location filename="../qml/BrowserMenu.qml" line="17"/>
<source>New Folder</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/BrowserMenu.qml" line="27"/>
<source>Paste</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/BrowserMenu.qml" line="39"/>
<source>Open in Terminal</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/BrowserMenu.qml" line="33"/>
<source>Select All</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/BrowserMenu.qml" line="44"/>
<source>Properties</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/BrowserMenu.qml" line="53"/>
<source>Empty Trash</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>BrowserView</name>
<message>
<location filename="../qml/BrowserView.qml" line="44"/>
<source>No Files</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>DesktopView</name>
<message>
<location filename="../src/desktop/desktopview.cpp" line="19"/>
<source>Desktop</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FolderModel</name>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1603"/>
<source>Cut</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1606"/>
<source>Copy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1609"/>
<source>Undo</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1617"/>
<source>Paste</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1624"/>
<source>New Folder</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1627"/>
<source>New Documents</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1633"/>
<source>Rename</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1636"/>
<source>Move To Trash</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1639"/>
<source>&amp;Empty Trash</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1642"/>
<source>Restore from trash</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1645"/>
<source>Delete</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1648"/>
<source>&amp;Open</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1799"/>
<source>Select All</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1816"/>
<source>Change Wallpaper</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1825"/>
<source>Properties</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1873"/>
<source>Set as Wallpaper</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1881"/>
<source>&amp;Properties</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ItemMenu</name>
<message>
<location filename="../qml/ItemMenu.qml" line="24"/>
<source>Open</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="32"/>
<source>Copy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="40"/>
<source>Cut</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="48"/>
<source>Move to Trash</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="58"/>
<source>Rename</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="66"/>
<source>Open in Terminal</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="71"/>
<source>Set As Wallpaper</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="81"/>
<source>Properties</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PlacesModel</name>
<message>
<location filename="../src/lib/placesmodel.cpp" line="11"/>
<source>Home</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="18"/>
<source>Desktop</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="25"/>
<source>Documents</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="32"/>
<source>Downloads</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="39"/>
<source>Music</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="46"/>
<source>Pictures</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="53"/>
<source>Videos</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="58"/>
<source>Trash</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PropertiesDialog</name>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="9"/>
<source>Properties</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="71"/>
<source>Type:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="80"/>
<source>Location:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="90"/>
<source>Size:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="102"/>
<source>Created:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="114"/>
<source>Modified:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="126"/>
<source>Accessed:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="147"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="152"/>
<source>OK</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>main</name>
<message>
<location filename="../qml/main.qml" line="17"/>
<source>File Manager</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

283
translations/zh_CN.ts Normal file
View file

@ -0,0 +1,283 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>BrowserMenu</name>
<message>
<location filename="../qml/BrowserMenu.qml" line="17"/>
<source>New Folder</source>
<translation></translation>
</message>
<message>
<location filename="../qml/BrowserMenu.qml" line="27"/>
<source>Paste</source>
<translation></translation>
</message>
<message>
<location filename="../qml/BrowserMenu.qml" line="39"/>
<source>Open in Terminal</source>
<translation></translation>
</message>
<message>
<location filename="../qml/BrowserMenu.qml" line="33"/>
<source>Select All</source>
<translation></translation>
</message>
<message>
<location filename="../qml/BrowserMenu.qml" line="44"/>
<source>Properties</source>
<translation></translation>
</message>
<message>
<location filename="../qml/BrowserMenu.qml" line="53"/>
<source>Empty Trash</source>
<translation></translation>
</message>
</context>
<context>
<name>BrowserView</name>
<message>
<location filename="../qml/BrowserView.qml" line="44"/>
<source>No Files</source>
<translation></translation>
</message>
</context>
<context>
<name>DesktopView</name>
<message>
<location filename="../src/desktop/desktopview.cpp" line="19"/>
<source>Desktop</source>
<translation></translation>
</message>
</context>
<context>
<name>FolderModel</name>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1603"/>
<source>Cut</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1606"/>
<source>Copy</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1609"/>
<source>Undo</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1617"/>
<source>Paste</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1624"/>
<source>New Folder</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1627"/>
<source>New Documents</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1633"/>
<source>Rename</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1636"/>
<source>Move To Trash</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1639"/>
<source>&amp;Empty Trash</source>
<translation>&amp;</translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1642"/>
<source>Restore from trash</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1645"/>
<source>Delete</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1648"/>
<source>&amp;Open</source>
<translation>&amp;</translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1799"/>
<source>Select All</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1816"/>
<source>Change Wallpaper</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1825"/>
<source>Properties</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1873"/>
<source>Set as Wallpaper</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/foldermodel.cpp" line="1881"/>
<source>&amp;Properties</source>
<translation>&amp;</translation>
</message>
</context>
<context>
<name>ItemMenu</name>
<message>
<location filename="../qml/ItemMenu.qml" line="24"/>
<source>Open</source>
<translation></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="32"/>
<source>Copy</source>
<translation></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="40"/>
<source>Cut</source>
<translation></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="48"/>
<source>Move to Trash</source>
<translation></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="58"/>
<source>Rename</source>
<translation></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="66"/>
<source>Open in Terminal</source>
<translation></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="71"/>
<source>Set As Wallpaper</source>
<translation></translation>
</message>
<message>
<location filename="../qml/ItemMenu.qml" line="81"/>
<source>Properties</source>
<translation></translation>
</message>
</context>
<context>
<name>PlacesModel</name>
<message>
<location filename="../src/lib/placesmodel.cpp" line="11"/>
<source>Home</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="18"/>
<source>Desktop</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="25"/>
<source>Documents</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="32"/>
<source>Downloads</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="39"/>
<source>Music</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="46"/>
<source>Pictures</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="53"/>
<source>Videos</source>
<translation></translation>
</message>
<message>
<location filename="../src/lib/placesmodel.cpp" line="58"/>
<source>Trash</source>
<translation></translation>
</message>
</context>
<context>
<name>PropertiesDialog</name>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="9"/>
<source>Properties</source>
<translation></translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="71"/>
<source>Type:</source>
<translation>:</translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="80"/>
<source>Location:</source>
<translation>:</translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="90"/>
<source>Size:</source>
<translation>:</translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="102"/>
<source>Created:</source>
<translation>:</translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="114"/>
<source>Modified:</source>
<translation>:</translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="126"/>
<source>Accessed:</source>
<translation>访:</translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="147"/>
<source>Cancel</source>
<translation></translation>
</message>
<message>
<location filename="../qml/Dialogs/PropertiesDialog.qml" line="152"/>
<source>OK</source>
<translation></translation>
</message>
</context>
<context>
<name>main</name>
<message>
<location filename="../qml/main.qml" line="17"/>
<source>File Manager</source>
<translation></translation>
</message>
</context>
</TS>