commit
99d4cf5e0b
@ -0,0 +1,6 @@
|
||||
Upstream Authors:
|
||||
LXQt team: http://lxqt.org
|
||||
Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
|
||||
Copyright:
|
||||
Copyright (c) 2013-2015 LXQt team
|
@ -0,0 +1,131 @@
|
||||
cmake_minimum_required(VERSION 3.0.2)
|
||||
project(libfm-qt)
|
||||
|
||||
set(LIBFM_QT_LIBRARY_NAME "fm-qt" CACHE STRING "fm-qt")
|
||||
|
||||
set(LIBFM_QT_VERSION_MAJOR 0)
|
||||
set(LIBFM_QT_VERSION_MINOR 10)
|
||||
set(LIBFM_QT_VERSION_PATCH 0)
|
||||
set(LIBFM_QT_VERSION ${LIBFM_QT_VERSION_MAJOR}.${LIBFM_QT_VERSION_MINOR}.${LIBFM_QT_VERSION_PATCH})
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
|
||||
|
||||
# We use the libtool versioning scheme for the internal so name, "current:revision:age"
|
||||
# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
|
||||
# https://www.sourceware.org/autobook/autobook/autobook_91.html
|
||||
# http://pusling.com/blog/?p=352
|
||||
# Actually, libtool uses different ways on different operating systems. So there is no
|
||||
# universal way to translate a libtool version-info to a cmake version.
|
||||
# We use "(current-age).age.revision" as the cmake version.
|
||||
# current: 2, revision: 0, age: 0 => version: 2.0.0
|
||||
set(LIBFM_QT_LIB_VERSION "2.0.0")
|
||||
set(LIBFM_QT_LIB_SOVERSION "2")
|
||||
|
||||
set(REQUIRED_QT_VERSION "5.2")
|
||||
set(REQUIRED_LIBFM_VERSION "1.2.0")
|
||||
set(REQUIRED_LIBMENUCACHE_VERSION "0.4.0")
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
endif()
|
||||
|
||||
find_package(Qt5Widgets "${REQUIRED_QT_VERSION}" REQUIRED)
|
||||
find_package(Qt5LinguistTools "${REQUIRED_QT_VERSION}" REQUIRED)
|
||||
find_package(Qt5X11Extras "${REQUIRED_QT_VERSION}" REQUIRED)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(SYSTEM_LIBS REQUIRED
|
||||
glib-2.0
|
||||
gio-2.0
|
||||
gio-unix-2.0
|
||||
)
|
||||
|
||||
pkg_check_modules(LIBFM REQUIRED libfm>="${REQUIRED_LIBFM_VERSION}")
|
||||
pkg_check_modules(LIBMENUCACHE REQUIRED libmenu-cache>="${REQUIRED_LIBMENUCACHE_VERSION}")
|
||||
|
||||
option(UPDATE_TRANSLATIONS "Update source translation translations/*.ts files" OFF)
|
||||
include(GNUInstallDirs)
|
||||
include(GenerateExportHeader)
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(LXQtTranslateTs)
|
||||
include(LXQtTranslateDesktop)
|
||||
|
||||
add_definitions(-DQT_NO_KEYWORDS)
|
||||
|
||||
# set visibility to hidden to hide symbols, unless they're exported manually in the code
|
||||
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
||||
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
|
||||
|
||||
if (CMAKE_VERSION VERSION_LESS "3.1")
|
||||
include(CheckCXXCompilerFlag)
|
||||
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
|
||||
if(COMPILER_SUPPORTS_CXX11)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
else()
|
||||
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
|
||||
# -std=c++0x is deprecated but some tools e.g. qmake or older gcc are still using it
|
||||
if(COMPILER_SUPPORTS_CXX0X)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
|
||||
else()
|
||||
message(FATAL_ERROR "Compiler ${CMAKE_CXX_COMPILER} does not support c++11/c++0x")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
endif()
|
||||
|
||||
if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set(CMAKE_CXX_FLAGS "-fno-exceptions ${CMAKE_CXX_FLAGS}")
|
||||
endif()
|
||||
|
||||
set(CMAKE_AUTOMOC TRUE)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
write_basic_package_version_file(
|
||||
"${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-config-version.cmake"
|
||||
VERSION ${LIBFM_QT_LIB_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion
|
||||
)
|
||||
|
||||
install(FILES
|
||||
"${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-config-version.cmake"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
|
||||
COMPONENT Devel
|
||||
)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
# add Doxygen support to generate API docs
|
||||
# References:
|
||||
# http://majewsky.wordpress.com/2010/08/14/tip-of-the-day-cmake-and-doxygen/
|
||||
# http://www.bluequartz.net/projects/EIM_Segmentation/SoftwareDocumentation/html/usewithcmakeproject.html
|
||||
option(BUILD_DOCUMENTATION "Use Doxygen to create the HTML based API documentation" OFF)
|
||||
if(BUILD_DOCUMENTATION)
|
||||
find_package(Doxygen REQUIRED)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile" @ONLY)
|
||||
add_custom_target(doc ALL
|
||||
${DOXYGEN_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
COMMENT "Generating API documentation with Doxygen" VERBATIM
|
||||
)
|
||||
install(DIRECTORY
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/docs"
|
||||
DESTINATION "${CMAKE_INSTALL_DOCDIR}"
|
||||
COMPONENT Devel
|
||||
)
|
||||
endif()
|
||||
|
||||
# building tarball with CPack -------------------------------------------------
|
||||
# To create a source distribution, type:
|
||||
# make package_source
|
||||
include(InstallRequiredSystemLibraries)
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
||||
set(CPACK_PACKAGE_VENDOR "")
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${LIBFM_QT_VERSION_MAJOR})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${LIBFM_QT_VERSION_MINOR})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${LIBFM_QT_VERSION_PATCH})
|
||||
set(CPACK_GENERATOR TBZ2)
|
||||
set(CPACK_SOURCE_GENERATOR TBZ2)
|
||||
set(CPACK_SOURCE_IGNORE_FILES /build/;.gitignore;.*~;.git;.kdev4;temp)
|
||||
include(CPack)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,458 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
@ -0,0 +1,21 @@
|
||||
# libfm-qt
|
||||
|
||||
Includes libfm-qt, the Qt port of the libfm - a library providing components to
|
||||
build desktop file managers.
|
||||
|
||||
Issue tracker:
|
||||
https://github.com/lxde/pcmanfm-qt/issues
|
||||
|
||||
LXQt website:
|
||||
http://lxqt.org
|
||||
|
||||
LXDE website:
|
||||
http://lxde.org
|
||||
|
||||
|
||||
# License
|
||||
|
||||
libfm-qt is licensed under the terms of the
|
||||
[LGPLv2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) or any later version.
|
||||
|
||||
See the LICENSE file for the full text of the license.
|
@ -0,0 +1,107 @@
|
||||
#=============================================================================
|
||||
# The lxqt_translate_desktop() function was copied from the the
|
||||
# LXQt LxQtTranste.cmake
|
||||
#
|
||||
# Original Author: Alexander Sokolov <sokoloff.a@gmail.com>
|
||||
#
|
||||
# funtion lxqt_translate_desktop(_RESULT
|
||||
# SOURCES <sources>
|
||||
# [TRANSLATION_DIR] translation_directory
|
||||
# )
|
||||
# Output:
|
||||
# _RESULT The generated .desktop (.desktop) files
|
||||
#
|
||||
# Input:
|
||||
#
|
||||
# SOURCES List of input desktop files (.destktop.in) to be translated
|
||||
# (merged), relative to the CMakeList.txt.
|
||||
#
|
||||
# TRANSLATION_DIR Optional path to the directory with the .ts files,
|
||||
# relative to the CMakeList.txt. Defaults to
|
||||
# "translations".
|
||||
#
|
||||
#=============================================================================
|
||||
|
||||
function(lxqt_translate_desktop _RESULT)
|
||||
# Parse arguments ***************************************
|
||||
set(oneValueArgs TRANSLATION_DIR)
|
||||
set(multiValueArgs SOURCES)
|
||||
|
||||
cmake_parse_arguments(_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
# check for unknown arguments
|
||||
set(_UNPARSED_ARGS ${_ARGS_UNPARSED_ARGUMENTS})
|
||||
if (NOT ${_UNPARSED_ARGS} STREQUAL "")
|
||||
MESSAGE(FATAL_ERROR
|
||||
"Unknown arguments '${_UNPARSED_ARGS}'.\n"
|
||||
"See lxqt_translate_desktop() documenation for more information.\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED _ARGS_SOURCES)
|
||||
set(${_RESULT} "" PARENT_SCOPE)
|
||||
return()
|
||||
else()
|
||||
set(_sources ${_ARGS_SOURCES})
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED _ARGS_TRANSLATION_DIR)
|
||||
set(_translationDir "translations")
|
||||
else()
|
||||
set(_translationDir ${_ARGS_TRANSLATION_DIR})
|
||||
endif()
|
||||
|
||||
|
||||
get_filename_component (_translationDir ${_translationDir} ABSOLUTE)
|
||||
|
||||
foreach (_inFile ${_sources})
|
||||
get_filename_component(_inFile ${_inFile} ABSOLUTE)
|
||||
get_filename_component(_fileName ${_inFile} NAME_WE)
|
||||
#Extract the real extension ............
|
||||
get_filename_component(_fileExt ${_inFile} EXT)
|
||||
string(REPLACE ".in" "" _fileExt ${_fileExt})
|
||||
#.......................................
|
||||
set(_outFile "${CMAKE_CURRENT_BINARY_DIR}/${_fileName}${_fileExt}")
|
||||
|
||||
file(GLOB _translations
|
||||
${_translationDir}/${_fileName}_*${_fileExt}
|
||||
${_translationDir}/local/${_fileName}_*${_fileExt}
|
||||
)
|
||||
|
||||
set(_pattern "'\\[.*]\\s*='")
|
||||
if (_translations)
|
||||
add_custom_command(OUTPUT ${_outFile}
|
||||
COMMAND grep -v "'#TRANSLATIONS_DIR='" ${_inFile} > ${_outFile}
|
||||
COMMAND grep -h ${_pattern} ${_translations} >> ${_outFile}
|
||||
COMMENT "Generating ${_fileName}${_fileExt}"
|
||||
)
|
||||
else()
|
||||
add_custom_command(OUTPUT ${_outFile}
|
||||
COMMAND grep -v "'#TRANSLATIONS_DIR='" ${_inFile} > ${_outFile}
|
||||
COMMENT "Generating ${_fileName}${_fileExt}"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(__result ${__result} ${_outFile})
|
||||
|
||||
|
||||
# TX file ***********************************************
|
||||
set(_txFile "${CMAKE_BINARY_DIR}/tx/${_fileName}${_fileExt}.tx.sh")
|
||||
string(REPLACE "${CMAKE_SOURCE_DIR}/" "" _tx_translationDir ${_translationDir})
|
||||
string(REPLACE "${CMAKE_SOURCE_DIR}/" "" _tx_inFile ${_inFile})
|
||||
string(REPLACE "." "" _fileType ${_fileExt})
|
||||
|
||||
file(WRITE ${_txFile}
|
||||
"[ -f ${_inFile} ] || exit 0\n"
|
||||
"echo '[lxde-qt.${_fileName}_${_fileType}]'\n"
|
||||
"echo 'type = DESKTOP'\n"
|
||||
"echo 'source_lang = en'\n"
|
||||
"echo 'source_file = ${_tx_inFile}'\n"
|
||||
"echo 'file_filter = ${_tx_translationDir}/${_fileName}_<lang>${_fileExt}'\n"
|
||||
"echo ''\n"
|
||||
)
|
||||
|
||||
endforeach()
|
||||
|
||||
set(${_RESULT} ${__result} PARENT_SCOPE)
|
||||
endfunction(lxqt_translate_desktop)
|
@ -0,0 +1,131 @@
|
||||
#=============================================================================
|
||||
# Copyright 2014 Luís Pereira <luis.artur.pereira@gmail.com>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#=============================================================================
|
||||
#
|
||||
# funtion lxqt_translate_ts(qmFiles
|
||||
# [USE_QT5 [Yes | No]]
|
||||
# [UPDATE_TRANSLATIONS [Yes | No]]
|
||||
# SOURCES <sources>
|
||||
# [TEMPLATE] translation_template
|
||||
# [TRANSLATION_DIR] translation_directory
|
||||
# [INSTALL_DIR] install_directory
|
||||
# )
|
||||
# Output:
|
||||
# qmFiles The generated compiled translations (.qm) files
|
||||
#
|
||||
# Input:
|
||||
# USE_QT5 Optional flag to choose between Qt4 and Qt5. Defaults to Qt5
|
||||
#
|
||||
# UPDATE_TRANSLATIONS Optional flag. Setting it to Yes, extracts and
|
||||
# compiles the translations. Setting it No, only
|
||||
# compiles them.
|
||||
#
|
||||
# TEMPLATE Optional translations files base name. Defaults to
|
||||
# ${PROJECT_NAME}. An .ts extensions is added.
|
||||
#
|
||||
# TRANSLATION_DIR Optional path to the directory with the .ts files,
|
||||
# relative to the CMakeList.txt. Defaults to
|
||||
# "translations".
|
||||
#
|
||||
# INSTALL_DIR Optional destination of the file compiled files (qmFiles).
|
||||
# If not present no installation is performed
|
||||
|
||||
|
||||
|
||||
# CMake v2.8.3 needed to use the CMakeParseArguments module
|
||||
cmake_minimum_required(VERSION 2.8.3 FATAL_ERROR)
|
||||
|
||||
# We use our patched version to round a annoying bug.
|
||||
include(Qt5PatchedLinguistToolsMacros)
|
||||
|
||||
function(lxqt_translate_ts qmFiles)
|
||||
set(oneValueArgs USE_QT5 UPDATE_TRANSLATIONS TEMPLATE TRANSLATION_DIR INSTALL_DIR)
|
||||
set(multiValueArgs SOURCES)
|
||||
cmake_parse_arguments(TR "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
if (NOT DEFINED TR_UPDATE_TRANSLATIONS)
|
||||
set(TR_UPDATE_TRANSLATIONS "No")
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED TR_USE_QT5)
|
||||
set(TR_USE_QT5 "Yes")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED TR_TEMPLATE)
|
||||
set(TR_TEMPLATE "${PROJECT_NAME}")
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED TR_TRANSLATION_DIR)
|
||||
set(TR_TRANSLATION_DIR "translations")
|
||||
endif()
|
||||
|
||||
file(GLOB tsFiles "${TR_TRANSLATION_DIR}/${TR_TEMPLATE}_*.ts")
|
||||
set(templateFile "${TR_TRANSLATION_DIR}/${TR_TEMPLATE}.ts")
|
||||
|
||||
if(TR_USE_QT5)
|
||||
# Qt5
|
||||
if (TR_UPDATE_TRANSLATIONS)
|
||||
qt5_patched_create_translation(QMS
|
||||
${TR_SOURCES}
|
||||
${templateFile}
|
||||
OPTIONS -locations absolute
|
||||
)
|
||||
qt5_patched_create_translation(QM
|
||||
${TR_SOURCES}
|
||||
${tsFiles}
|
||||
OPTIONS -locations absolute
|
||||
)
|
||||
else()
|
||||
qt5_patched_add_translation(QM ${tsFiles})
|
||||
endif()
|
||||
else()
|
||||
# Qt4
|
||||
if(TR_UPDATE_TRANSLATIONS)
|
||||
qt4_create_translation(QMS
|
||||
${TR_SOURCES}
|
||||
${templateFile}
|
||||
OPTIONS -locations absolute
|
||||
)
|
||||
qt4_create_translation(QM
|
||||
${TR_SOURCES}
|
||||
${tsFiles}
|
||||
OPTIONS -locations absolute
|
||||
)
|
||||
else()
|
||||
qt4_add_translation(QM ${tsFiles})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(TR_UPDATE_TRANSLATIONS)
|
||||
add_custom_target("update_${TR_TEMPLATE}_ts" ALL DEPENDS ${QMS})
|
||||
endif()
|
||||
|
||||
if(DEFINED TR_INSTALL_DIR)
|
||||
install(FILES ${QM} DESTINATION ${TR_INSTALL_DIR})
|
||||
endif()
|
||||
|
||||
set(${qmFiles} ${QM} PARENT_SCOPE)
|
||||
endfunction()
|
@ -0,0 +1,112 @@
|
||||
#=============================================================================
|
||||
# Copyright 2005-2011 Kitware, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of Kitware, Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#=============================================================================
|
||||
|
||||
include(CMakeParseArguments)
|
||||
|
||||
function(QT5_PATCHED_CREATE_TRANSLATION _qm_files)
|
||||
set(options)
|
||||
set(oneValueArgs)
|
||||
set(multiValueArgs OPTIONS)
|
||||
|
||||
cmake_parse_arguments(_LUPDATE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
set(_lupdate_files ${_LUPDATE_UNPARSED_ARGUMENTS})
|
||||
set(_lupdate_options ${_LUPDATE_OPTIONS})
|
||||
|
||||
set(_my_sources)
|
||||
set(_my_tsfiles)
|
||||
foreach(_file ${_lupdate_files})
|
||||
get_filename_component(_ext ${_file} EXT)
|
||||
get_filename_component(_abs_FILE ${_file} ABSOLUTE)
|
||||
if(_ext MATCHES "ts")
|
||||
list(APPEND _my_tsfiles ${_abs_FILE})
|
||||
else()
|
||||
list(APPEND _my_sources ${_abs_FILE})
|
||||
endif()
|
||||
endforeach()
|
||||
foreach(_ts_file ${_my_tsfiles})
|
||||
if(_my_sources)
|
||||
# make a list file to call lupdate on, so we don't make our commands too
|
||||
# long for some systems
|
||||
# get_filename_component(_ts_name ${_ts_file} NAME_WE)
|
||||
|
||||
get_filename_component(_name ${_ts_file} NAME)
|
||||
string(REGEX REPLACE "^(.+)(\\.[^.]+)$" "\\1" _ts_name ${_name})
|
||||
|
||||
set(_ts_lst_file "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_ts_name}_lst_file")
|
||||
set(_lst_file_srcs)
|
||||
foreach(_lst_file_src ${_my_sources})
|
||||
set(_lst_file_srcs "${_lst_file_src}\n${_lst_file_srcs}")
|
||||
endforeach()
|
||||
|
||||
get_directory_property(_inc_DIRS INCLUDE_DIRECTORIES)
|
||||
foreach(_pro_include ${_inc_DIRS})
|
||||
get_filename_component(_abs_include "${_pro_include}" ABSOLUTE)
|
||||
set(_lst_file_srcs "-I${_pro_include}\n${_lst_file_srcs}")
|
||||
endforeach()
|
||||
|
||||
file(WRITE ${_ts_lst_file} "${_lst_file_srcs}")
|
||||
endif()
|
||||
add_custom_command(OUTPUT ${_ts_file}
|
||||
COMMAND ${Qt5_LUPDATE_EXECUTABLE}
|
||||
ARGS ${_lupdate_options} "@${_ts_lst_file}" -ts ${_ts_file}
|
||||
DEPENDS ${_my_sources} ${_ts_lst_file} VERBATIM)
|
||||
endforeach()
|
||||
qt5_patched_add_translation(${_qm_files} ${_my_tsfiles})
|
||||
set(${_qm_files} ${${_qm_files}} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
|
||||
function(QT5_PATCHED_ADD_TRANSLATION _qm_files)
|
||||
foreach(_current_FILE ${ARGN})
|
||||
get_filename_component(_abs_FILE ${_current_FILE} ABSOLUTE)
|
||||
# get_filename_component(qm ${_abs_FILE} NAME_WE)
|
||||
|
||||
get_filename_component(_name ${_abs_FILE} NAME)
|
||||
string(REGEX REPLACE "^(.+)(\\.[^.]+)$" "\\1" qm ${_name})
|
||||
|
||||
get_source_file_property(output_location ${_abs_FILE} OUTPUT_LOCATION)
|
||||
if(output_location)
|
||||
file(MAKE_DIRECTORY "${output_location}")
|
||||
set(qm "${output_location}/${qm}.qm")
|
||||
else()
|
||||
set(qm "${CMAKE_CURRENT_BINARY_DIR}/${qm}.qm")
|
||||
endif()
|
||||
|
||||
add_custom_command(OUTPUT ${qm}
|
||||
COMMAND ${Qt5_LRELEASE_EXECUTABLE}
|
||||
ARGS ${_abs_FILE} -qm ${qm}
|
||||
DEPENDS ${_abs_FILE} VERBATIM
|
||||
)
|
||||
list(APPEND ${_qm_files} ${qm})
|
||||
endforeach()
|
||||
set(${_qm_files} ${${_qm_files}} PARENT_SCOPE)
|
||||
endfunction()
|
@ -0,0 +1,63 @@
|
||||
#=============================================================================
|
||||
# Copyright 2015 Luís Pereira <luis.artur.pereira@gmail.com>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#=============================================================================
|
||||
|
||||
@PACKAGE_INIT@
|
||||
|
||||
if (CMAKE_VERSION VERSION_LESS 3.0.2)
|
||||
message(FATAL_ERROR \"fm-qt requires at least CMake version 3.0.2\")
|
||||
endif()
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
find_dependency(Qt5Widgets "@REQUIRED_QT_VERSION@")
|
||||
find_dependency(Qt5X11Extras "@REQUIRED_QT_VERSION@")
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(PKG_GLIB glib-2.0)
|
||||
pkg_check_modules(PKG_GIO
|
||||
gio-2.0
|
||||
gio-unix-2.0
|
||||
)
|
||||
|
||||
pkg_check_modules(PKG_FM REQUIRED libfm>=@REQUIRED_LIBFM_VERSION@)
|
||||
pkg_check_modules(PKG_MENUCACHE REQUIRED libmenu-cache>=@REQUIRED_LIBMENUCACHE_VERSION@)
|
||||
|
||||
if (NOT TARGET @LIBFM_QT_LIBRARY_NAME@)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@LIBFM_QT_LIBRARY_NAME@-targets.cmake")
|
||||
set_property(TARGET "@LIBFM_QT_LIBRARY_NAME@" APPEND PROPERTY
|
||||
INTERFACE_INCLUDE_DIRECTORIES
|
||||
"${PKG_GLIB_INCLUDE_DIRS}"
|
||||
"${PKG_GIO_INCLUDE_DIRS}"
|
||||
"${PKG_FM_INCLUDE_DIRS}"
|
||||
"${PKG_MENUCACHE_INCLUDE_DIRS}"
|
||||
)
|
||||
set_property(TARGET "@LIBFM_QT_LIBRARY_NAME@" APPEND PROPERTY
|
||||
INTERFACE_COMPILE_DEFINITIONS
|
||||
"QT_NO_KEYWORDS"
|
||||
"LIBFM_DATA_DIR=\"${PKG_FM_PREFIX}/share/libfm\""
|
||||
)
|
||||
endif()
|
@ -0,0 +1,195 @@
|
||||
set(libfm_TRANSLATION_TEMPLATE ${PROJECT_NAME})
|
||||
|
||||
include_directories(
|
||||
"${LIBFM_INCLUDE_DIRS}"
|
||||
"${LIBFM_INCLUDEDIR}/libfm" # to workaround incorrect #include in fm-actions.
|
||||
"${LIBMENUCACHE_INCLUDE_DIRS}"
|
||||
"${SYSTEM_LIBS_INCLUDE_DIRS}"
|
||||
)
|
||||
|
||||
link_directories(
|
||||
"${LIBFM_LIBRARY_DIRS}"
|
||||
"${LIBMENUCACHE_LIBRARY_DIRS}"
|
||||
"${SYSTEM_LIBS_LIBRARY_DIRS}"
|
||||
)
|
||||
|
||||
set(libfm_SRCS
|
||||
libfmqt.cpp
|
||||
bookmarkaction.cpp
|
||||
sidepane.cpp
|
||||
icontheme.cpp
|
||||
filelauncher.cpp
|
||||
foldermodel.cpp
|
||||
foldermodelitem.cpp
|
||||
cachedfoldermodel.cpp
|
||||
proxyfoldermodel.cpp
|
||||
folderview.cpp
|
||||
folderitemdelegate.cpp
|
||||
createnewmenu.cpp
|
||||
filemenu.cpp
|
||||
foldermenu.cpp
|
||||
filepropsdialog.cpp
|
||||
applaunchcontext.cpp
|
||||
placesview.cpp
|
||||
placesmodel.cpp
|
||||
placesmodelitem.cpp
|
||||
dirtreeview.cpp
|
||||
dirtreemodel.cpp
|
||||
dirtreemodelitem.cpp
|
||||
dnddest.cpp
|
||||
mountoperation.cpp
|
||||
mountoperationpassworddialog.cpp
|
||||
mountoperationquestiondialog.cpp
|
||||
fileoperation.cpp
|
||||
fileoperationdialog.cpp
|
||||
renamedialog.cpp
|
||||
pathedit.cpp
|
||||
colorbutton.cpp
|
||||
fontbutton.cpp
|
||||
browsehistory.cpp
|
||||
utilities.cpp
|
||||
dndactionmenu.cpp
|
||||
editbookmarksdialog.cpp
|
||||
thumbnailloader.cpp
|
||||
path.cpp
|
||||
execfiledialog.cpp
|
||||
appchoosercombobox.cpp
|
||||
appmenuview.cpp
|
||||
appchooserdialog.cpp
|
||||
filesearchdialog.cpp
|
||||
fm-search.c # might be moved to libfm later
|
||||
)
|
||||
|
||||
set(libfm_UIS
|
||||
file-props.ui
|
||||
file-operation-dialog.ui
|
||||
rename-dialog.ui
|
||||
mount-operation-password.ui
|
||||
edit-bookmarks.ui
|
||||
exec-file.ui
|
||||
app-chooser-dialog.ui
|
||||
filesearch.ui
|
||||
)
|
||||
|
||||
qt5_wrap_ui(libfm_UIS_H ${libfm_UIS})
|
||||
|
||||
# add translation for libfm-qt
|
||||
lxqt_translate_ts(QM_FILES
|
||||
UPDATE_TRANSLATIONS ${UPDATE_TRANSLATIONS}
|
||||
SOURCES ${libfm_SRCS} ${libfm_UIS}
|
||||
TEMPLATE ${libfm_TRANSLATION_TEMPLATE}
|
||||
)
|
||||
|
||||
add_library(${LIBFM_QT_LIBRARY_NAME} SHARED
|
||||
${libfm_SRCS}
|
||||
${libfm_UIS_H}
|
||||
${QM_FILES}
|
||||
)
|
||||
|
||||
set_property(
|
||||
TARGET ${LIBFM_QT_LIBRARY_NAME} APPEND
|
||||
PROPERTY COMPILE_DEFINITIONS
|
||||
LIBFM_DATA_DIR="${CMAKE_INSTALL_FULL_DATADIR}/libfm-qt"
|
||||
)
|
||||
|
||||
# only turn on custom actions support if it is enabled in libfm.
|
||||
if(EXISTS "${LIBFM_INCLUDEDIR}/libfm/fm-actions.h")
|
||||
set_property(TARGET ${LIBFM_QT_LIBRARY_NAME} APPEND PROPERTY COMPILE_DEFINITIONS CUSTOM_ACTIONS)
|
||||
endif()
|
||||
|
||||
install(EXPORT
|
||||
"${LIBFM_QT_LIBRARY_NAME}-targets"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
|
||||
COMPONENT Devel
|
||||
)
|
||||
|
||||
target_link_libraries(${LIBFM_QT_LIBRARY_NAME}
|
||||
Qt5::Widgets
|
||||
Qt5::X11Extras
|
||||
${LIBFM_LIBRARIES}
|
||||
${LIBMENUCACHE_LIBRARIES}
|
||||
${SYSTEM_LIBS_LIBRARIES}
|
||||
)
|
||||
|
||||
# set libtool soname
|
||||
set_target_properties(${LIBFM_QT_LIBRARY_NAME} PROPERTIES
|
||||
VERSION ${LIBFM_QT_LIB_VERSION}
|
||||
SOVERSION ${LIBFM_QT_LIB_SOVERSION}
|
||||
)
|
||||
|
||||
target_include_directories(${LIBFM_QT_LIBRARY_NAME}
|
||||
INTERFACE "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
|
||||
)
|
||||
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}_export.h"
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libfm-qt"
|
||||
COMPONENT Devel
|
||||
)
|
||||
|
||||
# install include header files (FIXME: can we make this cleaner? should dir name be versioned?)
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/"
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libfm-qt"
|
||||
COMPONENT Devel
|
||||
FILES_MATCHING PATTERN "*.h"
|
||||
)
|
||||
|
||||
generate_export_header(${LIBFM_QT_LIBRARY_NAME}
|
||||
EXPORT_MACRO_NAME LIBFM_QT_API
|
||||
)
|
||||
|
||||
configure_package_config_file(
|
||||
"${PROJECT_SOURCE_DIR}/cmake/fm-qt-config.cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/install/${LIBFM_QT_LIBRARY_NAME}-config.cmake"
|
||||
INSTALL_DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
|
||||
)
|
||||
|
||||
install(FILES
|
||||
"${PROJECT_BINARY_DIR}/install/${LIBFM_QT_LIBRARY_NAME}-config.cmake"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
|
||||
COMPONENT Devel
|
||||
)
|
||||
|
||||
# FIXME: add libtool version to the lib (soname) later.
|
||||
# FIXME: only export public symbols
|
||||
|
||||
install(TARGETS ${LIBFM_QT_LIBRARY_NAME}
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
EXPORT "${LIBFM_QT_LIBRARY_NAME}-targets"
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
PUBLIC_HEADER
|
||||
COMPONENT Runtime
|
||||
)
|
||||
|
||||
export(TARGETS ${LIBFM_QT_LIBRARY_NAME}
|
||||
FILE "${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-targets.cmake"
|
||||
EXPORT_LINK_INTERFACE_LIBRARIES
|
||||
)
|
||||
|
||||
# install a pkgconfig file for libfm-qt
|
||||
set(REQUIRED_QT "Qt5Widgets >= ${REQUIRED_QT_VERSION} Qt5X11Extras >= ${REQUIRED_QT_VERSION}")
|
||||
configure_file(libfm-qt.pc.in lib${LIBFM_QT_LIBRARY_NAME}.pc @ONLY)
|
||||
# FreeBSD loves to install files to different locations
|
||||
# http://www.freebsd.org/doc/handbook/dirstructure.html
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/lib${LIBFM_QT_LIBRARY_NAME}.pc"
|
||||
DESTINATION libdata/pkgconfig
|
||||
COMPONENT Devel
|
||||
)
|
||||
else()
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/lib${LIBFM_QT_LIBRARY_NAME}.pc"
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
|
||||
COMPONENT Devel
|
||||
)
|
||||
endif()
|
||||
|
||||
install(FILES
|
||||
${QM_FILES}
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/libfm-qt/translations"
|
||||
COMPONENT Runtime
|
||||
)
|
||||
|
||||
# prevent the generated files from being deleted during make cleaner
|
||||
set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM true)
|
@ -0,0 +1,183 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AppChooserDialog</class>
|
||||
<widget class="QDialog" name="AppChooserDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>432</width>
|
||||
<height>387</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Choose an Application</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="fileTypeHeader"/>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Installed Applications</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="Fm::AppMenuView" name="appMenuView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Custom Command</string>
|
||||
</attribute>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Command line to execute:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLineEdit" name="cmdLine"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Application name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="appName"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string><b>These special codes can be used in the command line:</b>
|
||||
<ul>
|
||||
<li><b>%f</b>: Represents a single file name</li>
|
||||
<li><b>%F</b>: Represents multiple file names</li>
|
||||
<li><b>%u</b>: Represents a single URI of the file</li>
|
||||
<li><b>%U</b>: Represents multiple URIs</li>
|
||||
</ul></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="keepTermOpen">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Keep terminal window open after command execution</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="useTerminal">
|
||||
<property name="text">
|
||||
<string>Execute in terminal emulator</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="setDefault">
|
||||
<property name="text">
|
||||
<string>Set selected application as default action of this file type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Fm::AppMenuView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>appmenuview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>AppChooserDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>227</x>
|
||||
<y>359</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>AppChooserDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>295</x>
|
||||
<y>365</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>useTerminal</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>keepTermOpen</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>72</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>79</x>
|
||||
<y>282</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "appchoosercombobox.h"
|
||||
#include "icontheme.h"
|
||||
#include "appchooserdialog.h"
|
||||
#include "utilities.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
AppChooserComboBox::AppChooserComboBox(QWidget* parent):
|
||||
QComboBox(parent),
|
||||
defaultApp_(NULL),
|
||||
appInfos_(NULL),
|
||||
defaultAppIndex_(-1),
|
||||
prevIndex_(0),
|
||||
mimeType_(NULL),
|
||||
blockOnCurrentIndexChanged_(false) {
|
||||
|
||||
// the new Qt5 signal/slot syntax cannot handle overloaded methods by default
|
||||
// hence a type-casting is needed here. really ugly!
|
||||
// reference: http://qt-project.org/forums/viewthread/21513
|
||||
connect((QComboBox*)this, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &AppChooserComboBox::onCurrentIndexChanged);
|
||||
}
|
||||
|
||||
AppChooserComboBox::~AppChooserComboBox() {
|
||||
if(mimeType_)
|
||||
fm_mime_type_unref(mimeType_);
|
||||
if(defaultApp_)
|
||||
g_object_unref(defaultApp_);
|
||||
// delete GAppInfo objects stored for Combobox
|
||||
if(appInfos_) {
|
||||
g_list_foreach(appInfos_, (GFunc)g_object_unref, NULL);
|
||||
g_list_free(appInfos_);
|
||||
}
|
||||
}
|
||||
|
||||
void AppChooserComboBox::setMimeType(FmMimeType* mimeType) {
|
||||
clear();
|
||||
if(mimeType_)
|
||||
fm_mime_type_unref(mimeType_);
|
||||
|
||||
mimeType_ = fm_mime_type_ref(mimeType);
|
||||
if(mimeType_) {
|
||||
const char* typeName = fm_mime_type_get_type(mimeType_);
|
||||
defaultApp_ = g_app_info_get_default_for_type(typeName, FALSE);
|
||||
appInfos_ = g_app_info_get_all_for_type(typeName);
|
||||
int i = 0;
|
||||
for(GList* l = appInfos_; l; l = l->next, ++i) {
|
||||
GAppInfo* app = G_APP_INFO(l->data);
|
||||
GIcon* gicon = g_app_info_get_icon(app);
|
||||
QString name = QString::fromUtf8(g_app_info_get_name(app));
|
||||
// QVariant data = qVariantFromValue<void*>(app);
|
||||
// addItem(IconTheme::icon(gicon), name, data);
|
||||
addItem(IconTheme::icon(gicon), name);
|
||||
if(g_app_info_equal(app, defaultApp_))
|
||||
defaultAppIndex_ = i;
|
||||
}
|
||||
}
|
||||
// add "Other applications" item
|
||||
insertSeparator(count());
|
||||
addItem(tr("Customize"));
|
||||
if(defaultAppIndex_ != -1)
|
||||
setCurrentIndex(defaultAppIndex_);
|
||||
}
|
||||
|
||||
// returns the currently selected app.
|
||||
GAppInfo* AppChooserComboBox::selectedApp() {
|
||||
return G_APP_INFO(g_list_nth_data(appInfos_, currentIndex()));
|
||||
}
|
||||
|
||||
bool AppChooserComboBox::isChanged() {
|
||||
return (defaultAppIndex_ != currentIndex());
|
||||
}
|
||||
|
||||
void AppChooserComboBox::onCurrentIndexChanged(int index) {
|
||||
if(index == -1 || index == prevIndex_ || blockOnCurrentIndexChanged_)
|
||||
return;
|
||||
|
||||
// the last item is "Customize"
|
||||
if(index == (count() - 1)) {
|
||||
/* TODO: let the user choose an app or add custom actions here. */
|
||||
QWidget* toplevel = topLevelWidget();
|
||||
AppChooserDialog dlg(mimeType_, toplevel);
|
||||
dlg.setWindowModality(Qt::WindowModal);
|
||||
dlg.setCanSetDefault(false);
|
||||
if(dlg.exec() == QDialog::Accepted) {
|
||||
GAppInfo* app = dlg.selectedApp();
|
||||
if(app) {
|
||||
/* see if it's already in the list to prevent duplication */
|
||||
GList* found = NULL;
|
||||
for(found = appInfos_; found; found = found->next) {
|
||||
if(g_app_info_equal(app, G_APP_INFO(found->data)))
|
||||
break;
|
||||
}
|
||||
|
||||
// inserting new items or change current index will recursively trigger onCurrentIndexChanged.
|
||||
// we need to block our handler to prevent recursive calls.
|
||||
blockOnCurrentIndexChanged_ = true;
|
||||
/* if it's already in the list, select it */
|
||||
if(found) {
|
||||
setCurrentIndex(g_list_position(appInfos_, found));
|
||||
g_object_unref(app);
|
||||
}
|
||||
else { /* if it's not found, add it to the list */
|
||||
appInfos_ = g_list_prepend(appInfos_, app);
|
||||
GIcon* gicon = g_app_info_get_icon(app);
|
||||
QString name = QString::fromUtf8(g_app_info_get_name(app));
|
||||
insertItem(0, IconTheme::icon(gicon), name);
|
||||
setCurrentIndex(0);
|
||||
}
|
||||
blockOnCurrentIndexChanged_ = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// block our handler to prevent recursive calls.
|
||||
blockOnCurrentIndexChanged_ = true;
|
||||
// restore to previously selected item
|
||||
setCurrentIndex(prevIndex_);
|
||||
blockOnCurrentIndexChanged_ = false;
|
||||
}
|
||||
else {
|
||||
prevIndex_ = index;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
/* get a list of custom apps added with app-chooser.
|
||||
* the returned GList is owned by the combo box and shouldn't be freed. */
|
||||
const GList* AppChooserComboBox::customApps() {
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_APPCHOOSERCOMBOBOX_H
|
||||
#define FM_APPCHOOSERCOMBOBOX_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QComboBox>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API AppChooserComboBox : public QComboBox {
|
||||
Q_OBJECT
|
||||
public:
|
||||
~AppChooserComboBox();
|
||||
AppChooserComboBox(QWidget* parent);
|
||||
|
||||
void setMimeType(FmMimeType* mimeType);
|
||||
|
||||
FmMimeType* mimeType() {
|
||||
return mimeType_;
|
||||
}
|
||||
|
||||
GAppInfo* selectedApp();
|
||||
// const GList* customApps();
|
||||
|
||||
bool isChanged();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onCurrentIndexChanged(int index);
|
||||
|
||||
private:
|
||||
FmMimeType* mimeType_;
|
||||
GList* appInfos_; // applications used to open the file type
|
||||
GAppInfo* defaultApp_; // default application used to open the file type
|
||||
int defaultAppIndex_;
|
||||
int prevIndex_;
|
||||
bool blockOnCurrentIndexChanged_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_APPCHOOSERCOMBOBOX_H
|
@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright 2010-2014 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
* Copyright 2012-2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "appchooserdialog.h"
|
||||
#include "ui_app-chooser-dialog.h"
|
||||
#include <QPushButton>
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
AppChooserDialog::AppChooserDialog(FmMimeType* mimeType, QWidget* parent, Qt::WindowFlags f):
|
||||
QDialog(parent, f),
|
||||
mimeType_(NULL),
|
||||
selectedApp_(NULL),
|
||||
canSetDefault_(true),
|
||||
ui(new Ui::AppChooserDialog()) {
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->appMenuView, &AppMenuView::selectionChanged, this, &AppChooserDialog::onSelectionChanged);
|
||||
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &AppChooserDialog::onTabChanged);
|
||||
|
||||
if(!ui->appMenuView->isAppSelected())
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); // disable OK button
|
||||
|
||||
if(mimeType)
|
||||
setMimeType(mimeType);
|
||||
}
|
||||
|
||||
AppChooserDialog::~AppChooserDialog() {
|
||||
delete ui;
|
||||
if(mimeType_)
|
||||
fm_mime_type_unref(mimeType_);
|
||||
if(selectedApp_)
|
||||
g_object_unref(selectedApp_);
|
||||
}
|
||||
|
||||
bool AppChooserDialog::isSetDefault() {
|
||||
return ui->setDefault->isChecked();
|
||||
}
|
||||
|
||||
|
||||
static void on_temp_appinfo_destroy(gpointer data, GObject* objptr) {
|
||||
char* filename = (char*)data;
|
||||
if(g_unlink(filename) < 0)
|
||||
g_critical("failed to remove %s", filename);
|
||||
/* else
|
||||
qDebug("temp file %s removed", filename); */
|
||||
g_free(filename);
|
||||
}
|
||||
|
||||
static GAppInfo* app_info_create_from_commandline(const char* commandline,
|
||||
const char* application_name,
|
||||
const char* bin_name,
|
||||
const char* mime_type,
|
||||
gboolean terminal, gboolean keep) {
|
||||
GAppInfo* app = NULL;
|
||||
char* dirname = g_build_filename(g_get_user_data_dir(), "applications", NULL);
|
||||
const char* app_basename = strrchr(bin_name, '/');
|
||||
|
||||
if(app_basename)
|
||||
app_basename++;
|
||||
else
|
||||
app_basename = bin_name;
|
||||
if(g_mkdir_with_parents(dirname, 0700) == 0) {
|
||||
char* filename = g_strdup_printf("%s/userapp-%s-XXXXXX.desktop", dirname, app_basename);
|
||||
int fd = g_mkstemp(filename);
|
||||
if(fd != -1) {
|
||||
GString* content = g_string_sized_new(256);
|
||||
g_string_printf(content,
|
||||
"[" G_KEY_FILE_DESKTOP_GROUP "]\n"
|
||||
G_KEY_FILE_DESKTOP_KEY_TYPE "=" G_KEY_FILE_DESKTOP_TYPE_APPLICATION "\n"
|
||||
G_KEY_FILE_DESKTOP_KEY_NAME "=%s\n"
|
||||
G_KEY_FILE_DESKTOP_KEY_EXEC "=%s\n"
|
||||
G_KEY_FILE_DESKTOP_KEY_CATEGORIES "=Other;\n"
|
||||
G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY "=true\n",
|
||||
application_name,
|
||||
commandline
|
||||
);
|
||||
if(mime_type)
|
||||
g_string_append_printf(content,
|
||||
G_KEY_FILE_DESKTOP_KEY_MIME_TYPE "=%s\n",
|
||||
mime_type);
|
||||
g_string_append_printf(content,
|
||||
G_KEY_FILE_DESKTOP_KEY_TERMINAL "=%s\n",
|
||||
terminal ? "true" : "false");
|
||||
if(terminal)
|
||||
g_string_append_printf(content, "X-KeepTerminal=%s\n",
|
||||
keep ? "true" : "false");
|
||||
close(fd); /* g_file_set_contents() may fail creating duplicate */
|
||||
if(g_file_set_contents(filename, content->str, content->len, NULL)) {
|
||||
char* fbname = g_path_get_basename(filename);
|
||||
app = G_APP_INFO(g_desktop_app_info_new(fbname));
|
||||
g_free(fbname);
|
||||
/* if there is mime_type set then created application will be
|
||||
saved for the mime type (see fm_choose_app_for_mime_type()
|
||||
below) but if not then we should remove this temp. file */
|
||||
if(!mime_type || !application_name[0])
|
||||
/* save the name so this file will be removed later */
|
||||
g_object_weak_ref(G_OBJECT(app), on_temp_appinfo_destroy,
|
||||
g_strdup(filename));
|
||||
}
|
||||
else
|
||||
g_unlink(filename);
|
||||
g_string_free(content, TRUE);
|
||||
}
|
||||
g_free(filename);
|
||||
}
|
||||
g_free(dirname);
|
||||
return app;
|
||||
}
|
||||
|
||||
inline static char* get_binary(const char* cmdline, gboolean* arg_found) {
|
||||
/* see if command line contains %f, %F, %u, or %U. */
|
||||
const char* p = strstr(cmdline, " %");
|
||||
if(p) {
|
||||
if(!strchr("fFuU", *(p + 2)))
|
||||
p = NULL;
|
||||
}
|
||||
if(arg_found)
|
||||
*arg_found = (p != NULL);
|
||||
if(p)
|
||||
return g_strndup(cmdline, p - cmdline);
|
||||
else
|
||||
return g_strdup(cmdline);
|
||||
}
|
||||
|
||||
GAppInfo* AppChooserDialog::customCommandToApp() {
|
||||
GAppInfo* app = NULL;
|
||||
QByteArray cmdline = ui->cmdLine->text().toLocal8Bit();
|
||||
QByteArray app_name = ui->appName->text().toUtf8();
|
||||
if(!cmdline.isEmpty()) {
|
||||
gboolean arg_found = FALSE;
|
||||
char* bin1 = get_binary(cmdline.constData(), &arg_found);
|
||||
qDebug("bin1 = %s", bin1);
|
||||
/* see if command line contains %f, %F, %u, or %U. */
|
||||
if(!arg_found) { /* append %f if no %f, %F, %u, or %U was found. */
|
||||
cmdline += " %f";
|
||||
}
|
||||
|
||||
/* FIXME: is there any better way to do this? */
|
||||
/* We need to ensure that no duplicated items are added */
|
||||
if(mimeType_) {
|
||||
MenuCache* menu_cache;
|
||||
/* see if the command is already in the list of known apps for this mime-type */
|
||||
GList* apps = g_app_info_get_all_for_type(fm_mime_type_get_type(mimeType_));
|
||||
GList* l;
|
||||
for(l = apps; l; l = l->next) {
|
||||
GAppInfo* app2 = G_APP_INFO(l->data);
|
||||
const char* cmd = g_app_info_get_commandline(app2);
|
||||
char* bin2 = get_binary(cmd, NULL);
|
||||
if(g_strcmp0(bin1, bin2) == 0) {
|
||||
app = G_APP_INFO(g_object_ref(app2));
|
||||
qDebug("found in app list");
|
||||
g_free(bin2);
|
||||
break;
|
||||
}
|
||||
g_free(bin2);
|
||||
}
|
||||
g_list_foreach(apps, (GFunc)g_object_unref, NULL);
|
||||
g_list_free(apps);
|
||||
if(app)
|
||||
goto _out;
|
||||
|
||||
/* see if this command can be found in menu cache */
|
||||
menu_cache = menu_cache_lookup("applications.menu");
|
||||
if(menu_cache) {
|
||||
MenuCacheDir* root_dir = menu_cache_dup_root_dir(menu_cache);
|
||||
if(root_dir) {
|
||||
GSList* all_apps = menu_cache_list_all_apps(menu_cache);
|
||||
GSList* l;
|
||||
for(l = all_apps; l; l = l->next) {
|
||||
MenuCacheApp* ma = MENU_CACHE_APP(l->data);
|
||||
const char* exec = menu_cache_app_get_exec(ma);
|
||||
char* bin2;
|
||||
if(exec == NULL) {
|
||||
g_warning("application %s has no Exec statement", menu_cache_item_get_id(MENU_CACHE_ITEM(ma)));
|
||||
continue;
|
||||
}
|
||||
bin2 = get_binary(exec, NULL);
|
||||
if(g_strcmp0(bin1, bin2) == 0) {
|
||||
app = G_APP_INFO(g_desktop_app_info_new(menu_cache_item_get_id(MENU_CACHE_ITEM(ma))));
|
||||
qDebug("found in menu cache");
|
||||
menu_cache_item_unref(MENU_CACHE_ITEM(ma));
|
||||
g_free(bin2);
|
||||
break;
|
||||
}
|
||||
menu_cache_item_unref(MENU_CACHE_ITEM(ma));
|
||||
g_free(bin2);
|
||||
}
|
||||
g_slist_free(all_apps);
|
||||
menu_cache_item_unref(MENU_CACHE_ITEM(root_dir));
|
||||
}
|
||||
menu_cache_unref(menu_cache);
|
||||
}
|
||||
if(app)
|
||||
goto _out;
|
||||
}
|
||||
|
||||
/* FIXME: g_app_info_create_from_commandline force the use of %f or %u, so this is not we need */
|
||||
app = app_info_create_from_commandline(cmdline.constData(), app_name.constData(), bin1,
|
||||
mimeType_ ? fm_mime_type_get_type(mimeType_) : NULL,
|
||||
ui->useTerminal->isChecked(), ui->keepTermOpen->isChecked());
|
||||
_out:
|
||||
g_free(bin1);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
void AppChooserDialog::accept() {
|
||||
QDialog::accept();
|
||||
|
||||
if(ui->tabWidget->currentIndex() == 0) {
|
||||
selectedApp_ = ui->appMenuView->selectedApp();
|
||||
}
|
||||
else { // custom command line
|
||||
selectedApp_ = customCommandToApp();
|
||||
}
|
||||
|
||||
if(selectedApp_) {
|
||||
if(mimeType_ && fm_mime_type_get_type(mimeType_) && g_app_info_get_name(selectedApp_)[0]) {
|
||||
/* add this app to the mime-type */
|
||||
#if GLIB_CHECK_VERSION(2, 27, 6)
|
||||
g_app_info_set_as_last_used_for_type(selectedApp_, fm_mime_type_get_type(mimeType_), NULL);
|
||||
#else
|
||||
g_app_info_add_supports_type(selectedApp_, fm_mime_type_get_type(mimeType_), NULL);
|
||||
#endif
|
||||
/* if need to set default */
|
||||
if(ui->setDefault->isChecked())
|
||||
g_app_info_set_as_default_for_type(selectedApp_, fm_mime_type_get_type(mimeType_), NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppChooserDialog::onSelectionChanged() {
|
||||
bool isAppSelected = ui->appMenuView->isAppSelected();
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isAppSelected);
|
||||
}
|
||||
|
||||
void AppChooserDialog::setMimeType(FmMimeType* mimeType) {
|
||||
if(mimeType_)
|
||||
fm_mime_type_unref(mimeType_);
|
||||
|
||||
mimeType_ = mimeType ? fm_mime_type_ref(mimeType) : NULL;
|
||||
if(mimeType_) {
|
||||
QString text = tr("Select an application to open \"%1\" files")
|
||||
.arg(QString::fromUtf8(fm_mime_type_get_desc(mimeType_)));
|
||||
ui->fileTypeHeader->setText(text);
|
||||
}
|
||||
else {
|
||||
ui->fileTypeHeader->hide();
|
||||
ui->setDefault->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void AppChooserDialog::setCanSetDefault(bool value) {
|
||||
canSetDefault_ = value;
|
||||
ui->setDefault->setVisible(value);
|
||||
}
|
||||
|
||||
void AppChooserDialog::onTabChanged(int index) {
|
||||
if(index == 0) { // app menu view
|
||||
onSelectionChanged();
|
||||
}
|
||||
else if(index == 1) { // custom command
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2010-2014 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
* Copyright 2012-2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_APPCHOOSERDIALOG_H
|
||||
#define FM_APPCHOOSERDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "libfmqtglobals.h"
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Ui {
|
||||
class AppChooserDialog;
|
||||
}
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API AppChooserDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AppChooserDialog(FmMimeType* mimeType, QWidget* parent = NULL, Qt::WindowFlags f = 0);
|
||||
~AppChooserDialog();
|
||||
|
||||
virtual void accept();
|
||||
|
||||
void setMimeType(FmMimeType* mimeType);
|
||||
FmMimeType* mimeType() {
|
||||
return mimeType_;
|
||||
}
|
||||
|
||||
void setCanSetDefault(bool value);
|
||||
bool canSetDefault() {
|
||||
return canSetDefault_;
|
||||
}
|
||||
|
||||
GAppInfo* selectedApp() {
|
||||
return G_APP_INFO(g_object_ref(selectedApp_));
|
||||
}
|
||||
|
||||
bool isSetDefault();
|
||||
|
||||
private:
|
||||
GAppInfo* customCommandToApp();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onSelectionChanged();
|
||||
void onTabChanged(int index);
|
||||
|
||||
private:
|
||||
Ui::AppChooserDialog* ui;
|
||||
FmMimeType* mimeType_;
|
||||
bool canSetDefault_;
|
||||
GAppInfo* selectedApp_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_APPCHOOSERDIALOG_H
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "applaunchcontext.h"
|
||||
#include <QX11Info>
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
typedef struct _FmAppLaunchContext {
|
||||
GAppLaunchContext parent;
|
||||
}FmAppLaunchContext;
|
||||
|
||||
G_DEFINE_TYPE(FmAppLaunchContext, fm_app_launch_context, G_TYPE_APP_LAUNCH_CONTEXT)
|
||||
|
||||
static char* fm_app_launch_context_get_display(GAppLaunchContext *context, GAppInfo *info, GList *files) {
|
||||
Display* dpy = QX11Info::display();
|
||||
if(dpy) {
|
||||
char* xstr = DisplayString(dpy);
|
||||
return g_strdup(xstr);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char* fm_app_launch_context_get_startup_notify_id(GAppLaunchContext *context, GAppInfo *info, GList *files) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void fm_app_launch_context_class_init(FmAppLaunchContextClass* klass) {
|
||||
GAppLaunchContextClass* app_launch_class = G_APP_LAUNCH_CONTEXT_CLASS(klass);
|
||||
app_launch_class->get_display = fm_app_launch_context_get_display;
|
||||
app_launch_class->get_startup_notify_id = fm_app_launch_context_get_startup_notify_id;
|
||||
}
|
||||
|
||||
static void fm_app_launch_context_init(FmAppLaunchContext* context) {
|
||||
}
|
||||
|
||||
FmAppLaunchContext* fm_app_launch_context_new_for_widget(QWidget* widget) {
|
||||
FmAppLaunchContext* context = (FmAppLaunchContext*)g_object_new(FM_TYPE_APP_LAUNCH_CONTEXT, NULL);
|
||||
return context;
|
||||
}
|
||||
|
||||
FmAppLaunchContext* fm_app_launch_context_new() {
|
||||
FmAppLaunchContext* context = (FmAppLaunchContext*)g_object_new(FM_TYPE_APP_LAUNCH_CONTEXT, NULL);
|
||||
return context;
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_APP_LAUNCHCONTEXT_H
|
||||
#define FM_APP_LAUNCHCONTEXT_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <gio/gio.h>
|
||||
#include <QWidget>
|
||||
|
||||
#define FM_TYPE_APP_LAUNCH_CONTEXT (fm_app_launch_context_get_type())
|
||||
#define FM_APP_LAUNCH_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\
|
||||
FM_TYPE_APP_LAUNCH_CONTEXT, FmAppLaunchContext))
|
||||
#define FM_APP_LAUNCH_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),\
|
||||
FM_TYPE_APP_LAUNCH_CONTEXT, FmAppLaunchContextClass))
|
||||
#define FM_IS_APP_LAUNCH_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),\
|
||||
FM_TYPE_APP_LAUNCH_CONTEXT))
|
||||
#define FM_IS_APP_LAUNCH_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),\
|
||||
FM_TYPE_APP_LAUNCH_CONTEXT))
|
||||
#define FM_APP_LAUNCH_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),\
|
||||
FM_TYPE_APP_LAUNCH_CONTEXT, FmAppLaunchContextClass))
|
||||
|
||||
typedef struct _FmAppLaunchContext FmAppLaunchContext;
|
||||
|
||||
typedef struct _FmAppLaunchContextClass {
|
||||
GAppLaunchContextClass parent;
|
||||
}FmAppLaunchContextClass;
|
||||
|
||||
FmAppLaunchContext* fm_app_launch_context_new();
|
||||
FmAppLaunchContext* fm_app_launch_context_new_for_widget(QWidget* widget);
|
||||
GType fm_app_launch_context_get_type();
|
||||
|
||||
#endif // FM_APPLAUNCHCONTEXT_H
|
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "appmenuview.h"
|
||||
#include <QStandardItemModel>
|
||||
#include "icontheme.h"
|
||||
#include "appmenuview_p.h"
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
AppMenuView::AppMenuView(QWidget* parent):
|
||||
model_(new QStandardItemModel()),
|
||||
menu_cache(NULL),
|
||||
menu_cache_reload_notify(NULL),
|
||||
QTreeView(parent) {
|
||||
|
||||
setHeaderHidden(true);
|
||||
setSelectionMode(SingleSelection);
|
||||
|
||||
// initialize model
|
||||
// TODO: share one model among all app menu view widgets
|
||||
// ensure that we're using lxmenu-data (FIXME: should we do this?)
|
||||
QByteArray oldenv = qgetenv("XDG_MENU_PREFIX");
|
||||
qputenv("XDG_MENU_PREFIX", "lxde-");
|
||||
menu_cache = menu_cache_lookup("applications.menu");
|
||||
// if(!oldenv.isEmpty())
|
||||
qputenv("XDG_MENU_PREFIX", oldenv); // restore the original value if needed
|
||||
|
||||
if(menu_cache) {
|
||||
MenuCacheDir* dir = menu_cache_dup_root_dir(menu_cache);
|
||||
menu_cache_reload_notify = menu_cache_add_reload_notify(menu_cache, _onMenuCacheReload, this);
|
||||
if(dir) { /* content of menu is already loaded */
|
||||
addMenuItems(NULL, dir);
|
||||
menu_cache_item_unref(MENU_CACHE_ITEM(dir));
|
||||
}
|
||||
}
|
||||
setModel(model_);
|
||||
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &AppMenuView::selectionChanged);
|
||||
selectionModel()->select(model_->index(0, 0), QItemSelectionModel::SelectCurrent);
|
||||
}
|
||||
|
||||
AppMenuView::~AppMenuView() {
|
||||
delete model_;
|
||||
if(menu_cache) {
|
||||
if(menu_cache_reload_notify)
|
||||
menu_cache_remove_reload_notify(menu_cache, menu_cache_reload_notify);
|
||||
menu_cache_unref(menu_cache);
|
||||
}
|
||||
}
|
||||
|
||||
void AppMenuView::addMenuItems(QStandardItem* parentItem, MenuCacheDir* dir) {
|
||||
GSList* l;
|
||||
GSList* list;
|
||||
/* Iterate over all menu items in this directory. */
|
||||
for(l = list = menu_cache_dir_list_children(dir); l != NULL; l = l->next) {
|
||||
/* Get the menu item. */
|
||||
MenuCacheItem* menuItem = MENU_CACHE_ITEM(l->data);
|
||||
switch(menu_cache_item_get_type(menuItem)) {
|
||||
case MENU_CACHE_TYPE_NONE:
|
||||
case MENU_CACHE_TYPE_SEP:
|
||||
break;
|
||||
case MENU_CACHE_TYPE_APP:
|
||||
case MENU_CACHE_TYPE_DIR: {
|
||||
AppMenuViewItem* newItem = new AppMenuViewItem(menuItem);
|
||||
if(parentItem)
|
||||
parentItem->insertRow(parentItem->rowCount(), newItem);
|
||||
else
|
||||
model_->insertRow(model_->rowCount(), newItem);
|
||||
|
||||
if(menu_cache_item_get_type(menuItem) == MENU_CACHE_TYPE_DIR)
|
||||
addMenuItems(newItem, MENU_CACHE_DIR(menuItem));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_slist_free_full(list, (GDestroyNotify)menu_cache_item_unref);
|
||||
}
|
||||
|
||||
void AppMenuView::onMenuCacheReload(MenuCache* mc) {
|
||||
MenuCacheDir* dir = menu_cache_dup_root_dir(mc);
|
||||
model_->clear();
|
||||
/* FIXME: preserve original selection */
|
||||
if(dir) {
|
||||
addMenuItems(NULL, dir);
|
||||
menu_cache_item_unref(MENU_CACHE_ITEM(dir));
|
||||
selectionModel()->select(model_->index(0, 0), QItemSelectionModel::SelectCurrent);
|
||||
}
|
||||
}
|
||||
|
||||
bool AppMenuView::isAppSelected() {
|
||||
AppMenuViewItem* item = selectedItem();
|
||||
return (item && item->isApp());
|
||||
}
|
||||
|
||||
AppMenuViewItem* AppMenuView::selectedItem() {
|
||||
QModelIndexList selected = selectedIndexes();
|
||||
if(!selected.isEmpty()) {
|
||||
AppMenuViewItem* item = static_cast<AppMenuViewItem*>(model_->itemFromIndex(selected.first()
|
||||
));
|
||||
return item;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GAppInfo* AppMenuView::selectedApp() {
|
||||
const char* id = selectedAppDesktopId();
|
||||
return id ? G_APP_INFO(g_desktop_app_info_new(id)) : NULL;
|
||||
}
|
||||
|
||||
QByteArray AppMenuView::selectedAppDesktopFilePath() {
|
||||
AppMenuViewItem* item = selectedItem();
|
||||
if(item && item->isApp()) {
|
||||
char* path = menu_cache_item_get_file_path(item->item());
|
||||
QByteArray ret(path);
|
||||
g_free(path);
|
||||
return ret;
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
const char* AppMenuView::selectedAppDesktopId() {
|
||||
AppMenuViewItem* item = selectedItem();
|
||||
if(item && item->isApp()) {
|
||||
return menu_cache_item_get_id(item->item());
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FmPath* AppMenuView::selectedAppDesktopPath() {
|
||||
AppMenuViewItem* item = selectedItem();
|
||||
if(item && item->isApp()) {
|
||||
char* mpath = menu_cache_dir_make_path(MENU_CACHE_DIR(item));
|
||||
FmPath* path = fm_path_new_relative(fm_path_get_apps_menu(),
|
||||
mpath + 13 /* skip "/Applications" */);
|
||||
g_free(mpath);
|
||||
return path;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_APPMENUVIEW_H
|
||||
#define FM_APPMENUVIEW_H
|
||||
|
||||
#include <QTreeView>
|
||||
#include "libfmqtglobals.h"
|
||||
#include <libfm/fm.h>
|
||||
#include <menu-cache/menu-cache.h>
|
||||
|
||||
class QStandardItemModel;
|
||||
class QStandardItem;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class AppMenuViewItem;
|
||||
|
||||
class LIBFM_QT_API AppMenuView : public QTreeView {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AppMenuView(QWidget* parent = NULL);
|
||||
~AppMenuView();
|
||||
|
||||
GAppInfo* selectedApp();
|
||||
|
||||
const char* selectedAppDesktopId();
|
||||
|
||||
QByteArray selectedAppDesktopFilePath();
|
||||
|
||||
FmPath * selectedAppDesktopPath();
|
||||
|
||||
bool isAppSelected();
|
||||
|
||||
Q_SIGNALS:
|
||||
void selectionChanged();
|
||||
|
||||
private:
|
||||
void addMenuItems(QStandardItem* parentItem, MenuCacheDir* dir);
|
||||
void onMenuCacheReload(MenuCache* mc);
|
||||
static void _onMenuCacheReload(MenuCache* mc, gpointer user_data) {
|
||||
static_cast<AppMenuView*>(user_data)->onMenuCacheReload(mc);
|
||||
}
|
||||
|
||||
AppMenuViewItem* selectedItem();
|
||||
|
||||
private:
|
||||
// gboolean fm_app_menu_view_is_item_app(, GtkTreeIter* it);
|
||||
QStandardItemModel* model_;
|
||||
MenuCache* menu_cache;
|
||||
MenuCacheNotifyId menu_cache_reload_notify;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_APPMENUVIEW_H
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_APPMENUVIEW_P_H
|
||||
#define FM_APPMENUVIEW_P_H
|
||||
|
||||
#include <QStandardItem>
|
||||
#include <menu-cache/menu-cache.h>
|
||||
#include "icontheme.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class AppMenuViewItem : public QStandardItem {
|
||||
public:
|
||||
explicit AppMenuViewItem(MenuCacheItem* item):
|
||||
item_(menu_cache_item_ref(item)) {
|
||||
FmIcon* fmicon;
|
||||
if(menu_cache_item_get_icon(item))
|
||||
fmicon = fm_icon_from_name(menu_cache_item_get_icon(item));
|
||||
else
|
||||
fmicon = nullptr;
|
||||
setText(QString::fromUtf8(menu_cache_item_get_name(item)));
|
||||
setEditable(false);
|
||||
setDragEnabled(false);
|
||||
if(fmicon) {
|
||||
setIcon(IconTheme::icon(fmicon));
|
||||
fm_icon_unref(fmicon);
|
||||
}
|
||||
}
|
||||
|
||||
~AppMenuViewItem() {
|
||||
menu_cache_item_unref(item_);
|
||||
}
|
||||
|
||||
MenuCacheItem* item() {
|
||||
return item_;
|
||||
}
|
||||
|
||||
MenuCacheType type() {
|
||||
return menu_cache_item_get_type(item_);
|
||||
}
|
||||
|
||||
bool isApp() {
|
||||
return type() == MENU_CACHE_TYPE_APP;
|
||||
}
|
||||
|
||||
bool isDir() {
|
||||
return type() == MENU_CACHE_TYPE_DIR;
|
||||
}
|
||||
|
||||
private:
|
||||
MenuCacheItem* item_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_APPMENUVIEW_P_H
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "bookmarkaction.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
BookmarkAction::BookmarkAction(FmBookmarkItem* item, QObject* parent):
|
||||
QAction(parent),
|
||||
item_(fm_bookmark_item_ref(item)) {
|
||||
|
||||
setText(QString::fromUtf8(item->name));
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef BOOKMARKACTION_H
|
||||
#define BOOKMARKACTION_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QAction>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
// action used to create bookmark menu items
|
||||
class LIBFM_QT_API BookmarkAction : public QAction {
|
||||
public:
|
||||
explicit BookmarkAction(FmBookmarkItem* item, QObject* parent = 0);
|
||||
|
||||
virtual ~BookmarkAction() {
|
||||
if(item_)
|
||||
fm_bookmark_item_unref(item_);
|
||||
}
|
||||
|
||||
FmBookmarkItem* bookmark() {
|
||||
return item_;
|
||||
}
|
||||
|
||||
FmPath* path() {
|
||||
return item_->path;
|
||||
}
|
||||
|
||||
private:
|
||||
FmBookmarkItem* item_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // BOOKMARKACTION_H
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "browsehistory.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
BrowseHistory::BrowseHistory():
|
||||
currentIndex_(0),
|
||||
maxCount_(10) {
|
||||
}
|
||||
|
||||
BrowseHistory::~BrowseHistory() {
|
||||
}
|
||||
|
||||
void BrowseHistory::add(FmPath* path, int scrollPos) {
|
||||
int lastIndex = size() - 1;
|
||||
if(currentIndex_ < lastIndex) {
|
||||
// if we're not at the last item, remove items after the current one.
|
||||
erase(begin() + currentIndex_ + 1, end());
|
||||
}
|
||||
|
||||
if(size() + 1 > maxCount_) {
|
||||
// if there are too many items, remove the oldest one.
|
||||
// FIXME: what if currentIndex_ == 0? remove the last item instead?
|
||||
if(currentIndex_ == 0)
|
||||
remove(lastIndex);
|
||||
else {
|
||||
remove(0);
|
||||
--currentIndex_;
|
||||
}
|
||||
}
|
||||
// add a path and current scroll position to browse history
|
||||
append(BrowseHistoryItem(path, scrollPos));
|
||||
currentIndex_ = size() - 1;
|
||||
}
|
||||
|
||||
void BrowseHistory::setCurrentIndex(int index) {
|
||||
if(index >= 0 && index < size()) {
|
||||
currentIndex_ = index;
|
||||
// FIXME: should we emit a signal for the change?
|
||||
}
|
||||
}
|
||||
|
||||
bool BrowseHistory::canBackward() const {
|
||||
return (currentIndex_ > 0);
|
||||
}
|
||||
|
||||
int BrowseHistory::backward() {
|
||||
if(canBackward())
|
||||
--currentIndex_;
|
||||
return currentIndex_;
|
||||
}
|
||||
|
||||
bool BrowseHistory::canForward() const {
|
||||
return (currentIndex_ + 1 < size());
|
||||
}
|
||||
|
||||
int BrowseHistory::forward() {
|
||||
if(canForward())
|
||||
++currentIndex_;
|
||||
return currentIndex_;
|
||||
}
|
||||
|
||||
void BrowseHistory::setMaxCount(int maxCount) {
|
||||
maxCount_ = maxCount;
|
||||
if(size() > maxCount) {
|
||||
// TODO: remove some items
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_BROWSEHISTORY_H
|
||||
#define FM_BROWSEHISTORY_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QVector>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
// class used to story browsing history of folder views
|
||||
// We use this class to replace FmNavHistory provided by libfm since
|
||||
// the original Libfm API is hard to use and confusing.
|
||||
|
||||
class LIBFM_QT_API BrowseHistoryItem {
|
||||
public:
|
||||
|
||||
BrowseHistoryItem():
|
||||
path_(NULL),
|
||||
scrollPos_(0) {
|
||||
}
|
||||
|
||||
BrowseHistoryItem(FmPath* path, int scrollPos = 0):
|
||||
path_(fm_path_ref(path)),
|
||||
scrollPos_(scrollPos) {
|
||||
}
|
||||
|
||||
BrowseHistoryItem(const BrowseHistoryItem& other):
|
||||
path_(other.path_ ? fm_path_ref(other.path_) : NULL),
|
||||
scrollPos_(other.scrollPos_) {
|
||||
}
|
||||
|
||||
~BrowseHistoryItem() {
|
||||
if(path_)
|
||||
fm_path_unref(path_);
|
||||
}
|
||||
|
||||
BrowseHistoryItem& operator=(const BrowseHistoryItem& other) {
|
||||
if(path_)
|
||||
fm_path_unref(path_);
|
||||
path_ = other.path_ ? fm_path_ref(other.path_) : NULL;
|
||||
scrollPos_ = other.scrollPos_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FmPath* path() const {
|
||||
return path_;
|
||||
}
|
||||
|
||||
int scrollPos() const {
|
||||
return scrollPos_;
|
||||
}
|
||||
|
||||
void setScrollPos(int pos) {
|
||||
scrollPos_ = pos;
|
||||
}
|
||||
|
||||
private:
|
||||
FmPath* path_;
|
||||
int scrollPos_;
|
||||
// TODO: we may need to store current selection as well. reserve room for furutre expansion.
|
||||
// void* reserved1;
|
||||
// void* reserved2;
|
||||
};
|
||||
|
||||
class LIBFM_QT_API BrowseHistory : public QVector<BrowseHistoryItem> {
|
||||
|
||||
public:
|
||||
BrowseHistory();
|
||||
virtual ~BrowseHistory();
|
||||
|
||||
int currentIndex() const {
|
||||
return currentIndex_;
|
||||
}
|
||||
void setCurrentIndex(int index);
|
||||
|
||||
FmPath* currentPath() const {
|
||||
return at(currentIndex_).path();
|
||||
}
|
||||
|
||||
int currentScrollPos() const {
|
||||
return at(currentIndex_).scrollPos();
|
||||
}
|
||||
|
||||
BrowseHistoryItem& currentItem() {
|
||||
return operator[](currentIndex_);
|
||||
}
|
||||
|
||||
void add(FmPath* path, int scrollPos = 0);
|
||||
|
||||
bool canForward() const;
|
||||
|
||||
bool canBackward() const;
|
||||
|
||||
int backward();
|
||||
|
||||
int forward();
|
||||
|
||||
int maxCount() const {
|
||||
return maxCount_;
|
||||
}
|
||||
|
||||
void setMaxCount(int maxCount);
|
||||
|
||||
private:
|
||||
int currentIndex_;
|
||||
int maxCount_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_BROWSEHISTORY_H
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "cachedfoldermodel.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
static GQuark data_id = 0;
|
||||
|
||||
|
||||
CachedFolderModel::CachedFolderModel(FmFolder* folder):
|
||||
FolderModel(),
|
||||
refCount(1) {
|
||||
|
||||
FolderModel::setFolder(folder);
|
||||
}
|
||||
|
||||
CachedFolderModel::~CachedFolderModel() {
|
||||
}
|
||||
|
||||
CachedFolderModel* CachedFolderModel::modelFromFolder(FmFolder* folder) {
|
||||
CachedFolderModel* model = NULL;
|
||||
if(!data_id)
|
||||
data_id = g_quark_from_static_string("CachedFolderModel");
|
||||
gpointer qdata = g_object_get_qdata(G_OBJECT(folder), data_id);
|
||||
model = reinterpret_cast<CachedFolderModel*>(qdata);
|
||||
if(model) {
|
||||
// qDebug("cache found!!");
|
||||
model->ref();
|
||||
}
|
||||
else {
|
||||
model = new CachedFolderModel(folder);
|
||||
g_object_set_qdata(G_OBJECT(folder), data_id, model);
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
CachedFolderModel* CachedFolderModel::modelFromPath(FmPath* path) {
|
||||
FmFolder* folder = fm_folder_from_path(path);
|
||||
if(folder) {
|
||||
CachedFolderModel* model = modelFromFolder(folder);
|
||||
g_object_unref(folder);
|
||||
return model;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void CachedFolderModel::unref() {
|
||||
// qDebug("unref cache");
|
||||
--refCount;
|
||||
if(refCount <= 0) {
|
||||
g_object_set_qdata(G_OBJECT(folder()), data_id, NULL);
|
||||
deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_CACHEDFOLDERMODEL_H
|
||||
#define FM_CACHEDFOLDERMODEL_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include "foldermodel.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API CachedFolderModel : public FolderModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
CachedFolderModel(FmFolder* folder);
|
||||
void ref() {
|
||||
++refCount;
|
||||
}
|
||||
void unref();
|
||||
|
||||
static CachedFolderModel* modelFromFolder(FmFolder* folder);
|
||||
static CachedFolderModel* modelFromPath(FmPath* path);
|
||||
|
||||
private:
|
||||
virtual ~CachedFolderModel();
|
||||
void setFolder(FmFolder* folder);
|
||||
private:
|
||||
int refCount;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_CACHEDFOLDERMODEL_H
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "colorbutton.h"
|
||||
#include <QColorDialog>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
ColorButton::ColorButton(QWidget* parent): QPushButton(parent) {
|
||||
connect(this, &QPushButton::clicked, this, &ColorButton::onClicked);
|
||||
}
|
||||
|
||||
ColorButton::~ColorButton() {
|
||||
|
||||
}
|
||||
|
||||
void ColorButton::onClicked() {
|
||||
QColorDialog dlg(color_);
|
||||
if(dlg.exec() == QDialog::Accepted) {
|
||||
setColor(dlg.selectedColor());
|
||||
}
|
||||
}
|
||||
|
||||
void ColorButton::setColor(const QColor& color) {
|
||||
if(color != color_) {
|
||||
color_ = color;
|
||||
// use qss instead of QPalette to set the background color
|
||||
// otherwise, this won't work when using the gtk style.
|
||||
QString style = QString("QPushButton{background-color:%1;}").arg(color.name());
|
||||
setStyleSheet(style);
|
||||
Q_EMIT changed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_COLORBUTTON_H
|
||||
#define FM_COLORBUTTON_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QPushButton>
|
||||
#include <QColor>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API ColorButton : public QPushButton {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ColorButton(QWidget* parent = 0);
|
||||
virtual ~ColorButton();
|
||||
|
||||
void setColor(const QColor&);
|
||||
|
||||
QColor color() const {
|
||||
return color_;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void changed();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onClicked();
|
||||
|
||||
private:
|
||||
QColor color_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_COLORBUTTON_H
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "createnewmenu.h"
|
||||
#include "folderview.h"
|
||||
#include "icontheme.h"
|
||||
#include "utilities.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
CreateNewMenu::CreateNewMenu(QWidget* dialogParent, FmPath* dirPath, QWidget* parent):
|
||||
QMenu(parent), dialogParent_(dialogParent), dirPath_(dirPath) {
|
||||
QAction* action = new QAction(QIcon::fromTheme("folder-new"), tr("Folder"), this);
|
||||
connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNewFolder);
|
||||
addAction(action);
|
||||
|
||||
action = new QAction(QIcon::fromTheme("document-new"), tr("Blank File"), this);
|
||||
connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNewFile);
|
||||
addAction(action);
|
||||
|
||||
// add more items to "Create New" menu from templates
|
||||
GList* templates = fm_template_list_all(fm_config->only_user_templates);
|
||||
if(templates) {
|
||||
addSeparator();
|
||||
for(GList* l = templates; l; l = l->next) {
|
||||
FmTemplate* templ = (FmTemplate*)l->data;
|
||||
/* we support directories differently */
|
||||
if(fm_template_is_directory(templ))
|
||||
continue;
|
||||
FmMimeType* mime_type = fm_template_get_mime_type(templ);
|
||||
const char* label = fm_template_get_label(templ);
|
||||
QString text = QString("%1 (%2)").arg(QString::fromUtf8(label)).arg(QString::fromUtf8(fm_mime_type_get_desc(mime_type)));
|
||||
FmIcon* icon = fm_template_get_icon(templ);
|
||||
if(!icon)
|
||||
icon = fm_mime_type_get_icon(mime_type);
|
||||
QAction* action = addAction(IconTheme::icon(icon), text);
|
||||
action->setObjectName(QString::fromUtf8(fm_template_get_name(templ, NULL)));
|
||||
connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNew);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CreateNewMenu::~CreateNewMenu() {
|
||||
}
|
||||
|
||||
void CreateNewMenu::onCreateNewFile() {
|
||||
if(dirPath_)
|
||||
createFileOrFolder(CreateNewTextFile, dirPath_);
|
||||
}
|
||||
|
||||
void CreateNewMenu::onCreateNewFolder() {
|
||||
if(dirPath_)
|
||||
createFileOrFolder(CreateNewFolder, dirPath_);
|
||||
}
|
||||
|
||||
void CreateNewMenu::onCreateNew() {
|
||||
QAction* action = static_cast<QAction*>(sender());
|
||||
QByteArray name = action->objectName().toUtf8();
|
||||
GList* templates = fm_template_list_all(fm_config->only_user_templates);
|
||||
FmTemplate* templ = NULL;
|
||||
for(GList* l = templates; l; l = l->next) {
|
||||
FmTemplate* t = (FmTemplate*)l->data;
|
||||
if(name == fm_template_get_name(t, NULL)) {
|
||||
templ = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(templ) { // template found
|
||||
if(dirPath_)
|
||||
createFileOrFolder(CreateWithTemplate, dirPath_, templ, dialogParent_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_CREATENEWMENU_H
|
||||
#define FM_CREATENEWMENU_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QMenu>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class FolderView;
|
||||
|
||||
class LIBFM_QT_API CreateNewMenu : public QMenu {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CreateNewMenu(QWidget* dialogParent, FmPath* dirPath,
|
||||
QWidget* parent = 0);
|
||||
virtual ~CreateNewMenu();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void onCreateNewFolder();
|
||||
void onCreateNewFile();
|
||||
void onCreateNew();
|
||||
|
||||
private:
|
||||
QWidget* dialogParent_;
|
||||
FmPath* dirPath_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_CREATENEWMENU_H
|
@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "dirtreemodel.h"
|
||||
#include "dirtreemodelitem.h"
|
||||
#include <QDebug>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
DirTreeModel::DirTreeModel(QObject* parent):
|
||||
showHidden_(false) {
|
||||
}
|
||||
|
||||
DirTreeModel::~DirTreeModel() {
|
||||
}
|
||||
|
||||
// QAbstractItemModel implementation
|
||||
|
||||
Qt::ItemFlags DirTreeModel::flags(const QModelIndex& index) const {
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
if(item && item->isPlaceHolder())
|
||||
return Qt::ItemIsEnabled;
|
||||
return QAbstractItemModel::flags(index);
|
||||
}
|
||||
|
||||
QVariant DirTreeModel::data(const QModelIndex& index, int role) const {
|
||||
if(!index.isValid() || index.column() > 1) {
|
||||
return QVariant();
|
||||
}
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
if(item) {
|
||||
FmFileInfo* info = item->fileInfo_;
|
||||
switch(role) {
|
||||
case Qt::ToolTipRole:
|
||||
return QVariant(item->displayName_);
|
||||
case Qt::DisplayRole:
|
||||
return QVariant(item->displayName_);
|
||||
case Qt::DecorationRole:
|
||||
return QVariant(item->icon_);
|
||||
case FileInfoRole:
|
||||
return qVariantFromValue((void*)info);
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
int DirTreeModel::columnCount(const QModelIndex& parent) const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DirTreeModel::rowCount(const QModelIndex& parent) const {
|
||||
if(!parent.isValid())
|
||||
return rootItems_.count();
|
||||
DirTreeModelItem* item = itemFromIndex(parent);
|
||||
if(item)
|
||||
return item->children_.count();
|
||||
return 0;
|
||||
}
|
||||
|
||||
QModelIndex DirTreeModel::parent(const QModelIndex& child) const {
|
||||
DirTreeModelItem* item = itemFromIndex(child);
|
||||
if(item && item->parent_) {
|
||||
item = item->parent_; // go to parent item
|
||||
if(item) {
|
||||
const QList<DirTreeModelItem*>& items = item->parent_ ? item->parent_->children_ : rootItems_;
|
||||
int row = items.indexOf(item); // this is Q(n) and may be slow :-(
|
||||
if(row >= 0)
|
||||
return createIndex(row, 0, (void*)item);
|
||||
}
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex DirTreeModel::index(int row, int column, const QModelIndex& parent) const {
|
||||
if(row >= 0 && column >= 0 && column == 0) {
|
||||
if(!parent.isValid()) { // root items
|
||||
if(row < rootItems_.count()) {
|
||||
const DirTreeModelItem* item = rootItems_.at(row);
|
||||
return createIndex(row, column, (void*)item);
|
||||
}
|
||||
}
|
||||
else { // child items
|
||||
DirTreeModelItem* parentItem = itemFromIndex(parent);
|
||||
if(row < parentItem->children_.count()) {
|
||||
const DirTreeModelItem* item = parentItem->children_.at(row);
|
||||
return createIndex(row, column, (void*)item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return QModelIndex(); // invalid index
|
||||
}
|
||||
|
||||
bool DirTreeModel::hasChildren(const QModelIndex& parent) const {
|
||||
DirTreeModelItem* item = itemFromIndex(parent);
|
||||
return item ? !item->isPlaceHolder() : true;
|
||||
}
|
||||
|
||||
QModelIndex DirTreeModel::indexFromItem(DirTreeModelItem* item) const {
|
||||
Q_ASSERT(item);
|
||||
const QList<DirTreeModelItem*>& items = item->parent_ ? item->parent_->children_ : rootItems_;
|
||||
int row = items.indexOf(item);
|
||||
if(row >= 0)
|
||||
return createIndex(row, 0, (void*)item);
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
// public APIs
|
||||
QModelIndex DirTreeModel::addRoot(FmFileInfo* root) {
|
||||
DirTreeModelItem* item = new DirTreeModelItem(root, this);
|
||||
int row = rootItems_.count();
|
||||
beginInsertRows(QModelIndex(), row, row);
|
||||
item->fileInfo_ = fm_file_info_ref(root);
|
||||
rootItems_.append(item);
|
||||
// add_place_holder_child_item(model, item_l, NULL, FALSE);
|
||||
endInsertRows();
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
DirTreeModelItem* DirTreeModel::itemFromIndex(const QModelIndex& index) const {
|
||||
return reinterpret_cast<DirTreeModelItem*>(index.internalPointer());
|
||||
}
|
||||
|
||||
QModelIndex DirTreeModel::indexFromPath(FmPath* path) const {
|
||||
DirTreeModelItem* item = itemFromPath(path);
|
||||
return item ? item->index() : QModelIndex();
|
||||
}
|
||||
|
||||
DirTreeModelItem* DirTreeModel::itemFromPath(FmPath* path) const {
|
||||
Q_FOREACH(DirTreeModelItem* item, rootItems_) {
|
||||
if(item->fileInfo_ && fm_path_equal(path, fm_file_info_get_path(item->fileInfo_))) {
|
||||
return item;
|
||||
}
|
||||
else {
|
||||
DirTreeModelItem* child = item->childFromPath(path, true);
|
||||
if(child)
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void DirTreeModel::loadRow(const QModelIndex& index) {
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
Q_ASSERT(item);
|
||||
if(item && !item->isPlaceHolder())
|
||||
item->loadFolder();
|
||||
}
|
||||
|
||||
void DirTreeModel::unloadRow(const QModelIndex& index) {
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
if(item && !item->isPlaceHolder())
|
||||
item->unloadFolder();
|
||||
}
|
||||
|
||||
bool DirTreeModel::isLoaded(const QModelIndex& index) {
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
return item ? item->loaded_ : false;
|
||||
}
|
||||
|
||||
QIcon DirTreeModel::icon(const QModelIndex& index) {
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
return item ? item->icon_ : QIcon();
|
||||
}
|
||||
|
||||
FmFileInfo* DirTreeModel::fileInfo(const QModelIndex& index) {
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
return item ? item->fileInfo_ : NULL;
|
||||
}
|
||||
|
||||
FmPath* DirTreeModel::filePath(const QModelIndex& index) {
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
return item && item->fileInfo_ ? fm_file_info_get_path(item->fileInfo_) : NULL;
|
||||
}
|
||||
|
||||
QString DirTreeModel::dispName(const QModelIndex& index) {
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
return item ? item->displayName_ : QString();
|
||||
}
|
||||
|
||||
void DirTreeModel::setShowHidden(bool show_hidden) {
|
||||
showHidden_ = show_hidden;
|
||||
Q_FOREACH(DirTreeModelItem* item, rootItems_) {
|
||||
item->setShowHidden(show_hidden);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_DIRTREEMODEL_H
|
||||
#define FM_DIRTREEMODEL_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QModelIndex>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QIcon>
|
||||
#include <QList>
|
||||
#include <QSharedPointer>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class DirTreeModelItem;
|
||||
class DirTreeView;
|
||||
|
||||
class LIBFM_QT_API DirTreeModel : public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
friend class DirTreeModelItem; // allow direct access of private members in DirTreeModelItem
|
||||
friend class DirTreeView; // allow direct access of private members in DirTreeView
|
||||
|
||||
enum Role {
|
||||
FileInfoRole = Qt::UserRole
|
||||
};
|
||||
|
||||
explicit DirTreeModel(QObject* parent);
|
||||
~DirTreeModel();
|
||||
|
||||
QModelIndex addRoot(FmFileInfo* root);
|
||||
void loadRow(const QModelIndex& index);
|
||||
void unloadRow(const QModelIndex& index);
|
||||
|
||||
bool isLoaded(const QModelIndex& index);
|
||||
QIcon icon(const QModelIndex& index);
|
||||
FmFileInfo* fileInfo(const QModelIndex& index);
|
||||
FmPath* filePath(const QModelIndex& index);
|
||||
QString dispName(const QModelIndex& index);
|
||||
|
||||
void setShowHidden(bool show_hidden);
|
||||
bool showHidden() const {
|
||||
return showHidden_;
|
||||
}
|
||||
|
||||
QModelIndex indexFromPath(FmPath* path) const;
|
||||
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const;
|
||||
virtual QVariant data(const QModelIndex& index, int role) const;
|
||||
virtual int columnCount(const QModelIndex& parent) const;
|
||||
virtual int rowCount(const QModelIndex& parent) const;
|
||||
virtual QModelIndex parent(const QModelIndex& child) const;
|
||||
virtual QModelIndex index(int row, int column, const QModelIndex& parent) const;
|
||||
virtual bool hasChildren(const QModelIndex& parent = QModelIndex()) const;
|
||||
|
||||
private:
|
||||
DirTreeModelItem* itemFromPath(FmPath* path) const;
|
||||
DirTreeModelItem* itemFromIndex(const QModelIndex& index) const;
|
||||
QModelIndex indexFromItem(DirTreeModelItem* item) const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void rowLoaded(const QModelIndex& index);
|
||||
|
||||
private:
|
||||
bool showHidden_;
|
||||
QList<DirTreeModelItem*> rootItems_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // FM_DIRTREEMODEL_H
|
@ -0,0 +1,337 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "dirtreemodelitem.h"
|
||||
#include "dirtreemodel.h"
|
||||
#include "icontheme.h"
|
||||
#include <QDebug>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
DirTreeModelItem::DirTreeModelItem():
|
||||
model_(nullptr),
|
||||
folder_(nullptr),
|
||||
expanded_(false),
|
||||
loaded_(false),
|
||||
fileInfo_(nullptr),
|
||||
placeHolderChild_(nullptr),
|
||||
parent_(nullptr) {
|
||||
}
|
||||
|
||||
DirTreeModelItem::DirTreeModelItem(FmFileInfo* info, DirTreeModel* model, DirTreeModelItem* parent):
|
||||
model_(model),
|
||||
folder_(nullptr),
|
||||
expanded_(false),
|
||||
loaded_(false),
|
||||
fileInfo_(fm_file_info_ref(info)),
|
||||
displayName_(QString::fromUtf8(fm_file_info_get_disp_name(info))),
|
||||
icon_(IconTheme::icon(fm_file_info_get_icon(info))),
|
||||
placeHolderChild_(nullptr),
|
||||
parent_(parent) {
|
||||
|
||||
if(info)
|
||||
addPlaceHolderChild();
|
||||
}
|
||||
|
||||
DirTreeModelItem::~DirTreeModelItem() {
|
||||
if(fileInfo_)
|
||||
fm_file_info_unref(fileInfo_);
|
||||
|
||||
if(folder_)
|
||||
freeFolder();
|
||||
|
||||
// delete child items if needed
|
||||
if(!children_.isEmpty()) {
|
||||
Q_FOREACH(DirTreeModelItem* item, children_) {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
if(!hiddenChildren_.isEmpty()) {
|
||||
Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeModelItem::addPlaceHolderChild() {
|
||||
placeHolderChild_ = new DirTreeModelItem();
|
||||
placeHolderChild_->parent_ = this;
|
||||
placeHolderChild_->model_ = model_;
|
||||
placeHolderChild_->displayName_ = DirTreeModel::tr("Loading...");
|
||||
children_.append(placeHolderChild_);
|
||||
}
|
||||
|
||||
void DirTreeModelItem::freeFolder() {
|
||||
if(folder_) {
|
||||
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFinishLoading), this);
|
||||
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFilesAdded), this);
|
||||
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFilesRemoved), this);
|
||||
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFilesChanged), this);
|
||||
g_object_unref(folder_);
|
||||
folder_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeModelItem::loadFolder() {
|
||||
if(!expanded_) {
|
||||
/* dynamically load content of the folder. */
|
||||
folder_ = fm_folder_from_path(fm_file_info_get_path(fileInfo_));
|
||||
/* g_debug("fm_dir_tree_model_load_row()"); */
|
||||
/* associate the data with loaded handler */
|
||||
g_signal_connect(folder_, "finish-loading", G_CALLBACK(onFolderFinishLoading), this);
|
||||
g_signal_connect(folder_, "files-added", G_CALLBACK(onFolderFilesAdded), this);
|
||||
g_signal_connect(folder_, "files-removed", G_CALLBACK(onFolderFilesRemoved), this);
|
||||
g_signal_connect(folder_, "files-changed", G_CALLBACK(onFolderFilesChanged), this);
|
||||
|
||||
/* set 'expanded' flag beforehand as callback may check it */
|
||||
expanded_ = true;
|
||||
/* if the folder is already loaded, call "loaded" handler ourselves */
|
||||
if(fm_folder_is_loaded(folder_)) { // already loaded
|
||||
GList* file_l;
|
||||
FmFileInfoList* files = fm_folder_get_files(folder_);
|
||||
for(file_l = fm_file_info_list_peek_head_link(files); file_l; file_l = file_l->next) {
|
||||
FmFileInfo* fi = FM_FILE_INFO(file_l->data);
|
||||
if(fm_file_info_is_dir(fi)) {
|
||||
insertFileInfo(fi);
|
||||
}
|
||||
}
|
||||
onFolderFinishLoading(folder_, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeModelItem::unloadFolder() {
|
||||
if(expanded_) { /* do some cleanup */
|
||||
/* remove all children, and replace them with a dummy child
|
||||
* item to keep expander in the tree view around. */
|
||||
|
||||
// delete all visible child items
|
||||
model_->beginRemoveRows(index(), 0, children_.count() - 1);
|
||||
if(!children_.isEmpty()) {
|
||||
Q_FOREACH(DirTreeModelItem* item, children_) {
|
||||
delete item;
|
||||
}
|
||||
children_.clear();
|
||||
}
|
||||
model_->endRemoveRows();
|
||||
|
||||
// remove hidden children
|
||||
if(!hiddenChildren_.isEmpty()) {
|
||||
Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) {
|
||||
delete item;
|
||||
}
|
||||
hiddenChildren_.clear();
|
||||
}
|
||||
|
||||
/* now, we have no child since all child items are removed.
|
||||
* So we add a place holder child item to keep the expander around. */
|
||||
addPlaceHolderChild();
|
||||
/* deactivate folder since it will be reactivated on expand */
|
||||
freeFolder();
|
||||
expanded_ = false;
|
||||
loaded_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex DirTreeModelItem::index() {
|
||||
Q_ASSERT(model_);
|
||||
return model_->indexFromItem(this);
|
||||
}
|
||||
|
||||
/* Add file info to parent node to proper position.
|
||||
* GtkTreePath tp is the tree path of parent node. */
|
||||
DirTreeModelItem* DirTreeModelItem::insertFileInfo(FmFileInfo* fi) {
|
||||
// qDebug() << "insertFileInfo: " << fm_file_info_get_disp_name(fi);
|
||||
DirTreeModelItem* item = new DirTreeModelItem(fi, model_);
|
||||
insertItem(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
// find a good position to insert the new item
|
||||
int DirTreeModelItem::insertItem(DirTreeModelItem* newItem) {
|
||||
if(model_->showHidden() || !newItem->fileInfo_ || !fm_file_info_is_hidden(newItem->fileInfo_)) {
|
||||
const char* new_key = fm_file_info_get_collate_key(newItem->fileInfo_);
|
||||
int pos = 0;
|
||||
QList<DirTreeModelItem*>::iterator it;
|
||||
for(it = children_.begin(); it != children_.end(); ++it) {
|
||||
DirTreeModelItem* child = *it;
|
||||
if(G_UNLIKELY(!child->fileInfo_))
|
||||
continue;
|
||||
const char* key = fm_file_info_get_collate_key(child->fileInfo_);
|
||||
if(strcmp(new_key, key) <= 0)
|
||||
break;
|
||||
++pos;
|
||||
}
|
||||
// inform the world that we're about to insert the item
|
||||
model_->beginInsertRows(index(), pos, pos);
|
||||
newItem->parent_ = this;
|
||||
children_.insert(it, newItem);
|
||||
model_->endInsertRows();
|
||||
return pos;
|
||||
}
|
||||
else { // hidden folder
|
||||
hiddenChildren_.append(newItem);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// FmFolder signal handlers
|
||||
|
||||
// static
|
||||
void DirTreeModelItem::onFolderFinishLoading(FmFolder* folder, gpointer user_data) {
|
||||
DirTreeModelItem* _this = (DirTreeModelItem*)user_data;
|
||||
DirTreeModel* model = _this->model_;
|
||||
/* set 'loaded' flag beforehand as callback may check it */
|
||||
_this->loaded_ = true;
|
||||
QModelIndex index = _this->index();
|
||||
qDebug() << "folder loaded";
|
||||
// remove the placeholder child if needed
|
||||
if(_this->children_.count() == 1) { // we have no other child other than the place holder item, leave it
|
||||
_this->placeHolderChild_->displayName_ = DirTreeModel::tr("<No sub folders>");
|
||||
QModelIndex placeHolderIndex = _this->placeHolderChild_->index();
|
||||
// qDebug() << "placeHolderIndex: "<<placeHolderIndex;
|
||||
Q_EMIT model->dataChanged(placeHolderIndex, placeHolderIndex);
|
||||
}
|
||||
else {
|
||||
int pos = _this->children_.indexOf(_this->placeHolderChild_);
|
||||
model->beginRemoveRows(index, pos, pos);
|
||||
_this->children_.removeAt(pos);
|
||||
delete _this->placeHolderChild_;
|
||||
model->endRemoveRows();
|
||||
_this->placeHolderChild_ = nullptr;
|
||||
}
|
||||
|
||||
Q_EMIT model->rowLoaded(index);
|
||||
}
|
||||
|
||||
// static
|
||||
void DirTreeModelItem::onFolderFilesAdded(FmFolder* folder, GSList* files, gpointer user_data) {
|
||||
GSList* l;
|
||||
DirTreeModelItem* _this = (DirTreeModelItem*)user_data;
|
||||
for(l = files; l; l = l->next) {
|
||||
FmFileInfo* fi = FM_FILE_INFO(l->data);
|
||||
if(fm_file_info_is_dir(fi)) { /* FIXME: maybe adding files can be allowed later */
|
||||
/* Ideally FmFolder should not emit files-added signals for files that
|
||||
* already exists. So there is no need to check for duplication here. */
|
||||
_this->insertFileInfo(fi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void DirTreeModelItem::onFolderFilesRemoved(FmFolder* folder, GSList* files, gpointer user_data) {
|
||||
DirTreeModelItem* _this = (DirTreeModelItem*)user_data;
|
||||
DirTreeModel* model = _this->model_;
|
||||
|
||||
for(GSList* l = files; l; l = l->next) {
|
||||
FmFileInfo* fi = FM_FILE_INFO(l->data);
|
||||
int pos;
|
||||
DirTreeModelItem* child = _this->childFromName(fm_file_info_get_name(fi), &pos);
|
||||
if(child) {
|
||||
model->beginRemoveRows(_this->index(), pos, pos);
|
||||
_this->children_.removeAt(pos);
|
||||
delete child;
|
||||
model->endRemoveRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void DirTreeModelItem::onFolderFilesChanged(FmFolder* folder, GSList* files, gpointer user_data) {
|
||||
DirTreeModelItem* _this = (DirTreeModelItem*)user_data;
|
||||
DirTreeModel* model = _this->model_;
|
||||
|
||||
for(GSList* l = files; l; l = l->next) {
|
||||
FmFileInfo* changedFile = FM_FILE_INFO(l->data);
|
||||
int pos;
|
||||
DirTreeModelItem* child = _this->childFromName(fm_file_info_get_name(changedFile), &pos);
|
||||
if(child) {
|
||||
QModelIndex childIndex = child->index();
|
||||
Q_EMIT model->dataChanged(childIndex, childIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DirTreeModelItem* DirTreeModelItem::childFromName(const char* utf8_name, int* pos) {
|
||||
int i = 0;
|
||||
for (const auto item : children_) {
|
||||
if(item->fileInfo_ && strcmp(fm_file_info_get_name(item->fileInfo_), utf8_name) == 0) {
|
||||
if(pos)
|
||||
*pos = i;
|
||||
return item;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DirTreeModelItem* DirTreeModelItem::childFromPath(FmPath* path, bool recursive) const {
|
||||
Q_ASSERT(path != nullptr);
|
||||
|
||||
Q_FOREACH(DirTreeModelItem* item, children_) {
|
||||
// if(item->fileInfo_)
|
||||
// qDebug() << "child: " << QString::fromUtf8(fm_file_info_get_disp_name(item->fileInfo_));
|
||||
if(item->fileInfo_ && fm_path_equal(fm_file_info_get_path(item->fileInfo_), path)) {
|
||||
return item;
|
||||
} else if(recursive) {
|
||||
DirTreeModelItem* child = item->childFromPath(path, true);
|
||||
if(child)
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DirTreeModelItem::setShowHidden(bool show) {
|
||||
if(show) {
|
||||
// move all hidden children to visible list
|
||||
Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) {
|
||||
insertItem(item);
|
||||
}
|
||||
hiddenChildren_.clear();
|
||||
}
|
||||
else { // hide hidden folders
|
||||
QModelIndex _index = index();
|
||||
QList<DirTreeModelItem*>::iterator it, next;
|
||||
int pos = 0;
|
||||
for(it = children_.begin(); it != children_.end(); ++pos) {
|
||||
DirTreeModelItem* item = *it;
|
||||
next = it + 1;
|
||||
if(item->fileInfo_) {
|
||||
if(fm_file_info_is_hidden(item->fileInfo_)) { // hidden folder
|
||||
// remove from the model and add to the hiddenChildren_ list
|
||||
model_->beginRemoveRows(_index, pos, pos);
|
||||
children_.erase(it);
|
||||
hiddenChildren_.append(item);
|
||||
model_->endRemoveRows();
|
||||
}
|
||||
else { // visible folder, recursively filter its children
|
||||
item->setShowHidden(show);
|
||||
}
|
||||
}
|
||||
it = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Fm
|
||||
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_DIRTREEMODELITEM_H
|
||||
#define FM_DIRTREEMODELITEM_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <libfm/fm.h>
|
||||
#include <QIcon>
|
||||
#include <QList>
|
||||
#include <QModelIndex>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class DirTreeModel;
|
||||
class DirTreeView;
|
||||
|
||||
class LIBFM_QT_API DirTreeModelItem {
|
||||
public:
|
||||
friend class DirTreeModel; // allow direct access of private members in DirTreeModel
|
||||
friend class DirTreeView; // allow direct access of private members in DirTreeView
|
||||
|
||||
explicit DirTreeModelItem();
|
||||
explicit DirTreeModelItem(FmFileInfo* info, DirTreeModel* model, DirTreeModelItem* parent = nullptr);
|
||||
~DirTreeModelItem();
|
||||
|
||||
void loadFolder();
|
||||
void unloadFolder();
|
||||
|
||||
inline bool isPlaceHolder() const {
|
||||
return (fileInfo_ == nullptr);
|
||||
}
|
||||
|
||||
void setShowHidden(bool show);
|
||||
|
||||
private:
|
||||
void freeFolder();
|
||||
void addPlaceHolderChild();
|
||||
DirTreeModelItem* childFromName(const char* utf8_name, int* pos);
|
||||
DirTreeModelItem* childFromPath(FmPath* path, bool recursive) const;
|
||||
|
||||
DirTreeModelItem* insertFileInfo(FmFileInfo* fi);
|
||||
int insertItem(Fm::DirTreeModelItem* newItem);
|
||||
QModelIndex index();
|
||||
|
||||
static void onFolderFinishLoading(FmFolder* folder, gpointer user_data);
|
||||
static void onFolderFilesAdded(FmFolder* folder, GSList* files, gpointer user_data);
|
||||
static void onFolderFilesRemoved(FmFolder* folder, GSList* files, gpointer user_data);
|
||||
static void onFolderFilesChanged(FmFolder* folder, GSList* files, gpointer user_data);
|
||||
|
||||
private:
|
||||
FmFileInfo* fileInfo_;
|
||||
FmFolder* folder_;
|
||||
QString displayName_ ;
|
||||
QIcon icon_;
|
||||
bool expanded_;
|
||||
bool loaded_;
|
||||
DirTreeModelItem* parent_;
|
||||
DirTreeModelItem* placeHolderChild_;
|
||||
QList<DirTreeModelItem*> children_;
|
||||
QList<DirTreeModelItem*> hiddenChildren_;
|
||||
DirTreeModel* model_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_DIRTREEMODELITEM_H
|
@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "dirtreeview.h"
|
||||
#include <QHeaderView>
|
||||
#include <QDebug>
|
||||
#include <QItemSelection>
|
||||
#include <QGuiApplication>
|
||||
#include <QMouseEvent>
|
||||
#include "dirtreemodel.h"
|
||||
#include "dirtreemodelitem.h"
|
||||
#include "filemenu.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
DirTreeView::DirTreeView(QWidget* parent):
|
||||
currentExpandingItem_(NULL),
|
||||
currentPath_(NULL) {
|
||||
|
||||
setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
setHeaderHidden(true);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
header()->setStretchLastSection(false);
|
||||
|
||||
connect(this, &DirTreeView::collapsed, this, &DirTreeView::onCollapsed);
|
||||
connect(this, &DirTreeView::expanded, this, &DirTreeView::onExpanded);
|
||||
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(this, &DirTreeView::customContextMenuRequested,
|
||||
this, &DirTreeView::onCustomContextMenuRequested);
|
||||
}
|
||||
|
||||
DirTreeView::~DirTreeView() {
|
||||
if(currentPath_)
|
||||
fm_path_unref(currentPath_);
|
||||
}
|
||||
|
||||
void DirTreeView::cancelPendingChdir() {
|
||||
if(!pathsToExpand_.isEmpty()) {
|
||||
pathsToExpand_.clear();
|
||||
if(!currentExpandingItem_)
|
||||
return;
|
||||
DirTreeModel* _model = static_cast<DirTreeModel*>(model());
|
||||
disconnect(_model, &DirTreeModel::rowLoaded, this, &DirTreeView::onRowLoaded);
|
||||
currentExpandingItem_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeView::expandPendingPath() {
|
||||
if(pathsToExpand_.isEmpty())
|
||||
return;
|
||||
|
||||
FmPath* path = pathsToExpand_.first().data();
|
||||
// qDebug() << "expanding: " << Path(path).displayBasename();
|
||||
DirTreeModel* _model = static_cast<DirTreeModel*>(model());
|
||||
DirTreeModelItem* item = _model->itemFromPath(path);
|
||||
// qDebug() << "findItem: " << item;
|
||||
if(item) {
|
||||
currentExpandingItem_ = item;
|
||||
connect(_model, &DirTreeModel::rowLoaded, this, &DirTreeView::onRowLoaded);
|
||||
if(item->loaded_) { // the node is already loaded
|
||||
onRowLoaded(item->index());
|
||||
}
|
||||
else {
|
||||
// _model->loadRow(item->index());
|
||||
item->loadFolder();
|
||||
}
|
||||
}
|
||||
else {
|
||||
selectionModel()->clear();
|
||||
/* since we never get it loaded we need to update cwd here */
|
||||
if(currentPath_)
|
||||
fm_path_unref(currentPath_);
|
||||
currentPath_ = fm_path_ref(path);
|
||||
|
||||
cancelPendingChdir(); // FIXME: is this correct? this is not done in the gtk+ version of libfm.
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeView::onRowLoaded(const QModelIndex& index) {
|
||||
DirTreeModel* _model = static_cast<DirTreeModel*>(model());
|
||||
if(!currentExpandingItem_)
|
||||
return;
|
||||
if(currentExpandingItem_ != _model->itemFromIndex(index)) {
|
||||
return;
|
||||
}
|
||||
/* disconnect the handler since we only need it once */
|
||||
disconnect(_model, &DirTreeModel::rowLoaded, this, &DirTreeView::onRowLoaded);
|
||||
|
||||
// DirTreeModelItem* item = _model->itemFromIndex(index);
|
||||
// qDebug() << "row loaded: " << item->displayName_;
|
||||
/* after the folder is loaded, the files should have been added to
|
||||
* the tree model */
|
||||
expand(index);
|
||||
|
||||
/* remove the expanded path from pending list */
|
||||
pathsToExpand_.removeFirst();
|
||||
if(pathsToExpand_.isEmpty()) { /* this is the last one and we're done, select the item */
|
||||
// qDebug() << "Done!";
|
||||
selectionModel()->select(index, QItemSelectionModel::SelectCurrent|QItemSelectionModel::Clear);
|
||||
scrollTo(index, QAbstractItemView::EnsureVisible);
|
||||
}
|
||||
else { /* continue expanding next pending path */
|
||||
expandPendingPath();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DirTreeView::setCurrentPath(FmPath* path) {
|
||||
DirTreeModel* _model = static_cast<DirTreeModel*>(model());
|
||||
if(!_model)
|
||||
return;
|
||||
int rowCount = _model->rowCount(QModelIndex());
|
||||
if(rowCount <= 0 || fm_path_equal(currentPath_, path))
|
||||
return;
|
||||
|
||||
if(currentPath_)
|
||||
fm_path_unref(currentPath_);
|
||||
currentPath_ = fm_path_ref(path);
|
||||
|
||||
// NOTE: The content of each node is loaded on demand dynamically.
|
||||
// So, when we ask for a chdir operation, some nodes do not exists yet.
|
||||
// We have to wait for the loading of child nodes and continue the
|
||||
// pending chdir operation after the child nodes become available.
|
||||
|
||||
// cancel previous pending tree expansion
|
||||
cancelPendingChdir();
|
||||
|
||||
/* find a root item containing this path */
|
||||
FmPath* root;
|
||||
for(int row = 0; row < rowCount; ++row) {
|
||||
QModelIndex index = _model->index(row, 0, QModelIndex());
|
||||
root = _model->filePath(index);
|
||||
if(fm_path_has_prefix(path, root))
|
||||
break;
|
||||
root = NULL;
|
||||
}
|
||||
|
||||
if(root) { /* root item is found */
|
||||
do { /* add path elements one by one to a list */
|
||||
pathsToExpand_.prepend(path);
|
||||
// qDebug() << "prepend path: " << Path(path).displayBasename();
|
||||
if(fm_path_equal(path, root))
|
||||
break;
|
||||
path = fm_path_get_parent(path);
|
||||
}
|
||||
while(path);
|
||||
|
||||
expandPendingPath();
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeView::setModel(QAbstractItemModel* model) {
|
||||
Q_ASSERT(model->inherits("Fm::DirTreeModel"));
|
||||
|
||||
if(!pathsToExpand_.isEmpty()) // if a chdir request is in progress, cancel it
|
||||
cancelPendingChdir();
|
||||
|
||||
QTreeView::setModel(model);
|
||||
header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||
connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &DirTreeView::onSelectionChanged);
|
||||
}
|
||||
|
||||
void DirTreeView::mousePressEvent(QMouseEvent* event) {
|
||||
if(event && event->button() == Qt::RightButton &&
|
||||
event->type() == QEvent::MouseButtonPress) {
|
||||
// Do not change the selection when the context menu is activated.
|
||||
return;
|
||||
}
|
||||
QTreeView::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void DirTreeView::onCustomContextMenuRequested(const QPoint& pos) {
|
||||
QModelIndex index = indexAt(pos);
|
||||
if(index.isValid()) {
|
||||
QVariant data = index.data(DirTreeModel::FileInfoRole);
|
||||
FmFileInfo* fileInfo = reinterpret_cast<FmFileInfo*>(data.value<void*>());
|
||||
if(fileInfo) {
|
||||
FmPath* path = fm_file_info_get_path(fileInfo);
|
||||
FmFileInfoList* files = fm_file_info_list_new();
|
||||
fm_file_info_list_push_tail(files, fileInfo);
|
||||
Fm::FileMenu* menu = new Fm::FileMenu(files, fileInfo, path);
|
||||
// FIXME: apply some settings to the menu and set a proper file launcher to it
|
||||
Q_EMIT prepareFileMenu(menu);
|
||||
fm_file_info_list_unref(files);
|
||||
QVariant pathData = qVariantFromValue(reinterpret_cast<void*>(path));
|
||||
QAction* action = menu->openAction();
|
||||
action->disconnect();
|
||||
action->setData(index);
|
||||
connect(action, &QAction::triggered, this, &DirTreeView::onOpen);
|
||||
action = new QAction(QIcon::fromTheme("window-new"), tr("Open in New T&ab"), menu);
|
||||
action->setData(pathData);
|
||||
connect(action, &QAction::triggered, this, &DirTreeView::onNewTab);
|
||||
menu->insertAction(menu->separator1(), action);
|
||||
action = new QAction(QIcon::fromTheme("window-new"), tr("Open in New Win&dow"), menu);
|
||||
action->setData(pathData);
|
||||
connect(action, &QAction::triggered, this, &DirTreeView::onNewWindow);
|
||||
menu->insertAction(menu->separator1(), action);
|
||||
if(fm_file_info_is_native(fileInfo)) {
|
||||
action = new QAction(QIcon::fromTheme("utilities-terminal"), tr("Open in Termina&l"), menu);
|
||||
action->setData(pathData);
|
||||
connect(action, &QAction::triggered, this, &DirTreeView::onOpenInTerminal);
|
||||
menu->insertAction(menu->separator1(), action);
|
||||
}
|
||||
menu->exec(mapToGlobal(pos));
|
||||
delete menu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeView::onOpen() {
|
||||
if(QAction* action = qobject_cast<QAction*>(sender())) {
|
||||
setCurrentIndex(action->data().toModelIndex());
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeView::onNewWindow() {
|
||||
if(QAction* action = qobject_cast<QAction*>(sender())) {
|
||||
FmPath* path = reinterpret_cast<FmPath*>(action->data().value<void*>());
|
||||
Q_EMIT openFolderInNewWindowRequested(path);
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeView::onNewTab() {
|
||||
if(QAction* action = qobject_cast<QAction*>(sender())) {
|
||||
FmPath* path = reinterpret_cast<FmPath*>(action->data().value<void*>());
|
||||
Q_EMIT openFolderInNewTabRequested(path);
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeView::onOpenInTerminal() {
|
||||
if(QAction* action = qobject_cast<QAction*>(sender())) {
|
||||
FmPath* path = reinterpret_cast<FmPath*>(action->data().value<void*>());
|
||||
Q_EMIT openFolderInTerminalRequested(path);
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeView::onNewFolder() {
|
||||
if(QAction* action = qobject_cast<QAction*>(sender())) {
|
||||
FmPath* path = reinterpret_cast<FmPath*>(action->data().value<void*>());
|
||||
Q_EMIT createNewFolderRequested(path);
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeView::onCollapsed(const QModelIndex& index) {
|
||||
DirTreeModel* treeModel = static_cast<DirTreeModel*>(model());
|
||||
if(treeModel) {
|
||||
treeModel->unloadRow(index);
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeView::onExpanded(const QModelIndex& index) {
|
||||
DirTreeModel* treeModel = static_cast<DirTreeModel*>(model());
|
||||
if(treeModel) {
|
||||
treeModel->loadRow(index);
|
||||
}
|
||||
}
|
||||
|
||||
void DirTreeView::onSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected) {
|
||||
if(!selected.isEmpty()) {
|
||||
QModelIndex index = selected.first().topLeft();
|
||||
DirTreeModel* _model = static_cast<DirTreeModel*>(model());
|
||||
FmPath* path = _model->filePath(index);
|
||||
if(path && currentPath_ && fm_path_equal(path, currentPath_))
|
||||
return;
|
||||
cancelPendingChdir();
|
||||
if(!path)
|
||||
return;
|
||||
if(currentPath_)
|
||||
fm_path_unref(currentPath_);
|
||||
currentPath_ = fm_path_ref(path);
|
||||
|
||||
// FIXME: use enums for type rather than hard-coded values 0 or 1
|
||||
int type = 0;
|
||||
if(QGuiApplication::mouseButtons() & Qt::MiddleButton)
|
||||
type = 1;
|
||||
Q_EMIT chdirRequested(type, path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_DIRTREEVIEW_H
|
||||
#define FM_DIRTREEVIEW_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QTreeView>
|
||||
#include <libfm/fm.h>
|
||||
#include "path.h"
|
||||
|
||||
class QItemSelection;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class FileMenu;
|
||||
class DirTreeModelItem;
|
||||
|
||||
class LIBFM_QT_API DirTreeView : public QTreeView {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DirTreeView(QWidget* parent);
|
||||
~DirTreeView();
|
||||
|
||||
FmPath* currentPath() {
|
||||
return currentPath_;
|
||||
}
|
||||
|
||||
void setCurrentPath(FmPath* path);
|
||||
|
||||
// libfm-gtk compatible alias
|
||||
FmPath* getCwd() {
|
||||
return currentPath();
|
||||
}
|
||||
|
||||
void chdir(FmPath* path) {
|
||||
setCurrentPath(path);
|
||||
}
|
||||
|
||||
virtual void setModel(QAbstractItemModel* model);
|
||||
|
||||
protected:
|
||||
virtual void mousePressEvent(QMouseEvent* event);
|
||||
|
||||
private:
|
||||
void cancelPendingChdir();
|
||||
void expandPendingPath();
|
||||
|
||||
Q_SIGNALS:
|
||||
void chdirRequested(int type, FmPath* path);
|
||||
void openFolderInNewWindowRequested(FmPath* path);
|
||||
void openFolderInNewTabRequested(FmPath* path);
|
||||
void openFolderInTerminalRequested(FmPath* path);
|
||||
void createNewFolderRequested(FmPath* path);
|
||||
void prepareFileMenu(Fm::FileMenu* menu); // emit before showing a Fm::FileMenu
|
||||
|
||||
protected Q_SLOTS:
|
||||
void onCollapsed(const QModelIndex & index);
|
||||
void onExpanded(const QModelIndex & index);
|
||||
void onRowLoaded(const QModelIndex& index);
|
||||
void onSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected);
|
||||
void onCustomContextMenuRequested(const QPoint& pos);
|
||||
void onOpen();
|
||||
void onNewWindow();
|
||||
void onNewTab();
|
||||
void onOpenInTerminal();
|
||||
void onNewFolder();
|
||||
|
||||
private:
|
||||
FmPath* currentPath_;
|
||||
QList<Path> pathsToExpand_;
|
||||
DirTreeModelItem* currentExpandingItem_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_DIRTREEVIEW_H
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "dndactionmenu.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
DndActionMenu::DndActionMenu(Qt::DropActions possibleActions, QWidget* parent)
|
||||
: QMenu(parent)
|
||||
, copyAction(nullptr)
|
||||
, moveAction(nullptr)
|
||||
, linkAction(nullptr)
|
||||
, cancelAction(nullptr)
|
||||
{
|
||||
if (possibleActions.testFlag(Qt::CopyAction))
|
||||
copyAction = addAction(QIcon::fromTheme("edit-copy"), tr("Copy here"));
|
||||
if (possibleActions.testFlag(Qt::MoveAction))
|
||||
moveAction = addAction(tr("Move here"));
|
||||
if (possibleActions.testFlag(Qt::LinkAction))
|
||||
linkAction = addAction(tr("Create symlink here"));
|
||||
addSeparator();
|
||||
cancelAction = addAction(tr("Cancel"));
|
||||
}
|
||||
|
||||
DndActionMenu::~DndActionMenu() {
|
||||
|
||||
}
|
||||
|
||||
Qt::DropAction DndActionMenu::askUser(Qt::DropActions possibleActions, QPoint pos) {
|
||||
Qt::DropAction result = Qt::IgnoreAction;
|
||||
DndActionMenu menu{possibleActions};
|
||||
QAction* action = menu.exec(pos);
|
||||
if (nullptr != action)
|
||||
{
|
||||
if(action == menu.copyAction)
|
||||
result = Qt::CopyAction;
|
||||
else if(action == menu.moveAction)
|
||||
result = Qt::MoveAction;
|
||||
else if(action == menu.linkAction)
|
||||
result = Qt::LinkAction;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_DNDACTIONMENU_H
|
||||
#define FM_DNDACTIONMENU_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class DndActionMenu : public QMenu {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DndActionMenu(Qt::DropActions possibleActions, QWidget* parent = 0);
|
||||
virtual ~DndActionMenu();
|
||||
|
||||
static Qt::DropAction askUser(Qt::DropActions possibleActions, QPoint pos);
|
||||
|
||||
private:
|
||||
QAction* copyAction;
|
||||
QAction* moveAction;
|
||||
QAction* linkAction;
|
||||
QAction* cancelAction;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_DNDACTIONMENU_H
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "dnddest.h"
|
||||
#include "fileoperation.h"
|
||||
#include "utilities.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
const char* supportedMimeTypes[] = {
|
||||
"text/uri-list"
|
||||
"XdndDirectSave0"/* X direct save */
|
||||
/* TODO: add more targets to support: text types, _NETSCAPE_URL, property/bgimage ... */
|
||||
};
|
||||
|
||||
DndDest::DndDest() {
|
||||
|
||||
}
|
||||
|
||||
DndDest::~DndDest() {
|
||||
|
||||
}
|
||||
|
||||
bool DndDest::dropMimeData(const QMimeData* data, Qt::DropAction action) {
|
||||
// FIXME: should we put this in dropEvent handler of FolderView instead?
|
||||
if(data->hasUrls()) {
|
||||
qDebug("drop action: %d", action);
|
||||
FmPathList* srcPaths = pathListFromQUrls(data->urls());
|
||||
switch(action) {
|
||||
case Qt::CopyAction:
|
||||
FileOperation::copyFiles(srcPaths, destPath_.data());
|
||||
break;
|
||||
case Qt::MoveAction:
|
||||
FileOperation::moveFiles(srcPaths, destPath_.data());
|
||||
break;
|
||||
case Qt::LinkAction:
|
||||
FileOperation::symlinkFiles(srcPaths, destPath_.data());
|
||||
default:
|
||||
fm_path_list_unref(srcPaths);
|
||||
return false;
|
||||
}
|
||||
fm_path_list_unref(srcPaths);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DndDest::isSupported(const QMimeData* data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DndDest::isSupported(QString mimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_DNDDEST_H
|
||||
#define FM_DNDDEST_H
|
||||
|
||||
#include <QMimeData>
|
||||
#include "path.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class DndDest {
|
||||
public:
|
||||
DndDest();
|
||||
~DndDest();
|
||||
|
||||
void setDestPath(FmPath* dest) {
|
||||
destPath_ = dest;
|
||||
}
|
||||
|
||||
const Path& destPath() {
|
||||
return destPath_;
|
||||
}
|
||||
|
||||
bool isSupported(const QMimeData* data);
|
||||
bool isSupported(QString mimeType);
|
||||
|
||||
bool dropMimeData(const QMimeData* data, Qt::DropAction action);
|
||||
|
||||
private:
|
||||
Path destPath_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_DNDDEST_H
|
@ -0,0 +1,143 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>EditBookmarksDialog</class>
|
||||
<widget class="QDialog" name="EditBookmarksDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>480</width>
|
||||
<height>320</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Edit Bookmarks</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QTreeWidget" name="treeWidget">
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::InternalMove</enum>
|
||||
</property>
|
||||
<property name="defaultDropAction">
|
||||
<enum>Qt::MoveAction</enum>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="itemsExpandable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerDefaultSectionSize">
|
||||
<number>100</number>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Location</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="addItem">
|
||||
<property name="text">
|
||||
<string>&Add Item</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeItem">
|
||||
<property name="text">
|
||||
<string>&Remove Item</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-remove"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Use drag and drop to reorder the items</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>EditBookmarksDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>EditBookmarksDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "editbookmarksdialog.h"
|
||||
#include "ui_edit-bookmarks.h"
|
||||
#include <QByteArray>
|
||||
#include <QUrl>
|
||||
#include <QSaveFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
EditBookmarksDialog::EditBookmarksDialog(FmBookmarks* bookmarks, QWidget* parent, Qt::WindowFlags f):
|
||||
QDialog(parent, f),
|
||||
ui(new Ui::EditBookmarksDialog()),
|
||||
bookmarks_(FM_BOOKMARKS(g_object_ref(bookmarks))) {
|
||||
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose); // auto delete on close
|
||||
|
||||
// load bookmarks
|
||||
GList* allBookmarks = fm_bookmarks_get_all(bookmarks_);
|
||||
for(GList* l = allBookmarks; l; l = l->next) {
|
||||
FmBookmarkItem* bookmark = reinterpret_cast<FmBookmarkItem*>(l->data);
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem();
|
||||
char* path_str = fm_path_display_name(bookmark->path, false);
|
||||
item->setData(0, Qt::DisplayRole, QString::fromUtf8(bookmark->name));
|
||||
item->setData(1, Qt::DisplayRole, QString::fromUtf8(path_str));
|
||||
item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEditable|Qt::ItemIsDragEnabled|Qt::ItemIsEnabled);
|
||||
g_free(path_str);
|
||||
ui->treeWidget->addTopLevelItem(item);
|
||||
}
|
||||
g_list_free_full(allBookmarks, (GDestroyNotify)fm_bookmark_item_unref);
|
||||
|
||||
connect(ui->addItem, &QPushButton::clicked, this, &EditBookmarksDialog::onAddItem);
|
||||
connect(ui->removeItem, &QPushButton::clicked, this, &EditBookmarksDialog::onRemoveItem);
|
||||
}
|
||||
|
||||
EditBookmarksDialog::~EditBookmarksDialog() {
|
||||
g_object_unref(bookmarks_);
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void EditBookmarksDialog::accept() {
|
||||
// save bookmarks
|
||||
// it's easier to recreate the whole bookmark file than
|
||||
// to manipulate FmBookmarks object. So here we generate the file directly.
|
||||
// FIXME: maybe in the future we should add a libfm API to easily replace all FmBookmarks.
|
||||
// Here we use gtk+ 3.0 bookmarks rather than the gtk+ 2.0 one.
|
||||
// Since gtk+ 2.24.12, gtk+2 reads gtk+3 bookmarks file if it exists.
|
||||
// So it's safe to only save gtk+3 bookmarks file.
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||
path += QLatin1String("/gtk-3.0");
|
||||
if(!QDir().mkpath(path))
|
||||
return; // fail to create ~/.config/gtk-3.0 dir
|
||||
path += QLatin1String("/bookmarks");
|
||||
QSaveFile file(path); // use QSaveFile for atomic file operation
|
||||
if(file.open(QIODevice::WriteOnly)){
|
||||
for(int row = 0; ; ++row) {
|
||||
QTreeWidgetItem* item = ui->treeWidget->topLevelItem(row);
|
||||
if(!item)
|
||||
break;
|
||||
QString name = item->data(0, Qt::DisplayRole).toString();
|
||||
QUrl url = QUrl::fromUserInput(item->data(1, Qt::DisplayRole).toString());
|
||||
file.write(url.toEncoded());
|
||||
file.write(" ");
|
||||
file.write(name.toUtf8());
|
||||
file.write("\n");
|
||||
}
|
||||
// FIXME: should we support Qt or KDE specific bookmarks in the future?
|
||||
file.commit();
|
||||
}
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void EditBookmarksDialog::onAddItem() {
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem();
|
||||
item->setData(0, Qt::DisplayRole, tr("New bookmark"));
|
||||
item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEditable|Qt::ItemIsDragEnabled|Qt::ItemIsEnabled);
|
||||
ui->treeWidget->addTopLevelItem(item);
|
||||
ui->treeWidget->editItem(item);
|
||||
}
|
||||
|
||||
void EditBookmarksDialog::onRemoveItem() {
|
||||
QList<QTreeWidgetItem*> sels = ui->treeWidget->selectedItems();
|
||||
Q_FOREACH(QTreeWidgetItem* item, sels) {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_EDITBOOKMARKSDIALOG_H
|
||||
#define FM_EDITBOOKMARKSDIALOG_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QDialog>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Ui {
|
||||
class EditBookmarksDialog;
|
||||
};
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API EditBookmarksDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EditBookmarksDialog(FmBookmarks* bookmarks, QWidget* parent = 0, Qt::WindowFlags f = 0);
|
||||
virtual ~EditBookmarksDialog();
|
||||
|
||||
virtual void accept();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onAddItem();
|
||||
void onRemoveItem();
|
||||
|
||||
private:
|
||||
Ui::EditBookmarksDialog* ui;
|
||||
FmBookmarks* bookmarks_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_EDITBOOKMARKSDIALOG_H
|
@ -0,0 +1,163 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ExecFileDialog</class>
|
||||
<widget class="QDialog" name="ExecFileDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>487</width>
|
||||
<height>58</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Execute file</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="icon"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="msg">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="open">
|
||||
<property name="text">
|
||||
<string>&Open</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-open"/>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="exec">
|
||||
<property name="text">
|
||||
<string>E&xecute</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="system-run"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="execTerm">
|
||||
<property name="text">
|
||||
<string>Execute in &Terminal</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="utilities-terminal"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancel">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="dialog-cancel"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>cancel</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ExecFileDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>341</x>
|
||||
<y>39</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>196</x>
|
||||
<y>28</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>exec</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ExecFileDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>56</x>
|
||||
<y>39</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>196</x>
|
||||
<y>28</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>execTerm</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ExecFileDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>201</x>
|
||||
<y>39</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>196</x>
|
||||
<y>28</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>open</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ExecFileDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>346</x>
|
||||
<y>39</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>250</x>
|
||||
<y>28</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "execfiledialog_p.h"
|
||||
#include "ui_exec-file.h"
|
||||
#include "icontheme.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
ExecFileDialog::ExecFileDialog(FmFileInfo* file, QWidget* parent, Qt::WindowFlags f):
|
||||
QDialog (parent, f),
|
||||
fileInfo_(fm_file_info_ref(file)),
|
||||
result_(FM_FILE_LAUNCHER_EXEC_CANCEL),
|
||||
ui(new Ui::ExecFileDialog()) {
|
||||
|
||||
ui->setupUi(this);
|
||||
// show file icon
|
||||
FmIcon* icon = fm_file_info_get_icon(fileInfo_);
|
||||
ui->icon->setPixmap(IconTheme::icon(icon).pixmap(QSize(48, 48)));
|
||||
|
||||
QString msg;
|
||||
if(fm_file_info_is_text(file)) {
|
||||
msg = tr("This text file '%1' seems to be an executable script.\nWhat do you want to do with it?")
|
||||
.arg(QString::fromUtf8(fm_file_info_get_disp_name(file)));
|
||||
ui->execTerm->setDefault(true);
|
||||
}
|
||||
else {
|
||||
msg= tr("This file '%1' is executable. Do you want to execute it?")
|
||||
.arg(QString::fromUtf8(fm_file_info_get_disp_name(file)));
|
||||
ui->exec->setDefault(true);
|
||||
ui->open->hide();
|
||||
}
|
||||
ui->msg->setText(msg);
|
||||
}
|
||||
|
||||
ExecFileDialog::~ExecFileDialog() {
|
||||
delete ui;
|
||||
if(fileInfo_)
|
||||
fm_file_info_unref(fileInfo_);
|
||||
}
|
||||
|
||||
void ExecFileDialog::accept() {
|
||||
QObject* _sender = sender();
|
||||
if(_sender == ui->exec)
|
||||
result_ = FM_FILE_LAUNCHER_EXEC;
|
||||
else if(_sender == ui->execTerm)
|
||||
result_ = FM_FILE_LAUNCHER_EXEC_IN_TERMINAL;
|
||||
else if(_sender == ui->open)
|
||||
result_ = FM_FILE_LAUNCHER_EXEC_OPEN;
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_EXECFILEDIALOG_H
|
||||
#define FM_EXECFILEDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Ui {
|
||||
class ExecFileDialog;
|
||||
}
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class ExecFileDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
~ExecFileDialog();
|
||||
ExecFileDialog(FmFileInfo* fileInfo, QWidget* parent = 0, Qt::WindowFlags f = 0);
|
||||
|
||||
FmFileLauncherExecAction result() {
|
||||
return result_;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void accept();
|
||||
|
||||
private:
|
||||
Ui::ExecFileDialog* ui;
|
||||
FmFileInfo* fileInfo_;
|
||||
FmFileLauncherExecAction result_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_EXECFILEDIALOG_H
|
@ -0,0 +1,171 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FileOperationDialog</class>
|
||||
<widget class="QDialog" name="FileOperationDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>450</width>
|
||||
<height>246</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="message">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="destLabel">
|
||||
<property name="text">
|
||||
<string>Destination:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="dest">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Processing:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="curFile">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preparing...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Progress</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Time remaining:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="timeRemaining">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QListWidget" name="sourceFiles">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>FileOperationDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>FileOperationDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -0,0 +1,736 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FilePropsDialog</class>
|
||||
<widget class="QDialog" name="FilePropsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>424</width>
|
||||
<height>456</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>File Properties</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset theme="document-properties">
|
||||
<normaloff/>
|
||||
</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="generalPage">
|
||||
<attribute name="title">
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<property name="horizontalSpacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="iconButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="unknown">
|
||||
<normaloff/>
|
||||
</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="fileName"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Location:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="location">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>File type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="fileType">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Mime type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="mimeType">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>File size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLabel" name="fileSize">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>On-disk size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLabel" name="onDiskSize">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Last modified:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QLabel" name="lastModified">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="targetLabel">
|
||||
<property name="text">
|
||||
<string>Link target:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="target">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="openWithLabel">
|
||||
<property name="text">
|
||||
<string>Open With:</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="Fm::AppChooserComboBox" name="openWith">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Last accessed:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QLabel" name="lastAccessed">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="permissionsPage">
|
||||
<attribute name="title">
|
||||
<string>Permissions</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Ownership</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="horizontalSpacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="owner"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="ownerGroup"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Group:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Owner:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Access Control</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page">
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="ownerLabel">
|
||||
<property name="text">
|
||||
<string>Owner:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="ownerPerm">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="groupLabel">
|
||||
<property name="text">
|
||||
<string>Group:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="groupPerm">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="otherLabel">
|
||||
<property name="text">
|
||||
<string>Other:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="otherPerm">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="executable">
|
||||
<property name="text">
|
||||
<string>Make the file executable</string>
|
||||
</property>
|
||||
<property name="tristate">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_2">
|
||||
<layout class="QGridLayout" name="gridLayout_2" columnstretch="1,0,0">
|
||||
<item row="0" column="0" rowspan="3">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Owner:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="checkBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Read</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QCheckBox" name="checkBox_5">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Write</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QCheckBox" name="checkBox_8">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Execute</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Group:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_4">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Read</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QCheckBox" name="checkBox_9">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Write</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QCheckBox" name="checkBox_3">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Execute</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Other:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_7">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Read</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QCheckBox" name="checkBox_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Write</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QCheckBox" name="checkBox_6">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Execute</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QCheckBox" name="checkBox_10">
|
||||
<property name="text">
|
||||
<string>Sticky</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QCheckBox" name="checkBox_11">
|
||||
<property name="text">
|
||||
<string>SetUID</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QCheckBox" name="checkBox_12">
|
||||
<property name="text">
|
||||
<string>SetGID</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="3">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Advanced Mode</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Fm::AppChooserComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>appchoosercombobox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>FilePropsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>FilePropsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "filelauncher.h"
|
||||
#include "applaunchcontext.h"
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
#include "execfiledialog_p.h"
|
||||
#include "appchooserdialog.h"
|
||||
#include "utilities.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FmFileLauncher FileLauncher::funcs = {
|
||||
FileLauncher::_getApp,
|
||||
/* gboolean (*before_open)(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data); */
|
||||
(FmLaunchFolderFunc)FileLauncher::_openFolder,
|
||||
FileLauncher::_execFile,
|
||||
FileLauncher::_error,
|
||||
FileLauncher::_ask
|
||||
};
|
||||
|
||||
FileLauncher::FileLauncher():
|
||||
quickExec_(false) {
|
||||
}
|
||||
|
||||
FileLauncher::~FileLauncher() {
|
||||
}
|
||||
|
||||
//static
|
||||
bool FileLauncher::launchFiles(QWidget* parent, GList* file_infos) {
|
||||
FmAppLaunchContext* context = fm_app_launch_context_new_for_widget(parent);
|
||||
bool ret = fm_launch_files(G_APP_LAUNCH_CONTEXT(context), file_infos, &funcs, this);
|
||||
g_object_unref(context);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileLauncher::launchPaths(QWidget* parent, GList* paths) {
|
||||
FmAppLaunchContext* context = fm_app_launch_context_new_for_widget(parent);
|
||||
bool ret = fm_launch_paths(G_APP_LAUNCH_CONTEXT(context), paths, &funcs, this);
|
||||
g_object_unref(context);
|
||||
return ret;
|
||||
}
|
||||
|
||||
GAppInfo* FileLauncher::getApp(GList* file_infos, FmMimeType* mime_type, GError** err) {
|
||||
AppChooserDialog dlg(NULL);
|
||||
if(mime_type)
|
||||
dlg.setMimeType(mime_type);
|
||||
else
|
||||
dlg.setCanSetDefault(false);
|
||||
// FIXME: show error properly?
|
||||
if(execModelessDialog(&dlg) == QDialog::Accepted) {
|
||||
return dlg.selectedApp();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool FileLauncher::openFolder(GAppLaunchContext* ctx, GList* folder_infos, GError** err) {
|
||||
for(GList* l = folder_infos; l; l = l->next) {
|
||||
FmFileInfo* fi = FM_FILE_INFO(l->data);
|
||||
qDebug() << " folder:" << QString::fromUtf8(fm_file_info_get_disp_name(fi));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FmFileLauncherExecAction FileLauncher::execFile(FmFileInfo* file) {
|
||||
if (quickExec_) {
|
||||
/* SF bug#838: open terminal for each script may be just a waste.
|
||||
User should open a terminal and start the script there
|
||||
in case if user wants to see the script output anyway.
|
||||
if (fm_file_info_is_text(file))
|
||||
return FM_FILE_LAUNCHER_EXEC_IN_TERMINAL; */
|
||||
return FM_FILE_LAUNCHER_EXEC;
|
||||
}
|
||||
|
||||
FmFileLauncherExecAction res = FM_FILE_LAUNCHER_EXEC_CANCEL;
|
||||
ExecFileDialog dlg(file);
|
||||
if(execModelessDialog(&dlg) == QDialog::Accepted) {
|
||||
res = dlg.result();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int FileLauncher::ask(const char* msg, char* const* btn_labels, int default_btn) {
|
||||
/* FIXME: set default button properly */
|
||||
// return fm_askv(data->parent, NULL, msg, btn_labels);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool FileLauncher::error(GAppLaunchContext* ctx, GError* err, FmPath* path) {
|
||||
/* ask for mount if trying to launch unmounted path */
|
||||
if(err->domain == G_IO_ERROR) {
|
||||
if(path && err->code == G_IO_ERROR_NOT_MOUNTED) {
|
||||
//if(fm_mount_path(data->parent, path, TRUE))
|
||||
// return FALSE; /* ask to retry */
|
||||
}
|
||||
else if(err->code == G_IO_ERROR_FAILED_HANDLED)
|
||||
return true; /* don't show error message */
|
||||
}
|
||||
QMessageBox dlg(QMessageBox::Critical, QObject::tr("Error"), QString::fromUtf8(err->message), QMessageBox::Ok);
|
||||
execModelessDialog(&dlg);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FILELAUNCHER_H
|
||||
#define FM_FILELAUNCHER_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QWidget>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FileLauncher {
|
||||
public:
|
||||
FileLauncher();
|
||||
virtual ~FileLauncher();
|
||||
|
||||
bool launchFiles(QWidget* parent, FmFileInfoList* file_infos) {
|
||||
GList* fileList = fm_file_info_list_peek_head_link(file_infos);
|
||||
return launchFiles(parent, fileList);
|
||||
}
|
||||
bool launchPaths(QWidget* parent, FmPathList* paths) {
|
||||
GList* pathList = fm_path_list_peek_head_link(paths);
|
||||
return launchPaths(parent, pathList);
|
||||
}
|
||||
|
||||
bool launchFiles(QWidget* parent, GList* file_infos);
|
||||
bool launchPaths(QWidget* parent, GList* paths);
|
||||
|
||||
bool quickExec() const {
|
||||
return quickExec_;
|
||||
}
|
||||
|
||||
void setQuickExec(bool value) {
|
||||
quickExec_ = value;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
virtual GAppInfo* getApp(GList* file_infos, FmMimeType* mime_type, GError** err);
|
||||
virtual bool openFolder(GAppLaunchContext* ctx, GList* folder_infos, GError** err);
|
||||
virtual FmFileLauncherExecAction execFile(FmFileInfo* file);
|
||||
virtual bool error(GAppLaunchContext* ctx, GError* err, FmPath* path);
|
||||
virtual int ask(const char* msg, char* const* btn_labels, int default_btn);
|
||||
|
||||
private:
|
||||
static GAppInfo* _getApp(GList* file_infos, FmMimeType* mime_type, gpointer user_data, GError** err) {
|
||||
return reinterpret_cast<FileLauncher*>(user_data)->getApp(file_infos, mime_type, err);
|
||||
}
|
||||
static gboolean _openFolder(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data, GError** err) {
|
||||
return reinterpret_cast<FileLauncher*>(user_data)->openFolder(ctx, folder_infos, err);
|
||||
}
|
||||
static FmFileLauncherExecAction _execFile(FmFileInfo* file, gpointer user_data) {
|
||||
return reinterpret_cast<FileLauncher*>(user_data)->execFile(file);
|
||||
}
|
||||
static gboolean _error(GAppLaunchContext* ctx, GError* err, FmPath* file, gpointer user_data) {
|
||||
return reinterpret_cast<FileLauncher*>(user_data)->error(ctx, err, file);
|
||||
}
|
||||
static int _ask(const char* msg, char* const* btn_labels, int default_btn, gpointer user_data) {
|
||||
return reinterpret_cast<FileLauncher*>(user_data)->ask(msg, btn_labels, default_btn);
|
||||
}
|
||||
|
||||
private:
|
||||
static FmFileLauncher funcs;
|
||||
bool quickExec_; // Don't ask options on launch executable file
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FILELAUNCHER_H
|
@ -0,0 +1,397 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "filemenu.h"
|
||||
#include "createnewmenu.h"
|
||||
#include "icontheme.h"
|
||||
#include "filepropsdialog.h"
|
||||
#include "utilities.h"
|
||||
#include "fileoperation.h"
|
||||
#include "filelauncher.h"
|
||||
#include "appchooserdialog.h"
|
||||
#ifdef CUSTOM_ACTIONS
|
||||
#include <libfm/fm-actions.h>
|
||||
#endif
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
#include "filemenu_p.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileMenu::FileMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd, QWidget* parent):
|
||||
QMenu(parent),
|
||||
fileLauncher_(NULL) {
|
||||
createMenu(files, info, cwd);
|
||||
}
|
||||
|
||||
FileMenu::FileMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd, const QString& title, QWidget* parent):
|
||||
QMenu(title, parent),
|
||||
fileLauncher_(NULL),
|
||||
unTrashAction_(NULL) {
|
||||
createMenu(files, info, cwd);
|
||||
}
|
||||
|
||||
FileMenu::~FileMenu() {
|
||||
if(files_)
|
||||
fm_file_info_list_unref(files_);
|
||||
if(info_)
|
||||
fm_file_info_unref(info_);
|
||||
if(cwd_)
|
||||
fm_path_unref(cwd_);
|
||||
}
|
||||
|
||||
void FileMenu::createMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd) {
|
||||
useTrash_ = true;
|
||||
confirmDelete_ = true;
|
||||
confirmTrash_ = false; // Confirm before moving files into "trash can"
|
||||
|
||||
openAction_ = NULL;
|
||||
openWithMenuAction_ = NULL;
|
||||
openWithAction_ = NULL;
|
||||
separator1_ = NULL;
|
||||
cutAction_ = NULL;
|
||||
copyAction_ = NULL;
|
||||
pasteAction_ = NULL;
|
||||
deleteAction_ = NULL;
|
||||
unTrashAction_ = NULL;
|
||||
renameAction_ = NULL;
|
||||
separator2_ = NULL;
|
||||
propertiesAction_ = NULL;
|
||||
|
||||
files_ = fm_file_info_list_ref(files);
|
||||
info_ = info ? fm_file_info_ref(info) : NULL;
|
||||
cwd_ = cwd ? fm_path_ref(cwd) : NULL;
|
||||
|
||||
FmFileInfo* first = fm_file_info_list_peek_head(files);
|
||||
FmMimeType* mime_type = fm_file_info_get_mime_type(first);
|
||||
FmPath* path = fm_file_info_get_path(first);
|
||||
// check if the files are of the same type
|
||||
sameType_ = fm_file_info_list_is_same_type(files);
|
||||
// check if the files are on the same filesystem
|
||||
sameFilesystem_ = fm_file_info_list_is_same_fs(files);
|
||||
// check if the files are all virtual
|
||||
allVirtual_ = sameFilesystem_ && fm_path_is_virtual(path);
|
||||
// check if the files are all in the trash can
|
||||
allTrash_ = sameFilesystem_ && fm_path_is_trash(path);
|
||||
|
||||
openAction_ = new QAction(QIcon::fromTheme("document-open"), tr("Open"), this);
|
||||
connect(openAction_ , &QAction::triggered, this, &FileMenu::onOpenTriggered);
|
||||
addAction(openAction_);
|
||||
|
||||
openWithMenuAction_ = new QAction(tr("Open With..."), this);
|
||||
addAction(openWithMenuAction_);
|
||||
// create the "Open with..." sub menu
|
||||
QMenu* menu = new QMenu();
|
||||
openWithMenuAction_->setMenu(menu);
|
||||
|
||||
if(sameType_) { /* add specific menu items for this mime type */
|
||||
if(mime_type && !allVirtual_) { /* the file has a valid mime-type and its not virtual */
|
||||
GList* apps = g_app_info_get_all_for_type(fm_mime_type_get_type(mime_type));
|
||||
GList* l;
|
||||
for(l=apps;l;l=l->next) {
|
||||
GAppInfo* app = G_APP_INFO(l->data);
|
||||
|
||||
// check if the command really exists
|
||||
gchar * program_path = g_find_program_in_path(g_app_info_get_executable(app));
|
||||
if (!program_path)
|
||||
continue;
|
||||
g_free(program_path);
|
||||
|
||||
// create a QAction for the application.
|
||||
AppInfoAction* action = new AppInfoAction(app);
|
||||
connect(action, &QAction::triggered, this, &FileMenu::onApplicationTriggered);
|
||||
menu->addAction(action);
|
||||
}
|
||||
g_list_free(apps); /* don't unref GAppInfos now */
|
||||
}
|
||||
}
|
||||
menu->addSeparator();
|
||||
openWithAction_ = new QAction(tr("Other Applications"), this);
|
||||
connect(openWithAction_ , &QAction::triggered, this, &FileMenu::onOpenWithTriggered);
|
||||
menu->addAction(openWithAction_);
|
||||
|
||||
separator1_ = addSeparator();
|
||||
|
||||
createAction_ = new QAction(tr("Create &New"), this);
|
||||
FmPath* dirPath = fm_file_info_list_get_length(files) == 1 && fm_file_info_is_dir(first)
|
||||
? path : cwd_;
|
||||
createAction_->setMenu(new CreateNewMenu(NULL, dirPath, this));
|
||||
addAction(createAction_);
|
||||
|
||||
separator2_ = addSeparator();
|
||||
|
||||
if(allTrash_) { // all selected files are in trash:///
|
||||
bool can_restore = true;
|
||||
/* only immediate children of trash:/// can be restored. */
|
||||
for(GList* l = fm_file_info_list_peek_head_link(files_); l; l=l->next) {
|
||||
FmPath *trash_path = fm_file_info_get_path(FM_FILE_INFO(l->data));
|
||||
if(!fm_path_get_parent(trash_path) ||
|
||||
!fm_path_is_trash_root(fm_path_get_parent(trash_path))) {
|
||||
can_restore = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(can_restore) {
|
||||
unTrashAction_ = new QAction(tr("&Restore"), this);
|
||||
connect(unTrashAction_, &QAction::triggered, this, &FileMenu::onUnTrashTriggered);
|
||||
addAction(unTrashAction_);
|
||||
}
|
||||
}
|
||||
else { // ordinary files
|
||||
cutAction_ = new QAction(QIcon::fromTheme("edit-cut"), tr("Cut"), this);
|
||||
connect(cutAction_, &QAction::triggered, this, &FileMenu::onCutTriggered);
|
||||
addAction(cutAction_);
|
||||
|
||||
copyAction_ = new QAction(QIcon::fromTheme("edit-copy"), tr("Copy"), this);
|
||||
connect(copyAction_, &QAction::triggered, this, &FileMenu::onCopyTriggered);
|
||||
addAction(copyAction_);
|
||||
|
||||
pasteAction_ = new QAction(QIcon::fromTheme("edit-paste"), tr("Paste"), this);
|
||||
connect(pasteAction_, &QAction::triggered, this, &FileMenu::onPasteTriggered);
|
||||
addAction(pasteAction_);
|
||||
|
||||
deleteAction_ = new QAction(QIcon::fromTheme("user-trash"), tr("&Move to Trash"), this);
|
||||
connect(deleteAction_, &QAction::triggered, this, &FileMenu::onDeleteTriggered);
|
||||
addAction(deleteAction_);
|
||||
|
||||
renameAction_ = new QAction(tr("Rename"), this);
|
||||
connect(renameAction_, &QAction::triggered, this, &FileMenu::onRenameTriggered);
|
||||
addAction(renameAction_);
|
||||
}
|
||||
|
||||
#ifdef CUSTOM_ACTIONS
|
||||
// DES-EMA custom actions integration
|
||||
GList* files_list = fm_file_info_list_peek_head_link(files);
|
||||
GList* items = fm_get_actions_for_files(files_list);
|
||||
if(items) {
|
||||
GList* l;
|
||||
for(l=items; l; l=l->next) {
|
||||
FmFileActionItem* item = FM_FILE_ACTION_ITEM(l->data);
|
||||
addCustomActionItem(this, item);
|
||||
}
|
||||
}
|
||||
g_list_foreach(items, (GFunc)fm_file_action_item_unref, NULL);
|
||||
g_list_free(items);
|
||||
#endif
|
||||
// archiver integration
|
||||
// FIXME: we need to modify upstream libfm to include some Qt-based archiver programs.
|
||||
if(!allVirtual_) {
|
||||
if(sameType_) {
|
||||
FmArchiver* archiver = fm_archiver_get_default();
|
||||
if(archiver) {
|
||||
if(fm_archiver_is_mime_type_supported(archiver, fm_mime_type_get_type(mime_type))) {
|
||||
if(cwd_ && archiver->extract_to_cmd) {
|
||||
QAction* action = new QAction(tr("Extract to..."), this);
|
||||
connect(action, &QAction::triggered, this, &FileMenu::onExtract);
|
||||
addAction(action);
|
||||
}
|
||||
if(archiver->extract_cmd) {
|
||||
QAction* action = new QAction(tr("Extract Here"), this);
|
||||
connect(action, &QAction::triggered, this, &FileMenu::onExtractHere);
|
||||
addAction(action);
|
||||
}
|
||||
}
|
||||
else {
|
||||
QAction* action = new QAction(tr("Compress"), this);
|
||||
connect(action, &QAction::triggered, this, &FileMenu::onCompress);
|
||||
addAction(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
separator3_ = addSeparator();
|
||||
|
||||
propertiesAction_ = new QAction(QIcon::fromTheme("document-properties"), tr("Properties"), this);
|
||||
connect(propertiesAction_, &QAction::triggered, this, &FileMenu::onFilePropertiesTriggered);
|
||||
addAction(propertiesAction_);
|
||||
}
|
||||
|
||||
#ifdef CUSTOM_ACTIONS
|
||||
void FileMenu::addCustomActionItem(QMenu* menu, FmFileActionItem* item) {
|
||||
if(!item) { // separator
|
||||
addSeparator();
|
||||
return;
|
||||
}
|
||||
|
||||
// this action is not for context menu
|
||||
if(fm_file_action_item_is_action(item) && !(fm_file_action_item_get_target(item) & FM_FILE_ACTION_TARGET_CONTEXT))
|
||||
return;
|
||||
|
||||
CustomAction* action = new CustomAction(item, menu);
|
||||
menu->addAction(action);
|
||||
if(fm_file_action_item_is_menu(item)) {
|
||||
GList* subitems = fm_file_action_item_get_sub_items(item);
|
||||
for(GList* l = subitems; l; l = l->next) {
|
||||
FmFileActionItem* subitem = FM_FILE_ACTION_ITEM(l->data);
|
||||
QMenu* submenu = new QMenu(menu);
|
||||
addCustomActionItem(submenu, subitem);
|
||||
action->setMenu(submenu);
|
||||
}
|
||||
}
|
||||
else if(fm_file_action_item_is_action(item)) {
|
||||
connect(action, &QAction::triggered, this, &FileMenu::onCustomActionTrigerred);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void FileMenu::onOpenTriggered() {
|
||||
if(fileLauncher_) {
|
||||
fileLauncher_->launchFiles(NULL, files_);
|
||||
}
|
||||
else { // use the default launcher
|
||||
Fm::FileLauncher launcher;
|
||||
launcher.launchFiles(NULL, files_);
|
||||
}
|
||||
}
|
||||
|
||||
void FileMenu::onOpenWithTriggered() {
|
||||
AppChooserDialog dlg(NULL);
|
||||
if(sameType_) {
|
||||
dlg.setMimeType(fm_file_info_get_mime_type(info_));
|
||||
}
|
||||
else { // we can only set the selected app as default if all files are of the same type
|
||||
dlg.setCanSetDefault(false);
|
||||
}
|
||||
|
||||
if(execModelessDialog(&dlg) == QDialog::Accepted) {
|
||||
GAppInfo* app = dlg.selectedApp();
|
||||
if(app) {
|
||||
openFilesWithApp(app);
|
||||
g_object_unref(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileMenu::openFilesWithApp(GAppInfo* app) {
|
||||
FmPathList* paths = fm_path_list_new_from_file_info_list(files_);
|
||||
GList* uris = NULL;
|
||||
for(GList* l = fm_path_list_peek_head_link(paths); l; l = l->next) {
|
||||
FmPath* path = FM_PATH(l->data);
|
||||
char* uri = fm_path_to_uri(path);
|
||||
uris = g_list_prepend(uris, uri);
|
||||
}
|
||||
fm_path_list_unref(paths);
|
||||
fm_app_info_launch_uris(app, uris, NULL, NULL);
|
||||
g_list_foreach(uris, (GFunc)g_free, NULL);
|
||||
g_list_free(uris);
|
||||
}
|
||||
|
||||
void FileMenu::onApplicationTriggered() {
|
||||
AppInfoAction* action = static_cast<AppInfoAction*>(sender());
|
||||
openFilesWithApp(action->appInfo());
|
||||
}
|
||||
|
||||
#ifdef CUSTOM_ACTIONS
|
||||
void FileMenu::onCustomActionTrigerred() {
|
||||
CustomAction* action = static_cast<CustomAction*>(sender());
|
||||
FmFileActionItem* item = action->item();
|
||||
|
||||
GList* files = fm_file_info_list_peek_head_link(files_);
|
||||
char* output = NULL;
|
||||
/* g_debug("item: %s is activated, id:%s", fm_file_action_item_get_name(item),
|
||||
fm_file_action_item_get_id(item)); */
|
||||
fm_file_action_item_launch(item, NULL, files, &output);
|
||||
if(output) {
|
||||
QMessageBox::information(this, tr("Output"), QString::fromUtf8(output));
|
||||
g_free(output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void FileMenu::onFilePropertiesTriggered() {
|
||||
FilePropsDialog::showForFiles(files_);
|
||||
}
|
||||
|
||||
void FileMenu::onCopyTriggered() {
|
||||
FmPathList* paths = fm_path_list_new_from_file_info_list(files_);
|
||||
Fm::copyFilesToClipboard(paths);
|
||||
fm_path_list_unref(paths);
|
||||
}
|
||||
|
||||
void FileMenu::onCutTriggered() {
|
||||
FmPathList* paths = fm_path_list_new_from_file_info_list(files_);
|
||||
Fm::cutFilesToClipboard(paths);
|
||||
fm_path_list_unref(paths);
|
||||
}
|
||||
|
||||
void FileMenu::onDeleteTriggered() {
|
||||
FmPathList* paths = fm_path_list_new_from_file_info_list(files_);
|
||||
if(useTrash_)
|
||||
FileOperation::trashFiles(paths, confirmTrash_);
|
||||
else
|
||||
FileOperation::deleteFiles(paths, confirmDelete_);
|
||||
fm_path_list_unref(paths);
|
||||
}
|
||||
|
||||
void FileMenu::onUnTrashTriggered() {
|
||||
FmPathList* paths = fm_path_list_new_from_file_info_list(files_);
|
||||
FileOperation::unTrashFiles(paths);
|
||||
}
|
||||
|
||||
void FileMenu::onPasteTriggered() {
|
||||
Fm::pasteFilesFromClipboard(cwd_);
|
||||
}
|
||||
|
||||
void FileMenu::onRenameTriggered() {
|
||||
for(GList* l = fm_file_info_list_peek_head_link(files_); l; l = l->next) {
|
||||
FmFileInfo* info = FM_FILE_INFO(l->data);
|
||||
Fm::renameFile(info, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void FileMenu::setUseTrash(bool trash) {
|
||||
if(useTrash_ != trash) {
|
||||
useTrash_ = trash;
|
||||
if(deleteAction_) {
|
||||
deleteAction_->setText(useTrash_ ? tr("&Move to Trash") : tr("&Delete"));
|
||||
deleteAction_->setIcon(useTrash_ ? QIcon::fromTheme("user-trash") : QIcon::fromTheme("edit-delete"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileMenu::onCompress() {
|
||||
FmArchiver* archiver = fm_archiver_get_default();
|
||||
if(archiver) {
|
||||
FmPathList* paths = fm_path_list_new_from_file_info_list(files_);
|
||||
fm_archiver_create_archive(archiver, NULL, paths);
|
||||
fm_path_list_unref(paths);
|
||||
}
|
||||
}
|
||||
|
||||
void FileMenu::onExtract() {
|
||||
FmArchiver* archiver = fm_archiver_get_default();
|
||||
if(archiver) {
|
||||
FmPathList* paths = fm_path_list_new_from_file_info_list(files_);
|
||||
fm_archiver_extract_archives(archiver, NULL, paths);
|
||||
fm_path_list_unref(paths);
|
||||
}
|
||||
}
|
||||
|
||||
void FileMenu::onExtractHere() {
|
||||
FmArchiver* archiver = fm_archiver_get_default();
|
||||
if(archiver) {
|
||||
FmPathList* paths = fm_path_list_new_from_file_info_list(files_);
|
||||
fm_archiver_extract_archives_to(archiver, NULL, paths, cwd_);
|
||||
fm_path_list_unref(paths);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FILEMENU_H
|
||||
#define FM_FILEMENU_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QMenu>
|
||||
#include <qabstractitemmodel.h>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
class QAction;
|
||||
|
||||
struct _FmFileActionItem;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class FileLauncher;
|
||||
|
||||
class LIBFM_QT_API FileMenu : public QMenu {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FileMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd, QWidget* parent = 0);
|
||||
explicit FileMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd, const QString& title, QWidget* parent = 0);
|
||||
~FileMenu();
|
||||
|
||||
bool useTrash() {
|
||||
return useTrash_;
|
||||
}
|
||||
|
||||
void setUseTrash(bool trash);
|
||||
|
||||
bool confirmDelete() {
|
||||
return confirmDelete_;
|
||||
}
|
||||
|
||||
void setConfirmDelete(bool confirm) {
|
||||
confirmDelete_ = confirm;
|
||||
}
|
||||
|
||||
QAction* openAction() {
|
||||
return openAction_;
|
||||
}
|
||||
|
||||
QAction* openWithMenuAction() {
|
||||
return openWithMenuAction_;
|
||||
}
|
||||
|
||||
QAction* openWithAction() {
|
||||
return openWithAction_;
|
||||
}
|
||||
|
||||
QAction* separator1() {
|
||||
return separator1_;
|
||||
}
|
||||
|
||||
QAction* createAction() {
|
||||
return createAction_;
|
||||
}
|
||||
|
||||
QAction* separator2() {
|
||||
return separator2_;
|
||||
}
|
||||
|
||||
QAction* cutAction() {
|
||||
return cutAction_;
|
||||
}
|
||||
|
||||
QAction* copyAction() {
|
||||
return copyAction_;
|
||||
}
|
||||
|
||||
QAction* pasteAction() {
|
||||
return pasteAction_;
|
||||
}
|
||||
|
||||
QAction* deleteAction() {
|
||||
return deleteAction_;
|
||||
}
|
||||
|
||||
QAction* unTrashAction() {
|
||||
return unTrashAction_;
|
||||
}
|
||||
|
||||
QAction* renameAction() {
|
||||
return renameAction_;
|
||||
}
|
||||
|
||||
QAction* separator3() {
|
||||
return separator3_;
|
||||
}
|
||||
|
||||
QAction* propertiesAction() {
|
||||
return propertiesAction_;
|
||||
}
|
||||
|
||||
FmFileInfoList* files() {
|
||||
return files_;
|
||||
}
|
||||
|
||||
FmFileInfo* firstFile() {
|
||||
return info_;
|
||||
}
|
||||
|
||||
FmPath* cwd() {
|
||||
return cwd_;
|
||||
}
|
||||
|
||||
void setFileLauncher(FileLauncher* launcher) {
|
||||
fileLauncher_ = launcher;
|
||||
}
|
||||
|
||||
FileLauncher* fileLauncher() {
|
||||
return fileLauncher_;
|
||||
}
|
||||
|
||||
bool sameType() const {
|
||||
return sameType_;
|
||||
}
|
||||
|
||||
bool sameFilesystem() const {
|
||||
return sameFilesystem_;
|
||||
}
|
||||
|
||||
bool allVirtual() const {
|
||||
return allVirtual_;
|
||||
}
|
||||
|
||||
bool allTrash() const {
|
||||
return allTrash_;
|
||||
}
|
||||
|
||||
bool confirmTrash() const {
|
||||
return confirmTrash_;
|
||||
}
|
||||
|
||||
void setConfirmTrash(bool value) {
|
||||
confirmTrash_ = value;
|
||||
}
|
||||
|
||||
protected:
|
||||
void createMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd);
|
||||
#ifdef CUSTOM_ACTIONS
|
||||
void addCustomActionItem(QMenu* menu, struct _FmFileActionItem* item);
|
||||
#endif
|
||||
void openFilesWithApp(GAppInfo* app);
|
||||
|
||||
protected Q_SLOTS:
|
||||
void onOpenTriggered();
|
||||
void onOpenWithTriggered();
|
||||
void onFilePropertiesTriggered();
|
||||
void onApplicationTriggered();
|
||||
#ifdef CUSTOM_ACTIONS
|
||||
void onCustomActionTrigerred();
|
||||
#endif
|
||||
void onCompress();
|
||||
void onExtract();
|
||||
void onExtractHere();
|
||||
|
||||
void onCutTriggered();
|
||||
void onCopyTriggered();
|
||||
void onPasteTriggered();
|
||||
void onRenameTriggered();
|
||||
void onDeleteTriggered();
|
||||
void onUnTrashTriggered();
|
||||
|
||||
private:
|
||||
FmFileInfoList* files_;
|
||||
FmFileInfo* info_;
|
||||
FmPath* cwd_;
|
||||
bool useTrash_;
|
||||
bool confirmDelete_;
|
||||
bool confirmTrash_; // Confirm before moving files into "trash can"
|
||||
|
||||
bool sameType_;
|
||||
bool sameFilesystem_;
|
||||
bool allVirtual_;
|
||||
bool allTrash_;
|
||||
|
||||
QAction* openAction_;
|
||||
QAction* openWithMenuAction_;
|
||||
QAction* openWithAction_;
|
||||
QAction* separator1_;
|
||||
QAction* createAction_;
|
||||
QAction* separator2_;
|
||||
QAction* cutAction_;
|
||||
QAction* copyAction_;
|
||||
QAction* pasteAction_;
|
||||
QAction* deleteAction_;
|
||||
QAction* unTrashAction_;
|
||||
QAction* renameAction_;
|
||||
QAction* separator3_;
|
||||
QAction* propertiesAction_;
|
||||
|
||||
FileLauncher* fileLauncher_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FILEMENU_H
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_FILEMENU_P_H
|
||||
#define FM_FILEMENU_P_H
|
||||
|
||||
#include "icontheme.h"
|
||||
#ifdef CUSTOM_ACTIONS
|
||||
#include <libfm/fm-actions.h>
|
||||
#endif
|
||||
#include <QDebug>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class AppInfoAction : public QAction {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AppInfoAction(GAppInfo* app, QObject* parent = 0):
|
||||
QAction(QString::fromUtf8(g_app_info_get_name(app)), parent),
|
||||
appInfo_(G_APP_INFO(g_object_ref(app))) {
|
||||
setToolTip(QString::fromUtf8(g_app_info_get_description(app)));
|
||||
GIcon* gicon = g_app_info_get_icon(app);
|
||||
QIcon icon = IconTheme::icon(gicon);
|
||||
setIcon(icon);
|
||||
}
|
||||
|
||||
virtual ~AppInfoAction() {
|
||||
if(appInfo_)
|
||||
g_object_unref(appInfo_);
|
||||
}
|
||||
|
||||
GAppInfo* appInfo() const {
|
||||
return appInfo_;
|
||||
}
|
||||
|
||||
private:
|
||||
GAppInfo* appInfo_;
|
||||
};
|
||||
|
||||
#ifdef CUSTOM_ACTIONS
|
||||
class CustomAction : public QAction {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CustomAction(FmFileActionItem* item, QObject* parent = NULL):
|
||||
QAction(QString::fromUtf8(fm_file_action_item_get_name(item)), parent),
|
||||
item_(reinterpret_cast<FmFileActionItem*>(fm_file_action_item_ref(item))) {
|
||||
const char* icon_name = fm_file_action_item_get_icon(item);
|
||||
if(icon_name)
|
||||
setIcon(QIcon::fromTheme(icon_name));
|
||||
}
|
||||
|
||||
virtual ~CustomAction() {
|
||||
fm_file_action_item_unref(item_);
|
||||
}
|
||||
|
||||
FmFileActionItem* item() {
|
||||
return item_;
|
||||
}
|
||||
|
||||
private:
|
||||
FmFileActionItem* item_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif
|
@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "fileoperation.h"
|
||||
#include "fileoperationdialog.h"
|
||||
#include <QTimer>
|
||||
#include <QElapsedTimer>
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
#define SHOW_DLG_DELAY 1000
|
||||
|
||||
FileOperation::FileOperation(Type type, FmPathList* srcFiles, QObject* parent):
|
||||
QObject(parent),
|
||||
dlg(NULL),
|
||||
destPath(NULL),
|
||||
srcPaths(fm_path_list_ref(srcFiles)),
|
||||
uiTimer(NULL),
|
||||
elapsedTimer_(NULL),
|
||||
lastElapsed_(0),
|
||||
updateRemainingTime_(true),
|
||||
autoDestroy_(true),
|
||||
job_(fm_file_ops_job_new((FmFileOpType)type, srcFiles)) {
|
||||
|
||||
g_signal_connect(job_, "ask", G_CALLBACK(onFileOpsJobAsk), this);
|
||||
g_signal_connect(job_, "ask-rename", G_CALLBACK(onFileOpsJobAskRename), this);
|
||||
g_signal_connect(job_, "error", G_CALLBACK(onFileOpsJobError), this);
|
||||
g_signal_connect(job_, "prepared", G_CALLBACK(onFileOpsJobPrepared), this);
|
||||
g_signal_connect(job_, "cur-file", G_CALLBACK(onFileOpsJobCurFile), this);
|
||||
g_signal_connect(job_, "percent", G_CALLBACK(onFileOpsJobPercent), this);
|
||||
g_signal_connect(job_, "finished", G_CALLBACK(onFileOpsJobFinished), this);
|
||||
g_signal_connect(job_, "cancelled", G_CALLBACK(onFileOpsJobCancelled), this);
|
||||
}
|
||||
|
||||
void FileOperation::disconnectJob() {
|
||||
g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobAsk), this);
|
||||
g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobAskRename), this);
|
||||
g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobError), this);
|
||||
g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobPrepared), this);
|
||||
g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobCurFile), this);
|
||||
g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobPercent), this);
|
||||
g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobFinished), this);
|
||||
g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobCancelled), this);
|
||||
}
|
||||
|
||||
FileOperation::~FileOperation() {
|
||||
if(uiTimer) {
|
||||
uiTimer->stop();
|
||||
delete uiTimer;
|
||||
uiTimer = NULL;
|
||||
}
|
||||
if(elapsedTimer_) {
|
||||
delete elapsedTimer_;
|
||||
elapsedTimer_ = NULL;
|
||||
}
|
||||
|
||||
if(job_) {
|
||||
disconnectJob();
|
||||
g_object_unref(job_);
|
||||
}
|
||||
|
||||
if(srcPaths)
|
||||
fm_path_list_unref(srcPaths);
|
||||
|
||||
if(destPath)
|
||||
fm_path_unref(destPath);
|
||||
}
|
||||
|
||||
bool FileOperation::run() {
|
||||
delete uiTimer;
|
||||
// run the job
|
||||
uiTimer = new QTimer();
|
||||
uiTimer->start(SHOW_DLG_DELAY);
|
||||
connect(uiTimer, &QTimer::timeout, this, &FileOperation::onUiTimeout);
|
||||
|
||||
return fm_job_run_async(FM_JOB(job_));
|
||||
}
|
||||
|
||||
void FileOperation::onUiTimeout() {
|
||||
if(dlg) {
|
||||
dlg->setCurFile(curFile);
|
||||
// estimate remaining time based on past history
|
||||
// FIXME: avoid directly access data member of FmFileOpsJob
|
||||
if(Q_LIKELY(job_->percent > 0 && updateRemainingTime_)) {
|
||||
gint64 remaining = elapsedTime() * ((double(100 - job_->percent) / job_->percent) / 1000);
|
||||
dlg->setRemainingTime(remaining);
|
||||
}
|
||||
// this timeout slot is called every 0.5 second.
|
||||
// by adding this flag, we can update remaining time every 1 second.
|
||||
updateRemainingTime_ = !updateRemainingTime_;
|
||||
}
|
||||
else{
|
||||
showDialog();
|
||||
}
|
||||
}
|
||||
|
||||
void FileOperation::showDialog() {
|
||||
if(!dlg) {
|
||||
dlg = new FileOperationDialog(this);
|
||||
dlg->setSourceFiles(srcPaths);
|
||||
|
||||
if(destPath)
|
||||
dlg->setDestPath(destPath);
|
||||
|
||||
if(curFile.isEmpty()) {
|
||||
dlg->setPrepared();
|
||||
dlg->setCurFile(curFile);
|
||||
}
|
||||
uiTimer->setInterval(500); // change the interval of the timer
|
||||
// now the timer is used to update current file display
|
||||
dlg->show();
|
||||
}
|
||||
}
|
||||
|
||||
gint FileOperation::onFileOpsJobAsk(FmFileOpsJob* job, const char* question, char*const* options, FileOperation* pThis) {
|
||||
pThis->pauseElapsedTimer();
|
||||
pThis->showDialog();
|
||||
int ret = pThis->dlg->ask(QString::fromUtf8(question), options);
|
||||
pThis->resumeElapsedTimer();
|
||||
return ret;
|
||||
}
|
||||
|
||||
gint FileOperation::onFileOpsJobAskRename(FmFileOpsJob* job, FmFileInfo* src, FmFileInfo* dest, char** new_name, FileOperation* pThis) {
|
||||
pThis->pauseElapsedTimer();
|
||||
pThis->showDialog();
|
||||
QString newName;
|
||||
int ret = pThis->dlg->askRename(src, dest, newName);
|
||||
if(!newName.isEmpty()) {
|
||||
*new_name = g_strdup(newName.toUtf8().constData());
|
||||
}
|
||||
pThis->resumeElapsedTimer();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void FileOperation::onFileOpsJobCancelled(FmFileOpsJob* job, FileOperation* pThis) {
|
||||
qDebug("file operation is cancelled!");
|
||||
}
|
||||
|
||||
void FileOperation::onFileOpsJobCurFile(FmFileOpsJob* job, const char* cur_file, FileOperation* pThis) {
|
||||
pThis->curFile = QString::fromUtf8(cur_file);
|
||||
|
||||
// We update the current file name in a timeout slot because drawing a string
|
||||
// in the UI is expansive. Updating the label text too often cause
|
||||
// significant impact on performance.
|
||||
// if(pThis->dlg)
|
||||
// pThis->dlg->setCurFile(pThis->curFile);
|
||||
}
|
||||
|
||||
FmJobErrorAction FileOperation::onFileOpsJobError(FmFileOpsJob* job, GError* err, FmJobErrorSeverity severity, FileOperation* pThis) {
|
||||
pThis->pauseElapsedTimer();
|
||||
pThis->showDialog();
|
||||
FmJobErrorAction act = pThis->dlg->error(err, severity);
|
||||
pThis->resumeElapsedTimer();
|
||||
return act;
|
||||
}
|
||||
|
||||
void FileOperation::onFileOpsJobFinished(FmFileOpsJob* job, FileOperation* pThis) {
|
||||
pThis->handleFinish();
|
||||
}
|
||||
|
||||
void FileOperation::onFileOpsJobPercent(FmFileOpsJob* job, guint percent, FileOperation* pThis) {
|
||||
if(pThis->dlg) {
|
||||
pThis->dlg->setPercent(percent);
|
||||
}
|
||||
}
|
||||
|
||||
void FileOperation::onFileOpsJobPrepared(FmFileOpsJob* job, FileOperation* pThis) {
|
||||
if(!pThis->elapsedTimer_) {
|
||||
pThis->elapsedTimer_ = new QElapsedTimer();
|
||||
pThis->elapsedTimer_->start();
|
||||
}
|
||||
if(pThis->dlg) {
|
||||
pThis->dlg->setPrepared();
|
||||
}
|
||||
}
|
||||
|
||||
void FileOperation::handleFinish() {
|
||||
disconnectJob();
|
||||
|
||||
if(uiTimer) {
|
||||
uiTimer->stop();
|
||||
delete uiTimer;
|
||||
uiTimer = NULL;
|
||||
}
|
||||
|
||||
if(dlg) {
|
||||
dlg->done(QDialog::Accepted);
|
||||
delete dlg;
|
||||
dlg = NULL;
|
||||
}
|
||||
Q_EMIT finished();
|
||||
|
||||
/* sepcial handling for trash
|
||||
* FIXME: need to refactor this to use a more elegant way later. */
|
||||
if(job_->type == FM_FILE_OP_TRASH) { /* FIXME: direct access to job struct! */
|
||||
FmPathList* unable_to_trash = static_cast<FmPathList*>(g_object_get_data(G_OBJECT(job_), "trash-unsupported"));
|
||||
/* some files cannot be trashed because underlying filesystems don't support it. */
|
||||
if(unable_to_trash) { /* delete them instead */
|
||||
/* FIXME: parent window might be already destroyed! */
|
||||
QWidget* parent = NULL; // FIXME: currently, parent window is not set
|
||||
if(QMessageBox::question(parent, tr("Error"),
|
||||
tr("Some files cannot be moved to trash can because "
|
||||
"the underlying file systems don't support this operation.\n"
|
||||
"Do you want to delete them instead?")) == QMessageBox::Yes) {
|
||||
deleteFiles(unable_to_trash, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
g_object_unref(job_);
|
||||
job_ = NULL;
|
||||
|
||||
if(autoDestroy_)
|
||||
delete this;
|
||||
}
|
||||
|
||||
// static
|
||||
FileOperation* FileOperation::copyFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent) {
|
||||
FileOperation* op = new FileOperation(FileOperation::Copy, srcFiles);
|
||||
op->setDestination(dest);
|
||||
op->run();
|
||||
return op;
|
||||
}
|
||||
|
||||
// static
|
||||
FileOperation* FileOperation::moveFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent) {
|
||||
FileOperation* op = new FileOperation(FileOperation::Move, srcFiles);
|
||||
op->setDestination(dest);
|
||||
op->run();
|
||||
return op;
|
||||
}
|
||||
|
||||
//static
|
||||
FileOperation* FileOperation::symlinkFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent) {
|
||||
FileOperation* op = new FileOperation(FileOperation::Link, srcFiles);
|
||||
op->setDestination(dest);
|
||||
op->run();
|
||||
return op;
|
||||
}
|
||||
|
||||
//static
|
||||
FileOperation* FileOperation::deleteFiles(FmPathList* srcFiles, bool prompt, QWidget* parent) {
|
||||
if(prompt) {
|
||||
int result = QMessageBox::warning(parent, tr("Confirm"),
|
||||
tr("Do you want to delete the selected files?"),
|
||||
QMessageBox::Yes|QMessageBox::No,
|
||||
QMessageBox::No);
|
||||
if(result != QMessageBox::Yes)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FileOperation* op = new FileOperation(FileOperation::Delete, srcFiles);
|
||||
op->run();
|
||||
return op;
|
||||
}
|
||||
|
||||
//static
|
||||
FileOperation* FileOperation::trashFiles(FmPathList* srcFiles, bool prompt, QWidget* parent) {
|
||||
if(prompt) {
|
||||
int result = QMessageBox::warning(parent, tr("Confirm"),
|
||||
tr("Do you want to move the selected files to trash can?"),
|
||||
QMessageBox::Yes|QMessageBox::No,
|
||||
QMessageBox::No);
|
||||
if(result != QMessageBox::Yes)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FileOperation* op = new FileOperation(FileOperation::Trash, srcFiles);
|
||||
op->run();
|
||||
return op;
|
||||
}
|
||||
|
||||
//static
|
||||
FileOperation* FileOperation::unTrashFiles(FmPathList* srcFiles, QWidget* parent) {
|
||||
FileOperation* op = new FileOperation(FileOperation::UnTrash, srcFiles);
|
||||
op->run();
|
||||
return op;
|
||||
}
|
||||
|
||||
// static
|
||||
FileOperation* FileOperation::changeAttrFiles(FmPathList* srcFiles, QWidget* parent) {
|
||||
//TODO
|
||||
FileOperation* op = new FileOperation(FileOperation::ChangeAttr, srcFiles);
|
||||
op->run();
|
||||
return op;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FILEOPERATION_H
|
||||
#define FM_FILEOPERATION_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QObject>
|
||||
#include <QElapsedTimer>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
class QTimer;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class FileOperationDialog;
|
||||
|
||||
class LIBFM_QT_API FileOperation : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Type {
|
||||
Copy = FM_FILE_OP_COPY,
|
||||
Move = FM_FILE_OP_MOVE,
|
||||
Link = FM_FILE_OP_LINK,
|
||||
Delete = FM_FILE_OP_DELETE,
|
||||
Trash = FM_FILE_OP_TRASH,
|
||||
UnTrash = FM_FILE_OP_UNTRASH,
|
||||
ChangeAttr = FM_FILE_OP_CHANGE_ATTR
|
||||
};
|
||||
|
||||
public:
|
||||
explicit FileOperation(Type type, FmPathList* srcFiles, QObject* parent = 0);
|
||||
virtual ~FileOperation();
|
||||
|
||||
void setDestination(FmPath* dest) {
|
||||
destPath = fm_path_ref(dest);
|
||||
fm_file_ops_job_set_dest(job_, dest);
|
||||
}
|
||||
|
||||
void setChmod(mode_t newMode, mode_t newModeMask) {
|
||||
fm_file_ops_job_set_chmod(job_, newMode, newModeMask);
|
||||
}
|
||||
|
||||
void setChown(gint uid, gint gid) {
|
||||
fm_file_ops_job_set_chown(job_, uid, gid);
|
||||
}
|
||||
|
||||
// This only work for change attr jobs.
|
||||
void setRecursiveChattr(bool recursive) {
|
||||
fm_file_ops_job_set_recursive(job_, (gboolean)recursive);
|
||||
}
|
||||
|
||||
bool run();
|
||||
|
||||
void cancel() {
|
||||
if(job_)
|
||||
fm_job_cancel(FM_JOB(job_));
|
||||
}
|
||||
|
||||
bool isRunning() const {
|
||||
return job_ ? fm_job_is_running(FM_JOB(job_)) : false;
|
||||
}
|
||||
|
||||
bool isCancelled() const {
|
||||
return job_ ? fm_job_is_cancelled(FM_JOB(job_)) : false;
|
||||
}
|
||||
|
||||
FmFileOpsJob* job() {
|
||||
return job_;
|
||||
}
|
||||
|
||||
bool autoDestroy() {
|
||||
return autoDestroy_;
|
||||
}
|
||||
void setAutoDestroy(bool destroy = true) {
|
||||
autoDestroy_ = destroy;
|
||||
}
|
||||
|
||||
Type type() {
|
||||
return (Type)job_->type;
|
||||
}
|
||||
|
||||
// convinient static functions
|
||||
static FileOperation* copyFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent = 0);
|
||||
static FileOperation* moveFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent = 0);
|
||||
static FileOperation* symlinkFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent = 0);
|
||||
static FileOperation* deleteFiles(FmPathList* srcFiles, bool promp = true, QWidget* parent = 0);
|
||||
static FileOperation* trashFiles(FmPathList* srcFiles, bool promp = true, QWidget* parent = 0);
|
||||
static FileOperation* unTrashFiles(FmPathList* srcFiles, QWidget* parent = 0);
|
||||
static FileOperation* changeAttrFiles(FmPathList* srcFiles, QWidget* parent = 0);
|
||||
|
||||
Q_SIGNALS:
|
||||
void finished();
|
||||
|
||||
private:
|
||||
static gint onFileOpsJobAsk(FmFileOpsJob* job, const char* question, char* const* options, FileOperation* pThis);
|
||||
static gint onFileOpsJobAskRename(FmFileOpsJob* job, FmFileInfo* src, FmFileInfo* dest, char** new_name, FileOperation* pThis);
|
||||
static FmJobErrorAction onFileOpsJobError(FmFileOpsJob* job, GError* err, FmJobErrorSeverity severity, FileOperation* pThis);
|
||||
static void onFileOpsJobPrepared(FmFileOpsJob* job, FileOperation* pThis);
|
||||
static void onFileOpsJobCurFile(FmFileOpsJob* job, const char* cur_file, FileOperation* pThis);
|
||||
static void onFileOpsJobPercent(FmFileOpsJob* job, guint percent, FileOperation* pThis);
|
||||
static void onFileOpsJobFinished(FmFileOpsJob* job, FileOperation* pThis);
|
||||
static void onFileOpsJobCancelled(FmFileOpsJob* job, FileOperation* pThis);
|
||||
|
||||
void handleFinish();
|
||||
void disconnectJob();
|
||||
void showDialog();
|
||||
|
||||
void pauseElapsedTimer() {
|
||||
if(Q_LIKELY(elapsedTimer_ != NULL)) {
|
||||
lastElapsed_ += elapsedTimer_->elapsed();
|
||||
elapsedTimer_->invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void resumeElapsedTimer() {
|
||||
if(Q_LIKELY(elapsedTimer_ != NULL)) {
|
||||
elapsedTimer_->start();
|
||||
}
|
||||
}
|
||||
|
||||
qint64 elapsedTime() {
|
||||
if(Q_LIKELY(elapsedTimer_ != NULL)) {
|
||||
return lastElapsed_ + elapsedTimer_->elapsed();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private Q_SLOTS:
|
||||
void onUiTimeout();
|
||||
|
||||
private:
|
||||
FmFileOpsJob* job_;
|
||||
FileOperationDialog* dlg;
|
||||
FmPath* destPath;
|
||||
FmPathList* srcPaths;
|
||||
QTimer* uiTimer;
|
||||
QElapsedTimer* elapsedTimer_;
|
||||
qint64 lastElapsed_;
|
||||
bool updateRemainingTime_;
|
||||
QString curFile;
|
||||
bool autoDestroy_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FILEOPERATION_H
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "fileoperationdialog.h"
|
||||
#include "fileoperation.h"
|
||||
#include "renamedialog.h"
|
||||
#include <QMessageBox>
|
||||
#include "ui_file-operation-dialog.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileOperationDialog::FileOperationDialog(FileOperation* _operation):
|
||||
QDialog(NULL),
|
||||
operation(_operation),
|
||||
defaultOption(-1) {
|
||||
|
||||
ui = new Ui::FileOperationDialog();
|
||||
ui->setupUi(this);
|
||||
|
||||
QString title;
|
||||
QString message;
|
||||
switch(_operation->type()) {
|
||||
case FM_FILE_OP_MOVE:
|
||||
title = tr("Move files");
|
||||
message = tr("Moving the following files to destination folder:");
|
||||
break;
|
||||
case FM_FILE_OP_COPY:
|
||||
title = tr("Copy Files");
|
||||
message = tr("Copying the following files to destination folder:");
|
||||
break;
|
||||
case FM_FILE_OP_TRASH:
|
||||
title = tr("Trash Files");
|
||||
message = tr("Moving the following files to trash can:");
|
||||
break;
|
||||
case FM_FILE_OP_DELETE:
|
||||
title = tr("Delete Files");
|
||||
message = tr("Deleting the following files");
|
||||
ui->dest->hide();
|
||||
ui->destLabel->hide();
|
||||
break;
|
||||
case FM_FILE_OP_LINK:
|
||||
title = tr("Create Symlinks");
|
||||
message = tr("Creating symlinks for the following files:");
|
||||
break;
|
||||
case FM_FILE_OP_CHANGE_ATTR:
|
||||
title = tr("Change Attributes");
|
||||
message = tr("Changing attributes of the following files:");
|
||||
ui->dest->hide();
|
||||
ui->destLabel->hide();
|
||||
break;
|
||||
case FM_FILE_OP_UNTRASH:
|
||||
title = tr("Restore Trashed Files");
|
||||
message = tr("Restoring the following files from trash can:");
|
||||
ui->dest->hide();
|
||||
ui->destLabel->hide();
|
||||
break;
|
||||
}
|
||||
ui->message->setText(message);
|
||||
setWindowTitle(title);
|
||||
}
|
||||
|
||||
|
||||
FileOperationDialog::~FileOperationDialog() {
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void FileOperationDialog::setDestPath(FmPath* dest) {
|
||||
char* pathStr = fm_path_display_name(dest, false);
|
||||
ui->dest->setText(QString::fromUtf8(pathStr));
|
||||
g_free(pathStr);
|
||||
}
|
||||
|
||||
void FileOperationDialog::setSourceFiles(FmPathList* srcFiles) {
|
||||
GList* l;
|
||||
for(l = fm_path_list_peek_head_link(srcFiles); l; l = l->next) {
|
||||
FmPath* path = FM_PATH(l->data);
|
||||
char* pathStr = fm_path_display_name(path, false);
|
||||
ui->sourceFiles->addItem(QString::fromUtf8(pathStr));
|
||||
g_free(pathStr);
|
||||
}
|
||||
}
|
||||
|
||||
int FileOperationDialog::ask(QString question, char*const* options) {
|
||||
// TODO: implement FileOperationDialog::ask()
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FileOperationDialog::askRename(FmFileInfo* src, FmFileInfo* dest, QString& new_name) {
|
||||
int ret;
|
||||
if(defaultOption == -1) { // default action is not set, ask the user
|
||||
RenameDialog dlg(src, dest, this);
|
||||
dlg.exec();
|
||||
switch(dlg.action()) {
|
||||
case RenameDialog::ActionOverwrite:
|
||||
ret = FM_FILE_OP_OVERWRITE;
|
||||
if(dlg.applyToAll())
|
||||
defaultOption = ret;
|
||||
break;
|
||||
case RenameDialog::ActionRename:
|
||||
ret = FM_FILE_OP_RENAME;
|
||||
new_name = dlg.newName();
|
||||
break;
|
||||
case RenameDialog::ActionIgnore:
|
||||
ret = FM_FILE_OP_SKIP;
|
||||
if(dlg.applyToAll())
|
||||
defaultOption = ret;
|
||||
break;
|
||||
default:
|
||||
ret = FM_FILE_OP_CANCEL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
ret = defaultOption;
|
||||
return ret;
|
||||
}
|
||||
|
||||
FmJobErrorAction FileOperationDialog::error(GError* err, FmJobErrorSeverity severity) {
|
||||
if(severity >= FM_JOB_ERROR_MODERATE) {
|
||||
QMessageBox::critical(this, tr("Error"), QString::fromUtf8(err->message));
|
||||
if(severity == FM_JOB_ERROR_CRITICAL)
|
||||
return FM_JOB_ABORT;
|
||||
}
|
||||
return FM_JOB_CONTINUE;
|
||||
}
|
||||
|
||||
void FileOperationDialog::setCurFile(QString cur_file) {
|
||||
ui->curFile->setText(cur_file);
|
||||
}
|
||||
|
||||
void FileOperationDialog::setPercent(unsigned int percent) {
|
||||
ui->progressBar->setValue(percent);
|
||||
}
|
||||
|
||||
void FileOperationDialog::setRemainingTime(unsigned int sec) {
|
||||
unsigned int min = 0;
|
||||
unsigned int hr = 0;
|
||||
if(sec > 60) {
|
||||
min = sec / 60;
|
||||
sec %= 60;
|
||||
if(min > 60) {
|
||||
hr = min / 60;
|
||||
min %= 60;
|
||||
}
|
||||
}
|
||||
ui->timeRemaining->setText(QString("%1:%2:%3")
|
||||
.arg(hr, 2, 10, QChar('0'))
|
||||
.arg(min, 2, 10, QChar('0'))
|
||||
.arg(sec, 2, 10, QChar('0')));
|
||||
}
|
||||
|
||||
void FileOperationDialog::setPrepared() {
|
||||
}
|
||||
|
||||
void FileOperationDialog::reject() {
|
||||
operation->cancel();
|
||||
QDialog::reject();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FILEOPERATIONDIALOG_H
|
||||
#define FM_FILEOPERATIONDIALOG_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QDialog>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Ui {
|
||||
class FileOperationDialog;
|
||||
};
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class FileOperation;
|
||||
|
||||
class LIBFM_QT_API FileOperationDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FileOperationDialog(FileOperation* _operation);
|
||||
virtual ~FileOperationDialog();
|
||||
|
||||
void setSourceFiles(FmPathList* srcFiles);
|
||||
void setDestPath(FmPath* dest);
|
||||
|
||||
int ask(QString question, char* const* options);
|
||||
int askRename(FmFileInfo* src, FmFileInfo* dest, QString& new_name);
|
||||
FmJobErrorAction error(GError* err, FmJobErrorSeverity severity);
|
||||
void setPrepared();
|
||||
void setCurFile(QString cur_file);
|
||||
void setPercent(unsigned int percent);
|
||||
void setRemainingTime(unsigned int sec);
|
||||
|
||||
virtual void reject();
|
||||
|
||||
private:
|
||||
Ui::FileOperationDialog* ui;
|
||||
FileOperation* operation;
|
||||
int defaultOption;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FILEOPERATIONDIALOG_H
|
@ -0,0 +1,444 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "filepropsdialog.h"
|
||||
#include "ui_file-props.h"
|
||||
#include "icontheme.h"
|
||||
#include "utilities.h"
|
||||
#include "fileoperation.h"
|
||||
#include <QStringBuilder>
|
||||
#include <QStringListModel>
|
||||
#include <QMessageBox>
|
||||
#include <qdial.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
|
||||
#define DIFFERENT_UIDS ((uid)-1)
|
||||
#define DIFFERENT_GIDS ((gid)-1)
|
||||
#define DIFFERENT_PERMS ((mode_t)-1)
|
||||
|
||||
namespace Fm {
|
||||
|
||||
enum {
|
||||
ACCESS_NO_CHANGE = 0,
|
||||
ACCESS_READ_ONLY,
|
||||
ACCESS_READ_WRITE,
|
||||
ACCESS_FORBID
|
||||
};
|
||||
|
||||
FilePropsDialog::FilePropsDialog(FmFileInfoList* files, QWidget* parent, Qt::WindowFlags f):
|
||||
QDialog(parent, f),
|
||||
fileInfos_(fm_file_info_list_ref(files)),
|
||||
singleType(fm_file_info_list_is_same_type(files)),
|
||||
singleFile(fm_file_info_list_get_length(files) == 1 ? true:false),
|
||||
fileInfo(fm_file_info_list_peek_head(files)),
|
||||
mimeType(NULL) {
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
ui = new Ui::FilePropsDialog();
|
||||
ui->setupUi(this);
|
||||
|
||||
if(singleType) {
|
||||
mimeType = fm_mime_type_ref(fm_file_info_get_mime_type(fileInfo));
|
||||
}
|
||||
|
||||
FmPathList* paths = fm_path_list_new_from_file_info_list(files);
|
||||
deepCountJob = fm_deep_count_job_new(paths, FM_DC_JOB_DEFAULT);
|
||||
fm_path_list_unref(paths);
|
||||
|
||||
initGeneralPage();
|
||||
initPermissionsPage();
|
||||
}
|
||||
|
||||
FilePropsDialog::~FilePropsDialog() {
|
||||
delete ui;
|
||||
|
||||
if(fileInfos_)
|
||||
fm_file_info_list_unref(fileInfos_);
|
||||
if(deepCountJob)
|
||||
g_object_unref(deepCountJob);
|
||||
if(fileSizeTimer) {
|
||||
fileSizeTimer->stop();
|
||||
delete fileSizeTimer;
|
||||
fileSizeTimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void FilePropsDialog::initApplications() {
|
||||
if(singleType && mimeType && !fm_file_info_is_dir(fileInfo)) {
|
||||
ui->openWith->setMimeType(mimeType);
|
||||
}
|
||||
else {
|
||||
ui->openWith->hide();
|
||||
ui->openWithLabel->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void FilePropsDialog::initPermissionsPage() {
|
||||
// ownership handling
|
||||
// get owner/group and mode of the first file in the list
|
||||
uid = fm_file_info_get_uid(fileInfo);
|
||||
gid = fm_file_info_get_gid(fileInfo);
|
||||
mode_t mode = fm_file_info_get_mode(fileInfo);
|
||||
ownerPerm = (mode & (S_IRUSR|S_IWUSR|S_IXUSR));
|
||||
groupPerm = (mode & (S_IRGRP|S_IWGRP|S_IXGRP));
|
||||
otherPerm = (mode & (S_IROTH|S_IWOTH|S_IXOTH));
|
||||
execPerm = (mode & (S_IXUSR|S_IXGRP|S_IXOTH));
|
||||
allNative = fm_file_info_is_native(fileInfo);
|
||||
hasDir = S_ISDIR(mode);
|
||||
|
||||
// check if all selected files belongs to the same owner/group or have the same mode
|
||||
// at the same time, check if all files are on native unix filesystems
|
||||
GList* l;
|
||||
for(l = fm_file_info_list_peek_head_link(fileInfos_)->next; l; l = l->next) {
|
||||
FmFileInfo* fi = FM_FILE_INFO(l->data);
|
||||
if(allNative && !fm_file_info_is_native(fi))
|
||||
allNative = false; // not all of the files are native
|
||||
|
||||
mode_t fi_mode = fm_file_info_get_mode(fi);
|
||||
if(S_ISDIR(fi_mode))
|
||||
hasDir = true; // the files list contains dir(s)
|
||||
|
||||
if(uid != DIFFERENT_UIDS && uid != fm_file_info_get_uid(fi))
|
||||
uid = DIFFERENT_UIDS; // not all files have the same owner
|
||||
if(gid != DIFFERENT_GIDS && gid != fm_file_info_get_gid(fi))
|
||||
gid = DIFFERENT_GIDS; // not all files have the same owner group
|
||||
|
||||
if(ownerPerm != DIFFERENT_PERMS && ownerPerm != (fi_mode & (S_IRUSR|S_IWUSR|S_IXUSR)))
|
||||
ownerPerm = DIFFERENT_PERMS; // not all files have the same permission for owner
|
||||
if(groupPerm != DIFFERENT_PERMS && groupPerm != (fi_mode & (S_IRGRP|S_IWGRP|S_IXGRP)))
|
||||
groupPerm = DIFFERENT_PERMS; // not all files have the same permission for grop
|
||||
if(otherPerm != DIFFERENT_PERMS && otherPerm != (fi_mode & (S_IROTH|S_IWOTH|S_IXOTH)))
|
||||
otherPerm = DIFFERENT_PERMS; // not all files have the same permission for other
|
||||
if(execPerm != DIFFERENT_PERMS && execPerm != (fi_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))
|
||||
execPerm = DIFFERENT_PERMS; // not all files have the same executable permission
|
||||
}
|
||||
|
||||
// init owner/group
|
||||
initOwner();
|
||||
|
||||
// if all files are of the same type, and some of them are dirs => all of the items are dirs
|
||||
// rwx values have different meanings for dirs
|
||||
// Let's make it clear for the users
|
||||
// init combo boxes for file permissions here
|
||||
QStringList comboItems;
|
||||
comboItems.append("---"); // no change
|
||||
if(singleType && hasDir) { // all files are dirs
|
||||
comboItems.append(tr("View folder content"));
|
||||
comboItems.append(tr("View and modify folder content"));
|
||||
ui->executable->hide();
|
||||
}
|
||||
else { //not all of the files are dirs
|
||||
comboItems.append(tr("Read"));
|
||||
comboItems.append(tr("Read and write"));
|
||||
}
|
||||
comboItems.append(tr("Forbidden"));
|
||||
QStringListModel* comboModel = new QStringListModel(comboItems, this);
|
||||
ui->ownerPerm->setModel(comboModel);
|
||||
ui->groupPerm->setModel(comboModel);
|
||||
ui->otherPerm->setModel(comboModel);
|
||||
|
||||
// owner
|
||||
ownerPermSel = ACCESS_NO_CHANGE;
|
||||
if(ownerPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files
|
||||
if(ownerPerm & S_IRUSR) { // can read
|
||||
if(ownerPerm & S_IWUSR) // can write
|
||||
ownerPermSel = ACCESS_READ_WRITE;
|
||||
else
|
||||
ownerPermSel = ACCESS_READ_ONLY;
|
||||
}
|
||||
else {
|
||||
if((ownerPerm & S_IWUSR) == 0) // cannot read or write
|
||||
ownerPermSel = ACCESS_FORBID;
|
||||
}
|
||||
}
|
||||
ui->ownerPerm->setCurrentIndex(ownerPermSel);
|
||||
|
||||
// owner and group
|
||||
groupPermSel = ACCESS_NO_CHANGE;
|
||||
if(groupPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files
|
||||
if(groupPerm & S_IRGRP) { // can read
|
||||
if(groupPerm & S_IWGRP) // can write
|
||||
groupPermSel = ACCESS_READ_WRITE;
|
||||
else
|
||||
groupPermSel = ACCESS_READ_ONLY;
|
||||
}
|
||||
else {
|
||||
if((groupPerm & S_IWGRP) == 0) // cannot read or write
|
||||
groupPermSel = ACCESS_FORBID;
|
||||
}
|
||||
}
|
||||
ui->groupPerm->setCurrentIndex(groupPermSel);
|
||||
|
||||
// other
|
||||
otherPermSel = ACCESS_NO_CHANGE;
|
||||
if(otherPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files
|
||||
if(otherPerm & S_IROTH) { // can read
|
||||
if(otherPerm & S_IWOTH) // can write
|
||||
otherPermSel = ACCESS_READ_WRITE;
|
||||
else
|
||||
otherPermSel = ACCESS_READ_ONLY;
|
||||
}
|
||||
else {
|
||||
if((otherPerm & S_IWOTH) == 0) // cannot read or write
|
||||
otherPermSel = ACCESS_FORBID;
|
||||
}
|
||||
|
||||
}
|
||||
ui->otherPerm->setCurrentIndex(otherPermSel);
|
||||
|
||||
// set the checkbox to partially checked state
|
||||
// when owner, group, and other have different executable flags set.
|
||||
// some of them have exec, and others do not have.
|
||||
execCheckState = Qt::PartiallyChecked;
|
||||
if(execPerm != DIFFERENT_PERMS) { // if all files have the same executable permission
|
||||
// check if the files are all executable
|
||||
if((mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == (S_IXUSR|S_IXGRP|S_IXOTH)) {
|
||||
// owner, group, and other all have exec permission.
|
||||
ui->executable->setTristate(false);
|
||||
execCheckState = Qt::Checked;
|
||||
}
|
||||
else if((mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0) {
|
||||
// owner, group, and other all have no exec permission
|
||||
ui->executable->setTristate(false);
|
||||
execCheckState = Qt::Unchecked;
|
||||
}
|
||||
}
|
||||
ui->executable->setCheckState(execCheckState);
|
||||
}
|
||||
|
||||
void FilePropsDialog::initGeneralPage() {
|
||||
// update UI
|
||||
if(singleType) { // all files are of the same mime-type
|
||||
FmIcon* icon = NULL;
|
||||
// FIXME: handle custom icons for some files
|
||||
// FIXME: display special property pages for special files or
|
||||
// some specified mime-types.
|
||||
if(singleFile) { // only one file is selected.
|
||||
icon = fm_file_info_get_icon(fileInfo);
|
||||
}
|
||||
if(mimeType) {
|
||||
if(!icon) // get an icon from mime type if needed
|
||||
icon = fm_mime_type_get_icon(mimeType);
|
||||
ui->fileType->setText(QString::fromUtf8(fm_mime_type_get_desc(mimeType)));
|
||||
ui->mimeType->setText(QString::fromUtf8(fm_mime_type_get_type(mimeType)));
|
||||
}
|
||||
if(icon) {
|
||||
ui->iconButton->setIcon(IconTheme::icon(icon));
|
||||
}
|
||||
|
||||
if(singleFile && fm_file_info_is_symlink(fileInfo)) {
|
||||
ui->target->setText(QString::fromUtf8(fm_file_info_get_target(fileInfo)));
|
||||
}
|
||||
else {
|
||||
ui->target->hide();
|
||||
ui->targetLabel->hide();
|
||||
}
|
||||
} // end if(singleType)
|
||||
else { // not singleType, multiple files are selected at the same time
|
||||
ui->fileType->setText(tr("Files of different types"));
|
||||
ui->target->hide();
|
||||
ui->targetLabel->hide();
|
||||
}
|
||||
|
||||
// FIXME: check if all files has the same parent dir, mtime, or atime
|
||||
if(singleFile) { // only one file is selected
|
||||
FmPath* parent_path = fm_path_get_parent(fm_file_info_get_path(fileInfo));
|
||||
char* parent_str = parent_path ? fm_path_display_name(parent_path, true) : NULL;
|
||||
|
||||
ui->fileName->setText(QString::fromUtf8(fm_file_info_get_disp_name(fileInfo)));
|
||||
if(parent_str) {
|
||||
ui->location->setText(QString::fromUtf8(parent_str));
|
||||
g_free(parent_str);
|
||||
}
|
||||
else
|
||||
ui->location->clear();
|
||||
|
||||
ui->lastModified->setText(QString::fromUtf8(fm_file_info_get_disp_mtime(fileInfo)));
|
||||
|
||||
// FIXME: need to encapsulate this in an libfm API.
|
||||
time_t atime;
|
||||
struct tm tm;
|
||||
atime = fm_file_info_get_atime(fileInfo);
|
||||
localtime_r(&atime, &tm);
|
||||
char buf[128];
|
||||
strftime(buf, sizeof(buf), "%x %R", &tm);
|
||||
ui->lastAccessed->setText(QString::fromUtf8(buf));
|
||||
}
|
||||
else {
|
||||
ui->fileName->setText(tr("Multiple Files"));
|
||||
ui->fileName->setEnabled(false);
|
||||
}
|
||||
|
||||
initApplications(); // init applications combo box
|
||||
|
||||
// calculate total file sizes
|
||||
fileSizeTimer = new QTimer(this);
|
||||
connect(fileSizeTimer, &QTimer::timeout, this, &FilePropsDialog::onFileSizeTimerTimeout);
|
||||
fileSizeTimer->start(600);
|
||||
g_signal_connect(deepCountJob, "finished", G_CALLBACK(onDeepCountJobFinished), this);
|
||||
gboolean ret = fm_job_run_async(FM_JOB(deepCountJob));
|
||||
}
|
||||
|
||||
/*static */ void FilePropsDialog::onDeepCountJobFinished(FmDeepCountJob* job, FilePropsDialog* pThis) {
|
||||
|
||||
pThis->onFileSizeTimerTimeout(); // update file size display
|
||||
|
||||
// free the job
|
||||
g_object_unref(pThis->deepCountJob);
|
||||
pThis->deepCountJob = NULL;
|
||||
|
||||
// stop the timer
|
||||
if(pThis->fileSizeTimer) {
|
||||
pThis->fileSizeTimer->stop();
|
||||
delete pThis->fileSizeTimer;
|
||||
pThis->fileSizeTimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void FilePropsDialog::onFileSizeTimerTimeout() {
|
||||
if(deepCountJob && !fm_job_is_cancelled(FM_JOB(deepCountJob))) {
|
||||
char size_str[128];
|
||||
fm_file_size_to_str(size_str, sizeof(size_str), deepCountJob->total_size,
|
||||
fm_config->si_unit);
|
||||
// FIXME:
|
||||
// OMG! It's really unbelievable that Qt developers only implement
|
||||
// QObject::tr(... int n). GNU gettext developers are smarter and
|
||||
// they use unsigned long instead of int.
|
||||
// We cannot use Qt here to handle plural forms. So sad. :-(
|
||||
QString str = QString::fromUtf8(size_str) %
|
||||
QString(" (%1 B)").arg(deepCountJob->total_size);
|
||||
// tr(" (%n) byte(s)", "", deepCountJob->total_size);
|
||||
ui->fileSize->setText(str);
|
||||
|
||||
fm_file_size_to_str(size_str, sizeof(size_str), deepCountJob->total_ondisk_size,
|
||||
fm_config->si_unit);
|
||||
str = QString::fromUtf8(size_str) %
|
||||
QString(" (%1 B)").arg(deepCountJob->total_ondisk_size);
|
||||
// tr(" (%n) byte(s)", "", deepCountJob->total_ondisk_size);
|
||||
ui->onDiskSize->setText(str);
|
||||
}
|
||||
}
|
||||
|
||||
void FilePropsDialog::accept() {
|
||||
|
||||
// applications
|
||||
if(mimeType && ui->openWith->isChanged()) {
|
||||
GAppInfo* currentApp = ui->openWith->selectedApp();
|
||||
g_app_info_set_as_default_for_type(currentApp, fm_mime_type_get_type(mimeType), NULL);
|
||||
}
|
||||
|
||||
// check if chown or chmod is needed
|
||||
guint32 newUid = uidFromName(ui->owner->text());
|
||||
guint32 newGid = gidFromName(ui->ownerGroup->text());
|
||||
bool needChown = (newUid != -1 && newUid != uid) || (newGid != -1 && newGid != gid);
|
||||
|
||||
int newOwnerPermSel = ui->ownerPerm->currentIndex();
|
||||
int newGroupPermSel = ui->groupPerm->currentIndex();
|
||||
int newOtherPermSel = ui->otherPerm->currentIndex();
|
||||
Qt::CheckState newExecCheckState = ui->executable->checkState();
|
||||
bool needChmod = ((newOwnerPermSel != ownerPermSel) ||
|
||||
(newGroupPermSel != groupPermSel) ||
|
||||
(newOtherPermSel != otherPermSel) ||
|
||||
(newExecCheckState != execCheckState));
|
||||
|
||||
if(needChmod || needChown) {
|
||||
FmPathList* paths = fm_path_list_new_from_file_info_list(fileInfos_);
|
||||
FileOperation* op = new FileOperation(FileOperation::ChangeAttr, paths);
|
||||
fm_path_list_unref(paths);
|
||||
if(needChown) {
|
||||
// don't do chown if new uid/gid and the original ones are actually the same.
|
||||
if(newUid == uid)
|
||||
newUid = -1;
|
||||
if(newGid == gid)
|
||||
newGid = -1;
|
||||
op->setChown(newUid, newGid);
|
||||
}
|
||||
if(needChmod) {
|
||||
mode_t newMode = 0;
|
||||
mode_t newModeMask = 0;
|
||||
// FIXME: we need to make sure that folders with "r" permission also have "x"
|
||||
// at the same time. Otherwise, it's not able to browse the folder later.
|
||||
if(newOwnerPermSel != ownerPermSel && newOwnerPermSel != ACCESS_NO_CHANGE) {
|
||||
// owner permission changed
|
||||
newModeMask |= (S_IRUSR|S_IWUSR); // affect user bits
|
||||
if(newOwnerPermSel == ACCESS_READ_ONLY)
|
||||
newMode |= S_IRUSR;
|
||||
else if(newOwnerPermSel == ACCESS_READ_WRITE)
|
||||
newMode |= (S_IRUSR|S_IWUSR);
|
||||
}
|
||||
if(newGroupPermSel != groupPermSel && newGroupPermSel != ACCESS_NO_CHANGE) {
|
||||
qDebug("newGroupPermSel: %d", newGroupPermSel);
|
||||
// group permission changed
|
||||
newModeMask |= (S_IRGRP|S_IWGRP); // affect group bits
|
||||
if(newGroupPermSel == ACCESS_READ_ONLY)
|
||||
newMode |= S_IRGRP;
|
||||
else if(newGroupPermSel == ACCESS_READ_WRITE)
|
||||
newMode |= (S_IRGRP|S_IWGRP);
|
||||
}
|
||||
if(newOtherPermSel != otherPermSel && newOtherPermSel != ACCESS_NO_CHANGE) {
|
||||
// other permission changed
|
||||
newModeMask |= (S_IROTH|S_IWOTH); // affect other bits
|
||||
if(newOtherPermSel == ACCESS_READ_ONLY)
|
||||
newMode |= S_IROTH;
|
||||
else if(newOtherPermSel == ACCESS_READ_WRITE)
|
||||
newMode |= (S_IROTH|S_IWOTH);
|
||||
}
|
||||
if(newExecCheckState != execCheckState && newExecCheckState != Qt::PartiallyChecked) {
|
||||
// executable state changed
|
||||
newModeMask |= (S_IXUSR|S_IXGRP|S_IXOTH);
|
||||
if(newExecCheckState == Qt::Checked)
|
||||
newMode |= (S_IXUSR|S_IXGRP|S_IXOTH);
|
||||
}
|
||||
op->setChmod(newMode, newModeMask);
|
||||
|
||||
if(hasDir) { // if there are some dirs in our selected files
|
||||
QMessageBox::StandardButton r = QMessageBox::question(this,
|
||||
tr("Apply changes"),
|
||||
tr("Do you want to recursively apply these changes to all files and sub-folders?"),
|
||||
QMessageBox::Yes|QMessageBox::No);
|
||||
if(r == QMessageBox::Yes)
|
||||
op->setRecursiveChattr(true);
|
||||
}
|
||||
}
|
||||
op->setAutoDestroy(true);
|
||||
op->run();
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void FilePropsDialog::initOwner() {
|
||||
if(allNative) {
|
||||
if(uid != DIFFERENT_UIDS)
|
||||
ui->owner->setText(uidToName(uid));
|
||||
if(gid != DIFFERENT_GIDS)
|
||||
ui->ownerGroup->setText(gidToName(gid));
|
||||
|
||||
if(geteuid() != 0) { // on local filesystems, only root can do chown.
|
||||
ui->owner->setEnabled(false);
|
||||
ui->ownerGroup->setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FILEPROPSDIALOG_H
|
||||
#define FM_FILEPROPSDIALOG_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QDialog>
|
||||
#include <QTimer>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Ui {
|
||||
class FilePropsDialog;
|
||||
};
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FilePropsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FilePropsDialog(FmFileInfoList* files, QWidget* parent = 0, Qt::WindowFlags f = 0);
|
||||
virtual ~FilePropsDialog();
|
||||
|
||||
virtual void accept();
|
||||
|
||||
static FilePropsDialog* showForFile(FmFileInfo* file, QWidget* parent = 0) {
|
||||
FmFileInfoList* files = fm_file_info_list_new();
|
||||
fm_file_info_list_push_tail(files, file);
|
||||
FilePropsDialog* dlg = showForFiles(files, parent);
|
||||
fm_file_info_list_unref(files);
|
||||
return dlg;
|
||||
}
|
||||
|
||||
static FilePropsDialog* showForFiles(FmFileInfoList* files, QWidget* parent = 0) {
|
||||
FilePropsDialog* dlg = new FilePropsDialog(files, parent);
|
||||
dlg->show();
|
||||
return dlg;
|
||||
}
|
||||
|
||||
private:
|
||||
void initGeneralPage();
|
||||
void initApplications();
|
||||
void initPermissionsPage();
|
||||
void initOwner();
|
||||
|
||||
static void onDeepCountJobFinished(FmDeepCountJob* job, FilePropsDialog* pThis);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onFileSizeTimerTimeout();
|
||||
|
||||
private:
|
||||
Ui::FilePropsDialog* ui;
|
||||
FmFileInfoList* fileInfos_; // list of all file infos
|
||||
FmFileInfo* fileInfo; // file info of the first file in the list
|
||||
bool singleType; // all files are of the same type?
|
||||
bool singleFile; // only one file is selected?
|
||||
bool hasDir; // is there any dir in the files?
|
||||
bool allNative; // all files are on native UNIX filesystems (not virtual or remote)
|
||||
|
||||
FmMimeType* mimeType; // mime type of the files
|
||||
|
||||
gint32 uid; // owner uid of the files, -1 means all files do not have the same uid
|
||||
gint32 gid; // owner gid of the files, -1 means all files do not have the same uid
|
||||
mode_t ownerPerm; // read permission of the files, -1 means not all files have the same value
|
||||
int ownerPermSel;
|
||||
mode_t groupPerm; // read permission of the files, -1 means not all files have the same value
|
||||
int groupPermSel;
|
||||
mode_t otherPerm; // read permission of the files, -1 means not all files have the same value
|
||||
int otherPermSel;
|
||||
mode_t execPerm; // exec permission of the files
|
||||
Qt::CheckState execCheckState;
|
||||
|
||||
FmDeepCountJob* deepCountJob; // job used to count total size
|
||||
QTimer* fileSizeTimer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FILEPROPSDIALOG_H
|
@ -0,0 +1,449 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SearchDialog</class>
|
||||
<widget class="QDialog" name="SearchDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>512</width>
|
||||
<height>420</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Search Files</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset theme="system-search">
|
||||
<normaloff/>
|
||||
</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Name/Location</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>File Name Patterns:</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="namePatterns">
|
||||
<property name="text">
|
||||
<string>*</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="nameCaseInsensitive">
|
||||
<property name="text">
|
||||
<string>Case insensitive</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="nameRegExp">
|
||||
<property name="text">
|
||||
<string>Use regular expression</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Places to Search:</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="listView"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QPushButton" name="addPath">
|
||||
<property name="text">
|
||||
<string>&Add</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add">
|
||||
<normaloff/>
|
||||
</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removePath">
|
||||
<property name="text">
|
||||
<string>&Remove</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-remove">
|
||||
<normaloff/>
|
||||
</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="recursiveSearch">
|
||||
<property name="text">
|
||||
<string>Search in sub directories</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="searchHidden">
|
||||
<property name="text">
|
||||
<string>Search for hidden files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>File Type</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Only search for files of following types:</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="searchTextFiles">
|
||||
<property name="text">
|
||||
<string>Text files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="searchImages">
|
||||
<property name="text">
|
||||
<string>Image files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="searchAudio">
|
||||
<property name="text">
|
||||
<string>Audio files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="searchVideo">
|
||||
<property name="text">
|
||||
<string>Video files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="searchDocuments">
|
||||
<property name="text">
|
||||
<string>Documents</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="searchFolders">
|
||||
<property name="text">
|
||||
<string>Folders</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>Content</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>File contains:</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="contentPattern"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="contentCaseInsensitive">
|
||||
<property name="text">
|
||||
<string>Case insensiti&ve</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="contentRegExp">
|
||||
<property name="text">
|
||||
<string>&Use regular expression</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>186</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_4">
|
||||
<attribute name="title">
|
||||
<string>Properties</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_10">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>File Size:</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="largerThan">
|
||||
<property name="text">
|
||||
<string>Larger than:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="minSize"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="minSizeUnit">
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bytes</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>KiB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>MiB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>GiB</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="smallerThan">
|
||||
<property name="text">
|
||||
<string>Smaller than:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="maxSize"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="maxSizeUnit">
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bytes</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>KiB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>MiB</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>GiB</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_6">
|
||||
<property name="title">
|
||||
<string>Last Modified Time:</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="earlierThan">
|
||||
<property name="text">
|
||||
<string>Earlier than:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="laterThan">
|
||||
<property name="text">
|
||||
<string>Later than:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDateEdit" name="maxTime">
|
||||
<property name="calendarPopup">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDateEdit" name="minTime">
|
||||
<property name="calendarPopup">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SearchDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SearchDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "filesearchdialog.h"
|
||||
#include <QMessageBox>
|
||||
#include "fm-search.h"
|
||||
#include "ui_filesearch.h"
|
||||
#include <limits>
|
||||
#include <QFileDialog>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileSearchDialog::FileSearchDialog(QStringList paths, QWidget* parent, Qt::WindowFlags f):
|
||||
QDialog(parent, f),
|
||||
ui(new Ui::SearchDialog()) {
|
||||
ui->setupUi(this);
|
||||
ui->minSize->setMaximum(std::numeric_limits<int>().max());
|
||||
ui->maxSize->setMaximum(std::numeric_limits<int>().max());
|
||||
Q_FOREACH(const QString& path, paths) {
|
||||
ui->listView->addItem(path);
|
||||
}
|
||||
|
||||
ui->maxTime->setDate(QDate::currentDate());
|
||||
ui->minTime->setDate(QDate::currentDate());
|
||||
|
||||
connect(ui->addPath, &QPushButton::clicked, this, &FileSearchDialog::onAddPath);
|
||||
connect(ui->removePath, &QPushButton::clicked, this, &FileSearchDialog::onRemovePath);
|
||||
}
|
||||
|
||||
FileSearchDialog::~FileSearchDialog() {
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void FileSearchDialog::accept() {
|
||||
// build the search:/// uri
|
||||
int n = ui->listView->count();
|
||||
if(n > 0) {
|
||||
FmSearch* search = fm_search_new();
|
||||
int i;
|
||||
for(i = 0; i < n; ++i) { // add directories
|
||||
QListWidgetItem* item = ui->listView->item(i);
|
||||
fm_search_add_dir(search, item->text().toLocal8Bit().constData());
|
||||
}
|
||||
|
||||
fm_search_set_recursive(search, ui->recursiveSearch->isChecked());
|
||||
fm_search_set_show_hidden(search, ui->searchHidden->isChecked());
|
||||
fm_search_set_name_patterns(search, ui->namePatterns->text().toUtf8().constData());
|
||||
fm_search_set_name_ci(search, ui->nameCaseInsensitive->isChecked());
|
||||
fm_search_set_name_regex(search, ui->nameRegExp->isChecked());
|
||||
|
||||
fm_search_set_content_pattern(search, ui->contentPattern->text().toUtf8().constData());
|
||||
fm_search_set_content_ci(search, ui->contentCaseInsensitive->isChecked());
|
||||
fm_search_set_content_regex(search, ui->contentRegExp->isChecked());
|
||||
|
||||
// search for the files of specific mime-types
|
||||
if(ui->searchTextFiles->isChecked())
|
||||
fm_search_add_mime_type(search, "text/plain");
|
||||
if(ui->searchImages->isChecked())
|
||||
fm_search_add_mime_type(search, "image/*");
|
||||
if(ui->searchAudio->isChecked())
|
||||
fm_search_add_mime_type(search, "audio/*");
|
||||
if(ui->searchVideo->isChecked())
|
||||
fm_search_add_mime_type(search, "video/*");
|
||||
if(ui->searchFolders->isChecked())
|
||||
fm_search_add_mime_type(search, "inode/directory");
|
||||
if(ui->searchDocuments->isChecked()) {
|
||||
const char* doc_types[] = {
|
||||
"application/pdf",
|
||||
/* "text/html;" */
|
||||
"application/vnd.oasis.opendocument.*",
|
||||
"application/vnd.openxmlformats-officedocument.*",
|
||||
"application/msword;application/vnd.ms-word",
|
||||
"application/msexcel;application/vnd.ms-excel"
|
||||
};
|
||||
for(i = 0; i < sizeof(doc_types)/sizeof(char*); ++i)
|
||||
fm_search_add_mime_type(search, doc_types[i]);
|
||||
}
|
||||
|
||||
// search based on file size
|
||||
const unsigned int unit_bytes[] = {1, (1024), (1024*1024), (1024*1024*1024)};
|
||||
if(ui->largerThan->isChecked()) {
|
||||
guint64 size = ui->minSize->value() * unit_bytes[ui->minSizeUnit->currentIndex()];
|
||||
fm_search_set_min_size(search, size);
|
||||
}
|
||||
|
||||
if(ui->smallerThan->isChecked()) {
|
||||
guint64 size = ui->maxSize->value() * unit_bytes[ui->maxSizeUnit->currentIndex()];
|
||||
fm_search_set_min_size(search, size);
|
||||
}
|
||||
|
||||
// search based on file mtime (we only support date in YYYY-MM-DD format)
|
||||
if(ui->earlierThan->isChecked()) {
|
||||
fm_search_set_max_mtime(search, ui->maxTime->date().toString(QStringLiteral("yyyy-MM-dd")).toUtf8().constData());
|
||||
}
|
||||
if(ui->laterThan->isChecked()) {
|
||||
fm_search_set_min_mtime(search, ui->minTime->date().toString(QStringLiteral("yyyy-MM-dd")).toUtf8().constData());
|
||||
}
|
||||
|
||||
searchUri_.take(fm_search_dup_path(search));
|
||||
|
||||
fm_search_free(search);
|
||||
}
|
||||
else {
|
||||
QMessageBox::critical(this, tr("Error"), tr("You should add at least add one directory to search."));
|
||||
return;
|
||||
}
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void FileSearchDialog::onAddPath() {
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Select a folder"));
|
||||
if(dir.isEmpty())
|
||||
return;
|
||||
// avoid adding duplicated items
|
||||
if(ui->listView->findItems(dir, Qt::MatchFixedString|Qt::MatchCaseSensitive).isEmpty()) {
|
||||
ui->listView->addItem(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void FileSearchDialog::onRemovePath() {
|
||||
// remove selected items
|
||||
Q_FOREACH(QListWidgetItem* item, ui->listView->selectedItems()) {
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_FILESEARCHDIALOG_H
|
||||
#define FM_FILESEARCHDIALOG_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QDialog>
|
||||
#include "path.h"
|
||||
|
||||
namespace Ui {
|
||||
class SearchDialog;
|
||||
}
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FileSearchDialog : public QDialog
|
||||
{
|
||||
public:
|
||||
FileSearchDialog(QStringList paths = QStringList(), QWidget * parent = 0, Qt::WindowFlags f = 0);
|
||||
~FileSearchDialog();
|
||||
|
||||
Path searchUri() const {
|
||||
return searchUri_;
|
||||
}
|
||||
|
||||
virtual void accept();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onAddPath();
|
||||
void onRemovePath();
|
||||
|
||||
private:
|
||||
Ui::SearchDialog* ui;
|
||||
Path searchUri_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FILESEARCHDIALOG_H
|
@ -0,0 +1,317 @@
|
||||
/*
|
||||
* fm-search-uri.c
|
||||
*
|
||||
* Copyright 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
* Copyright 2012-2014 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "fm-search.h"
|
||||
#include <string.h>
|
||||
|
||||
struct _FmSearch
|
||||
{
|
||||
gboolean recursive;
|
||||
gboolean show_hidden;
|
||||
char* name_patterns;
|
||||
gboolean name_ci;
|
||||
gboolean name_regex;
|
||||
char* content_pattern;
|
||||
gboolean content_ci;
|
||||
gboolean content_regex;
|
||||
GList* mime_types;
|
||||
GList* search_path_list;
|
||||
guint64 max_size;
|
||||
guint64 min_size;
|
||||
char* max_mtime;
|
||||
char* min_mtime;
|
||||
};
|
||||
|
||||
FmSearch* fm_search_new (void)
|
||||
{
|
||||
FmSearch* search = (FmSearch*)g_slice_new0(FmSearch);
|
||||
return search;
|
||||
}
|
||||
|
||||
void fm_search_free(FmSearch* search)
|
||||
{
|
||||
g_list_free_full(search->mime_types, (GDestroyNotify)g_free);
|
||||
g_list_free_full(search->search_path_list, (GDestroyNotify)g_free);
|
||||
g_free(search->name_patterns);
|
||||
g_free(search->content_pattern);
|
||||
g_free(search->max_mtime);
|
||||
g_free(search->min_mtime);
|
||||
g_slice_free(FmSearch, search);
|
||||
}
|
||||
|
||||
gboolean fm_search_get_recursive(FmSearch* search)
|
||||
{
|
||||
return search->recursive;
|
||||
}
|
||||
|
||||
void fm_search_set_recursive(FmSearch* search, gboolean recursive)
|
||||
{
|
||||
search->recursive = recursive;
|
||||
}
|
||||
|
||||
gboolean fm_search_get_show_hidden(FmSearch* search)
|
||||
{
|
||||
return search->show_hidden;
|
||||
}
|
||||
|
||||
void fm_search_set_show_hidden(FmSearch* search, gboolean show_hidden)
|
||||
{
|
||||
search->show_hidden = show_hidden;
|
||||
}
|
||||
|
||||
const char* fm_search_get_name_patterns(FmSearch* search)
|
||||
{
|
||||
return search->name_patterns;
|
||||
}
|
||||
|
||||
void fm_search_set_name_patterns(FmSearch* search, const char* name_patterns)
|
||||
{
|
||||
g_free(search->name_patterns);
|
||||
search->name_patterns = g_strdup(name_patterns);
|
||||
}
|
||||
|
||||
gboolean fm_search_get_name_ci(FmSearch* search)
|
||||
{
|
||||
return search->name_ci;
|
||||
}
|
||||
|
||||
void fm_search_set_name_ci(FmSearch* search, gboolean name_ci)
|
||||
{
|
||||
search->name_ci = name_ci;
|
||||
}
|
||||
|
||||
gboolean fm_search_get_name_regex(FmSearch* search)
|
||||
{
|
||||
return search->name_regex;
|
||||
}
|
||||
|
||||
void fm_search_set_name_regex(FmSearch* search, gboolean name_regex)
|
||||
{
|
||||
search->name_regex = name_regex;
|
||||
}
|
||||
|
||||
const char* fm_search_get_content_pattern(FmSearch* search)
|
||||
{
|
||||
return search->content_pattern;
|
||||
}
|
||||
|
||||
void fm_search_set_content_pattern(FmSearch* search, const char* content_pattern)
|
||||
{
|
||||
g_free(search->content_pattern);
|
||||
search->content_pattern = g_strdup(content_pattern);
|
||||
}
|
||||
|
||||
gboolean fm_search_get_content_ci(FmSearch* search)
|
||||
{
|
||||
return search->content_ci;
|
||||
}
|
||||
|
||||
void fm_search_set_content_ci(FmSearch* search, gboolean content_ci)
|
||||
{
|
||||
search->content_ci = content_ci;
|
||||
}
|
||||
|
||||
gboolean fm_search_get_content_regex(FmSearch* search)
|
||||
{
|
||||
return search->content_regex;
|
||||
}
|
||||
|
||||
void fm_search_set_content_regex(FmSearch* search, gboolean content_regex)
|
||||
{
|
||||
search->content_regex = content_regex;
|
||||
}
|
||||
|
||||
void fm_search_add_dir(FmSearch* search, const char* dir)
|
||||
{
|
||||
GList* l = g_list_find_custom(search->search_path_list, dir, (GCompareFunc)strcmp);
|
||||
if(!l)
|
||||
search->search_path_list = g_list_prepend(search->search_path_list, g_strdup(dir));
|
||||
}
|
||||
|
||||
void fm_search_remove_dir(FmSearch* search, const char* dir)
|
||||
{
|
||||
GList* l = g_list_find_custom(search->search_path_list, dir, (GCompareFunc)strcmp);
|
||||
if(G_LIKELY(l))
|
||||
{
|
||||
g_free(l->data);
|
||||
search->search_path_list = g_list_delete_link(search->search_path_list, l);
|
||||
}
|
||||
}
|
||||
|
||||
GList* fm_search_get_dirs(FmSearch* search)
|
||||
{
|
||||
return search->search_path_list;
|
||||
}
|
||||
|
||||
void fm_search_add_mime_type(FmSearch* search, const char* mime_type)
|
||||
{
|
||||
GList* l = g_list_find_custom(search->mime_types, mime_type, (GCompareFunc)strcmp);
|
||||
if(!l)
|
||||
search->mime_types = g_list_prepend(search->mime_types, g_strdup(mime_type));
|
||||
}
|
||||
|
||||
void fm_search_remove_mime_type(FmSearch* search, const char* mime_type)
|
||||
{
|
||||
GList* l = g_list_find_custom(search->mime_types, mime_type, (GCompareFunc)strcmp);
|
||||
if(G_LIKELY(l))
|
||||
{
|
||||
g_free(l->data);
|
||||
search->mime_types = g_list_delete_link(search->mime_types, l);
|
||||
}
|
||||
}
|
||||
|
||||
GList* fm_search_get_mime_types(FmSearch* search)
|
||||
{
|
||||
return search->mime_types;
|
||||
}
|
||||
|
||||
guint64 fm_search_get_max_size(FmSearch* search)
|
||||
{
|
||||
return search->max_size;
|
||||
}
|
||||
|
||||
void fm_search_set_max_size(FmSearch* search, guint64 size)
|
||||
{
|
||||
search->max_size = size;
|
||||
}
|
||||
|
||||
guint64 fm_search_get_min_size(FmSearch* search)
|
||||
{
|
||||
return search->min_size;
|
||||
}
|
||||
|
||||
void fm_search_set_min_size(FmSearch* search, guint64 size)
|
||||
{
|
||||
search->min_size = size;
|
||||
}
|
||||
|
||||
/* format of mtime: YYYY-MM-DD */
|
||||
const char* fm_search_get_max_mtime(FmSearch* search)
|
||||
{
|
||||
return search->max_mtime;
|
||||
}
|
||||
|
||||
void fm_search_set_max_mtime(FmSearch* search, const char* mtime)
|
||||
{
|
||||
g_free(search->max_mtime);
|
||||
search->max_mtime = g_strdup(mtime);
|
||||
}
|
||||
|
||||
/* format of mtime: YYYY-MM-DD */
|
||||
const char* fm_search_get_min_mtime(FmSearch* search)
|
||||
{
|
||||
return search->min_mtime;
|
||||
}
|
||||
|
||||
void fm_search_set_min_mtime(FmSearch* search, const char* mtime)
|
||||
{
|
||||
g_free(search->min_mtime);
|
||||
search->min_mtime = g_strdup(mtime);
|
||||
}
|
||||
|
||||
/* really build the path */
|
||||
FmPath* fm_search_dup_path(FmSearch* search)
|
||||
{
|
||||
FmPath* search_path = NULL;
|
||||
GString* search_str = g_string_sized_new(1024);
|
||||
/* build the search:// URI to perform the search */
|
||||
g_string_append(search_str, "search://");
|
||||
|
||||
if(search->search_path_list) /* we need to have at least one dir path */
|
||||
{
|
||||
char *escaped;
|
||||
/* add paths */
|
||||
GList* l;
|
||||
for(l = search->search_path_list; ; )
|
||||
{
|
||||
char *path_str = (char*)l->data;
|
||||
/* escape possible '?' and ',' */
|
||||
escaped = g_uri_escape_string(path_str, "!$&'()*+:;=/@", TRUE);
|
||||
g_string_append(search_str, escaped);
|
||||
g_free(escaped);
|
||||
|
||||
l = l->next;
|
||||
if(!l) /* no more items */
|
||||
break;
|
||||
g_string_append_c(search_str, ','); /* separator for paths */
|
||||
}
|
||||
|
||||
g_string_append_c(search_str, '?');
|
||||
g_string_append_printf(search_str, "recursive=%c", search->recursive ? '1' : '0');
|
||||
g_string_append_printf(search_str, "&show_hidden=%c", search->show_hidden ? '1' : '0');
|
||||
if(search->name_patterns && *search->name_patterns)
|
||||
{
|
||||
/* escape ampersands in pattern */
|
||||
escaped = g_uri_escape_string(search->name_patterns, ":/?#[]@!$'()*+,;", TRUE);
|
||||
if(search->name_regex)
|
||||
g_string_append_printf(search_str, "&name_regex=%s", escaped);
|
||||
else
|
||||
g_string_append_printf(search_str, "&name=%s", escaped);
|
||||
if(search->name_ci)
|
||||
g_string_append_printf(search_str, "&name_ci=%c", search->name_ci ? '1' : '0');
|
||||
g_free(escaped);
|
||||
}
|
||||
|
||||
if(search->content_pattern && *search->content_pattern)
|
||||
{
|
||||
/* escape ampersands in pattern */
|
||||
escaped = g_uri_escape_string(search->content_pattern, ":/?#[]@!$'()*+,;^<>{}", TRUE);
|
||||
if(search->content_regex)
|
||||
g_string_append_printf(search_str, "&content_regex=%s", escaped);
|
||||
else
|
||||
g_string_append_printf(search_str, "&content=%s", escaped);
|
||||
g_free(escaped);
|
||||
if(search->content_ci)
|
||||
g_string_append_printf(search_str, "&content_ci=%c", search->content_ci ? '1' : '0');
|
||||
}
|
||||
|
||||
/* search for the files of specific mime-types */
|
||||
if(search->mime_types)
|
||||
{
|
||||
GList* l;
|
||||
g_string_append(search_str, "&mime_types=");
|
||||
for(l = search->mime_types; l; l=l->next)
|
||||
{
|
||||
const char* mime_type = (const char*)l->data;
|
||||
g_string_append(search_str, mime_type);
|
||||
if(l->next)
|
||||
g_string_append_c(search_str, ';');
|
||||
}
|
||||
}
|
||||
|
||||
if(search->min_size)
|
||||
g_string_append_printf(search_str, "&min_size=%llu", (unsigned long long)search->min_size);
|
||||
|
||||
if(search->max_size)
|
||||
g_string_append_printf(search_str, "&max_size=%llu", (unsigned long long)search->max_size);
|
||||
|
||||
if(search->min_mtime)
|
||||
g_string_append_printf(search_str, "&min_mtime=%s", search->min_mtime);
|
||||
|
||||
if(search->max_mtime)
|
||||
g_string_append_printf(search_str, "&max_mtime=%s", search->max_mtime);
|
||||
|
||||
search_path = fm_path_new_for_uri(search_str->str);
|
||||
g_string_free(search_str, TRUE);
|
||||
}
|
||||
return search_path;
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* fm-search-uri.h
|
||||
*
|
||||
* Copyright 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
* Copyright 2012-2014 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
/* FmSearch implements a tool used to generate a search:// URI used by libfm to search for files.
|
||||
* This API might become part of libfm in the future.
|
||||
*/
|
||||
|
||||
#ifndef _FM_SEARCH_H_
|
||||
#define _FM_SEARCH_H_
|
||||
|
||||
#include <libfm/fm.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _FmSearch FmSearch;
|
||||
|
||||
FmSearch* fm_search_new(void);
|
||||
void fm_search_free(FmSearch* search);
|
||||
|
||||
FmPath* fm_search_dup_path(FmSearch* search);
|
||||
|
||||
gboolean fm_search_get_recursive(FmSearch* search);
|
||||
void fm_search_set_recursive(FmSearch* search, gboolean recursive);
|
||||
|
||||
gboolean fm_search_get_show_hidden(FmSearch* search);
|
||||
void fm_search_set_show_hidden(FmSearch* search, gboolean show_hidden);
|
||||
|
||||
const char* fm_search_get_name_patterns(FmSearch* search);
|
||||
void fm_search_set_name_patterns(FmSearch* search, const char* name_patterns);
|
||||
|
||||
gboolean fm_search_get_name_ci(FmSearch* search);
|
||||
void fm_search_set_name_ci(FmSearch* search, gboolean name_ci);
|
||||
|
||||
gboolean fm_search_get_name_regex(FmSearch* search);
|
||||
void fm_search_set_name_regex(FmSearch* search, gboolean name_regex);
|
||||
|
||||
const char* fm_search_get_content_pattern(FmSearch* search);
|
||||
void fm_search_set_content_pattern(FmSearch* search, const char* content_pattern);
|
||||
|
||||
gboolean fm_search_get_content_ci(FmSearch* search);
|
||||
void fm_search_set_content_ci(FmSearch* search, gboolean content_ci);
|
||||
|
||||
gboolean fm_search_get_content_regex(FmSearch* search);
|
||||
void fm_search_set_content_regex(FmSearch* search, gboolean content_regex);
|
||||
|
||||
void fm_search_add_dir(FmSearch* search, const char* dir);
|
||||
void fm_search_remove_dir(FmSearch* search, const char* dir);
|
||||
GList* fm_search_get_dirs(FmSearch* search);
|
||||
|
||||
void fm_search_add_mime_type(FmSearch* search, const char* mime_type);
|
||||
void fm_search_remove_mime_type(FmSearch* search, const char* mime_type);
|
||||
GList* fm_search_get_mime_types(FmSearch* search);
|
||||
|
||||
guint64 fm_search_get_max_size(FmSearch* search);
|
||||
void fm_search_set_max_size(FmSearch* search, guint64 size);
|
||||
|
||||
guint64 fm_search_get_min_size(FmSearch* search);
|
||||
void fm_search_set_min_size(FmSearch* search, guint64 size);
|
||||
|
||||
/* format of mtime: YYYY-MM-DD */
|
||||
const char* fm_search_get_max_mtime(FmSearch* search);
|
||||
void fm_search_set_max_mtime(FmSearch* search, const char* mtime);
|
||||
|
||||
/* format of mtime: YYYY-MM-DD */
|
||||
const char* fm_search_get_min_mtime(FmSearch* search);
|
||||
void fm_search_set_min_mtime(FmSearch* search, const char* mtime);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* _FM_SEARCH_H_ */
|
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "folderitemdelegate.h"
|
||||
#include "foldermodel.h"
|
||||
#include <QPainter>
|
||||
#include <QModelIndex>
|
||||
#include <QStyleOptionViewItemV4>
|
||||
#include <QApplication>
|
||||
#include <QIcon>
|
||||
#include <QTextLayout>
|
||||
#include <QTextOption>
|
||||
#include <QTextLine>
|
||||
#include <QDebug>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FolderItemDelegate::FolderItemDelegate(QAbstractItemView* view, QObject* parent):
|
||||
QStyledItemDelegate(parent ? parent : view),
|
||||
symlinkIcon_(QIcon::fromTheme("emblem-symbolic-link")),
|
||||
view_(view) {
|
||||
}
|
||||
|
||||
FolderItemDelegate::~FolderItemDelegate() {
|
||||
|
||||
}
|
||||
|
||||
QSize FolderItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
|
||||
QVariant value = index.data(Qt::SizeHintRole);
|
||||
if(value.isValid())
|
||||
return qvariant_cast<QSize>(value);
|
||||
if(option.decorationPosition == QStyleOptionViewItem::Top ||
|
||||
option.decorationPosition == QStyleOptionViewItem::Bottom) {
|
||||
|
||||
QStyleOptionViewItemV4 opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
opt.decorationAlignment = Qt::AlignHCenter|Qt::AlignTop;
|
||||
opt.displayAlignment = Qt::AlignTop|Qt::AlignHCenter;
|
||||
|
||||
Q_ASSERT(gridSize_ != QSize());
|
||||
QRectF textRect(0, 0, gridSize_.width(), gridSize_.height() - opt.decorationSize.height());
|
||||
drawText(nullptr, opt, textRect); // passing NULL for painter will calculate the bounding rect only.
|
||||
int width = qMax((int)textRect.width(), opt.decorationSize.width());
|
||||
int height = opt.decorationSize.height() + textRect.height();
|
||||
return QSize(width, height);
|
||||
}
|
||||
return QStyledItemDelegate::sizeHint(option, index);
|
||||
}
|
||||
|
||||
QIcon::Mode FolderItemDelegate::iconModeFromState(const QStyle::State state) {
|
||||
|
||||
if(state & QStyle::State_Enabled)
|
||||
return (state & QStyle::State_Selected) ? QIcon::Selected : QIcon::Normal;
|
||||
|
||||
return QIcon::Disabled;
|
||||
}
|
||||
|
||||
// special thanks to Razor-qt developer Alec Moskvin(amoskvin) for providing the fix!
|
||||
void FolderItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
|
||||
Q_ASSERT(index.isValid());
|
||||
FmFileInfo* file = static_cast<FmFileInfo*>(index.data(FolderModel::FileInfoRole).value<void*>());
|
||||
bool isSymlink = file && fm_file_info_is_symlink(file);
|
||||
|
||||
if(option.decorationPosition == QStyleOptionViewItem::Top ||
|
||||
option.decorationPosition == QStyleOptionViewItem::Bottom) {
|
||||
painter->save();
|
||||
painter->setClipRect(option.rect);
|
||||
|
||||
QStyleOptionViewItemV4 opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
opt.decorationAlignment = Qt::AlignHCenter|Qt::AlignTop;
|
||||
opt.displayAlignment = Qt::AlignTop|Qt::AlignHCenter;
|
||||
|
||||
// draw the icon
|
||||
QIcon::Mode iconMode = iconModeFromState(opt.state);
|
||||
QPoint iconPos(opt.rect.x() + (opt.rect.width() - opt.decorationSize.width()) / 2, opt.rect.y());
|
||||
QPixmap pixmap = opt.icon.pixmap(opt.decorationSize, iconMode);
|
||||
painter->drawPixmap(iconPos, pixmap);
|
||||
|
||||
// draw some emblems for the item if needed
|
||||
// we only support symlink emblem at the moment
|
||||
if(isSymlink)
|
||||
painter->drawPixmap(iconPos, symlinkIcon_.pixmap(opt.decorationSize / 2, iconMode));
|
||||
|
||||
// draw the text
|
||||
// The text rect dimensions should be exactly as they were in sizeHint()
|
||||
QRectF textRect(opt.rect.x() - (gridSize_.width() - opt.rect.width()) / 2,
|
||||
opt.rect.y() + opt.decorationSize.height(),
|
||||
gridSize_.width(),
|
||||
gridSize_.height() - opt.decorationSize.height());
|
||||
drawText(painter, opt, textRect);
|
||||
painter->restore();
|
||||
}
|
||||
else {
|
||||
// let QStyledItemDelegate does its default painting
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
|
||||
// draw emblems if needed
|
||||
if(isSymlink) {
|
||||
QStyleOptionViewItemV4 opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
QIcon::Mode iconMode = iconModeFromState(opt.state);
|
||||
QPoint iconPos(opt.rect.x(), opt.rect.y() + (opt.rect.height() - opt.decorationSize.height()) / 2);
|
||||
// draw some emblems for the item if needed
|
||||
// we only support symlink emblem at the moment
|
||||
painter->drawPixmap(iconPos, symlinkIcon_.pixmap(opt.decorationSize / 2, iconMode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if painter is nullptr, the method calculate the bounding rectangle of the text and save it to textRect
|
||||
void FolderItemDelegate::drawText(QPainter* painter, QStyleOptionViewItemV4& opt, QRectF& textRect) const {
|
||||
QTextLayout layout(opt.text, opt.font);
|
||||
QTextOption textOption;
|
||||
textOption.setAlignment(opt.displayAlignment);
|
||||
textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
||||
textOption.setTextDirection(opt.direction);
|
||||
layout.setTextOption(textOption);
|
||||
qreal height = 0;
|
||||
qreal width = 0;
|
||||
int visibleLines = 0;
|
||||
layout.beginLayout();
|
||||
QString elidedText;
|
||||
textRect.adjust(2, 2, -2, -2); // a 2-px margin is considered at FolderView::updateGridSize()
|
||||
for(;;) {
|
||||
QTextLine line = layout.createLine();
|
||||
if(!line.isValid())
|
||||
break;
|
||||
line.setLineWidth(textRect.width());
|
||||
height += opt.fontMetrics.leading();
|
||||
line.setPosition(QPointF(0, height));
|
||||
if((height + line.height() + textRect.y()) > textRect.bottom()) {
|
||||
// if part of this line falls outside the textRect, ignore it and quit.
|
||||
QTextLine lastLine = layout.lineAt(visibleLines - 1);
|
||||
elidedText = opt.text.mid(lastLine.textStart());
|
||||
elidedText = opt.fontMetrics.elidedText(elidedText, opt.textElideMode, textRect.width());
|
||||
if(visibleLines == 1) // this is the only visible line
|
||||
width = textRect.width();
|
||||
break;
|
||||
}
|
||||
height += line.height();
|
||||
width = qMax(width, line.naturalTextWidth());
|
||||
++ visibleLines;
|
||||
}
|
||||
layout.endLayout();
|
||||
width = qMax(width, (qreal)opt.fontMetrics.width(elidedText));
|
||||
|
||||
// draw background for selected item
|
||||
QRectF boundRect = layout.boundingRect();
|
||||
//qDebug() << "bound rect: " << boundRect << "width: " << width;
|
||||
boundRect.setWidth(width);
|
||||
boundRect.setHeight(height);
|
||||
boundRect.moveTo(textRect.x() + (textRect.width() - width)/2, textRect.y());
|
||||
|
||||
QRectF selRect = boundRect.adjusted(-2, -2, 2, 2);
|
||||
|
||||
if(!painter) { // no painter, calculate the bounding rect only
|
||||
textRect = selRect;
|
||||
return;
|
||||
}
|
||||
|
||||
QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
|
||||
if(opt.state & QStyle::State_Selected) {
|
||||
painter->fillRect(selRect, opt.palette.highlight());
|
||||
painter->setPen(opt.palette.color(cg, QPalette::HighlightedText));
|
||||
}
|
||||
else
|
||||
painter->setPen(opt.palette.color(cg, QPalette::Text));
|
||||
|
||||
// draw text
|
||||
for(int i = 0; i < visibleLines; ++i) {
|
||||
QTextLine line = layout.lineAt(i);
|
||||
if(i == (visibleLines - 1) && !elidedText.isEmpty()) { // the last line, draw elided text
|
||||
QPointF pos(boundRect.x() + line.position().x(), boundRect.y() + line.y() + line.ascent());
|
||||
painter->drawText(pos, elidedText);
|
||||
}
|
||||
else {
|
||||
line.draw(painter, textRect.topLeft());
|
||||
}
|
||||
}
|
||||
|
||||
if(opt.state & QStyle::State_HasFocus) {
|
||||
// draw focus rect
|
||||
QStyleOptionFocusRect o;
|
||||
o.QStyleOption::operator=(opt);
|
||||
o.rect = selRect.toRect(); // subElementRect(SE_ItemViewItemFocusRect, vopt, widget);
|
||||
o.state |= QStyle::State_KeyboardFocusChange;
|
||||
o.state |= QStyle::State_Item;
|
||||
QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled)
|
||||
? QPalette::Normal : QPalette::Disabled;
|
||||
o.backgroundColor = opt.palette.color(cg, (opt.state & QStyle::State_Selected)
|
||||
? QPalette::Highlight : QPalette::Window);
|
||||
if (const QWidget* widget = opt.widget) {
|
||||
QStyle* style = widget->style() ? widget->style() : qApp->style();
|
||||
style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FOLDERITEMDELEGATE_H
|
||||
#define FM_FOLDERITEMDELEGATE_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QAbstractItemView>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FolderItemDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FolderItemDelegate(QAbstractItemView* view, QObject* parent = nullptr);
|
||||
virtual ~FolderItemDelegate();
|
||||
|
||||
inline void setGridSize(QSize size) {
|
||||
gridSize_ = size;
|
||||
}
|
||||
|
||||
inline QSize gridSize() const {
|
||||
return gridSize_;
|
||||
}
|
||||
|
||||
virtual QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const;
|
||||
virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
|
||||
|
||||
private:
|
||||
void drawText(QPainter* painter, QStyleOptionViewItemV4& opt, QRectF& textRect) const;
|
||||
static QIcon::Mode iconModeFromState(QStyle::State state);
|
||||
|
||||
private:
|
||||
QAbstractItemView* view_;
|
||||
QIcon symlinkIcon_;
|
||||
QSize gridSize_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FOLDERITEMDELEGATE_H
|
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
* Copyright (C) 2012 - 2014 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "foldermenu.h"
|
||||
#include "createnewmenu.h"
|
||||
#include "filepropsdialog.h"
|
||||
#include "folderview.h"
|
||||
#include "utilities.h"
|
||||
#include <cstring> // for memset
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FolderMenu::FolderMenu(FolderView* view, QWidget* parent):
|
||||
QMenu(parent),
|
||||
view_(view) {
|
||||
|
||||
ProxyFolderModel* model = view_->model();
|
||||
|
||||
createAction_ = new QAction(tr("Create &New"), this);
|
||||
addAction(createAction_);
|
||||
|
||||
createAction_->setMenu(new CreateNewMenu(view_, view_->path(), this));
|
||||
|
||||
separator1_ = addSeparator();
|
||||
|
||||
pasteAction_ = new QAction(QIcon::fromTheme("edit-paste"), tr("&Paste"), this);
|
||||
addAction(pasteAction_);
|
||||
connect(pasteAction_, &QAction::triggered, this, &FolderMenu::onPasteActionTriggered);
|
||||
|
||||
separator2_ = addSeparator();
|
||||
|
||||
selectAllAction_ = new QAction(tr("Select &All"), this);
|
||||
addAction(selectAllAction_);
|
||||
connect(selectAllAction_, &QAction::triggered, this, &FolderMenu::onSelectAllActionTriggered);
|
||||
|
||||
invertSelectionAction_ = new QAction(tr("Invert Selection"), this);
|
||||
addAction(invertSelectionAction_);
|
||||
connect(invertSelectionAction_, &QAction::triggered, this, &FolderMenu::onInvertSelectionActionTriggered);
|
||||
|
||||
separator3_ = addSeparator();
|
||||
|
||||
sortAction_ = new QAction(tr("Sorting"), this);
|
||||
addAction(sortAction_);
|
||||
createSortMenu();
|
||||
sortAction_->setMenu(sortMenu_);
|
||||
|
||||
showHiddenAction_ = new QAction(tr("Show Hidden"), this);
|
||||
addAction(showHiddenAction_);
|
||||
showHiddenAction_->setCheckable(true);
|
||||
showHiddenAction_->setChecked(model->showHidden());
|
||||
connect(showHiddenAction_, &QAction::triggered, this, &FolderMenu::onShowHiddenActionTriggered);
|
||||
|
||||
separator4_ = addSeparator();
|
||||
|
||||
propertiesAction_ = new QAction(tr("Folder Pr&operties"), this);
|
||||
addAction(propertiesAction_);
|
||||
connect(propertiesAction_, &QAction::triggered, this, &FolderMenu::onPropertiesActionTriggered);
|
||||
}
|
||||
|
||||
FolderMenu::~FolderMenu() {
|
||||
}
|
||||
|
||||
void FolderMenu::addSortMenuItem(QString title, int id) {
|
||||
QAction* action = new QAction(title, this);
|
||||
sortMenu_->addAction(action);
|
||||
action->setCheckable(true);
|
||||
sortActionGroup_->addAction(action);
|
||||
connect(action, &QAction::triggered, this, &FolderMenu::onSortActionTriggered);
|
||||
sortActions_[id] = action;
|
||||
}
|
||||
|
||||
void FolderMenu::createSortMenu() {
|
||||
ProxyFolderModel* model = view_->model();
|
||||
|
||||
sortMenu_ = new QMenu(this);
|
||||
sortActionGroup_ = new QActionGroup(sortMenu_);
|
||||
sortActionGroup_->setExclusive(true);
|
||||
|
||||
std::memset(sortActions_, 0, sizeof(sortActions_));
|
||||
|
||||
addSortMenuItem(tr("By File Name"), FolderModel::ColumnFileName);
|
||||
addSortMenuItem(tr("By Modification Time"), FolderModel::ColumnFileMTime);
|
||||
addSortMenuItem(tr("By File Size"), FolderModel::ColumnFileSize);
|
||||
addSortMenuItem(tr("By File Type"), FolderModel::ColumnFileType);
|
||||
addSortMenuItem(tr("By File Owner"), FolderModel::ColumnFileOwner);
|
||||
|
||||
int col = model->sortColumn();
|
||||
|
||||
if(col >= 0 && col < FolderModel::NumOfColumns) {
|
||||
sortActions_[col]->setChecked(true);;
|
||||
}
|
||||
|
||||
sortMenu_->addSeparator();
|
||||
|
||||
QActionGroup* group = new QActionGroup(this);
|
||||
group->setExclusive(true);
|
||||
actionAscending_ = new QAction(tr("Ascending"), this);
|
||||
actionAscending_->setCheckable(true);
|
||||
sortMenu_->addAction(actionAscending_);
|
||||
group->addAction(actionAscending_);
|
||||
|
||||
actionDescending_ = new QAction(tr("Descending"), this);
|
||||
actionDescending_->setCheckable(true);
|
||||
sortMenu_->addAction(actionDescending_);
|
||||
group->addAction(actionDescending_);
|
||||
|
||||
if(model->sortOrder() == Qt::AscendingOrder)
|
||||
actionAscending_->setChecked(true);
|
||||
else
|
||||
actionDescending_->setChecked(true);
|
||||
|
||||
connect(actionAscending_, &QAction::triggered, this, &FolderMenu::onSortOrderActionTriggered);
|
||||
connect(actionDescending_, &QAction::triggered, this, &FolderMenu::onSortOrderActionTriggered);
|
||||
|
||||
sortMenu_->addSeparator();
|
||||
|
||||
QAction* actionFolderFirst = new QAction(tr("Folder First"), this);
|
||||
sortMenu_->addAction(actionFolderFirst);
|
||||
actionFolderFirst->setCheckable(true);
|
||||
|
||||
if(model->folderFirst())
|
||||
actionFolderFirst->setChecked(true);
|
||||
|
||||
connect(actionFolderFirst, &QAction::triggered, this, &FolderMenu::onFolderFirstActionTriggered);
|
||||
|
||||
QAction* actionCaseSensitive = new QAction(tr("Case Sensitive"), this);
|
||||
sortMenu_->addAction(actionCaseSensitive);
|
||||
actionCaseSensitive->setCheckable(true);
|
||||
|
||||
if(model->sortCaseSensitivity() == Qt::CaseSensitive)
|
||||
actionCaseSensitive->setChecked(true);
|
||||
|
||||
connect(actionCaseSensitive, &QAction::triggered, this, &FolderMenu::onCaseSensitiveActionTriggered);
|
||||
}
|
||||
|
||||
void FolderMenu::onPasteActionTriggered() {
|
||||
FmPath* folderPath = view_->path();
|
||||
|
||||
if(folderPath)
|
||||
pasteFilesFromClipboard(folderPath);
|
||||
}
|
||||
|
||||
void FolderMenu::onSelectAllActionTriggered() {
|
||||
view_->selectAll();
|
||||
}
|
||||
|
||||
void FolderMenu::onInvertSelectionActionTriggered() {
|
||||
view_->invertSelection();
|
||||
}
|
||||
|
||||
void FolderMenu::onSortActionTriggered(bool checked) {
|
||||
ProxyFolderModel* model = view_->model();
|
||||
|
||||
if(model) {
|
||||
QAction* action = static_cast<QAction*>(sender());
|
||||
|
||||
for(int col = 0; col < FolderModel::NumOfColumns; ++col) {
|
||||
if(action == sortActions_[col]) {
|
||||
model->sort(col, model->sortOrder());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FolderMenu::onSortOrderActionTriggered(bool checked) {
|
||||
ProxyFolderModel* model = view_->model();
|
||||
|
||||
if(model) {
|
||||
QAction* action = static_cast<QAction*>(sender());
|
||||
Qt::SortOrder order;
|
||||
|
||||
if(action == actionAscending_)
|
||||
order = Qt::AscendingOrder;
|
||||
else
|
||||
order = Qt::DescendingOrder;
|
||||
|
||||
model->sort(model->sortColumn(), order);
|
||||
}
|
||||
}
|
||||
|
||||
void FolderMenu::onShowHiddenActionTriggered(bool checked) {
|
||||
ProxyFolderModel* model = view_->model();
|
||||
|
||||
if(model) {
|
||||
qDebug("show hidden: %d", checked);
|
||||
model->setShowHidden(checked);
|
||||
}
|
||||
}
|
||||
|
||||
void FolderMenu::onCaseSensitiveActionTriggered(bool checked) {
|
||||
ProxyFolderModel* model = view_->model();
|
||||
|
||||
if(model) {
|
||||
model->setSortCaseSensitivity(checked ? Qt::CaseSensitive : Qt::CaseInsensitive);
|
||||
}
|
||||
}
|
||||
|
||||
void FolderMenu::onFolderFirstActionTriggered(bool checked) {
|
||||
ProxyFolderModel* model = view_->model();
|
||||
|
||||
if(model) {
|
||||
model->setFolderFirst(checked);
|
||||
}
|
||||
}
|
||||
|
||||
void FolderMenu::onPropertiesActionTriggered() {
|
||||
FmFileInfo* folderInfo = view_->folderInfo();
|
||||
|
||||
if(folderInfo)
|
||||
FilePropsDialog::showForFile(folderInfo);
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FOLDERMENU_H
|
||||
#define FM_FOLDERMENU_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QMenu>
|
||||
#include <libfm/fm.h>
|
||||
#include "foldermodel.h"
|
||||
|
||||
class QAction;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class FolderView;
|
||||
|
||||
class LIBFM_QT_API FolderMenu : public QMenu {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FolderMenu(FolderView* view, QWidget* parent = 0);
|
||||
virtual ~FolderMenu();
|
||||
|
||||
QAction* createAction() {
|
||||
return createAction_;
|
||||
}
|
||||
|
||||
QAction* separator1() {
|
||||
return separator1_;
|
||||
}
|
||||
|
||||
QAction* pasteAction() {
|
||||
return pasteAction_;
|
||||
}
|
||||
|
||||
QAction* separator2() {
|
||||
return separator2_;
|
||||
}
|
||||
|
||||
QAction* selectAllAction() {
|
||||
return selectAllAction_;
|
||||
}
|
||||
|
||||
QAction* invertSelectionAction() {
|
||||
return invertSelectionAction_;
|
||||
}
|
||||
|
||||
QAction* separator3() {
|
||||
return separator3_;
|
||||
}
|
||||
|
||||
QAction* sortAction() {
|
||||
return sortAction_;
|
||||
}
|
||||
|
||||
QAction* showHiddenAction() {
|
||||
return showHiddenAction_;
|
||||
}
|
||||
|
||||
QAction* separator4() {
|
||||
return separator4_;
|
||||
}
|
||||
|
||||
QAction* propertiesAction() {
|
||||
return propertiesAction_;
|
||||
}
|
||||
|
||||
FolderView* view() {
|
||||
return view_;
|
||||
}
|
||||
|
||||
protected Q_SLOTS:
|
||||
void onPasteActionTriggered();
|
||||
void onSelectAllActionTriggered();
|
||||
void onInvertSelectionActionTriggered();
|
||||
void onSortActionTriggered(bool checked);
|
||||
void onSortOrderActionTriggered(bool checked);
|
||||
void onShowHiddenActionTriggered(bool checked);
|
||||
void onCaseSensitiveActionTriggered(bool checked);
|
||||
void onFolderFirstActionTriggered(bool checked);
|
||||
void onPropertiesActionTriggered();
|
||||
|
||||
private:
|
||||
void createSortMenu();
|
||||
void addSortMenuItem(QString title, int id);
|
||||
|
||||
private:
|
||||
FolderView* view_;
|
||||
QAction* createAction_;
|
||||
QAction* separator1_;
|
||||
QAction* pasteAction_;
|
||||
QAction* separator2_;
|
||||
QAction* selectAllAction_;
|
||||
QAction* invertSelectionAction_;
|
||||
QAction* separator3_;
|
||||
QAction* sortAction_;
|
||||
QActionGroup* sortActionGroup_;
|
||||
QMenu* sortMenu_;
|
||||
QAction* sortActions_[FolderModel::NumOfColumns];
|
||||
QAction* actionAscending_;
|
||||
QAction* actionDescending_;
|
||||
QAction* showHiddenAction_;
|
||||
QAction* separator4_;
|
||||
QAction* propertiesAction_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FOLDERMENU_H
|
@ -0,0 +1,554 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "foldermodel.h"
|
||||
#include "icontheme.h"
|
||||
#include <iostream>
|
||||
#include <QtAlgorithms>
|
||||
#include <QVector>
|
||||
#include <qmimedata.h>
|
||||
#include <QMimeData>
|
||||
#include <QByteArray>
|
||||
#include <QPixmap>
|
||||
#include <QPainter>
|
||||
#include "utilities.h"
|
||||
#include "fileoperation.h"
|
||||
#include "thumbnailloader.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FolderModel::FolderModel() :
|
||||
folder_(nullptr) {
|
||||
/*
|
||||
ColumnIcon,
|
||||
ColumnName,
|
||||
ColumnFileType,
|
||||
ColumnMTime,
|
||||
NumOfColumns
|
||||
*/
|
||||
thumbnailRefCounts.reserve(4);
|
||||
|
||||
// reload all icons when the icon theme is changed
|
||||
connect(IconTheme::instance(), &IconTheme::changed, this, &FolderModel::updateIcons);
|
||||
}
|
||||
|
||||
FolderModel::~FolderModel() {
|
||||
qDebug("delete FolderModel");
|
||||
|
||||
if(folder_)
|
||||
setFolder(nullptr);
|
||||
|
||||
// if the thumbnail requests list is not empty, cancel them
|
||||
if(!thumbnailResults.empty()) {
|
||||
Q_FOREACH(FmThumbnailLoader* res, thumbnailResults) {
|
||||
ThumbnailLoader::cancel(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FolderModel::setFolder(FmFolder* new_folder) {
|
||||
if(folder_) {
|
||||
removeAll(); // remove old items
|
||||
g_signal_handlers_disconnect_by_func(folder_, gpointer(onStartLoading), this);
|
||||
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFinishLoading), this);
|
||||
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFilesAdded), this);
|
||||
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFilesChanged), this);
|
||||
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFilesRemoved), this);
|
||||
g_object_unref(folder_);
|
||||
}
|
||||
if(new_folder) {
|
||||
folder_ = FM_FOLDER(g_object_ref(new_folder));
|
||||
g_signal_connect(folder_, "start-loading", G_CALLBACK(onStartLoading), this);
|
||||
g_signal_connect(folder_, "finish-loading", G_CALLBACK(onFinishLoading), this);
|
||||
g_signal_connect(folder_, "files-added", G_CALLBACK(onFilesAdded), this);
|
||||
g_signal_connect(folder_, "files-changed", G_CALLBACK(onFilesChanged), this);
|
||||
g_signal_connect(folder_, "files-removed", G_CALLBACK(onFilesRemoved), this);
|
||||
// handle the case if the folder is already loaded
|
||||
if(fm_folder_is_loaded(folder_))
|
||||
insertFiles(0, fm_folder_get_files(folder_));
|
||||
}
|
||||
else
|
||||
folder_ = nullptr;
|
||||
}
|
||||
|
||||
void FolderModel::onStartLoading(FmFolder* folder, gpointer user_data) {
|
||||
FolderModel* model = static_cast<FolderModel*>(user_data);
|
||||
// remove all items
|
||||
model->removeAll();
|
||||
}
|
||||
|
||||
void FolderModel::onFinishLoading(FmFolder* folder, gpointer user_data) {
|
||||
Q_UNUSED(folder)
|
||||
Q_UNUSED(user_data)
|
||||
}
|
||||
|
||||
void FolderModel::onFilesAdded(FmFolder* folder, GSList* files, gpointer user_data) {
|
||||
FolderModel* model = static_cast<FolderModel*>(user_data);
|
||||
int n_files = g_slist_length(files);
|
||||
model->beginInsertRows(QModelIndex(), model->items.count(), model->items.count() + n_files - 1);
|
||||
for(GSList* l = files; l; l = l->next) {
|
||||
FmFileInfo* info = FM_FILE_INFO(l->data);
|
||||
FolderModelItem item(info);
|
||||
/*
|
||||
if(fm_file_info_is_hidden(info)) {
|
||||
model->hiddenItems.append(item);
|
||||
continue;
|
||||
}
|
||||
*/
|
||||
model->items.append(item);
|
||||
}
|
||||
model->endInsertRows();
|
||||
}
|
||||
|
||||
//static
|
||||
void FolderModel::onFilesChanged(FmFolder* folder, GSList* files, gpointer user_data) {
|
||||
FolderModel* model = static_cast<FolderModel*>(user_data);
|
||||
for(GSList* l = files; l; l = l->next) {
|
||||
FmFileInfo* info = FM_FILE_INFO(l->data);
|
||||
int row;
|
||||
QList<FolderModelItem>::iterator it = model->findItemByFileInfo(info, &row);
|
||||
if(it != model->items.end()) {
|
||||
FolderModelItem& item = *it;
|
||||
// try to update the item
|
||||
item.displayName = QString::fromUtf8(fm_file_info_get_disp_name(info));
|
||||
item.updateIcon();
|
||||
item.thumbnails.clear();
|
||||
QModelIndex index = model->createIndex(row, 0, &item);
|
||||
Q_EMIT model->dataChanged(index, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//static
|
||||
void FolderModel::onFilesRemoved(FmFolder* folder, GSList* files, gpointer user_data) {
|
||||
FolderModel* model = static_cast<FolderModel*>(user_data);
|
||||
for(GSList* l = files; l; l = l->next) {
|
||||
FmFileInfo* info = FM_FILE_INFO(l->data);
|
||||
const char* name = fm_file_info_get_name(info);
|
||||
int row;
|
||||
QList<FolderModelItem>::iterator it = model->findItemByName(name, &row);
|
||||
if(it != model->items.end()) {
|
||||
model->beginRemoveRows(QModelIndex(), row, row);
|
||||
model->items.erase(it);
|
||||
model->endRemoveRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FolderModel::insertFiles(int row, FmFileInfoList* files) {
|
||||
int n_files = fm_file_info_list_get_length(files);
|
||||
beginInsertRows(QModelIndex(), row, row + n_files - 1);
|
||||
for(GList* l = fm_file_info_list_peek_head_link(files); l; l = l->next) {
|
||||
FolderModelItem item(FM_FILE_INFO(l->data));
|
||||
items.append(item);
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void FolderModel::removeAll() {
|
||||
if(items.empty())
|
||||
return;
|
||||
beginRemoveRows(QModelIndex(), 0, items.size() - 1);
|
||||
items.clear();
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
int FolderModel::rowCount(const QModelIndex & parent) const {
|
||||
if(parent.isValid())
|
||||
return 0;
|
||||
return items.size();
|
||||
}
|
||||
|
||||
int FolderModel::columnCount (const QModelIndex & parent = QModelIndex()) const {
|
||||
if(parent.isValid())
|
||||
return 0;
|
||||
return NumOfColumns;
|
||||
}
|
||||
|
||||
FolderModelItem* FolderModel::itemFromIndex(const QModelIndex& index) const {
|
||||
return reinterpret_cast<FolderModelItem*>(index.internalPointer());
|
||||
}
|
||||
|
||||
FmFileInfo* FolderModel::fileInfoFromIndex(const QModelIndex& index) const {
|
||||
FolderModelItem* item = itemFromIndex(index);
|
||||
return item ? item->info : nullptr;
|
||||
}
|
||||
|
||||
QVariant FolderModel::data(const QModelIndex & index, int role/* = Qt::DisplayRole*/) const {
|
||||
if(!index.isValid() || index.row() > items.size() || index.column() >= NumOfColumns) {
|
||||
return QVariant();
|
||||
}
|
||||
FolderModelItem* item = itemFromIndex(index);
|
||||
FmFileInfo* info = item->info;
|
||||
|
||||
switch(role) {
|
||||
case Qt::ToolTipRole:
|
||||
return QVariant(item->displayName);
|
||||
case Qt::DisplayRole: {
|
||||
switch(index.column()) {
|
||||
case ColumnFileName: {
|
||||
return QVariant(item->displayName);
|
||||
}
|
||||
case ColumnFileType: {
|
||||
FmMimeType* mime = fm_file_info_get_mime_type(info);
|
||||
const char* desc = fm_mime_type_get_desc(mime);
|
||||
return QString::fromUtf8(desc);
|
||||
}
|
||||
case ColumnFileMTime: {
|
||||
const char* name = fm_file_info_get_disp_mtime(info);
|
||||
return QString::fromUtf8(name);
|
||||
}
|
||||
case ColumnFileSize: {
|
||||
const char* name = fm_file_info_get_disp_size(info);
|
||||
return QString::fromUtf8(name);
|
||||
}
|
||||
case ColumnFileOwner: {
|
||||
const char* name = fm_file_info_get_disp_owner(info);
|
||||
return QString::fromUtf8(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
case Qt::DecorationRole: {
|
||||
if(index.column() == 0) {
|
||||
// QPixmap pix = IconTheme::loadIcon(fm_file_info_get_icon(info), iconSize_);
|
||||
return QVariant(item->icon);
|
||||
// return QVariant(pix);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FileInfoRole:
|
||||
return qVariantFromValue((void*)info);
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant FolderModel::headerData(int section, Qt::Orientation orientation, int role/* = Qt::DisplayRole*/) const {
|
||||
if(role == Qt::DisplayRole) {
|
||||
if(orientation == Qt::Horizontal) {
|
||||
QString title;
|
||||
switch(section) {
|
||||
case ColumnFileName:
|
||||
title = tr("Name");
|
||||
break;
|
||||
case ColumnFileType:
|
||||
title = tr("Type");
|
||||
break;
|
||||
case ColumnFileSize:
|
||||
title = tr("Size");
|
||||
break;
|
||||
case ColumnFileMTime:
|
||||
title = tr("Modified");
|
||||
break;
|
||||
case ColumnFileOwner:
|
||||
title = tr("Owner");
|
||||
break;
|
||||
}
|
||||
return QVariant(title);
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QModelIndex FolderModel::index(int row, int column, const QModelIndex & parent) const {
|
||||
if(row <0 || row >= items.size() || column < 0 || column >= NumOfColumns)
|
||||
return QModelIndex();
|
||||
const FolderModelItem& item = items.at(row);
|
||||
return createIndex(row, column, (void*)&item);
|
||||
}
|
||||
|
||||
QModelIndex FolderModel::parent(const QModelIndex & index) const {
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
Qt::ItemFlags FolderModel::flags(const QModelIndex& index) const {
|
||||
// FIXME: should not return same flags unconditionally for all columns
|
||||
Qt::ItemFlags flags;
|
||||
if(index.isValid()) {
|
||||
flags = Qt::ItemIsEnabled|Qt::ItemIsSelectable;
|
||||
if(index.column() == ColumnFileName)
|
||||
flags |= (Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled);
|
||||
}
|
||||
else {
|
||||
flags = Qt::ItemIsDropEnabled;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
// FIXME: this is very inefficient and should be replaced with a
|
||||
// more reasonable implementation later.
|
||||
QList<FolderModelItem>::iterator FolderModel::findItemByPath(FmPath* path, int* row) {
|
||||
QList<FolderModelItem>::iterator it = items.begin();
|
||||
int i = 0;
|
||||
while(it != items.end()) {
|
||||
FolderModelItem& item = *it;
|
||||
FmPath* item_path = fm_file_info_get_path(item.info);
|
||||
if(fm_path_equal(item_path, path)) {
|
||||
*row = i;
|
||||
return it;
|
||||
}
|
||||
++it;
|
||||
++i;
|
||||
}
|
||||
return items.end();
|
||||
}
|
||||
|
||||
// FIXME: this is very inefficient and should be replaced with a
|
||||
// more reasonable implementation later.
|
||||
QList<FolderModelItem>::iterator FolderModel::findItemByName(const char* name, int* row) {
|
||||
QList<FolderModelItem>::iterator it = items.begin();
|
||||
int i = 0;
|
||||
while(it != items.end()) {
|
||||
FolderModelItem& item = *it;
|
||||
const char* item_name = fm_file_info_get_name(item.info);
|
||||
if(strcmp(name, item_name) == 0) {
|
||||
*row = i;
|
||||
return it;
|
||||
}
|
||||
++it;
|
||||
++i;
|
||||
}
|
||||
return items.end();
|
||||
}
|
||||
|
||||
QList< FolderModelItem >::iterator FolderModel::findItemByFileInfo(FmFileInfo* info, int* row) {
|
||||
QList<FolderModelItem>::iterator it = items.begin();
|
||||
int i = 0;
|
||||
while(it != items.end()) {
|
||||
FolderModelItem& item = *it;
|
||||
if(item.info == info) {
|
||||
*row = i;
|
||||
return it;
|
||||
}
|
||||
++it;
|
||||
++i;
|
||||
}
|
||||
return items.end();
|
||||
}
|
||||
|
||||
QStringList FolderModel::mimeTypes() const {
|
||||
qDebug("FolderModel::mimeTypes");
|
||||
QStringList types = QAbstractItemModel::mimeTypes();
|
||||
// now types contains "application/x-qabstractitemmodeldatalist"
|
||||
types << "text/uri-list";
|
||||
// types << "x-special/gnome-copied-files";
|
||||
return types;
|
||||
}
|
||||
|
||||
QMimeData* FolderModel::mimeData(const QModelIndexList& indexes) const {
|
||||
QMimeData* data = QAbstractItemModel::mimeData(indexes);
|
||||
qDebug("FolderModel::mimeData");
|
||||
// build a uri list
|
||||
QByteArray urilist;
|
||||
urilist.reserve(4096);
|
||||
|
||||
for(const auto &index : indexes) {
|
||||
FolderModelItem* item = itemFromIndex(index);
|
||||
if(item) {
|
||||
FmPath* path = fm_file_info_get_path(item->info);
|
||||
char* uri = fm_path_to_uri(path);
|
||||
urilist.append(uri);
|
||||
urilist.append('\n');
|
||||
g_free(uri);
|
||||
}
|
||||
}
|
||||
data->setData("text/uri-list", urilist);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
|
||||
qDebug("FolderModel::dropMimeData");
|
||||
if(!folder_)
|
||||
return false;
|
||||
FmPath* destPath;
|
||||
if(parent.isValid()) { // drop on an item
|
||||
FmFileInfo* info;
|
||||
if(row == -1 && column == -1)
|
||||
info = fileInfoFromIndex(parent);
|
||||
else {
|
||||
QModelIndex itemIndex = parent.child(row, column);
|
||||
info = fileInfoFromIndex(itemIndex);
|
||||
}
|
||||
if(info)
|
||||
destPath = fm_file_info_get_path(info);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else { // drop on blank area of the folder
|
||||
destPath = path();
|
||||
}
|
||||
|
||||
// FIXME: should we put this in dropEvent handler of FolderView instead?
|
||||
if(data->hasUrls()) {
|
||||
qDebug("drop action: %d", action);
|
||||
FmPathList* srcPaths = pathListFromQUrls(data->urls());
|
||||
switch(action) {
|
||||
case Qt::CopyAction:
|
||||
FileOperation::copyFiles(srcPaths, destPath);
|
||||
break;
|
||||
case Qt::MoveAction:
|
||||
FileOperation::moveFiles(srcPaths, destPath);
|
||||
break;
|
||||
case Qt::LinkAction:
|
||||
FileOperation::symlinkFiles(srcPaths, destPath);
|
||||
default:
|
||||
fm_path_list_unref(srcPaths);
|
||||
return false;
|
||||
}
|
||||
fm_path_list_unref(srcPaths);
|
||||
return true;
|
||||
}
|
||||
else if(data->hasFormat("application/x-qabstractitemmodeldatalist")) {
|
||||
return true;
|
||||
}
|
||||
return QAbstractListModel::dropMimeData(data, action, row, column, parent);
|
||||
}
|
||||
|
||||
Qt::DropActions FolderModel::supportedDropActions() const {
|
||||
qDebug("FolderModel::supportedDropActions");
|
||||
return Qt::CopyAction|Qt::MoveAction|Qt::LinkAction;
|
||||
}
|
||||
|
||||
// ask the model to load thumbnails of the specified size
|
||||
void FolderModel::cacheThumbnails(const int size) {
|
||||
QVector<QPair<int, int>>::iterator it = thumbnailRefCounts.begin();
|
||||
while (it != thumbnailRefCounts.end()) {
|
||||
if (it->first == size) {
|
||||
++it->second;
|
||||
return;
|
||||
} else ++it;
|
||||
}
|
||||
thumbnailRefCounts.append(QPair<int, int>(size, 1));
|
||||
}
|
||||
|
||||
// ask the model to free cached thumbnails of the specified size
|
||||
void FolderModel::releaseThumbnails(int size) {
|
||||
QVector<QPair<int, int> >::iterator it;
|
||||
for(it = thumbnailRefCounts.begin(); it != thumbnailRefCounts.end(); ++it) {
|
||||
if(it->first == size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(it != thumbnailRefCounts.end()) {
|
||||
--it->second;
|
||||
if(it->second == 0) {
|
||||
thumbnailRefCounts.erase(it);
|
||||
|
||||
// remove thumbnails that ara queued for loading from thumbnailResults
|
||||
QLinkedList<FmThumbnailLoader*>::iterator it;
|
||||
for(it = thumbnailResults.begin(); it != thumbnailResults.end();) {
|
||||
QLinkedList<FmThumbnailLoader*>::iterator next = it + 1;
|
||||
FmThumbnailLoader* res = *it;
|
||||
if(ThumbnailLoader::size(res) == size) {
|
||||
ThumbnailLoader::cancel(res);
|
||||
thumbnailResults.erase(it);
|
||||
}
|
||||
it = next;
|
||||
}
|
||||
|
||||
// remove all cached thumbnails of the specified size
|
||||
QList<FolderModelItem>::iterator itemIt;
|
||||
for(itemIt = items.begin(); itemIt != items.end(); ++itemIt) {
|
||||
FolderModelItem& item = *itemIt;
|
||||
item.removeThumbnail(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FolderModel::onThumbnailLoaded(FmThumbnailLoader* res, gpointer user_data) {
|
||||
FolderModel* pThis = reinterpret_cast<FolderModel*>(user_data);
|
||||
QLinkedList<FmThumbnailLoader*>::iterator it;
|
||||
for(it = pThis->thumbnailResults.begin(); it != pThis->thumbnailResults.end(); ++it) {
|
||||
if(*it == res) { // the thumbnail result is in our list
|
||||
pThis->thumbnailResults.erase(it); // remove it from the list
|
||||
FmFileInfo* info = ThumbnailLoader::fileInfo(res);
|
||||
int row = -1;
|
||||
// find the model item this thumbnail belongs to
|
||||
QList<FolderModelItem>::iterator it = pThis->findItemByFileInfo(info, &row);
|
||||
if(it != pThis->items.end()) {
|
||||
// the file is found in our model
|
||||
FolderModelItem& item = *it;
|
||||
QModelIndex index = pThis->createIndex(row, 0, (void*)&item);
|
||||
// store the image in the folder model item.
|
||||
int size = ThumbnailLoader::size(res);
|
||||
QImage image = ThumbnailLoader::image(res);
|
||||
FolderModelItem::Thumbnail* thumbnail = item.findThumbnail(size);
|
||||
thumbnail->image = image;
|
||||
// qDebug("thumbnail loaded for: %s, size: %d", item.displayName.toUtf8().constData(), size);
|
||||
if(image.isNull())
|
||||
thumbnail->status = FolderModelItem::ThumbnailFailed;
|
||||
else {
|
||||
thumbnail->status = FolderModelItem::ThumbnailLoaded;
|
||||
// FIXME: due to bugs in Qt's QStyledItemDelegate, if the image width and height
|
||||
// are not the same, painting errors will happen. It's quite unfortunate.
|
||||
// Let's do some padding to make its width and height equals.
|
||||
// This greatly decrease performance :-(
|
||||
// Later if we can re-implement our own item delegate, this can be avoided.
|
||||
QPixmap pixmap = QPixmap(size, size);
|
||||
pixmap.fill(QColor(0, 0, 0, 0)); // fill the pixmap with transparent color (alpha:0)
|
||||
QPainter painter(&pixmap);
|
||||
int x = (size - image.width()) / 2;
|
||||
int y = (size - image.height()) / 2;
|
||||
painter.drawImage(QPoint(x, y), image); // draw the image to the pixmap at center.
|
||||
// FIXME: should we cache QPixmap instead for performance reason?
|
||||
thumbnail->image = pixmap.toImage(); // convert it back to image
|
||||
|
||||
// tell the world that we have the thumbnail loaded
|
||||
Q_EMIT pThis->thumbnailLoaded(index, size);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get a thumbnail of size at the index
|
||||
// if a thumbnail is not yet loaded, this will initiate loading of the thumbnail.
|
||||
QImage FolderModel::thumbnailFromIndex(const QModelIndex& index, int size) {
|
||||
FolderModelItem* item = itemFromIndex(index);
|
||||
if(item) {
|
||||
FolderModelItem::Thumbnail* thumbnail = item->findThumbnail(size);
|
||||
// qDebug("FolderModel::thumbnailFromIndex: %d, %s", thumbnail->status, item->displayName.toUtf8().data());
|
||||
switch(thumbnail->status) {
|
||||
case FolderModelItem::ThumbnailNotChecked: {
|
||||
// load the thumbnail
|
||||
FmThumbnailLoader* res = ThumbnailLoader::load(item->info, size, onThumbnailLoaded, this);
|
||||
thumbnailResults.push_back(res);
|
||||
thumbnail->status = FolderModelItem::ThumbnailLoading;
|
||||
break;
|
||||
}
|
||||
case FolderModelItem::ThumbnailLoaded:
|
||||
return thumbnail->image;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
return QImage();
|
||||
}
|
||||
|
||||
void FolderModel::updateIcons() {
|
||||
QList<FolderModelItem>::iterator it = items.begin();
|
||||
for(;it != items.end(); ++it) {
|
||||
(*it).updateIcon();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FOLDERMODEL_H
|
||||
#define FM_FOLDERMODEL_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QAbstractListModel>
|
||||
#include <QIcon>
|
||||
#include <QImage>
|
||||
#include <libfm/fm.h>
|
||||
#include <QList>
|
||||
#include <QVector>
|
||||
#include <QLinkedList>
|
||||
#include <QPair>
|
||||
#include "foldermodelitem.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FolderModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
enum Role {
|
||||
FileInfoRole = Qt::UserRole
|
||||
};
|
||||
|
||||
enum ColumnId {
|
||||
ColumnFileName,
|
||||
ColumnFileType,
|
||||
ColumnFileSize,
|
||||
ColumnFileMTime,
|
||||
ColumnFileOwner,
|
||||
NumOfColumns
|
||||
};
|
||||
|
||||
public:
|
||||
FolderModel();
|
||||
virtual ~FolderModel();
|
||||
|
||||
FmFolder* folder() {
|
||||
return folder_;
|
||||
}
|
||||
void setFolder(FmFolder* new_folder);
|
||||
|
||||
FmPath* path() {
|
||||
return folder_ ? fm_folder_get_path(folder_) : NULL;
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex & parent = QModelIndex()) const;
|
||||
int columnCount (const QModelIndex & parent) const;
|
||||
QVariant data(const QModelIndex & index, int role) const;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||
QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const;
|
||||
QModelIndex parent( const QModelIndex & index ) const;
|
||||
// void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex & index) const;
|
||||
|
||||
virtual QStringList mimeTypes() const;
|
||||
virtual QMimeData* mimeData(const QModelIndexList & indexes) const;
|
||||
virtual Qt::DropActions supportedDropActions() const;
|
||||
virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
|
||||
|
||||
FmFileInfo* fileInfoFromIndex(const QModelIndex& index) const;
|
||||
FolderModelItem* itemFromIndex(const QModelIndex& index) const;
|
||||
QImage thumbnailFromIndex(const QModelIndex& index, int size);
|
||||
|
||||
void cacheThumbnails(int size);
|
||||
void releaseThumbnails(int size);
|
||||
|
||||
Q_SIGNALS:
|
||||
void thumbnailLoaded(const QModelIndex& index, int size);
|
||||
|
||||
public Q_SLOTS:
|
||||
void updateIcons();
|
||||
|
||||
protected:
|
||||
static void onStartLoading(FmFolder* folder, gpointer user_data);
|
||||
static void onFinishLoading(FmFolder* folder, gpointer user_data);
|
||||
static void onFilesAdded(FmFolder* folder, GSList* files, gpointer user_data);
|
||||
static void onFilesChanged(FmFolder* folder, GSList* files, gpointer user_data);
|
||||
static void onFilesRemoved(FmFolder* folder, GSList* files, gpointer user_data);
|
||||
static void onThumbnailLoaded(FmThumbnailLoader *res, gpointer user_data);
|
||||
|
||||
void insertFiles(int row, FmFileInfoList* files);
|
||||
void removeAll();
|
||||
QList<FolderModelItem>::iterator findItemByPath(FmPath* path, int* row);
|
||||
QList<FolderModelItem>::iterator findItemByName(const char* name, int* row);
|
||||
QList<FolderModelItem>::iterator findItemByFileInfo(FmFileInfo* info, int* row);
|
||||
|
||||
private:
|
||||
FmFolder* folder_;
|
||||
// FIXME: should we use a hash table here so item lookup becomes much faster?
|
||||
QList<FolderModelItem> items;
|
||||
|
||||
// record what size of thumbnails we should cache in an array of <size, refCount> pairs.
|
||||
QVector<QPair<int, int> > thumbnailRefCounts;
|
||||
QLinkedList<FmThumbnailLoader*> thumbnailResults;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FOLDERMODEL_H
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "foldermodelitem.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FolderModelItem::FolderModelItem(FmFileInfo* _info):
|
||||
info(fm_file_info_ref(_info)) {
|
||||
displayName = QString::fromUtf8(fm_file_info_get_disp_name(info));
|
||||
icon = IconTheme::icon(fm_file_info_get_icon(_info));
|
||||
thumbnails.reserve(2);
|
||||
}
|
||||
|
||||
FolderModelItem::FolderModelItem(const FolderModelItem& other) {
|
||||
info = other.info ? fm_file_info_ref(other.info) : NULL;
|
||||
displayName = QString::fromUtf8(fm_file_info_get_disp_name(info));
|
||||
icon = other.icon;
|
||||
thumbnails = other.thumbnails;
|
||||
}
|
||||
|
||||
FolderModelItem::~FolderModelItem() {
|
||||
if(info)
|
||||
fm_file_info_unref(info);
|
||||
}
|
||||
|
||||
// find thumbnail of the specified size
|
||||
// The returned thumbnail item is temporary and short-lived
|
||||
// If you need to use the struct later, copy it to your own struct to keep it.
|
||||
FolderModelItem::Thumbnail* FolderModelItem::findThumbnail(int size) {
|
||||
QVector<Thumbnail>::iterator it;
|
||||
for(it = thumbnails.begin(); it != thumbnails.end(); ++it) {
|
||||
if(it->size == size) { // an image of the same size is found
|
||||
return it;
|
||||
}
|
||||
}
|
||||
if(it == thumbnails.end()) {
|
||||
Thumbnail thumbnail;
|
||||
thumbnail.status = ThumbnailNotChecked;
|
||||
thumbnail.size = size;
|
||||
thumbnails.append(thumbnail);
|
||||
}
|
||||
return &thumbnails.back();
|
||||
}
|
||||
|
||||
// remove cached thumbnail of the specified size
|
||||
void FolderModelItem::removeThumbnail(int size) {
|
||||
QVector<Thumbnail>::iterator it;
|
||||
for(it = thumbnails.begin(); it != thumbnails.end(); ++it) {
|
||||
if(it->size == size) { // an image of the same size is found
|
||||
thumbnails.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// cache the thumbnail of the specified size in the folder item
|
||||
void FolderModelItem::setThumbnail(int size, QImage image) {
|
||||
QVector<Thumbnail>::iterator it;
|
||||
for(it = thumbnails.begin(); it != thumbnails.end(); ++it) {
|
||||
if(it->size == size) { // an image of the same size already exists
|
||||
it->image = image; // replace it
|
||||
it->status = ThumbnailLoaded;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(it == thumbnails.end()) { // the image is not found
|
||||
Thumbnail thumbnail;
|
||||
thumbnail.size = size;
|
||||
thumbnail.status = ThumbnailLoaded;
|
||||
thumbnail.image = image;
|
||||
thumbnails.append(thumbnail); // add a new entry
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FOLDERMODELITEM_H
|
||||
#define FM_FOLDERMODELITEM_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <libfm/fm.h>
|
||||
#include <QImage>
|
||||
#include <QString>
|
||||
#include <QIcon>
|
||||
#include <QVector>
|
||||
#include "icontheme.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FolderModelItem {
|
||||
public:
|
||||
|
||||
enum ThumbnailStatus {
|
||||
ThumbnailNotChecked,
|
||||
ThumbnailLoading,
|
||||
ThumbnailLoaded,
|
||||
ThumbnailFailed
|
||||
};
|
||||
|
||||
struct Thumbnail {
|
||||
int size;
|
||||
ThumbnailStatus status;
|
||||
QImage image;
|
||||
};
|
||||
|
||||
public:
|
||||
FolderModelItem(FmFileInfo* _info);
|
||||
FolderModelItem(const FolderModelItem& other);
|
||||
virtual ~FolderModelItem();
|
||||
|
||||
Thumbnail* findThumbnail(int size);
|
||||
// void setThumbnail(int size, QImage image);
|
||||
void removeThumbnail(int size);
|
||||
|
||||
void updateIcon() {
|
||||
icon = IconTheme::icon(fm_file_info_get_icon(info));
|
||||
}
|
||||
|
||||
QString displayName;
|
||||
QIcon icon;
|
||||
FmFileInfo* info;
|
||||
QVector<Thumbnail> thumbnails;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FOLDERMODELITEM_H
|
@ -0,0 +1,984 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "folderview.h"
|
||||
#include "foldermodel.h"
|
||||
#include <QHeaderView>
|
||||
#include <QVBoxLayout>
|
||||
#include <QContextMenuEvent>
|
||||
#include "proxyfoldermodel.h"
|
||||
#include "folderitemdelegate.h"
|
||||
#include "dndactionmenu.h"
|
||||
#include "filemenu.h"
|
||||
#include "foldermenu.h"
|
||||
#include "filelauncher.h"
|
||||
#include <QTimer>
|
||||
#include <QDate>
|
||||
#include <QDebug>
|
||||
#include <QMimeData>
|
||||
#include <QHoverEvent>
|
||||
#include <QApplication>
|
||||
#include <QScrollBar>
|
||||
#include <QMetaType>
|
||||
#include "folderview_p.h"
|
||||
|
||||
Q_DECLARE_OPAQUE_POINTER(FmFileInfo*)
|
||||
|
||||
using namespace Fm;
|
||||
|
||||
FolderViewListView::FolderViewListView(QWidget* parent):
|
||||
QListView(parent),
|
||||
activationAllowed_(true) {
|
||||
connect(this, &QListView::activated, this, &FolderViewListView::activation);
|
||||
}
|
||||
|
||||
FolderViewListView::~FolderViewListView() {
|
||||
}
|
||||
|
||||
void FolderViewListView::startDrag(Qt::DropActions supportedActions) {
|
||||
if(movement() != Static)
|
||||
QListView::startDrag(supportedActions);
|
||||
else
|
||||
QAbstractItemView::startDrag(supportedActions);
|
||||
}
|
||||
|
||||
void FolderViewListView::mousePressEvent(QMouseEvent* event) {
|
||||
QListView::mousePressEvent(event);
|
||||
static_cast<FolderView*>(parent())->childMousePressEvent(event);
|
||||
}
|
||||
|
||||
QModelIndex FolderViewListView::indexAt(const QPoint& point) const {
|
||||
QModelIndex index = QListView::indexAt(point);
|
||||
// NOTE: QListView has a severe design flaw here. It does hit-testing based on the
|
||||
// total bound rect of the item. The width of an item is determined by max(icon_width, text_width).
|
||||
// So if the text label is much wider than the icon, when you click outside the icon but
|
||||
// the point is still within the outer bound rect, the item is still selected.
|
||||
// This results in very poor usability. Let's do precise hit-testing here.
|
||||
// An item is hit only when the point is in the icon or text label.
|
||||
// If the point is in the bound rectangle but outside the icon or text, it should not be selected.
|
||||
if(viewMode() == QListView::IconMode && index.isValid()) {
|
||||
// FIXME: this hack only improves the usability partially. We still need more precise sizeHint handling.
|
||||
// FolderItemDelegate* delegate = static_cast<FolderItemDelegate*>(itemDelegateForColumn(FolderModel::ColumnFileName));
|
||||
// Q_ASSERT(delegate != nullptr);
|
||||
// We use the grid size - (2, 2) as the size of the bounding rectangle of the whole item.
|
||||
// The width of the text label hence is gridSize.width - 2, and the width and height of the icon is from iconSize().
|
||||
QRect visRect = visualRect(index); // visibal area on the screen
|
||||
QSize itemSize = gridSize();
|
||||
itemSize.setWidth(itemSize.width() - 2);
|
||||
itemSize.setHeight(itemSize.height() - 2);
|
||||
QSize _iconSize = iconSize();
|
||||
int textHeight = itemSize.height() - _iconSize.height();
|
||||
if(point.y() < visRect.bottom() - textHeight) {
|
||||
// the point is in the icon area, not over the text label
|
||||
int iconXMargin = (itemSize.width() - _iconSize.width()) / 2;
|
||||
if(point.x() < (visRect.left() + iconXMargin) || point.x() > (visRect.right() - iconXMargin))
|
||||
return QModelIndex();
|
||||
}
|
||||
// qDebug() << "visualRect: " << visRect << "point:" << point;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
// NOTE:
|
||||
// QListView has a problem which I consider a bug or a design flaw.
|
||||
// When you set movement property to Static, theoratically the icons
|
||||
// should not be movable. However, if you turned on icon mode,
|
||||
// the icons becomes freely movable despite the value of movement is Static.
|
||||
// To overcome this bug, we override all drag handling methods, and
|
||||
// call QAbstractItemView directly, bypassing QListView.
|
||||
// In this way, we can workaround the buggy behavior.
|
||||
// The drag handlers of QListView basically does the same things
|
||||
// as its parent QAbstractItemView, but it also stores the currently
|
||||
// dragged item and paint them in the view as needed.
|
||||
// TODO: I really should file a bug report to Qt developers.
|
||||
|
||||
void FolderViewListView::dragEnterEvent(QDragEnterEvent* event) {
|
||||
if(movement() != Static)
|
||||
QListView::dragEnterEvent(event);
|
||||
else
|
||||
QAbstractItemView::dragEnterEvent(event);
|
||||
qDebug("dragEnterEvent");
|
||||
//static_cast<FolderView*>(parent())->childDragEnterEvent(event);
|
||||
}
|
||||
|
||||
void FolderViewListView::dragLeaveEvent(QDragLeaveEvent* e) {
|
||||
if(movement() != Static)
|
||||
QListView::dragLeaveEvent(e);
|
||||
else
|
||||
QAbstractItemView::dragLeaveEvent(e);
|
||||
static_cast<FolderView*>(parent())->childDragLeaveEvent(e);
|
||||
}
|
||||
|
||||
void FolderViewListView::dragMoveEvent(QDragMoveEvent* e) {
|
||||
if(movement() != Static)
|
||||
QListView::dragMoveEvent(e);
|
||||
else
|
||||
QAbstractItemView::dragMoveEvent(e);
|
||||
static_cast<FolderView*>(parent())->childDragMoveEvent(e);
|
||||
}
|
||||
|
||||
void FolderViewListView::dropEvent(QDropEvent* e) {
|
||||
|
||||
static_cast<FolderView*>(parent())->childDropEvent(e);
|
||||
|
||||
if(movement() != Static)
|
||||
QListView::dropEvent(e);
|
||||
else
|
||||
QAbstractItemView::dropEvent(e);
|
||||
}
|
||||
|
||||
void FolderViewListView::mouseReleaseEvent(QMouseEvent* event) {
|
||||
bool activationWasAllowed = activationAllowed_;
|
||||
if ((!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) {
|
||||
activationAllowed_ = false;
|
||||
}
|
||||
|
||||
QListView::mouseReleaseEvent(event);
|
||||
|
||||
activationAllowed_ = activationWasAllowed;
|
||||
}
|
||||
|
||||
void FolderViewListView::mouseDoubleClickEvent(QMouseEvent* event) {
|
||||
bool activationWasAllowed = activationAllowed_;
|
||||
if ((style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) {
|
||||
activationAllowed_ = false;
|
||||
}
|
||||
|
||||
QListView::mouseDoubleClickEvent(event);
|
||||
|
||||
activationAllowed_ = activationWasAllowed;
|
||||
}
|
||||
|
||||
void FolderViewListView::activation(const QModelIndex &index) {
|
||||
if (activationAllowed_) {
|
||||
Q_EMIT activatedFiltered(index);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
FolderViewTreeView::FolderViewTreeView(QWidget* parent):
|
||||
QTreeView(parent),
|
||||
layoutTimer_(nullptr),
|
||||
doingLayout_(false),
|
||||
activationAllowed_(true) {
|
||||
|
||||
header()->setStretchLastSection(true);
|
||||
setIndentation(0);
|
||||
|
||||
connect(this, &QTreeView::activated, this, &FolderViewTreeView::activation);
|
||||
}
|
||||
|
||||
FolderViewTreeView::~FolderViewTreeView() {
|
||||
if(layoutTimer_)
|
||||
delete layoutTimer_;
|
||||
}
|
||||
|
||||
void FolderViewTreeView::setModel(QAbstractItemModel* model) {
|
||||
QTreeView::setModel(model);
|
||||
layoutColumns();
|
||||
if(ProxyFolderModel* proxyModel = qobject_cast<ProxyFolderModel*>(model)) {
|
||||
connect(proxyModel, &ProxyFolderModel::sortFilterChanged, this, &FolderViewTreeView::onSortFilterChanged,
|
||||
Qt::UniqueConnection);
|
||||
onSortFilterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void FolderViewTreeView::mousePressEvent(QMouseEvent* event) {
|
||||
QTreeView::mousePressEvent(event);
|
||||
static_cast<FolderView*>(parent())->childMousePressEvent(event);
|
||||
}
|
||||
|
||||
void FolderViewTreeView::dragEnterEvent(QDragEnterEvent* event) {
|
||||
QTreeView::dragEnterEvent(event);
|
||||
static_cast<FolderView*>(parent())->childDragEnterEvent(event);
|
||||
}
|
||||
|
||||
void FolderViewTreeView::dragLeaveEvent(QDragLeaveEvent* e) {
|
||||
QTreeView::dragLeaveEvent(e);
|
||||
static_cast<FolderView*>(parent())->childDragLeaveEvent(e);
|
||||
}
|
||||
|
||||
void FolderViewTreeView::dragMoveEvent(QDragMoveEvent* e) {
|
||||
QTreeView::dragMoveEvent(e);
|
||||
static_cast<FolderView*>(parent())->childDragMoveEvent(e);
|
||||
}
|
||||
|
||||
void FolderViewTreeView::dropEvent(QDropEvent* e) {
|
||||
static_cast<FolderView*>(parent())->childDropEvent(e);
|
||||
QTreeView::dropEvent(e);
|
||||
}
|
||||
|
||||
// the default list mode of QListView handles column widths
|
||||
// very badly (worse than gtk+) and it's not very flexible.
|
||||
// so, let's handle column widths outselves.
|
||||
void FolderViewTreeView::layoutColumns() {
|
||||
// qDebug("layoutColumns");
|
||||
if(!model())
|
||||
return;
|
||||
doingLayout_ = true;
|
||||
QHeaderView* headerView = header();
|
||||
// the width that's available for showing the columns.
|
||||
int availWidth = viewport()->contentsRect().width();
|
||||
int desiredWidth = 0;
|
||||
|
||||
// get the width that every column want
|
||||
int numCols = headerView->count();
|
||||
if(numCols > 0) {
|
||||
int* widths = new int[numCols]; // array to store the widths every column needs
|
||||
int column;
|
||||
for(column = 0; column < numCols; ++column) {
|
||||
int columnId = headerView->logicalIndex(column);
|
||||
// get the size that the column needs
|
||||
widths[column] = sizeHintForColumn(columnId);
|
||||
// compute the total width needed
|
||||
desiredWidth += widths[column];
|
||||
}
|
||||
|
||||
int filenameColumn = headerView->visualIndex(FolderModel::ColumnFileName);
|
||||
// if the total witdh we want exceeds the available space
|
||||
if(desiredWidth > availWidth) {
|
||||
// Compute the width available for the filename column
|
||||
int filenameAvailWidth = availWidth - desiredWidth + widths[filenameColumn];
|
||||
|
||||
// Compute the minimum acceptable width for the filename column
|
||||
int filenameMinWidth = qMin(200, sizeHintForColumn(filenameColumn));
|
||||
|
||||
if (filenameAvailWidth > filenameMinWidth) {
|
||||
// Shrink the filename column to the available width
|
||||
widths[filenameColumn] = filenameAvailWidth;
|
||||
}
|
||||
else {
|
||||
// Set the filename column to its minimum width
|
||||
widths[filenameColumn] = filenameMinWidth;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Fill the extra available space with the filename column
|
||||
widths[filenameColumn] += availWidth - desiredWidth;
|
||||
}
|
||||
|
||||
// really do the resizing for every column
|
||||
for(int column = 0; column < numCols; ++column) {
|
||||
headerView->resizeSection(column, widths[column]);
|
||||
}
|
||||
delete []widths;
|
||||
}
|
||||
doingLayout_ = false;
|
||||
|
||||
if(layoutTimer_) {
|
||||
delete layoutTimer_;
|
||||
layoutTimer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void FolderViewTreeView::resizeEvent(QResizeEvent* event) {
|
||||
QAbstractItemView::resizeEvent(event);
|
||||
// prevent endless recursion.
|
||||
// When manually resizing columns, at the point where a horizontal scroll
|
||||
// bar has to be inserted or removed, the vertical size changes, a resize
|
||||
// event occurs and the column headers are flickering badly if the column
|
||||
// layout is modified at this point. Therefore only layout the columns if
|
||||
// the horizontal size changes.
|
||||
if(!doingLayout_ && event->size().width() != event->oldSize().width())
|
||||
layoutColumns(); // layoutColumns() also triggers resizeEvent
|
||||
}
|
||||
|
||||
void FolderViewTreeView::rowsInserted(const QModelIndex& parent, int start, int end) {
|
||||
QTreeView::rowsInserted(parent, start, end);
|
||||
queueLayoutColumns();
|
||||
}
|
||||
|
||||
void FolderViewTreeView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) {
|
||||
QTreeView::rowsAboutToBeRemoved(parent, start, end);
|
||||
queueLayoutColumns();
|
||||
}
|
||||
|
||||
void FolderViewTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) {
|
||||
QTreeView::dataChanged(topLeft, bottomRight);
|
||||
// FIXME: this will be very inefficient
|
||||
// queueLayoutColumns();
|
||||
}
|
||||
|
||||
void FolderViewTreeView::reset() {
|
||||
// Sometimes when the content of the model is radically changed, Qt does reset()
|
||||
// on the model rather than doing large amount of insertion and deletion.
|
||||
// This is for performance reason so in this case rowsInserted() and rowsAboutToBeRemoved()
|
||||
// might not be called. Hence we also have to re-layout the columns when the model is reset.
|
||||
// This fixes bug #190
|
||||
// https://github.com/lxde/pcmanfm-qt/issues/190
|
||||
QTreeView::reset();
|
||||
queueLayoutColumns();
|
||||
}
|
||||
|
||||
void FolderViewTreeView::queueLayoutColumns() {
|
||||
// qDebug("queueLayoutColumns");
|
||||
if(!layoutTimer_) {
|
||||
layoutTimer_ = new QTimer();
|
||||
layoutTimer_->setSingleShot(true);
|
||||
layoutTimer_->setInterval(0);
|
||||
connect(layoutTimer_, &QTimer::timeout, this, &FolderViewTreeView::layoutColumns);
|
||||
}
|
||||
layoutTimer_->start();
|
||||
}
|
||||
|
||||
void FolderViewTreeView::mouseReleaseEvent(QMouseEvent* event) {
|
||||
bool activationWasAllowed = activationAllowed_;
|
||||
if ((!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) {
|
||||
activationAllowed_ = false;
|
||||
}
|
||||
|
||||
QTreeView::mouseReleaseEvent(event);
|
||||
|
||||
activationAllowed_ = activationWasAllowed;
|
||||
}
|
||||
|
||||
void FolderViewTreeView::mouseDoubleClickEvent(QMouseEvent* event) {
|
||||
bool activationWasAllowed = activationAllowed_;
|
||||
if ((style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) {
|
||||
activationAllowed_ = false;
|
||||
}
|
||||
|
||||
QTreeView::mouseDoubleClickEvent(event);
|
||||
|
||||
activationAllowed_ = activationWasAllowed;
|
||||
}
|
||||
|
||||
void FolderViewTreeView::activation(const QModelIndex &index) {
|
||||
if (activationAllowed_) {
|
||||
Q_EMIT activatedFiltered(index);
|
||||
}
|
||||
}
|
||||
|
||||
void FolderViewTreeView::onSortFilterChanged() {
|
||||
if(QSortFilterProxyModel* proxyModel = qobject_cast<QSortFilterProxyModel*>(model())) {
|
||||
header()->setSortIndicatorShown(true);
|
||||
header()->setSortIndicator(proxyModel->sortColumn(), proxyModel->sortOrder());
|
||||
if (!isSortingEnabled()) {
|
||||
setSortingEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
FolderView::FolderView(ViewMode _mode, QWidget* parent):
|
||||
QWidget(parent),
|
||||
view(nullptr),
|
||||
mode((ViewMode)0),
|
||||
autoSelectionDelay_(600),
|
||||
autoSelectionTimer_(nullptr),
|
||||
selChangedTimer_(nullptr),
|
||||
fileLauncher_(nullptr),
|
||||
model_(nullptr) {
|
||||
|
||||
iconSize_[IconMode - FirstViewMode] = QSize(48, 48);
|
||||
iconSize_[CompactMode - FirstViewMode] = QSize(24, 24);
|
||||
iconSize_[ThumbnailMode - FirstViewMode] = QSize(128, 128);
|
||||
iconSize_[DetailedListMode - FirstViewMode] = QSize(24, 24);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setMargin(0);
|
||||
setLayout(layout);
|
||||
|
||||
setViewMode(_mode);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
connect(this, &FolderView::clicked, this, &FolderView::onFileClicked);
|
||||
}
|
||||
|
||||
FolderView::~FolderView() {
|
||||
}
|
||||
|
||||
void FolderView::onItemActivated(QModelIndex index) {
|
||||
if(index.isValid() && index.model()) {
|
||||
QVariant data = index.model()->data(index, FolderModel::FileInfoRole);
|
||||
FmFileInfo* info = (FmFileInfo*)data.value<void*>();
|
||||
if(info) {
|
||||
if (!(QApplication::keyboardModifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier))) {
|
||||
Q_EMIT clicked(ActivatedClick, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FolderView::onSelChangedTimeout() {
|
||||
selChangedTimer_->deleteLater();
|
||||
selChangedTimer_ = nullptr;
|
||||
|
||||
QItemSelectionModel* selModel = selectionModel();
|
||||
int nSel = 0;
|
||||
if(viewMode() == DetailedListMode)
|
||||
nSel = selModel->selectedRows().count();
|
||||
else {
|
||||
nSel = selModel->selectedIndexes().count();
|
||||
}
|
||||
// qDebug()<<"selected:" << nSel;
|
||||
Q_EMIT selChanged(nSel); // FIXME: this is inefficient
|
||||
}
|
||||
|
||||
void FolderView::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {
|
||||
// It's possible that the selected items change too often and this slot gets called for thousands of times.
|
||||
// For example, when you select thousands of files and delete them, we will get one selectionChanged() event
|
||||
// for every deleted file. So, we use a timer to delay the handling to avoid too frequent updates of the UI.
|
||||
if(!selChangedTimer_) {
|
||||
selChangedTimer_ = new QTimer(this);
|
||||
selChangedTimer_->setSingleShot(true);
|
||||
connect(selChangedTimer_, &QTimer::timeout, this, &FolderView::onSelChangedTimeout);
|
||||
selChangedTimer_->start(200);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FolderView::setViewMode(ViewMode _mode) {
|
||||
if(_mode == mode) // if it's the same more, ignore
|
||||
return;
|
||||
// FIXME: retain old selection
|
||||
|
||||
// since only detailed list mode uses QTreeView, and others
|
||||
// all use QListView, it's wise to preserve QListView when possible.
|
||||
bool recreateView = false;
|
||||
if(view && (mode == DetailedListMode || _mode == DetailedListMode)) {
|
||||
delete view; // FIXME: no virtual dtor?
|
||||
view = nullptr;
|
||||
recreateView = true;
|
||||
}
|
||||
mode = _mode;
|
||||
QSize iconSize = iconSize_[mode - FirstViewMode];
|
||||
|
||||
if(mode == DetailedListMode) {
|
||||
FolderViewTreeView* treeView = new FolderViewTreeView(this);
|
||||
connect(treeView, &FolderViewTreeView::activatedFiltered, this, &FolderView::onItemActivated);
|
||||
|
||||
view = treeView;
|
||||
treeView->setItemsExpandable(false);
|
||||
treeView->setRootIsDecorated(false);
|
||||
treeView->setAllColumnsShowFocus(false);
|
||||
|
||||
// set our own custom delegate
|
||||
FolderItemDelegate* delegate = new FolderItemDelegate(treeView);
|
||||
treeView->setItemDelegateForColumn(FolderModel::ColumnFileName, delegate);
|
||||
}
|
||||
else {
|
||||
FolderViewListView* listView;
|
||||
if(view)
|
||||
listView = static_cast<FolderViewListView*>(view);
|
||||
else {
|
||||
listView = new FolderViewListView(this);
|
||||
connect(listView, &FolderViewListView::activatedFiltered, this, &FolderView::onItemActivated);
|
||||
view = listView;
|
||||
}
|
||||
// set our own custom delegate
|
||||
FolderItemDelegate* delegate = new FolderItemDelegate(listView);
|
||||
listView->setItemDelegateForColumn(FolderModel::ColumnFileName, delegate);
|
||||
// FIXME: should we expose the delegate?
|
||||
listView->setMovement(QListView::Static);
|
||||
listView->setResizeMode(QListView::Adjust);
|
||||
listView->setWrapping(true);
|
||||
switch(mode) {
|
||||
case IconMode: {
|
||||
listView->setViewMode(QListView::IconMode);
|
||||
listView->setWordWrap(true);
|
||||
listView->setFlow(QListView::LeftToRight);
|
||||
break;
|
||||
}
|
||||
case CompactMode: {
|
||||
listView->setViewMode(QListView::ListMode);
|
||||
listView->setWordWrap(false);
|
||||
listView->setFlow(QListView::QListView::TopToBottom);
|
||||
break;
|
||||
}
|
||||
case ThumbnailMode: {
|
||||
listView->setViewMode(QListView::IconMode);
|
||||
listView->setWordWrap(true);
|
||||
listView->setFlow(QListView::LeftToRight);
|
||||
break;
|
||||
}
|
||||
default:;
|
||||
}
|
||||
updateGridSize();
|
||||
}
|
||||
if(view) {
|
||||
// we have to install the event filter on the viewport instead of the view itself.
|
||||
view->viewport()->installEventFilter(this);
|
||||
// we want the QEvent::HoverMove event for single click + auto-selection support
|
||||
view->viewport()->setAttribute(Qt::WA_Hover, true);
|
||||
view->setContextMenuPolicy(Qt::NoContextMenu); // defer the context menu handling to parent widgets
|
||||
view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
view->setIconSize(iconSize);
|
||||
|
||||
view->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
layout()->addWidget(view);
|
||||
|
||||
// enable dnd
|
||||
view->setDragEnabled(true);
|
||||
view->setAcceptDrops(true);
|
||||
view->setDragDropMode(QAbstractItemView::DragDrop);
|
||||
view->setDropIndicatorShown(true);
|
||||
|
||||
if(model_) {
|
||||
// FIXME: preserve selections
|
||||
model_->setThumbnailSize(iconSize.width());
|
||||
view->setModel(model_);
|
||||
if(recreateView)
|
||||
connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FolderView::onSelectionChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set proper grid size for the QListView based on current view mode, icon size, and font size.
|
||||
void FolderView::updateGridSize() {
|
||||
if(mode == DetailedListMode || !view)
|
||||
return;
|
||||
FolderViewListView* listView = static_cast<FolderViewListView*>(view);
|
||||
QSize icon = iconSize(mode); // size of the icon
|
||||
QFontMetrics fm = fontMetrics(); // size of current font
|
||||
QSize grid; // the final grid size
|
||||
switch(mode) {
|
||||
case IconMode:
|
||||
case ThumbnailMode: {
|
||||
// NOTE by PCMan about finding the optimal text label size:
|
||||
// The average filename length on my root filesystem is roughly 18-20 chars.
|
||||
// So, a reasonable size for the text label is about 10 chars each line since string of this length
|
||||
// can be shown in two lines. If you consider word wrap, then the result is around 10 chars per word.
|
||||
// In average, 10 char per line should be enough to display a "word" in the filename without breaking.
|
||||
// The values can be estimated with this command:
|
||||
// > find / | xargs basename -a | sed -e s'/[_-]/ /g' | wc -mcw
|
||||
// However, this average only applies to English. For some Asian characters, such as Chinese chars,
|
||||
// each char actually takes doubled space. To be safe, we use 13 chars per line x average char width
|
||||
// to get a nearly optimal width for the text label. As most of the filenames have less than 40 chars
|
||||
// 13 chars x 3 lines should be enough to show the full filenames for most files.
|
||||
int textWidth = fm.averageCharWidth() * 13;
|
||||
int textHeight = fm.lineSpacing() * 3;
|
||||
grid.setWidth(qMax(icon.width(), textWidth) + 4); // a margin of 2 px for selection rects
|
||||
grid.setHeight(icon.height() + textHeight + 4); // a margin of 2 px for selection rects
|
||||
break;
|
||||
}
|
||||
default:
|
||||
; // do not use grid size
|
||||
}
|
||||
if(mode == IconMode || mode == ThumbnailMode)
|
||||
listView->setGridSize(grid + QSize(6, 6)); // a margin of 6 px for every cell
|
||||
else
|
||||
listView->setGridSize(grid);
|
||||
FolderItemDelegate* delegate = static_cast<FolderItemDelegate*>(listView->itemDelegateForColumn(FolderModel::ColumnFileName));
|
||||
delegate->setGridSize(grid);
|
||||
}
|
||||
|
||||
void FolderView::setIconSize(ViewMode mode, QSize size) {
|
||||
Q_ASSERT(mode >= FirstViewMode && mode <= LastViewMode);
|
||||
iconSize_[mode - FirstViewMode] = size;
|
||||
if(viewMode() == mode) {
|
||||
view->setIconSize(size);
|
||||
if(model_)
|
||||
model_->setThumbnailSize(size.width());
|
||||
updateGridSize();
|
||||
}
|
||||
}
|
||||
|
||||
QSize FolderView::iconSize(ViewMode mode) const {
|
||||
Q_ASSERT(mode >= FirstViewMode && mode <= LastViewMode);
|
||||
return iconSize_[mode - FirstViewMode];
|
||||
}
|
||||
|
||||
FolderView::ViewMode FolderView::viewMode() const {
|
||||
return mode;
|
||||
}
|
||||
|
||||
void FolderView::setAutoSelectionDelay(int delay) {
|
||||
autoSelectionDelay_ = delay;
|
||||
}
|
||||
|
||||
QAbstractItemView* FolderView::childView() const {
|
||||
return view;
|
||||
}
|
||||
|
||||
ProxyFolderModel* FolderView::model() const {
|
||||
return model_;
|
||||
}
|
||||
|
||||
void FolderView::setModel(ProxyFolderModel* model) {
|
||||
if(view) {
|
||||
view->setModel(model);
|
||||
QSize iconSize = iconSize_[mode - FirstViewMode];
|
||||
model->setThumbnailSize(iconSize.width());
|
||||
if(view->selectionModel())
|
||||
connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FolderView::onSelectionChanged);
|
||||
}
|
||||
if(model_)
|
||||
delete model_;
|
||||
model_ = model;
|
||||
}
|
||||
|
||||
bool FolderView::event(QEvent* event) {
|
||||
switch(event->type()) {
|
||||
case QEvent::StyleChange:
|
||||
break;
|
||||
case QEvent::FontChange:
|
||||
updateGridSize();
|
||||
break;
|
||||
};
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
void FolderView::contextMenuEvent(QContextMenuEvent* event) {
|
||||
QWidget::contextMenuEvent(event);
|
||||
QPoint pos = event->pos();
|
||||
QPoint view_pos = view->mapFromParent(pos);
|
||||
QPoint viewport_pos = view->viewport()->mapFromParent(view_pos);
|
||||
emitClickedAt(ContextMenuClick, viewport_pos);
|
||||
}
|
||||
|
||||
void FolderView::childMousePressEvent(QMouseEvent* event) {
|
||||
// called from mousePressEvent() of child view
|
||||
Qt::MouseButton button = event->button();
|
||||
if(button == Qt::MiddleButton) {
|
||||
emitClickedAt(MiddleClick, event->pos());
|
||||
} else if (button == Qt::BackButton) {
|
||||
Q_EMIT clickedBack();
|
||||
} else if (button == Qt::ForwardButton) {
|
||||
Q_EMIT clickedForward();
|
||||
}
|
||||
}
|
||||
|
||||
void FolderView::emitClickedAt(ClickType type, const QPoint& pos) {
|
||||
// indexAt() needs a point in "viewport" coordinates.
|
||||
QModelIndex index = view->indexAt(pos);
|
||||
if(index.isValid()) {
|
||||
QVariant data = index.data(FolderModel::FileInfoRole);
|
||||
FmFileInfo* info = reinterpret_cast<FmFileInfo*>(data.value<void*>());
|
||||
Q_EMIT clicked(type, info);
|
||||
}
|
||||
else {
|
||||
// FIXME: should we show popup menu for the selected files instead
|
||||
// if there are selected files?
|
||||
if(type == ContextMenuClick) {
|
||||
// clear current selection if clicked outside selected files
|
||||
view->clearSelection();
|
||||
Q_EMIT clicked(type, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndexList FolderView::selectedRows(int column) const {
|
||||
QItemSelectionModel* selModel = selectionModel();
|
||||
if(selModel) {
|
||||
return selModel->selectedRows(column);
|
||||
}
|
||||
return QModelIndexList();
|
||||
}
|
||||
|
||||
// This returns all selected "cells", which means all cells of the same row are returned.
|
||||
QModelIndexList FolderView::selectedIndexes() const {
|
||||
QItemSelectionModel* selModel = selectionModel();
|
||||
if(selModel) {
|
||||
return selModel->selectedIndexes();
|
||||
}
|
||||
return QModelIndexList();
|
||||
}
|
||||
|
||||
QItemSelectionModel* FolderView::selectionModel() const {
|
||||
return view ? view->selectionModel() : nullptr;
|
||||
}
|
||||
|
||||
FmPathList* FolderView::selectedFilePaths() const {
|
||||
if(model_) {
|
||||
QModelIndexList selIndexes = mode == DetailedListMode ? selectedRows() : selectedIndexes();
|
||||
if(!selIndexes.isEmpty()) {
|
||||
FmPathList* paths = fm_path_list_new();
|
||||
QModelIndexList::const_iterator it;
|
||||
for(it = selIndexes.begin(); it != selIndexes.end(); ++it) {
|
||||
FmFileInfo* file = model_->fileInfoFromIndex(*it);
|
||||
fm_path_list_push_tail(paths, fm_file_info_get_path(file));
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QModelIndex FolderView::indexFromFolderPath(FmPath* folderPath) const {
|
||||
if(!model_ || !folderPath) return QModelIndex();
|
||||
QModelIndex index;
|
||||
int count = model_->rowCount();
|
||||
for(int row = 0; row < count; ++row) {
|
||||
index = model_->index(row, 0);
|
||||
FmFileInfo* info = model_->fileInfoFromIndex(index);
|
||||
if(info && fm_file_info_is_dir(info) && fm_path_equal(folderPath,fm_file_info_get_path(info)))
|
||||
return index;
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
FmFileInfoList* FolderView::selectedFiles() const {
|
||||
if(model_) {
|
||||
QModelIndexList selIndexes = mode == DetailedListMode ? selectedRows() : selectedIndexes();
|
||||
if(!selIndexes.isEmpty()) {
|
||||
FmFileInfoList* files = fm_file_info_list_new();
|
||||
QModelIndexList::const_iterator it;
|
||||
for(it = selIndexes.constBegin(); it != selIndexes.constEnd(); ++it) {
|
||||
FmFileInfo* file = model_->fileInfoFromIndex(*it);
|
||||
fm_file_info_list_push_tail(files, file);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FolderView::selectAll() {
|
||||
if(mode == DetailedListMode)
|
||||
view->selectAll();
|
||||
else {
|
||||
// NOTE: By default QListView::selectAll() selects all columns in the model.
|
||||
// However, QListView only show the first column. Normal selection by mouse
|
||||
// can only select the first column of every row. I consider this discripancy yet
|
||||
// another design flaw of Qt. To make them consistent, we do it ourselves by only
|
||||
// selecting the first column of every row and do not select all columns as Qt does.
|
||||
// This will trigger one selectionChanged event per row, which is very inefficient,
|
||||
// but we have no other choices to workaround the Qt bug.
|
||||
// I'll report a Qt bug for this later.
|
||||
if(model_) {
|
||||
int rowCount = model_->rowCount();
|
||||
for(int row = 0; row < rowCount; ++row) {
|
||||
QModelIndex index = model_->index(row, 0);
|
||||
selectionModel()->select(index, QItemSelectionModel::Select);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FolderView::invertSelection() {
|
||||
if(model_) {
|
||||
QItemSelectionModel* selModel = view->selectionModel();
|
||||
int rows = model_->rowCount();
|
||||
QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Toggle;
|
||||
if(mode == DetailedListMode)
|
||||
flags |= QItemSelectionModel::Rows;
|
||||
for(int row = 0; row < rows; ++row) {
|
||||
QModelIndex index = model_->index(row, 0);
|
||||
selModel->select(index, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FolderView::childDragEnterEvent(QDragEnterEvent* event) {
|
||||
qDebug("drag enter");
|
||||
if(event->mimeData()->hasFormat("text/uri-list")) {
|
||||
event->accept();
|
||||
}
|
||||
else
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void FolderView::childDragLeaveEvent(QDragLeaveEvent* e) {
|
||||
qDebug("drag leave");
|
||||
e->accept();
|
||||
}
|
||||
|
||||
void FolderView::childDragMoveEvent(QDragMoveEvent* e) {
|
||||
qDebug("drag move");
|
||||
}
|
||||
|
||||
void FolderView::childDropEvent(QDropEvent* e) {
|
||||
qDebug("drop");
|
||||
if(e->keyboardModifiers() == Qt::NoModifier) {
|
||||
// if no key modifiers are used, popup a menu
|
||||
// to ask the user for the action he/she wants to perform.
|
||||
Qt::DropAction action = DndActionMenu::askUser(e->possibleActions(), QCursor::pos());
|
||||
e->setDropAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
bool FolderView::eventFilter(QObject* watched, QEvent* event) {
|
||||
// NOTE: Instead of simply filtering the drag and drop events of the child view in
|
||||
// the event filter, we overrided each event handler virtual methods in
|
||||
// both QListView and QTreeView and added some childXXXEvent() callbacks.
|
||||
// We did this because of a design flaw of Qt.
|
||||
// All QAbstractScrollArea derived widgets, including QAbstractItemView
|
||||
// contains an internal child widget, which is called a viewport.
|
||||
// The events actually comes from the child viewport, not the parent view itself.
|
||||
// Qt redirects the events of viewport to the viewportEvent() method of
|
||||
// QAbstractScrollArea and let the parent widget handle the events.
|
||||
// Qt implemented this using a event filter installed on the child viewport widget.
|
||||
// That means, when we try to install an event filter on the viewport,
|
||||
// there is already a filter installed by Qt which will be called before ours.
|
||||
// So we can never intercept the event handling of QAbstractItemView by using a filter.
|
||||
// That's why we override respective virtual methods for different events.
|
||||
if(view && watched == view->viewport()) {
|
||||
switch(event->type()) {
|
||||
case QEvent::HoverMove:
|
||||
// activate items on single click
|
||||
if(style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) {
|
||||
QHoverEvent* hoverEvent = static_cast<QHoverEvent*>(event);
|
||||
QModelIndex index = view->indexAt(hoverEvent->pos()); // find out the hovered item
|
||||
if(index.isValid()) { // change the cursor to a hand when hovering on an item
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
if(!selectionModel()->hasSelection())
|
||||
selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current);
|
||||
}
|
||||
else
|
||||
setCursor(Qt::ArrowCursor);
|
||||
// turn on auto-selection for hovered item when single click mode is used.
|
||||
if(autoSelectionDelay_ > 0 && model_) {
|
||||
if(!autoSelectionTimer_) {
|
||||
autoSelectionTimer_ = new QTimer(this);
|
||||
connect(autoSelectionTimer_, &QTimer::timeout, this, &FolderView::onAutoSelectionTimeout);
|
||||
lastAutoSelectionIndex_ = QModelIndex();
|
||||
}
|
||||
autoSelectionTimer_->start(autoSelectionDelay_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QEvent::HoverLeave:
|
||||
if(style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick))
|
||||
setCursor(Qt::ArrowCursor);
|
||||
break;
|
||||
case QEvent::Wheel:
|
||||
// This is to fix #85: Scrolling doesn't work in compact view
|
||||
// Actually, I think it's the bug of Qt, not ours.
|
||||
// When in compact mode, only the horizontal scroll bar is used and the vertical one is hidden.
|
||||
// So, when a user scroll his mouse wheel, it's reasonable to scroll the horizontal scollbar.
|
||||
// Qt does not implement such a simple feature, unfortunately.
|
||||
// We do it by forwarding the scroll event in the viewport to the horizontal scrollbar.
|
||||
// FIXME: if someday Qt supports this, we have to disable the workaround.
|
||||
if(mode == CompactMode) {
|
||||
QScrollBar* scroll = view->horizontalScrollBar();
|
||||
if(scroll) {
|
||||
QApplication::sendEvent(scroll, event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
// this slot handles auto-selection of items.
|
||||
void FolderView::onAutoSelectionTimeout() {
|
||||
if(QApplication::mouseButtons() != Qt::NoButton)
|
||||
return;
|
||||
|
||||
Qt::KeyboardModifiers mods = QApplication::keyboardModifiers();
|
||||
QPoint pos = view->viewport()->mapFromGlobal(QCursor::pos()); // convert to viewport coordinates
|
||||
QModelIndex index = view->indexAt(pos); // find out the hovered item
|
||||
QItemSelectionModel::SelectionFlags flags = (mode == DetailedListMode ? QItemSelectionModel::Rows : QItemSelectionModel::NoUpdate);
|
||||
QItemSelectionModel* selModel = view->selectionModel();
|
||||
|
||||
if(mods & Qt::ControlModifier) { // Ctrl key is pressed
|
||||
if(selModel->isSelected(index) && index != lastAutoSelectionIndex_) {
|
||||
// unselect a previously selected item
|
||||
selModel->select(index, flags|QItemSelectionModel::Deselect);
|
||||
lastAutoSelectionIndex_ = QModelIndex();
|
||||
}
|
||||
else {
|
||||
// select an unselected item
|
||||
selModel->select(index, flags|QItemSelectionModel::Select);
|
||||
lastAutoSelectionIndex_ = index;
|
||||
}
|
||||
selModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); // move the cursor
|
||||
}
|
||||
else if(mods & Qt::ShiftModifier) { // Shift key is pressed
|
||||
// select all items between current index and the hovered index.
|
||||
QModelIndex current = selModel->currentIndex();
|
||||
if(selModel->hasSelection() && current.isValid()) {
|
||||
selModel->clear(); // clear old selection
|
||||
selModel->setCurrentIndex(current, QItemSelectionModel::NoUpdate);
|
||||
int begin = current.row();
|
||||
int end = index.row();
|
||||
if(begin > end)
|
||||
qSwap(begin, end);
|
||||
for(int row = begin; row <= end; ++row) {
|
||||
QModelIndex sel = model_->index(row, 0);
|
||||
selModel->select(sel, flags|QItemSelectionModel::Select);
|
||||
}
|
||||
}
|
||||
else { // no items are selected, select the hovered item.
|
||||
if(index.isValid()) {
|
||||
selModel->select(index, flags|QItemSelectionModel::SelectCurrent);
|
||||
selModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
|
||||
}
|
||||
}
|
||||
lastAutoSelectionIndex_ = index;
|
||||
}
|
||||
else if(mods == Qt::NoModifier) { // no modifier keys are pressed.
|
||||
if(index.isValid()) {
|
||||
// select the hovered item
|
||||
view->clearSelection();
|
||||
selModel->select(index, flags|QItemSelectionModel::SelectCurrent);
|
||||
selModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
|
||||
}
|
||||
lastAutoSelectionIndex_ = index;
|
||||
}
|
||||
|
||||
autoSelectionTimer_->deleteLater();
|
||||
autoSelectionTimer_ = nullptr;
|
||||
}
|
||||
|
||||
void FolderView::onFileClicked(int type, FmFileInfo* fileInfo) {
|
||||
if(type == ActivatedClick) {
|
||||
if(fileLauncher_) {
|
||||
GList* files = g_list_append(nullptr, fileInfo);
|
||||
fileLauncher_->launchFiles(nullptr, files);
|
||||
g_list_free(files);
|
||||
}
|
||||
}
|
||||
else if(type == ContextMenuClick) {
|
||||
FmPath* folderPath = nullptr;
|
||||
FmFileInfoList* files = selectedFiles();
|
||||
if (files) {
|
||||
FmFileInfo* first = fm_file_info_list_peek_head(files);
|
||||
if (fm_file_info_list_get_length(files) == 1 && fm_file_info_is_dir(first))
|
||||
folderPath = fm_file_info_get_path(first);
|
||||
}
|
||||
if (!folderPath)
|
||||
folderPath = path();
|
||||
QMenu* menu = nullptr;
|
||||
if(fileInfo) {
|
||||
// show context menu
|
||||
if (FmFileInfoList* files = selectedFiles()) {
|
||||
Fm::FileMenu* fileMenu = new Fm::FileMenu(files, fileInfo, folderPath);
|
||||
fileMenu->setFileLauncher(fileLauncher_);
|
||||
prepareFileMenu(fileMenu);
|
||||
fm_file_info_list_unref(files);
|
||||
menu = fileMenu;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Fm::FolderMenu* folderMenu = new Fm::FolderMenu(this);
|
||||
prepareFolderMenu(folderMenu);
|
||||
menu = folderMenu;
|
||||
}
|
||||
if (menu) {
|
||||
menu->exec(QCursor::pos());
|
||||
delete menu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FolderView::prepareFileMenu(FileMenu* menu) {
|
||||
}
|
||||
|
||||
void FolderView::prepareFolderMenu(FolderMenu* menu) {
|
||||
}
|
||||
|
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FOLDERVIEW_H
|
||||
#define FM_FOLDERVIEW_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QWidget>
|
||||
#include <QListView>
|
||||
#include <QTreeView>
|
||||
#include <QMouseEvent>
|
||||
#include <libfm/fm.h>
|
||||
#include "foldermodel.h"
|
||||
#include "proxyfoldermodel.h"
|
||||
|
||||
class QTimer;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class FileMenu;
|
||||
class FolderMenu;
|
||||
class FileLauncher;
|
||||
class FolderViewStyle;
|
||||
|
||||
class LIBFM_QT_API FolderView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
enum ViewMode {
|
||||
FirstViewMode = 1,
|
||||
IconMode = FirstViewMode,
|
||||
CompactMode,
|
||||
DetailedListMode,
|
||||
ThumbnailMode,
|
||||
LastViewMode = ThumbnailMode,
|
||||
NumViewModes = (LastViewMode - FirstViewMode + 1)
|
||||
};
|
||||
|
||||
enum ClickType {
|
||||
ActivatedClick,
|
||||
MiddleClick,
|
||||
ContextMenuClick
|
||||
};
|
||||
|
||||
friend class FolderViewTreeView;
|
||||
friend class FolderViewListView;
|
||||
|
||||
explicit FolderView(ViewMode _mode = IconMode, QWidget* parent = 0);
|
||||
virtual ~FolderView();
|
||||
|
||||
void setViewMode(ViewMode _mode);
|
||||
ViewMode viewMode() const;
|
||||
|
||||
void setIconSize(ViewMode mode, QSize size);
|
||||
QSize iconSize(ViewMode mode) const;
|
||||
|
||||
QAbstractItemView* childView() const;
|
||||
|
||||
ProxyFolderModel* model() const;
|
||||
void setModel(ProxyFolderModel* _model);
|
||||
|
||||
FmFolder* folder() {
|
||||
return model_ ? static_cast<FolderModel*>(model_->sourceModel())->folder() : NULL;
|
||||
}
|
||||
|
||||
FmFileInfo* folderInfo() {
|
||||
FmFolder* _folder = folder();
|
||||
return _folder ? fm_folder_get_info(_folder) : NULL;
|
||||
}
|
||||
|
||||
FmPath* path() {
|
||||
FmFolder* _folder = folder();
|
||||
return _folder ? fm_folder_get_path(_folder) : NULL;
|
||||
}
|
||||
|
||||
QItemSelectionModel* selectionModel() const;
|
||||
FmFileInfoList* selectedFiles() const;
|
||||
FmPathList* selectedFilePaths() const;
|
||||
QModelIndex indexFromFolderPath(FmPath* folderPath) const;
|
||||
|
||||
void selectAll();
|
||||
|
||||
void invertSelection();
|
||||
|
||||
void setFileLauncher(FileLauncher* launcher) {
|
||||
fileLauncher_ = launcher;
|
||||
}
|
||||
|
||||
FileLauncher* fileLauncher() {
|
||||
return fileLauncher_;
|
||||
}
|
||||
|
||||
int autoSelectionDelay() const {
|
||||
return autoSelectionDelay_;
|
||||
}
|
||||
|
||||
void setAutoSelectionDelay(int delay);
|
||||
|
||||
protected:
|
||||
virtual bool event(QEvent* event);
|
||||
virtual void contextMenuEvent(QContextMenuEvent* event);
|
||||
virtual void childMousePressEvent(QMouseEvent* event);
|
||||
virtual void childDragEnterEvent(QDragEnterEvent* event);
|
||||
virtual void childDragMoveEvent(QDragMoveEvent* e);
|
||||
virtual void childDragLeaveEvent(QDragLeaveEvent* e);
|
||||
virtual void childDropEvent(QDropEvent* e);
|
||||
|
||||
void emitClickedAt(ClickType type, const QPoint& pos);
|
||||
|
||||
QModelIndexList selectedRows ( int column = 0 ) const;
|
||||
QModelIndexList selectedIndexes() const;
|
||||
|
||||
virtual void prepareFileMenu(Fm::FileMenu* menu);
|
||||
virtual void prepareFolderMenu(Fm::FolderMenu* menu);
|
||||
|
||||
virtual bool eventFilter(QObject* watched, QEvent* event);
|
||||
|
||||
void updateGridSize(); // called when view mode, icon size, or font size is changed
|
||||
|
||||
public Q_SLOTS:
|
||||
void onItemActivated(QModelIndex index);
|
||||
void onSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected);
|
||||
virtual void onFileClicked(int type, FmFileInfo* fileInfo);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onAutoSelectionTimeout();
|
||||
void onSelChangedTimeout();
|
||||
|
||||
Q_SIGNALS:
|
||||
void clicked(int type, FmFileInfo* file);
|
||||
void clickedBack();
|
||||
void clickedForward();
|
||||
void selChanged(int n_sel);
|
||||
void sortChanged();
|
||||
|
||||
private:
|
||||
|
||||
QAbstractItemView* view;
|
||||
ProxyFolderModel* model_;
|
||||
ViewMode mode;
|
||||
QSize iconSize_[NumViewModes];
|
||||
FileLauncher* fileLauncher_;
|
||||
int autoSelectionDelay_;
|
||||
QTimer* autoSelectionTimer_;
|
||||
QModelIndex lastAutoSelectionIndex_;
|
||||
QTimer* selChangedTimer_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FOLDERVIEW_H
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FOLDERVIEW_P_H
|
||||
#define FM_FOLDERVIEW_P_H
|
||||
|
||||
#include <QListView>
|
||||
#include <QTreeView>
|
||||
#include <QMouseEvent>
|
||||
#include "folderview.h"
|
||||
|
||||
class QTimer;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
// override these classes for implementing FolderView
|
||||
class FolderViewListView : public QListView {
|
||||
Q_OBJECT
|
||||
public:
|
||||
friend class FolderView;
|
||||
FolderViewListView(QWidget* parent = 0);
|
||||
virtual ~FolderViewListView();
|
||||
virtual void startDrag(Qt::DropActions supportedActions);
|
||||
virtual void mousePressEvent(QMouseEvent* event);
|
||||
virtual void mouseReleaseEvent(QMouseEvent* event);
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent* event);
|
||||
virtual void dragEnterEvent(QDragEnterEvent* event);
|
||||
virtual void dragMoveEvent(QDragMoveEvent* e);
|
||||
virtual void dragLeaveEvent(QDragLeaveEvent* e);
|
||||
virtual void dropEvent(QDropEvent* e);
|
||||
|
||||
virtual QModelIndex indexAt(const QPoint & point) const;
|
||||
|
||||
inline void setPositionForIndex(const QPoint & position, const QModelIndex & index) {
|
||||
QListView::setPositionForIndex(position, index);
|
||||
}
|
||||
|
||||
inline QRect rectForIndex(const QModelIndex & index) const {
|
||||
return QListView::rectForIndex(index);
|
||||
}
|
||||
|
||||
inline QStyleOptionViewItem getViewOptions() {
|
||||
return viewOptions();
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void activatedFiltered(const QModelIndex &index);
|
||||
|
||||
private Q_SLOTS:
|
||||
void activation(const QModelIndex &index);
|
||||
|
||||
private:
|
||||
bool activationAllowed_;
|
||||
};
|
||||
|
||||
class FolderViewTreeView : public QTreeView {
|
||||
Q_OBJECT
|
||||
public:
|
||||
friend class FolderView;
|
||||
FolderViewTreeView(QWidget* parent = 0);
|
||||
virtual ~FolderViewTreeView();
|
||||
virtual void setModel(QAbstractItemModel* model);
|
||||
virtual void mousePressEvent(QMouseEvent* event);
|
||||
virtual void mouseReleaseEvent(QMouseEvent* event);
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent* event);
|
||||
virtual void dragEnterEvent(QDragEnterEvent* event);
|
||||
virtual void dragMoveEvent(QDragMoveEvent* e);
|
||||
virtual void dragLeaveEvent(QDragLeaveEvent* e);
|
||||
virtual void dropEvent(QDropEvent* e);
|
||||
|
||||
virtual void rowsInserted(const QModelIndex& parent,int start, int end);
|
||||
virtual void rowsAboutToBeRemoved(const QModelIndex& parent,int start, int end);
|
||||
virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
virtual void reset();
|
||||
|
||||
virtual void resizeEvent(QResizeEvent* event);
|
||||
void queueLayoutColumns();
|
||||
|
||||
Q_SIGNALS:
|
||||
void activatedFiltered(const QModelIndex &index);
|
||||
|
||||
private Q_SLOTS:
|
||||
void layoutColumns();
|
||||
void activation(const QModelIndex &index);
|
||||
void onSortFilterChanged();
|
||||
|
||||
private:
|
||||
bool doingLayout_;
|
||||
QTimer* layoutTimer_;
|
||||
bool activationAllowed_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM_FOLDERVIEW_P_H
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "fontbutton.h"
|
||||
#include <QFontDialog>
|
||||
#include <X11/X.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FontButton::FontButton(QWidget* parent): QPushButton(parent) {
|
||||
connect(this, &QPushButton::clicked, this, &FontButton::onClicked);
|
||||
}
|
||||
|
||||
FontButton::~FontButton() {
|
||||
}
|
||||
|
||||
void FontButton::onClicked() {
|
||||
QFontDialog dlg(font_);
|
||||
if(dlg.exec() == QDialog::Accepted) {
|
||||
setFont(dlg.selectedFont());
|
||||
}
|
||||
}
|
||||
|
||||
void FontButton::setFont(QFont font) {
|
||||
font_ = font;
|
||||
QString text = font.family();
|
||||
if(font.bold()) {
|
||||
text += " ";
|
||||
text += tr("Bold");
|
||||
}
|
||||
if(font.italic()) {
|
||||
text += " ";
|
||||
text += tr("Italic");
|
||||
}
|
||||
text += QString(" %1").arg(font.pointSize());
|
||||
setText(text);
|
||||
Q_EMIT changed();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_FONTBUTTON_H
|
||||
#define FM_FONTBUTTON_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QPushButton>
|
||||
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FontButton : public QPushButton {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FontButton(QWidget* parent = 0);
|
||||
virtual ~FontButton();
|
||||
|
||||
QFont font() {
|
||||
return font_;
|
||||
}
|
||||
|
||||
void setFont(QFont font);
|
||||
|
||||
Q_SIGNALS:
|
||||
void changed();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onClicked();
|
||||
|
||||
private:
|
||||
QFont font_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_FONTBUTTON_H
|
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "icontheme.h"
|
||||
#include <libfm/fm.h>
|
||||
#include <QList>
|
||||
#include <QIcon>
|
||||
#include <QtGlobal>
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
static IconTheme* theIconTheme = NULL; // the global single instance of IconTheme.
|
||||
static const char* fallbackNames[] = {"unknown", "application-octet-stream", NULL};
|
||||
|
||||
static void fmIconDataDestroy(gpointer data) {
|
||||
QIcon* picon = reinterpret_cast<QIcon*>(data);
|
||||
delete picon;
|
||||
}
|
||||
|
||||
IconTheme::IconTheme():
|
||||
currentThemeName_(QIcon::themeName()) {
|
||||
// NOTE: only one instance is allowed
|
||||
Q_ASSERT(theIconTheme == NULL);
|
||||
Q_ASSERT(qApp != NULL); // QApplication should exists before contructing IconTheme.
|
||||
|
||||
theIconTheme = this;
|
||||
fm_icon_set_user_data_destroy(reinterpret_cast<GDestroyNotify>(fmIconDataDestroy));
|
||||
fallbackIcon_ = iconFromNames(fallbackNames);
|
||||
|
||||
// We need to get notified when there is a QEvent::StyleChange event so
|
||||
// we can check if the current icon theme name is changed.
|
||||
// To do this, we can filter QApplication object itself to intercept
|
||||
// signals of all widgets, but this may be too inefficient.
|
||||
// So, we only filter the events on QDesktopWidget instead.
|
||||
qApp->desktop()->installEventFilter(this);
|
||||
}
|
||||
|
||||
IconTheme::~IconTheme() {
|
||||
}
|
||||
|
||||
IconTheme* IconTheme::instance() {
|
||||
return theIconTheme;
|
||||
}
|
||||
|
||||
// check if the icon theme name is changed and emit "changed()" signal if any change is detected.
|
||||
void IconTheme::checkChanged() {
|
||||
if(QIcon::themeName() != theIconTheme->currentThemeName_) {
|
||||
// if the icon theme is changed
|
||||
theIconTheme->currentThemeName_ = QIcon::themeName();
|
||||
// invalidate the cached data
|
||||
fm_icon_reset_user_data_cache(fm_qdata_id);
|
||||
|
||||
theIconTheme->fallbackIcon_ = iconFromNames(fallbackNames);
|
||||
Q_EMIT theIconTheme->changed();
|
||||
}
|
||||
}
|
||||
|
||||
QIcon IconTheme::iconFromNames(const char* const* names) {
|
||||
const gchar* const* name;
|
||||
// qDebug("names: %p", names);
|
||||
for(name = names; *name; ++name) {
|
||||
// qDebug("icon name=%s", *name);
|
||||
QString qname = *name;
|
||||
QIcon qicon = QIcon::fromTheme(qname);
|
||||
if(!qicon.isNull()) {
|
||||
return qicon;
|
||||
}
|
||||
}
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
QIcon IconTheme::convertFromGIcon(GIcon* gicon) {
|
||||
if(G_IS_THEMED_ICON(gicon)) {
|
||||
const gchar * const * names = g_themed_icon_get_names(G_THEMED_ICON(gicon));
|
||||
QIcon icon = iconFromNames(names);
|
||||
if(!icon.isNull())
|
||||
return icon;
|
||||
}
|
||||
else if(G_IS_FILE_ICON(gicon)) {
|
||||
GFile* file = g_file_icon_get_file(G_FILE_ICON(gicon));
|
||||
char* fpath = g_file_get_path(file);
|
||||
QString path = fpath;
|
||||
g_free(fpath);
|
||||
return QIcon(path);
|
||||
}
|
||||
return theIconTheme->fallbackIcon_;
|
||||
}
|
||||
|
||||
|
||||
//static
|
||||
QIcon IconTheme::icon(FmIcon* fmicon) {
|
||||
// check if we have a cached version
|
||||
QIcon* picon = reinterpret_cast<QIcon*>(fm_icon_get_user_data(fmicon));
|
||||
if(!picon) { // we don't have a cache yet
|
||||
picon = new QIcon(); // what a waste!
|
||||
*picon = convertFromGIcon(G_ICON(fmicon));
|
||||
fm_icon_set_user_data(fmicon, picon); // store it in FmIcon
|
||||
}
|
||||
return *picon;
|
||||
}
|
||||
|
||||
//static
|
||||
QIcon IconTheme::icon(GIcon* gicon) {
|
||||
if(G_IS_THEMED_ICON(gicon)) {
|
||||
FmIcon* fmicon = fm_icon_from_gicon(gicon);
|
||||
QIcon qicon = icon(fmicon);
|
||||
fm_icon_unref(fmicon);
|
||||
return qicon;
|
||||
}
|
||||
else if(G_IS_FILE_ICON(gicon)) {
|
||||
// we do not map GFileIcon to FmIcon deliberately.
|
||||
return convertFromGIcon(gicon);
|
||||
}
|
||||
return theIconTheme->fallbackIcon_;
|
||||
}
|
||||
|
||||
// this method is called whenever there is an event on the QDesktopWidget object.
|
||||
bool IconTheme::eventFilter(QObject* obj, QEvent* event) {
|
||||
// we're only interested in the StyleChange event.
|
||||
if(event->type() == QEvent::StyleChange) {
|
||||
checkChanged(); // check if the icon theme is changed
|
||||
}
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_ICONTHEME_H
|
||||
#define FM_ICONTHEME_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QIcon>
|
||||
#include <QString>
|
||||
#include "libfm/fm.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
// NOTE:
|
||||
// Qt seems to has its own QIcon pixmap caching mechanism internally.
|
||||
// Besides, it also caches QIcon objects created by QIcon::fromTheme().
|
||||
// So maybe we should not duplicate the work.
|
||||
// See http://qt.gitorious.org/qt/qt/blobs/4.8/src/gui/image/qicon.cpp
|
||||
// QPixmap QPixmapIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state).
|
||||
// In addition, QPixmap is actually stored in X11 server, not client side.
|
||||
// Hence maybe we should not cache too many pixmaps, I guess?
|
||||
// Let's have Qt do its work and only translate GIcon to QIcon here.
|
||||
|
||||
// Nice article about QPixmap from KDE: http://techbase.kde.org/Development/Tutorials/Graphics/Performance
|
||||
|
||||
class LIBFM_QT_API IconTheme: public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
IconTheme();
|
||||
~IconTheme();
|
||||
|
||||
static IconTheme* instance();
|
||||
static QIcon icon(FmIcon* fmicon);
|
||||
static QIcon icon(GIcon* gicon);
|
||||
|
||||
static void checkChanged(); // check if current icon theme name is changed
|
||||
Q_SIGNALS:
|
||||
void changed(); // emitted when the name of current icon theme is changed
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
static QIcon convertFromGIcon(GIcon* gicon);
|
||||
static QIcon iconFromNames(const char * const * names);
|
||||
|
||||
protected:
|
||||
QIcon fallbackIcon_;
|
||||
QString currentThemeName_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_ICONTHEME_H
|
@ -0,0 +1,12 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: libfm-qt
|
||||
Description: A Qt/glib/gio-based lib used to develop file managers providing some file management utilities. (This is a Qt port of the original libfm library)
|
||||
URL: http://pcmanfm.sourceforge.net/
|
||||
Requires: @REQUIRED_QT@ libfm >= 1.2.0
|
||||
Version: @LIBFM_QT_VERSION@
|
||||
Libs: -L${libdir} -lfm -l@LIBFM_QT_LIBRARY_NAME@
|
||||
Cflags: -I${includedir}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <libfm/fm.h>
|
||||
#include "libfmqt.h"
|
||||
#include <QLocale>
|
||||
#include "icontheme.h"
|
||||
#include "thumbnailloader.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
struct LibFmQtData {
|
||||
LibFmQtData();
|
||||
~LibFmQtData();
|
||||
|
||||
IconTheme* iconTheme;
|
||||
ThumbnailLoader* thumbnailLoader;
|
||||
QTranslator translator;
|
||||
int refCount;
|
||||
Q_DISABLE_COPY(LibFmQtData)
|
||||
};
|
||||
|
||||
static LibFmQtData* theLibFmData = NULL;
|
||||
|
||||
LibFmQtData::LibFmQtData(): refCount(1) {
|
||||
#if !GLIB_CHECK_VERSION(2, 36, 0)
|
||||
g_type_init();
|
||||
#endif
|
||||
fm_init(NULL);
|
||||
// turn on glib debug message
|
||||
// g_setenv("G_MESSAGES_DEBUG", "all", true);
|
||||
iconTheme = new IconTheme();
|
||||
thumbnailLoader = new ThumbnailLoader();
|
||||
translator.load("libfm-qt_" + QLocale::system().name(), LIBFM_DATA_DIR "/translations");
|
||||
}
|
||||
|
||||
LibFmQtData::~LibFmQtData() {
|
||||
delete iconTheme;
|
||||
delete thumbnailLoader;
|
||||
fm_finalize();
|
||||
}
|
||||
|
||||
LibFmQt::LibFmQt() {
|
||||
if(!theLibFmData) {
|
||||
theLibFmData = new LibFmQtData();
|
||||
}
|
||||
else
|
||||
++theLibFmData->refCount;
|
||||
d = theLibFmData;
|
||||
}
|
||||
|
||||
LibFmQt::~LibFmQt() {
|
||||
if(--d->refCount == 0) {
|
||||
delete d;
|
||||
theLibFmData = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
QTranslator* LibFmQt::translator() {
|
||||
return &d->translator;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_APPLICATION_H
|
||||
#define FM_APPLICATION_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QtGlobal>
|
||||
#include <QTranslator>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
struct LibFmQtData;
|
||||
|
||||
class LIBFM_QT_API LibFmQt {
|
||||
public:
|
||||
LibFmQt();
|
||||
~LibFmQt();
|
||||
|
||||
QTranslator* translator();
|
||||
|
||||
private:
|
||||
LibFmQt(LibFmQt& other); // disable copy
|
||||
LibFmQtData* d;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_APPLICATION_H
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LIBFM_QT_GLOBALS_
|
||||
#define _LIBFM_QT_GLOBALS_
|
||||
|
||||
#include "fm-qt_export.h"
|
||||
|
||||
#endif
|
@ -0,0 +1,205 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MountOperationPasswordDialog</class>
|
||||
<widget class="QDialog" name="MountOperationPasswordDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>244</width>
|
||||
<height>302</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Mount</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset theme="dialog-password"/>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="message">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="Anonymous">
|
||||
<property name="text">
|
||||
<string>Connect &anonymously</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">usernameGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="asUser">
|
||||
<property name="text">
|
||||
<string>Connect as u&ser:</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">usernameGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="username"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Username:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>username</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Password:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>password</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="domainLabel">
|
||||
<property name="text">
|
||||
<string>&Domain:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>domain</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="domain"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="forgetPassword">
|
||||
<property name="text">
|
||||
<string>Forget password &immediately</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">passwordGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sessionPassword">
|
||||
<property name="text">
|
||||
<string>Remember password until you &logout</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">passwordGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="storePassword">
|
||||
<property name="text">
|
||||
<string>Remember &forever</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">passwordGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>MountOperationPasswordDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>MountOperationPasswordDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="usernameGroup"/>
|
||||
<buttongroup name="passwordGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "mountoperation.h"
|
||||
#include <glib/gi18n.h> // for _()
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QEventLoop>
|
||||
#include "mountoperationpassworddialog_p.h"
|
||||
#include "mountoperationquestiondialog_p.h"
|
||||
#include "ui_mount-operation-password.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
MountOperation::MountOperation(bool interactive, QWidget* parent):
|
||||
QObject(parent),
|
||||
interactive_(interactive),
|
||||
running(false),
|
||||
op(g_mount_operation_new()),
|
||||
cancellable_(g_cancellable_new()),
|
||||
eventLoop(NULL),
|
||||
autoDestroy_(true) {
|
||||
|
||||
g_signal_connect(op, "ask-password", G_CALLBACK(onAskPassword), this);
|
||||
g_signal_connect(op, "ask-question", G_CALLBACK(onAskQuestion), this);
|
||||
// g_signal_connect(op, "reply", G_CALLBACK(onReply), this);
|
||||
|
||||
#if GLIB_CHECK_VERSION(2, 20, 0)
|
||||
g_signal_connect(op, "aborted", G_CALLBACK(onAbort), this);
|
||||
#endif
|
||||
#if GLIB_CHECK_VERSION(2, 22, 0)
|
||||
g_signal_connect(op, "show-processes", G_CALLBACK(onShowProcesses), this);
|
||||
#endif
|
||||
#if GLIB_CHECK_VERSION(2, 34, 0)
|
||||
g_signal_connect(op, "show-unmount-progress", G_CALLBACK(onShowUnmountProgress), this);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
MountOperation::~MountOperation() {
|
||||
qDebug("delete MountOperation");
|
||||
if(cancellable_) {
|
||||
cancel();
|
||||
g_object_unref(cancellable_);
|
||||
}
|
||||
|
||||
if(eventLoop) { // if wait() is called to block the main loop, but the event loop is still running
|
||||
// NOTE: is this possible?
|
||||
eventLoop->exit(1);
|
||||
}
|
||||
|
||||
if(op) {
|
||||
g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onAskPassword), this);
|
||||
g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onAskQuestion), this);
|
||||
#if GLIB_CHECK_VERSION(2, 20, 0)
|
||||
g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onAbort), this);
|
||||
#endif
|
||||
#if GLIB_CHECK_VERSION(2, 22, 0)
|
||||
g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onShowProcesses), this);
|
||||
#endif
|
||||
#if GLIB_CHECK_VERSION(2, 34, 0)
|
||||
g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onShowUnmountProgress), this);
|
||||
#endif
|
||||
g_object_unref(op);
|
||||
}
|
||||
// qDebug("MountOperation deleted");
|
||||
}
|
||||
|
||||
void MountOperation::onAbort(GMountOperation* _op, MountOperation* pThis) {
|
||||
|
||||
}
|
||||
|
||||
void MountOperation::onAskPassword(GMountOperation* _op, gchar* message, gchar* default_user, gchar* default_domain, GAskPasswordFlags flags, MountOperation* pThis) {
|
||||
qDebug("ask password");
|
||||
MountOperationPasswordDialog dlg(pThis, flags);
|
||||
dlg.setMessage(QString::fromUtf8(message));
|
||||
dlg.setDefaultUser(QString::fromUtf8(default_user));
|
||||
dlg.setDefaultDomain(QString::fromUtf8(default_domain));
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void MountOperation::onAskQuestion(GMountOperation* _op, gchar* message, GStrv choices, MountOperation* pThis) {
|
||||
qDebug("ask question");
|
||||
MountOperationQuestionDialog dialog(pThis, message, choices);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
/*
|
||||
void MountOperation::onReply(GMountOperation* _op, GMountOperationResult result, MountOperation* pThis) {
|
||||
qDebug("reply");
|
||||
}
|
||||
*/
|
||||
|
||||
void MountOperation::onShowProcesses(GMountOperation* _op, gchar* message, GArray* processes, GStrv choices, MountOperation* pThis) {
|
||||
qDebug("show processes");
|
||||
}
|
||||
|
||||
void MountOperation::onShowUnmountProgress(GMountOperation* _op, gchar* message, gint64 time_left, gint64 bytes_left, MountOperation* pThis) {
|
||||
qDebug("show unmount progress");
|
||||
}
|
||||
|
||||
void MountOperation::onEjectMountFinished(GMount* mount, GAsyncResult* res, QPointer< MountOperation >* pThis) {
|
||||
if(*pThis) {
|
||||
GError* error = NULL;
|
||||
g_mount_eject_with_operation_finish(mount, res, &error);
|
||||
(*pThis)->handleFinish(error);
|
||||
}
|
||||
delete pThis;
|
||||
}
|
||||
|
||||
void MountOperation::onEjectVolumeFinished(GVolume* volume, GAsyncResult* res, QPointer< MountOperation >* pThis) {
|
||||
if(*pThis) {
|
||||
GError* error = NULL;
|
||||
g_volume_eject_with_operation_finish(volume, res, &error);
|
||||
(*pThis)->handleFinish(error);
|
||||
}
|
||||
delete pThis;
|
||||
}
|
||||
|
||||
void MountOperation::onMountFileFinished(GFile* file, GAsyncResult* res, QPointer< MountOperation >* pThis) {
|
||||
if(*pThis) {
|
||||
GError* error = NULL;
|
||||
g_file_mount_enclosing_volume_finish(file, res, &error);
|
||||
(*pThis)->handleFinish(error);
|
||||
}
|
||||
delete pThis;
|
||||
}
|
||||
|
||||
void MountOperation::onMountVolumeFinished(GVolume* volume, GAsyncResult* res, QPointer< MountOperation >* pThis) {
|
||||
if(*pThis) {
|
||||
GError* error = NULL;
|
||||
g_volume_mount_finish(volume, res, &error);
|
||||
(*pThis)->handleFinish(error);
|
||||
}
|
||||
delete pThis;
|
||||
}
|
||||
|
||||
void MountOperation::onUnmountMountFinished(GMount* mount, GAsyncResult* res, QPointer< MountOperation >* pThis) {
|
||||
if(*pThis) {
|
||||
GError* error = NULL;
|
||||
g_mount_unmount_with_operation_finish(mount, res, &error);
|
||||
(*pThis)->handleFinish(error);
|
||||
}
|
||||
delete pThis;
|
||||
}
|
||||
|
||||
void MountOperation::handleFinish(GError* error) {
|
||||
qDebug("operation finished: %p", error);
|
||||
if(error) {
|
||||
bool showError = interactive_;
|
||||
if(error->domain == G_IO_ERROR) {
|
||||
if(error->code == G_IO_ERROR_FAILED) {
|
||||
// Generate a more human-readable error message instead of using a gvfs one.
|
||||
// The original error message is something like:
|
||||
// Error unmounting: umount exited with exit code 1:
|
||||
// helper failed with: umount: only root can unmount
|
||||
// UUID=18cbf00c-e65f-445a-bccc-11964bdea05d from /media/sda4 */
|
||||
// Why they pass this back to us? This is not human-readable for the users at all.
|
||||
if(strstr(error->message, "only root can ")) {
|
||||
g_free(error->message);
|
||||
error->message = g_strdup(_("Only system administrators have the permission to do this."));
|
||||
}
|
||||
}
|
||||
else if(error->code == G_IO_ERROR_FAILED_HANDLED)
|
||||
showError = false;
|
||||
}
|
||||
if(showError)
|
||||
QMessageBox::critical(NULL, QObject::tr("Error"), QString::fromUtf8(error->message));
|
||||
}
|
||||
|
||||
Q_EMIT finished(error);
|
||||
|
||||
if(eventLoop) { // if wait() is called to block the main loop
|
||||
eventLoop->exit(error != NULL ? 1 : 0);
|
||||
eventLoop = NULL;
|
||||
}
|
||||
|
||||
if(error)
|
||||
g_error_free(error);
|
||||
|
||||
// free ourself here!!
|
||||
if(autoDestroy_)
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void MountOperation::prepareUnmount(GMount* mount) {
|
||||
/* ensure that CWD is not on the mounted filesystem. */
|
||||
char* cwd_str = g_get_current_dir();
|
||||
GFile* cwd = g_file_new_for_path(cwd_str);
|
||||
GFile* root = g_mount_get_root(mount);
|
||||
g_free(cwd_str);
|
||||
/* FIXME: This cannot cover 100% cases since symlinks are not checked.
|
||||
* There may be other cases that cwd is actually under mount root
|
||||
* but checking prefix is not enough. We already did our best, though. */
|
||||
if(g_file_has_prefix(cwd, root))
|
||||
g_chdir("/");
|
||||
g_object_unref(cwd);
|
||||
g_object_unref(root);
|
||||
}
|
||||
|
||||
// block the operation used an internal QEventLoop and returns
|
||||
// only after the whole operation is finished.
|
||||
bool MountOperation::wait() {
|
||||
QEventLoop loop;
|
||||
eventLoop = &loop;
|
||||
int exitCode = loop.exec();
|
||||
return exitCode == 0 ? true : false;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_MOUNTOPERATION_H
|
||||
#define FM_MOUNTOPERATION_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QWidget>
|
||||
#include <QDialog>
|
||||
#include <libfm/fm.h>
|
||||
#include <gio/gio.h>
|
||||
#include <QPointer>
|
||||
|
||||
class QEventLoop;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
// FIXME: the original APIs in gtk+ version of libfm for mounting devices is poor.
|
||||
// Need to find a better API design which make things fully async and cancellable.
|
||||
|
||||
// FIXME: parent_ does not work. All dialogs shown by the mount operation has no parent window assigned.
|
||||
// FIXME: Need to reconsider the propery way of API design. Blocking sync calls are handy, but
|
||||
// indeed causes some problems. :-(
|
||||
|
||||
class LIBFM_QT_API MountOperation: public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MountOperation(bool interactive = true, QWidget* parent = 0);
|
||||
~MountOperation();
|
||||
|
||||
void mount(FmPath* path) {
|
||||
GFile* gf = fm_path_to_gfile(path);
|
||||
g_file_mount_enclosing_volume(gf, G_MOUNT_MOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onMountFileFinished, new QPointer<MountOperation>(this));
|
||||
g_object_unref(gf);
|
||||
}
|
||||
|
||||
void mount(GVolume* volume) {
|
||||
g_volume_mount(volume, G_MOUNT_MOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onMountVolumeFinished, new QPointer<MountOperation>(this));
|
||||
}
|
||||
|
||||
void unmount(GMount* mount) {
|
||||
prepareUnmount(mount);
|
||||
g_mount_unmount_with_operation(mount, G_MOUNT_UNMOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onUnmountMountFinished, new QPointer<MountOperation>(this));
|
||||
}
|
||||
|
||||
void unmount(GVolume* volume) {
|
||||
GMount* mount = g_volume_get_mount(volume);
|
||||
if(!mount)
|
||||
return;
|
||||
unmount(mount);
|
||||
g_object_unref(mount);
|
||||
}
|
||||
|
||||
void eject(GMount* mount) {
|
||||
prepareUnmount(mount);
|
||||
g_mount_eject_with_operation(mount, G_MOUNT_UNMOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onEjectMountFinished, new QPointer<MountOperation>(this));
|
||||
}
|
||||
|
||||
void eject(GVolume* volume) {
|
||||
GMount* mnt = g_volume_get_mount(volume);
|
||||
prepareUnmount(mnt);
|
||||
g_object_unref(mnt);
|
||||
g_volume_eject_with_operation(volume, G_MOUNT_UNMOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onEjectVolumeFinished, new QPointer<MountOperation>(this));
|
||||
}
|
||||
|
||||
QWidget* parent() const {
|
||||
return parent_;
|
||||
}
|
||||
|
||||
void setParent(QWidget* parent) {
|
||||
parent_ = parent;
|
||||
}
|
||||
|
||||
GCancellable* cancellable() const {
|
||||
return cancellable_;
|
||||
}
|
||||
|
||||
GMountOperation* mountOperation() {
|
||||
return op;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
g_cancellable_cancel(cancellable_);
|
||||
}
|
||||
|
||||
bool isRunning() const {
|
||||
return running;
|
||||
}
|
||||
|
||||
// block the operation used an internal QEventLoop and returns
|
||||
// only after the whole operation is finished.
|
||||
bool wait();
|
||||
|
||||
bool autoDestroy() {
|
||||
return autoDestroy_;
|
||||
}
|
||||
|
||||
void setAutoDestroy(bool destroy = true) {
|
||||
autoDestroy_ = destroy;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void finished(GError* error = NULL);
|
||||
|
||||
private:
|
||||
void prepareUnmount(GMount* mount);
|
||||
|
||||
static void onAskPassword(GMountOperation *_op, gchar* message, gchar* default_user, gchar* default_domain, GAskPasswordFlags flags, MountOperation* pThis);
|
||||
static void onAskQuestion(GMountOperation *_op, gchar* message, GStrv choices, MountOperation* pThis);
|
||||
// static void onReply(GMountOperation *_op, GMountOperationResult result, MountOperation* pThis);
|
||||
|
||||
static void onAbort(GMountOperation *_op, MountOperation* pThis);
|
||||
static void onShowProcesses(GMountOperation *_op, gchar* message, GArray* processes, GStrv choices, MountOperation* pThis);
|
||||
static void onShowUnmountProgress(GMountOperation *_op, gchar* message, gint64 time_left, gint64 bytes_left, MountOperation* pThis);
|
||||
|
||||
// it's possible that this object is freed when the callback is called by gio, so guarding with QPointer is needed here.
|
||||
static void onMountFileFinished(GFile* file, GAsyncResult *res, QPointer<MountOperation>* pThis);
|
||||
static void onMountVolumeFinished(GVolume* volume, GAsyncResult *res, QPointer<MountOperation>* pThis);
|
||||
static void onUnmountMountFinished(GMount* mount, GAsyncResult *res, QPointer<MountOperation>* pThis);
|
||||
static void onEjectMountFinished(GMount* mount, GAsyncResult *res, QPointer<MountOperation>* pThis);
|
||||
static void onEjectVolumeFinished(GVolume* volume, GAsyncResult *res, QPointer<MountOperation>* pThis);
|
||||
|
||||
void handleFinish(GError* error);
|
||||
|
||||
private:
|
||||
GMountOperation* op;
|
||||
GCancellable* cancellable_;
|
||||
QWidget* parent_;
|
||||
bool running;
|
||||
bool interactive_;
|
||||
QEventLoop* eventLoop;
|
||||
bool autoDestroy_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_MOUNTOPERATION_H
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "mountoperationpassworddialog_p.h"
|
||||
#include "ui_mount-operation-password.h"
|
||||
#include "mountoperation.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
MountOperationPasswordDialog::MountOperationPasswordDialog(MountOperation* op, GAskPasswordFlags flags):
|
||||
QDialog(),
|
||||
mountOperation(op),
|
||||
canAnonymous(flags & G_ASK_PASSWORD_ANONYMOUS_SUPPORTED ? true : false),
|
||||
canSavePassword(flags & G_ASK_PASSWORD_SAVING_SUPPORTED ? true : false),
|
||||
needUserName(flags & G_ASK_PASSWORD_NEED_USERNAME ? true : false),
|
||||
needPassword(flags & G_ASK_PASSWORD_NEED_PASSWORD ? true : false),
|
||||
needDomain(flags & G_ASK_PASSWORD_NEED_DOMAIN ? true : false) {
|
||||
|
||||
ui = new Ui::MountOperationPasswordDialog();
|
||||
ui->setupUi(this);
|
||||
|
||||
// change the text of Ok button to Connect
|
||||
ui->buttonBox->buttons().first()->setText(tr("&Connect"));
|
||||
connect(ui->Anonymous, &QAbstractButton::toggled, this, &MountOperationPasswordDialog::onAnonymousToggled);
|
||||
|
||||
if(canAnonymous) {
|
||||
// select ananymous by default if applicable.
|
||||
ui->Anonymous->setChecked(true);
|
||||
}
|
||||
else {
|
||||
ui->Anonymous->setEnabled(false);
|
||||
}
|
||||
if(!needUserName) {
|
||||
ui->username->setEnabled(false);
|
||||
}
|
||||
if(!needPassword) {
|
||||
ui->password->setEnabled(false);
|
||||
}
|
||||
if(!needDomain) {
|
||||
ui->domain->hide();
|
||||
ui->domainLabel->hide();
|
||||
}
|
||||
if(canSavePassword) {
|
||||
ui->sessionPassword->setChecked(true);
|
||||
}
|
||||
else {
|
||||
ui->storePassword->setEnabled(false);
|
||||
ui->sessionPassword->setEnabled(false);
|
||||
ui->forgetPassword->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
MountOperationPasswordDialog::~MountOperationPasswordDialog() {
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MountOperationPasswordDialog::onAnonymousToggled(bool checked) {
|
||||
// disable username/password entries if anonymous mode is used
|
||||
bool useUserPassword = !checked;
|
||||
if(needUserName)
|
||||
ui->username->setEnabled(useUserPassword);
|
||||
if(needPassword)
|
||||
ui->password->setEnabled(useUserPassword);
|
||||
if(needDomain)
|
||||
ui->domain->setEnabled(useUserPassword);
|
||||
|
||||
if(canSavePassword) {
|
||||
ui->forgetPassword->setEnabled(useUserPassword);
|
||||
ui->sessionPassword->setEnabled(useUserPassword);
|
||||
ui->storePassword->setEnabled(useUserPassword);
|
||||
}
|
||||
}
|
||||
|
||||
void MountOperationPasswordDialog::setMessage(QString message) {
|
||||
ui->message->setText(message);
|
||||
}
|
||||
|
||||
void MountOperationPasswordDialog::setDefaultDomain(QString domain) {
|
||||
ui->domain->setText(domain);
|
||||
}
|
||||
|
||||
void MountOperationPasswordDialog::setDefaultUser(QString user) {
|
||||
ui->username->setText(user);
|
||||
}
|
||||
|
||||
void MountOperationPasswordDialog::done(int r) {
|
||||
GMountOperation* gmop = mountOperation->mountOperation();
|
||||
|
||||
if(r == QDialog::Accepted) {
|
||||
|
||||
if(needUserName)
|
||||
g_mount_operation_set_username(gmop, ui->username->text().toUtf8());
|
||||
if(needDomain)
|
||||
g_mount_operation_set_domain(gmop, ui->domain->text().toUtf8());
|
||||
if(needPassword)
|
||||
g_mount_operation_set_password(gmop, ui->password->text().toUtf8());
|
||||
if(canAnonymous)
|
||||
g_mount_operation_set_anonymous(gmop, ui->Anonymous->isChecked());
|
||||
|
||||
g_mount_operation_reply(gmop, G_MOUNT_OPERATION_HANDLED);
|
||||
}
|
||||
else {
|
||||
g_mount_operation_reply(gmop, G_MOUNT_OPERATION_ABORTED);
|
||||
}
|
||||
QDialog::done(r);
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_MOUNTOPERATIONPASSWORDDIALOG_H
|
||||
#define FM_MOUNTOPERATIONPASSWORDDIALOG_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QDialog>
|
||||
#include <gio/gio.h>
|
||||
|
||||
namespace Ui {
|
||||
class MountOperationPasswordDialog;
|
||||
};
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class MountOperation;
|
||||
|
||||
class MountOperationPasswordDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MountOperationPasswordDialog(MountOperation* op, GAskPasswordFlags flags);
|
||||
virtual ~MountOperationPasswordDialog();
|
||||
|
||||
void setMessage(QString message);
|
||||
void setDefaultUser(QString user);
|
||||
void setDefaultDomain(QString domain);
|
||||
|
||||
virtual void done(int r);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onAnonymousToggled(bool checked);
|
||||
|
||||
private:
|
||||
Ui::MountOperationPasswordDialog* ui;
|
||||
MountOperation* mountOperation;
|
||||
bool needPassword;
|
||||
bool needUserName;
|
||||
bool needDomain;
|
||||
bool canSavePassword;
|
||||
bool canAnonymous;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_MOUNTOPERATIONPASSWORDDIALOG_H
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "mountoperationquestiondialog_p.h"
|
||||
#include "mountoperation.h"
|
||||
#include <QPushButton>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
MountOperationQuestionDialog::MountOperationQuestionDialog(MountOperation* op, gchar* message, GStrv choices):
|
||||
QMessageBox(),
|
||||
mountOperation(op) {
|
||||
|
||||
setIcon(QMessageBox::Question);
|
||||
setText(QString::fromUtf8(message));
|
||||
|
||||
choiceCount = g_strv_length(choices);
|
||||
choiceButtons = new QAbstractButton*[choiceCount];
|
||||
for(int i = 0; i < choiceCount; ++i) {
|
||||
// It's not allowed to add custom buttons without standard roles
|
||||
// to QMessageBox. So we set role of all buttons to AcceptRole and
|
||||
// handle their clicked() signals in our own slots.
|
||||
// When anyone of the buttons is clicked, exec() always returns "accept".
|
||||
QPushButton* button = new QPushButton(QString::fromUtf8(choices[i]));
|
||||
addButton(button, QMessageBox::AcceptRole);
|
||||
choiceButtons[i] = button;
|
||||
}
|
||||
connect(this, &MountOperationQuestionDialog::buttonClicked, this, &MountOperationQuestionDialog::onButtonClicked);
|
||||
}
|
||||
|
||||
MountOperationQuestionDialog::~MountOperationQuestionDialog() {
|
||||
delete []choiceButtons;
|
||||
}
|
||||
|
||||
void MountOperationQuestionDialog::done(int r) {
|
||||
if(r != QDialog::Accepted) {
|
||||
GMountOperation* op = mountOperation->mountOperation();
|
||||
g_mount_operation_reply(op, G_MOUNT_OPERATION_ABORTED);
|
||||
}
|
||||
QDialog::done(r);
|
||||
}
|
||||
|
||||
void MountOperationQuestionDialog::onButtonClicked(QAbstractButton* button) {
|
||||
GMountOperation* op = mountOperation->mountOperation();
|
||||
for(int i = 0; i < choiceCount; ++i) {
|
||||
if(choiceButtons[i] == button) {
|
||||
g_mount_operation_set_choice(op, i);
|
||||
g_mount_operation_reply(op, G_MOUNT_OPERATION_HANDLED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_MOUNTOPERATIONQUESTIONDIALOG_H
|
||||
#define FM_MOUNTOPERATIONQUESTIONDIALOG_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QMessageBox>
|
||||
#include <gio/gio.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class MountOperation;
|
||||
|
||||
class MountOperationQuestionDialog : public QMessageBox {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MountOperationQuestionDialog(MountOperation* op, gchar* message, GStrv choices);
|
||||
virtual ~MountOperationQuestionDialog();
|
||||
|
||||
virtual void done(int r);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onButtonClicked(QAbstractButton* button);
|
||||
|
||||
private:
|
||||
MountOperation* mountOperation;
|
||||
QAbstractButton** choiceButtons;
|
||||
int choiceCount;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_MOUNTOPERATIONQUESTIONDIALOG_H
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "path.h"
|
||||
|
||||
using namespace Fm;
|
@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FM_PATH_H
|
||||
#define FM_PATH_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <libfm/fm.h>
|
||||
#include <QString>
|
||||
#include <QMetaType>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API Path {
|
||||
public:
|
||||
|
||||
Path(): data_(nullptr) {
|
||||
}
|
||||
|
||||
Path(FmPath* path, bool takeOwnership = false): data_(path) {
|
||||
if(path && !takeOwnership)
|
||||
fm_path_ref(data_);
|
||||
}
|
||||
|
||||
Path(const Path& other): data_(other.data_ ? fm_path_ref(other.data_) : nullptr) {
|
||||
}
|
||||
|
||||
Path(GFile* gf): data_(fm_path_new_for_gfile(gf)) {
|
||||
}
|
||||
|
||||
~Path() {
|
||||
if(data_)
|
||||
fm_path_unref(data_);
|
||||
}
|
||||
|
||||
static Path fromPathName(const char* path_name) {
|
||||
return Path(fm_path_new_for_path(path_name), true);
|
||||
}
|
||||
|
||||
static Path fromUri(const char* uri) {
|
||||
return Path(fm_path_new_for_uri(uri), true);
|
||||
}
|
||||
|
||||
static Path fromDisplayName(const char* path_name) {
|
||||
return Path(fm_path_new_for_display_name(path_name), true);
|
||||
}
|
||||
|
||||
static Path fromString(const char* path_str) {
|
||||
return Path(fm_path_new_for_str(path_str), true);
|
||||
}
|
||||
|
||||
static Path fromCommandlineArg(const char* arg) {
|
||||
return Path(fm_path_new_for_commandline_arg(arg), true);
|
||||
}
|
||||
|
||||
Path child(const char* basename) {
|
||||
return Path(fm_path_new_child(data_, basename), true);
|
||||
}
|
||||
|
||||
Path child(const char* basename, int name_len) {
|
||||
return Path(fm_path_new_child_len(data_, basename, name_len), true);
|
||||
}
|
||||
|
||||
Path relative(const char* rel) {
|
||||
return Path(fm_path_new_relative(data_, rel), true);
|
||||
}
|
||||
|
||||
/* predefined paths */
|
||||
static Path root(void) { /* / */
|
||||
return Path(fm_path_get_root(), false);
|
||||
}
|
||||
|
||||
static Path home(void) { /* home directory */
|
||||
return Path(fm_path_get_home(), false);
|
||||
}
|
||||
|
||||
static Path desktop(void) { /* $HOME/Desktop */
|
||||
return Path(fm_path_get_desktop(), false);
|
||||
}
|
||||
|
||||
static Path trash(void) { /* trash:/// */
|
||||
return Path(fm_path_get_trash(), false);
|
||||
}
|
||||
|
||||
static Path appsMenu(void) { /* menu://applications.menu/ */
|
||||
return Path(fm_path_get_apps_menu(), false);
|
||||
}
|
||||
|
||||
Path parent() {
|
||||
return Path(data_ != nullptr ? fm_path_get_parent(data_) : nullptr, false);
|
||||
}
|
||||
|
||||
const char* basename() {
|
||||
return fm_path_get_basename(data_);
|
||||
}
|
||||
|
||||
FmPathFlags flags() {
|
||||
return fm_path_get_flags(data_);
|
||||
}
|
||||
|
||||
bool hasPrefix(FmPath* prefix) {
|
||||
return fm_path_has_prefix(data_, prefix);
|
||||
}
|
||||
|
||||
Path schemePath() {
|
||||
return Path(fm_path_get_scheme_path(data_), true);
|
||||
}
|
||||
|
||||
bool isNative() {
|
||||
return fm_path_is_native(data_);
|
||||
}
|
||||
|
||||
bool isTrash() {
|
||||
return fm_path_is_trash(data_);
|
||||
}
|
||||
|
||||
bool isTrashRoot() {
|
||||
return fm_path_is_trash_root(data_);
|
||||
}
|
||||
|
||||
bool isNativeOrTrash() {
|
||||
return fm_path_is_native_or_trash(data_);
|
||||
}
|
||||
|
||||
char* toString() {
|
||||
return fm_path_to_str(data_);
|
||||
}
|
||||
|
||||
QByteArray toByteArray() {
|
||||
char* s = fm_path_to_str(data_);
|
||||
QByteArray str(s);
|
||||
g_free(s);
|
||||
return str;
|
||||
}
|
||||
|
||||
char* toUri() {
|
||||
return fm_path_to_uri(data_);
|
||||
}
|
||||
|
||||
GFile* toGfile() {
|
||||
return fm_path_to_gfile(data_);
|
||||
}
|
||||
|
||||
/*
|
||||
char* displayName(bool human_readable = true) {
|
||||
return fm_path_display_name(data_, human_readable);
|
||||
}
|
||||
*/
|
||||
|
||||
QString displayName(bool human_readable = true) {
|
||||
char* dispname = fm_path_display_name(data_, human_readable);
|
||||
QString str = QString::fromUtf8(dispname);
|
||||
g_free(dispname);
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
char* displayBasename() {
|
||||
return fm_path_display_basename(data_);
|
||||
}
|
||||
*/
|
||||
|
||||
QString displayBasename() {
|
||||
char* basename = fm_path_display_basename(data_);
|
||||
QString s = QString::fromUtf8(basename);
|
||||
g_free(basename);
|
||||
return s;
|
||||
}
|
||||
|
||||
/* For used in hash tables */
|
||||
guint hash() {
|
||||
return fm_path_hash(data_);
|
||||
}
|
||||
|
||||
void take(FmPath* path) { // take the ownership of the "path"
|
||||
if(data_)
|
||||
fm_path_unref(data_);
|
||||
data_ = path;
|
||||
}
|
||||
|
||||
Path& operator = (const Path& other) {
|
||||
if(data_)
|
||||
fm_path_unref(data_);
|
||||
data_ = fm_path_ref(other.data_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator == (const Path& other) const {
|
||||
return fm_path_equal(data_, other.data_);
|
||||
}
|
||||
|
||||
bool operator != (const Path& other) const {
|
||||
return !fm_path_equal(data_, other.data_);
|
||||
}
|
||||
|
||||
bool operator < (const Path& other) const {
|
||||
return compare(other);
|
||||
}
|
||||
|
||||
bool operator > (const Path& other) const {
|
||||
return (other < *this);
|
||||
}
|
||||
|
||||
/* can be used for sorting */
|
||||
int compare(const Path& other) const {
|
||||
return fm_path_compare(data_, other.data_);
|
||||
}
|
||||
|
||||
/* used for completion in entry */
|
||||
bool equal(const gchar *str, int n) const {
|
||||
return fm_path_equal_str(data_, str, n);
|
||||
}
|
||||
|
||||
/* calculate how many elements are in this path. */
|
||||
int depth() const {
|
||||
return fm_path_depth(data_);
|
||||
}
|
||||
|
||||
FmPath* data() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
private:
|
||||
FmPath* data_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_OPAQUE_POINTER(FmPath*)
|
||||
|
||||
#endif // FM_PATH_H
|
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "pathedit.h"
|
||||
#include "pathedit_p.h"
|
||||
#include <QCompleter>
|
||||
#include <QStringListModel>
|
||||
#include <QStringBuilder>
|
||||
#include <QThread>
|
||||
#include <QDebug>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
void PathEditJob::runJob() {
|
||||
GError *err = NULL;
|
||||
GFileEnumerator* enu = g_file_enumerate_children(dirName,
|
||||
// G_FILE_ATTRIBUTE_STANDARD_NAME","
|
||||
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","
|
||||
G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
||||
G_FILE_QUERY_INFO_NONE, cancellable,
|
||||
&err);
|
||||
if(enu) {
|
||||
while(!g_cancellable_is_cancelled(cancellable)) {
|
||||
GFileInfo* inf = g_file_enumerator_next_file(enu, cancellable, &err);
|
||||
if(inf) {
|
||||
GFileType type = g_file_info_get_file_type(inf);
|
||||
if(type == G_FILE_TYPE_DIRECTORY) {
|
||||
const char* name = g_file_info_get_display_name(inf);
|
||||
// FIXME: encoding conversion here?
|
||||
subDirs.append(QString::fromUtf8(name));
|
||||
}
|
||||
g_object_unref(inf);
|
||||
}
|
||||
else {
|
||||
if(err) {
|
||||
g_error_free(err);
|
||||
err = NULL;
|
||||
}
|
||||
else /* EOF */
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_file_enumerator_close(enu, cancellable, NULL);
|
||||
g_object_unref(enu);
|
||||
}
|
||||
// finished! let's update the UI in the main thread
|
||||
Q_EMIT finished();
|
||||
}
|
||||
|
||||
|
||||
PathEdit::PathEdit(QWidget* parent):
|
||||
QLineEdit(parent),
|
||||
cancellable_(NULL),
|
||||
model_(new QStringListModel()),
|
||||
completer_(new QCompleter()) {
|
||||
setCompleter(completer_);
|
||||
completer_->setModel(model_);
|
||||
connect(this, &PathEdit::textChanged, this, &PathEdit::onTextChanged);
|
||||
}
|
||||
|
||||
PathEdit::~PathEdit() {
|
||||
delete completer_;
|
||||
if(model_)
|
||||
delete model_;
|
||||
if(cancellable_) {
|
||||
g_cancellable_cancel(cancellable_);
|
||||
g_object_unref(cancellable_);
|
||||
}
|
||||
}
|
||||
|
||||
void PathEdit::focusInEvent(QFocusEvent* e) {
|
||||
QLineEdit::focusInEvent(e);
|
||||
// build the completion list only when we have the keyboard focus
|
||||
reloadCompleter(true);
|
||||
}
|
||||
|
||||
void PathEdit::focusOutEvent(QFocusEvent* e) {
|
||||
QLineEdit::focusOutEvent(e);
|
||||
// free the completion list since we don't need it anymore
|
||||
freeCompleter();
|
||||
}
|
||||
|
||||
void PathEdit::onTextChanged(const QString& text) {
|
||||
int pos = text.lastIndexOf('/');
|
||||
if(pos >= 0)
|
||||
++pos;
|
||||
else
|
||||
pos = text.length();
|
||||
QString newPrefix = text.left(pos);
|
||||
if(currentPrefix_ != newPrefix) {
|
||||
currentPrefix_ = newPrefix;
|
||||
// only build the completion list if we have the keyboard focus
|
||||
// if we don't have the focus now, then we'll rebuild the completion list
|
||||
// when focusInEvent happens. this avoid unnecessary dir loading.
|
||||
if(hasFocus())
|
||||
reloadCompleter(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PathEdit::reloadCompleter(bool triggeredByFocusInEvent) {
|
||||
// parent dir has been changed, reload dir list
|
||||
// if(currentPrefix_[0] == "~") { // special case for home dir
|
||||
// cancel running dir-listing jobs, if there's any
|
||||
if(cancellable_) {
|
||||
g_cancellable_cancel(cancellable_);
|
||||
g_object_unref(cancellable_);
|
||||
}
|
||||
|
||||
// create a new job to do dir listing
|
||||
PathEditJob* job = new PathEditJob();
|
||||
job->edit = this;
|
||||
job->triggeredByFocusInEvent = triggeredByFocusInEvent;
|
||||
// need to use fm_file_new_for_commandline_arg() rather than g_file_new_for_commandline_arg().
|
||||
// otherwise, our own vfs, such as menu://, won't be loaded.
|
||||
job->dirName = fm_file_new_for_commandline_arg(currentPrefix_.toLocal8Bit().constData());
|
||||
// qDebug("load: %s", g_file_get_uri(data->dirName));
|
||||
cancellable_ = g_cancellable_new();
|
||||
job->cancellable = (GCancellable*)g_object_ref(cancellable_);
|
||||
|
||||
// launch a new worker thread to handle the job
|
||||
QThread* thread = new QThread();
|
||||
job->moveToThread(thread);
|
||||
connect(thread, &QThread::started, job, &PathEditJob::runJob);
|
||||
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
||||
connect(thread, &QThread::finished, job, &QObject::deleteLater);
|
||||
connect(job, &PathEditJob::finished, this, &PathEdit::onJobFinished);
|
||||
thread->start(QThread::LowPriority);
|
||||
}
|
||||
|
||||
void PathEdit::freeCompleter() {
|
||||
if(cancellable_) {
|
||||
g_cancellable_cancel(cancellable_);
|
||||
g_object_unref(cancellable_);
|
||||
cancellable_ = NULL;
|
||||
}
|
||||
model_->setStringList(QStringList());
|
||||
}
|
||||
|
||||
// This slot is called from main thread so it's safe to access the GUI
|
||||
void PathEdit::onJobFinished() {
|
||||
PathEditJob* data = static_cast<PathEditJob*>(sender());
|
||||
if(!g_cancellable_is_cancelled(data->cancellable)) {
|
||||
// update the completer only if the job is not cancelled
|
||||
QStringList::iterator it;
|
||||
for(it = data->subDirs.begin(); it != data->subDirs.end(); ++it) {
|
||||
// qDebug("%s", it->toUtf8().constData());
|
||||
*it = (currentPrefix_ % *it);
|
||||
}
|
||||
model_->setStringList(data->subDirs);
|
||||
// trigger completion manually
|
||||
if(hasFocus() && !data->triggeredByFocusInEvent)
|
||||
completer_->complete();
|
||||
}
|
||||
else
|
||||
model_->setStringList(QStringList());
|
||||
if(cancellable_) {
|
||||
g_object_unref(cancellable_);
|
||||
cancellable_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_PATHEDIT_H
|
||||
#define FM_PATHEDIT_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QLineEdit>
|
||||
#include <gio/gio.h>
|
||||
|
||||
class QCompleter;
|
||||
class QStringListModel;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class PathEditJob;
|
||||
|
||||
class LIBFM_QT_API PathEdit : public QLineEdit {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PathEdit(QWidget* parent = 0);
|
||||
virtual ~PathEdit();
|
||||
|
||||
protected:
|
||||
virtual void focusInEvent(QFocusEvent* e);
|
||||
virtual void focusOutEvent(QFocusEvent* e);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onTextChanged(const QString & text);
|
||||
|
||||
private:
|
||||
void reloadCompleter(bool triggeredByFocusInEvent = false);
|
||||
void freeCompleter();
|
||||
void onJobFinished();
|
||||
|
||||
private:
|
||||
QCompleter* completer_;
|
||||
QStringListModel* model_;
|
||||
QString currentPrefix_;
|
||||
GCancellable* cancellable_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_PATHEDIT_H
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_PATHEDIT_P_H
|
||||
#define FM_PATHEDIT_P_H
|
||||
|
||||
#include <QObject>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class PathEdit;
|
||||
|
||||
class PathEditJob : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GCancellable* cancellable;
|
||||
GFile* dirName;
|
||||
QStringList subDirs;
|
||||
PathEdit* edit;
|
||||
bool triggeredByFocusInEvent;
|
||||
|
||||
~PathEditJob() {
|
||||
g_object_unref(dirName);
|
||||
g_object_unref(cancellable);
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void finished();
|
||||
|
||||
public Q_SLOTS:
|
||||
void runJob();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_PATHEDIT_P_H
|
@ -0,0 +1,608 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "placesmodel.h"
|
||||
#include "icontheme.h"
|
||||
#include <gio/gio.h>
|
||||
#include <QDebug>
|
||||
#include <QMimeData>
|
||||
#include <QTimer>
|
||||
#include <QPointer>
|
||||
#include "utilities.h"
|
||||
#include "placesmodelitem.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
PlacesModel::PlacesModel(QObject* parent):
|
||||
QStandardItemModel(parent),
|
||||
showApplications_(true),
|
||||
showDesktop_(true),
|
||||
ejectIcon_(QIcon::fromTheme("media-eject")) {
|
||||
|
||||
setColumnCount(2);
|
||||
|
||||
placesRoot = new QStandardItem(tr("Places"));
|
||||
placesRoot->setSelectable(false);
|
||||
placesRoot->setColumnCount(2);
|
||||
appendRow(placesRoot);
|
||||
|
||||
homeItem = new PlacesModelItem("user-home", g_get_user_name(), fm_path_get_home());
|
||||
placesRoot->appendRow(homeItem);
|
||||
|
||||
desktopItem = new PlacesModelItem("user-desktop", tr("Desktop"), fm_path_get_desktop());
|
||||
placesRoot->appendRow(desktopItem);
|
||||
|
||||
createTrashItem();
|
||||
|
||||
FmPath* path;
|
||||
// FIXME: add an option to hide network:///
|
||||
if(true) {
|
||||
path = fm_path_new_for_uri("computer:///");
|
||||
computerItem = new PlacesModelItem("computer", tr("Computer"), path);
|
||||
fm_path_unref(path);
|
||||
placesRoot->appendRow(computerItem);
|
||||
}
|
||||
else
|
||||
computerItem = NULL;
|
||||
|
||||
// FIXME: add an option to hide applications:///
|
||||
const char* applicaion_icon_names[] = {"system-software-install", "applications-accessories", "application-x-executable"};
|
||||
// NOTE: g_themed_icon_new_from_names() accepts char**, but actually const char** is OK.
|
||||
GIcon* gicon = g_themed_icon_new_from_names((char**)applicaion_icon_names, G_N_ELEMENTS(applicaion_icon_names));
|
||||
FmIcon* fmicon = fm_icon_from_gicon(gicon);
|
||||
g_object_unref(gicon);
|
||||
applicationsItem = new PlacesModelItem(fmicon, tr("Applications"), fm_path_get_apps_menu());
|
||||
fm_icon_unref(fmicon);
|
||||
placesRoot->appendRow(applicationsItem);
|
||||
|
||||
// FIXME: add an option to hide network:///
|
||||
if(true) {
|
||||
const char* network_icon_names[] = {"network", "folder-network", "folder"};
|
||||
// NOTE: g_themed_icon_new_from_names() accepts char**, but actually const char** is OK.
|
||||
gicon = g_themed_icon_new_from_names((char**)network_icon_names, G_N_ELEMENTS(network_icon_names));
|
||||
fmicon = fm_icon_from_gicon(gicon);
|
||||
g_object_unref(gicon);
|
||||
path = fm_path_new_for_uri("network:///");
|
||||
networkItem = new PlacesModelItem(fmicon, tr("Network"), path);
|
||||
fm_icon_unref(fmicon);
|
||||
fm_path_unref(path);
|
||||
placesRoot->appendRow(networkItem);
|
||||
}
|
||||
else
|
||||
networkItem = NULL;
|
||||
|
||||
devicesRoot = new QStandardItem(tr("Devices"));
|
||||
devicesRoot->setSelectable(false);
|
||||
devicesRoot->setColumnCount(2);
|
||||
appendRow(devicesRoot);
|
||||
|
||||
// volumes
|
||||
volumeMonitor = g_volume_monitor_get();
|
||||
if(volumeMonitor) {
|
||||
g_signal_connect(volumeMonitor, "volume-added", G_CALLBACK(onVolumeAdded), this);
|
||||
g_signal_connect(volumeMonitor, "volume-removed", G_CALLBACK(onVolumeRemoved), this);
|
||||
g_signal_connect(volumeMonitor, "volume-changed", G_CALLBACK(onVolumeChanged), this);
|
||||
g_signal_connect(volumeMonitor, "mount-added", G_CALLBACK(onMountAdded), this);
|
||||
g_signal_connect(volumeMonitor, "mount-changed", G_CALLBACK(onMountChanged), this);
|
||||
g_signal_connect(volumeMonitor, "mount-removed", G_CALLBACK(onMountRemoved), this);
|
||||
|
||||
// add volumes to side-pane
|
||||
GList* vols = g_volume_monitor_get_volumes(volumeMonitor);
|
||||
GList* l;
|
||||
for(l = vols; l; l = l->next) {
|
||||
GVolume* volume = G_VOLUME(l->data);
|
||||
onVolumeAdded(volumeMonitor, volume, this);
|
||||
g_object_unref(volume);
|
||||
}
|
||||
g_list_free(vols);
|
||||
|
||||
/* add mounts to side-pane */
|
||||
vols = g_volume_monitor_get_mounts(volumeMonitor);
|
||||
for(l = vols; l; l = l->next) {
|
||||
GMount* mount = G_MOUNT(l->data);
|
||||
GVolume* volume = g_mount_get_volume(mount);
|
||||
if(volume)
|
||||
g_object_unref(volume);
|
||||
else { /* network mounts or others */
|
||||
gboolean shadowed = FALSE;
|
||||
#if GLIB_CHECK_VERSION(2, 20, 0)
|
||||
shadowed = g_mount_is_shadowed(mount);
|
||||
#endif
|
||||
// according to gio API doc, a shadowed mount should not be visible to the user
|
||||
if(shadowed) {
|
||||
shadowedMounts_.push_back(mount);
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
PlacesModelItem* item = new PlacesModelMountItem(mount);
|
||||
devicesRoot->appendRow(item);
|
||||
}
|
||||
}
|
||||
g_object_unref(mount);
|
||||
}
|
||||
g_list_free(vols);
|
||||
}
|
||||
|
||||
// bookmarks
|
||||
bookmarksRoot = new QStandardItem(tr("Bookmarks"));
|
||||
bookmarksRoot->setSelectable(false);
|
||||
bookmarksRoot->setColumnCount(2);
|
||||
appendRow(bookmarksRoot);
|
||||
|
||||
bookmarks = fm_bookmarks_dup();
|
||||
loadBookmarks();
|
||||
g_signal_connect(bookmarks, "changed", G_CALLBACK(onBookmarksChanged), this);
|
||||
|
||||
// update some icons when the icon theme is changed
|
||||
connect(IconTheme::instance(), &IconTheme::changed, this, &PlacesModel::updateIcons);
|
||||
}
|
||||
|
||||
void PlacesModel::loadBookmarks() {
|
||||
GList* allBookmarks = fm_bookmarks_get_all(bookmarks);
|
||||
for(GList* l = allBookmarks; l; l = l->next) {
|
||||
FmBookmarkItem* bm_item = (FmBookmarkItem*)l->data;
|
||||
PlacesModelBookmarkItem* item = new PlacesModelBookmarkItem(bm_item);
|
||||
bookmarksRoot->appendRow(item);
|
||||
}
|
||||
g_list_free_full(allBookmarks, (GDestroyNotify)fm_bookmark_item_unref);
|
||||
}
|
||||
|
||||
PlacesModel::~PlacesModel() {
|
||||
if(bookmarks) {
|
||||
g_signal_handlers_disconnect_by_func(bookmarks, (gpointer)onBookmarksChanged, this);
|
||||
g_object_unref(bookmarks);
|
||||
}
|
||||
if(volumeMonitor) {
|
||||
g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onVolumeAdded), this);
|
||||
g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onVolumeRemoved), this);
|
||||
g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onVolumeChanged), this);
|
||||
g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onMountAdded), this);
|
||||
g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onMountChanged), this);
|
||||
g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onMountRemoved), this);
|
||||
g_object_unref(volumeMonitor);
|
||||
}
|
||||
if(trashMonitor_) {
|
||||
g_signal_handlers_disconnect_by_func(trashMonitor_, (gpointer)G_CALLBACK(onTrashChanged), this);
|
||||
g_object_unref(trashMonitor_);
|
||||
}
|
||||
|
||||
Q_FOREACH(GMount* mount, shadowedMounts_) {
|
||||
g_object_unref(mount);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void PlacesModel::onTrashChanged(GFileMonitor* monitor, GFile* gf, GFile* other, GFileMonitorEvent evt, PlacesModel* pThis) {
|
||||
QTimer::singleShot(0, pThis, SLOT(updateTrash()));
|
||||
}
|
||||
|
||||
void PlacesModel::updateTrash() {
|
||||
|
||||
struct UpdateTrashData {
|
||||
QPointer<PlacesModel> model;
|
||||
GFile* gf;
|
||||
UpdateTrashData(PlacesModel* _model) : model(_model) {
|
||||
gf = fm_file_new_for_uri("trash:///");
|
||||
}
|
||||
~UpdateTrashData() {
|
||||
g_object_unref(gf);
|
||||
}
|
||||
};
|
||||
|
||||
if(trashItem_) {
|
||||
UpdateTrashData* data = new UpdateTrashData(this);
|
||||
g_file_query_info_async(data->gf, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT, G_FILE_QUERY_INFO_NONE, G_PRIORITY_LOW, NULL,
|
||||
[](GObject *source_object, GAsyncResult *res, gpointer user_data) {
|
||||
// the callback lambda function is called when the asyn query operation is finished
|
||||
UpdateTrashData* data = reinterpret_cast<UpdateTrashData*>(user_data);
|
||||
PlacesModel* _this = data->model.data();
|
||||
if(_this != nullptr) { // ensure that our model object is not deleted yet
|
||||
GFileInfo* inf = g_file_query_info_finish(data->gf, res, NULL);
|
||||
if(inf) {
|
||||
if(_this->trashItem_ != nullptr) { // it's possible that when we finish, the trash item is removed
|
||||
guint32 n = g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT);
|
||||
const char* icon_name = n > 0 ? "user-trash-full" : "user-trash";
|
||||
FmIcon* icon = fm_icon_from_name(icon_name);
|
||||
_this->trashItem_->setIcon(icon);
|
||||
fm_icon_unref(icon);
|
||||
}
|
||||
g_object_unref(inf);
|
||||
}
|
||||
}
|
||||
delete data; // free the data used for this async operation.
|
||||
}, data);
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesModel::createTrashItem() {
|
||||
GFile* gf;
|
||||
gf = fm_file_new_for_uri("trash:///");
|
||||
// check if trash is supported by the current vfs
|
||||
// if gvfs is not installed, this can be unavailable.
|
||||
if(!g_file_query_exists(gf, NULL)) {
|
||||
g_object_unref(gf);
|
||||
trashItem_ = NULL;
|
||||
trashMonitor_ = NULL;
|
||||
return;
|
||||
}
|
||||
trashItem_ = new PlacesModelItem("user-trash", tr("Trash"), fm_path_get_trash());
|
||||
|
||||
trashMonitor_ = fm_monitor_directory(gf, NULL);
|
||||
if(trashMonitor_)
|
||||
g_signal_connect(trashMonitor_, "changed", G_CALLBACK(onTrashChanged), this);
|
||||
g_object_unref(gf);
|
||||
|
||||
placesRoot->insertRow(desktopItem->row() + 1, trashItem_);
|
||||
QTimer::singleShot(0, this, SLOT(updateTrash()));
|
||||
}
|
||||
|
||||
void PlacesModel::setShowApplications(bool show) {
|
||||
if(showApplications_ != show) {
|
||||
showApplications_ = show;
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesModel::setShowDesktop(bool show) {
|
||||
if(showDesktop_ != show) {
|
||||
showDesktop_ = show;
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesModel::setShowTrash(bool show) {
|
||||
if(show) {
|
||||
if(!trashItem_)
|
||||
createTrashItem();
|
||||
}
|
||||
else {
|
||||
if(trashItem_) {
|
||||
if(trashMonitor_) {
|
||||
g_signal_handlers_disconnect_by_func(trashMonitor_, (gpointer)G_CALLBACK(onTrashChanged), this);
|
||||
g_object_unref(trashMonitor_);
|
||||
trashMonitor_ = NULL;
|
||||
}
|
||||
placesRoot->removeRow(trashItem_->row()); // delete trashItem_;
|
||||
trashItem_ = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlacesModelItem* PlacesModel::itemFromPath(FmPath* path) {
|
||||
PlacesModelItem* item = itemFromPath(placesRoot, path);
|
||||
if(!item)
|
||||
item = itemFromPath(devicesRoot, path);
|
||||
if(!item)
|
||||
item = itemFromPath(bookmarksRoot, path);
|
||||
return item;
|
||||
}
|
||||
|
||||
PlacesModelItem* PlacesModel::itemFromPath(QStandardItem* rootItem, FmPath* path) {
|
||||
int rowCount = rootItem->rowCount();
|
||||
for(int i = 0; i < rowCount; ++i) {
|
||||
PlacesModelItem* item = static_cast<PlacesModelItem*>(rootItem->child(i, 0));
|
||||
if(fm_path_equal(item->path(), path))
|
||||
return item;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PlacesModelVolumeItem* PlacesModel::itemFromVolume(GVolume* volume) {
|
||||
int rowCount = devicesRoot->rowCount();
|
||||
for(int i = 0; i < rowCount; ++i) {
|
||||
PlacesModelItem* item = static_cast<PlacesModelItem*>(devicesRoot->child(i, 0));
|
||||
if(item->type() == PlacesModelItem::Volume) {
|
||||
PlacesModelVolumeItem* volumeItem = static_cast<PlacesModelVolumeItem*>(item);
|
||||
if(volumeItem->volume() == volume)
|
||||
return volumeItem;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PlacesModelMountItem* PlacesModel::itemFromMount(GMount* mount) {
|
||||
int rowCount = devicesRoot->rowCount();
|
||||
for(int i = 0; i < rowCount; ++i) {
|
||||
PlacesModelItem* item = static_cast<PlacesModelItem*>(devicesRoot->child(i, 0));
|
||||
if(item->type() == PlacesModelItem::Mount) {
|
||||
PlacesModelMountItem* mountItem = static_cast<PlacesModelMountItem*>(item);
|
||||
if(mountItem->mount() == mount)
|
||||
return mountItem;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PlacesModelBookmarkItem* PlacesModel::itemFromBookmark(FmBookmarkItem* bkitem) {
|
||||
int rowCount = bookmarksRoot->rowCount();
|
||||
for(int i = 0; i < rowCount; ++i) {
|
||||
PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(bookmarksRoot->child(i, 0));
|
||||
if(item->bookmark() == bkitem)
|
||||
return item;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void PlacesModel::onMountAdded(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis) {
|
||||
// according to gio API doc, a shadowed mount should not be visible to the user
|
||||
#if GLIB_CHECK_VERSION(2, 20, 0)
|
||||
if(g_mount_is_shadowed(mount)) {
|
||||
if(pThis->shadowedMounts_.indexOf(mount) == -1)
|
||||
pThis->shadowedMounts_.push_back(G_MOUNT(g_object_ref(mount)));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
GVolume* vol = g_mount_get_volume(mount);
|
||||
if(vol) { // mount-added is also emitted when a volume is newly mounted.
|
||||
PlacesModelVolumeItem* item = pThis->itemFromVolume(vol);
|
||||
if(item && !item->path()) {
|
||||
// update the mounted volume and show a button for eject.
|
||||
GFile* gf = g_mount_get_root(mount);
|
||||
FmPath* path = fm_path_new_for_gfile(gf);
|
||||
g_object_unref(gf);
|
||||
item->setPath(path);
|
||||
if(path)
|
||||
fm_path_unref(path);
|
||||
// update the mount indicator (eject button)
|
||||
QStandardItem* ejectBtn = item->parent()->child(item->row(), 1);
|
||||
Q_ASSERT(ejectBtn);
|
||||
ejectBtn->setIcon(pThis->ejectIcon_);
|
||||
}
|
||||
g_object_unref(vol);
|
||||
}
|
||||
else { // network mounts and others
|
||||
PlacesModelMountItem* item = pThis->itemFromMount(mount);
|
||||
/* for some unknown reasons, sometimes we get repeated mount-added
|
||||
* signals and added a device more than one. So, make a sanity check here. */
|
||||
if(!item) {
|
||||
item = new PlacesModelMountItem(mount);
|
||||
QStandardItem* eject_btn = new QStandardItem(pThis->ejectIcon_, QString());
|
||||
pThis->devicesRoot->appendRow(QList<QStandardItem*>() << item << eject_btn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesModel::onMountChanged(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis) {
|
||||
gboolean shadowed = FALSE;
|
||||
// according to gio API doc, a shadowed mount should not be visible to the user
|
||||
#if GLIB_CHECK_VERSION(2, 20, 0)
|
||||
shadowed = g_mount_is_shadowed(mount);
|
||||
// qDebug() << "changed:" << mount << shadowed;
|
||||
#endif
|
||||
PlacesModelMountItem* item = pThis->itemFromMount(mount);
|
||||
if(item) {
|
||||
if(shadowed) { // if a visible item becomes shadowed, remove it from the model
|
||||
pThis->shadowedMounts_.push_back(G_MOUNT(g_object_ref(mount))); // remember the shadowed mount
|
||||
pThis->devicesRoot->removeRow(item->row());
|
||||
}
|
||||
else { // otherwise, update its status
|
||||
item->update();
|
||||
}
|
||||
}
|
||||
else {
|
||||
#if GLIB_CHECK_VERSION(2, 20, 0)
|
||||
if(!shadowed) { // if a mount is unshadowed
|
||||
int i = pThis->shadowedMounts_.indexOf(mount);
|
||||
if(i != -1) { // a previously shadowed mount is unshadowed
|
||||
pThis->shadowedMounts_.removeAt(i);
|
||||
onMountAdded(monitor, mount, pThis); // add it to our model again
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesModel::onMountRemoved(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis) {
|
||||
GVolume* vol = g_mount_get_volume(mount);
|
||||
// qDebug() << "mount removed" << mount << "volume umounted: " << vol;
|
||||
if(vol) {
|
||||
// a volume is being unmounted
|
||||
// NOTE: Due to some problems of gvfs, sometimes the volume does not receive
|
||||
// "change" signal so it does not update the eject button. Let's workaround
|
||||
// this by calling onVolumeChanged() handler manually. (This is needed for mtp://)
|
||||
onVolumeChanged(monitor, vol, pThis);
|
||||
g_object_unref(vol);
|
||||
}
|
||||
else { // network mounts and others
|
||||
PlacesModelMountItem* item = pThis->itemFromMount(mount);
|
||||
if(item) {
|
||||
pThis->devicesRoot->removeRow(item->row());
|
||||
}
|
||||
}
|
||||
|
||||
#if GLIB_CHECK_VERSION(2, 20, 0)
|
||||
// NOTE: g_mount_is_shadowed() sometimes returns FALSE here even if the mount is shadowed.
|
||||
// I don't know whether this is a bug in gvfs or not.
|
||||
// So let's check if its in our list instead.
|
||||
if(pThis->shadowedMounts_.removeOne(mount)) {
|
||||
// if this is a shadowed mount
|
||||
// qDebug() << "remove shadow mount";
|
||||
g_object_unref(mount);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void PlacesModel::onVolumeAdded(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis) {
|
||||
// for some unknown reasons, sometimes we get repeated volume-added
|
||||
// signals and added a device more than one. So, make a sanity check here.
|
||||
PlacesModelVolumeItem* volumeItem = pThis->itemFromVolume(volume);
|
||||
if(!volumeItem) {
|
||||
volumeItem = new PlacesModelVolumeItem(volume);
|
||||
QStandardItem* ejectBtn = new QStandardItem();
|
||||
if(volumeItem->isMounted())
|
||||
ejectBtn->setIcon(pThis->ejectIcon_);
|
||||
pThis->devicesRoot->appendRow(QList<QStandardItem*>() << volumeItem << ejectBtn);
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesModel::onVolumeChanged(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis) {
|
||||
PlacesModelVolumeItem* item = pThis->itemFromVolume(volume);
|
||||
if(item) {
|
||||
item->update();
|
||||
if(!item->isMounted()) { // the volume is unmounted, remove the eject button if needed
|
||||
// remove the eject button for the volume (at column 1 of the same row)
|
||||
QStandardItem* ejectBtn = item->parent()->child(item->row(), 1);
|
||||
Q_ASSERT(ejectBtn);
|
||||
ejectBtn->setIcon(QIcon());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesModel::onVolumeRemoved(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis) {
|
||||
PlacesModelVolumeItem* item = pThis->itemFromVolume(volume);
|
||||
if(item) {
|
||||
pThis->devicesRoot->removeRow(item->row());
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesModel::onBookmarksChanged(FmBookmarks* bookmarks, PlacesModel* pThis) {
|
||||
// remove all items
|
||||
pThis->bookmarksRoot->removeRows(0, pThis->bookmarksRoot->rowCount());
|
||||
pThis->loadBookmarks();
|
||||
}
|
||||
|
||||
void PlacesModel::updateIcons() {
|
||||
// the icon theme is changed and we need to update the icons
|
||||
PlacesModelItem* item;
|
||||
int row;
|
||||
int n = placesRoot->rowCount();
|
||||
for(row = 0; row < n; ++row) {
|
||||
item = static_cast<PlacesModelItem*>(placesRoot->child(row));
|
||||
item->updateIcon();
|
||||
}
|
||||
n = devicesRoot->rowCount();
|
||||
for(row = 0; row < n; ++row) {
|
||||
item = static_cast<PlacesModelItem*>(devicesRoot->child(row));
|
||||
item->updateIcon();
|
||||
}
|
||||
}
|
||||
|
||||
Qt::ItemFlags PlacesModel::flags(const QModelIndex& index) const {
|
||||
if(index.column() == 1) // make 2nd column of every row selectable.
|
||||
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
||||
if(!index.parent().isValid()) { // root items
|
||||
if(index.row() == 2) // bookmarks root
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled;
|
||||
else
|
||||
return Qt::ItemIsEnabled;
|
||||
}
|
||||
return QStandardItemModel::flags(index);
|
||||
}
|
||||
|
||||
bool PlacesModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
|
||||
QStandardItem* item = itemFromIndex(parent);
|
||||
if(data->hasFormat("application/x-bookmark-row")) { // the data being dopped is a bookmark row
|
||||
// decode it and do bookmark reordering
|
||||
QByteArray buf = data->data("application/x-bookmark-row");
|
||||
QDataStream stream(&buf, QIODevice::ReadOnly);
|
||||
int oldPos = -1;
|
||||
char* pathStr = NULL;
|
||||
stream >> oldPos >> pathStr;
|
||||
// find the source bookmark item being dragged
|
||||
GList* allBookmarks = fm_bookmarks_get_all(bookmarks);
|
||||
FmBookmarkItem* draggedItem = static_cast<FmBookmarkItem*>(g_list_nth_data(allBookmarks, oldPos));
|
||||
// If we cannot find the dragged bookmark item at position <oldRow>, or we find an item,
|
||||
// but the path of the item is not the same as what we expected, than it's the wrong item.
|
||||
// This means that the bookmarks are changed during our dnd processing, which is an extremely rare case.
|
||||
if(!draggedItem || !fm_path_equal_str(draggedItem->path, pathStr, -1)) {
|
||||
delete []pathStr;
|
||||
return false;
|
||||
}
|
||||
delete []pathStr;
|
||||
|
||||
int newPos = -1;
|
||||
if(row == -1 && column == -1) { // drop on an item
|
||||
// we only allow dropping on an bookmark item
|
||||
if(item && item->parent() == bookmarksRoot)
|
||||
newPos = parent.row();
|
||||
}
|
||||
else { // drop on a position between items
|
||||
if(item == bookmarksRoot) // we only allow dropping on a bookmark item
|
||||
newPos = row;
|
||||
}
|
||||
if(newPos != -1 && newPos != oldPos) // reorder the bookmark item
|
||||
fm_bookmarks_reorder(bookmarks, draggedItem, newPos);
|
||||
}
|
||||
else if(data->hasUrls()) { // files uris are dropped
|
||||
if(row == -1 && column == -1) { // drop uris on an item
|
||||
if(item && item->parent()) { // need to be a child item
|
||||
PlacesModelItem* placesItem = static_cast<PlacesModelItem*>(item);
|
||||
if(placesItem->path()) {
|
||||
qDebug() << "dropped dest:" << placesItem->text();
|
||||
// TODO: copy or move the dragged files to the dir pointed by the item.
|
||||
qDebug() << "drop on" << item->text();
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // drop uris on a position between items
|
||||
if(item == bookmarksRoot) { // we only allow dropping on blank row of bookmarks section
|
||||
FmPathList* paths = pathListFromQUrls(data->urls());
|
||||
for(GList* l = fm_path_list_peek_head_link(paths); l; l = l->next) {
|
||||
FmPath* path = FM_PATH(l->data);
|
||||
GFile* gf = fm_path_to_gfile(path);
|
||||
// FIXME: this is a blocking call
|
||||
if(g_file_query_file_type(gf, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
NULL) == G_FILE_TYPE_DIRECTORY) {
|
||||
char* disp_name = fm_path_display_basename(path);
|
||||
fm_bookmarks_insert(bookmarks, path, disp_name, row);
|
||||
g_free(disp_name);
|
||||
}
|
||||
g_object_unref(gf);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// we only support dragging bookmark items and use our own
|
||||
// custom pseudo-mime-type: application/x-bookmark-row
|
||||
QMimeData* PlacesModel::mimeData(const QModelIndexList& indexes) const {
|
||||
if(!indexes.isEmpty()) {
|
||||
// we only allow dragging one bookmark item at a time, so handle the first index only.
|
||||
QModelIndex index = indexes.first();
|
||||
QStandardItem* item = itemFromIndex(index);
|
||||
// ensure that it's really a bookmark item
|
||||
if(item && item->parent() == bookmarksRoot) {
|
||||
PlacesModelBookmarkItem* bookmarkItem = static_cast<PlacesModelBookmarkItem*>(item);
|
||||
QMimeData* mime = new QMimeData();
|
||||
QByteArray data;
|
||||
QDataStream stream(&data, QIODevice::WriteOnly);
|
||||
// There is no safe and cross-process way to store a reference of a row.
|
||||
// Let's store the pos, name, and path of the bookmark item instead.
|
||||
char* pathStr = fm_path_to_str(bookmarkItem->path());
|
||||
stream << index.row() << pathStr;
|
||||
g_free(pathStr);
|
||||
mime->setData("application/x-bookmark-row", data);
|
||||
return mime;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
QStringList PlacesModel::mimeTypes() const {
|
||||
return QStringList() << "application/x-bookmark-row" << "text/uri-list";
|
||||
}
|
||||
|
||||
Qt::DropActions PlacesModel::supportedDropActions() const {
|
||||
return QStandardItemModel::supportedDropActions();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_PLACESMODEL_H
|
||||
#define FM_PLACESMODEL_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QStandardItemModel>
|
||||
#include <QStandardItem>
|
||||
#include <QList>
|
||||
#include <QAction>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class PlacesModelItem;
|
||||
class PlacesModelVolumeItem;
|
||||
class PlacesModelMountItem;
|
||||
class PlacesModelBookmarkItem;
|
||||
|
||||
class LIBFM_QT_API PlacesModel : public QStandardItemModel {
|
||||
Q_OBJECT
|
||||
friend class PlacesView;
|
||||
public:
|
||||
|
||||
// QAction used for popup menus
|
||||
class ItemAction : public QAction {
|
||||
public:
|
||||
ItemAction(const QModelIndex& index, QString text, QObject* parent = 0):
|
||||
QAction(text, parent),
|
||||
index_(index) {
|
||||
}
|
||||
|
||||
QPersistentModelIndex& index() {
|
||||
return index_;
|
||||
}
|
||||
private:
|
||||
QPersistentModelIndex index_;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit PlacesModel(QObject* parent = 0);
|
||||
virtual ~PlacesModel();
|
||||
|
||||
bool showTrash() {
|
||||
return trashItem_ != NULL;
|
||||
}
|
||||
void setShowTrash(bool show);
|
||||
|
||||
bool showApplications() {
|
||||
return showApplications_;
|
||||
}
|
||||
void setShowApplications(bool show);
|
||||
|
||||
bool showDesktop() {
|
||||
return showDesktop_;
|
||||
}
|
||||
void setShowDesktop(bool show);
|
||||
|
||||
public Q_SLOTS:
|
||||
void updateIcons();
|
||||
void updateTrash();
|
||||
|
||||
protected:
|
||||
|
||||
PlacesModelItem* itemFromPath(FmPath* path);
|
||||
PlacesModelItem* itemFromPath(QStandardItem* rootItem, FmPath* path);
|
||||
PlacesModelVolumeItem* itemFromVolume(GVolume* volume);
|
||||
PlacesModelMountItem* itemFromMount(GMount* mount);
|
||||
PlacesModelBookmarkItem* itemFromBookmark(FmBookmarkItem* bkitem);
|
||||
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const;
|
||||
virtual QStringList mimeTypes() const;
|
||||
virtual QMimeData* mimeData(const QModelIndexList& indexes) const;
|
||||
virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
|
||||
Qt::DropActions supportedDropActions() const;
|
||||
|
||||
void createTrashItem();
|
||||
|
||||
private:
|
||||
void loadBookmarks();
|
||||
|
||||
static void onVolumeAdded(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis);
|
||||
static void onVolumeRemoved(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis);
|
||||
static void onVolumeChanged(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis);
|
||||
static void onMountAdded(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis);
|
||||
static void onMountRemoved(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis);
|
||||
static void onMountChanged(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis);
|
||||
|
||||
static void onBookmarksChanged(FmBookmarks* bookmarks, PlacesModel* pThis);
|
||||
|
||||
static void onTrashChanged(GFileMonitor *monitor, GFile *gf, GFile *other, GFileMonitorEvent evt, PlacesModel* pThis);
|
||||
|
||||
private:
|
||||
FmBookmarks* bookmarks;
|
||||
GVolumeMonitor* volumeMonitor;
|
||||
QList<FmJob*> jobs;
|
||||
bool showApplications_;
|
||||
bool showDesktop_;
|
||||
QStandardItem* placesRoot;
|
||||
QStandardItem* devicesRoot;
|
||||
QStandardItem* bookmarksRoot;
|
||||
PlacesModelItem* trashItem_;
|
||||
GFileMonitor* trashMonitor_;
|
||||
PlacesModelItem* desktopItem;
|
||||
PlacesModelItem* homeItem;
|
||||
PlacesModelItem* computerItem;
|
||||
PlacesModelItem* networkItem;
|
||||
PlacesModelItem* applicationsItem;
|
||||
QIcon ejectIcon_;
|
||||
QList<GMount*> shadowedMounts_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_PLACESMODEL_H
|
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "placesmodelitem.h"
|
||||
#include "icontheme.h"
|
||||
#include <gio/gio.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
PlacesModelItem::PlacesModelItem():
|
||||
QStandardItem(),
|
||||
path_(NULL),
|
||||
icon_(NULL),
|
||||
fileInfo_(NULL) {
|
||||
}
|
||||
|
||||
PlacesModelItem::PlacesModelItem(const char* iconName, QString title, FmPath* path):
|
||||
QStandardItem(title),
|
||||
path_(path ? fm_path_ref(path) : NULL),
|
||||
icon_(fm_icon_from_name(iconName)),
|
||||
fileInfo_(NULL) {
|
||||
if(icon_)
|
||||
QStandardItem::setIcon(IconTheme::icon(icon_));
|
||||
setEditable(false);
|
||||
}
|
||||
|
||||
PlacesModelItem::PlacesModelItem(FmIcon* icon, QString title, FmPath* path):
|
||||
QStandardItem(title),
|
||||
path_(path ? fm_path_ref(path) : NULL),
|
||||
icon_(icon ? fm_icon_ref(icon) : NULL),
|
||||
fileInfo_(NULL) {
|
||||
if(icon_)
|
||||
QStandardItem::setIcon(IconTheme::icon(icon));
|
||||
setEditable(false);
|
||||
}
|
||||
|
||||
PlacesModelItem::PlacesModelItem(QIcon icon, QString title, FmPath* path):
|
||||
QStandardItem(icon, title),
|
||||
icon_(NULL),
|
||||
path_(path ? fm_path_ref(path) : NULL),
|
||||
fileInfo_(NULL) {
|
||||
setEditable(false);
|
||||
}
|
||||
|
||||
PlacesModelItem::~PlacesModelItem() {
|
||||
if(path_)
|
||||
fm_path_unref(path_);
|
||||
if(fileInfo_)
|
||||
g_object_unref(fileInfo_);
|
||||
if(icon_)
|
||||
fm_icon_unref(icon_);
|
||||
}
|
||||
|
||||
void PlacesModelItem::setPath(FmPath* path) {
|
||||
if(path_)
|
||||
fm_path_unref(path_);
|
||||
path_ = path ? fm_path_ref(path) : NULL;
|
||||
}
|
||||
|
||||
void PlacesModelItem::setIcon(FmIcon* icon) {
|
||||
if(icon_)
|
||||
fm_icon_unref(icon_);
|
||||
if(icon) {
|
||||
icon_ = fm_icon_ref(icon);
|
||||
QStandardItem::setIcon(IconTheme::icon(icon_));
|
||||
}
|
||||
else {
|
||||
icon_ = NULL;
|
||||
QStandardItem::setIcon(QIcon());
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesModelItem::setIcon(GIcon* gicon) {
|
||||
FmIcon* icon = gicon ? fm_icon_from_gicon(gicon) : NULL;
|
||||
setIcon(icon);
|
||||
fm_icon_unref(icon);
|
||||
}
|
||||
|
||||
void PlacesModelItem::updateIcon() {
|
||||
if(icon_)
|
||||
QStandardItem::setIcon(IconTheme::icon(icon_));
|
||||
}
|
||||
|
||||
QVariant PlacesModelItem::data(int role) const {
|
||||
// we use a QPixmap from FmIcon cache rather than QIcon object for decoration role.
|
||||
return QStandardItem::data(role);
|
||||
}
|
||||
|
||||
void PlacesModelItem::setFileInfo(FmFileInfo* fileInfo) {
|
||||
// FIXME: how can we correctly update icon?
|
||||
if(fileInfo_)
|
||||
fm_file_info_unref(fileInfo_);
|
||||
|
||||
if(fileInfo) {
|
||||
fileInfo_ = fm_file_info_ref(fileInfo);
|
||||
}
|
||||
else
|
||||
fileInfo_ = NULL;
|
||||
}
|
||||
|
||||
PlacesModelBookmarkItem::PlacesModelBookmarkItem(FmBookmarkItem* bm_item):
|
||||
PlacesModelItem(QIcon::fromTheme("folder"), QString::fromUtf8(bm_item->name), bm_item->path),
|
||||
bookmarkItem_(fm_bookmark_item_ref(bm_item)) {
|
||||
setEditable(true);
|
||||
}
|
||||
|
||||
PlacesModelVolumeItem::PlacesModelVolumeItem(GVolume* volume):
|
||||
PlacesModelItem(),
|
||||
volume_(reinterpret_cast<GVolume*>(g_object_ref(volume))) {
|
||||
update();
|
||||
setEditable(false);
|
||||
}
|
||||
|
||||
void PlacesModelVolumeItem::update() {
|
||||
// set title
|
||||
char* volumeName = g_volume_get_name(volume_);
|
||||
setText(QString::fromUtf8(volumeName));
|
||||
g_free(volumeName);
|
||||
|
||||
// set icon
|
||||
GIcon* gicon = g_volume_get_icon(volume_);
|
||||
setIcon(gicon);
|
||||
g_object_unref(gicon);
|
||||
|
||||
// set dir path
|
||||
GMount* mount = g_volume_get_mount(volume_);
|
||||
if(mount) {
|
||||
GFile* mount_root = g_mount_get_root(mount);
|
||||
FmPath* mount_path = fm_path_new_for_gfile(mount_root);
|
||||
setPath(mount_path);
|
||||
fm_path_unref(mount_path);
|
||||
g_object_unref(mount_root);
|
||||
g_object_unref(mount);
|
||||
}
|
||||
else {
|
||||
setPath(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool PlacesModelVolumeItem::isMounted() {
|
||||
GMount* mount = g_volume_get_mount(volume_);
|
||||
if(mount)
|
||||
g_object_unref(mount);
|
||||
return mount != NULL ? true : false;
|
||||
}
|
||||
|
||||
|
||||
PlacesModelMountItem::PlacesModelMountItem(GMount* mount):
|
||||
PlacesModelItem(),
|
||||
mount_(reinterpret_cast<GMount*>(mount)) {
|
||||
update();
|
||||
setEditable(false);
|
||||
}
|
||||
|
||||
void PlacesModelMountItem::update() {
|
||||
// set title
|
||||
setText(QString::fromUtf8(g_mount_get_name(mount_)));
|
||||
|
||||
// set path
|
||||
GFile* mount_root = g_mount_get_root(mount_);
|
||||
FmPath* mount_path = fm_path_new_for_gfile(mount_root);
|
||||
setPath(mount_path);
|
||||
fm_path_unref(mount_path);
|
||||
g_object_unref(mount_root);
|
||||
|
||||
// set icon
|
||||
GIcon* gicon = g_mount_get_icon(mount_);
|
||||
setIcon(gicon);
|
||||
g_object_unref(gicon);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FM_PLACESMODELITEM_H
|
||||
#define FM_PLACESMODELITEM_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QStandardItemModel>
|
||||
#include <QStandardItem>
|
||||
#include <QList>
|
||||
#include <QAction>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
// model item
|
||||
class LIBFM_QT_API PlacesModelItem : public QStandardItem {
|
||||
public:
|
||||
enum Type {
|
||||
Places = QStandardItem::UserType + 1,
|
||||
Volume,
|
||||
Mount,
|
||||
Bookmark
|
||||
};
|
||||
|
||||
public:
|
||||
PlacesModelItem();
|
||||
PlacesModelItem(QIcon icon, QString title, FmPath* path = NULL);
|
||||
PlacesModelItem(const char* iconName, QString title, FmPath* path = NULL);
|
||||
PlacesModelItem(FmIcon* icon, QString title, FmPath* path = NULL);
|
||||
~PlacesModelItem();
|
||||
|
||||
FmFileInfo* fileInfo() {
|
||||
return fileInfo_;
|
||||
}
|
||||
void setFileInfo(FmFileInfo* fileInfo);
|
||||
|
||||
FmPath* path() {
|
||||
return path_;
|
||||
}
|
||||
void setPath(FmPath* path);
|
||||
|
||||
FmIcon* icon() {
|
||||
return icon_;
|
||||
}
|
||||
void setIcon(FmIcon* icon);
|
||||
void setIcon(GIcon* gicon);
|
||||
void updateIcon();
|
||||
|
||||
QVariant data(int role = Qt::UserRole + 1) const;
|
||||
|
||||
virtual int type() const {
|
||||
return Places;
|
||||
}
|
||||
|
||||
private:
|
||||
FmPath* path_;
|
||||
FmFileInfo* fileInfo_;
|
||||
FmIcon* icon_;
|
||||
};
|
||||
|
||||
class LIBFM_QT_API PlacesModelVolumeItem : public PlacesModelItem {
|
||||
public:
|
||||
PlacesModelVolumeItem(GVolume* volume);
|
||||
bool isMounted();
|
||||
bool canEject() {
|
||||
return g_volume_can_eject(volume_);
|
||||
}
|
||||
virtual int type() const {
|
||||
return Volume;
|
||||
}
|
||||
GVolume* volume() {
|
||||
return volume_;
|
||||
}
|
||||
void update();
|
||||
private:
|
||||
GVolume* volume_;
|
||||
};
|
||||
|
||||
class LIBFM_QT_API PlacesModelMountItem : public PlacesModelItem {
|
||||
public:
|
||||
PlacesModelMountItem(GMount* mount);
|
||||
virtual int type() const {
|
||||
return Mount;
|
||||
}
|
||||
GMount* mount() const {
|
||||
return mount_;
|
||||
}
|
||||
void update();
|
||||
private:
|
||||
GMount* mount_;
|
||||
};
|
||||
|
||||
class LIBFM_QT_API PlacesModelBookmarkItem : public PlacesModelItem {
|
||||
public:
|
||||
virtual int type() const {
|
||||
return Bookmark;
|
||||
}
|
||||
PlacesModelBookmarkItem(FmBookmarkItem* bm_item);
|
||||
virtual ~PlacesModelBookmarkItem() {
|
||||
if(bookmarkItem_)
|
||||
fm_bookmark_item_unref(bookmarkItem_);
|
||||
}
|
||||
FmBookmarkItem* bookmark() const {
|
||||
return bookmarkItem_;
|
||||
}
|
||||
private:
|
||||
FmBookmarkItem* bookmarkItem_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_PLACESMODELITEM_H
|
@ -0,0 +1,425 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "placesview.h"
|
||||
#include "placesmodel.h"
|
||||
#include "placesmodelitem.h"
|
||||
#include "mountoperation.h"
|
||||
#include "fileoperation.h"
|
||||
#include <QMenu>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QHeaderView>
|
||||
#include <QDebug>
|
||||
#include <QGuiApplication>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
PlacesView::PlacesView(QWidget* parent):
|
||||
QTreeView(parent),
|
||||
currentPath_(NULL) {
|
||||
setRootIsDecorated(false);
|
||||
setHeaderHidden(true);
|
||||
setIndentation(12);
|
||||
|
||||
connect(this, &QTreeView::clicked, this, &PlacesView::onClicked);
|
||||
connect(this, &QTreeView::pressed, this, &PlacesView::onPressed);
|
||||
|
||||
setIconSize(QSize(24, 24));
|
||||
|
||||
// FIXME: we may share this model amont all views
|
||||
model_ = new PlacesModel(this);
|
||||
setModel(model_);
|
||||
|
||||
QHeaderView* headerView = header();
|
||||
headerView->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
headerView->setSectionResizeMode(1, QHeaderView::Fixed);
|
||||
headerView->setStretchLastSection(false);
|
||||
expandAll();
|
||||
|
||||
// FIXME: is there any better way to make the first column span the whole row?
|
||||
setFirstColumnSpanned(0, QModelIndex(), true); // places root
|
||||
setFirstColumnSpanned(1, QModelIndex(), true); // devices root
|
||||
setFirstColumnSpanned(2, QModelIndex(), true); // bookmarks root
|
||||
|
||||
// the 2nd column is for the eject buttons
|
||||
setSelectionBehavior(QAbstractItemView::SelectRows); // FIXME: why this does not work?
|
||||
setAllColumnsShowFocus(false);
|
||||
|
||||
setAcceptDrops(true);
|
||||
setDragEnabled(true);
|
||||
|
||||
// update the umount button's column width based on icon size
|
||||
onIconSizeChanged(iconSize());
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) // this signal requires Qt >= 5.5
|
||||
connect(this, &QAbstractItemView::iconSizeChanged, this, &PlacesView::onIconSizeChanged);
|
||||
#endif
|
||||
}
|
||||
|
||||
PlacesView::~PlacesView() {
|
||||
if(currentPath_)
|
||||
fm_path_unref(currentPath_);
|
||||
// qDebug("delete PlacesView");
|
||||
}
|
||||
|
||||
void PlacesView::activateRow(int type, const QModelIndex& index) {
|
||||
if(!index.parent().isValid()) // ignore root items
|
||||
return;
|
||||
PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(index));
|
||||
if(item) {
|
||||
FmPath* path = item->path();
|
||||
if(!path) {
|
||||
// check if mounting volumes is needed
|
||||
if(item->type() == PlacesModelItem::Volume) {
|
||||
PlacesModelVolumeItem* volumeItem = static_cast<PlacesModelVolumeItem*>(item);
|
||||
if(!volumeItem->isMounted()) {
|
||||
// Mount the volume
|
||||
GVolume* volume = volumeItem->volume();
|
||||
MountOperation* op = new MountOperation(true, this);
|
||||
op->mount(volume);
|
||||
// connect(op, SIGNAL(finished(GError*)), SLOT(onMountOperationFinished(GError*)));
|
||||
// blocking here until the mount operation is finished?
|
||||
|
||||
// FIXME: update status of the volume after mount is finished!!
|
||||
if(!op->wait())
|
||||
return;
|
||||
path = item->path();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(path) {
|
||||
Q_EMIT chdirRequested(type, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mouse button pressed
|
||||
void PlacesView::onPressed(const QModelIndex& index) {
|
||||
// if middle button is pressed
|
||||
if(QGuiApplication::mouseButtons() & Qt::MiddleButton) {
|
||||
// the real item is at column 0
|
||||
activateRow(1, 0 == index.column() ? index : index.sibling(index.row(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesView::onIconSizeChanged(const QSize& size) {
|
||||
setColumnWidth(1, size.width() + 5);
|
||||
}
|
||||
|
||||
void PlacesView::onEjectButtonClicked(PlacesModelItem* item) {
|
||||
// The eject button is clicked for a device item (volume or mount)
|
||||
if(item->type() == PlacesModelItem::Volume) {
|
||||
PlacesModelVolumeItem* volumeItem = static_cast<PlacesModelVolumeItem*>(item);
|
||||
MountOperation* op = new MountOperation(true, this);
|
||||
if(volumeItem->canEject()) // do eject if applicable
|
||||
op->eject(volumeItem->volume());
|
||||
else // otherwise, do unmount instead
|
||||
op->unmount(volumeItem->volume());
|
||||
}
|
||||
else if(item->type() == PlacesModelItem::Mount) {
|
||||
PlacesModelMountItem* mountItem = static_cast<PlacesModelMountItem*>(item);
|
||||
MountOperation* op = new MountOperation(true, this);
|
||||
op->unmount(mountItem->mount());
|
||||
}
|
||||
qDebug("PlacesView::onEjectButtonClicked");
|
||||
}
|
||||
|
||||
void PlacesView::onClicked(const QModelIndex& index) {
|
||||
if(!index.parent().isValid()) // ignore root items
|
||||
return;
|
||||
|
||||
if(index.column() == 0) {
|
||||
activateRow(0, index);
|
||||
}
|
||||
else if(index.column() == 1) { // column 1 contains eject buttons of the mounted devices
|
||||
if(index.parent() == model_->devicesRoot->index()) { // this is a mounted device
|
||||
// the eject button is clicked
|
||||
QModelIndex itemIndex = index.sibling(index.row(), 0); // the real item is at column 0
|
||||
PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(itemIndex));
|
||||
if(item) {
|
||||
// eject the volume or the mount
|
||||
onEjectButtonClicked(item);
|
||||
}
|
||||
}
|
||||
else
|
||||
activateRow(0, index.sibling(index.row(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesView::setCurrentPath(FmPath* path) {
|
||||
if(currentPath_)
|
||||
fm_path_unref(currentPath_);
|
||||
if(path) {
|
||||
currentPath_ = fm_path_ref(path);
|
||||
// TODO: search for item with the path in model_ and select it.
|
||||
PlacesModelItem* item = model_->itemFromPath(currentPath_);
|
||||
if(item) {
|
||||
selectionModel()->select(item->index(), QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows);
|
||||
}
|
||||
else
|
||||
clearSelection();
|
||||
}
|
||||
else {
|
||||
currentPath_ = NULL;
|
||||
clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PlacesView::dragMoveEvent(QDragMoveEvent* event) {
|
||||
QTreeView::dragMoveEvent(event);
|
||||
/*
|
||||
QModelIndex index = indexAt(event->pos());
|
||||
if(event->isAccepted() && index.isValid() && index.parent() == model_->bookmarksRoot->index()) {
|
||||
if(dropIndicatorPosition() != OnItem) {
|
||||
event->setDropAction(Qt::LinkAction);
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void PlacesView::dropEvent(QDropEvent* event) {
|
||||
QTreeView::dropEvent(event);
|
||||
}
|
||||
|
||||
void PlacesView::onEmptyTrash() {
|
||||
FmPathList* files = fm_path_list_new();
|
||||
fm_path_list_push_tail(files, fm_path_get_trash());
|
||||
Fm::FileOperation::deleteFiles(files);
|
||||
fm_path_list_unref(files);
|
||||
}
|
||||
|
||||
void PlacesView::onMoveBookmarkUp()
|
||||
{
|
||||
PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
|
||||
if(!action->index().isValid())
|
||||
return;
|
||||
PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(action->index()));
|
||||
|
||||
int row = item->row();
|
||||
if(row > 0) {
|
||||
FmBookmarkItem* bookmarkItem = item->bookmark();
|
||||
FmBookmarks* bookmarks = fm_bookmarks_dup();
|
||||
fm_bookmarks_reorder(bookmarks, bookmarkItem, row - 1);
|
||||
g_object_unref(bookmarks);
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesView::onMoveBookmarkDown()
|
||||
{
|
||||
PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
|
||||
if(!action->index().isValid())
|
||||
return;
|
||||
PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(action->index()));
|
||||
|
||||
int row = item->row();
|
||||
if(row < model_->rowCount()) {
|
||||
FmBookmarkItem* bookmarkItem = item->bookmark();
|
||||
FmBookmarks* bookmarks = fm_bookmarks_dup();
|
||||
fm_bookmarks_reorder(bookmarks, bookmarkItem, row + 1);
|
||||
g_object_unref(bookmarks);
|
||||
}
|
||||
}
|
||||
|
||||
void PlacesView::onDeleteBookmark() {
|
||||
PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
|
||||
if(!action->index().isValid())
|
||||
return;
|
||||
PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(action->index()));
|
||||
FmBookmarkItem* bookmarkItem = item->bookmark();
|
||||
FmBookmarks* bookmarks = fm_bookmarks_dup();
|
||||
fm_bookmarks_remove(bookmarks, bookmarkItem);
|
||||
g_object_unref(bookmarks);
|
||||
}
|
||||
|
||||
// virtual
|
||||
void PlacesView::commitData(QWidget * editor) {
|
||||
QTreeView::commitData(editor);
|
||||
PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(currentIndex()));
|
||||
FmBookmarkItem* bookmarkItem = item->bookmark();
|
||||
FmBookmarks* bookmarks = fm_bookmarks_dup();
|
||||
// rename bookmark
|
||||
fm_bookmarks_rename(bookmarks, bookmarkItem, item->text().toUtf8().constData());
|
||||
g_object_unref(bookmarks);
|
||||
}
|
||||
|
||||
void PlacesView::onOpenNewTab()
|
||||
{
|
||||
PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
|
||||
if(!action->index().isValid())
|
||||
return;
|
||||
PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(action->index()));
|
||||
if(item)
|
||||
Q_EMIT chdirRequested(1, item->path());
|
||||
}
|
||||
|
||||
void PlacesView::onOpenNewWindow()
|
||||
{
|
||||
PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
|
||||
if(!action->index().isValid())
|
||||
return;
|
||||
PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(action->index()));
|
||||
if(item)
|
||||
Q_EMIT chdirRequested(2, item->path());
|
||||
}
|
||||
|
||||
void PlacesView::onRenameBookmark() {
|
||||
PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
|
||||
if(!action->index().isValid())
|
||||
return;
|
||||
PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(action->index()));
|
||||
setFocus();
|
||||
setCurrentIndex(item->index());
|
||||
edit(item->index());
|
||||
}
|
||||
|
||||
void PlacesView::onMountVolume() {
|
||||
PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
|
||||
if(!action->index().isValid())
|
||||
return;
|
||||
PlacesModelVolumeItem* item = static_cast<PlacesModelVolumeItem*>(model_->itemFromIndex(action->index()));
|
||||
MountOperation* op = new MountOperation(true, this);
|
||||
op->mount(item->volume());
|
||||
op->wait();
|
||||
}
|
||||
|
||||
void PlacesView::onUnmountVolume() {
|
||||
PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
|
||||
if(!action->index().isValid())
|
||||
return;
|
||||
PlacesModelVolumeItem* item = static_cast<PlacesModelVolumeItem*>(model_->itemFromIndex(action->index()));
|
||||
MountOperation* op = new MountOperation(true, this);
|
||||
op->unmount(item->volume());
|
||||
op->wait();
|
||||
}
|
||||
|
||||
void PlacesView::onUnmountMount() {
|
||||
PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
|
||||
if(!action->index().isValid())
|
||||
return;
|
||||
PlacesModelMountItem* item = static_cast<PlacesModelMountItem*>(model_->itemFromIndex(action->index()));
|
||||
GMount* mount = item->mount();
|
||||
MountOperation* op = new MountOperation(true, this);
|
||||
op->unmount(mount);
|
||||
op->wait();
|
||||
}
|
||||
|
||||
void PlacesView::onEjectVolume() {
|
||||
PlacesModel::ItemAction* action = static_cast<PlacesModel::ItemAction*>(sender());
|
||||
if(!action->index().isValid())
|
||||
return;
|
||||
PlacesModelVolumeItem* item = static_cast<PlacesModelVolumeItem*>(model_->itemFromIndex(action->index()));
|
||||
MountOperation* op = new MountOperation(true, this);
|
||||
op->eject(item->volume());
|
||||
op->wait();
|
||||
}
|
||||
|
||||
void PlacesView::contextMenuEvent(QContextMenuEvent* event) {
|
||||
QModelIndex index = indexAt(event->pos());
|
||||
if(index.isValid() && index.parent().isValid()) {
|
||||
if(index.column() != 0) // the real item is at column 0
|
||||
index = index.sibling(index.row(), 0);
|
||||
|
||||
// Do not take the ownership of the menu since
|
||||
// it will be deleted with deleteLater() upon hidden.
|
||||
// This is possibly related to #145 - https://github.com/lxde/pcmanfm-qt/issues/145
|
||||
QMenu* menu = new QMenu();
|
||||
QAction* action;
|
||||
PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(index));
|
||||
|
||||
if(item->type() != PlacesModelItem::Mount
|
||||
&& (item->type() != PlacesModelItem::Volume
|
||||
|| static_cast<PlacesModelVolumeItem*>(item)->isMounted())) {
|
||||
action = new PlacesModel::ItemAction(item->index(), tr("Open in New Tab"), menu);
|
||||
connect(action, &QAction::triggered, this, &PlacesView::onOpenNewTab);
|
||||
menu->addAction(action);
|
||||
action = new PlacesModel::ItemAction(item->index(), tr("Open in New Window"), menu);
|
||||
connect(action, &QAction::triggered, this, &PlacesView::onOpenNewWindow);
|
||||
menu->addAction(action);
|
||||
}
|
||||
|
||||
switch(item->type()) {
|
||||
case PlacesModelItem::Places: {
|
||||
FmPath* path = item->path();
|
||||
if(path && fm_path_equal(fm_path_get_trash(), path)) {
|
||||
action = new PlacesModel::ItemAction(item->index(), tr("Empty Trash"), menu);
|
||||
connect(action, &QAction::triggered, this, &PlacesView::onEmptyTrash);
|
||||
menu->addAction(action);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PlacesModelItem::Bookmark: {
|
||||
// create context menu for bookmark item
|
||||
if(item->index().row() > 0) {
|
||||
action = new PlacesModel::ItemAction(item->index(), tr("Move Bookmark Up"), menu);
|
||||
connect(action, &QAction::triggered, this, &PlacesView::onMoveBookmarkUp);
|
||||
menu->addAction(action);
|
||||
}
|
||||
if(item->index().row() < model_->rowCount()) {
|
||||
action = new PlacesModel::ItemAction(item->index(), tr("Move Bookmark Down"), menu);
|
||||
connect(action, &QAction::triggered, this, &PlacesView::onMoveBookmarkDown);
|
||||
menu->addAction(action);
|
||||
}
|
||||
action = new PlacesModel::ItemAction(item->index(), tr("Rename Bookmark"), menu);
|
||||
connect(action, &QAction::triggered, this, &PlacesView::onRenameBookmark);
|
||||
menu->addAction(action);
|
||||
action = new PlacesModel::ItemAction(item->index(), tr("Remove Bookmark"), menu);
|
||||
connect(action, &QAction::triggered, this, &PlacesView::onDeleteBookmark);
|
||||
menu->addAction(action);
|
||||
break;
|
||||
}
|
||||
case PlacesModelItem::Volume: {
|
||||
PlacesModelVolumeItem* volumeItem = static_cast<PlacesModelVolumeItem*>(item);
|
||||
|
||||
if(volumeItem->isMounted()) {
|
||||
action = new PlacesModel::ItemAction(item->index(), tr("Unmount"), menu);
|
||||
connect(action, &QAction::triggered, this, &PlacesView::onUnmountVolume);
|
||||
}
|
||||
else {
|
||||
action = new PlacesModel::ItemAction(item->index(), tr("Mount"), menu);
|
||||
connect(action, &QAction::triggered, this, &PlacesView::onMountVolume);
|
||||
}
|
||||
menu->addAction(action);
|
||||
|
||||
if(volumeItem->canEject()) {
|
||||
action = new PlacesModel::ItemAction(item->index(), tr("Eject"), menu);
|
||||
connect(action, &QAction::triggered, this, &PlacesView::onEjectVolume);
|
||||
menu->addAction(action);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PlacesModelItem::Mount: {
|
||||
action = new PlacesModel::ItemAction(item->index(), tr("Unmount"), menu);
|
||||
connect(action, &QAction::triggered, this, &PlacesView::onUnmountMount);
|
||||
menu->addAction(action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(menu->actions().size()) {
|
||||
menu->popup(mapToGlobal(event->pos()));
|
||||
connect(menu, &QMenu::aboutToHide, menu, &QMenu::deleteLater);
|
||||
} else {
|
||||
menu->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue