init
54
.gitignore
vendored
Normal 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
|
@ -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
|
@ -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
|
@ -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.
|
11
cutefish-filemanager.desktop
Normal 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
|
@ -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 |
14
images/dark/go-previous.svg
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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 |
14
images/light/go-previous.svg
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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"
|
||||
}
|
||||
}
|
1053
qml/Desktop/DesktopFolderView.qml
Normal file
116
qml/Desktop/FolderItemDelegate.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
43
qml/Desktop/FolderTools.js
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
53
qml/Desktop/FolderViewDropArea.qml
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
174
qml/Dialogs/PropertiesDialog.qml
Normal 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
|
@ -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
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
79
qml/FolderListDelegate.qml
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
55
src/desktop/desktopsettings.cpp
Normal 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();
|
||||
}
|
||||
}
|
34
src/desktop/desktopsettings.h
Normal 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
|
59
src/desktop/desktopview.cpp
Normal 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
|
@ -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
|
146
src/dialogs/propertiesdialog.cpp
Normal 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();
|
||||
}
|
71
src/dialogs/propertiesdialog.h
Normal 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
|
@ -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
|
@ -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
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
626
src/fmlist.cpp
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
20
src/lib/fileitemactions.cpp
Normal 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
|
@ -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
372
src/lib/foldermodel.h
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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>&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>&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>&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
|
@ -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>&Empty Trash</source>
|
||||
<translation>&清空回收站</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>&Open</source>
|
||||
<translation>&打开</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>&Properties</source>
|
||||
<translation>&属性</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>
|