Import Upstream version 3.14.0.6
|
@ -0,0 +1,224 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
__3.0.18__
|
||||
----------
|
||||
|
||||
* Fallback to standard QApplication class on iOS and Android systems where
|
||||
the library is not supported.
|
||||
|
||||
__3.0.17__
|
||||
----------
|
||||
|
||||
* Fixed compilation warning/error caused by `geteuid()` on unix based systems.
|
||||
|
||||
_Iakov Kirilenko_
|
||||
|
||||
* Added CMake support
|
||||
|
||||
_Hennadii Chernyshchyk_
|
||||
|
||||
__3.0.16__
|
||||
----------
|
||||
|
||||
* Use geteuid and getpwuid to get username on Unix, fallback to environment variable.
|
||||
|
||||
_Jonas Kvinge_
|
||||
|
||||
__3.0.15__
|
||||
----------
|
||||
|
||||
* Bug Fix: sendMessage() might return false even though data was actually written.
|
||||
|
||||
_Jonas Kvinge_
|
||||
|
||||
__3.0.14__
|
||||
----------
|
||||
|
||||
* Fixed uninitialised variables in the `SingleApplicationPrivate` constructor.
|
||||
|
||||
__3.0.13a__
|
||||
----------
|
||||
|
||||
* Process socket events asynchronously
|
||||
* Fix undefined variable error on Windows
|
||||
|
||||
_Francis Giraldeau_
|
||||
|
||||
__3.0.12a__
|
||||
----------
|
||||
|
||||
* Removed signal handling.
|
||||
|
||||
__3.0.11a__
|
||||
----------
|
||||
|
||||
* Fixed bug where the message sent by the second process was not received
|
||||
correctly when the message is sent immediately following a connection.
|
||||
|
||||
_Francis Giraldeau_
|
||||
|
||||
* Refactored code and implemented shared memory block consistency checks
|
||||
via `qChecksum()` (CRC-16).
|
||||
* Explicit `qWarning` and `qCritical` when the library is unable to initialise
|
||||
correctly.
|
||||
|
||||
__3.0.10__
|
||||
----------
|
||||
|
||||
* Removed C style casts and eliminated all clang warnings. Fixed `instanceId`
|
||||
reading from only one byte in the message deserialization. Cleaned up
|
||||
serialization code using `QDataStream`. Changed connection type to use
|
||||
`quint8 enum` rather than `char`.
|
||||
* Renamed `SingleAppConnectionType` to `ConnectionType`. Added initialization
|
||||
values to all `ConnectionType` enum cases.
|
||||
|
||||
_Jedidiah Buck McCready_
|
||||
|
||||
__3.0.9__
|
||||
---------
|
||||
|
||||
* Added SingleApplicationPrivate::primaryPid() as a solution to allow
|
||||
bringing the primary window of an application to the foreground on
|
||||
Windows.
|
||||
|
||||
_Eelco van Dam from Peacs BV_
|
||||
|
||||
__3.0.8__
|
||||
---------
|
||||
|
||||
* Bug fix - changed QApplication::instance() to QCoreApplication::instance()
|
||||
|
||||
_Evgeniy Bazhenov_
|
||||
|
||||
__3.0.7a__
|
||||
----------
|
||||
|
||||
* Fixed compilation error with Mingw32 in MXE thanks to Vitaly Tonkacheyev.
|
||||
* Removed QMutex used for thread safe behaviour. The implementation now uses
|
||||
QCoreApplication::instance() to get an instance to SingleApplication for
|
||||
memory deallocation.
|
||||
|
||||
__3.0.6a__
|
||||
----------
|
||||
|
||||
* Reverted GetUserName API usage on Windows. Fixed bug with missing library.
|
||||
* Fixed bug in the Calculator example, preventing it's window to be raised
|
||||
on Windows.
|
||||
|
||||
Special thanks to Charles Gunawan.
|
||||
|
||||
__3.0.5a__
|
||||
----------
|
||||
|
||||
* Fixed a memory leak in the SingleApplicationPrivate destructor.
|
||||
|
||||
_Sergei Moiseev_
|
||||
|
||||
__3.0.4a__
|
||||
----------
|
||||
|
||||
* Fixed shadow and uninitialised variable warnings.
|
||||
|
||||
_Paul Walmsley_
|
||||
|
||||
__3.0.3a__
|
||||
----------
|
||||
|
||||
* Removed Microsoft Windows specific code for getting username due to
|
||||
multiple problems and compiler differences on Windows platforms. On
|
||||
Windows the shared memory block in User mode now includes the user's
|
||||
home path (which contains the user's username).
|
||||
|
||||
* Explicitly getting absolute path of the user's home directory as on Unix
|
||||
a relative path (`~`) may be returned.
|
||||
|
||||
__3.0.2a__
|
||||
----------
|
||||
|
||||
* Fixed bug on Windows when username containing wide characters causes the
|
||||
library to crash.
|
||||
|
||||
_Le Liu_
|
||||
|
||||
__3.0.1a__
|
||||
----------
|
||||
|
||||
* Allows the application path and version to be excluded from the server name
|
||||
hash. The following flags were added for this purpose:
|
||||
* `SingleApplication::Mode::ExcludeAppVersion`
|
||||
* `SingleApplication::Mode::ExcludeAppPath`
|
||||
* Allow a non elevated process to connect to a local server created by an
|
||||
elevated process run by the same user on Windows
|
||||
* Fixes a problem with upper case letters in paths on Windows
|
||||
|
||||
_Le Liu_
|
||||
|
||||
__v3.0a__
|
||||
---------
|
||||
|
||||
* Deprecated secondary instances count.
|
||||
* Added a sendMessage() method to send a message to the primary instance.
|
||||
* Added a receivedMessage() signal, emitted when a message is received from a
|
||||
secondary instance.
|
||||
* The SingleApplication constructor's third parameter is now a bool
|
||||
specifying if the current instance should be allowed to run as a secondary
|
||||
instance if there is already a primary instance.
|
||||
* The SingleApplication constructor accept a fourth parameter specifying if
|
||||
the SingleApplication block should be User-wide or System-wide.
|
||||
* SingleApplication no longer relies on `applicationName` and
|
||||
`organizationName` to be set. It instead concatenates all of the following
|
||||
data and computes a `SHA256` hash which is used as the key of the
|
||||
`QSharedMemory` block and the `QLocalServer`. Since at least
|
||||
`applicationFilePath` is always present there is no need to explicitly set
|
||||
any of the following prior to initialising `SingleApplication`.
|
||||
* `QCoreApplication::applicationName`
|
||||
* `QCoreApplication::applicationVersion`
|
||||
* `QCoreApplication::applicationFilePath`
|
||||
* `QCoreApplication::organizationName`
|
||||
* `QCoreApplication::organizationDomain`
|
||||
* User name or home directory path if in User mode
|
||||
* The primary instance is no longer notified when a secondary instance had
|
||||
been started by default. A `Mode` flag for this feature exists.
|
||||
* Added `instanceNumber()` which represents a unique identifier for each
|
||||
secondary instance started. When called from the primary instance will
|
||||
return `0`.
|
||||
|
||||
__v2.4__
|
||||
--------
|
||||
|
||||
* Stability improvements
|
||||
* Support for secondary instances.
|
||||
* The library now recovers safely after the primary process has crashed
|
||||
and the shared memory had not been deleted.
|
||||
|
||||
__v2.3__
|
||||
--------
|
||||
|
||||
* Improved pimpl design and inheritance safety.
|
||||
|
||||
_Vladislav Pyatnichenko_
|
||||
|
||||
__v2.2__
|
||||
--------
|
||||
|
||||
* The `QAPPLICATION_CLASS` macro can now be defined in the file including the
|
||||
Single Application header or with a `DEFINES+=` statement in the project file.
|
||||
|
||||
__v2.1__
|
||||
--------
|
||||
|
||||
* A race condition can no longer occur when starting two processes nearly
|
||||
simultaneously.
|
||||
|
||||
Fix issue [#3](https://github.com/itay-grudev/SingleApplication/issues/3)
|
||||
|
||||
__v2.0__
|
||||
--------
|
||||
|
||||
* SingleApplication is now being passed a reference to `argc` instead of a
|
||||
copy.
|
||||
|
||||
Fix issue [#1](https://github.com/itay-grudev/SingleApplication/issues/1)
|
||||
|
||||
* Improved documentation.
|
|
@ -0,0 +1,43 @@
|
|||
cmake_minimum_required(VERSION 3.1.0)
|
||||
|
||||
project(SingleApplication)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
# SingleApplication base class
|
||||
set(QAPPLICATION_CLASS QCoreApplication CACHE STRING "Inheritance class for SingleApplication")
|
||||
set_property(CACHE QAPPLICATION_CLASS PROPERTY STRINGS QApplication QGuiApplication QCoreApplication)
|
||||
|
||||
# Libary target
|
||||
add_library(${PROJECT_NAME} STATIC
|
||||
singleapplication.cpp
|
||||
singleapplication_p.cpp
|
||||
)
|
||||
|
||||
# Find dependencies
|
||||
find_package(Qt5Network)
|
||||
if(QAPPLICATION_CLASS STREQUAL QApplication)
|
||||
find_package(Qt5 COMPONENTS Widgets REQUIRED)
|
||||
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
|
||||
find_package(Qt5 COMPONENTS Gui REQUIRED)
|
||||
else()
|
||||
find_package(Qt5 COMPONENTS Core REQUIRED)
|
||||
endif()
|
||||
add_compile_definitions(QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
|
||||
|
||||
# Link dependencies
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Network)
|
||||
if(QAPPLICATION_CLASS STREQUAL QApplication)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets)
|
||||
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Gui)
|
||||
else()
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE advapi32)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
|
@ -0,0 +1,24 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Itay Grudev 2015 - 2016
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Note: Some of the examples include code not distributed under the terms of the
|
||||
MIT License.
|
|
@ -0,0 +1,277 @@
|
|||
SingleApplication
|
||||
=================
|
||||
|
||||
This is a replacement of the QtSingleApplication for `Qt5`.
|
||||
|
||||
Keeps the Primary Instance of your Application and kills each subsequent
|
||||
instances. It can (if enabled) spawn secondary (non-related to the primary)
|
||||
instances and can send data to the primary instance from secondary instances.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
The `SingleApplication` class inherits from whatever `Q[Core|Gui]Application`
|
||||
class you specify via the `QAPPLICATION_CLASS` macro (`QCoreApplication` is the
|
||||
default). Further usage is similar to the use of the `Q[Core|Gui]Application`
|
||||
classes.
|
||||
|
||||
The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
|
||||
instance of your Application is your Primary Instance. It would check if the
|
||||
shared memory block exists and if not it will start a `QLocalServer` and listen
|
||||
for connections. Each subsequent instance of your application would check if the
|
||||
shared memory block exists and if it does, it will connect to the QLocalServer
|
||||
to notify the primary instance that a new instance had been started, after which
|
||||
it would terminate with status code `0`. In the Primary Instance
|
||||
`SingleApplication` would emit the `instanceStarted()` signal upon detecting
|
||||
that a new instance had been started.
|
||||
|
||||
The library uses `stdlib` to terminate the program with the `exit()` function.
|
||||
|
||||
You can use the library as if you use any other `QCoreApplication` derived
|
||||
class:
|
||||
|
||||
```cpp
|
||||
#include <QApplication>
|
||||
#include <SingleApplication.h>
|
||||
|
||||
int main( int argc, char* argv[] )
|
||||
{
|
||||
SingleApplication app( argc, argv );
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
```
|
||||
|
||||
To include the library files I would recommend that you add it as a git
|
||||
submodule to your project and include it's contents with a `.pri` file. Here is
|
||||
how:
|
||||
|
||||
```bash
|
||||
git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication
|
||||
```
|
||||
|
||||
**Qmake:**
|
||||
|
||||
Then include the `singleapplication.pri` file in your `.pro` project file.
|
||||
|
||||
```qmake
|
||||
include(singleapplication/singleapplication.pri)
|
||||
DEFINES += QAPPLICATION_CLASS=QApplication
|
||||
```
|
||||
|
||||
**CMake:**
|
||||
|
||||
Then include the subdirectory in your `CMakeLists.txt` project file.
|
||||
|
||||
```cmake
|
||||
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
|
||||
add_subdirectory(src/third-party/singleapplication)
|
||||
```
|
||||
|
||||
Also don't forget to specify which `QCoreApplication` class your app is using if it
|
||||
is not `QCoreApplication` as in examples above.
|
||||
|
||||
The `Instance Started` signal
|
||||
------------------------
|
||||
|
||||
The SingleApplication class implements a `instanceStarted()` signal. You can
|
||||
bind to that signal to raise your application's window when a new instance had
|
||||
been started, for example.
|
||||
|
||||
```cpp
|
||||
// window is a QWindow instance
|
||||
QObject::connect(
|
||||
&app,
|
||||
&SingleApplication::instanceStarted,
|
||||
&window,
|
||||
&QWindow::raise
|
||||
);
|
||||
```
|
||||
|
||||
Using `SingleApplication::instance()` is a neat way to get the
|
||||
`SingleApplication` instance for binding to it's signals anywhere in your
|
||||
program.
|
||||
|
||||
__Note:__ On Windows the ability to bring the application windows to the
|
||||
foreground is restricted. See [Windows specific implementations](Windows.md)
|
||||
for a workaround and an example implementation.
|
||||
|
||||
|
||||
Secondary Instances
|
||||
-------------------
|
||||
|
||||
If you want to be able to launch additional Secondary Instances (not related to
|
||||
your Primary Instance) you have to enable that with the third parameter of the
|
||||
`SingleApplication` constructor. The default is `false` meaning no Secondary
|
||||
Instances. Here is an example of how you would start a Secondary Instance send
|
||||
a message with the command line arguments to the primary instance and then shut
|
||||
down.
|
||||
|
||||
```cpp
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
SingleApplication app( argc, argv, true );
|
||||
|
||||
if( app.isSecondary() ) {
|
||||
app.sendMessage( app.arguments().join(' ')).toUtf8() );
|
||||
app.exit( 0 );
|
||||
}
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
```
|
||||
|
||||
*__Note:__ A secondary instance won't cause the emission of the
|
||||
`instanceStarted()` signal by default. See `SingleApplication::Mode` for more
|
||||
details.*
|
||||
|
||||
You can check whether your instance is a primary or secondary with the following
|
||||
methods:
|
||||
|
||||
```cpp
|
||||
app.isPrimary();
|
||||
// or
|
||||
app.isSecondary();
|
||||
```
|
||||
|
||||
*__Note:__ If your Primary Instance is terminated a newly launched instance
|
||||
will replace the Primary one even if the Secondary flag has been set.*
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
### Members
|
||||
|
||||
```cpp
|
||||
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100 )
|
||||
```
|
||||
|
||||
Depending on whether `allowSecondary` is set, this constructor may terminate
|
||||
your app if there is already a primary instance running. Additional `Options`
|
||||
can be specified to set whether the SingleApplication block should work
|
||||
user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be
|
||||
used to notify the primary instance whenever a secondary instance had been
|
||||
started (disabled by default). `timeout` specifies the maximum time in
|
||||
milliseconds to wait for blocking operations.
|
||||
|
||||
*__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it
|
||||
recognizes.*
|
||||
|
||||
*__Note:__ `Mode::SecondaryNotification` only works if set on both the primary
|
||||
and the secondary instance.*
|
||||
|
||||
*__Note:__ Operating system can restrict the shared memory blocks to the same
|
||||
user, in which case the User/System modes will have no effect and the block will
|
||||
be user wide.*
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
bool SingleApplication::sendMessage( QByteArray message, int timeout = 100 )
|
||||
```
|
||||
|
||||
Sends `message` to the Primary Instance. Uses `timeout` as a the maximum timeout
|
||||
in milliseconds for blocking functions
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
bool SingleApplication::isPrimary()
|
||||
```
|
||||
|
||||
Returns if the instance is the primary instance.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
bool SingleApplication::isSecondary()
|
||||
```
|
||||
Returns if the instance is a secondary instance.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
quint32 SingleApplication::instanceId()
|
||||
```
|
||||
|
||||
Returns a unique identifier for the current instance.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
qint64 SingleApplication::primaryPid()
|
||||
```
|
||||
|
||||
Returns the process ID (PID) of the primary instance.
|
||||
|
||||
### Signals
|
||||
|
||||
```cpp
|
||||
void SingleApplication::instanceStarted()
|
||||
```
|
||||
|
||||
Triggered whenever a new instance had been started, except for secondary
|
||||
instances if the `Mode::SecondaryNotification` flag is not specified.
|
||||
|
||||
---
|
||||
|
||||
```cpp
|
||||
void SingleApplication::receivedMessage( quint32 instanceId, QByteArray message )
|
||||
```
|
||||
|
||||
Triggered whenever there is a message received from a secondary instance.
|
||||
|
||||
---
|
||||
|
||||
### Flags
|
||||
|
||||
```cpp
|
||||
enum SingleApplication::Mode
|
||||
```
|
||||
|
||||
* `Mode::User` - The SingleApplication block should apply user wide. This adds
|
||||
user specific data to the key used for the shared memory and server name.
|
||||
This is the default functionality.
|
||||
* `Mode::System` – The SingleApplication block applies system-wide.
|
||||
* `Mode::SecondaryNotification` – Whether to trigger `instanceStarted()` even
|
||||
whenever secondary instances are started.
|
||||
* `Mode::ExcludeAppPath` – Excludes the application path from the server name
|
||||
(and memory block) hash.
|
||||
* `Mode::ExcludeAppVersion` – Excludes the application version from the server
|
||||
name (and memory block) hash.
|
||||
|
||||
*__Note:__ `Mode::SecondaryNotification` only works if set on both the primary
|
||||
and the secondary instance.*
|
||||
|
||||
*__Note:__ Operating system can restrict the shared memory blocks to the same
|
||||
user, in which case the User/System modes will have no effect and the block will
|
||||
be user wide.*
|
||||
|
||||
---
|
||||
|
||||
Versioning
|
||||
----------
|
||||
|
||||
Each major version introduces either very significant changes or is not
|
||||
backwards compatible with the previous version. Minor versions only add
|
||||
additional features, bug fixes or performance improvements and are backwards
|
||||
compatible with the previous release. See [`CHANGELOG.md`](CHANGELOG.md) for
|
||||
more details.
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
The library is implemented with a QSharedMemory block which is thread safe and
|
||||
guarantees a race condition will not occur. It also uses a QLocalSocket to
|
||||
notify the main process that a new instance had been spawned and thus invoke the
|
||||
`instanceStarted()` signal and for messaging the primary instance.
|
||||
|
||||
Additionally the library can recover from being forcefully killed on *nix
|
||||
systems and will reset the memory block given that there are no other
|
||||
instances running.
|
||||
|
||||
License
|
||||
-------
|
||||
This library and it's supporting documentation are released under
|
||||
`The MIT License (MIT)` with the exception of the Qt calculator examples which
|
||||
is distributed under the BSD license.
|
|
@ -0,0 +1,46 @@
|
|||
Windows Specific Implementations
|
||||
================================
|
||||
|
||||
Setting the foreground window
|
||||
-----------------------------
|
||||
|
||||
In the `instanceStarted()` example in the `README` we demonstrated how an
|
||||
application can bring it's primary instance window whenever a second copy
|
||||
of the application is started.
|
||||
|
||||
On Windows the ability to bring the application windows to the foreground is
|
||||
restricted, see [`AllowSetForegroundWindow()`][AllowSetForegroundWindow] for more
|
||||
details.
|
||||
|
||||
The background process (the primary instance) can bring its windows to the
|
||||
foreground if it is allowed by the current foreground process (the secondary
|
||||
instance). To bypass this `SingleApplication` must be initialized with the
|
||||
`allowSecondary` parameter set to `true` and the `options` parameter must
|
||||
include `Mode::SecondaryNotification`, See `SingleApplication::Mode` for more
|
||||
details.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```cpp
|
||||
if( app.isSecondary() ) {
|
||||
// This API requires LIBS += User32.lib to be added to the project
|
||||
AllowSetForegroundWindow( DWORD( app.primaryPid() ) );
|
||||
}
|
||||
|
||||
if( app.isPrimary() ) {
|
||||
QObject::connect(
|
||||
&app,
|
||||
&SingleApplication::instanceStarted,
|
||||
this,
|
||||
&App::instanceStarted
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
void App::instanceStarted() {
|
||||
QApplication::setActiveWindow( [window/widget to set to the foreground] );
|
||||
}
|
||||
```
|
||||
|
||||
[AllowSetForegroundWindow]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms632668.aspx
|
|
@ -0,0 +1,181 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#include <QtCore/QTime>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QSharedMemory>
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor. Checks and fires up LocalServer or closes the program
|
||||
* if another instance already exists
|
||||
* @param argc
|
||||
* @param argv
|
||||
* @param {bool} allowSecondaryInstances
|
||||
*/
|
||||
SingleApplication::SingleApplication(int &argc, char *argv[], const char* appName, bool allowSecondary, Options options, int timeout )
|
||||
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
|
||||
{
|
||||
Q_D(SingleApplication);
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
// On Android and iOS since the library is not supported fallback to
|
||||
// standard QApplication behaviour by simply returning at this point.
|
||||
qWarning() << "SingleApplication is not supported on Android and iOS systems.";
|
||||
return;
|
||||
#endif
|
||||
|
||||
// Store the current mode of the program
|
||||
d->options = options;
|
||||
|
||||
// Generating an application ID used for identifying the shared memory
|
||||
// block and QLocalServer
|
||||
d->genBlockServerName(appName);
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
// By explicitly attaching it and then deleting it we make sure that the
|
||||
// memory is deleted even after the process has crashed on Unix.
|
||||
d->memory = new QSharedMemory( d->blockServerName );
|
||||
d->memory->attach();
|
||||
delete d->memory;
|
||||
#endif
|
||||
// Guarantee thread safe behaviour with a shared memory block.
|
||||
d->memory = new QSharedMemory( d->blockServerName );
|
||||
|
||||
// Create a shared memory block
|
||||
if( d->memory->create( sizeof( InstancesInfo ) ) ) {
|
||||
// Initialize the shared memory block
|
||||
d->memory->lock();
|
||||
d->initializeMemoryBlock();
|
||||
d->memory->unlock();
|
||||
} else {
|
||||
// Attempt to attach to the memory segment
|
||||
if( ! d->memory->attach() ) {
|
||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
||||
qCritical() << d->memory->errorString();
|
||||
delete d;
|
||||
::exit( EXIT_FAILURE );
|
||||
}
|
||||
}
|
||||
|
||||
InstancesInfo* inst = static_cast<InstancesInfo*>( d->memory->data() );
|
||||
QTime time;
|
||||
time.start();
|
||||
|
||||
// Make sure the shared memory block is initialised and in consistent state
|
||||
while( true ) {
|
||||
d->memory->lock();
|
||||
|
||||
if( d->blockChecksum() == inst->checksum ) break;
|
||||
|
||||
if( time.elapsed() > 5000 ) {
|
||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
||||
d->initializeMemoryBlock();
|
||||
}
|
||||
|
||||
d->memory->unlock();
|
||||
|
||||
// Random sleep here limits the probability of a collision between two racing apps
|
||||
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
|
||||
QThread::sleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ) );
|
||||
}
|
||||
|
||||
if( inst->primary == false) {
|
||||
d->startPrimary();
|
||||
d->memory->unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if another instance can be started
|
||||
if( allowSecondary ) {
|
||||
inst->secondary += 1;
|
||||
inst->checksum = d->blockChecksum();
|
||||
d->instanceNumber = inst->secondary;
|
||||
d->startSecondary();
|
||||
if( d->options & Mode::SecondaryNotification ) {
|
||||
d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
|
||||
}
|
||||
d->memory->unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
d->memory->unlock();
|
||||
|
||||
d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
|
||||
|
||||
delete d;
|
||||
|
||||
::exit( EXIT_SUCCESS );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
SingleApplication::~SingleApplication()
|
||||
{
|
||||
Q_D(SingleApplication);
|
||||
delete d;
|
||||
}
|
||||
|
||||
bool SingleApplication::isPrimary()
|
||||
{
|
||||
Q_D(SingleApplication);
|
||||
return d->server != nullptr;
|
||||
}
|
||||
|
||||
bool SingleApplication::isSecondary()
|
||||
{
|
||||
Q_D(SingleApplication);
|
||||
return d->server == nullptr;
|
||||
}
|
||||
|
||||
quint32 SingleApplication::instanceId()
|
||||
{
|
||||
Q_D(SingleApplication);
|
||||
return d->instanceNumber;
|
||||
}
|
||||
|
||||
qint64 SingleApplication::primaryPid()
|
||||
{
|
||||
Q_D(SingleApplication);
|
||||
return d->primaryPid();
|
||||
}
|
||||
|
||||
bool SingleApplication::sendMessage( QByteArray message, int timeout )
|
||||
{
|
||||
Q_D(SingleApplication);
|
||||
|
||||
// Nobody to connect to
|
||||
if( isPrimary() ) return false;
|
||||
|
||||
// Make sure the socket is connected
|
||||
d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect );
|
||||
|
||||
d->socket->write( message );
|
||||
bool dataWritten = d->socket->waitForBytesWritten( timeout );
|
||||
d->socket->flush();
|
||||
return dataWritten;
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#ifndef SINGLE_APPLICATION_H
|
||||
#define SINGLE_APPLICATION_H
|
||||
|
||||
#include <QtCore/QtGlobal>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
|
||||
#ifndef QAPPLICATION_CLASS
|
||||
#define QAPPLICATION_CLASS QCoreApplication
|
||||
#endif
|
||||
|
||||
#include QT_STRINGIFY(QAPPLICATION_CLASS)
|
||||
|
||||
class SingleApplicationPrivate;
|
||||
|
||||
/**
|
||||
* @brief The SingleApplication class handles multiple instances of the same
|
||||
* Application
|
||||
* @see QCoreApplication
|
||||
*/
|
||||
class SingleApplication : public QAPPLICATION_CLASS
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
typedef QAPPLICATION_CLASS app_t;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Mode of operation of SingleApplication.
|
||||
* Whether the block should be user-wide or system-wide and whether the
|
||||
* primary instance should be notified when a secondary instance had been
|
||||
* started.
|
||||
* @note Operating system can restrict the shared memory blocks to the same
|
||||
* user, in which case the User/System modes will have no effect and the
|
||||
* block will be user wide.
|
||||
* @enum
|
||||
*/
|
||||
enum Mode {
|
||||
User = 1 << 0,
|
||||
System = 1 << 1,
|
||||
SecondaryNotification = 1 << 2,
|
||||
ExcludeAppVersion = 1 << 3,
|
||||
ExcludeAppPath = 1 << 4
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Mode)
|
||||
|
||||
/**
|
||||
* @brief Intitializes a SingleApplication instance with argc command line
|
||||
* arguments in argv
|
||||
* @arg {int &} argc - Number of arguments in argv
|
||||
* @arg {const char *[]} argv - Supplied command line arguments
|
||||
* @arg {bool} allowSecondary - Whether to start the instance as secondary
|
||||
* if there is already a primary instance.
|
||||
* @arg {Mode} mode - Whether for the SingleApplication block to be applied
|
||||
* User wide or System wide.
|
||||
* @arg {int} timeout - Timeout to wait in milliseconds.
|
||||
* @note argc and argv may be changed as Qt removes arguments that it
|
||||
* recognizes
|
||||
* @note Mode::SecondaryNotification only works if set on both the primary
|
||||
* instance and the secondary instance.
|
||||
* @note The timeout is just a hint for the maximum time of blocking
|
||||
* operations. It does not guarantee that the SingleApplication
|
||||
* initialisation will be completed in given time, though is a good hint.
|
||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
||||
*/
|
||||
explicit SingleApplication(int &argc, char *argv[], const char *appName = "SingleApplication", bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 );
|
||||
~SingleApplication();
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is the primary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isPrimary();
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is a secondary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isSecondary();
|
||||
|
||||
/**
|
||||
* @brief Returns a unique identifier for the current instance
|
||||
* @returns {qint32}
|
||||
*/
|
||||
quint32 instanceId();
|
||||
|
||||
/**
|
||||
* @brief Returns the process ID (PID) of the primary instance
|
||||
* @returns {qint64}
|
||||
*/
|
||||
qint64 primaryPid();
|
||||
|
||||
/**
|
||||
* @brief Sends a message to the primary instance. Returns true on success.
|
||||
* @param {int} timeout - Timeout for connecting
|
||||
* @returns {bool}
|
||||
* @note sendMessage() will return false if invoked from the primary
|
||||
* instance.
|
||||
*/
|
||||
bool sendMessage( QByteArray message, int timeout = 100 );
|
||||
|
||||
Q_SIGNALS:
|
||||
void instanceStarted();
|
||||
void receivedMessage( quint32 instanceId, QByteArray message );
|
||||
|
||||
private:
|
||||
SingleApplicationPrivate *d_ptr;
|
||||
Q_DECLARE_PRIVATE(SingleApplication)
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
||||
|
||||
#endif // SINGLE_APPLICATION_H
|
|
@ -0,0 +1,19 @@
|
|||
QT += core network
|
||||
CONFIG += c++11
|
||||
|
||||
HEADERS += $$PWD/singleapplication.h \
|
||||
$$PWD/singleapplication_p.h
|
||||
SOURCES += $$PWD/singleapplication.cpp \
|
||||
$$PWD/singleapplication_p.cpp
|
||||
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
win32 {
|
||||
msvc:LIBS += Advapi32.lib
|
||||
gcc:LIBS += -ladvapi32
|
||||
}
|
||||
|
||||
DISTFILES += \
|
||||
$$PWD/README.md \
|
||||
$$PWD/CHANGELOG.md \
|
||||
$$PWD/Windows.md
|
|
@ -0,0 +1,407 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This file is not part of the SingleApplication API. It is used purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or may even be removed.
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstddef>
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <lmcons.h>
|
||||
#endif
|
||||
|
||||
SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
|
||||
: q_ptr( q_ptr )
|
||||
{
|
||||
server = nullptr;
|
||||
socket = nullptr;
|
||||
memory = nullptr;
|
||||
instanceNumber = -1;
|
||||
}
|
||||
|
||||
SingleApplicationPrivate::~SingleApplicationPrivate()
|
||||
{
|
||||
if( socket != nullptr ) {
|
||||
socket->close();
|
||||
delete socket;
|
||||
}
|
||||
|
||||
memory->lock();
|
||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
||||
if( server != nullptr ) {
|
||||
server->close();
|
||||
delete server;
|
||||
inst->primary = false;
|
||||
inst->primaryPid = -1;
|
||||
inst->checksum = blockChecksum();
|
||||
}
|
||||
memory->unlock();
|
||||
|
||||
delete memory;
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::genBlockServerName(const char* appName)
|
||||
{
|
||||
QCryptographicHash appData( QCryptographicHash::Sha256 );
|
||||
appData.addData( appName, 17 );
|
||||
appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
|
||||
appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
|
||||
appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
|
||||
|
||||
if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ) {
|
||||
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
|
||||
}
|
||||
|
||||
if( ! (options & SingleApplication::Mode::ExcludeAppPath) ) {
|
||||
#ifdef Q_OS_WIN
|
||||
appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
|
||||
#else
|
||||
appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
|
||||
#endif
|
||||
}
|
||||
|
||||
// User level block requires a user specific data in the hash
|
||||
if( options & SingleApplication::Mode::User ) {
|
||||
#ifdef Q_OS_WIN
|
||||
wchar_t username [ UNLEN + 1 ];
|
||||
// Specifies size of the buffer on input
|
||||
DWORD usernameLength = UNLEN + 1;
|
||||
if( GetUserNameW( username, &usernameLength ) ) {
|
||||
appData.addData( QString::fromWCharArray(username).toUtf8() );
|
||||
} else {
|
||||
appData.addData( qgetenv("USERNAME") );
|
||||
}
|
||||
#endif
|
||||
#ifdef Q_OS_UNIX
|
||||
QByteArray username;
|
||||
uid_t uid = geteuid();
|
||||
struct passwd *pw = getpwuid(uid);
|
||||
if( pw ) {
|
||||
username = pw->pw_name;
|
||||
}
|
||||
if( username.isEmpty() ) {
|
||||
username = qgetenv("USER");
|
||||
}
|
||||
appData.addData(username);
|
||||
#endif
|
||||
}
|
||||
|
||||
appData.addData(qgetenv("DISPLAY"));
|
||||
|
||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
|
||||
// server naming requirements.
|
||||
blockServerName = appData.result().toBase64().replace("/", "_");
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::initializeMemoryBlock()
|
||||
{
|
||||
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() );
|
||||
inst->primary = false;
|
||||
inst->secondary = 0;
|
||||
inst->primaryPid = -1;
|
||||
inst->checksum = blockChecksum();
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startPrimary()
|
||||
{
|
||||
Q_Q(SingleApplication);
|
||||
|
||||
// Successful creation means that no main process exists
|
||||
// So we start a QLocalServer to listen for connections
|
||||
QLocalServer::removeServer( blockServerName );
|
||||
server = new QLocalServer();
|
||||
|
||||
// Restrict access to the socket according to the
|
||||
// SingleApplication::Mode::User flag on User level or no restrictions
|
||||
if( options & SingleApplication::Mode::User ) {
|
||||
server->setSocketOptions( QLocalServer::UserAccessOption );
|
||||
} else {
|
||||
server->setSocketOptions( QLocalServer::WorldAccessOption );
|
||||
}
|
||||
|
||||
server->listen( blockServerName );
|
||||
QObject::connect(
|
||||
server,
|
||||
&QLocalServer::newConnection,
|
||||
this,
|
||||
&SingleApplicationPrivate::slotConnectionEstablished
|
||||
);
|
||||
|
||||
// Reset the number of connections
|
||||
InstancesInfo* inst = static_cast <InstancesInfo*>( memory->data() );
|
||||
|
||||
inst->primary = true;
|
||||
inst->primaryPid = q->applicationPid();
|
||||
inst->checksum = blockChecksum();
|
||||
|
||||
instanceNumber = 0;
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startSecondary()
|
||||
{
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
|
||||
{
|
||||
// Connect to the Local Server of the Primary Instance if not already
|
||||
// connected.
|
||||
if( socket == nullptr ) {
|
||||
socket = new QLocalSocket();
|
||||
}
|
||||
|
||||
// If already connected - we are done;
|
||||
if( socket->state() == QLocalSocket::ConnectedState )
|
||||
return;
|
||||
|
||||
// If not connect
|
||||
if( socket->state() == QLocalSocket::UnconnectedState ||
|
||||
socket->state() == QLocalSocket::ClosingState ) {
|
||||
socket->connectToServer( blockServerName );
|
||||
}
|
||||
|
||||
// Wait for being connected
|
||||
if( socket->state() == QLocalSocket::ConnectingState ) {
|
||||
socket->waitForConnected( msecs );
|
||||
}
|
||||
|
||||
// Initialisation message according to the SingleApplication protocol
|
||||
if( socket->state() == QLocalSocket::ConnectedState ) {
|
||||
// Notify the parent that a new instance had been started;
|
||||
QByteArray initMsg;
|
||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
||||
#endif
|
||||
|
||||
writeStream << blockServerName.toLatin1();
|
||||
writeStream << static_cast<quint8>(connectionType);
|
||||
writeStream << instanceNumber;
|
||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||
writeStream << checksum;
|
||||
|
||||
// The header indicates the message length that follows
|
||||
QByteArray header;
|
||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
||||
#endif
|
||||
headerStream << static_cast <quint64>( initMsg.length() );
|
||||
|
||||
socket->write( header );
|
||||
socket->write( initMsg );
|
||||
socket->flush();
|
||||
socket->waitForBytesWritten( msecs );
|
||||
}
|
||||
}
|
||||
|
||||
quint16 SingleApplicationPrivate::blockChecksum()
|
||||
{
|
||||
return qChecksum(
|
||||
static_cast <const char *>( memory->data() ),
|
||||
offsetof( InstancesInfo, checksum )
|
||||
);
|
||||
}
|
||||
|
||||
qint64 SingleApplicationPrivate::primaryPid()
|
||||
{
|
||||
qint64 pid;
|
||||
|
||||
memory->lock();
|
||||
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() );
|
||||
pid = inst->primaryPid;
|
||||
memory->unlock();
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executed when a connection has been made to the LocalServer
|
||||
*/
|
||||
void SingleApplicationPrivate::slotConnectionEstablished()
|
||||
{
|
||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
||||
[nextConnSocket, this]() {
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
|
||||
[nextConnSocket, this]() {
|
||||
connectionMap.remove(nextConnSocket);
|
||||
nextConnSocket->deleteLater();
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
||||
[nextConnSocket, this]() {
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
switch(info.stage) {
|
||||
case StageHeader:
|
||||
readInitMessageHeader(nextConnSocket);
|
||||
break;
|
||||
case StageBody:
|
||||
readInitMessageBody(nextConnSocket);
|
||||
break;
|
||||
case StageConnected:
|
||||
Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
|
||||
{
|
||||
if (!connectionMap.contains( sock )) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDataStream headerStream( sock );
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
headerStream.setVersion( QDataStream::Qt_5_6 );
|
||||
#endif
|
||||
|
||||
// Read the header to know the message length
|
||||
quint64 msgLen = 0;
|
||||
headerStream >> msgLen;
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
info.stage = StageBody;
|
||||
info.msgLen = msgLen;
|
||||
|
||||
if ( sock->bytesAvailable() >= (qint64) msgLen ) {
|
||||
readInitMessageBody( sock );
|
||||
}
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
|
||||
{
|
||||
Q_Q(SingleApplication);
|
||||
|
||||
if (!connectionMap.contains( sock )) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
if( sock->bytesAvailable() < ( qint64 )info.msgLen ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the message body
|
||||
QByteArray msgBytes = sock->read(info.msgLen);
|
||||
QDataStream readStream(msgBytes);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
readStream.setVersion( QDataStream::Qt_5_6 );
|
||||
#endif
|
||||
|
||||
// server name
|
||||
QByteArray latin1Name;
|
||||
readStream >> latin1Name;
|
||||
|
||||
// connection type
|
||||
ConnectionType connectionType = InvalidConnection;
|
||||
quint8 connTypeVal = InvalidConnection;
|
||||
readStream >> connTypeVal;
|
||||
connectionType = static_cast <ConnectionType>( connTypeVal );
|
||||
|
||||
// instance id
|
||||
quint32 instanceId = 0;
|
||||
readStream >> instanceId;
|
||||
|
||||
// checksum
|
||||
quint16 msgChecksum = 0;
|
||||
readStream >> msgChecksum;
|
||||
|
||||
const quint16 actualChecksum = qChecksum( msgBytes.constData(), static_cast<quint32>( msgBytes.length() - sizeof( quint16 ) ) );
|
||||
|
||||
bool isValid = readStream.status() == QDataStream::Ok &&
|
||||
QLatin1String(latin1Name) == blockServerName &&
|
||||
msgChecksum == actualChecksum;
|
||||
|
||||
if( !isValid ) {
|
||||
sock->close();
|
||||
return;
|
||||
}
|
||||
|
||||
info.instanceId = instanceId;
|
||||
info.stage = StageConnected;
|
||||
|
||||
if( connectionType == NewInstance ||
|
||||
( connectionType == SecondaryInstance &&
|
||||
options & SingleApplication::Mode::SecondaryNotification ) )
|
||||
{
|
||||
Q_EMIT q->instanceStarted();
|
||||
}
|
||||
|
||||
if (sock->bytesAvailable() > 0) {
|
||||
Q_EMIT this->slotDataAvailable( sock, instanceId );
|
||||
}
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
|
||||
{
|
||||
Q_Q(SingleApplication);
|
||||
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
|
||||
{
|
||||
if( closedSocket->bytesAvailable() > 0 )
|
||||
Q_EMIT slotDataAvailable( closedSocket, instanceId );
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2016
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This file is not part of the SingleApplication API. It is used purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or may even be removed.
|
||||
//
|
||||
|
||||
#ifndef SINGLEAPPLICATION_P_H
|
||||
#define SINGLEAPPLICATION_P_H
|
||||
|
||||
#include <QtCore/QSharedMemory>
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
#include "singleapplication.h"
|
||||
|
||||
struct InstancesInfo {
|
||||
bool primary;
|
||||
quint32 secondary;
|
||||
qint64 primaryPid;
|
||||
quint16 checksum;
|
||||
};
|
||||
|
||||
struct ConnectionInfo {
|
||||
explicit ConnectionInfo() :
|
||||
msgLen(0), instanceId(0), stage(0) {}
|
||||
qint64 msgLen;
|
||||
quint32 instanceId;
|
||||
quint8 stage;
|
||||
};
|
||||
|
||||
class SingleApplicationPrivate : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ConnectionType : quint8 {
|
||||
InvalidConnection = 0,
|
||||
NewInstance = 1,
|
||||
SecondaryInstance = 2,
|
||||
Reconnect = 3
|
||||
};
|
||||
enum ConnectionStage : quint8 {
|
||||
StageHeader = 0,
|
||||
StageBody = 1,
|
||||
StageConnected = 2,
|
||||
};
|
||||
Q_DECLARE_PUBLIC(SingleApplication)
|
||||
|
||||
SingleApplicationPrivate( SingleApplication *q_ptr );
|
||||
~SingleApplicationPrivate();
|
||||
|
||||
void genBlockServerName(const char *appName);
|
||||
void initializeMemoryBlock();
|
||||
void startPrimary();
|
||||
void startSecondary();
|
||||
void connectToPrimary(int msecs, ConnectionType connectionType );
|
||||
quint16 blockChecksum();
|
||||
qint64 primaryPid();
|
||||
void readInitMessageHeader(QLocalSocket *socket);
|
||||
void readInitMessageBody(QLocalSocket *socket);
|
||||
|
||||
SingleApplication *q_ptr;
|
||||
QSharedMemory *memory;
|
||||
QLocalSocket *socket;
|
||||
QLocalServer *server;
|
||||
quint32 instanceNumber;
|
||||
QString blockServerName;
|
||||
SingleApplication::Options options;
|
||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
|
||||
|
||||
public Q_SLOTS:
|
||||
void slotConnectionEstablished();
|
||||
void slotDataAvailable( QLocalSocket*, quint32 );
|
||||
void slotClientConnectionClosed( QLocalSocket*, quint32 );
|
||||
};
|
||||
|
||||
#endif // SINGLEAPPLICATION_P_H
|
|
@ -0,0 +1 @@
|
|||
#include "qtlockedfile.h"
|
|
@ -0,0 +1 @@
|
|||
#include "qtsingleapplication.h"
|
|
@ -0,0 +1,216 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Solutions component.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "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 Digia Plc and its Subsidiary(-ies) 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
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include "qtlocalpeer.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QDataStream>
|
||||
#include <QTime>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <QLibrary>
|
||||
#include <qt_windows.h>
|
||||
typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*);
|
||||
static PProcessIdToSessionId pProcessIdToSessionId = 0;
|
||||
#endif
|
||||
#if defined(Q_OS_UNIX)
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace QtLP_Private {
|
||||
#include "qtlockedfile.cpp"
|
||||
#if defined(Q_OS_WIN)
|
||||
#include "qtlockedfile_win.cpp"
|
||||
#else
|
||||
#include "qtlockedfile_unix.cpp"
|
||||
#endif
|
||||
}
|
||||
|
||||
const char* QtLocalPeer::ack = "ack";
|
||||
|
||||
QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
|
||||
: QObject(parent), id(appId)
|
||||
{
|
||||
QString prefix = id;
|
||||
if (id.isEmpty()) {
|
||||
id = QCoreApplication::applicationFilePath();
|
||||
#if defined(Q_OS_WIN)
|
||||
id = id.toLower();
|
||||
#endif
|
||||
prefix = id.section(QLatin1Char('/'), -1);
|
||||
}
|
||||
prefix.remove(QRegExp("[^a-zA-Z]"));
|
||||
prefix.truncate(6);
|
||||
|
||||
QByteArray idc = id.toUtf8();
|
||||
quint16 idNum = qChecksum(idc.constData(), idc.size());
|
||||
socketName = QLatin1String("qtsingleapp-") + prefix
|
||||
+ QLatin1Char('-') + QString::number(idNum, 16);
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
if (!pProcessIdToSessionId) {
|
||||
QLibrary lib("kernel32");
|
||||
pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
|
||||
}
|
||||
if (pProcessIdToSessionId) {
|
||||
DWORD sessionId = 0;
|
||||
pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
|
||||
socketName += QLatin1Char('-') + QString::number(sessionId, 16);
|
||||
}
|
||||
#else
|
||||
socketName += QLatin1Char('-') + QString::number(::getuid(), 16);
|
||||
#endif
|
||||
|
||||
server = new QLocalServer(this);
|
||||
QString lockName = QDir(QDir::tempPath()).absolutePath()
|
||||
+ QLatin1Char('/') + socketName
|
||||
+ QLatin1String("-lockfile");
|
||||
lockFile.setFileName(lockName);
|
||||
lockFile.open(QIODevice::ReadWrite);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool QtLocalPeer::isClient()
|
||||
{
|
||||
if (lockFile.isLocked())
|
||||
return false;
|
||||
|
||||
if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
|
||||
return true;
|
||||
|
||||
bool res = server->listen(socketName);
|
||||
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0))
|
||||
// ### Workaround
|
||||
if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
|
||||
QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
|
||||
res = server->listen(socketName);
|
||||
}
|
||||
#endif
|
||||
if (!res)
|
||||
qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
|
||||
QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection()));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool QtLocalPeer::sendMessage(const QString &message, int timeout)
|
||||
{
|
||||
if (!isClient())
|
||||
return false;
|
||||
|
||||
QLocalSocket socket;
|
||||
bool connOk = false;
|
||||
for(int i = 0; i < 2; i++) {
|
||||
// Try twice, in case the other instance is just starting up
|
||||
socket.connectToServer(socketName);
|
||||
connOk = socket.waitForConnected(timeout/2);
|
||||
if (connOk || i)
|
||||
break;
|
||||
int ms = 250;
|
||||
#if defined(Q_OS_WIN)
|
||||
Sleep(DWORD(ms));
|
||||
#else
|
||||
struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
|
||||
nanosleep(&ts, NULL);
|
||||
#endif
|
||||
}
|
||||
if (!connOk)
|
||||
return false;
|
||||
|
||||
QByteArray uMsg(message.toUtf8());
|
||||
QDataStream ds(&socket);
|
||||
ds.writeBytes(uMsg.constData(), uMsg.size());
|
||||
bool res = socket.waitForBytesWritten(timeout);
|
||||
if (res) {
|
||||
res &= socket.waitForReadyRead(timeout); // wait for ack
|
||||
if (res)
|
||||
res &= (socket.read(qstrlen(ack)) == ack);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
void QtLocalPeer::receiveConnection()
|
||||
{
|
||||
QLocalSocket* socket = server->nextPendingConnection();
|
||||
if (!socket)
|
||||
return;
|
||||
|
||||
while (true) {
|
||||
if (socket->state() == QLocalSocket::UnconnectedState) {
|
||||
qWarning("QtLocalPeer: Peer disconnected");
|
||||
delete socket;
|
||||
socket = nullptr;
|
||||
return;
|
||||
}
|
||||
if (socket->bytesAvailable() >= qint64(sizeof(quint32)))
|
||||
break;
|
||||
socket->waitForReadyRead();
|
||||
}
|
||||
|
||||
QDataStream ds(socket);
|
||||
QByteArray uMsg;
|
||||
quint32 remaining;
|
||||
ds >> remaining;
|
||||
uMsg.resize(remaining);
|
||||
int got = 0;
|
||||
char* uMsgBuf = uMsg.data();
|
||||
do {
|
||||
got = ds.readRawData(uMsgBuf, remaining);
|
||||
remaining -= got;
|
||||
uMsgBuf += got;
|
||||
} while (remaining && got >= 0 && socket->waitForReadyRead(2000));
|
||||
if (got < 0) {
|
||||
qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData());
|
||||
delete socket;
|
||||
socket = nullptr;
|
||||
return;
|
||||
}
|
||||
QString message(QString::fromUtf8(uMsg));
|
||||
socket->write(ack, qstrlen(ack));
|
||||
socket->waitForBytesWritten(1000);
|
||||
socket->waitForDisconnected(1000); // make sure client reads ack
|
||||
delete socket;
|
||||
socket = nullptr;
|
||||
Q_EMIT messageReceived(message); //### (might take a long time to return)
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Solutions component.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "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 Digia Plc and its Subsidiary(-ies) 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
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QTLOCALPEER_H
|
||||
#define QTLOCALPEER_H
|
||||
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QDir>
|
||||
|
||||
#include "qtlockedfile.h"
|
||||
|
||||
class QtLocalPeer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QtLocalPeer(QObject *parent = 0, const QString &appId = QString());
|
||||
bool isClient();
|
||||
bool sendMessage(const QString &message, int timeout);
|
||||
QString applicationId() const
|
||||
{ return id; }
|
||||
|
||||
Q_SIGNALS:
|
||||
void messageReceived(const QString &message);
|
||||
|
||||
protected Q_SLOTS:
|
||||
void receiveConnection();
|
||||
|
||||
protected:
|
||||
QString id;
|
||||
QString socketName;
|
||||
QLocalServer* server;
|
||||
QtLP_Private::QtLockedFile lockFile;
|
||||
|
||||
private:
|
||||
static const char* ack;
|
||||
};
|
||||
|
||||
#endif // QTLOCALPEER_H
|
|
@ -0,0 +1,193 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Solutions component.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "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 Digia Plc and its Subsidiary(-ies) 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
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qtlockedfile.h"
|
||||
|
||||
/*!
|
||||
\class QtLockedFile
|
||||
|
||||
\brief The QtLockedFile class extends QFile with advisory locking
|
||||
functions.
|
||||
|
||||
A file may be locked in read or write mode. Multiple instances of
|
||||
\e QtLockedFile, created in multiple processes running on the same
|
||||
machine, may have a file locked in read mode. Exactly one instance
|
||||
may have it locked in write mode. A read and a write lock cannot
|
||||
exist simultaneously on the same file.
|
||||
|
||||
The file locks are advisory. This means that nothing prevents
|
||||
another process from manipulating a locked file using QFile or
|
||||
file system functions offered by the OS. Serialization is only
|
||||
guaranteed if all processes that access the file use
|
||||
QLockedFile. Also, while holding a lock on a file, a process
|
||||
must not open the same file again (through any API), or locks
|
||||
can be unexpectedly lost.
|
||||
|
||||
The lock provided by an instance of \e QtLockedFile is released
|
||||
whenever the program terminates. This is true even when the
|
||||
program crashes and no destructors are called.
|
||||
*/
|
||||
|
||||
/*! \enum QtLockedFile::LockMode
|
||||
|
||||
This enum describes the available lock modes.
|
||||
|
||||
\value ReadLock A read lock.
|
||||
\value WriteLock A write lock.
|
||||
\value NoLock Neither a read lock nor a write lock.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Constructs an unlocked \e QtLockedFile object. This constructor
|
||||
behaves in the same way as \e QFile::QFile().
|
||||
|
||||
\sa QFile::QFile()
|
||||
*/
|
||||
QtLockedFile::QtLockedFile()
|
||||
: QFile()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
wmutex = 0;
|
||||
rmutex = 0;
|
||||
#endif
|
||||
m_lock_mode = NoLock;
|
||||
}
|
||||
|
||||
/*!
|
||||
Constructs an unlocked QtLockedFile object with file \a name. This
|
||||
constructor behaves in the same way as \e QFile::QFile(const
|
||||
QString&).
|
||||
|
||||
\sa QFile::QFile()
|
||||
*/
|
||||
QtLockedFile::QtLockedFile(const QString &name)
|
||||
: QFile(name)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
wmutex = 0;
|
||||
rmutex = 0;
|
||||
#endif
|
||||
m_lock_mode = NoLock;
|
||||
}
|
||||
|
||||
/*!
|
||||
Opens the file in OpenMode \a mode.
|
||||
|
||||
This is identical to QFile::open(), with the one exception that the
|
||||
Truncate mode flag is disallowed. Truncation would conflict with the
|
||||
advisory file locking, since the file would be modified before the
|
||||
write lock is obtained. If truncation is required, use resize(0)
|
||||
after obtaining the write lock.
|
||||
|
||||
Returns true if successful; otherwise false.
|
||||
|
||||
\sa QFile::open(), QFile::resize()
|
||||
*/
|
||||
bool QtLockedFile::open(OpenMode mode)
|
||||
{
|
||||
if (mode & QIODevice::Truncate) {
|
||||
qWarning("QtLockedFile::open(): Truncate mode not allowed.");
|
||||
return false;
|
||||
}
|
||||
return QFile::open(mode);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \e true if this object has a in read or write lock;
|
||||
otherwise returns \e false.
|
||||
|
||||
\sa lockMode()
|
||||
*/
|
||||
bool QtLockedFile::isLocked() const
|
||||
{
|
||||
return m_lock_mode != NoLock;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the type of lock currently held by this object, or \e
|
||||
QtLockedFile::NoLock.
|
||||
|
||||
\sa isLocked()
|
||||
*/
|
||||
QtLockedFile::LockMode QtLockedFile::lockMode() const
|
||||
{
|
||||
return m_lock_mode;
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn bool QtLockedFile::lock(LockMode mode, bool block = true)
|
||||
|
||||
Obtains a lock of type \a mode. The file must be opened before it
|
||||
can be locked.
|
||||
|
||||
If \a block is true, this function will block until the lock is
|
||||
aquired. If \a block is false, this function returns \e false
|
||||
immediately if the lock cannot be aquired.
|
||||
|
||||
If this object already has a lock of type \a mode, this function
|
||||
returns \e true immediately. If this object has a lock of a
|
||||
different type than \a mode, the lock is first released and then a
|
||||
new lock is obtained.
|
||||
|
||||
This function returns \e true if, after it executes, the file is
|
||||
locked by this object, and \e false otherwise.
|
||||
|
||||
\sa unlock(), isLocked(), lockMode()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn bool QtLockedFile::unlock()
|
||||
|
||||
Releases a lock.
|
||||
|
||||
If the object has no lock, this function returns immediately.
|
||||
|
||||
This function returns \e true if, after it executes, the file is
|
||||
not locked by this object, and \e false otherwise.
|
||||
|
||||
\sa lock(), isLocked(), lockMode()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QtLockedFile::~QtLockedFile()
|
||||
|
||||
Destroys the \e QtLockedFile object. If any locks were held, they
|
||||
are released.
|
||||
*/
|
|
@ -0,0 +1,97 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Solutions component.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "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 Digia Plc and its Subsidiary(-ies) 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
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QTLOCKEDFILE_H
|
||||
#define QTLOCKEDFILE_H
|
||||
|
||||
#include <QFile>
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QVector>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT)
|
||||
# define QT_QTLOCKEDFILE_EXPORT
|
||||
# elif defined(QT_QTLOCKEDFILE_IMPORT)
|
||||
# if defined(QT_QTLOCKEDFILE_EXPORT)
|
||||
# undef QT_QTLOCKEDFILE_EXPORT
|
||||
# endif
|
||||
# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport)
|
||||
# elif defined(QT_QTLOCKEDFILE_EXPORT)
|
||||
# undef QT_QTLOCKEDFILE_EXPORT
|
||||
# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport)
|
||||
# endif
|
||||
#else
|
||||
# define QT_QTLOCKEDFILE_EXPORT
|
||||
#endif
|
||||
|
||||
namespace QtLP_Private {
|
||||
|
||||
class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile
|
||||
{
|
||||
public:
|
||||
enum LockMode { NoLock = 0, ReadLock, WriteLock };
|
||||
|
||||
QtLockedFile();
|
||||
QtLockedFile(const QString &name);
|
||||
~QtLockedFile();
|
||||
|
||||
bool open(OpenMode mode);
|
||||
|
||||
bool lock(LockMode mode, bool block = true);
|
||||
bool unlock();
|
||||
bool isLocked() const;
|
||||
LockMode lockMode() const;
|
||||
|
||||
private:
|
||||
#ifdef Q_OS_WIN
|
||||
Qt::HANDLE wmutex;
|
||||
Qt::HANDLE rmutex;
|
||||
QVector<Qt::HANDLE> rmutexes;
|
||||
QString mutexname;
|
||||
|
||||
Qt::HANDLE getMutexHandle(int idx, bool doCreate);
|
||||
bool waitMutex(Qt::HANDLE mutex, bool doBlock);
|
||||
|
||||
#endif
|
||||
LockMode m_lock_mode;
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,115 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Solutions component.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "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 Digia Plc and its Subsidiary(-ies) 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
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "qtlockedfile.h"
|
||||
|
||||
bool QtLockedFile::lock(LockMode mode, bool block)
|
||||
{
|
||||
if (!isOpen()) {
|
||||
qWarning("QtLockedFile::lock(): file is not opened");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode == NoLock)
|
||||
return unlock();
|
||||
|
||||
if (mode == m_lock_mode)
|
||||
return true;
|
||||
|
||||
if (m_lock_mode != NoLock)
|
||||
unlock();
|
||||
|
||||
struct flock fl;
|
||||
fl.l_whence = SEEK_SET;
|
||||
fl.l_start = 0;
|
||||
fl.l_len = 0;
|
||||
fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK;
|
||||
int cmd = block ? F_SETLKW : F_SETLK;
|
||||
int ret = fcntl(handle(), cmd, &fl);
|
||||
|
||||
if (ret == -1) {
|
||||
if (errno != EINTR && errno != EAGAIN)
|
||||
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
m_lock_mode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool QtLockedFile::unlock()
|
||||
{
|
||||
if (!isOpen()) {
|
||||
qWarning("QtLockedFile::unlock(): file is not opened");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isLocked())
|
||||
return true;
|
||||
|
||||
struct flock fl;
|
||||
fl.l_whence = SEEK_SET;
|
||||
fl.l_start = 0;
|
||||
fl.l_len = 0;
|
||||
fl.l_type = F_UNLCK;
|
||||
int ret = fcntl(handle(), F_SETLKW, &fl);
|
||||
|
||||
if (ret == -1) {
|
||||
qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_lock_mode = NoLock;
|
||||
return true;
|
||||
}
|
||||
|
||||
QtLockedFile::~QtLockedFile()
|
||||
{
|
||||
if (isOpen())
|
||||
unlock();
|
||||
}
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Solutions component.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "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 Digia Plc and its Subsidiary(-ies) 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
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qtlockedfile.h"
|
||||
#include <qt_windows.h>
|
||||
#include <QFileInfo>
|
||||
|
||||
#define MUTEX_PREFIX "QtLockedFile mutex "
|
||||
// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
|
||||
#define MAX_READERS MAXIMUM_WAIT_OBJECTS
|
||||
|
||||
#if QT_VERSION >= 0x050000
|
||||
#define QT_WA(unicode, ansi) unicode
|
||||
#endif
|
||||
|
||||
Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate)
|
||||
{
|
||||
if (mutexname.isEmpty()) {
|
||||
QFileInfo fi(*this);
|
||||
mutexname = QString::fromLatin1(MUTEX_PREFIX)
|
||||
+ fi.absoluteFilePath().toLower();
|
||||
}
|
||||
QString mname(mutexname);
|
||||
if (idx >= 0)
|
||||
mname += QString::number(idx);
|
||||
|
||||
Qt::HANDLE mutex;
|
||||
if (doCreate) {
|
||||
QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); },
|
||||
{ mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } );
|
||||
if (!mutex) {
|
||||
qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); },
|
||||
{ mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } );
|
||||
if (!mutex) {
|
||||
if (GetLastError() != ERROR_FILE_NOT_FOUND)
|
||||
qErrnoWarning("QtLockedFile::lock(): OpenMutex failed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return mutex;
|
||||
}
|
||||
|
||||
bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock)
|
||||
{
|
||||
Q_ASSERT(mutex);
|
||||
DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0);
|
||||
switch (res) {
|
||||
case WAIT_OBJECT_0:
|
||||
case WAIT_ABANDONED:
|
||||
return true;
|
||||
break;
|
||||
case WAIT_TIMEOUT:
|
||||
break;
|
||||
default:
|
||||
qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool QtLockedFile::lock(LockMode mode, bool block)
|
||||
{
|
||||
if (!isOpen()) {
|
||||
qWarning("QtLockedFile::lock(): file is not opened");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode == NoLock)
|
||||
return unlock();
|
||||
|
||||
if (mode == m_lock_mode)
|
||||
return true;
|
||||
|
||||
if (m_lock_mode != NoLock)
|
||||
unlock();
|
||||
|
||||
if (!wmutex && !(wmutex = getMutexHandle(-1, true)))
|
||||
return false;
|
||||
|
||||
if (!waitMutex(wmutex, block))
|
||||
return false;
|
||||
|
||||
if (mode == ReadLock) {
|
||||
int idx = 0;
|
||||
for (; idx < MAX_READERS; idx++) {
|
||||
rmutex = getMutexHandle(idx, false);
|
||||
if (!rmutex || waitMutex(rmutex, false))
|
||||
break;
|
||||
CloseHandle(rmutex);
|
||||
}
|
||||
bool ok = true;
|
||||
if (idx >= MAX_READERS) {
|
||||
qWarning("QtLockedFile::lock(): too many readers");
|
||||
rmutex = 0;
|
||||
ok = false;
|
||||
}
|
||||
else if (!rmutex) {
|
||||
rmutex = getMutexHandle(idx, true);
|
||||
if (!rmutex || !waitMutex(rmutex, false))
|
||||
ok = false;
|
||||
}
|
||||
if (!ok && rmutex) {
|
||||
CloseHandle(rmutex);
|
||||
rmutex = 0;
|
||||
}
|
||||
ReleaseMutex(wmutex);
|
||||
if (!ok)
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
Q_ASSERT(rmutexes.isEmpty());
|
||||
for (int i = 0; i < MAX_READERS; i++) {
|
||||
Qt::HANDLE mutex = getMutexHandle(i, false);
|
||||
if (mutex)
|
||||
rmutexes.append(mutex);
|
||||
}
|
||||
if (rmutexes.size()) {
|
||||
DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(),
|
||||
TRUE, block ? INFINITE : 0);
|
||||
if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) {
|
||||
if (res != WAIT_TIMEOUT)
|
||||
qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed");
|
||||
m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky
|
||||
unlock();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_lock_mode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QtLockedFile::unlock()
|
||||
{
|
||||
if (!isOpen()) {
|
||||
qWarning("QtLockedFile::unlock(): file is not opened");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isLocked())
|
||||
return true;
|
||||
|
||||
if (m_lock_mode == ReadLock) {
|
||||
ReleaseMutex(rmutex);
|
||||
CloseHandle(rmutex);
|
||||
rmutex = 0;
|
||||
}
|
||||
else {
|
||||
foreach(Qt::HANDLE mutex, rmutexes) {
|
||||
ReleaseMutex(mutex);
|
||||
CloseHandle(mutex);
|
||||
}
|
||||
rmutexes.clear();
|
||||
ReleaseMutex(wmutex);
|
||||
}
|
||||
|
||||
m_lock_mode = QtLockedFile::NoLock;
|
||||
return true;
|
||||
}
|
||||
|
||||
QtLockedFile::~QtLockedFile()
|
||||
{
|
||||
if (isOpen())
|
||||
unlock();
|
||||
if (wmutex)
|
||||
CloseHandle(wmutex);
|
||||
}
|
|
@ -0,0 +1,347 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Solutions component.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "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 Digia Plc and its Subsidiary(-ies) 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
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include "qtsingleapplication.h"
|
||||
#include "qtlocalpeer.h"
|
||||
#include <QWidget>
|
||||
|
||||
|
||||
/*!
|
||||
\class QtSingleApplication qtsingleapplication.h
|
||||
\brief The QtSingleApplication class provides an API to detect and
|
||||
communicate with running instances of an application.
|
||||
|
||||
This class allows you to create applications where only one
|
||||
instance should be running at a time. I.e., if the user tries to
|
||||
launch another instance, the already running instance will be
|
||||
activated instead. Another usecase is a client-server system,
|
||||
where the first started instance will assume the role of server,
|
||||
and the later instances will act as clients of that server.
|
||||
|
||||
By default, the full path of the executable file is used to
|
||||
determine whether two processes are instances of the same
|
||||
application. You can also provide an explicit identifier string
|
||||
that will be compared instead.
|
||||
|
||||
The application should create the QtSingleApplication object early
|
||||
in the startup phase, and call isRunning() to find out if another
|
||||
instance of this application is already running. If isRunning()
|
||||
returns false, it means that no other instance is running, and
|
||||
this instance has assumed the role as the running instance. In
|
||||
this case, the application should continue with the initialization
|
||||
of the application user interface before entering the event loop
|
||||
with exec(), as normal.
|
||||
|
||||
The messageReceived() signal will be emitted when the running
|
||||
application receives messages from another instance of the same
|
||||
application. When a message is received it might be helpful to the
|
||||
user to raise the application so that it becomes visible. To
|
||||
facilitate this, QtSingleApplication provides the
|
||||
setActivationWindow() function and the activateWindow() slot.
|
||||
|
||||
If isRunning() returns true, another instance is already
|
||||
running. It may be alerted to the fact that another instance has
|
||||
started by using the sendMessage() function. Also data such as
|
||||
startup parameters (e.g. the name of the file the user wanted this
|
||||
new instance to open) can be passed to the running instance with
|
||||
this function. Then, the application should terminate (or enter
|
||||
client mode).
|
||||
|
||||
If isRunning() returns true, but sendMessage() fails, that is an
|
||||
indication that the running instance is frozen.
|
||||
|
||||
Here's an example that shows how to convert an existing
|
||||
application to use QtSingleApplication. It is very simple and does
|
||||
not make use of all QtSingleApplication's functionality (see the
|
||||
examples for that).
|
||||
|
||||
\code
|
||||
// Original
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
MyMainWidget mmw;
|
||||
mmw.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
// Single instance
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QtSingleApplication app(argc, argv);
|
||||
|
||||
if (app.isRunning())
|
||||
return !app.sendMessage(someDataString);
|
||||
|
||||
MyMainWidget mmw;
|
||||
app.setActivationWindow(&mmw);
|
||||
mmw.show();
|
||||
return app.exec();
|
||||
}
|
||||
\endcode
|
||||
|
||||
Once this QtSingleApplication instance is destroyed (normally when
|
||||
the process exits or crashes), when the user next attempts to run the
|
||||
application this instance will not, of course, be encountered. The
|
||||
next instance to call isRunning() or sendMessage() will assume the
|
||||
role as the new running instance.
|
||||
|
||||
For console (non-GUI) applications, QtSingleCoreApplication may be
|
||||
used instead of this class, to avoid the dependency on the QtGui
|
||||
library.
|
||||
|
||||
\sa QtSingleCoreApplication
|
||||
*/
|
||||
|
||||
|
||||
void QtSingleApplication::sysInit(const QString &appId)
|
||||
{
|
||||
actWin = 0;
|
||||
peer = new QtLocalPeer(this, appId);
|
||||
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Creates a QtSingleApplication object. The application identifier
|
||||
will be QCoreApplication::applicationFilePath(). \a argc, \a
|
||||
argv, and \a GUIenabled are passed on to the QAppliation constructor.
|
||||
|
||||
If you are creating a console application (i.e. setting \a
|
||||
GUIenabled to false), you may consider using
|
||||
QtSingleCoreApplication instead.
|
||||
*/
|
||||
|
||||
QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled)
|
||||
: QApplication(argc, argv, GUIenabled)
|
||||
{
|
||||
sysInit();
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Creates a QtSingleApplication object with the application
|
||||
identifier \a appId. \a argc and \a argv are passed on to the
|
||||
QAppliation constructor.
|
||||
*/
|
||||
|
||||
QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
|
||||
: QApplication(argc, argv)
|
||||
{
|
||||
sysInit(appId);
|
||||
}
|
||||
|
||||
#if QT_VERSION < 0x050000
|
||||
|
||||
/*!
|
||||
Creates a QtSingleApplication object. The application identifier
|
||||
will be QCoreApplication::applicationFilePath(). \a argc, \a
|
||||
argv, and \a type are passed on to the QAppliation constructor.
|
||||
*/
|
||||
QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type)
|
||||
: QApplication(argc, argv, type)
|
||||
{
|
||||
sysInit();
|
||||
}
|
||||
|
||||
|
||||
# if defined(Q_WS_X11)
|
||||
/*!
|
||||
Special constructor for X11, ref. the documentation of
|
||||
QApplication's corresponding constructor. The application identifier
|
||||
will be QCoreApplication::applicationFilePath(). \a dpy, \a visual,
|
||||
and \a cmap are passed on to the QApplication constructor.
|
||||
*/
|
||||
QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap)
|
||||
: QApplication(dpy, visual, cmap)
|
||||
{
|
||||
sysInit();
|
||||
}
|
||||
|
||||
/*!
|
||||
Special constructor for X11, ref. the documentation of
|
||||
QApplication's corresponding constructor. The application identifier
|
||||
will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a
|
||||
argv, \a visual, and \a cmap are passed on to the QApplication
|
||||
constructor.
|
||||
*/
|
||||
QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
|
||||
: QApplication(dpy, argc, argv, visual, cmap)
|
||||
{
|
||||
sysInit();
|
||||
}
|
||||
|
||||
/*!
|
||||
Special constructor for X11, ref. the documentation of
|
||||
QApplication's corresponding constructor. The application identifier
|
||||
will be \a appId. \a dpy, \a argc, \a
|
||||
argv, \a visual, and \a cmap are passed on to the QApplication
|
||||
constructor.
|
||||
*/
|
||||
QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
|
||||
: QApplication(dpy, argc, argv, visual, cmap)
|
||||
{
|
||||
sysInit(appId);
|
||||
}
|
||||
# endif // Q_WS_X11
|
||||
#endif // QT_VERSION < 0x050000
|
||||
|
||||
|
||||
/*!
|
||||
Returns true if another instance of this application is running;
|
||||
otherwise false.
|
||||
|
||||
This function does not find instances of this application that are
|
||||
being run by a different user (on Windows: that are running in
|
||||
another session).
|
||||
|
||||
\sa sendMessage()
|
||||
*/
|
||||
|
||||
bool QtSingleApplication::isRunning()
|
||||
{
|
||||
return peer->isClient();
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Tries to send the text \a message to the currently running
|
||||
instance. The QtSingleApplication object in the running instance
|
||||
will emit the messageReceived() signal when it receives the
|
||||
message.
|
||||
|
||||
This function returns true if the message has been sent to, and
|
||||
processed by, the current instance. If there is no instance
|
||||
currently running, or if the running instance fails to process the
|
||||
message within \a timeout milliseconds, this function return false.
|
||||
|
||||
\sa isRunning(), messageReceived()
|
||||
*/
|
||||
bool QtSingleApplication::sendMessage(const QString &message, int timeout)
|
||||
{
|
||||
return peer->sendMessage(message, timeout);
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Returns the application identifier. Two processes with the same
|
||||
identifier will be regarded as instances of the same application.
|
||||
*/
|
||||
QString QtSingleApplication::id() const
|
||||
{
|
||||
return peer->applicationId();
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Sets the activation window of this application to \a aw. The
|
||||
activation window is the widget that will be activated by
|
||||
activateWindow(). This is typically the application's main window.
|
||||
|
||||
If \a activateOnMessage is true (the default), the window will be
|
||||
activated automatically every time a message is received, just prior
|
||||
to the messageReceived() signal being emitted.
|
||||
|
||||
\sa activateWindow(), messageReceived()
|
||||
*/
|
||||
|
||||
void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage)
|
||||
{
|
||||
actWin = aw;
|
||||
if (activateOnMessage)
|
||||
connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
|
||||
else
|
||||
disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Returns the applications activation window if one has been set by
|
||||
calling setActivationWindow(), otherwise returns 0.
|
||||
|
||||
\sa setActivationWindow()
|
||||
*/
|
||||
QWidget* QtSingleApplication::activationWindow() const
|
||||
{
|
||||
return actWin;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
De-minimizes, raises, and activates this application's activation window.
|
||||
This function does nothing if no activation window has been set.
|
||||
|
||||
This is a convenience function to show the user that this
|
||||
application instance has been activated when he has tried to start
|
||||
another instance.
|
||||
|
||||
This function should typically be called in response to the
|
||||
messageReceived() signal. By default, that will happen
|
||||
automatically, if an activation window has been set.
|
||||
|
||||
\sa setActivationWindow(), messageReceived(), initialize()
|
||||
*/
|
||||
void QtSingleApplication::activateWindow()
|
||||
{
|
||||
if (actWin) {
|
||||
//actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
|
||||
actWin->raise();
|
||||
actWin->activateWindow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\fn void QtSingleApplication::messageReceived(const QString& message)
|
||||
|
||||
This signal is emitted when the current instance receives a \a
|
||||
message from another instance of this application.
|
||||
|
||||
\sa sendMessage(), setActivationWindow(), activateWindow()
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\fn void QtSingleApplication::initialize(bool dummy = true)
|
||||
|
||||
\obsolete
|
||||
*/
|
|
@ -0,0 +1,105 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Solutions component.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "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 Digia Plc and its Subsidiary(-ies) 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
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QTSINGLEAPPLICATION_H
|
||||
#define QTSINGLEAPPLICATION_H
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
class QtLocalPeer;
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
# if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT)
|
||||
# define QT_QTSINGLEAPPLICATION_EXPORT
|
||||
# elif defined(QT_QTSINGLEAPPLICATION_IMPORT)
|
||||
# if defined(QT_QTSINGLEAPPLICATION_EXPORT)
|
||||
# undef QT_QTSINGLEAPPLICATION_EXPORT
|
||||
# endif
|
||||
# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport)
|
||||
# elif defined(QT_QTSINGLEAPPLICATION_EXPORT)
|
||||
# undef QT_QTSINGLEAPPLICATION_EXPORT
|
||||
# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport)
|
||||
# endif
|
||||
#else
|
||||
# define QT_QTSINGLEAPPLICATION_EXPORT
|
||||
#endif
|
||||
|
||||
class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QtSingleApplication(int &argc, char **argv, bool GUIenabled = true);
|
||||
QtSingleApplication(const QString &id, int &argc, char **argv);
|
||||
#if QT_VERSION < 0x050000
|
||||
QtSingleApplication(int &argc, char **argv, Type type);
|
||||
# if defined(Q_WS_X11)
|
||||
QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
|
||||
QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0);
|
||||
QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
|
||||
# endif // Q_WS_X11
|
||||
#endif // QT_VERSION < 0x050000
|
||||
|
||||
bool isRunning();
|
||||
QString id() const;
|
||||
|
||||
void setActivationWindow(QWidget* aw, bool activateOnMessage = true);
|
||||
QWidget* activationWindow() const;
|
||||
|
||||
// Obsolete:
|
||||
void initialize(bool dummy = true)
|
||||
{ isRunning(); Q_UNUSED(dummy) }
|
||||
|
||||
public Q_SLOTS:
|
||||
bool sendMessage(const QString &message, int timeout = 5000);
|
||||
void activateWindow();
|
||||
|
||||
|
||||
Q_SIGNALS:
|
||||
void messageReceived(const QString &message);
|
||||
|
||||
|
||||
private:
|
||||
void sysInit(const QString &appId = QString());
|
||||
QtLocalPeer *peer;
|
||||
QWidget *actWin;
|
||||
};
|
||||
|
||||
#endif // QTSINGLEAPPLICATION_H
|
|
@ -0,0 +1,16 @@
|
|||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
QT *= network
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT *= widgets
|
||||
|
||||
qtsingleapplication-uselib:!qtsingleapplication-buildlib {
|
||||
LIBS += -L$$QTSINGLEAPPLICATION_LIBDIR -l$$QTSINGLEAPPLICATION_LIBNAME
|
||||
} else {
|
||||
SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp
|
||||
HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h
|
||||
}
|
||||
|
||||
win32 {
|
||||
contains(TEMPLATE, lib):contains(CONFIG, shared):DEFINES += QT_QTSINGLEAPPLICATION_EXPORT
|
||||
else:qtsingleapplication-uselib:DEFINES += QT_QTSINGLEAPPLICATION_IMPORT
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Solutions component.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "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 Digia Plc and its Subsidiary(-ies) 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
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include "qtsinglecoreapplication.h"
|
||||
#include "qtlocalpeer.h"
|
||||
|
||||
/*!
|
||||
\class QtSingleCoreApplication qtsinglecoreapplication.h
|
||||
\brief A variant of the QtSingleApplication class for non-GUI applications.
|
||||
|
||||
This class is a variant of QtSingleApplication suited for use in
|
||||
console (non-GUI) applications. It is an extension of
|
||||
QCoreApplication (instead of QApplication). It does not require
|
||||
the QtGui library.
|
||||
|
||||
The API and usage is identical to QtSingleApplication, except that
|
||||
functions relating to the "activation window" are not present, for
|
||||
obvious reasons. Please refer to the QtSingleApplication
|
||||
documentation for explanation of the usage.
|
||||
|
||||
A QtSingleCoreApplication instance can communicate to a
|
||||
QtSingleApplication instance if they share the same application
|
||||
id. Hence, this class can be used to create a light-weight
|
||||
command-line tool that sends commands to a GUI application.
|
||||
|
||||
\sa QtSingleApplication
|
||||
*/
|
||||
|
||||
/*!
|
||||
Creates a QtSingleCoreApplication object. The application identifier
|
||||
will be QCoreApplication::applicationFilePath(). \a argc and \a
|
||||
argv are passed on to the QCoreAppliation constructor.
|
||||
*/
|
||||
|
||||
QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv)
|
||||
: QCoreApplication(argc, argv)
|
||||
{
|
||||
peer = new QtLocalPeer(this);
|
||||
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Creates a QtSingleCoreApplication object with the application
|
||||
identifier \a appId. \a argc and \a argv are passed on to the
|
||||
QCoreAppliation constructor.
|
||||
*/
|
||||
QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv)
|
||||
: QCoreApplication(argc, argv)
|
||||
{
|
||||
peer = new QtLocalPeer(this, appId);
|
||||
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Returns true if another instance of this application is running;
|
||||
otherwise false.
|
||||
|
||||
This function does not find instances of this application that are
|
||||
being run by a different user (on Windows: that are running in
|
||||
another session).
|
||||
|
||||
\sa sendMessage()
|
||||
*/
|
||||
|
||||
bool QtSingleCoreApplication::isRunning()
|
||||
{
|
||||
return peer->isClient();
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Tries to send the text \a message to the currently running
|
||||
instance. The QtSingleCoreApplication object in the running instance
|
||||
will emit the messageReceived() signal when it receives the
|
||||
message.
|
||||
|
||||
This function returns true if the message has been sent to, and
|
||||
processed by, the current instance. If there is no instance
|
||||
currently running, or if the running instance fails to process the
|
||||
message within \a timeout milliseconds, this function return false.
|
||||
|
||||
\sa isRunning(), messageReceived()
|
||||
*/
|
||||
|
||||
bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout)
|
||||
{
|
||||
return peer->sendMessage(message, timeout);
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Returns the application identifier. Two processes with the same
|
||||
identifier will be regarded as instances of the same application.
|
||||
*/
|
||||
|
||||
QString QtSingleCoreApplication::id() const
|
||||
{
|
||||
return peer->applicationId();
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\fn void QtSingleCoreApplication::messageReceived(const QString& message)
|
||||
|
||||
This signal is emitted when the current instance receives a \a
|
||||
message from another instance of this application.
|
||||
|
||||
\sa sendMessage()
|
||||
*/
|
|
@ -0,0 +1,71 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the Qt Solutions component.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** You may use this file under the terms of the BSD license as follows:
|
||||
**
|
||||
** "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 Digia Plc and its Subsidiary(-ies) 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
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QTSINGLECOREAPPLICATION_H
|
||||
#define QTSINGLECOREAPPLICATION_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
class QtLocalPeer;
|
||||
|
||||
class QtSingleCoreApplication : public QCoreApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QtSingleCoreApplication(int &argc, char **argv);
|
||||
QtSingleCoreApplication(const QString &id, int &argc, char **argv);
|
||||
|
||||
bool isRunning();
|
||||
QString id() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
bool sendMessage(const QString &message, int timeout = 5000);
|
||||
|
||||
|
||||
Q_SIGNALS:
|
||||
void messageReceived(const QString &message);
|
||||
|
||||
|
||||
private:
|
||||
QtLocalPeer* peer;
|
||||
};
|
||||
|
||||
#endif // QTSINGLECOREAPPLICATION_H
|
|
@ -0,0 +1,10 @@
|
|||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
HEADERS += $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h
|
||||
SOURCES += $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp
|
||||
|
||||
QT *= network
|
||||
|
||||
win32:contains(TEMPLATE, lib):contains(CONFIG, shared) {
|
||||
DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport)
|
||||
}
|
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
@ -0,0 +1,345 @@
|
|||
# ukui-search介绍
|
||||
|
||||
[dWIP] UKUI Search is a user-wide desktop search feature of UKUI desktop environment.
|
||||
|
||||
## 简介
|
||||
狭义上的ukui-search指ukui桌面环境中的全局搜索应用,目前最新版本为3.1-xxx。全局搜索应用提供了本地文件、文本内容、应用、设置项、便签等聚合搜索功能,基于其文件索引功能,可以为用户提供快速准确的搜索体验。
|
||||
|
||||
广义的ukui-search除了包括全局搜索应用,还包括在ukui桌面环境中的本地搜索服务以及其开发接口。基于文建索引服务,应用搜索数据服务等基础数据源服务,可以提供基于C++接口的搜索功能,应用开发者可以通过引用动态库的形式直接使用其搜索功能。除此之外,ukui桌面环境搜索服务还提供了一组基于Qt插件框架的插件接口,用户可以通过继承接口以实现搜索功能的扩展。
|
||||
以下提到的ukui-search如无说明均指后者。
|
||||
|
||||
ukui-search 目前共有5个包:
|
||||
+ ukui-search_xxxxxx.deb
|
||||
+ libukui-search-dev_xxxxx.deb
|
||||
+ libukui-search0_xxxxx.deb
|
||||
+ libchinese-segmentation0_xxxx.deb
|
||||
+ ukui-search-systemdbus_xxxxx.deb
|
||||
|
||||
xxx代表版本号。其中,ukui-search 为全局搜索应用本体,libukui-search包提供了搜索服务基本功能以及扩展接口,libukui-search-dev为其开发包。libchinese-segmentation包为搜索服务提供了NLP能力,如中文分词等。ukui-search-systemdbus包提供了一些systemdbus提权操作。
|
||||
|
||||
## 运行
|
||||
搜索服务相关的进程共有5个,包括ukui-search(全局搜索GUI界面),ukui-search-service(文件搜索服务),first-index(文件搜索服务子进程),inotify-index(文件搜索服务子进程),ukui-search-app-data-service(应用数据维护服务)。
|
||||
|
||||
ukui-search、ukui-search-service和ukui-search-app-data-service服务默认开机自启,其中first-index进程和inotify-index进程作为ukui-search-service的子进程,并由其控制启动和退出。
|
||||
|
||||
## 快捷键、命令行和dbus接口
|
||||
|
||||
呼出搜索GUI界面的系统快捷键为`WIN+s`。
|
||||
|
||||
ukui-search进程的命令行如下:
|
||||
|
||||
```shell
|
||||
Usage: ukui-search [options]
|
||||
Options:
|
||||
-h, --help Displays this help.
|
||||
-v, --version Displays version information.
|
||||
-q, --quit Quit ukui-search application
|
||||
-s, --show Show main window
|
||||
```
|
||||
|
||||
ukui-search-service的命令行如下:
|
||||
|
||||
```shell
|
||||
Usage: ukui-search-service [options]
|
||||
Options:
|
||||
-h, --help Displays this help.
|
||||
-v, --version Displays version information.
|
||||
-q, --quit Stop service
|
||||
-i, --index <option> start or stop file index
|
||||
```
|
||||
|
||||
ukui-search 提供的dbus接口:
|
||||
|
||||
```
|
||||
service: com.ukui.search.service
|
||||
path: /
|
||||
interface: com.ukui.search.service
|
||||
showWindow () ↦ () //显示搜索主窗口
|
||||
searchKeyword (String keyword) ↦ () //显示主窗口并搜索传入的关键字
|
||||
```
|
||||
|
||||
## 交互
|
||||
|
||||
搜索的功能有一部分依赖于其他桌面环境组件:
|
||||
|
||||
设置项搜索:依赖ukui-control-center提供的配置文件,安装路径为:
|
||||
|
||||
> /usr/share/ukui-control-center/shell/res/search.xml
|
||||
|
||||
跳转到搜索结果对应的控制面板页面使用了ukui-control-center的命令行:
|
||||
|
||||
```shell
|
||||
Usage: ukui-control-center [options]
|
||||
Options:
|
||||
-h, --help Displays this help.
|
||||
-v, --version Displays version information.
|
||||
-m <module> display the specified module page //全局搜索使用的是这个命令
|
||||
|
||||
```
|
||||
|
||||
在线应用搜索:依赖kylin-software-center提供的dbus接口:
|
||||
|
||||
```
|
||||
service: com.kylin.softwarecenter.getsearchresults
|
||||
path: /com/kylin/softwarecenter/getsearchresults
|
||||
interface: com.kylin.getsearchresults
|
||||
get_search_result (String keyword) ↦ (Boolean arg_1)
|
||||
```
|
||||
|
||||
跳转到软件商店安装页面的使用了以下dbus接口:
|
||||
|
||||
```
|
||||
service: com.kylin.softwarecenter
|
||||
path: /com/kylin/softwarecenter
|
||||
interface: com.kylin.utiliface
|
||||
show_search_result (String appname) ↦ (Array of [Dict of {String, String}] arg_1)
|
||||
```
|
||||
|
||||
如果软件商店未打开或接口不可用时,则调用以下命令行:
|
||||
|
||||
```shell
|
||||
kylin-software-center -find <包名>
|
||||
```
|
||||
|
||||
便签本搜索:依赖ukui-notebook提供的dbus接口:
|
||||
|
||||
```
|
||||
service:org.ukui.note
|
||||
path:/org/ukui/note
|
||||
interface:org.ukui.note.interface
|
||||
keywordMatch (Array of [String] keyList) ↦ (Dict of {String, Variant} arg_0)
|
||||
```
|
||||
|
||||
打开文件所在路径功能调用了peony提供的dbus接口:
|
||||
|
||||
```
|
||||
service:org.freedesktop.FileManager1
|
||||
path:/org/freedesktop/FileManager1
|
||||
interface: org.freedesktop.FileManager1
|
||||
ShowItems (Array of [String] uriList, String startUpId) ↦ ()
|
||||
```
|
||||
|
||||
## 原理与功能特点
|
||||
|
||||
全局搜索支持控制面板设置项搜索,应用搜索,文件搜索,便签本搜索。支持名称,拼音,或拼音首字母搜索(文本内容搜索和便签本搜索不支持拼音搜索)。其中,设置项搜索通过读取控制面板提供的配置文件实现,打开对应的控制面板页面也依赖与控制面板提供的命令行;应用搜索分为本地已安装应用(包括安卓兼容应用)和软件商店已上架的在线应用,在线应用的搜索和跳转安装通过软件商店提供的接口实现。所以,当怀疑搜索的设置搜索或应用搜索有问题时,可以直接测试控制面板或软件商店对应的接口。
|
||||
|
||||
文件搜索分为文件名(文件夹名)搜索和文本内容搜索。文件搜索有两种模式:`直接搜索`和`建立索引搜索`。
|
||||
|
||||
+ 直接搜索:类似文件管理的搜索,通过遍历匹配关键字搜索,不支持文本内容搜索。
|
||||
|
||||
+ 索引搜索:搜索通过遍历文件系统建立数据库(需要消耗一定的时间和资源),搜索时直接对数据库进行搜索,可以实现毫秒级的搜索响应,建立索引的过程中,搜索结果可能不全或者搜不出结果。
|
||||
首次索引进程first-index由ukui-search-service进程拉起,在用户首次开启索引功能或者索引损坏需要重建索引时开启;索引更新进程为inotify-index,同样由ukui-search-service进程拉起,但更新进程不会一直存在,其只在用户有文件更新时启动,一段时间后自动关闭。
|
||||
索引数据库会基于文件系统监听进行实时更新。但是由于解析文本需要时间,所以大文件的索引新可能会有短暂的延迟。由于各种意外原因,比如索引更新过程中掉电关机,可能会导致索引损坏,此时搜索在下次开机时会重新建立索引来保证正常的文件搜索功能。基于机器配置和本地文件的数量,大小以及种类,索引重建的时间可以从几秒到数分钟不等。
|
||||
索引搜索支持文本内容搜索,基本原理可以参考 [倒排索引与优麒麟的文件搜索](https://docs.qq.com/doc/DU0p0S1lRelp2aW1y) 。建立索引时,搜索会对常用的文本文件进行解析,提取关键词存入数据库。搜索时,用户输入的文本也会被提取关键词,和数据库中的关键词进行匹配, 所以文本索引并不能保证你搜索一个文本文件里的任意内容都能搜出这个文件,这也不是普遍的应用场景。搜索输入的文本中必须要包含【关键词】才可以。比如你搜索一个‘的’,由于‘的’并不是任何文件的关键词,所以并不会有搜索到任何文件。事实上,我们有一个停用词词库,专门用来排除‘我’‘的’于是‘等等基本上在每个文档都会出现的一些无用词。目前,搜索支持解析的文件格式有:docx,pptx, xlsx, txt(大部分编码格式), doc, dot, wps, ppt, pps, dps, et, xls, pdf,以上格式均不支持加密文件的解析。
|
||||
|
||||
> 注意:应用的.desktop文件并不是应用本身或者“快捷方式”,对于搜索来说它只是一个文件,所以搜索desktop文件的名字并不能搜出这个应用,除非它恰好和应用重名。另外,在文件搜索中显示的dekstop文件并不会以应用的形式显示,而是显示它本来的样子——一个文件。
|
||||
|
||||
## 配置文件与用户数据
|
||||
|
||||
ukui-search应用和ukui-search-service、ukui-search-app-data-service的配置文件以及用户数据都保存在如下路径:
|
||||
|
||||
> ~/.config/org.ukui/ukui-search
|
||||
|
||||
文件说明:
|
||||
|
||||
+ ukui-search.conf ------------------------------------全局搜索GUI配置文件。
|
||||
+ ukui-search-block-dirs.conf ---------------------文件搜索黑名单,在控制面板中设置
|
||||
+ ukui-search-index-status.conf ------------------文件索引服务状态记录
|
||||
+ index_data ---------------------------------------------文件索引数据库
|
||||
+ content_index_data ---------------------------------文本内容数据库
|
||||
+ ocr_index_data --------------------------------------- OCR图片搜索数据库
|
||||
|
||||
## 编译
|
||||
|
||||
下载源码,切换到ukss-dev分支(优麒麟2204版本)
|
||||
|
||||
根据debian/control文件安装编译依赖
|
||||
|
||||
```shell
|
||||
mkdir build;cd build;qmake ..;make
|
||||
```
|
||||
|
||||
编译会生成的二进制文件:
|
||||
|
||||
+ ukui-search(搜索应用)
|
||||
+ ukui-search-service(搜文件索引服务)
|
||||
+ ukui-search-app-data-service(应用数据服务)
|
||||
+ ukui-search-systemdbus(文建索引服务的system dbus提权接口)
|
||||
|
||||
库文件:
|
||||
|
||||
+ libchinese-segmentation.so(中文分词)
|
||||
+ libukui-search.so(提供搜索服务和搜索应用的API)
|
||||
+ libsearch-ukcc-plugin.so(ukui-contorl-center插件)
|
||||
|
||||
## 调试
|
||||
|
||||
ukui-search目前并未采用ukui-log4qt模块的日志功能。如需调试,可在以下目录新建`ukui-search.log`、`ukui-search-service.log`以及`ukui-search-app-data-service.log`文件,分别对应全局搜索GUI应用,全局搜索文件索引服务和应用数据服务。新建日志文件后,日志会自动打印到对应额文件中,但目前日志没有自动备份或删除机制。
|
||||
|
||||
## 开发接口
|
||||
|
||||
### 搜索服务接口(此接口目前处于快速更新总,请以代码为准)
|
||||
|
||||
#### Use with CMake:
|
||||
|
||||
```cmake
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(ukui-search REQUIRED ukui-search)
|
||||
include_directories(${ukui-search_INCLUDE_DIRS})
|
||||
target_link_libraries(yourapp ukui-search)
|
||||
```
|
||||
|
||||
#### Use with Qmake:
|
||||
|
||||
```
|
||||
CONFIG += link_pkgconfig
|
||||
PKGCONFIG += ukui-search
|
||||
```
|
||||
|
||||
使用示例:
|
||||
|
||||
```c++
|
||||
#include <UkuiSearchTask>
|
||||
......
|
||||
//初始化一个搜索实例
|
||||
UkuiSearch::UkuiSearchTask ukst;
|
||||
//初始化队列
|
||||
UkuiSearch::DataQueue<UkuiSearch::ResultItem> *queue = ukst.init();
|
||||
//加载想要使用的搜索插件
|
||||
ukst.initSearchPlugin(UkuiSearch::SearchType::File);
|
||||
//添加搜索条件
|
||||
ukst.setOnlySearchFile(true);
|
||||
ukst.addKeyword(m_keyword);
|
||||
//启动搜索(异步)
|
||||
ukst.startSearch(UkuiSearch::SearchType::File);
|
||||
//接收结果(示例)
|
||||
while(true) {
|
||||
if(!queue->isEmpty()) {
|
||||
qDebug() << queue->dequeue().getItemKey();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
目前搜索服务内置的可初始化的插件有(目前仅支持文件,文本和应用搜索):
|
||||
|
||||
```c++
|
||||
enum class SearchType{
|
||||
File = 0x1 << 0,
|
||||
FileContent = 0x1 << 1,
|
||||
Application = 0x1 << 2,
|
||||
Setting = 0x1 << 3,
|
||||
Note = 0x1 << 4,
|
||||
Mail = 0x1 << 5,
|
||||
Custom = 0x1 << 6
|
||||
};
|
||||
```
|
||||
|
||||
### 搜索服务插件接口
|
||||
|
||||
除了上面的内置插件,用户可以通过集成插件接口实现自定义搜索插件:
|
||||
|
||||
```c++
|
||||
namespace UkuiSearch {
|
||||
class SearchTaskPluginIface : public QObject, public PluginInterface{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual QString getCustomSearchType() = 0;
|
||||
virtual SearchType getSearchType() = 0;
|
||||
//Asynchronous,multithread.
|
||||
virtual void startSearch(std::shared_ptr<SearchController> searchController) = 0;
|
||||
virtual void stop() = 0;
|
||||
Q_SIGNALS:
|
||||
void searchFinished(size_t searchId);
|
||||
};
|
||||
}
|
||||
Q_DECLARE_INTERFACE(UkuiSearch::SearchTaskPluginIface, SearchTaskPluginIface_iid)
|
||||
```
|
||||
|
||||
调用方法和上面的类似,只是需要在初始化插件和启动搜索的时候,指定用户自定的插件名称(用户插件默认启动即加载):
|
||||
|
||||
表示加载用户插件
|
||||
|
||||
```c++
|
||||
ukst.initSearchPlugin(UkuiSearch::SearchType::Custom);
|
||||
```
|
||||
|
||||
启动搜索
|
||||
|
||||
```c++
|
||||
ukst.startSearch(UkuiSearch::SearchType::<用户自定义的名称>);
|
||||
```
|
||||
|
||||
### 搜索应用插件接口
|
||||
|
||||
搜索应用本身也提供了一个插件接口,可以通过加载用户实现的插件以实现额外搜索功能:
|
||||
|
||||
```c++
|
||||
namespace UkuiSearch {
|
||||
class SearchPluginIface : public PluginInterface
|
||||
{
|
||||
public:
|
||||
struct DescriptionInfo
|
||||
{
|
||||
QString key;
|
||||
QString value;
|
||||
};
|
||||
struct Actioninfo
|
||||
{
|
||||
int actionkey;
|
||||
QString displayName;
|
||||
};
|
||||
/**
|
||||
* @brief The ResultInfo struct
|
||||
*/
|
||||
struct ResultInfo
|
||||
{
|
||||
QIcon icon;
|
||||
QString name;
|
||||
QVector<DescriptionInfo> description;
|
||||
QString actionKey;
|
||||
int type;
|
||||
};
|
||||
|
||||
virtual ~SearchPluginIface() {}
|
||||
virtual QString getPluginName() = 0;
|
||||
virtual void KeywordSearch(QString keyword,DataQueue<ResultInfo> *searchResult) = 0;
|
||||
virtual void stopSearch() = 0;
|
||||
virtual QList<Actioninfo> getActioninfo(int type) = 0;
|
||||
virtual void openAction(int actionkey, QString key, int type) = 0;
|
||||
virtual QWidget *detailPage(const ResultInfo &ri) = 0;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
> 接口使用注意事项:
|
||||
>
|
||||
> 接口实现时,需要继承`SearchPluginIface`,并设置以下接口信息:
|
||||
>
|
||||
> Q_PLUGIN_METADATA(IID SearchPluginIface_iid FILE "common.json") Q_INTERFACES(Zeeker::SearchPluginIface)
|
||||
|
||||
其中 `common.json`为开发者自己编写的插件元数据文件,比如可以用来指定版本号等。
|
||||
|
||||
子类需要上面两个接口类的所有虚函数,其中,
|
||||
|
||||
```c++
|
||||
virtual void KeywordSearch(QString keyword,DataQueue<ResultInfo> *searchResult) = 0;
|
||||
```
|
||||
|
||||
函数会被UI直接调用,如果你的搜索功能十分费时,请在子线程里实现搜索,不要阻塞UI;
|
||||
|
||||
`ResultInfo`代表每一个结果项,`DataQueue`是前端取结果的数据队列。
|
||||
|
||||
```c++
|
||||
virtual QList<Actioninfo> getActioninfo(int type) = 0;
|
||||
```
|
||||
|
||||
这个函数用于获取每一项搜索结果可以执行的动作,比如打开等,`Actioninfo`中的`actionkey`用于指定特定的`action`,`type`用于指定搜索结果的类型(如果你的搜索结果有的话);
|
||||
|
||||
```c++
|
||||
virtual QWidget *detailPage(const ResultInfo &ri) = 0;
|
||||
```
|
||||
|
||||
这是用于获取每一项的详情页的函数,详情页同一时间只会显示一个,所以如果你的搜索结果详情页都是一致的风格,最好提前初始化,当这个方法被调用时只更新数据即可。
|
||||
|
||||
请一定要注意,搜索可能被快速触发,所以你需要确保当用户进行一次搜索时,队列里不会被错误的插入上一次的搜索结果。
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<schemalist gettext-domain="ukui-log4qt-ukui-search">
|
||||
<schema id="org.ukui.ukui-log4qt-ukui-search" path="/org/ukui/ukui-log4qt-ukui-search/">
|
||||
<key type="s" name="log4j-handleqtmessages">
|
||||
<default>"true"</default>
|
||||
<summary>hook qt messages</summary>
|
||||
<description>Control if hook qt messages</description>
|
||||
</key>
|
||||
<key type="s" name="log4j-rootlogger">
|
||||
<default>"WARN,console,daily"</default>
|
||||
<summary>config rootLogger's level and appenders</summary>
|
||||
<description>config rootLogger's level and appenders:"level,appender"</description>
|
||||
</key>
|
||||
<key type="s" name="log4j-appender-daily-datepattern">
|
||||
<default>".yyyy-MM-dd"</default>
|
||||
<summary>daily log file pattern</summary>
|
||||
<description>set daily log file pattern format:one day</description>
|
||||
</key>
|
||||
<key type="s" name="log4j-appender-daily-layout-conversionpattern">
|
||||
<default>"%d{yyyy-MM-dd HH:mm:ss,zzz}(%-4r)[%t]|%-5p| - %m%n"</default>
|
||||
<summary>set log message's format</summary>
|
||||
<description>set log message's format</description>
|
||||
</key>
|
||||
<key type="i" name="delaytime">
|
||||
<default>3600</default>
|
||||
<summary>set check log files delay time</summary>
|
||||
<description>set check log files delay time</description>
|
||||
</key>
|
||||
<key type="i" name="maxfilecount">
|
||||
<default>7</default>
|
||||
<summary>set log files count</summary>
|
||||
<description>set log files count,unit s</description>
|
||||
</key>
|
||||
<key type="i" name="maxfilesize">
|
||||
<default>512</default>
|
||||
<summary>set log files total size</summary>
|
||||
<description>set log files total size, unit M</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
|
@ -0,0 +1,14 @@
|
|||
<schemalist gettext-domain="ukui-search">
|
||||
<schema id="org.ukui.search.settings" path="/org/ukui/ukui-search/settings/">
|
||||
<key name="index-search" type="b">
|
||||
<default>false</default>
|
||||
<summary>search method</summary>
|
||||
<description>Is current search-method index-search.</description>
|
||||
</key>
|
||||
<key name="web-engine" type="s">
|
||||
<default>"baidu"</default>
|
||||
<summary>web engine</summary>
|
||||
<description>Web engine to search keyword online.</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
|
@ -0,0 +1,18 @@
|
|||
[Desktop Entry]
|
||||
Name=Search
|
||||
Name[zh_CN]=搜索
|
||||
Name[bo_CN]=བཤེར་འཚོལ།
|
||||
GenericName=UKUI Global Search
|
||||
GenericName[zh_CN]=全局搜索
|
||||
GenericName[bo_CN]=བཤེར་འཚོལ།
|
||||
Comment=ukui-search
|
||||
Comment[zh_CN]=全局搜索
|
||||
Comment[bo_CN]=ཁྱོན་ཡོངས་བཤེར་འཚོལ།
|
||||
Exec=/usr/bin/ukui-search -s
|
||||
Type=Application
|
||||
Icon=kylin-search
|
||||
X-UKUI-AutoRestart=true
|
||||
#NoDisplay=true
|
||||
NotShowIn=UKUI
|
||||
X-UKUI-Autostart-Phase=Application
|
||||
Terminal=false
|
|
@ -0,0 +1,16 @@
|
|||
[Desktop Entry]
|
||||
Name=ukui-search-service
|
||||
Name[zh_CN]=搜索服务
|
||||
GenericName=ukui-search-service
|
||||
GenericName[zh_CN]=搜索服务
|
||||
Comment=ukui-search-service
|
||||
Comment[zh_CN]=搜索服务
|
||||
Exec=/usr/bin/ukui-search-service %U
|
||||
Type=Application
|
||||
Icon=kylin-search
|
||||
X-UKUI-AutoRestart=true
|
||||
OnlyShowIn=UKUI
|
||||
NoDisplay=true
|
||||
X-UKUI-Autostart-Phase=Application
|
||||
Terminal=false
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
[Desktop Entry]
|
||||
Name=Search
|
||||
Name[zh_CN]=搜索
|
||||
GenericName=UKUI Global Search
|
||||
GenericName[zh_CN]=全局搜索
|
||||
Comment=ukui-search
|
||||
Comment[zh_CN]=全局搜索
|
||||
Exec=/usr/bin/ukui-search %U
|
||||
Type=Application
|
||||
Icon=kylin-search
|
||||
X-UKUI-AutoRestart=true
|
||||
OnlyShowIn=UKUI
|
||||
NoDisplay=true
|
||||
X-UKUI-Autostart-Phase=Application
|
||||
Terminal=false
|
|
@ -0,0 +1,14 @@
|
|||
include(stack-pages/stack-pages.pri)
|
||||
include(list-labels/list-labels.pri)
|
||||
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/search-line-edit.h \
|
||||
$$PWD/settings-widget.h \
|
||||
$$PWD/create-index-ask-dialog.h \
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/search-line-edit.cpp \
|
||||
$$PWD/settings-widget.cpp \
|
||||
$$PWD/create-index-ask-dialog.cpp \
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "create-index-ask-dialog.h"
|
||||
#include <QPainterPath>
|
||||
#include "kwindowsystem.h"
|
||||
|
||||
#define MAIN_SIZE QSize(380, 202)
|
||||
#define MAIN_SPACING 0
|
||||
#define MAIN_MARGINS 0,0,0,0
|
||||
#define TITLE_MARGINS 8,8,8,8
|
||||
#define TITLE_HEIGHT 40
|
||||
#define ICON_SIZE QSize(24, 24)
|
||||
#define TIP_LABEL_HEIGHT 64
|
||||
#define CLOSE_BTN_SIZE QSize(24, 24)
|
||||
#define CHECK_BOX_MARGINS 0,0,0,0
|
||||
#define BTN_FRAME_MARGINS 0,0,0,0
|
||||
#define CONTENT_MARGINS 32,0,32,24
|
||||
|
||||
using namespace UkuiSearch;
|
||||
CreateIndexAskDialog::CreateIndexAskDialog(QWidget *parent) : QDialog(parent) {
|
||||
// this->setWindowIcon(QIcon::fromTheme("kylin-search"));
|
||||
this->setWindowTitle(tr("ukui-search"));
|
||||
|
||||
initUi();
|
||||
|
||||
this->installEventFilter(this);
|
||||
}
|
||||
|
||||
void CreateIndexAskDialog::initUi() {
|
||||
KWindowSystem::setState(this->winId(),NET::SkipTaskbar | NET::SkipPager);
|
||||
this->setFixedSize(MAIN_SIZE);
|
||||
m_mainLyt = new QVBoxLayout(this);
|
||||
this->setLayout(m_mainLyt);
|
||||
m_mainLyt->setContentsMargins(MAIN_MARGINS);
|
||||
m_mainLyt->setSpacing(MAIN_SPACING);
|
||||
|
||||
//标题栏
|
||||
m_titleFrame = new QFrame(this);
|
||||
m_titleFrame->setFixedHeight(TITLE_HEIGHT);
|
||||
m_titleLyt = new QHBoxLayout(m_titleFrame);
|
||||
m_titleLyt->setContentsMargins(TITLE_MARGINS);
|
||||
m_titleFrame->setLayout(m_titleLyt);
|
||||
m_iconLabel = new QLabel(m_titleFrame);
|
||||
m_iconLabel->setFixedSize(ICON_SIZE);
|
||||
m_iconLabel->setPixmap(QIcon::fromTheme("kylin-search").pixmap(QSize(24, 24)));
|
||||
//主题改变时,更新自定义标题栏的图标
|
||||
connect(qApp, &QApplication::paletteChanged, this, [ = ]() {
|
||||
m_iconLabel->setPixmap(QIcon::fromTheme("kylin-search").pixmap(QSize(24, 24)));
|
||||
});
|
||||
m_titleLabel = new QLabel(m_titleFrame);
|
||||
m_titleLabel->setText(tr("Search"));
|
||||
m_closeBtn = new QPushButton(m_titleFrame);
|
||||
m_closeBtn->setFixedSize(CLOSE_BTN_SIZE);
|
||||
m_closeBtn->setIcon(QIcon::fromTheme("window-close-symbolic"));
|
||||
m_closeBtn->setProperty("isWindowButton", 0x02);
|
||||
m_closeBtn->setProperty("useIconHighlightEffect", 0x08);
|
||||
m_closeBtn->setFlat(true);
|
||||
connect(m_closeBtn, &QPushButton::clicked, this, [ = ]() {
|
||||
this->hide();
|
||||
});
|
||||
m_titleLyt->addWidget(m_iconLabel);
|
||||
m_titleLyt->addWidget(m_titleLabel);
|
||||
m_titleLyt->addStretch();
|
||||
m_titleLyt->addWidget(m_closeBtn);
|
||||
|
||||
m_mainLyt->addWidget(m_titleFrame);
|
||||
|
||||
//内容区域
|
||||
m_contentFrame = new QFrame(this);
|
||||
m_contentLyt = new QVBoxLayout(m_contentFrame);
|
||||
m_contentFrame->setLayout(m_contentLyt);
|
||||
m_contentLyt->setContentsMargins(CONTENT_MARGINS);
|
||||
|
||||
m_tipLabel = new QLabel(m_contentFrame);
|
||||
m_tipLabel->setText(tr("Creating index can help you getting results quickly, whether to create or not?"));
|
||||
m_tipLabel->setWordWrap(true);
|
||||
m_tipLabel->setAlignment(Qt::AlignVCenter);
|
||||
m_tipLabel->setMinimumHeight(TIP_LABEL_HEIGHT);
|
||||
m_contentLyt->addWidget(m_tipLabel);
|
||||
|
||||
m_checkFrame = new QFrame(m_contentFrame);
|
||||
m_checkLyt = new QHBoxLayout(m_checkFrame);
|
||||
m_checkLyt->setContentsMargins(CHECK_BOX_MARGINS);
|
||||
m_checkFrame->setLayout(m_checkLyt);
|
||||
m_checkBox = new QCheckBox(m_checkFrame);
|
||||
m_checkBox->setText(tr("Don't remind"));
|
||||
m_checkLyt->addWidget(m_checkBox);
|
||||
m_checkLyt->addStretch();
|
||||
m_contentLyt->addWidget(m_checkFrame);
|
||||
m_contentLyt->addStretch();
|
||||
|
||||
m_btnFrame = new QFrame(m_contentFrame);
|
||||
m_btnLyt = new QHBoxLayout(m_btnFrame);
|
||||
m_btnFrame->setLayout(m_btnLyt);
|
||||
m_btnLyt->setContentsMargins(BTN_FRAME_MARGINS);
|
||||
m_cancelBtn = new QPushButton(m_btnFrame);
|
||||
m_cancelBtn->setText(tr("No"));
|
||||
m_confirmBtn = new QPushButton(m_btnFrame);
|
||||
m_confirmBtn->setText(tr("Yes"));
|
||||
connect(m_cancelBtn, &QPushButton::clicked, this, [ = ]() {
|
||||
Q_EMIT this->btnClicked(false, m_checkBox->isChecked());
|
||||
this->hide();
|
||||
});
|
||||
connect(m_confirmBtn, &QPushButton::clicked, this, [ = ]() {
|
||||
Q_EMIT this->btnClicked(true, m_checkBox->isChecked());
|
||||
this->hide();
|
||||
});
|
||||
m_btnLyt->addStretch();
|
||||
m_btnLyt->addWidget(m_cancelBtn);
|
||||
m_btnLyt->addWidget(m_confirmBtn);
|
||||
m_contentLyt->addWidget(m_btnFrame);
|
||||
|
||||
m_mainLyt->addWidget(m_contentFrame);
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0))
|
||||
m_titleFrame->hide();
|
||||
this->setFixedSize(380, 162);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CreateIndexAskDialog::paintEvent 绘制窗口背景(默认背景较暗)
|
||||
*/
|
||||
void CreateIndexAskDialog::paintEvent(QPaintEvent *event) {
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
QPainterPath rectPath;
|
||||
rectPath.addRect(this->rect());
|
||||
p.save();
|
||||
p.fillPath(rectPath, palette().color(QPalette::Base));
|
||||
p.restore();
|
||||
return QDialog::paintEvent(event);
|
||||
}
|
||||
|
||||
// esc按键直接调用hide,产生hideEvent
|
||||
void CreateIndexAskDialog::hideEvent(QHideEvent *event)
|
||||
{
|
||||
Q_EMIT this->closed();
|
||||
QWidget::hideEvent(event);
|
||||
}
|
||||
|
||||
bool CreateIndexAskDialog::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
// kwin alt+f4发送的close事件会改变窗口的winid,屏蔽掉该事件,并发送hide事件
|
||||
if ((watched == this) && (event->type() == QEvent::Close)) {
|
||||
event->ignore();
|
||||
this->hide();
|
||||
return true;
|
||||
}
|
||||
return QDialog::eventFilter(watched, event);
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CREATEINDEXASKDIALOG_H
|
||||
#define CREATEINDEXASKDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QStyleOption>
|
||||
#include <QApplication>
|
||||
#include <QPainter>
|
||||
|
||||
namespace UkuiSearch {
|
||||
class CreateIndexAskDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
CreateIndexAskDialog(QWidget *parent = nullptr);
|
||||
~CreateIndexAskDialog() = default;
|
||||
|
||||
private:
|
||||
void initUi();
|
||||
|
||||
QVBoxLayout * m_mainLyt = nullptr;
|
||||
//标题栏
|
||||
QFrame * m_titleFrame = nullptr;
|
||||
QHBoxLayout * m_titleLyt = nullptr;
|
||||
QLabel * m_iconLabel = nullptr;
|
||||
QLabel * m_titleLabel = nullptr;
|
||||
QPushButton * m_closeBtn = nullptr;
|
||||
|
||||
//内容区域
|
||||
QFrame * m_contentFrame = nullptr;
|
||||
QVBoxLayout * m_contentLyt = nullptr;
|
||||
QLabel * m_tipLabel = nullptr; //提示语
|
||||
QFrame * m_checkFrame = nullptr; //"不再提示"选项框区域
|
||||
QHBoxLayout * m_checkLyt = nullptr;
|
||||
QCheckBox * m_checkBox = nullptr;
|
||||
QFrame * m_btnFrame = nullptr; //底部按钮区域
|
||||
QHBoxLayout * m_btnLyt = nullptr;
|
||||
QPushButton * m_cancelBtn = nullptr;
|
||||
QPushButton * m_confirmBtn = nullptr;
|
||||
|
||||
void paintEvent(QPaintEvent *);
|
||||
|
||||
protected:
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void closed();
|
||||
void btnClicked(const bool&, const bool&);
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif // CREATEINDEXASKDIALOG_H
|
|
@ -0,0 +1,162 @@
|
|||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "flow-layout.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QDebug>
|
||||
|
||||
FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) :
|
||||
QLayout(parent),
|
||||
m_hSpace(hSpacing),
|
||||
m_vSpace(vSpacing)
|
||||
{
|
||||
setContentsMargins(margin, margin, margin, margin);
|
||||
}
|
||||
|
||||
FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
|
||||
: m_hSpace(hSpacing),
|
||||
m_vSpace(vSpacing)
|
||||
{
|
||||
setContentsMargins(margin, margin, margin, margin);
|
||||
}
|
||||
|
||||
FlowLayout::~FlowLayout(){
|
||||
QLayoutItem * item;
|
||||
while ((item = takeAt(0))) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void FlowLayout::addItem(QLayoutItem *item){
|
||||
itemList.append(item);
|
||||
}
|
||||
|
||||
int FlowLayout::horizontalSpacing() const{
|
||||
if (m_hSpace >= 0) {
|
||||
return m_hSpace;
|
||||
} else {
|
||||
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
int FlowLayout::verticalSpacing() const{
|
||||
if (m_vSpace >= 0) {
|
||||
return m_vSpace;
|
||||
} else {
|
||||
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
int FlowLayout::count() const{
|
||||
return itemList.size();
|
||||
}
|
||||
|
||||
QLayoutItem * FlowLayout::itemAt(int index) const{
|
||||
return itemList.value(index);
|
||||
}
|
||||
|
||||
QLayoutItem * FlowLayout::takeAt(int index){
|
||||
if (index >= 0 && index < itemList.size())
|
||||
return itemList.takeAt(index);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
Qt::Orientations FlowLayout::expandingDirections() const{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool FlowLayout::hasHeightForWidth() const{
|
||||
return true;
|
||||
}
|
||||
|
||||
int FlowLayout::heightForWidth(int width) const{
|
||||
int height = doLayout(QRect(0, 0, width, 0), true);
|
||||
return height;
|
||||
}
|
||||
|
||||
void FlowLayout::setGeometry(const QRect &rect){
|
||||
QLayout::setGeometry(rect);
|
||||
doLayout(rect, false);
|
||||
}
|
||||
|
||||
QSize FlowLayout::sizeHint() const{
|
||||
return minimumSize();
|
||||
}
|
||||
|
||||
QSize FlowLayout::minimumSize() const{
|
||||
QSize size;
|
||||
QLayoutItem *item;
|
||||
Q_FOREACH (item, itemList)
|
||||
size = size.expandedTo(item->minimumSize());
|
||||
|
||||
size += QSize(2*margin(), 2*margin());
|
||||
return size;
|
||||
}
|
||||
|
||||
int FlowLayout::doLayout(const QRect &rect, bool testOnly) const{
|
||||
int left, top, right, bottom;
|
||||
getContentsMargins(&left, &top, &right, &bottom);
|
||||
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
|
||||
int x = effectiveRect.x();
|
||||
int y = effectiveRect.y();
|
||||
int lineHeight = 0;
|
||||
|
||||
QLayoutItem *item;
|
||||
Q_FOREACH (item, itemList) {
|
||||
QWidget *wid = item->widget();
|
||||
int spaceX = horizontalSpacing();
|
||||
if (spaceX == -1)
|
||||
spaceX = wid->style()->layoutSpacing(
|
||||
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
|
||||
int spaceY = verticalSpacing();
|
||||
if (spaceY == -1)
|
||||
spaceY = wid->style()->layoutSpacing(
|
||||
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
|
||||
|
||||
int nextX = x + item->sizeHint().width() + spaceX;
|
||||
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
|
||||
x = effectiveRect.x();
|
||||
y = y + lineHeight + spaceY;
|
||||
nextX = x + item->sizeHint().width() + spaceX;
|
||||
lineHeight = 0;
|
||||
}
|
||||
|
||||
if (!testOnly)
|
||||
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
|
||||
|
||||
x = nextX;
|
||||
lineHeight = qMax(lineHeight, item->sizeHint().height());
|
||||
}
|
||||
return y + lineHeight - rect.y() + bottom;
|
||||
}
|
||||
|
||||
int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const{
|
||||
QObject *parent = this->parent();
|
||||
if (!parent) {
|
||||
return -1;
|
||||
} else if (parent->isWidgetType()) {
|
||||
QWidget *pw = static_cast<QWidget *>(parent);
|
||||
return pw->style()->pixelMetric(pm, 0, pw);
|
||||
} else {
|
||||
return static_cast<QLayout *>(parent)->spacing();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* Copyright (C) 2019 Tianjin KYLIN Information Technology Co., Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
#ifndef FLOWLAYOUT_H
|
||||
#define FLOWLAYOUT_H
|
||||
|
||||
#include <QLayout>
|
||||
#include <QRect>
|
||||
#include <QStyle>
|
||||
|
||||
class FlowLayout : public QLayout
|
||||
{
|
||||
|
||||
public:
|
||||
explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
|
||||
explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
|
||||
~FlowLayout();
|
||||
|
||||
public:
|
||||
void addItem(QLayoutItem *item) Q_DECL_OVERRIDE;
|
||||
int horizontalSpacing() const;
|
||||
int verticalSpacing() const;
|
||||
Qt::Orientations expandingDirections() const Q_DECL_OVERRIDE;
|
||||
bool hasHeightForWidth() const Q_DECL_OVERRIDE;
|
||||
int heightForWidth(int) const Q_DECL_OVERRIDE;
|
||||
int count() const Q_DECL_OVERRIDE;
|
||||
QLayoutItem *itemAt(int index) const Q_DECL_OVERRIDE;
|
||||
QSize minimumSize() const Q_DECL_OVERRIDE;
|
||||
void setGeometry(const QRect &rect) Q_DECL_OVERRIDE;
|
||||
QSize sizeHint() const Q_DECL_OVERRIDE;
|
||||
QLayoutItem *takeAt(int index) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
int doLayout(const QRect &rect, bool testOnly) const;
|
||||
int smartSpacing(QStyle::PixelMetric pm) const;
|
||||
|
||||
private:
|
||||
QList<QLayoutItem *> itemList;
|
||||
int m_hSpace;
|
||||
int m_vSpace;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // FLOWLAYOUT_H
|
|
@ -0,0 +1,5 @@
|
|||
SOURCES += \
|
||||
$$PWD/flow-layout.cpp \
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/flow-layout.h \
|
|
@ -0,0 +1,9 @@
|
|||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/show-more-label.h \
|
||||
$$PWD/title-label.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/show-more-label.cpp \
|
||||
$$PWD/title-label.cpp
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "show-more-label.h"
|
||||
#include <QEvent>
|
||||
#include <QDebug>
|
||||
#include <QIcon>
|
||||
using namespace UkuiSearch;
|
||||
ShowMoreLabel::ShowMoreLabel(QWidget *parent) : QWidget(parent) {
|
||||
initUi();
|
||||
m_timer = new QTimer;
|
||||
}
|
||||
|
||||
void ShowMoreLabel::resetLabel() {
|
||||
m_isOpen = false;
|
||||
m_iconLabel->setPixmap(QIcon::fromTheme("ukui-down-symbolic", QIcon(":/res/icons/ukui-down-symbolic.svg")).pixmap(QSize(16, 16)));
|
||||
}
|
||||
|
||||
void ShowMoreLabel::setLabel()
|
||||
{
|
||||
m_isOpen = true;
|
||||
m_iconLabel->setPixmap(QIcon::fromTheme("ukui-up-symbolic", QIcon(":/res/icons/ukui-up-symbolic.svg")).pixmap(QSize(16, 16)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ShowMoreLabel::getExpanded 获取当前是否是展开状态
|
||||
* @return true已展开,false已收起
|
||||
*/
|
||||
bool ShowMoreLabel::getExpanded() {
|
||||
return m_isOpen;
|
||||
}
|
||||
|
||||
void ShowMoreLabel::initUi() {
|
||||
QPalette pal = palette();
|
||||
pal.setColor(QPalette::WindowText, QColor(55, 144, 250, 255));
|
||||
m_layout = new QHBoxLayout(this);
|
||||
m_layout->setContentsMargins(0, 0, 0, 6);
|
||||
m_iconLabel = new QLabel(this);
|
||||
m_iconLabel->setPixmap(QIcon::fromTheme("ukui-down-symbolic", QIcon(":/res/icons/ukui-down-symbolic.svg")).pixmap(QSize(16, 16)));
|
||||
m_iconLabel->setCursor(QCursor(Qt::PointingHandCursor));
|
||||
m_iconLabel->installEventFilter(this);
|
||||
// m_loadingIconLabel = new QLabel(this); //使用图片显示加载状态时,取消此label的注释
|
||||
// m_loadingIconLabel->setFixedSize(18, 18);
|
||||
// m_loadingIconLabel->hide();
|
||||
m_layout->setAlignment(Qt::AlignRight | Qt::AlignTop);
|
||||
m_layout->addWidget(m_iconLabel);
|
||||
m_iconLabel->setPalette(pal);
|
||||
m_iconLabel->setCursor(QCursor(Qt::PointingHandCursor));
|
||||
m_iconLabel->setProperty("useIconHighlightEffect", 0x08);
|
||||
m_iconLabel->setProperty("iconHighlightEffectMode", 1);
|
||||
// m_layout->addWidget(m_loadingIconLabel);
|
||||
}
|
||||
|
||||
bool ShowMoreLabel::eventFilter(QObject *watched, QEvent *event) {
|
||||
if(watched == m_iconLabel) {
|
||||
if(event->type() == QEvent::MouseButtonPress) {
|
||||
if(! m_timer->isActive()) {
|
||||
if(!m_isOpen) {
|
||||
m_iconLabel->setPixmap(QIcon::fromTheme("ukui-up-symbolic", QIcon(":/res/icons/ukui-up-symbolic.svg")).pixmap(QSize(16, 16)));
|
||||
m_isOpen = true;
|
||||
Q_EMIT this->showMoreClicked();
|
||||
} else {
|
||||
m_iconLabel->setPixmap(QIcon::fromTheme("ukui-down-symbolic", QIcon(":/res/icons/ukui-down-symbolic.svg")).pixmap(QSize(16, 16)));
|
||||
m_isOpen = false;
|
||||
Q_EMIT this->retractClicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(watched, event);
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#ifndef SHOWMORELABEL_H
|
||||
#define SHOWMORELABEL_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QLabel>
|
||||
#include <QHBoxLayout>
|
||||
#include <QTimer>
|
||||
|
||||
namespace UkuiSearch {
|
||||
class ShowMoreLabel : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ShowMoreLabel(QWidget *parent = nullptr);
|
||||
~ShowMoreLabel() = default;
|
||||
void resetLabel();
|
||||
void setLabel();
|
||||
bool getExpanded();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *, QEvent *);
|
||||
|
||||
private:
|
||||
QHBoxLayout * m_layout = nullptr;
|
||||
QLabel * m_iconLabel = nullptr;
|
||||
QLabel * m_loadingIconLabel = nullptr;
|
||||
QTimer * m_timer = nullptr;
|
||||
bool m_isOpen = false;
|
||||
int m_currentState = 0;
|
||||
|
||||
void initUi();
|
||||
|
||||
Q_SIGNALS:
|
||||
void showMoreClicked();
|
||||
void retractClicked();
|
||||
|
||||
public Q_SLOTS:
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SHOWMORELABEL_H
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "title-label.h"
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
#define UNFOLD_LABEL_HEIGHT 30
|
||||
#define NUM_LIMIT_SHOWN_DEFAULT 5
|
||||
using namespace UkuiSearch;
|
||||
TitleLabel::TitleLabel(QWidget * parent) : QLabel(parent) {
|
||||
initUi();
|
||||
initConnections();
|
||||
}
|
||||
|
||||
void TitleLabel::setShowMoreLableVisible()
|
||||
{
|
||||
m_showMoreLabel->setVisible(true);
|
||||
m_showMoreLabel->setLabel();
|
||||
}
|
||||
|
||||
void TitleLabel::initUi() {
|
||||
this->setContentsMargins(8, 0, 0, 0);
|
||||
this->setFixedHeight(24);
|
||||
m_titleLyt = new QHBoxLayout(this);
|
||||
this->setLayout(m_titleLyt);
|
||||
m_showMoreLabel = new ShowMoreLabel(this);
|
||||
m_showMoreLabel->setFixedHeight(UNFOLD_LABEL_HEIGHT);
|
||||
m_showMoreLabel->adjustSize();
|
||||
m_showMoreLabel->hide();
|
||||
m_titleLyt->addStretch();
|
||||
m_titleLyt->addWidget(m_showMoreLabel);
|
||||
}
|
||||
|
||||
void TitleLabel::initConnections() {
|
||||
connect(m_showMoreLabel, &ShowMoreLabel::showMoreClicked, this, &TitleLabel::showMoreClicked);
|
||||
connect(m_showMoreLabel, &ShowMoreLabel::retractClicked, this, &TitleLabel::retractClicked);
|
||||
|
||||
}
|
||||
|
||||
void TitleLabel::paintEvent(QPaintEvent * event) {
|
||||
Q_UNUSED(event)
|
||||
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
|
||||
QRect rect = this->rect();
|
||||
p.setRenderHint(QPainter::Antialiasing); // 反锯齿;
|
||||
p.setBrush(opt.palette.color(QPalette::Text));
|
||||
p.setOpacity(0.04);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRoundedRect(rect, 6, 6);
|
||||
return QLabel::paintEvent(event);
|
||||
}
|
||||
|
||||
void TitleLabel::onListLengthChanged(const int &length)
|
||||
{
|
||||
m_showMoreLabel->setVisible(length > NUM_LIMIT_SHOWN_DEFAULT);
|
||||
}
|
||||
|
||||
void TitleLabel::lableReset()
|
||||
{
|
||||
m_showMoreLabel->resetLabel();
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#ifndef TITLELABEL_H
|
||||
#define TITLELABEL_H
|
||||
|
||||
#include <QLabel>
|
||||
#include "show-more-label.h"
|
||||
#include <QHBoxLayout>
|
||||
|
||||
namespace UkuiSearch {
|
||||
class TitleLabel : public QLabel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
TitleLabel(QWidget * parent = nullptr);
|
||||
~TitleLabel() = default;
|
||||
|
||||
void setShowMoreLableVisible();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *);
|
||||
|
||||
private:
|
||||
void initUi();
|
||||
void initConnections();
|
||||
|
||||
QHBoxLayout * m_titleLyt = nullptr;
|
||||
ShowMoreLabel * m_showMoreLabel = nullptr;
|
||||
|
||||
public Q_SLOTS:
|
||||
void onListLengthChanged(const int &);
|
||||
void lableReset();
|
||||
|
||||
Q_SIGNALS:
|
||||
void startSearch(const QString &);
|
||||
void stopSearch();
|
||||
void showMoreClicked();
|
||||
void retractClicked();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TITLELABEL_H
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "search-line-edit.h"
|
||||
#include <KWindowEffects>
|
||||
#include <QPainterPath>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
extern void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed);
|
||||
QT_END_NAMESPACE
|
||||
using namespace UkuiSearch;
|
||||
/**
|
||||
* @brief UKuiSearchLineEdit 全局搜索的输入框
|
||||
*/
|
||||
SearchLineEdit::SearchLineEdit(QWidget *parent) : QLineEdit(parent) {
|
||||
setStyle(new LineEditStyle());
|
||||
this->setFocusPolicy(Qt::StrongFocus);
|
||||
this->setFixedSize(680, 50);
|
||||
this->setTextMargins(35, 0, 0, 0);
|
||||
this->setAttribute(Qt::WA_TranslucentBackground);
|
||||
this->setDragEnabled(true);
|
||||
|
||||
m_queryIcon = new QLabel;
|
||||
QPixmap pixmap;
|
||||
if (!QIcon::fromTheme("system-search-symbolic").isNull()) {
|
||||
pixmap = QPixmap(QIcon::fromTheme("system-search-symbolic").pixmap(QSize(18, 18)));
|
||||
} else {
|
||||
pixmap = QPixmap(QIcon(":/res/icons/system-search.symbolic.png").pixmap(QSize(18, 18)));
|
||||
}
|
||||
m_queryIcon->setProperty("useIconHighlightEffect", 0x10);
|
||||
m_queryIcon->setFixedSize(pixmap.size());
|
||||
m_queryIcon->setPixmap(pixmap);
|
||||
|
||||
m_ly = new QHBoxLayout(this);
|
||||
m_ly->addSpacing(8);
|
||||
m_ly->addWidget(m_queryIcon);
|
||||
m_ly->addStretch();
|
||||
|
||||
this->setPlaceholderText(tr("Search"));
|
||||
this->setMaxLength(100);
|
||||
|
||||
m_timer = new QTimer;
|
||||
connect(m_timer, &QTimer::timeout, this, [ = ]() {
|
||||
m_timer->stop();
|
||||
Q_EMIT this->requestSearchKeyword(this->text());
|
||||
});
|
||||
connect(this, &SearchLineEdit::textChanged, this, [ = ](QString text) {
|
||||
if(m_isEmpty) {
|
||||
m_isEmpty = false;
|
||||
Q_EMIT this->requestSearchKeyword(text);
|
||||
} else {
|
||||
if(text == "") {
|
||||
m_isEmpty = true;
|
||||
m_timer->stop();
|
||||
Q_EMIT this->requestSearchKeyword(text);
|
||||
return;
|
||||
}
|
||||
m_timer->start(0.1 * 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SearchLineEdit::~SearchLineEdit() {
|
||||
|
||||
}
|
||||
|
||||
void SearchLineEdit::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing); // 反锯齿;
|
||||
p.setBrush(palette().base());
|
||||
p.setOpacity(GlobalSettings::getInstance()->getValue(TRANSPARENCY_KEY).toDouble());
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRoundedRect(this->rect(), 12, 12);
|
||||
return QLineEdit::paintEvent(e);
|
||||
}
|
||||
|
||||
void SearchLineEdit::focusOutEvent(QFocusEvent *e)
|
||||
{
|
||||
this->setFocus();
|
||||
}
|
||||
|
||||
SeachBarWidget::SeachBarWidget(QWidget *parent): QWidget(parent) {
|
||||
m_ly = new QHBoxLayout(this);
|
||||
m_searchLineEdit = new SearchLineEdit(this);
|
||||
this->setFixedSize(m_searchLineEdit->width()+20, m_searchLineEdit->height()+20);
|
||||
m_ly->setContentsMargins(0,0,0,0);
|
||||
m_ly->addWidget(m_searchLineEdit);
|
||||
this->setFocusProxy(m_searchLineEdit);
|
||||
connect(m_searchLineEdit, &SearchLineEdit::requestSearchKeyword, this, &SeachBarWidget::requestSearchKeyword);
|
||||
}
|
||||
|
||||
SeachBarWidget::~SeachBarWidget() {
|
||||
}
|
||||
|
||||
void SeachBarWidget::clear()
|
||||
{
|
||||
m_searchLineEdit->clear();
|
||||
}
|
||||
|
||||
void SeachBarWidget::reSearch()
|
||||
{
|
||||
Q_EMIT this->m_searchLineEdit->requestSearchKeyword(m_searchLineEdit->text());
|
||||
}
|
||||
|
||||
void SeachBarWidget::setText(QString keyword)
|
||||
{
|
||||
m_searchLineEdit->setText(keyword);
|
||||
}
|
||||
|
||||
void SeachBarWidget::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
Q_UNUSED(e)
|
||||
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
QPainterPath rectPath;
|
||||
rectPath.addRoundedRect(this->rect().adjusted(10, 10, -10, -10), 12, 12);
|
||||
|
||||
|
||||
// 画一个黑底
|
||||
QPixmap pixmap(this->rect().size());
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter pixmapPainter(&pixmap);
|
||||
pixmapPainter.setRenderHint(QPainter::Antialiasing);
|
||||
// pixmapPainter.setCompositionMode(QPainter::CompositionMode_Difference);
|
||||
pixmapPainter.setPen(Qt::transparent);
|
||||
pixmapPainter.setBrush(Qt::black);
|
||||
pixmapPainter.setOpacity(0.65);
|
||||
pixmapPainter.drawPath(rectPath);
|
||||
pixmapPainter.end();
|
||||
|
||||
|
||||
// 模糊这个黑底
|
||||
QImage img = pixmap.toImage();
|
||||
qt_blurImage(img, 10, false, false);
|
||||
|
||||
|
||||
// 挖掉中心
|
||||
pixmap = QPixmap::fromImage(img);
|
||||
QPainter pixmapPainter2(&pixmap);
|
||||
pixmapPainter2.setRenderHint(QPainter::Antialiasing);
|
||||
pixmapPainter2.setCompositionMode(QPainter::CompositionMode_Clear);
|
||||
pixmapPainter2.setPen(Qt::transparent);
|
||||
pixmapPainter2.setBrush(Qt::transparent);
|
||||
pixmapPainter2.drawPath(rectPath);
|
||||
|
||||
|
||||
// 绘制阴影
|
||||
p.drawPixmap(this->rect(), pixmap, pixmap.rect());
|
||||
}
|
||||
|
||||
void LineEditStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
|
||||
{
|
||||
switch (element) {
|
||||
case PE_PanelLineEdit://UKUI Text edit style
|
||||
{
|
||||
if (const QStyleOptionFrame *f = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
|
||||
const bool enable = f->state & State_Enabled;
|
||||
const bool focus = f->state & State_HasFocus;
|
||||
|
||||
if (!enable) {
|
||||
painter->save();
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(f->palette.brush(QPalette::Disabled, QPalette::Button));
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
painter->drawRoundedRect(option->rect, 4, 4);
|
||||
painter->restore();
|
||||
return;
|
||||
}
|
||||
|
||||
if (f->state & State_ReadOnly) {
|
||||
painter->save();
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(f->palette.brush(QPalette::Active, QPalette::Button));
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
painter->drawRoundedRect(option->rect, 12, 12);
|
||||
painter->restore();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!focus) {
|
||||
QStyleOptionButton button;
|
||||
button.state = option->state & ~(State_Sunken | State_On);
|
||||
button.rect = option->rect;
|
||||
proxy()->drawPrimitive(PE_PanelButtonCommand, &button, painter, widget);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
// return QProxyStyle::drawPrimitive(element, option, painter, widget);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#ifndef SEARCHLINEEDIT_H
|
||||
#define SEARCHLINEEDIT_H
|
||||
#include <QWidget>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QtDBus/QtDBus>
|
||||
#include <QPainter>
|
||||
#include <QAction>
|
||||
#include <QTimer>
|
||||
#include <QStringListModel>
|
||||
#include <QCompleter>
|
||||
#include <QAbstractItemView>
|
||||
#include <QVector4D>
|
||||
#include <QListView>
|
||||
#include <QProxyStyle>
|
||||
#include "global-settings.h"
|
||||
|
||||
namespace UkuiSearch {
|
||||
|
||||
class SearchLineEdit : public QLineEdit {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SearchLineEdit(QWidget *parent = nullptr);
|
||||
// void record();
|
||||
~SearchLineEdit();
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *);
|
||||
void focusOutEvent(QFocusEvent *);
|
||||
|
||||
Q_SIGNALS:
|
||||
void requestSearchKeyword(QString text);
|
||||
private:
|
||||
QHBoxLayout *m_ly;
|
||||
QLabel *m_queryIcon;
|
||||
QTimer *m_timer;
|
||||
bool m_isEmpty = true;
|
||||
};
|
||||
|
||||
class SeachBarWidget: public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SeachBarWidget(QWidget *parent = nullptr);
|
||||
~SeachBarWidget();
|
||||
|
||||
void clear();
|
||||
void reSearch();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e);
|
||||
|
||||
Q_SIGNALS:
|
||||
void requestSearchKeyword(QString text);
|
||||
|
||||
public Q_SLOTS:
|
||||
void setText(QString keyword);
|
||||
|
||||
private:
|
||||
SearchLineEdit *m_searchLineEdit;
|
||||
QHBoxLayout *m_ly;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class LineEditStyle : public QProxyStyle
|
||||
{
|
||||
public:
|
||||
LineEditStyle() {}
|
||||
void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = nullptr) const override;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //SEARCHLINEEDIT_H
|
|
@ -0,0 +1,594 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "settings-widget.h"
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QFileDialog>
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
#include "global-settings.h"
|
||||
#include "file-utils.h"
|
||||
|
||||
using namespace UkuiSearch;
|
||||
extern void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed);
|
||||
SettingsWidget::SettingsWidget(QWidget *parent) : QWidget(parent) {
|
||||
// this->setWindowIcon(QIcon::fromTheme("kylin-search"));
|
||||
this->setWindowTitle(tr("ukui-search-settings"));
|
||||
// this->setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
|
||||
// this->setAttribute(Qt::WA_TranslucentBackground);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
m_hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS;
|
||||
m_hints.functions = MWM_FUNC_ALL;
|
||||
m_hints.decorations = MWM_DECOR_BORDER;
|
||||
XAtomHelper::getInstance()->setWindowMotifHint(winId(), m_hints);
|
||||
#endif
|
||||
|
||||
initUi();
|
||||
refreshIndexState();
|
||||
setupBlackList(GlobalSettings::getInstance()->getBlockDirs());
|
||||
resetWebEngine();
|
||||
}
|
||||
|
||||
SettingsWidget::~SettingsWidget() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::initUi 初始化界面UI
|
||||
*/
|
||||
void SettingsWidget::initUi() {
|
||||
QPalette pal = palette();
|
||||
pal.setColor(QPalette::Window, QColor(0, 0, 0, 0));
|
||||
// this->setFixedWidth(528);
|
||||
// this->setMinimumHeight(460);
|
||||
// this->setMaximumHeight(680);
|
||||
m_mainLyt = new QVBoxLayout(this);
|
||||
m_mainLyt->setContentsMargins(16, 8, 16, 24);
|
||||
this->setLayout(m_mainLyt);
|
||||
|
||||
//标题栏
|
||||
m_titleFrame = new QFrame(this);
|
||||
m_titleFrame->setFixedHeight(40);
|
||||
m_titleLyt = new QHBoxLayout(m_titleFrame);
|
||||
m_titleLyt->setContentsMargins(0, 0, 0, 0);
|
||||
m_titleFrame->setLayout(m_titleLyt);
|
||||
m_titleIcon = new QLabel(m_titleFrame);
|
||||
m_titleIcon->setPixmap(QIcon::fromTheme("kylin-search").pixmap(QSize(24, 24)));
|
||||
//主题改变时,更新自定义标题栏的图标
|
||||
connect(qApp, &QApplication::paletteChanged, this, [ = ]() {
|
||||
m_titleIcon->setPixmap(QIcon::fromTheme("kylin-search").pixmap(QSize(24, 24)));
|
||||
});
|
||||
m_titleLabel = new QLabel(m_titleFrame);
|
||||
m_titleLabel->setText(tr("Search"));
|
||||
m_closeBtn = new QPushButton(m_titleFrame);
|
||||
m_closeBtn->setFixedSize(24, 24);
|
||||
// m_closeBtn->setIcon(QIcon(":/res/icons/close.svg"));
|
||||
m_closeBtn->setIcon(QIcon::fromTheme("window-close-symbolic"));
|
||||
m_closeBtn->setProperty("isWindowButton", 0x02);
|
||||
m_closeBtn->setProperty("useIconHighlightEffect", 0x08);
|
||||
m_closeBtn->setFlat(true);
|
||||
connect(m_closeBtn, &QPushButton::clicked, this, [ = ]() {
|
||||
Q_EMIT this->settingWidgetClosed();
|
||||
m_timer->stop();
|
||||
this->close();
|
||||
});
|
||||
m_titleLyt->addWidget(m_titleIcon);
|
||||
m_titleLyt->addWidget(m_titleLabel);
|
||||
m_titleLyt->addStretch();
|
||||
m_titleLyt->addWidget(m_closeBtn);
|
||||
m_mainLyt->addWidget(m_titleFrame);
|
||||
|
||||
m_contentFrame = new QFrame(this);
|
||||
m_contentLyt = new QVBoxLayout(m_contentFrame);
|
||||
m_contentFrame->setLayout(m_contentLyt);
|
||||
m_contentLyt->setContentsMargins(8, 0, 8, 0);
|
||||
m_mainLyt->addWidget(m_contentFrame);
|
||||
|
||||
//设置
|
||||
m_settingLabel = new QLabel(m_contentFrame);
|
||||
m_settingLabel->setText(tr("<h2>Settings</h2>"));
|
||||
m_contentLyt->addWidget(m_settingLabel);
|
||||
|
||||
//文件索引
|
||||
m_indexTitleLabel = new QLabel(m_contentFrame);
|
||||
m_indexTitleLabel->setText(tr("<h3>Index State</h3>"));
|
||||
m_indexStateLabel = new QLabel(m_contentFrame);
|
||||
m_indexStateLabel->setText(tr("..."));
|
||||
m_indexNumLabel = new QLabel(m_contentFrame);
|
||||
m_indexNumLabel->setText(tr("..."));
|
||||
m_contentLyt->addWidget(m_indexTitleLabel);
|
||||
m_contentLyt->addWidget(m_indexStateLabel);
|
||||
// m_mainLyt->addWidget(m_indexNumLabel);
|
||||
m_indexNumLabel->hide();
|
||||
|
||||
//文件索引设置(黑名单)
|
||||
m_indexSettingLabel = new QLabel(m_contentFrame);
|
||||
m_indexSettingLabel->setText(tr("<h3>File Index Settings</h3>"));
|
||||
m_indexDescLabel = new QLabel(m_contentFrame);
|
||||
m_indexDescLabel->setText(tr("Following folders will not be searched. You can set it by adding and removing folders."));
|
||||
m_indexDescLabel->setWordWrap(true);
|
||||
m_indexBtnFrame = new QFrame(m_contentFrame);
|
||||
m_indexBtnLyt = new QHBoxLayout(m_indexBtnFrame);
|
||||
m_indexBtnLyt->setContentsMargins(0, 0, 0, 0);
|
||||
m_indexBtnFrame->setLayout(m_indexBtnLyt);
|
||||
m_addDirBtn = new QPushButton(m_indexBtnFrame);
|
||||
m_addDirBtn->setFixedHeight(32);
|
||||
m_addDirBtn->setMinimumWidth(164);
|
||||
m_addDirBtn->setText(tr("Add ignored folders"));
|
||||
connect(m_addDirBtn, &QPushButton::clicked, this, &SettingsWidget::onBtnAddClicked);
|
||||
m_indexBtnLyt->addWidget(m_addDirBtn);
|
||||
m_indexBtnLyt->addStretch();
|
||||
m_dirListArea = new QScrollArea(m_contentFrame);
|
||||
m_dirListArea->setPalette(pal);
|
||||
m_dirListArea->setFrameShape(QFrame::Shape::NoFrame);
|
||||
m_dirListWidget = new QWidget(m_contentFrame);
|
||||
m_dirListLyt = new QVBoxLayout(m_dirListWidget);
|
||||
m_dirListLyt->setContentsMargins(0, 0, 0, 0);
|
||||
m_dirListLyt->setSpacing(0);
|
||||
m_dirListWidget->setLayout(m_dirListLyt);
|
||||
m_dirListArea->setWidget(m_dirListWidget);
|
||||
m_dirListArea->setWidgetResizable(m_contentFrame);
|
||||
m_contentLyt->addWidget(m_indexSettingLabel);
|
||||
m_contentLyt->addWidget(m_indexDescLabel);
|
||||
m_contentLyt->addWidget(m_indexBtnFrame);
|
||||
m_contentLyt->addWidget(m_dirListArea);
|
||||
|
||||
//搜索引擎设置
|
||||
m_searchEngineLabel = new QLabel(m_contentFrame);
|
||||
m_searchEngineLabel->setText(tr("<h3>Search Engine Settings</h3>"));
|
||||
m_engineDescLabel = new QLabel(m_contentFrame);
|
||||
m_engineDescLabel->setText(tr("Please select search engine you preferred."));
|
||||
m_engineDescLabel->setWordWrap(true);
|
||||
m_engineBtnGroup = new QButtonGroup(m_contentFrame);
|
||||
m_baiduBtn = new QRadioButton(m_contentFrame);
|
||||
m_sougouBtn = new QRadioButton(m_contentFrame);
|
||||
m_360Btn = new QRadioButton(m_contentFrame);
|
||||
m_baiduBtn->setFixedSize(16, 16);
|
||||
m_sougouBtn->setFixedSize(16, 16);
|
||||
m_360Btn->setFixedSize(16, 16);
|
||||
m_radioBtnFrame = new QFrame(m_contentFrame);
|
||||
m_radioBtnLyt = new QHBoxLayout(m_radioBtnFrame);
|
||||
m_radioBtnFrame->setLayout(m_radioBtnLyt);
|
||||
m_baiduLabel = new QLabel();
|
||||
m_baiduLabel->setText(tr("baidu"));
|
||||
m_sougouLabel = new QLabel();
|
||||
m_sougouLabel->setText(tr("sougou"));
|
||||
m_360Label = new QLabel();
|
||||
m_360Label->setText(tr("360"));
|
||||
m_radioBtnLyt->setContentsMargins(0, 0, 0, 0);
|
||||
m_radioBtnLyt->addWidget(m_baiduBtn);
|
||||
m_radioBtnLyt->addWidget(m_baiduLabel);
|
||||
m_radioBtnLyt->addWidget(m_sougouBtn);
|
||||
m_radioBtnLyt->addWidget(m_sougouLabel);
|
||||
m_radioBtnLyt->addWidget(m_360Btn);
|
||||
m_radioBtnLyt->addWidget(m_360Label);
|
||||
m_radioBtnLyt->addStretch();
|
||||
m_engineBtnGroup->setExclusive(true);
|
||||
m_engineBtnGroup->addButton(m_baiduBtn);
|
||||
m_engineBtnGroup->addButton(m_sougouBtn);
|
||||
m_engineBtnGroup->addButton(m_360Btn);
|
||||
// m_engineBtnGroup->setId(m_baiduBtn, WebEngine::Baidu);
|
||||
// m_engineBtnGroup->setId(m_sougouBtn, WebEngine::Sougou);
|
||||
// m_engineBtnGroup->setId(m_360Btn, WebEngine::_360);
|
||||
// connect(m_engineBtnGroup, QOverload<int>::of(&QButtonGroup::buttonClicked), [ = ] (int id) {
|
||||
// setWebEngine(id);
|
||||
// });
|
||||
connect(m_baiduBtn, &QRadioButton::clicked, [ = ](bool checked) {
|
||||
if(checked) setWebEngine("baidu");
|
||||
});
|
||||
connect(m_sougouBtn, &QRadioButton::clicked, [ = ](bool checked) {
|
||||
if(checked) setWebEngine("sougou");
|
||||
});
|
||||
connect(m_360Btn, &QRadioButton::clicked, [ = ](bool checked) {
|
||||
if(checked) setWebEngine("360");
|
||||
});
|
||||
|
||||
m_contentLyt->addWidget(m_searchEngineLabel);
|
||||
m_contentLyt->addWidget(m_engineDescLabel);
|
||||
m_contentLyt->addWidget(m_radioBtnFrame);
|
||||
|
||||
//取消与确认按钮 (隐藏)
|
||||
// m_bottomBtnFrame = new QFrame(this);
|
||||
// m_bottomBtnLyt = new QHBoxLayout(m_bottomBtnFrame);
|
||||
// m_bottomBtnFrame->setLayout(m_bottomBtnLyt);
|
||||
// m_bottomBtnLyt->setSpacing(20);
|
||||
// m_cancelBtn = new QPushButton(m_bottomBtnFrame);
|
||||
// m_cancelBtn->setText(tr("Cancel"));
|
||||
// m_cancelBtn->setFixedSize(80, 32);
|
||||
// connect(m_cancelBtn, &QPushButton::clicked, this, &SettingsWidget::onBtnCancelClicked);
|
||||
// m_confirmBtn = new QPushButton(m_bottomBtnFrame);
|
||||
// m_confirmBtn->setText(tr("Confirm"));
|
||||
// m_confirmBtn->setFixedSize(80, 32);
|
||||
// connect(m_confirmBtn, &QPushButton::clicked, this, &SettingsWidget::onBtnConfirmClicked);
|
||||
// m_bottomBtnLyt->addStretch();
|
||||
// m_bottomBtnLyt->addWidget(m_cancelBtn);
|
||||
// m_bottomBtnLyt->addWidget(m_confirmBtn);
|
||||
// m_mainLyt->addWidget(m_bottomBtnFrame);
|
||||
|
||||
m_contentLyt->addStretch();
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0))
|
||||
this->m_titleFrame->hide();
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::setupBlackList 创建黑名单列表
|
||||
* @param list 文件夹路径列表
|
||||
*/
|
||||
void SettingsWidget::setupBlackList(const QStringList& list) {
|
||||
clearLayout(m_dirListLyt);
|
||||
m_blockdirs = 0;
|
||||
Q_FOREACH(QString path, list) {
|
||||
FolderListItem * item = new FolderListItem(m_dirListWidget, path);
|
||||
m_dirListLyt->addWidget(item);
|
||||
item->setMaximumWidth(this->width() - 52);
|
||||
connect(item, &FolderListItem::onDelBtnClicked, this, &SettingsWidget::onBtnDelClicked);
|
||||
m_blockdirs ++;
|
||||
}
|
||||
this->resize();
|
||||
m_dirListWidget->setFixedWidth(this->width() - 68);
|
||||
// m_dirListLyt->addStretch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::clearLayout 清空某个布局
|
||||
* @param layout 需要清空的布局
|
||||
*/
|
||||
void SettingsWidget::clearLayout(QLayout * layout) {
|
||||
if(! layout) return;
|
||||
QLayoutItem * child;
|
||||
while((child = layout->takeAt(0)) != 0) {
|
||||
if(child->widget()) {
|
||||
child->widget()->setParent(NULL);
|
||||
}
|
||||
delete child;
|
||||
}
|
||||
child = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::refreshIndexState 定时刷新索引项
|
||||
*/
|
||||
void SettingsWidget::refreshIndexState() {
|
||||
// qDebug()<<"FileUtils::indexStatus: "<<FileUtils::indexStatus;
|
||||
if(FileUtils::indexStatus != 0) {
|
||||
this->setIndexState(true);
|
||||
} else {
|
||||
this->setIndexState(false);
|
||||
}
|
||||
m_indexNumLabel->setText(QString("%1/%2").arg(QString::number(SearchManager::getCurrentIndexCount())).arg(QString::number(FileUtils::maxIndexCount)));
|
||||
m_timer = new QTimer;
|
||||
connect(m_timer, &QTimer::timeout, this, [ = ]() {
|
||||
qDebug() << "FileUtils::indexStatus: " << FileUtils::indexStatus;
|
||||
if(FileUtils::indexStatus != 0) {
|
||||
this->setIndexState(true);
|
||||
} else {
|
||||
this->setIndexState(false);
|
||||
}
|
||||
m_indexNumLabel->setText(QString("%1/%2").arg(QString::number(SearchManager::getCurrentIndexCount())).arg(QString::number(FileUtils::maxIndexCount)));
|
||||
});
|
||||
m_timer->start(0.5 * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::onBtnDelClicked 删除黑名单中的目录
|
||||
* @param path 文件夹路径
|
||||
*/
|
||||
void SettingsWidget::onBtnDelClicked(const QString& path) {
|
||||
QMessageBox message(QMessageBox::Question, tr("Search"), tr("Whether to delete this directory?"));
|
||||
QPushButton * buttonYes = message.addButton(tr("Yes"), QMessageBox::YesRole);
|
||||
message.addButton(tr("No"), QMessageBox::NoRole);
|
||||
message.exec();
|
||||
if(message.clickedButton() != buttonYes) {
|
||||
return;
|
||||
}
|
||||
|
||||
int returnCode = 0;
|
||||
if(GlobalSettings::getInstance()->setBlockDirs(path, returnCode, true)) {
|
||||
qDebug() << "Remove block dir in onBtnDelClicked() successed.";
|
||||
Q_FOREACH(FolderListItem * item, m_dirListWidget->findChildren<FolderListItem*>()) {
|
||||
if(item->getPath() == path) {
|
||||
item->deleteLater();
|
||||
item = NULL;
|
||||
m_blockdirs --;
|
||||
this->resize();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showWarningDialog(returnCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::resetWebEngine 获取当前的搜索引擎并反映在UI控件上
|
||||
*/
|
||||
void SettingsWidget::resetWebEngine() {
|
||||
QString engine = GlobalSettings::getInstance()->getValue(WEB_ENGINE).toString();
|
||||
m_engineBtnGroup->blockSignals(true);
|
||||
if(!engine.isEmpty()) {
|
||||
if(engine == "360") {
|
||||
m_360Btn->setChecked(true);
|
||||
} else if(engine == "sougou") {
|
||||
m_sougouBtn->setChecked(true);
|
||||
} else {
|
||||
m_baiduBtn->setChecked(true);
|
||||
}
|
||||
} else {
|
||||
m_baiduBtn->setChecked(true);
|
||||
}
|
||||
m_engineBtnGroup->blockSignals(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::setWebEngine
|
||||
* @param engine 选择的搜索引擎
|
||||
*/
|
||||
void SettingsWidget::setWebEngine(const QString& engine) {
|
||||
// GlobalSettings::getInstance()->setValue(WEB_ENGINE, engine);
|
||||
Q_EMIT this->webEngineChanged(engine);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief setIndexState 设置当前索引状态
|
||||
* @param isCreatingIndex 是否正在创建索引
|
||||
*/
|
||||
void SettingsWidget::setIndexState(bool isCreatingIndex) {
|
||||
if(isCreatingIndex) {
|
||||
m_indexStateLabel->setText(tr("Creating ..."));
|
||||
return;
|
||||
}
|
||||
m_indexStateLabel->setText(tr("Done"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::setIndexNum 设置当前索引项数量
|
||||
* @param num 索引项数量
|
||||
*/
|
||||
void SettingsWidget::setIndexNum(int num) {
|
||||
m_indexNumLabel->setText(QString(tr("Index Entry: %1")).arg(QString::number(num)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::showWidget 显示此窗口
|
||||
*/
|
||||
void SettingsWidget::showWidget() {
|
||||
Qt::WindowFlags flags = this->windowFlags();
|
||||
flags |= Qt::WindowStaysOnTopHint;
|
||||
this->setWindowFlags(flags);
|
||||
flags &= ~Qt::WindowStaysOnTopHint;
|
||||
this->setWindowFlags(flags);
|
||||
m_timer->start();
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
XAtomHelper::getInstance()->setWindowMotifHint(winId(), m_hints);
|
||||
#endif
|
||||
this->show();
|
||||
}
|
||||
|
||||
///**
|
||||
// * @brief SettingsWidget::onBtnConfirmClicked 点击确认按钮的槽函数
|
||||
// */
|
||||
//void SettingsWidget::onBtnConfirmClicked() {
|
||||
// Q_EMIT this->settingWidgetClosed();
|
||||
// m_timer->stop();
|
||||
// this->close();
|
||||
//}
|
||||
|
||||
///**
|
||||
// * @brief SettingsWidget::onBtnCancelClicked 点击取消按钮的槽函数
|
||||
// */
|
||||
//void SettingsWidget::onBtnCancelClicked() {
|
||||
// Q_EMIT this->settingWidgetClosed();
|
||||
// m_timer->stop();
|
||||
// this->close();
|
||||
//}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::onBtnAddClicked 点击添加黑名单按钮的槽函数
|
||||
*/
|
||||
void SettingsWidget::onBtnAddClicked() {
|
||||
QFileDialog * fileDialog = new QFileDialog(this);
|
||||
// fileDialog->setFileMode(QFileDialog::Directory); //允许查看文件和文件夹,但只允许选择文件夹
|
||||
fileDialog->setFileMode(QFileDialog::DirectoryOnly); //只允许查看文件夹
|
||||
// fileDialog->setViewMode(QFileDialog::Detail);
|
||||
fileDialog->setDirectory(QDir::homePath());
|
||||
fileDialog->setNameFilter(tr("Directories"));
|
||||
fileDialog->setWindowTitle(tr("select blocked folder"));
|
||||
fileDialog->setLabelText(QFileDialog::Accept, tr("Select"));
|
||||
fileDialog->setLabelText(QFileDialog::LookIn, tr("Position: "));
|
||||
fileDialog->setLabelText(QFileDialog::FileName, tr("FileName: "));
|
||||
fileDialog->setLabelText(QFileDialog::FileType, tr("FileType: "));
|
||||
fileDialog->setLabelText(QFileDialog::Reject, tr("Cancel"));
|
||||
if(fileDialog->exec() != QDialog::Accepted) {
|
||||
fileDialog->deleteLater();
|
||||
return;
|
||||
}
|
||||
QString selectedDir = 0;
|
||||
int returnCode;
|
||||
selectedDir = fileDialog->selectedFiles().first();
|
||||
qDebug() << "Selected a folder in onBtnAddClicked(): " << selectedDir << ". ->settings-widget.cpp #238";
|
||||
if(GlobalSettings::getInstance()->setBlockDirs(selectedDir, returnCode)) {
|
||||
setupBlackList(GlobalSettings::getInstance()->getBlockDirs());
|
||||
qDebug() << "Add block dir in onBtnAddClicked() successed. ->settings-widget.cpp #238";
|
||||
} else {
|
||||
showWarningDialog(returnCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::paintEvent 绘制弹窗阴影
|
||||
* @param event
|
||||
*/
|
||||
void SettingsWidget::paintEvent(QPaintEvent *event) {
|
||||
Q_UNUSED(event)
|
||||
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
QPainterPath rectPath;
|
||||
rectPath.addRect(this->rect());
|
||||
|
||||
// 绘制一个背景
|
||||
p.save();
|
||||
p.fillPath(rectPath, palette().color(QPalette::Base));
|
||||
p.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::resize 重新计算窗口应有大小
|
||||
*/
|
||||
void SettingsWidget::resize() {
|
||||
// if (m_blockdirs <= 1) {
|
||||
// this->setFixedSize(528, 455);
|
||||
// } else if (m_blockdirs <= 3) {
|
||||
// this->setFixedSize(528, 425 + 30 * m_blockdirs);
|
||||
// } else {
|
||||
// this->setFixedSize(528, 515);
|
||||
// }
|
||||
if(m_blockdirs <= 4) {
|
||||
m_dirListArea->setFixedHeight(32 * m_blockdirs + 4);
|
||||
m_dirListWidget->setFixedHeight(32 * m_blockdirs);
|
||||
} else {
|
||||
m_dirListWidget->setFixedHeight(32 * m_blockdirs);
|
||||
m_dirListArea->setFixedHeight(32 * 4 + 4);
|
||||
}
|
||||
this->setFixedSize(528, 410 + m_dirListArea->height());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SettingsWidget::showWarningDialog 显示警告弹窗
|
||||
* @param errorCode 错误码
|
||||
*/
|
||||
void SettingsWidget::showWarningDialog(const int & errorCode) {
|
||||
qWarning() << "Add block dir in onBtnAddClicked() failed. Code: " << errorCode << " ->settings-widget.cpp #238";
|
||||
QString errorMessage;
|
||||
switch(errorCode) {
|
||||
case 1: {
|
||||
errorMessage = tr("Choosen path is Empty!");
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
errorMessage = tr("Choosen path is not in \"home\"!");
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
errorMessage = tr("Its' parent folder has been blocked!");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
errorMessage = tr("Set blocked folder failed!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
QMessageBox message(QMessageBox::Warning, tr("Search"), errorMessage);
|
||||
message.addButton(tr("OK"), QMessageBox::AcceptRole);
|
||||
message.exec();
|
||||
}
|
||||
|
||||
|
||||
FolderListItem::FolderListItem(QWidget *parent, const QString &path) : QWidget(parent) {
|
||||
m_path = path;
|
||||
initUi();
|
||||
}
|
||||
|
||||
FolderListItem::~FolderListItem() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FolderListItem::initUi 构建ui
|
||||
*/
|
||||
void FolderListItem::initUi() {
|
||||
m_layout = new QVBoxLayout(this);
|
||||
m_layout->setSpacing(0);
|
||||
m_layout->setContentsMargins(0, 0, 0, 0);
|
||||
m_widget = new QWidget(this);
|
||||
m_widget->setObjectName("mWidget");
|
||||
this->setFixedHeight(32);
|
||||
m_layout->addWidget(m_widget);
|
||||
m_widgetlayout = new QHBoxLayout(m_widget);
|
||||
m_widgetlayout->setContentsMargins(8, 4, 8, 4);
|
||||
m_widget->setLayout(m_widgetlayout);
|
||||
|
||||
m_iconLabel = new QLabel(m_widget);
|
||||
m_pathLabel = new QLabel(m_widget);
|
||||
m_delLabel = new QLabel(m_widget);
|
||||
m_iconLabel->setPixmap(QIcon::fromTheme("inode-directory").pixmap(QSize(16, 16)));
|
||||
m_pathLabel->setText(m_path);
|
||||
m_delLabel->setText(tr("Delete the folder out of blacklist"));
|
||||
QPalette pal = palette();
|
||||
pal.setColor(QPalette::WindowText, QColor(55, 144, 250, 255));
|
||||
m_delLabel->setPalette(pal);
|
||||
m_delLabel->setCursor(QCursor(Qt::PointingHandCursor));
|
||||
m_delLabel->installEventFilter(this);
|
||||
m_delLabel->hide();
|
||||
m_widgetlayout->addWidget(m_iconLabel);
|
||||
m_widgetlayout->addWidget(m_pathLabel);
|
||||
m_widgetlayout->addStretch();
|
||||
m_widgetlayout->addWidget(m_delLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FolderListItem::getPath 获取当前文件夹路径
|
||||
* @return
|
||||
*/
|
||||
QString FolderListItem::getPath() {
|
||||
return m_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FolderListItem::enterEvent 鼠标移入事件
|
||||
* @param event
|
||||
*/
|
||||
void FolderListItem::enterEvent(QEvent *event) {
|
||||
m_delLabel->show();
|
||||
m_widget->setStyleSheet("QWidget#mWidget{background: rgba(0,0,0,0.1);}");
|
||||
QWidget::enterEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FolderListItem::leaveEvent 鼠标移出事件
|
||||
* @param event
|
||||
*/
|
||||
void FolderListItem::leaveEvent(QEvent *event) {
|
||||
m_delLabel->hide();
|
||||
m_widget->setStyleSheet("QWidget#mWidget{background: transparent;}");
|
||||
QWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief FolderListItem::eventFilter 处理删除按钮点击事件
|
||||
* @param watched
|
||||
* @param event
|
||||
* @return
|
||||
*/
|
||||
bool FolderListItem::eventFilter(QObject *watched, QEvent *event) {
|
||||
if(watched == m_delLabel) {
|
||||
if(event->type() == QEvent::MouseButtonPress) {
|
||||
// qDebug()<<"pressed!";
|
||||
Q_EMIT this->onDelBtnClicked(m_path);
|
||||
}
|
||||
}
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#ifndef SETTINGSWIDGET_H
|
||||
#define SETTINGSWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QDialog>
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QRadioButton>
|
||||
#include <QButtonGroup>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QTimer>
|
||||
#include <libsearch.h>
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
#include "xatom-helper.h"
|
||||
#endif
|
||||
|
||||
namespace UkuiSearch {
|
||||
class FolderListItem : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FolderListItem(QWidget *parent = nullptr, const QString &path = 0);
|
||||
~FolderListItem();
|
||||
QString getPath();
|
||||
|
||||
protected:
|
||||
virtual void enterEvent(QEvent *);
|
||||
virtual void leaveEvent(QEvent *);
|
||||
bool eventFilter(QObject *, QEvent *);
|
||||
|
||||
private:
|
||||
void initUi();
|
||||
|
||||
QString m_path;
|
||||
|
||||
QVBoxLayout * m_layout = nullptr;
|
||||
QWidget * m_widget = nullptr;
|
||||
QHBoxLayout * m_widgetlayout = nullptr;
|
||||
QLabel * m_iconLabel = nullptr;
|
||||
QLabel * m_pathLabel = nullptr;
|
||||
QLabel * m_delLabel = nullptr;
|
||||
Q_SIGNALS:
|
||||
void onDelBtnClicked(const QString&);
|
||||
};
|
||||
|
||||
class SettingsWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SettingsWidget(QWidget *parent = nullptr);
|
||||
~SettingsWidget();
|
||||
|
||||
void setIndexState(bool);
|
||||
void setIndexNum(int);
|
||||
void showWidget();
|
||||
void resetWebEngine();
|
||||
|
||||
private:
|
||||
void initUi();
|
||||
void setupBlackList(const QStringList &);
|
||||
void clearLayout(QLayout *);
|
||||
void refreshIndexState();
|
||||
void paintEvent(QPaintEvent *);
|
||||
void resize();
|
||||
void showWarningDialog(const int&);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
MotifWmHints m_hints;
|
||||
#endif
|
||||
|
||||
//标题栏
|
||||
QVBoxLayout * m_mainLyt = nullptr;
|
||||
QFrame * m_contentFrame = nullptr;
|
||||
QVBoxLayout * m_contentLyt = nullptr;
|
||||
QFrame * m_titleFrame = nullptr;
|
||||
QHBoxLayout * m_titleLyt = nullptr;
|
||||
QLabel * m_titleIcon = nullptr;
|
||||
QLabel * m_titleLabel = nullptr;
|
||||
QPushButton * m_closeBtn = nullptr;
|
||||
|
||||
//设置
|
||||
QLabel * m_settingLabel = nullptr;
|
||||
//文件索引
|
||||
QLabel * m_indexTitleLabel = nullptr;
|
||||
QLabel * m_indexStateLabel = nullptr;
|
||||
QLabel * m_indexNumLabel = nullptr;
|
||||
//文件索引设置(黑名单)
|
||||
QLabel * m_indexSettingLabel = nullptr;
|
||||
QLabel * m_indexDescLabel = nullptr;
|
||||
QFrame * m_indexBtnFrame = nullptr;
|
||||
QHBoxLayout * m_indexBtnLyt = nullptr;
|
||||
QPushButton * m_addDirBtn = nullptr;
|
||||
QScrollArea * m_dirListArea = nullptr;
|
||||
QWidget * m_dirListWidget = nullptr;
|
||||
QVBoxLayout * m_dirListLyt = nullptr;
|
||||
|
||||
//搜索引擎设置
|
||||
QLabel * m_searchEngineLabel = nullptr;
|
||||
QLabel * m_engineDescLabel = nullptr;
|
||||
QButtonGroup * m_engineBtnGroup = nullptr;
|
||||
QFrame * m_radioBtnFrame = nullptr;
|
||||
QHBoxLayout * m_radioBtnLyt = nullptr;
|
||||
QRadioButton * m_baiduBtn = nullptr;
|
||||
QLabel * m_baiduLabel = nullptr;
|
||||
QRadioButton * m_sougouBtn = nullptr;
|
||||
QLabel * m_sougouLabel = nullptr;
|
||||
QRadioButton * m_360Btn = nullptr;
|
||||
QLabel * m_360Label = nullptr;
|
||||
|
||||
//取消与确认按钮
|
||||
QFrame * m_bottomBtnFrame = nullptr;
|
||||
QHBoxLayout * m_bottomBtnLyt = nullptr;
|
||||
QPushButton * m_cancelBtn = nullptr;
|
||||
QPushButton * m_confirmBtn = nullptr;
|
||||
|
||||
QTimer * m_timer;
|
||||
|
||||
int m_blockdirs = 0; //黑名单文件夹数量
|
||||
|
||||
Q_SIGNALS:
|
||||
void settingWidgetClosed();
|
||||
void webEngineChanged(const QString&);
|
||||
|
||||
private Q_SLOTS:
|
||||
// void onBtnConfirmClicked();
|
||||
// void onBtnCancelClicked();
|
||||
void onBtnAddClicked();
|
||||
void onBtnDelClicked(const QString&);
|
||||
void setWebEngine(const QString&);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SETTINGSWIDGET_H
|
|
@ -0,0 +1,773 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "search-page-section.h"
|
||||
#include <QDebug>
|
||||
#include <QScrollBar>
|
||||
using namespace UkuiSearch;
|
||||
|
||||
#define RESULT_LAYOUT_MARGINS 0,0,0,0
|
||||
#define RESULT_BACKGROUND_COLOR QColor(0, 0, 0, 0)
|
||||
#define DETAIL_BACKGROUND_COLOR QColor(0, 0, 0, 0)
|
||||
#define DETAIL_WIDGET_TRANSPARENT 0.04
|
||||
#define DETAIL_WIDGET_BORDER_RADIUS 4
|
||||
#define DETAIL_WIDGET_MARGINS 8,0,8,0
|
||||
#define DETAIL_FRAME_MARGINS 8,0,0,0
|
||||
#define DETAIL_ICON_HEIGHT 120
|
||||
#define NAME_LABEL_WIDTH 280
|
||||
#define ICON_SIZE QSize(120, 120)
|
||||
#define LINE_STYLE "QFrame{background: rgba(0,0,0,0.2);}"
|
||||
#define ACTION_NORMAL_COLOR QColor(55, 144, 250, 255)
|
||||
#define ACTION_HOVER_COLOR QColor(64, 169, 251, 255)
|
||||
#define ACTION_PRESS_COLOR QColor(41, 108, 217, 255)
|
||||
#define TITLE_HEIGHT 30
|
||||
#define FRAME_HEIGHT 516
|
||||
#define DETAIL_FRAME_WIDTH 376
|
||||
|
||||
ResultArea::ResultArea(QWidget *parent) : QScrollArea(parent)
|
||||
{
|
||||
qRegisterMetaType<SearchPluginIface::ResultInfo>("SearchPluginIface::ResultInfo");
|
||||
this->viewport()->setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
initUi();
|
||||
initConnections();
|
||||
}
|
||||
|
||||
void ResultArea::appendWidet(ResultWidget *widget)
|
||||
{
|
||||
m_mainLyt->addWidget(widget);
|
||||
setupConnectionsForWidget(widget);
|
||||
widget->clearResult();
|
||||
m_widget_list.append(widget);
|
||||
int spacing_height = m_widget_list.length() > 1 ? m_mainLyt->spacing() : 0;
|
||||
m_widget->setFixedHeight(m_widget->height() + widget->height() + spacing_height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ResultArea::setVisibleList 设置哪些插件可见,默认全部可见
|
||||
* @param list
|
||||
*/
|
||||
void ResultArea::setVisibleList(const QStringList &list)
|
||||
{
|
||||
Q_FOREACH (auto widget, m_widget_list) {
|
||||
if (list.contains(widget->pluginId())) {
|
||||
widget->setEnabled(true);
|
||||
} else {
|
||||
widget->setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResultArea::pressEnter()
|
||||
{
|
||||
if (false == m_is_selected) {//未选中时默认选取bestlist第一项
|
||||
int resultNum = m_bestListWidget->getResultNum();
|
||||
if (0 == resultNum) {//无搜索结果时默认选中websearch
|
||||
for (ResultWidget * i : m_widget_list) {
|
||||
if (m_selectedPluginID == m_widget_list.back()->pluginId()) {
|
||||
QModelIndex index = i->getModlIndex(0, 0);
|
||||
i->setResultSelection(index);
|
||||
m_selectedPluginID = i->pluginId();
|
||||
m_is_selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {//选取bestlist第一项
|
||||
QModelIndex index = m_bestListWidget->getModlIndex(0, 0);
|
||||
m_bestListWidget->setResultSelection(index);
|
||||
m_selectedPluginID = m_bestListWidget->getWidgetName();
|
||||
m_is_selected = true;
|
||||
}
|
||||
} else {//选中状态时默认启动action首项
|
||||
//先判断详情页是否打开
|
||||
if (m_detail_open_state) {
|
||||
if (m_selectedPluginID == m_bestListWidget->getWidgetName()) {//最佳匹配
|
||||
m_bestListWidget->activateIndex();
|
||||
} else {
|
||||
for (ResultWidget * i : m_widget_list) {
|
||||
if (m_selectedPluginID == i->pluginId()) {
|
||||
i->activateIndex();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {//打开详情页
|
||||
m_detail_open_state = true;
|
||||
sendKeyPressSignal(m_selectedPluginID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResultArea::pressDown()
|
||||
{
|
||||
if (m_selectedPluginID == m_widget_list.back()->pluginId()) {//当前为web search,暂不处理
|
||||
return;
|
||||
} else if (m_selectedPluginID == m_bestListWidget->getWidgetName()) {
|
||||
QModelIndex index = m_bestListWidget->getCurrentSelection();
|
||||
int maxNum = m_bestListWidget->getExpandState() ?
|
||||
m_bestListWidget->getResultNum() : (m_bestListWidget->getResultNum() < NUM_LIMIT_SHOWN_DEFAULT ?
|
||||
m_bestListWidget->getResultNum() : NUM_LIMIT_SHOWN_DEFAULT);
|
||||
if (index.row() < maxNum - 1 and index.row() >= 0) {
|
||||
int row = index.row();
|
||||
QModelIndex setIndex = m_bestListWidget->getModlIndex(++row, 0);
|
||||
m_bestListWidget->setResultSelection(setIndex);
|
||||
sendKeyPressSignal(m_selectedPluginID);
|
||||
} else if (index.row() >= maxNum - 1 or index.row() < 0) {//跳转下一个widget
|
||||
m_bestListWidget->clearResultSelection();
|
||||
for (ResultWidget * plugin : m_widget_list) {
|
||||
if (plugin->getResultNum() != 0) {
|
||||
QModelIndex resultIndex = plugin->getModlIndex(0, 0);
|
||||
plugin->setResultSelection(resultIndex);
|
||||
m_selectedPluginID = plugin->pluginId();
|
||||
sendKeyPressSignal(m_selectedPluginID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qWarning() << "QModelIndex error ! row:" << index.row() << "maxNum:" << maxNum;
|
||||
}
|
||||
} else {
|
||||
for (ResultWidget * plugin : m_widget_list) {
|
||||
if (m_selectedPluginID == plugin->pluginId()) {
|
||||
QModelIndex index = plugin->getCurrentSelection();
|
||||
int maxNum = plugin->getExpandState() ?
|
||||
plugin->getResultNum() : (plugin->getResultNum() < NUM_LIMIT_SHOWN_DEFAULT ?
|
||||
plugin->getResultNum() : NUM_LIMIT_SHOWN_DEFAULT);
|
||||
if (index.row() < maxNum - 1 and index.row() >= 0) {
|
||||
int row = index.row();
|
||||
QModelIndex setIndex = plugin->getModlIndex(++row, 0);
|
||||
plugin->setResultSelection(setIndex);
|
||||
sendKeyPressSignal(m_selectedPluginID);
|
||||
} else if (index.row() >= maxNum - 1 or index.row() < 0) {//跳转下一个widget
|
||||
plugin->clearResultSelection();
|
||||
int indexNum = m_widget_list.indexOf(plugin);
|
||||
bool findNextWidget = false;
|
||||
while (++indexNum < m_widget_list.size()) {
|
||||
plugin = m_widget_list[indexNum];
|
||||
if (plugin->getResultNum() != 0) {
|
||||
QModelIndex resultIndex = plugin->getModlIndex(0, 0);
|
||||
plugin->setResultSelection(resultIndex);
|
||||
m_selectedPluginID = plugin->pluginId();
|
||||
findNextWidget = true;
|
||||
sendKeyPressSignal(m_selectedPluginID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (findNextWidget){
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "QModelIndex error ! row:" << index.row() << "maxNum:" << maxNum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResultArea::pressUp()
|
||||
{
|
||||
if (!m_is_selected) {
|
||||
return;
|
||||
}
|
||||
if (m_selectedPluginID == m_bestListWidget->getWidgetName()) {
|
||||
QModelIndex index = m_bestListWidget->getCurrentSelection();
|
||||
int maxNum = m_bestListWidget->getExpandState() ?
|
||||
m_bestListWidget->getResultNum() : (m_bestListWidget->getResultNum() < NUM_LIMIT_SHOWN_DEFAULT ?
|
||||
m_bestListWidget->getResultNum() : NUM_LIMIT_SHOWN_DEFAULT);
|
||||
if (index.row() > 0 and index.row() < maxNum) {
|
||||
int row = index.row();
|
||||
QModelIndex setIndex = m_bestListWidget->getModlIndex(--row, 0);
|
||||
m_bestListWidget->setResultSelection(setIndex);
|
||||
sendKeyPressSignal(m_selectedPluginID);
|
||||
} else if (index.row() == 0) {
|
||||
//已到最上层,暂不处理
|
||||
} else {
|
||||
QModelIndex setIndex = m_bestListWidget->getModlIndex(--maxNum, 0);
|
||||
m_bestListWidget->setResultSelection(setIndex);
|
||||
sendKeyPressSignal(m_selectedPluginID);
|
||||
}
|
||||
} else {
|
||||
for (ResultWidget * plugin : m_widget_list) {
|
||||
if (m_selectedPluginID == plugin->pluginId()) {
|
||||
int indexMaxNum = plugin->getExpandState() ?
|
||||
plugin->getResultNum() : (plugin->getResultNum() < NUM_LIMIT_SHOWN_DEFAULT ?
|
||||
plugin->getResultNum() : NUM_LIMIT_SHOWN_DEFAULT);
|
||||
QModelIndex index = plugin->getCurrentSelection();
|
||||
if (index.row() > 0 and index.row() < indexMaxNum) {
|
||||
int row = index.row();
|
||||
QModelIndex setIndex = plugin->getModlIndex(--row, 0);
|
||||
plugin->setResultSelection(setIndex);
|
||||
sendKeyPressSignal(m_selectedPluginID);
|
||||
} else if (index.row() == 0) {//跳转下一个widget
|
||||
plugin->clearResultSelection();
|
||||
int indexNum = m_widget_list.indexOf(plugin);
|
||||
bool findNextWidget = false;
|
||||
while (--indexNum >= 0) {
|
||||
plugin = m_widget_list[indexNum];
|
||||
if (plugin->getResultNum() != 0) {
|
||||
int maxNum = plugin->getExpandState() ?
|
||||
plugin->getResultNum() : (plugin->getResultNum() < NUM_LIMIT_SHOWN_DEFAULT ?
|
||||
plugin->getResultNum() : NUM_LIMIT_SHOWN_DEFAULT);
|
||||
QModelIndex resultIndex = plugin->getModlIndex(maxNum - 1, 0);
|
||||
plugin->setResultSelection(resultIndex);
|
||||
m_selectedPluginID = plugin->pluginId();
|
||||
findNextWidget = true;
|
||||
sendKeyPressSignal(m_selectedPluginID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (indexNum < 0) {//下一项是best list
|
||||
int bestListNum = m_bestListWidget->getExpandState() ?
|
||||
m_bestListWidget->getResultNum() : (m_bestListWidget->getResultNum() < NUM_LIMIT_SHOWN_DEFAULT ?
|
||||
m_bestListWidget->getResultNum() : NUM_LIMIT_SHOWN_DEFAULT);
|
||||
QModelIndex setIndex = m_bestListWidget->getModlIndex(--bestListNum, 0);
|
||||
m_bestListWidget->setResultSelection(setIndex);
|
||||
m_selectedPluginID = m_bestListWidget->getWidgetName();
|
||||
m_is_selected = true;
|
||||
sendKeyPressSignal(m_selectedPluginID);
|
||||
}
|
||||
if (findNextWidget){
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
QModelIndex setIndex = plugin->getModlIndex(indexMaxNum - 1, 0);
|
||||
plugin->setResultSelection(setIndex);
|
||||
sendKeyPressSignal(m_selectedPluginID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ResultArea::getSelectedState()
|
||||
{
|
||||
return m_is_selected;
|
||||
}
|
||||
|
||||
void ResultArea::sendKeyPressSignal(QString &pluginID)
|
||||
{
|
||||
int height(0);
|
||||
int resultHeight = m_bestListWidget->getResultHeight();
|
||||
if (pluginID == m_bestListWidget->getWidgetName()) {
|
||||
QModelIndex index = m_bestListWidget->getCurrentSelection();
|
||||
height = index.row() == 0 ? 0 : index.row() * resultHeight + TITLE_HEIGHT;
|
||||
height = (height - resultHeight) < 0 ? 0 : height - resultHeight;
|
||||
this->ensureVisible(0, height, 0, 0);
|
||||
if (m_detail_open_state) {
|
||||
Q_EMIT this->keyPressChanged(m_bestListWidget->getPluginInfo(index), m_bestListWidget->getIndexResultInfo(index));
|
||||
}
|
||||
} else {
|
||||
height += m_bestListWidget->height();
|
||||
for (ResultWidget *plugin : m_widget_list) {
|
||||
if (pluginID == plugin->pluginId()) {
|
||||
QModelIndex index = plugin->getCurrentSelection();
|
||||
height += index.row() == 0 ? 0 : index.row() * resultHeight + TITLE_HEIGHT;
|
||||
int moreHeight = index.row() == 0 ? (TITLE_HEIGHT + resultHeight * 2) : (resultHeight * 2);
|
||||
this->ensureVisible(0, height + moreHeight, 0, 0);
|
||||
height = (height - resultHeight) < 0 ? 0 : height - resultHeight;
|
||||
this->ensureVisible(0, height, 0, 0);
|
||||
if (m_detail_open_state) {
|
||||
Q_EMIT this->keyPressChanged(m_selectedPluginID, plugin->getIndexResultInfo(index));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
height += plugin->height();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResultArea::onWidgetSizeChanged()
|
||||
{
|
||||
int whole_height = 0;
|
||||
Q_FOREACH (ResultWidget *widget, m_widget_list) {
|
||||
whole_height += widget->height();
|
||||
}
|
||||
whole_height += m_bestListWidget->height();
|
||||
|
||||
int spacing_height = m_widget_list.length() > 1 ? m_mainLyt->spacing() : 0;
|
||||
m_widget->setFixedHeight(whole_height + spacing_height * (m_widget_list.length() - 1));
|
||||
Q_EMIT this->resizeHeight(whole_height + spacing_height * (m_widget_list.length() - 1));
|
||||
}
|
||||
|
||||
void ResultArea::setSelectionInfo(QString &pluginID)
|
||||
{
|
||||
m_detail_open_state = true;
|
||||
m_is_selected = true;
|
||||
m_selectedPluginID = pluginID;
|
||||
if (m_selectedPluginID != m_bestListWidget->getWidgetName()) {
|
||||
m_bestListWidget->clearResultSelection();
|
||||
}
|
||||
}
|
||||
|
||||
void ResultArea::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
// qDebug() << "mouse pressed" << event->source() << event->button();
|
||||
// m_pressPoint = event->pos();
|
||||
return QScrollArea::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void ResultArea::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
// if(m_pressPoint.isNull()) {
|
||||
// return QScrollArea::mouseMoveEvent(event);
|
||||
// }
|
||||
// int delta = ((event->pos().y() - m_pressPoint.y()) / this->widget()->height()) * verticalScrollBar()->maximum();
|
||||
// this->verticalScrollBar()->setValue(verticalScrollBar()->value() + delta);
|
||||
// event->accept();
|
||||
return QScrollArea::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void ResultArea::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
return QScrollArea::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
bool ResultArea::viewportEvent(QEvent *event)
|
||||
{
|
||||
if(event->type() == QEvent::TouchBegin) {
|
||||
QTouchEvent *e = dynamic_cast<QTouchEvent *>(event);
|
||||
if(e->touchPoints().size() == 1) {
|
||||
m_pressPoint = m_widget->mapFrom(this, e->touchPoints().at(0).pos().toPoint());
|
||||
event->accept();
|
||||
return true;
|
||||
}
|
||||
} else if (event->type() == QEvent::TouchUpdate) {
|
||||
QTouchEvent *e = dynamic_cast<QTouchEvent *>(event);
|
||||
// qDebug() << "touchpoint===========" << e->touchPoints().size();
|
||||
if(e->touchPoints().size() == 1) {
|
||||
int delta = m_pressPoint.y() - m_widget->mapFrom(this, e->touchPoints().at(0).pos().toPoint()).y();
|
||||
// qDebug() << "last pos:" << m_pressPoint.y();
|
||||
// qDebug() << "new pos:" << m_widget->mapFrom(this, e->touchPoints().at(0).pos().toPoint()).y();
|
||||
// qDebug() << "delta" << delta;
|
||||
// qDebug() << "height" << m_widget->height() << "--" << verticalScrollBar()->maximum();
|
||||
this->verticalScrollBar()->setValue(verticalScrollBar()->value() + delta);
|
||||
m_pressPoint = m_widget->mapFrom(this,e->touchPoints().at(0).pos().toPoint());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QScrollArea::viewportEvent(event);
|
||||
}
|
||||
|
||||
void ResultArea::initUi()
|
||||
{
|
||||
// this->verticalScrollBar()->setProperty("drawScrollBarGroove", false);
|
||||
QPalette pal = palette();
|
||||
QPalette scroll_bar_pal = this->verticalScrollBar()->palette();
|
||||
// pal.setColor(QPalette::Base, RESULT_BACKGROUND_COLOR);
|
||||
pal.setColor(QPalette::Window, RESULT_BACKGROUND_COLOR);
|
||||
scroll_bar_pal.setColor(QPalette::Base, RESULT_BACKGROUND_COLOR);
|
||||
this->verticalScrollBar()->setPalette(scroll_bar_pal);
|
||||
this->verticalScrollBar()->setProperty("drawScrollBarGroove", false);
|
||||
this->setFrameShape(QFrame::Shape::NoFrame);
|
||||
this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
this->setPalette(pal);
|
||||
this->setWidgetResizable(true);
|
||||
this->setFrameShape(QFrame::Shape::NoFrame);
|
||||
m_widget = new QWidget(this);
|
||||
this->setWidget(m_widget);
|
||||
m_mainLyt = new QVBoxLayout(m_widget);
|
||||
m_widget->setLayout(m_mainLyt);
|
||||
m_bestListWidget = new BestListWidget(this);
|
||||
m_bestListWidget->clearResult();
|
||||
m_mainLyt->addWidget(m_bestListWidget);
|
||||
|
||||
m_mainLyt->setContentsMargins(RESULT_LAYOUT_MARGINS);
|
||||
this->widget()->setContentsMargins(0,0,0,0);
|
||||
m_mainLyt->setSpacing(0);
|
||||
|
||||
m_titleLabel = new TitleLabel(this);
|
||||
m_titleLabel->hide();
|
||||
}
|
||||
|
||||
void ResultArea::initConnections()
|
||||
{
|
||||
connect(this, &ResultArea::startSearch, m_bestListWidget, &BestListWidget::startSearch);
|
||||
connect(this, &ResultArea::startSearch, this, [=] () {
|
||||
m_detail_open_state = false;
|
||||
m_is_selected = false;
|
||||
m_selectedPluginID = "";
|
||||
});
|
||||
connect(m_bestListWidget, &BestListWidget::sizeChanged, this, &ResultArea::onWidgetSizeChanged);
|
||||
connect(m_bestListWidget, &BestListWidget::sizeChanged, this, [=] () {
|
||||
QModelIndex index = m_bestListWidget->getModlIndex(0, 0);
|
||||
if (index.isValid()) {
|
||||
m_bestListWidget->setResultSelection(index);
|
||||
m_selectedPluginID = m_bestListWidget->getWidgetName();
|
||||
m_is_selected = true;
|
||||
|
||||
for (ResultWidget * i : m_widget_list) {
|
||||
if (i->pluginId() == m_widget_list.back()->pluginId()) {
|
||||
i->clearResultSelection();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_bestListWidget, &BestListWidget::currentRowChanged, this, &ResultArea::currentRowChanged);
|
||||
connect(m_bestListWidget, &BestListWidget::currentRowChanged, this, [=] () {
|
||||
m_detail_open_state = true;
|
||||
m_is_selected = true;
|
||||
m_selectedPluginID = m_bestListWidget->getWidgetName();
|
||||
});
|
||||
connect(this, &ResultArea::clearSelectedRow, m_bestListWidget, &BestListWidget::clearSelectedRow);
|
||||
connect(this, &ResultArea::resizeWidth, this, [=] (const int &size) {
|
||||
m_bestListWidget->setFixedWidth(size);
|
||||
});
|
||||
connect(m_bestListWidget, &BestListWidget::rowClicked, this, &ResultArea::rowClicked);
|
||||
connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this, [=] (int value) {//判断显示和隐藏逻辑
|
||||
Q_FOREACH(auto widget, m_widget_list) {
|
||||
if (!widget->getExpandState()) {
|
||||
continue;
|
||||
}
|
||||
if (value < (widget->pos().ry() + TITLE_HEIGHT)
|
||||
or value > (widget->pos().ry() + widget->height() - FRAME_HEIGHT + 2*TITLE_HEIGHT)) {//暂定下一项标题显示完全后悬浮标题隐藏
|
||||
if (!m_titleLabel->isHidden()) {
|
||||
m_titleLabel->hide();
|
||||
this->setViewportMargins(0,0,0,0);
|
||||
}
|
||||
} else {
|
||||
if (m_titleLabel->isHidden()) {
|
||||
m_titleLabel->setText(widget->pluginName());
|
||||
m_titleLabel->setShowMoreLableVisible();//防止多项展开后无法收回其他项
|
||||
m_titleLabel->show();
|
||||
this->setViewportMargins(0,TITLE_HEIGHT,0,0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(this->m_titleLabel, &TitleLabel::retractClicked, this, [=] () {
|
||||
Q_FOREACH(auto widget, m_widget_list) {
|
||||
if (widget->pluginName() == m_titleLabel->text()) {
|
||||
widget->reduceListSlot();
|
||||
widget->resetTitleLabel();
|
||||
if (!m_titleLabel->isHidden()) {
|
||||
m_titleLabel->hide();
|
||||
this->setViewportMargins(0,0,0,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(this, &ResultArea::resizeWidth, this, [=] (int size) {
|
||||
m_titleLabel->setFixedWidth(size);
|
||||
});
|
||||
}
|
||||
|
||||
void ResultArea::setupConnectionsForWidget(ResultWidget *widget)
|
||||
{
|
||||
connect(this, &ResultArea::startSearch, widget, &ResultWidget::startSearch);
|
||||
connect(this, &ResultArea::startSearch, this, [=] () {
|
||||
if (!m_titleLabel->isHidden()) {
|
||||
m_titleLabel->hide();
|
||||
this->setViewportMargins(0,0,0,0);
|
||||
}
|
||||
});
|
||||
connect(this, &ResultArea::stopSearch, widget, &ResultWidget::stopSearch);
|
||||
connect(widget, &ResultWidget::sizeChanged, this, &ResultArea::onWidgetSizeChanged);
|
||||
connect(widget, &ResultWidget::sizeChanged, this, [=] () {
|
||||
if (widget->pluginId() == m_widget_list.back()->pluginId() and m_selectedPluginID != m_bestListWidget->getWidgetName()) {//每次搜索默认选中websearch,由bestlist取消
|
||||
QModelIndex index = widget->getModlIndex(0, 0);
|
||||
if (index.isValid()) {
|
||||
widget->setResultSelection(index);
|
||||
m_is_selected = true;
|
||||
m_selectedPluginID = widget->pluginId();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(widget, &ResultWidget::showMoreClicked, this, [=] () {//点击展开搜索结果后 显示悬浮窗
|
||||
if (widget->height() > FRAME_HEIGHT) {//当前widget高度大于搜索结果界面高度则显示悬浮窗
|
||||
this->verticalScrollBar()->setValue(widget->pos().ry() + TITLE_HEIGHT); //置顶当前类型搜索结果,不显示标题栏
|
||||
viewport()->stackUnder(m_titleLabel);
|
||||
m_titleLabel->setText(widget->pluginName());
|
||||
m_titleLabel->setFixedSize(widget->width(), TITLE_HEIGHT);
|
||||
m_titleLabel->setShowMoreLableVisible();
|
||||
if (m_titleLabel->isHidden()) {
|
||||
m_titleLabel->show();
|
||||
this->setViewportMargins(0,TITLE_HEIGHT,0,0);
|
||||
}
|
||||
} else {
|
||||
this->verticalScrollBar()->setValue(widget->pos().ry()); //置顶当前类型搜索结果,显示标题栏
|
||||
}
|
||||
});
|
||||
connect(widget, &ResultWidget::retractClicked, this, [=] () {//点击收起搜索结果后
|
||||
if (!m_titleLabel->isHidden()) {
|
||||
m_titleLabel->hide();
|
||||
this->setViewportMargins(0,0,0,0);
|
||||
}
|
||||
});
|
||||
connect(widget, &ResultWidget::sendBestListData, m_bestListWidget, &BestListWidget::sendBestListData);
|
||||
}
|
||||
|
||||
DetailArea::DetailArea(QWidget *parent) : QScrollArea(parent)
|
||||
{
|
||||
initUi();
|
||||
connect(this, &DetailArea::setWidgetInfo, m_detailWidget, &DetailWidget::updateDetailPage);
|
||||
connect(this, &DetailArea::setWidgetInfo, this, &DetailArea::show);
|
||||
}
|
||||
|
||||
void DetailArea::initUi()
|
||||
{
|
||||
QPalette pal = palette();
|
||||
// pal.setColor(QPalette::Base, DETAIL_BACKGROUND_COLOR);
|
||||
pal.setColor(QPalette::Window, DETAIL_BACKGROUND_COLOR);
|
||||
QPalette scroll_bar_pal = this->verticalScrollBar()->palette();
|
||||
scroll_bar_pal.setColor(QPalette::Base, RESULT_BACKGROUND_COLOR);
|
||||
this->verticalScrollBar()->setPalette(scroll_bar_pal);
|
||||
this->setPalette(pal);
|
||||
this->setFrameShape(QFrame::Shape::NoFrame);
|
||||
this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
this->setWidgetResizable(true);
|
||||
this->setFixedSize(DETAIL_FRAME_WIDTH, FRAME_HEIGHT);
|
||||
// this->setStyleSheet("QScrollArea{border:2px solid red;}");
|
||||
this->setContentsMargins(0,0,0,0);
|
||||
m_detailWidget = new DetailWidget(this);
|
||||
this->setWidget(m_detailWidget);
|
||||
this->hide();
|
||||
}
|
||||
|
||||
DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
// initUi();
|
||||
// clear();
|
||||
this->setFixedWidth(368);
|
||||
m_mainLyt = new QVBoxLayout(this);
|
||||
this->setLayout(m_mainLyt);
|
||||
m_mainLyt->setContentsMargins(DETAIL_WIDGET_MARGINS);
|
||||
m_mainLyt->setAlignment(Qt::AlignHCenter);
|
||||
// m_mainLyt->addStretch();
|
||||
}
|
||||
|
||||
QString escapeHtml(const QString & str) {
|
||||
QString temp = str;
|
||||
temp.replace("<", "<");
|
||||
temp.replace(">", ">");
|
||||
return temp;
|
||||
}
|
||||
|
||||
void DetailWidget::setWidgetInfo(const QString &plugin_name, const SearchPluginIface::ResultInfo &info)
|
||||
{
|
||||
// clearLayout(m_descFrameLyt);
|
||||
// clearLayout(m_previewFrameLyt);
|
||||
// if(SearchPluginManager::getInstance()->getPlugin(plugin_name)->isPreviewEnable(info.actionKey,info.type)) {
|
||||
// m_iconLabel->hide();
|
||||
// m_previewFrameLyt->addWidget(SearchPluginManager::getInstance()->getPlugin(plugin_name)->previewPage(info.actionKey,info.type, m_previewFrame), 0 , Qt::AlignHCenter);
|
||||
// m_previewFrameLyt->setContentsMargins(0,0,0,0);
|
||||
// m_previewFrame->show();
|
||||
// } else {
|
||||
// m_previewFrame->hide();
|
||||
// m_iconLabel->setPixmap(info.icon.pixmap(info.icon.actualSize(ICON_SIZE)));
|
||||
// m_iconLabel->show();
|
||||
// }
|
||||
// QFontMetrics fontMetrics = m_nameLabel->fontMetrics();
|
||||
// QString name = fontMetrics.elidedText(info.name, Qt::ElideRight, NAME_LABEL_WIDTH - 8);
|
||||
// m_nameLabel->setText(QString("<h3 style=\"font-weight:normal;\">%1</h3>").arg(escapeHtml(name)));
|
||||
// m_nameLabel->setToolTip(info.name);
|
||||
// m_pluginLabel->setText(plugin_name);
|
||||
// m_nameFrame->show();
|
||||
// m_line_1->show();
|
||||
|
||||
// if (info.description.length() > 0) {
|
||||
// //NEW_TODO 样式待优化
|
||||
// clearLayout(m_descFrameLyt);
|
||||
// Q_FOREACH (SearchPluginIface::DescriptionInfo desc, info.description) {
|
||||
// QLabel * descLabel = new QLabel(m_descFrame);
|
||||
// descLabel->setTextFormat(Qt::PlainText);
|
||||
// descLabel->setWordWrap(true);
|
||||
// QString show_desc = desc.key + " " + desc.value;
|
||||
// descLabel->setText(show_desc);
|
||||
// m_descFrameLyt->addWidget(descLabel);
|
||||
// }
|
||||
// m_descFrame->show();
|
||||
// m_line_2->show();
|
||||
// }
|
||||
// clearLayout(m_actionFrameLyt);
|
||||
// Q_FOREACH (SearchPluginIface::Actioninfo actioninfo, SearchPluginManager::getInstance()->getPlugin(plugin_name)->getActioninfo(info.type)) {
|
||||
// ActionLabel * actionLabel = new ActionLabel(actioninfo.displayName, info.actionKey, actioninfo.actionkey, plugin_name, info.type, m_actionFrame);
|
||||
// m_actionFrameLyt->addWidget(actionLabel);
|
||||
// }
|
||||
// m_actionFrame->show();
|
||||
}
|
||||
|
||||
void DetailWidget::updateDetailPage(const QString &plugin_name, const SearchPluginIface::ResultInfo &info)
|
||||
{
|
||||
if(m_detailPage) {
|
||||
if(m_currentPluginId == plugin_name) {
|
||||
SearchPluginManager::getInstance()->getPlugin(plugin_name)->detailPage(info);
|
||||
} else {
|
||||
m_mainLyt->removeWidget(m_detailPage);
|
||||
m_detailPage->hide();
|
||||
m_detailPage = SearchPluginManager::getInstance()->getPlugin(plugin_name)->detailPage(info);
|
||||
m_detailPage->setParent(this);
|
||||
m_mainLyt->addWidget(m_detailPage);
|
||||
m_detailPage->show();
|
||||
// m_mainLyt->insertWidget(0, m_detailPage, 0);
|
||||
}
|
||||
} else {
|
||||
m_detailPage = SearchPluginManager::getInstance()->getPlugin(plugin_name)->detailPage(info);
|
||||
m_detailPage->setParent(this);
|
||||
m_mainLyt->addWidget(m_detailPage);
|
||||
// m_mainLyt->insertWidget(0, m_detailPage, 0);
|
||||
}
|
||||
m_currentPluginId = plugin_name;
|
||||
}
|
||||
|
||||
void DetailWidget::clear()
|
||||
{
|
||||
// m_iconLabel->hide();
|
||||
// m_nameFrame->hide();
|
||||
// m_line_1->hide();
|
||||
// m_descFrame->hide();
|
||||
// m_line_2->hide();
|
||||
// m_actionFrame->hide();
|
||||
}
|
||||
|
||||
void DetailWidget::initUi()
|
||||
{
|
||||
// this->setFixedSize(368, 516);
|
||||
// m_mainLyt = new QVBoxLayout(this);
|
||||
// this->setLayout(m_mainLyt);
|
||||
// m_mainLyt->setContentsMargins(DETAIL_WIDGET_MARGINS);
|
||||
// m_mainLyt->setAlignment(Qt::AlignHCenter);
|
||||
|
||||
// m_iconLabel = new QLabel(this);
|
||||
// m_iconLabel->setFixedHeight(DETAIL_ICON_HEIGHT);
|
||||
// m_iconLabel->setAlignment(Qt::AlignCenter);
|
||||
// m_previewFrame = new QFrame(this);
|
||||
// m_previewFrameLyt = new QHBoxLayout(m_previewFrame);
|
||||
|
||||
// m_nameFrame = new QFrame(this);
|
||||
// m_nameFrameLyt = new QHBoxLayout(m_nameFrame);
|
||||
// m_nameFrame->setLayout(m_nameFrameLyt);
|
||||
// m_nameFrameLyt->setContentsMargins(DETAIL_FRAME_MARGINS);
|
||||
// m_nameLabel = new QLabel(m_nameFrame);
|
||||
// m_nameLabel->setMaximumWidth(NAME_LABEL_WIDTH);
|
||||
// m_pluginLabel = new QLabel(m_nameFrame);
|
||||
// m_pluginLabel->setEnabled(false);
|
||||
// m_nameFrameLyt->addWidget(m_nameLabel);
|
||||
// m_nameFrameLyt->addStretch();
|
||||
// m_nameFrameLyt->addWidget(m_pluginLabel);
|
||||
|
||||
// m_line_1 = new QFrame(this);
|
||||
// m_line_1->setFixedHeight(1);
|
||||
// m_line_1->setLineWidth(0);
|
||||
// m_line_1->setStyleSheet(LINE_STYLE);
|
||||
// m_line_2 = new QFrame(this);
|
||||
// m_line_2->setFixedHeight(1);
|
||||
// m_line_2->setLineWidth(0);
|
||||
// m_line_2->setStyleSheet(LINE_STYLE);
|
||||
|
||||
// m_descFrame = new QFrame(this);
|
||||
// m_descFrameLyt = new QVBoxLayout(m_descFrame);
|
||||
// m_descFrame->setLayout(m_descFrameLyt);
|
||||
// m_descFrameLyt->setContentsMargins(DETAIL_FRAME_MARGINS);
|
||||
|
||||
// m_actionFrame = new QFrame(this);
|
||||
// m_actionFrameLyt = new QVBoxLayout(m_actionFrame);
|
||||
// m_actionFrame->setLayout(m_actionFrameLyt);
|
||||
// m_actionFrameLyt->setContentsMargins(DETAIL_FRAME_MARGINS);
|
||||
|
||||
// m_mainLyt->addWidget(m_iconLabel);
|
||||
// m_mainLyt->addWidget(m_previewFrame, 0, Qt::AlignHCenter);
|
||||
// m_mainLyt->addWidget(m_nameFrame);
|
||||
// m_mainLyt->addWidget(m_line_1);
|
||||
// m_mainLyt->addWidget(m_descFrame);
|
||||
// m_mainLyt->addWidget(m_line_2);
|
||||
// m_mainLyt->addWidget(m_actionFrame);
|
||||
// m_mainLyt->addStretch();
|
||||
}
|
||||
|
||||
void DetailWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
|
||||
QRect rect = this->rect().adjusted(0, 0, 0, 0);
|
||||
p.setRenderHint(QPainter::Antialiasing); // 反锯齿;
|
||||
p.setBrush(opt.palette.color(QPalette::Text));
|
||||
p.setOpacity(DETAIL_WIDGET_TRANSPARENT);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRoundedRect(rect, DETAIL_WIDGET_BORDER_RADIUS, DETAIL_WIDGET_BORDER_RADIUS);
|
||||
return QWidget::paintEvent(event);
|
||||
}
|
||||
|
||||
void DetailWidget::clearLayout(QLayout *layout)
|
||||
{
|
||||
if(!layout) return;
|
||||
QLayoutItem * child;
|
||||
while((child = layout->takeAt(0)) != 0) {
|
||||
if(child->widget()) {
|
||||
child->widget()->setParent(NULL);
|
||||
}
|
||||
delete child;
|
||||
}
|
||||
child = NULL;
|
||||
}
|
||||
|
||||
//ActionLabel::ActionLabel(const QString &action, const QString &key, const int &ActionKey, const QString &pluginId, const int type, QWidget *parent) : QLabel(parent)
|
||||
//{
|
||||
// m_action = action;
|
||||
// m_key = key;
|
||||
// m_actionKey = ActionKey;
|
||||
// m_type = type;
|
||||
// m_pluginId = pluginId;
|
||||
// this->initUi();
|
||||
// this->installEventFilter(this);
|
||||
//}
|
||||
|
||||
//void ActionLabel::initUi()
|
||||
//{
|
||||
// this->setText(m_action);
|
||||
// QPalette pal = palette();
|
||||
// pal.setColor(QPalette::WindowText, ACTION_NORMAL_COLOR);
|
||||
// pal.setColor(QPalette::Light, ACTION_HOVER_COLOR);
|
||||
// pal.setColor(QPalette::Dark, ACTION_PRESS_COLOR);
|
||||
// this->setPalette(pal);
|
||||
// this->setForegroundRole(QPalette::WindowText);
|
||||
// this->setCursor(QCursor(Qt::PointingHandCursor));
|
||||
//}
|
||||
|
||||
//bool ActionLabel::eventFilter(QObject *watched, QEvent *event)
|
||||
//{
|
||||
// if (watched == this) {
|
||||
// if(event->type() == QEvent::MouseButtonPress) {
|
||||
// this->setForegroundRole(QPalette::Dark);
|
||||
// return true;
|
||||
// } else if(event->type() == QEvent::MouseButtonRelease) {
|
||||
// SearchPluginIface *plugin = SearchPluginManager::getInstance()->getPlugin(m_pluginId);
|
||||
// if (plugin)
|
||||
// plugin->openAction(m_actionKey, m_key, m_type);
|
||||
// else
|
||||
// qWarning()<<"Get plugin failed!";
|
||||
// this->setForegroundRole(QPalette::Light);
|
||||
// return true;
|
||||
// } else if(event->type() == QEvent::Enter) {
|
||||
// this->setForegroundRole(QPalette::Light);
|
||||
// return true;
|
||||
// } else if(event->type() == QEvent::Leave) {
|
||||
// this->setForegroundRole(QPalette::WindowText);
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#ifndef SEARCHPAGESECTION_H
|
||||
#define SEARCHPAGESECTION_H
|
||||
#include <QScrollArea>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
#include "result-view.h"
|
||||
#include "search-plugin-iface.h"
|
||||
#include "best-list-view.h"
|
||||
#include "web-search-view.h"
|
||||
|
||||
namespace UkuiSearch {
|
||||
class ResultArea : public QScrollArea
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ResultArea(QWidget *parent = nullptr);
|
||||
~ResultArea() = default;
|
||||
void appendWidet(ResultWidget *);
|
||||
void setVisibleList(const QStringList &);
|
||||
void pressEnter();
|
||||
void pressDown();
|
||||
void pressUp();
|
||||
|
||||
void setResultSelection();
|
||||
bool getSelectedState();
|
||||
|
||||
void sendKeyPressSignal(QString &pluginID);
|
||||
|
||||
public Q_SLOTS:
|
||||
void onWidgetSizeChanged();
|
||||
void setSelectionInfo(QString &pluginID);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
void mouseMoveEvent(QMouseEvent *event);
|
||||
void mouseReleaseEvent(QMouseEvent *event);
|
||||
bool viewportEvent(QEvent *event);
|
||||
|
||||
|
||||
private:
|
||||
void initUi();
|
||||
void initConnections();
|
||||
void setupConnectionsForWidget(ResultWidget *);
|
||||
|
||||
QWidget * m_widget = nullptr;
|
||||
QVBoxLayout * m_mainLyt = nullptr;
|
||||
BestListWidget * m_bestListWidget = nullptr;
|
||||
QList<ResultWidget *> m_widget_list;
|
||||
TitleLabel * m_titleLabel = nullptr;
|
||||
|
||||
bool m_detail_open_state = false;
|
||||
bool m_is_selected = false;
|
||||
QString m_selectedPluginID;
|
||||
QPoint m_pressPoint;
|
||||
|
||||
Q_SIGNALS:
|
||||
void startSearch(const QString &);
|
||||
void stopSearch();
|
||||
void currentRowChanged(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
void keyPressChanged(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
void clearSelectedRow();
|
||||
void resizeHeight(int height);
|
||||
void resizeWidth(const int &);
|
||||
void rowClicked();
|
||||
};
|
||||
|
||||
class DetailWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
DetailWidget(QWidget *parent = nullptr);
|
||||
~DetailWidget() = default;
|
||||
void clear();
|
||||
|
||||
public Q_SLOTS:
|
||||
void setWidgetInfo(const QString &plugin_name, const SearchPluginIface::ResultInfo &info);
|
||||
void updateDetailPage(const QString &plugin_name, const SearchPluginIface::ResultInfo &info);
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event);
|
||||
private:
|
||||
void initUi();
|
||||
void clearLayout(QLayout *);
|
||||
QVBoxLayout * m_mainLyt = nullptr;
|
||||
QString m_currentPluginId;
|
||||
QWidget *m_detailPage = nullptr;
|
||||
// QLabel * m_iconLabel = nullptr;
|
||||
// QFrame *m_previewFrame = nullptr;
|
||||
// QHBoxLayout *m_previewFrameLyt = nullptr;
|
||||
// QFrame * m_nameFrame = nullptr;
|
||||
// QHBoxLayout * m_nameFrameLyt = nullptr;
|
||||
// QLabel * m_nameLabel = nullptr;
|
||||
// QLabel * m_pluginLabel = nullptr;
|
||||
// QFrame * m_line_1 = nullptr;
|
||||
// QFrame * m_descFrame = nullptr;
|
||||
// QVBoxLayout * m_descFrameLyt = nullptr;
|
||||
// QFrame * m_line_2 = nullptr;
|
||||
// QFrame * m_actionFrame = nullptr;
|
||||
// QVBoxLayout * m_actionFrameLyt = nullptr;
|
||||
};
|
||||
|
||||
class DetailArea : public QScrollArea
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
DetailArea(QWidget *parent = nullptr);
|
||||
~DetailArea() = default;
|
||||
private:
|
||||
void initUi();
|
||||
DetailWidget * m_detailWidget = nullptr;
|
||||
|
||||
Q_SIGNALS:
|
||||
void setWidgetInfo(const QString&, const SearchPluginIface::ResultInfo&);
|
||||
};
|
||||
|
||||
//class ActionLabel : public QLabel
|
||||
//{
|
||||
// Q_OBJECT
|
||||
//public:
|
||||
// ActionLabel(const QString &action, const QString &key, const int &ActionKey, const QString &pluginId, const int type = 0, QWidget *parent = nullptr);
|
||||
// ~ActionLabel() = default;
|
||||
//private:
|
||||
// void initUi();
|
||||
// QString m_action;
|
||||
// QString m_key;
|
||||
// int m_actionKey;
|
||||
// int m_type = 0;
|
||||
// QString m_pluginId;
|
||||
|
||||
//protected:
|
||||
// bool eventFilter(QObject *, QEvent *);
|
||||
//};
|
||||
|
||||
}
|
||||
|
||||
#endif // SEARCHPAGESECTION_H
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2021, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: jixiaoxu <jixiaoxu@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "search-result-page.h"
|
||||
#include <QPainterPath>
|
||||
QT_BEGIN_NAMESPACE
|
||||
extern void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed);
|
||||
QT_END_NAMESPACE
|
||||
using namespace UkuiSearch;
|
||||
|
||||
#define RESULT_WIDTH 266
|
||||
#define DETAIL_WIDTH 374
|
||||
|
||||
SearchResultPage::SearchResultPage(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
initUi();
|
||||
initConnections();
|
||||
setInternalPlugins();
|
||||
}
|
||||
|
||||
void SearchResultPage::setSize(const int&width, const int&height)
|
||||
{
|
||||
// m_splitter->setFixedSize(width, height);
|
||||
}
|
||||
|
||||
void SearchResultPage::setInternalPlugins()
|
||||
{
|
||||
Q_FOREACH (QString plugin_id, SearchPluginManager::getInstance()->getPluginIds()) {
|
||||
ResultWidget * widget = new ResultWidget(plugin_id, m_resultArea);
|
||||
m_resultArea->appendWidet(widget);
|
||||
setupConnectionsForWidget(widget);
|
||||
}
|
||||
}
|
||||
|
||||
void SearchResultPage::appendPlugin(const QString &plugin_id)
|
||||
{
|
||||
ResultWidget * widget = new ResultWidget(plugin_id, m_resultArea);
|
||||
m_resultArea->appendWidet(widget);
|
||||
setupConnectionsForWidget(widget);
|
||||
}
|
||||
|
||||
void SearchResultPage::pressEnter()
|
||||
{
|
||||
this->m_resultArea->pressEnter();
|
||||
}
|
||||
|
||||
void SearchResultPage::pressUp()
|
||||
{
|
||||
this->m_resultArea->pressUp();
|
||||
}
|
||||
|
||||
void SearchResultPage::pressDown()
|
||||
{
|
||||
this->m_resultArea->pressDown();
|
||||
}
|
||||
|
||||
bool SearchResultPage::getSelectedState()
|
||||
{
|
||||
return m_resultArea->getSelectedState();
|
||||
}
|
||||
|
||||
void SearchResultPage::sendResizeWidthSignal(int size)
|
||||
{
|
||||
Q_EMIT this->resizeWidth(size);
|
||||
}
|
||||
|
||||
void SearchResultPage::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.setBrush(palette().base());
|
||||
p.setOpacity(GlobalSettings::getInstance()->getValue(TRANSPARENCY_KEY).toDouble());
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRoundedRect(this->rect().adjusted(10,10,-10,-10), 12, 12);
|
||||
|
||||
QPainterPath rectPath;
|
||||
rectPath.addRoundedRect(this->rect().adjusted(10, 10, -10, -10), 12, 12);
|
||||
|
||||
// 画一个黑底
|
||||
QPixmap pixmap(this->rect().size());
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter pixmapPainter(&pixmap);
|
||||
pixmapPainter.setRenderHint(QPainter::Antialiasing);
|
||||
// pixmapPainter.setCompositionMode(QPainter::CompositionMode_Difference);
|
||||
pixmapPainter.setPen(Qt::transparent);
|
||||
pixmapPainter.setBrush(Qt::black);
|
||||
pixmapPainter.setOpacity(0.65);
|
||||
pixmapPainter.drawPath(rectPath);
|
||||
pixmapPainter.end();
|
||||
|
||||
|
||||
// 模糊这个黑底
|
||||
QImage img = pixmap.toImage();
|
||||
qt_blurImage(img, 10, false, false);
|
||||
|
||||
|
||||
// 挖掉中心
|
||||
pixmap = QPixmap::fromImage(img);
|
||||
QPainter pixmapPainter2(&pixmap);
|
||||
pixmapPainter2.setRenderHint(QPainter::Antialiasing);
|
||||
pixmapPainter2.setCompositionMode(QPainter::CompositionMode_Clear);
|
||||
pixmapPainter2.setPen(Qt::transparent);
|
||||
pixmapPainter2.setBrush(Qt::transparent);
|
||||
pixmapPainter2.drawPath(rectPath);
|
||||
|
||||
|
||||
// 绘制阴影
|
||||
p.drawPixmap(this->rect(), pixmap, pixmap.rect());
|
||||
}
|
||||
|
||||
void SearchResultPage::initUi()
|
||||
{
|
||||
this->setFixedSize(700,552);
|
||||
m_hlayout = new QHBoxLayout(this);
|
||||
m_hlayout->setContentsMargins(18 ,18, 10, 18);
|
||||
|
||||
// m_splitter = new QSplitter(this);
|
||||
// m_splitter->setContentsMargins(0, 0, 0, 0);
|
||||
// m_splitter->setFixedSize(664, 516);
|
||||
// m_splitter->move(this->rect().topLeft().x() + 18, this->rect().topLeft().y() + 18);
|
||||
m_resultArea = new ResultArea(this);
|
||||
m_detailArea = new DetailArea(this);
|
||||
|
||||
m_hlayout->addWidget(m_resultArea);
|
||||
m_hlayout->addWidget(m_detailArea);
|
||||
m_hlayout->setSpacing(0);
|
||||
this->setLayout(m_hlayout);
|
||||
// m_splitter->addWidget(m_resultArea);
|
||||
// m_splitter->addWidget(m_detailArea);
|
||||
// m_splitter->setOpaqueResize(false);
|
||||
// QList<int> size_list;
|
||||
// size_list<<664<<0;
|
||||
// m_splitter->setSizes(size_list);
|
||||
// m_splitter->handle(1)->setVisible(false); //暂时禁止拖动分隔条
|
||||
}
|
||||
|
||||
void SearchResultPage::initConnections()
|
||||
{
|
||||
connect(this, &SearchResultPage::startSearch, m_resultArea, &ResultArea::startSearch);
|
||||
connect(this, &SearchResultPage::stopSearch, m_resultArea, &ResultArea::stopSearch);
|
||||
connect(this, &SearchResultPage::startSearch, m_detailArea, &DetailArea::hide);
|
||||
connect(this, &SearchResultPage::stopSearch, m_detailArea, &DetailArea::hide);
|
||||
connect(this, &SearchResultPage::startSearch, this, [=] () {
|
||||
sendResizeWidthSignal(656);
|
||||
});
|
||||
connect(m_resultArea, &ResultArea::keyPressChanged, m_detailArea, &DetailArea::setWidgetInfo);
|
||||
connect(m_resultArea, &ResultArea::keyPressChanged, this, [=] () {
|
||||
sendResizeWidthSignal(280);
|
||||
});
|
||||
connect(m_resultArea, &ResultArea::currentRowChanged, m_detailArea, &DetailArea::setWidgetInfo);
|
||||
connect(m_resultArea, &ResultArea::currentRowChanged, this, &SearchResultPage::currentRowChanged);
|
||||
connect(this, &SearchResultPage::currentRowChanged, m_resultArea, &ResultArea::clearSelectedRow);
|
||||
connect(m_resultArea, &ResultArea::resizeHeight, this, &SearchResultPage::resizeHeight);
|
||||
connect(this, &SearchResultPage::resizeWidth, m_resultArea, &ResultArea::resizeWidth);
|
||||
connect(m_resultArea, &ResultArea::rowClicked, this, [=] () {
|
||||
sendResizeWidthSignal(280);
|
||||
});
|
||||
connect(this, &SearchResultPage::setSelectionInfo, m_resultArea, &ResultArea::setSelectionInfo);
|
||||
}
|
||||
|
||||
void SearchResultPage::setupConnectionsForWidget(ResultWidget *widget)
|
||||
{
|
||||
connect(widget, &ResultWidget::currentRowChanged, m_detailArea, &DetailArea::setWidgetInfo);
|
||||
connect(widget, &ResultWidget::currentRowChanged, this, &SearchResultPage::currentRowChanged);
|
||||
connect(widget, &ResultWidget::currentRowChanged, this, [=] {
|
||||
QString pluginID = widget->pluginId();
|
||||
Q_EMIT this->setSelectionInfo(pluginID);
|
||||
});
|
||||
connect(this, &SearchResultPage::currentRowChanged, widget, &ResultWidget::clearSelectedRow);
|
||||
connect(widget, &ResultWidget::rowClicked, this, [=] () {
|
||||
sendResizeWidthSignal(280);
|
||||
});
|
||||
connect(this, &SearchResultPage::resizeWidth, widget, &ResultWidget::resizeWidth);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#ifndef SEARCHRESULTPAGE_H
|
||||
#define SEARCHRESULTPAGE_H
|
||||
|
||||
#include <QSplitter>
|
||||
#include "search-page-section.h"
|
||||
|
||||
namespace UkuiSearch {
|
||||
class SearchResultPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SearchResultPage(QWidget *parent = nullptr);
|
||||
~SearchResultPage() = default;
|
||||
void setSize(const int&, const int&);
|
||||
void setInternalPlugins();
|
||||
void appendPlugin(const QString &plugin_id);
|
||||
void pressEnter();
|
||||
void pressUp();
|
||||
void pressDown();
|
||||
bool getSelectedState();
|
||||
void sendResizeWidthSignal(int size);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event);
|
||||
private:
|
||||
void initUi();
|
||||
void initConnections();
|
||||
void setupConnectionsForWidget(ResultWidget *);
|
||||
QSplitter * m_splitter = nullptr;
|
||||
QHBoxLayout * m_hlayout = nullptr;
|
||||
ResultArea * m_resultArea = nullptr;
|
||||
DetailArea * m_detailArea = nullptr;
|
||||
|
||||
Q_SIGNALS:
|
||||
void startSearch(const QString &);
|
||||
void stopSearch();
|
||||
void currentRowChanged(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
void effectiveSearch();
|
||||
void resizeHeight(int height);
|
||||
void resizeWidth(const int &);
|
||||
void setSelectionInfo(QString &);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SEARCHRESULTPAGE_H
|
|
@ -0,0 +1,9 @@
|
|||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/search-page-section.h \
|
||||
$$PWD/search-result-page.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/search-page-section.cpp \
|
||||
$$PWD/search-result-page.cpp
|
|
@ -0,0 +1,79 @@
|
|||
QT += core gui dbus KWindowSystem xml x11extras
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
VERSION = 1.0.0
|
||||
DEFINES += VERSION='\\"$${VERSION}\\"'
|
||||
TARGET = ukui-search
|
||||
TEMPLATE = app
|
||||
|
||||
PKGCONFIG += gio-2.0 glib-2.0 gio-unix-2.0
|
||||
CONFIG += c++11 link_pkgconfig no_keywords lrelease
|
||||
LIBS += -lxapian -lgsettings-qt -lquazip5 -lX11
|
||||
#LIBS += -lukui-log4qt
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any Qt feature that has been marked deprecated (the exact warnings
|
||||
# depend on your compiler). Please consult the documentation of the
|
||||
# deprecated API in order to know how to port your code away from it.
|
||||
DEFINES += QT_DEPRECATED_WARNINGS
|
||||
|
||||
# You can also make your code fail to compile if it uses deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
include(../libsearch/libukui-search-headers.pri)
|
||||
include(control/control.pri)
|
||||
include(model/model.pri)
|
||||
include(xatom/xatom.pri)
|
||||
include(../3rd-parties/qtsingleapplication/qtsingleapplication.pri)
|
||||
include(view/view.pri)
|
||||
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
mainwindow.cpp \
|
||||
ukui-search-dbus-service.cpp \
|
||||
ukui-search-gui.cpp
|
||||
|
||||
|
||||
HEADERS += \
|
||||
mainwindow.h \
|
||||
ukui-search-dbus-service.h \
|
||||
ukui-search-gui.h
|
||||
|
||||
# Default rules for deployment.
|
||||
|
||||
target.path = /usr/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
|
||||
data-menu.path = /usr/share/applications
|
||||
data-menu.files += ../data/ukui-search-menu.desktop
|
||||
data.path = /etc/xdg/autostart
|
||||
data.files += ../data/ukui-search.desktop
|
||||
|
||||
INSTALLS += data data-menu
|
||||
|
||||
RESOURCES += \
|
||||
resource.qrc
|
||||
|
||||
TRANSLATIONS += \
|
||||
../translations/ukui-search/zh_CN.ts \
|
||||
../translations/ukui-search/tr.ts \
|
||||
../translations/ukui-search/bo.ts
|
||||
|
||||
qm_files.path = /usr/share/ukui-search/translations/
|
||||
qm_files.files = $$OUT_PWD/.qm/*.qm
|
||||
|
||||
schemes.path = /usr/share/glib-2.0/schemas/
|
||||
schemes.files += ../data/org.ukui.log4qt.ukui-search.gschema.xml
|
||||
|
||||
INSTALLS += qm_files schemes
|
||||
|
||||
LIBS += -L$$OUT_PWD/../libchinese-segmentation -lchinese-segmentation \
|
||||
-L$$OUT_PWD/../libsearch -lukui-search
|
||||
|
||||
INCLUDEPATH += $$PWD/../libchinese-segmentation
|
||||
DEPENDPATH += $$PWD/../libchinese-segmentation
|
||||
|
||||
DISTFILES += \
|
||||
../data/org.ukui.log4qt.ukui-search.gschema.xml \
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
* Modified by: zhangpengfei <zhangpengfei@kylinos.cn>
|
||||
* Modified by: zhangzihao <zhangzihao@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <QDesktopWidget>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <syslog.h>
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
#include <ukui-log4qt.h>
|
||||
#endif
|
||||
#include <QObject>
|
||||
#include <QApplication>
|
||||
#include <QX11Info>
|
||||
#include "ukui-search-gui.h"
|
||||
|
||||
using namespace UkuiSearch;
|
||||
|
||||
void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
QByteArray localMsg = msg.toLocal8Bit();
|
||||
QByteArray currentTime = QTime::currentTime().toString().toLocal8Bit();
|
||||
|
||||
bool showDebug = true;
|
||||
// QString logFilePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/ukui-search.log";
|
||||
// QString logFilePath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.config/org.ukui/ukui-search/ukui-search.log";
|
||||
QString logFilePath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.config/org.ukui/ukui-search.log";
|
||||
if (!QFile::exists(logFilePath)) {
|
||||
showDebug = false;
|
||||
}
|
||||
FILE *log_file = nullptr;
|
||||
|
||||
if (showDebug) {
|
||||
log_file = fopen(logFilePath.toLocal8Bit().constData(), "a+");
|
||||
}
|
||||
|
||||
const char *file = context.file ? context.file : "";
|
||||
const char *function = context.function ? context.function : "";
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
if (!log_file) {
|
||||
break;
|
||||
}
|
||||
fprintf(log_file, "Debug: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function);
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
fprintf(log_file? log_file: stdout, "Info: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
fprintf(log_file? log_file: stderr, "Warning: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
fprintf(log_file? log_file: stderr, "Critical: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
fprintf(log_file? log_file: stderr, "Fatal: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function);
|
||||
break;
|
||||
}
|
||||
|
||||
if (log_file)
|
||||
fclose(log_file);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
//v101日志模块
|
||||
//#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
// //Init log module
|
||||
// initUkuiLog4qt("ukui-search");
|
||||
//#endif
|
||||
|
||||
// Determine whether the home directory has been created, and if not, keep waiting.
|
||||
char *p_home = NULL;
|
||||
|
||||
unsigned int i = 0;
|
||||
while(p_home == NULL) {
|
||||
::sleep(1);
|
||||
++i;
|
||||
p_home = getenv("HOME");
|
||||
if(i % 5 == 0) {
|
||||
qWarning() << "I can't find home! I'm done here!!";
|
||||
printf("I can't find home! I'm done here!!");
|
||||
syslog(LOG_ERR, "I can't find home! I'm done here!!\n");
|
||||
}
|
||||
}
|
||||
p_home = NULL;
|
||||
while(!QDir(QDir::homePath()).exists()) {
|
||||
qWarning() << "Home is not exits!!";
|
||||
printf("Home is not exits!!");
|
||||
syslog(LOG_ERR, "Home is not exits!!\n");
|
||||
::sleep(1);
|
||||
}
|
||||
|
||||
// Output log to file
|
||||
qInstallMessageHandler(messageOutput);
|
||||
//若使用v101日志模块,可以解放如下判断条件
|
||||
//#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0))
|
||||
// // Output log to file
|
||||
// qInstallMessageHandler(messageOutput);
|
||||
//#endif
|
||||
|
||||
// Register meta type
|
||||
qDebug() << "ukui-search main start";
|
||||
// If qt version bigger than 5.12, enable high dpi scaling and use high dpi pixmaps?
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
#endif
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
#endif
|
||||
UkuiSearchGui app(argc, argv, QString("ukui-search-gui-%1").arg(QX11Info::appScreen()));
|
||||
if (app.isRunning())
|
||||
return 0;
|
||||
|
||||
return app.exec();
|
||||
}
|
|
@ -0,0 +1,568 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
* Modified by: zhangpengfei <zhangpengfei@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include <QHBoxLayout>
|
||||
#include <QDebug>
|
||||
#include <QDesktopWidget>
|
||||
#include <syslog.h>
|
||||
#include <QPalette>
|
||||
#include <QScreen>
|
||||
#include <QStyleOption>
|
||||
#include <QPixmap>
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
#include <KWindowEffects>
|
||||
#include "kwindowsystem.h"
|
||||
#endif
|
||||
#include "global-settings.h"
|
||||
#include <QtX11Extras/QX11Info>
|
||||
|
||||
#define MAIN_MARGINS 0, 0, 0, 0
|
||||
#define TITLE_MARGINS 0,0,0,0
|
||||
#define UKUI_SEARCH_SCHEMAS "org.ukui.search.settings"
|
||||
#define SEARCH_METHOD_KEY "indexSearch"
|
||||
#define WEB_ENGINE_KEY "webEngine"
|
||||
#define WINDOW_WIDTH 700
|
||||
#define WINDOW_HEIGHT 610
|
||||
#define TITLE_HEIGHT 40
|
||||
#define WINDOW_ICON_SIZE 24
|
||||
#define SETTING_BTN_SIZE 30
|
||||
#define SEARCH_BAR_SIZE 48
|
||||
#define ASK_INDEX_TIME 5*1000
|
||||
#define RESEARCH_TIME 10*1000
|
||||
|
||||
using namespace UkuiSearch;
|
||||
extern void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed);
|
||||
/**
|
||||
* @brief MainWindow 主界面
|
||||
* @param parent
|
||||
*
|
||||
* 慎用KWindowSystem::setShowingDesktop(!KWindowSystem::showingDesktop());
|
||||
* 可能造成窗口属性的混乱
|
||||
*/
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent) {
|
||||
this->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
this->setWindowFlag(Qt::FramelessWindowHint);
|
||||
this->setAutoFillBackground(false);
|
||||
this->setFocusPolicy(Qt::StrongFocus);
|
||||
this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
this->setWindowTitle(tr("ukui-search"));
|
||||
KWindowSystem::setState(this->winId(),NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher );
|
||||
initUi();
|
||||
initTimer();
|
||||
|
||||
m_sys_tray_icon = new QSystemTrayIcon(this);
|
||||
m_sys_tray_icon->setIcon(QIcon::fromTheme("system-search-symbolic", QIcon(":/res/icons/edit-find-symbolic.svg")));
|
||||
m_sys_tray_icon->setToolTip(tr("Global Search"));
|
||||
m_sys_tray_icon->show();
|
||||
installEventFilter(this);
|
||||
initConnections();
|
||||
initGsettings();
|
||||
|
||||
connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this,[&](WId activeWindowId){
|
||||
if (activeWindowId != this->winId()) {
|
||||
tryHideMainwindow();
|
||||
}
|
||||
});
|
||||
|
||||
//NEW_TODO, register plugins
|
||||
// SearchPluginManager::getInstance()->registerPlugin(\\);
|
||||
// m_stackedWidget->setPlugins(SearchPluginManager::getInstance()->getPluginIds());
|
||||
// m_stackedWidget->setPlugins(SearchPluginManager::getInstance()->getPluginIds());
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0))
|
||||
if(m_settingsWidget) {
|
||||
delete m_settingsWidget;
|
||||
m_settingsWidget = NULL;
|
||||
}
|
||||
#endif
|
||||
if(m_askDialog) {
|
||||
delete m_askDialog;
|
||||
m_askDialog = NULL;
|
||||
}
|
||||
if(m_askTimer) {
|
||||
delete m_askTimer;
|
||||
m_askTimer = NULL;
|
||||
}
|
||||
if(m_search_gsettings) {
|
||||
delete m_search_gsettings;
|
||||
m_search_gsettings = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief initUi 初始化主界面主要ui控件
|
||||
*/
|
||||
void MainWindow::initUi() {
|
||||
this->setFixedSize(WINDOW_WIDTH, 68);
|
||||
// this->setStyleSheet("QMainWindow{border:2px solid red;}");
|
||||
|
||||
// m_widget = new QWidget(this);
|
||||
|
||||
// this->setCentralWidget(m_widget);
|
||||
// m_widget->setFixedSize(this->size());
|
||||
// QVBoxLayout * mainlayout = new QVBoxLayout(m_frame);
|
||||
// mainlayout->setContentsMargins(MAIN_MARGINS);
|
||||
// m_frame->setLayout(mainlayout);
|
||||
|
||||
// m_stackedWidget = new StackedWidget(m_frame);//内容栏
|
||||
m_searchBarWidget = new SeachBarWidget(this);
|
||||
m_searchBarWidget->move(this->rect().topLeft());
|
||||
m_searchBarWidget->show();
|
||||
m_searchResultPage = new SearchResultPage(this);
|
||||
m_searchResultPage->hide();
|
||||
m_searchResultPage->move(0, 58);
|
||||
this->setFocusProxy(m_searchBarWidget);
|
||||
|
||||
// m_searchResultPage->show();
|
||||
// m_searchWidget = new SeachBarWidget(this);
|
||||
// m_searchLayout = new SearchBarHLayout(this);
|
||||
// m_searchWidget->setLayout(m_searchLayout);
|
||||
// m_searchWidget->setFixedHeight(SEARCH_BAR_SIZE);
|
||||
|
||||
// mainlayout->addWidget(m_titleFrame);
|
||||
// mainlayout->addWidget(m_seachBarWidget);
|
||||
// mainlayout->addSpacing(8);
|
||||
// mainlayout->addWidget(m_searchResultPage);
|
||||
|
||||
//创建索引询问弹窗
|
||||
m_askDialog = new CreateIndexAskDialog(this);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
MotifWmHints ask_dialog_hints;
|
||||
ask_dialog_hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS;
|
||||
ask_dialog_hints.functions = MWM_FUNC_ALL;
|
||||
ask_dialog_hints.decorations = MWM_DECOR_BORDER;
|
||||
XAtomHelper::getInstance()->setWindowMotifHint(m_askDialog->winId(), ask_dialog_hints);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::initConnections()
|
||||
{
|
||||
connect(m_sys_tray_icon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivatedSlot);
|
||||
QObject::connect(this, &MainWindow::searchMethodChanged, this, [ = ](FileUtils::SearchMethod sm) {
|
||||
FileUtils::searchMethod = sm;
|
||||
});
|
||||
connect(QApplication::primaryScreen(), &QScreen::geometryChanged, this, &MainWindow::monitorResolutionChange);
|
||||
connect(qApp, &QApplication::primaryScreenChanged, this, &MainWindow::primaryScreenChangedSlot);
|
||||
connect(m_askDialog, &CreateIndexAskDialog::closed, this, [ = ]() {
|
||||
m_isAskDialogVisible = false;
|
||||
});
|
||||
connect(m_askDialog, &CreateIndexAskDialog::btnClicked, this, [ = ](const bool &is_create_index, const bool &is_ask_again) {
|
||||
setSearchMethodConfig(is_create_index, is_ask_again);
|
||||
});
|
||||
// connect(m_settingsBtn, &QPushButton::clicked, this, &MainWindow::settingsBtnClickedSlot);
|
||||
//主题改变时,更新自定义标题栏的图标
|
||||
// connect(qApp, &QApplication::paletteChanged, this, [ = ]() {
|
||||
// m_iconLabel->setPixmap(QIcon::fromTheme("kylin-search").pixmap(QSize(WINDOW_ICON_SIZE, WINDOW_ICON_SIZE)));
|
||||
// });
|
||||
connect(m_searchBarWidget, &SeachBarWidget::requestSearchKeyword, this, &MainWindow::searchKeywordSlot);
|
||||
// connect(m_stackedWidget, &StackedWidget::effectiveSearch, m_searchLayout, &SearchBarHLayout::effectiveSearchRecord);
|
||||
//connect(m_searchResultPage, &SearchResultPage::resizeHeight, this, &MainWindow::resizeHeight);
|
||||
connect(this,&MainWindow::setText,m_searchBarWidget,&SeachBarWidget::setText);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief bootOptionsFilter 过滤终端命令
|
||||
* @param opt
|
||||
*/
|
||||
void MainWindow::bootOptionsFilter(QString opt) {
|
||||
if(opt == "-s" || opt == "--show") {
|
||||
if (this->isHidden()) {
|
||||
clearSearchResult();
|
||||
centerToScreen(this);
|
||||
this->show();
|
||||
this->m_searchBarWidget->setFocus();
|
||||
this->activateWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief clearSearchResult 清空搜索结果
|
||||
*/
|
||||
void MainWindow::clearSearchResult() {
|
||||
m_searchBarWidget->clear();
|
||||
// m_searchLineEdit->clearFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MainWindow::trayIconActivatedSlot 点击任务栏图标的槽函数
|
||||
* @param reason
|
||||
*/
|
||||
void MainWindow::trayIconActivatedSlot(QSystemTrayIcon::ActivationReason reason)
|
||||
{
|
||||
if(reason == QSystemTrayIcon::Trigger) {
|
||||
if(!this->isVisible()) {
|
||||
clearSearchResult();
|
||||
centerToScreen(this);
|
||||
this->show();
|
||||
// this->m_searchLineEdit->focusIn(); //打开主界面时输入框夺焦,可直接输入
|
||||
this->raise();
|
||||
this->activateWindow();
|
||||
} else {
|
||||
tryHideMainwindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief setSearchMethodConfig 在询问弹窗点击按钮后执行
|
||||
* @param create_index 是否同意创建索引
|
||||
* @param no_longer_ask 是否勾选了不再提示
|
||||
*/
|
||||
void MainWindow::setSearchMethodConfig(const bool &create_index, const bool &no_longer_ask)
|
||||
{
|
||||
if(no_longer_ask) {
|
||||
GlobalSettings::getInstance()->setValue(ENABLE_CREATE_INDEX_ASK_DIALOG, "false");
|
||||
} else {
|
||||
GlobalSettings::getInstance()->setValue(ENABLE_CREATE_INDEX_ASK_DIALOG, "true");
|
||||
}
|
||||
if(create_index) {
|
||||
if(m_search_gsettings && m_search_gsettings->keys().contains(SEARCH_METHOD_KEY)) {
|
||||
m_search_gsettings->set(SEARCH_METHOD_KEY, true);
|
||||
}
|
||||
Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::INDEXSEARCH);
|
||||
//创建索引十秒后重新搜索一次(如果用户十秒内没有退出搜索界面且没有重新搜索)
|
||||
m_researchTimer->start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MainWindow::settingsBtnClickedSlot 点击设置按钮的槽函数
|
||||
*/
|
||||
void MainWindow::settingsBtnClickedSlot()
|
||||
{
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0))
|
||||
if(m_settingsWidget) { //当此窗口已存在时,仅需置顶
|
||||
if(!m_settingsWidget->isVisible()) {
|
||||
centerToScreen(m_settingsWidget);
|
||||
}
|
||||
m_settingsWidget->showWidget();
|
||||
return;
|
||||
}
|
||||
m_settingsWidget = new SettingsWidget();
|
||||
connect(this, &MainWindow::webEngineChanged, m_settingsWidget, [ = ]() {
|
||||
m_settingsWidget->resetWebEngine();
|
||||
});
|
||||
connect(m_settingsWidget, &SettingsWidget::webEngineChanged, this, [ = ](const QString & engine) {
|
||||
if(m_search_gsettings && m_search_gsettings->keys().contains(WEB_ENGINE_KEY)) {
|
||||
m_search_gsettings->set(WEB_ENGINE_KEY, engine);
|
||||
} else {
|
||||
GlobalSettings::getInstance()->setValue(WEB_ENGINE, engine);
|
||||
}
|
||||
});
|
||||
centerToScreen(m_settingsWidget);
|
||||
m_settingsWidget->show();
|
||||
connect(m_settingsWidget, &SettingsWidget::settingWidgetClosed, this, [ = ]() {
|
||||
QTimer::singleShot(100, this, [ = ] {
|
||||
// clearSearchResult(); //现暂定从设置页返回主页面不清空搜索结果
|
||||
this->setWindowState(this->windowState() & ~Qt::WindowMinimized);
|
||||
this->raise();
|
||||
this->showNormal();
|
||||
this->activateWindow();
|
||||
});
|
||||
});
|
||||
#endif
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
//打开控制面板的设置页
|
||||
QProcess process;
|
||||
process.startDetached("ukui-control-center -m search");
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MainWindow::searchKeywordSlot 执行搜索的槽函数
|
||||
* @param keyword 关键词
|
||||
*/
|
||||
void MainWindow::searchKeywordSlot(const QString &keyword)
|
||||
{
|
||||
//NEW_TODO
|
||||
if(keyword == "") {
|
||||
// m_stackedWidget->setPage(int(StackedPage::HomePage));
|
||||
m_askTimer->stop();
|
||||
Q_EMIT m_searchResultPage->stopSearch();
|
||||
m_searchResultPage->hide();
|
||||
this->resizeHeight(68);
|
||||
|
||||
} else {
|
||||
// m_stackedWidget->setPage(int(StackedPage::SearchPage));
|
||||
QTimer::singleShot(10, this, [ = ]() {
|
||||
//允许弹窗且当前次搜索(为关闭主界面,算一次搜索过程)未询问且当前为暴力搜索
|
||||
if(GlobalSettings::getInstance()->getValue(ENABLE_CREATE_INDEX_ASK_DIALOG).toString() != "false" && !m_currentSearchAsked && FileUtils::searchMethod == FileUtils::SearchMethod::DIRECTSEARCH)
|
||||
m_askTimer->start();
|
||||
Q_EMIT m_searchResultPage->startSearch(keyword);
|
||||
this->resizeHeight(WINDOW_HEIGHT);
|
||||
|
||||
m_searchResultPage->move(0, 58);
|
||||
m_searchResultPage->show();
|
||||
});
|
||||
}
|
||||
m_researchTimer->stop(); //如果搜索内容发生改变,则停止建索引后重新搜索的倒计时
|
||||
}
|
||||
|
||||
void MainWindow::resizeHeight(int height)
|
||||
{
|
||||
this->setFixedHeight(height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief monitorResolutionChange 监听屏幕改变
|
||||
* @param rect
|
||||
*/
|
||||
void MainWindow::monitorResolutionChange(QRect rect) {
|
||||
Q_UNUSED(rect);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief primaryScreenChangedSlot 监听分辨率改变
|
||||
* @param screen
|
||||
*/
|
||||
void MainWindow::primaryScreenChangedSlot(QScreen *screen) {
|
||||
Q_UNUSED(screen);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MainWindow::moveToPanel 将主界面移动到任务栏旁边(跟随任务栏位置)
|
||||
*/
|
||||
void MainWindow::moveToPanel() {
|
||||
QRect availableGeometry = qApp->primaryScreen()->availableGeometry();
|
||||
QRect screenGeometry = qApp->primaryScreen()->geometry();
|
||||
|
||||
QDBusInterface primaryScreenInterface("org.ukui.SettingsDaemon",
|
||||
"/org/ukui/SettingsDaemon/wayland",
|
||||
"org.ukui.SettingsDaemon.wayland",
|
||||
QDBusConnection::sessionBus());
|
||||
if(QDBusReply<int>(primaryScreenInterface.call("x")).isValid()) {
|
||||
QDBusReply<int> x = primaryScreenInterface.call("x");
|
||||
QDBusReply<int> y = primaryScreenInterface.call("y");
|
||||
QDBusReply<int> width = primaryScreenInterface.call("width");
|
||||
QDBusReply<int> height = primaryScreenInterface.call("height");
|
||||
screenGeometry.setX(x);
|
||||
screenGeometry.setY(y);
|
||||
screenGeometry.setWidth(width);
|
||||
screenGeometry.setHeight(height);
|
||||
availableGeometry.setX(x);
|
||||
availableGeometry.setY(y);
|
||||
availableGeometry.setWidth(width);
|
||||
availableGeometry.setHeight(height);
|
||||
}
|
||||
|
||||
QDesktopWidget * desktopWidget = QApplication::desktop();
|
||||
QRect screenMainRect = desktopWidget->screenGeometry(0);//获取设备屏幕大小
|
||||
|
||||
QDBusInterface interface("com.ukui.panel.desktop",
|
||||
"/",
|
||||
"com.ukui.panel.desktop",
|
||||
QDBusConnection::sessionBus());
|
||||
|
||||
int position = QDBusReply<int>(interface.call("GetPanelPosition", "position"));
|
||||
int height = QDBusReply<int>(interface.call("GetPanelSize", "height"));
|
||||
int d = 8; //窗口边沿到任务栏距离
|
||||
|
||||
if(position == 0) {
|
||||
//任务栏在下侧
|
||||
this->move(availableGeometry.x() + availableGeometry.width() - this->width() - d, screenGeometry.y() + screenGeometry.height() - this->height() - height - d);
|
||||
} else if(position == 1) {
|
||||
//任务栏在上侧
|
||||
this->move(availableGeometry.x() + availableGeometry.width() - this->width() - d, screenGeometry.y() + height + d);
|
||||
} else if(position == 2) {
|
||||
//任务栏在左侧
|
||||
this->move(screenGeometry.x() + height + d, screenGeometry.y() + screenGeometry.height() - this->height() - d);
|
||||
} else if(position == 3) {
|
||||
//任务栏在右侧
|
||||
this->move(screenGeometry.x() + screenGeometry.width() - this->width() - height - d, screenGeometry.y() + screenGeometry.height() - this->height() - d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MainWindow::centerToScreen 使窗口显示在屏幕中间
|
||||
* @param widget
|
||||
*/
|
||||
void MainWindow::centerToScreen(QWidget* widget) {
|
||||
if(!widget)
|
||||
return;
|
||||
KWindowSystem::setState(this->winId(),NET::SkipTaskbar | NET::SkipPager);
|
||||
QDesktopWidget* m = QApplication::desktop();
|
||||
QRect desk_rect = m->screenGeometry(m->screenNumber(QCursor::pos()));
|
||||
int desk_x = desk_rect.width();
|
||||
int desk_y = desk_rect.height();
|
||||
int x = widget->width();
|
||||
int y = widget->height();
|
||||
// QDBusInterface primaryScreenInterface("org.ukui.SettingsDaemon",
|
||||
// "/org/ukui/SettingsDaemon/wayland",
|
||||
// "org.ukui.SettingsDaemon.wayland",
|
||||
// QDBusConnection::sessionBus());
|
||||
// if(QDBusReply<int>(primaryScreenInterface.call("x")).isValid()) {
|
||||
// QDBusReply<int> width = primaryScreenInterface.call("width");
|
||||
// QDBusReply<int> height = primaryScreenInterface.call("height");
|
||||
// desk_x = width;
|
||||
// desk_y = height;
|
||||
// }
|
||||
widget->move(desk_x / 2 - x / 2 + desk_rect.left(), desk_y / 3 + desk_rect.top());
|
||||
}
|
||||
|
||||
void MainWindow::initGsettings() {
|
||||
const QByteArray id(UKUI_SEARCH_SCHEMAS);
|
||||
if(QGSettings::isSchemaInstalled(id)) {
|
||||
m_search_gsettings = new QGSettings(id);
|
||||
connect(m_search_gsettings, &QGSettings::changed, this, [ = ](const QString & key) {
|
||||
if(key == SEARCH_METHOD_KEY) {
|
||||
bool is_index_search = m_search_gsettings->get(SEARCH_METHOD_KEY).toBool();
|
||||
this->setSearchMethod(is_index_search);
|
||||
} else if(key == WEB_ENGINE_KEY) {
|
||||
QString web_engine = m_search_gsettings->get(WEB_ENGINE_KEY).toString();
|
||||
GlobalSettings::getInstance()->setValue(WEB_ENGINE, web_engine);
|
||||
Q_EMIT this->webEngineChanged();
|
||||
}
|
||||
});
|
||||
if(m_search_gsettings->keys().contains(SEARCH_METHOD_KEY)) {
|
||||
bool is_index_search = m_search_gsettings->get(SEARCH_METHOD_KEY).toBool();
|
||||
this->setSearchMethod(is_index_search);
|
||||
}
|
||||
if(m_search_gsettings->keys().contains(WEB_ENGINE_KEY)) {
|
||||
QString web_engine = m_search_gsettings->get(WEB_ENGINE_KEY).toString();
|
||||
GlobalSettings::getInstance()->setValue(WEB_ENGINE, web_engine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//使用GSetting获取当前窗口应该使用的透明度
|
||||
double MainWindow::getTransparentData() {
|
||||
return GlobalSettings::getInstance()->getValue(TRANSPARENCY_KEY).toDouble();
|
||||
}
|
||||
|
||||
void MainWindow::initTimer() {
|
||||
m_askTimer = new QTimer;
|
||||
m_askTimer->setInterval(ASK_INDEX_TIME);
|
||||
connect(m_askTimer, &QTimer::timeout, this, [ = ]() {
|
||||
if(this->isVisible()) {
|
||||
m_isAskDialogVisible = true;
|
||||
m_askDialog->show();
|
||||
m_currentSearchAsked = true;
|
||||
}
|
||||
m_askTimer->stop();
|
||||
});
|
||||
m_researchTimer = new QTimer;
|
||||
m_researchTimer->setInterval(RESEARCH_TIME);
|
||||
connect(m_researchTimer, &QTimer::timeout, this, [ = ]() {
|
||||
if(this->isVisible()) {
|
||||
m_searchBarWidget->reSearch();
|
||||
}
|
||||
m_researchTimer->stop();
|
||||
});
|
||||
connect(m_searchBarWidget, &SeachBarWidget::requestSearchKeyword, this, [ = ](QString text) {
|
||||
if(text == "" || text.isEmpty()) {
|
||||
m_askTimer->stop();
|
||||
} else {
|
||||
//允许弹窗且当前次搜索(为关闭主界面,算一次搜索过程)未询问且当前为暴力搜索
|
||||
if(GlobalSettings::getInstance()->getValue(ENABLE_CREATE_INDEX_ASK_DIALOG).toString() != "false" && !m_currentSearchAsked && FileUtils::searchMethod == FileUtils::SearchMethod::DIRECTSEARCH)
|
||||
m_askTimer->start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MainWindow::tryHideMainwindow 尝试隐藏主界面并停止部分未完成的动作,重置部分状态值
|
||||
*/
|
||||
bool MainWindow::tryHideMainwindow()
|
||||
{
|
||||
if (!m_isAskDialogVisible && QApplication::activeModalWidget() == nullptr) {
|
||||
qDebug()<<"Mainwindow will be hidden";
|
||||
m_currentSearchAsked = false;
|
||||
this->hide();
|
||||
m_askTimer->stop();
|
||||
m_researchTimer->stop();
|
||||
Q_EMIT m_searchResultPage->stopSearch();
|
||||
return true;
|
||||
} else {
|
||||
//有上层弹窗未关闭,不允许隐藏主界面
|
||||
qWarning()<<"There is a dialog onside, so that mainwindow can not be hidden.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MainWindow::setSearchMethod 设置搜索模式
|
||||
* @param is_index_search true为索引搜索,false为暴力搜索
|
||||
*/
|
||||
void MainWindow::setSearchMethod(const bool &is_index_search) {
|
||||
if(is_index_search) {
|
||||
Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::INDEXSEARCH);
|
||||
} else {
|
||||
Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::DIRECTSEARCH);
|
||||
m_researchTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MainWindow::keyPressEvent 按esc键关闭主界面
|
||||
* @param event
|
||||
*/
|
||||
void MainWindow::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Escape) {
|
||||
tryHideMainwindow();
|
||||
} else if (event->key() == Qt::Key_Return or event->key() == Qt::Key_Enter) {
|
||||
if (!m_searchResultPage->isHidden()) {
|
||||
//显示最佳匹配中第一项的详情页,无搜索结果则调取网页搜索
|
||||
qDebug() << "Press Enter";
|
||||
m_searchResultPage->pressEnter();
|
||||
}
|
||||
} else if (event->key() == Qt::Key_Up) {
|
||||
qDebug() << "Press ↑";
|
||||
m_searchResultPage->pressUp();
|
||||
} else if (event->key() == Qt::Key_Down) {
|
||||
qDebug() << "Press ↓";
|
||||
if (!m_searchResultPage->getSelectedState()) {
|
||||
m_searchResultPage->pressEnter();
|
||||
} else {
|
||||
m_searchResultPage->pressDown();
|
||||
}
|
||||
}
|
||||
return QWidget::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::paintEvent(QPaintEvent *event) {
|
||||
|
||||
QPainterPath path;
|
||||
|
||||
path.addRoundedRect(m_searchBarWidget->x()+10, m_searchBarWidget->y()+10, m_searchBarWidget->width()-20, m_searchBarWidget->height()-20, 12, 12);
|
||||
path.addRoundedRect(m_searchResultPage->x()+10, m_searchResultPage->y()+10, m_searchResultPage->width()-20, m_searchResultPage->height()-20, 12, 12);
|
||||
|
||||
KWindowEffects::enableBlurBehind(this->winId(), true, QRegion(path.toFillPolygon().toPolygon()));
|
||||
|
||||
}
|
||||
|
||||
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
//kwin alt+f4发出close事件, 需要在存在子窗口时屏蔽该事件
|
||||
if ((watched == this) && (event->type() == QEvent::Close)) {
|
||||
event->ignore();
|
||||
tryHideMainwindow();
|
||||
return true;
|
||||
}
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
* Modified by: zhangpengfei <zhangpengfei@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainterPath>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
#include <QEvent>
|
||||
#include <QSpacerItem>
|
||||
#include <QKeyEvent>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QGraphicsDropShadowEffect>
|
||||
#include <QSettings>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QFileInfo>
|
||||
#include <QLabel>
|
||||
#include <QFrame>
|
||||
#include <QPushButton>
|
||||
#include <QKeyEvent>
|
||||
#include <QGSettings/QGSettings>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QTimer>
|
||||
|
||||
#include "index-generator.h"
|
||||
#include "libsearch.h"
|
||||
#include "create-index-ask-dialog.h"
|
||||
#include "search-line-edit.h"
|
||||
#include "search-result-page.h"
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
#include "xatom-helper.h"
|
||||
#endif
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0))
|
||||
#include "settings-widget.h"
|
||||
#endif
|
||||
|
||||
namespace UkuiSearch {
|
||||
class SearchResult;
|
||||
class MainWindow : public QMainWindow {
|
||||
friend class SearchResult;
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
/**
|
||||
* @brief Load the main window
|
||||
*/
|
||||
|
||||
// The position which mainwindow shows follow the ukui-panel.
|
||||
void moveToPanel();
|
||||
|
||||
// The position which mainwindow shows in the center of screen where the cursor in.
|
||||
void centerToScreen(QWidget* widget);
|
||||
void initGsettings();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *);
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
void initUi();
|
||||
void initConnections();
|
||||
|
||||
public:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void searchMethodChanged(FileUtils::SearchMethod);
|
||||
void webEngineChanged();
|
||||
void setText(QString keyword);
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* @brief Monitor screen resolution
|
||||
* @param rect: Screen resolution
|
||||
*/
|
||||
void monitorResolutionChange(QRect rect);
|
||||
/**
|
||||
* @brief Monitor primary screen changes
|
||||
* @param screen: Primary screen
|
||||
*/
|
||||
void primaryScreenChangedSlot(QScreen *screen);
|
||||
|
||||
void bootOptionsFilter(QString opt); // 过滤终端命令
|
||||
void clearSearchResult(); //清空搜索结果
|
||||
void trayIconActivatedSlot(QSystemTrayIcon::ActivationReason reason);
|
||||
void settingsBtnClickedSlot();
|
||||
void searchKeywordSlot(const QString&);
|
||||
void resizeHeight(int height);
|
||||
|
||||
private:
|
||||
|
||||
QWidget *m_widget = nullptr; // central widget
|
||||
QFrame * m_titleFrame = nullptr; // Title bar frame
|
||||
QHBoxLayout * m_titleLyt = nullptr; // Title layout
|
||||
QLabel * m_iconLabel = nullptr; // Icon lable
|
||||
QLabel * m_titleLabel = nullptr; // Title lable
|
||||
QPushButton * m_settingsBtn = nullptr; // Menu button
|
||||
// StackedWidget * m_stackedWidget = nullptr; // Stacked widget
|
||||
// SearchBarHLayout * m_searchLayout = nullptr; // Search bar layout
|
||||
// SeachBarWidget * m_searchWidget = nullptr; // Search bar
|
||||
SeachBarWidget * m_searchBarWidget;
|
||||
SearchResultPage * m_searchResultPage;
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0))
|
||||
SettingsWidget * m_settingsWidget = nullptr; // Settings Widget
|
||||
#endif
|
||||
|
||||
QStringList m_dirList;
|
||||
|
||||
QQueue<QString> * m_search_result_file = nullptr;
|
||||
QQueue<QString> * m_search_result_dir = nullptr;
|
||||
QQueue<QPair<QString, QStringList>> * m_search_result_content = nullptr;
|
||||
|
||||
QSystemTrayIcon * m_sys_tray_icon = nullptr;
|
||||
CreateIndexAskDialog * m_askDialog = nullptr;
|
||||
bool m_isAskDialogVisible = false;
|
||||
|
||||
QTimer * m_askTimer = nullptr; //询问是否创建索引弹窗弹出的计时器
|
||||
QTimer * m_researchTimer = nullptr; //创建索引后重新执行一次搜索的计时器
|
||||
bool m_currentSearchAsked = false; //本次搜索是否已经询问过是否创建索引了
|
||||
QGSettings * m_search_gsettings = nullptr;
|
||||
|
||||
void setSearchMethod(const bool&);
|
||||
double getTransparentData();
|
||||
void initTimer();
|
||||
bool tryHideMainwindow();
|
||||
void setSearchMethodConfig(const bool&, const bool&);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MAINWINDOW_H
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2021, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: jixiaoxu <jixiaoxu@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "best-list-model.h"
|
||||
#include "search-plugin-manager.h"
|
||||
using namespace UkuiSearch;
|
||||
|
||||
BestListModel::BestListModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
m_item = new SearchResultItem;
|
||||
initConnections();
|
||||
}
|
||||
|
||||
QModelIndex BestListModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if(row < 0 || row > m_item->m_result_info_list.length() - 1)
|
||||
return QModelIndex();
|
||||
return createIndex(row, column, m_item);
|
||||
}
|
||||
|
||||
QModelIndex BestListModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
int BestListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : (m_isExpanded ? m_item->m_result_info_list.length() : NUM_LIMIT_SHOWN_DEFAULT);
|
||||
}
|
||||
|
||||
int BestListModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
|
||||
QVariant BestListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
switch(role) {
|
||||
case Qt::DecorationRole: {
|
||||
return m_item->m_result_info_list.at(index.row()).icon;
|
||||
}
|
||||
case Qt::DisplayRole: {
|
||||
return m_item->m_result_info_list.at(index.row()).name;
|
||||
}
|
||||
case Qt::ToolTipRole: {
|
||||
return m_item->m_result_info_list.at(index.row()).name;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const SearchPluginIface::ResultInfo &BestListModel::getInfo(const QModelIndex &index)
|
||||
{
|
||||
return m_item->m_result_info_list.at(index.row());
|
||||
}
|
||||
|
||||
const QString &BestListModel::getPluginInfo(const QModelIndex &index)
|
||||
{
|
||||
return m_plugin_id_list.at(index.row());
|
||||
}
|
||||
|
||||
void BestListModel::setExpanded(const bool &is_expanded)
|
||||
{
|
||||
this->beginResetModel();
|
||||
m_isExpanded = is_expanded;
|
||||
this->endResetModel();
|
||||
Q_EMIT this->itemListChanged(m_item->m_result_info_list.length());
|
||||
}
|
||||
|
||||
const bool &BestListModel::isExpanded()
|
||||
{
|
||||
return m_isExpanded;
|
||||
}
|
||||
|
||||
QStringList BestListModel::getActions(const QModelIndex &index)
|
||||
{
|
||||
// if (m_item->m_result_info_list.length() > index.row() && index.row() >= 0)
|
||||
// return m_item->m_result_info_list.at(index.row()).actionList;
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
QString BestListModel::getKey(const QModelIndex &index)
|
||||
{
|
||||
if (m_item->m_result_info_list.length() > index.row() && index.row() >= 0)
|
||||
return m_item->m_result_info_list.at(index.row()).actionKey;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void BestListModel::appendInfo(const QString &pluginId, const SearchPluginIface::ResultInfo &info)
|
||||
{
|
||||
if (m_plugin_action_key_list.contains(info.actionKey)) {
|
||||
// qDebug() << "plugin ID:" << pluginId << "name:" << info.name << "action key:" << info.actionKey << "is same with pre-result!";
|
||||
return;
|
||||
} else {
|
||||
m_plugin_action_key_list.append(info.actionKey);
|
||||
}
|
||||
if (m_plugin_id_list.contains(pluginId)) {
|
||||
if (info.name == m_item->m_result_info_list.at(m_plugin_id_list.lastIndexOf(pluginId)).name) {
|
||||
return;
|
||||
}
|
||||
// qDebug()<<"plugin ID:"<<pluginId<<"Repalce result. name ="<<info.name;
|
||||
this->beginResetModel();
|
||||
m_item->m_result_info_list.replace(m_plugin_id_list.lastIndexOf(pluginId), info);
|
||||
this->endResetModel();
|
||||
return;
|
||||
}
|
||||
this->beginResetModel();
|
||||
// qDebug()<<"plugin ID:"<<pluginId<<"Got a result. name ="<<info.name;
|
||||
m_plugin_id_list.append(pluginId);
|
||||
m_item->m_result_info_list.append(info);
|
||||
QVector<SearchPluginIface::ResultInfo> result_info_list_tmp;
|
||||
QVector<QString> plugin_id_list_tmp;
|
||||
QStringList plugin_order = SearchPluginManager::getInstance()->getPluginIds();
|
||||
Q_FOREACH (QString plugin, plugin_order) {
|
||||
if (m_plugin_id_list.contains(plugin)) {
|
||||
result_info_list_tmp.append(m_item->m_result_info_list.at(m_plugin_id_list.lastIndexOf(plugin)));
|
||||
plugin_id_list_tmp.append(plugin);
|
||||
}
|
||||
}
|
||||
m_item->m_result_info_list.clear();
|
||||
m_item->m_result_info_list.swap(result_info_list_tmp);
|
||||
m_plugin_id_list.clear();
|
||||
m_plugin_id_list.swap(plugin_id_list_tmp);
|
||||
this->endResetModel();
|
||||
Q_EMIT this->itemListChanged(m_item->m_result_info_list.length());
|
||||
|
||||
}
|
||||
|
||||
void BestListModel::startSearch(const QString &keyword)
|
||||
{
|
||||
if (!m_item->m_result_info_list.isEmpty()) {
|
||||
this->beginResetModel();
|
||||
m_plugin_id_list.clear();
|
||||
m_plugin_action_key_list.clear();
|
||||
m_item->m_result_info_list.clear();
|
||||
this->endResetModel();
|
||||
Q_EMIT this->itemListChanged(m_item->m_result_info_list.length());
|
||||
}
|
||||
}
|
||||
|
||||
void BestListModel::initConnections()
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef BESTLISTMODEL_H
|
||||
#define BESTLISTMODEL_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include "search-result-model.h"
|
||||
|
||||
#define NUM_LIMIT_SHOWN_DEFAULT 5
|
||||
|
||||
namespace UkuiSearch {
|
||||
class BestListModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BestListModel(QObject *parent = nullptr);
|
||||
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
const SearchPluginIface::ResultInfo & getInfo(const QModelIndex&);
|
||||
const QString & getPluginInfo(const QModelIndex&);
|
||||
|
||||
void setExpanded(const bool&);
|
||||
const bool &isExpanded();
|
||||
|
||||
QStringList getActions(const QModelIndex &);
|
||||
QString getKey(const QModelIndex &);
|
||||
|
||||
public Q_SLOTS:
|
||||
void appendInfo(const QString &, const SearchPluginIface::ResultInfo &);
|
||||
void startSearch(const QString &);
|
||||
|
||||
Q_SIGNALS:
|
||||
void stopSearch();
|
||||
void itemListChanged(const int&);
|
||||
|
||||
private:
|
||||
void initConnections();
|
||||
SearchResultItem * m_item = nullptr;
|
||||
QVector<QString> m_plugin_id_list;
|
||||
QVector<QString> m_plugin_action_key_list;
|
||||
bool m_isExpanded = false;
|
||||
};
|
||||
}
|
||||
#endif // BESTLISTMODEL_H
|
|
@ -0,0 +1,13 @@
|
|||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/best-list-model.h \
|
||||
$$PWD/search-result-manager.h \
|
||||
$$PWD/search-result-model.h \
|
||||
$$PWD/web-search-model.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/best-list-model.cpp \
|
||||
$$PWD/search-result-manager.cpp \
|
||||
$$PWD/search-result-model.cpp \
|
||||
$$PWD/web-search-model.cpp
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "search-result-manager.h"
|
||||
|
||||
using namespace UkuiSearch;
|
||||
SearchResultManager::SearchResultManager(const QString& plugin_id, QObject *parent) : QObject(parent)
|
||||
{
|
||||
m_plugin_id = plugin_id;
|
||||
m_result_queue = new DataQueue<SearchPluginIface::ResultInfo>;
|
||||
m_get_result_thread = new ReceiveResultThread(m_result_queue, this);
|
||||
initConnections();
|
||||
}
|
||||
|
||||
void SearchResultManager::startSearch(const QString &keyword)
|
||||
{
|
||||
qDebug()<<m_plugin_id<<"started";
|
||||
if(! m_get_result_thread->isRunning()) {
|
||||
m_get_result_thread->start();
|
||||
}
|
||||
m_result_queue->clear();
|
||||
SearchPluginIface *plugin = SearchPluginManager::getInstance()->getPlugin(m_plugin_id);
|
||||
plugin->KeywordSearch(keyword, m_result_queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SearchResultManager::stopSearch 停止搜索,开始一次新搜索前或主界面退出时执行
|
||||
*/
|
||||
void SearchResultManager::stopSearch()
|
||||
{
|
||||
if(m_get_result_thread->isRunning()) {
|
||||
qDebug()<<m_plugin_id<<"stopped";
|
||||
m_get_result_thread->stop();
|
||||
SearchPluginIface *plugin = SearchPluginManager::getInstance()->getPlugin(m_plugin_id);
|
||||
plugin->stopSearch();
|
||||
// m_get_result_thread->quit();
|
||||
}
|
||||
}
|
||||
|
||||
void SearchResultManager::initConnections()
|
||||
{
|
||||
connect(m_get_result_thread, &ReceiveResultThread::gotResultInfo, this, &SearchResultManager::gotResultInfo);
|
||||
}
|
||||
|
||||
ReceiveResultThread::ReceiveResultThread(DataQueue<SearchPluginIface::ResultInfo> * result_queue, QObject *parent)
|
||||
{
|
||||
m_result_queue = result_queue;
|
||||
}
|
||||
|
||||
void ReceiveResultThread::stop()
|
||||
{
|
||||
this->requestInterruption();
|
||||
this->quit();
|
||||
}
|
||||
|
||||
void ReceiveResultThread::run()
|
||||
{
|
||||
QTimer * m_timer = new QTimer;
|
||||
m_timer->setInterval(3000);
|
||||
bool is_empty;
|
||||
while(!isInterruptionRequested()) {
|
||||
is_empty = false;
|
||||
if(!m_result_queue->isEmpty()) {
|
||||
Q_EMIT this->gotResultInfo(m_result_queue->dequeue());
|
||||
|
||||
} else {
|
||||
is_empty = true;
|
||||
}
|
||||
if(m_timer->isActive() && m_timer->remainingTime() < 0.01) {
|
||||
this->requestInterruption();
|
||||
qWarning()<<"-------------->stopped by itself";
|
||||
}
|
||||
if(is_empty && !m_timer->isActive()) {
|
||||
m_timer->start();
|
||||
} else if(!is_empty) {
|
||||
m_timer->stop();
|
||||
} else {
|
||||
msleep(100);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#ifndef SEARCHRESULTMANAGER_H
|
||||
#define SEARCHRESULTMANAGER_H
|
||||
|
||||
#include <QQueue>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
#include "pluginmanage/search-plugin-manager.h"
|
||||
|
||||
namespace UkuiSearch {
|
||||
|
||||
class ReceiveResultThread : public QThread {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ReceiveResultThread(DataQueue<SearchPluginIface::ResultInfo> * result_queue, QObject * parent = nullptr);
|
||||
~ReceiveResultThread() = default;
|
||||
void stop();
|
||||
protected:
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
DataQueue<SearchPluginIface::ResultInfo> * m_result_queue;
|
||||
|
||||
Q_SIGNALS:
|
||||
void gotResultInfo(const SearchPluginIface::ResultInfo&);
|
||||
|
||||
};
|
||||
|
||||
class SearchResultManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SearchResultManager(const QString &plugin_id, QObject *parent = nullptr);
|
||||
~SearchResultManager() = default;
|
||||
|
||||
public Q_SLOTS:
|
||||
void startSearch(const QString &);
|
||||
void stopSearch();
|
||||
|
||||
private:
|
||||
void initConnections();
|
||||
QString m_plugin_id;
|
||||
DataQueue<SearchPluginIface::ResultInfo> * m_result_queue;
|
||||
ReceiveResultThread * m_get_result_thread = nullptr;
|
||||
|
||||
Q_SIGNALS:
|
||||
void gotResultInfo(const SearchPluginIface::ResultInfo&);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SEARCHRESULTMANAGER_H
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "search-result-model.h"
|
||||
using namespace UkuiSearch;
|
||||
|
||||
SearchResultModel::SearchResultModel(const QString &plugin_id)
|
||||
{
|
||||
m_item = new SearchResultItem;
|
||||
m_plugin_id = plugin_id;
|
||||
m_search_manager = new SearchResultManager(plugin_id);
|
||||
m_timer = new QTimer(this);
|
||||
m_timer->setInterval(500);
|
||||
initConnections();
|
||||
}
|
||||
|
||||
QModelIndex SearchResultModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if(row < 0 || row > m_item->m_result_info_list.length() - 1)
|
||||
return QModelIndex();
|
||||
// QVector<SearchPluginIface::ResultInfo> * m_info = &m_result_info_list;
|
||||
return createIndex(row, column, m_item);
|
||||
}
|
||||
|
||||
QModelIndex SearchResultModel::parent(const QModelIndex &child) const
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
int SearchResultModel::rowCount(const QModelIndex &index) const
|
||||
{
|
||||
return index.isValid() ? 0 : (m_isExpanded ? m_item->m_result_info_list.length() : NUM_LIMIT_SHOWN_DEFAULT);
|
||||
}
|
||||
|
||||
int SearchResultModel::columnCount(const QModelIndex &index) const
|
||||
{
|
||||
return index.isValid() ? 0 : 1;
|
||||
}
|
||||
|
||||
QVariant SearchResultModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
switch(role) {
|
||||
case Qt::DecorationRole: {
|
||||
return m_item->m_result_info_list.at(index.row()).icon;
|
||||
}
|
||||
case Qt::DisplayRole: {
|
||||
return m_item->m_result_info_list.at(index.row()).name;
|
||||
}
|
||||
case Qt::ToolTipRole: {
|
||||
return m_item->m_result_info_list.at(index.row()).name;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void SearchResultModel::appendInfo(const SearchPluginIface::ResultInfo &info)//TODO 代码逻辑可尝试梳理优化
|
||||
{
|
||||
if (m_item->m_result_info_list.length() > 5 //搜索结果大于5个并且搜索结果处于收起状态时只存储数据无需刷新UI
|
||||
and !m_isExpanded) {
|
||||
m_item->m_result_info_list.append(info);
|
||||
return;
|
||||
}
|
||||
if (m_item->m_result_info_list.length() > 50
|
||||
and m_isExpanded) {//搜索结果大于50个并且搜索结果处于展开状态时只存储数据并启动定时,500ms刷新一次UI
|
||||
m_item->m_result_info_list.append(info);
|
||||
if (!m_timer->isActive()) {
|
||||
m_timer->start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this->beginResetModel();
|
||||
m_item->m_result_info_list.append(info);
|
||||
this->endResetModel();
|
||||
Q_EMIT this->itemListChanged(m_item->m_result_info_list.length());
|
||||
if (m_plugin_id != "Web Page") {
|
||||
Q_EMIT this->sendBestListData(m_plugin_id, m_item->m_result_info_list.at(0));
|
||||
}
|
||||
}
|
||||
|
||||
void SearchResultModel::startSearch(const QString &keyword)
|
||||
{
|
||||
if (!m_item->m_result_info_list.isEmpty()) {
|
||||
this->beginResetModel();
|
||||
m_item->m_result_info_list.clear();
|
||||
this->endResetModel();
|
||||
Q_EMIT this->itemListChanged(m_item->m_result_info_list.length());
|
||||
}
|
||||
m_search_manager->startSearch(keyword);
|
||||
}
|
||||
|
||||
void SearchResultModel::initConnections()
|
||||
{
|
||||
connect(this, &SearchResultModel::stopSearch, m_search_manager, &SearchResultManager::stopSearch);
|
||||
connect(m_search_manager, &SearchResultManager::gotResultInfo, this, &SearchResultModel::appendInfo);
|
||||
connect(m_timer, &QTimer::timeout, [ = ] () {//500ms刷新一次UI,防止搜索结果数据量过大导致的UI卡顿
|
||||
Q_EMIT this->itemListChanged(m_item->m_result_info_list.length());
|
||||
m_timer->stop();
|
||||
});
|
||||
}
|
||||
|
||||
const SearchPluginIface::ResultInfo &SearchResultModel::getInfo(const QModelIndex &index)
|
||||
{
|
||||
return m_item->m_result_info_list.at(index.row());
|
||||
}
|
||||
|
||||
void SearchResultModel::setExpanded(const bool &is_expanded)
|
||||
{
|
||||
this->beginResetModel();
|
||||
m_isExpanded = is_expanded;
|
||||
this->endResetModel();
|
||||
Q_EMIT this->itemListChanged(m_item->m_result_info_list.length());
|
||||
}
|
||||
|
||||
const bool &SearchResultModel::isExpanded()
|
||||
{
|
||||
return m_isExpanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SearchResultModel::getActions 获取操作列表
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
QStringList SearchResultModel::getActions(const QModelIndex &index)
|
||||
{
|
||||
if (m_item->m_result_info_list.length() > index.row() && index.row() >= 0)
|
||||
// return m_item->m_result_info_list.at(index.row()).actionList;
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
QString SearchResultModel::getKey(const QModelIndex &index)
|
||||
{
|
||||
if (m_item->m_result_info_list.length() > index.row() && index.row() >= 0)
|
||||
// return m_item->m_result_info_list.at(index.row()).key;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void SearchResultModel::refresh()
|
||||
{
|
||||
this->beginResetModel();
|
||||
this->endResetModel();
|
||||
}
|
||||
|
||||
SearchResultItem::SearchResultItem(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#ifndef SEARCHRESULTMODEL_H
|
||||
#define SEARCHRESULTMODEL_H
|
||||
#include <QAbstractItemModel>
|
||||
#include "search-result-manager.h"
|
||||
|
||||
#define NUM_LIMIT_SHOWN_DEFAULT 5
|
||||
|
||||
namespace UkuiSearch {
|
||||
|
||||
class SearchResultItem : public QObject {
|
||||
friend class SearchResultModel;
|
||||
friend class BestListModel;
|
||||
friend class WebSearchModel;
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SearchResultItem(QObject *parent = nullptr);
|
||||
~SearchResultItem() = default;
|
||||
private:
|
||||
//此插件所有搜索结果<具体信息>
|
||||
QVector<SearchPluginIface::ResultInfo> m_result_info_list;
|
||||
};
|
||||
class SearchResultModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SearchResultModel(const QString &plugin_id);
|
||||
~SearchResultModel() = default;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex &child) const override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
// bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex())override;
|
||||
const SearchPluginIface::ResultInfo & getInfo(const QModelIndex&);
|
||||
void setExpanded(const bool&);
|
||||
const bool &isExpanded();
|
||||
QStringList getActions(const QModelIndex &);
|
||||
QString getKey(const QModelIndex &);
|
||||
void refresh();
|
||||
|
||||
public Q_SLOTS:
|
||||
void appendInfo(const SearchPluginIface::ResultInfo &);
|
||||
void startSearch(const QString &);
|
||||
|
||||
Q_SIGNALS:
|
||||
void stopSearch();
|
||||
void itemListChanged(const int&);
|
||||
void sendBestListData(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
|
||||
private:
|
||||
void initConnections();
|
||||
SearchResultItem * m_item = nullptr;
|
||||
QString m_plugin_id;
|
||||
SearchResultManager * m_search_manager = nullptr;
|
||||
bool m_isExpanded = false;
|
||||
QTimer * m_timer;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SEARCHRESULTMODEL_H
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2021, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: jixiaoxu <jixiaoxu@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "web-search-model.h"
|
||||
#include <QDebug>
|
||||
|
||||
using namespace UkuiSearch;
|
||||
WebSearchModel::WebSearchModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
m_item = new SearchResultItem;
|
||||
}
|
||||
|
||||
QModelIndex WebSearchModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if(row < 0 || row > m_item->m_result_info_list.length() - 1)
|
||||
return QModelIndex();
|
||||
return createIndex(row, column, m_item);
|
||||
}
|
||||
|
||||
QModelIndex WebSearchModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
int WebSearchModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
int WebSearchModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
|
||||
QVariant WebSearchModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
switch(role) {
|
||||
case Qt::DecorationRole: {
|
||||
return m_item->m_result_info_list.at(index.row()).icon;
|
||||
}
|
||||
case Qt::DisplayRole: {
|
||||
return m_item->m_result_info_list.at(index.row()).name;
|
||||
}
|
||||
case Qt::ToolTipRole: {
|
||||
return m_item->m_result_info_list.at(index.row()).name;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void WebSearchModel::startSearch(const QString &keyword)
|
||||
{
|
||||
this->beginResetModel();
|
||||
m_item->m_result_info_list.clear();
|
||||
SearchPluginIface::ResultInfo info;
|
||||
info.icon = QIcon(":/res/icons/edit-find-symbolic.svg");
|
||||
info.name = keyword;
|
||||
m_item->m_result_info_list.append(info);
|
||||
this->endResetModel();
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef WEBSEARCHMODEL_H
|
||||
#define WEBSEARCHMODEL_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QIcon>
|
||||
#include <QLabel>
|
||||
#include "search-result-model.h"
|
||||
|
||||
namespace UkuiSearch {
|
||||
class WebSearchModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WebSearchModel(QObject *parent = nullptr);
|
||||
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
public Q_SLOTS:
|
||||
void startSearch(const QString &);
|
||||
|
||||
private:
|
||||
SearchResultItem * m_item = nullptr;
|
||||
};
|
||||
}
|
||||
#endif // WEBSEARCHMODEL_H
|
|
@ -0,0 +1 @@
|
|||
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{opacity:0.85;}</style></defs><title>close</title><path class="cls-1" d="M8.71,8l5.14-5.15a.49.49,0,0,0-.7-.7L8,7.29,2.85,2.15a.49.49,0,0,0-.7.7L7.29,8,2.15,13.15a.48.48,0,0,0,0,.7.48.48,0,0,0,.7,0L8,8.71l5.15,5.14a.48.48,0,0,0,.7,0,.48.48,0,0,0,0-.7Z"/></svg>
|
After Width: | Height: | Size: 374 B |
After Width: | Height: | Size: 998 B |
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>edit-find-symbolic</title>
|
||||
<g id="Icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Icon-Sets" transform="translate(-68.000000, -2529.000000)" fill-rule="nonzero">
|
||||
<g id="编组-9" transform="translate(0.000000, 2399.000000)">
|
||||
<g id="编组" transform="translate(68.000000, 130.000000)">
|
||||
<polygon id="矩形备份" fill="#000000" opacity="0" points="0 0 16 0 16 16 0 16"/>
|
||||
<path d="M6.86630772,1 C8.48762671,1 9.95614366,1.64816049 11.0189488,2.69780957 C12.0771269,3.74288891 12.7327736,5.18634211 12.7327736,6.78146875 C12.7327736,8.28192337 12.1523076,9.64806734 11.2023613,10.6749557 L11.2023613,10.6749557 L14.9869478,14.4127374 L10.6532367,11.3057557 C9.58274699,12.0665702 8.28388099,12.5629531 6.86630772,12.5629531 C5.2449219,12.5629531 3.77647181,11.9148388 2.71373977,10.8652353 C1.65557494,9.82014255 1,8.37664384 1,6.78146875 C1,5.18630217 1.65557811,3.74280894 2.71374274,2.69771882 C3.77647668,1.64811584 5.24492743,1 6.86630772,1 Z" id="路径" stroke="#2FB3E8" stroke-width="2"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="98px" height="87px" viewBox="0 0 98 87" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 59 (86127) - https://sketch.com -->
|
||||
<title>image-viewer-app-symbolic</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<filter x="-2.4%" y="-2.7%" width="104.7%" height="105.3%" filterUnits="objectBoundingBox" id="filter-1">
|
||||
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="4" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
||||
<feMerge>
|
||||
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="综合搜索" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.200000003">
|
||||
<g id="搜索结果——网页" transform="translate(-883.000000, -627.000000)">
|
||||
<g id="编组-2" filter="url(#filter-1)" transform="translate(447.000000, 427.000000)">
|
||||
<g id="image-viewer-app-symbolic" transform="translate(436.000000, 200.000000)">
|
||||
<g id="图层_2" transform="translate(4.000000, 5.000000)">
|
||||
<path d="M24,72 L12,72 C5.4,72 0,66.6 0,60 L0,12 C0,5.4 5.4,0 12,0 L78,0 C84.6,0 90,5.4 90,12 L90,60" id="路径" stroke="#1F2022" stroke-width="6" stroke-linecap="round"></path>
|
||||
<path d="M51,15 C34.2,15 21,28.2 21,45 C21,61.8 34.2,75 51,75 C67.8,75 81,61.8 81,45 C81,28.2 67.8,15 51,15 Z M51,21 C64.2,21 75,31.8 75,45 C75,58.2 64.2,69 51,69 C37.8,69 27,58.2 27,45 C27,31.8 37.8,21 51,21 Z" id="circle2567-9" fill="#1F2022" fill-rule="nonzero"></path>
|
||||
<path d="M71.4,60.6 L67.2,64.8 L80.1,77.7 C81.259798,78.859798 83.140202,78.859798 84.3,77.7 C85.459798,76.540202 85.459798,74.659798 84.3,73.5 L71.4,60.6 L71.4,60.6 Z" id="path2570-36" fill="#1F2022"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 498 B |
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_2_1_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{opacity:0.75;fill:#1F2022;}
|
||||
</style>
|
||||
<path class="st0" d="M8,11c-0.1,0-0.3,0-0.4-0.1l-4-4c-0.2-0.2-0.2-0.5,0-0.7s0.5-0.2,0.7,0L8,9.8l3.6-3.6c0.2-0.2,0.5-0.2,0.7,0
|
||||
s0.2,0.5,0,0.7l-4,4C8.3,11,8.1,11,8,11z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 603 B |
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_15" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{display:none;opacity:0.75;fill:#1F2022;enable-background:new ;}
|
||||
.st1{opacity:0.75;fill:#1F2022;}
|
||||
</style>
|
||||
<path class="st0" d="M13.5,11.5c-0.1,0-0.3-0.1-0.4-0.2L8,5.3l-5.1,6c-0.2,0.2-0.5,0.2-0.7,0.1C2,11.2,2,10.9,2.1,10.7l5.5-6.5
|
||||
C7.8,4,8.2,4,8.4,4.2l5.5,6.5c0.2,0.2,0.2,0.5-0.1,0.7C13.7,11.5,13.6,11.5,13.5,11.5z"/>
|
||||
<path class="st1" d="M12,10c-0.1,0-0.3,0-0.4-0.1L8,6.2L4.4,9.9C4.2,10,3.8,10,3.6,9.9s-0.2-0.5,0-0.7l4-4C7.8,5,8.2,5,8.4,5.1l4,4
|
||||
c0.2,0.2,0.2,0.5,0,0.7C12.3,10,12.1,10,12,10z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 897 B |
|
@ -0,0 +1,12 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>res/icons/edit-find-symbolic.svg</file>
|
||||
<file>res/icons/desktop.png</file>
|
||||
<file>res/icons/close.svg</file>
|
||||
<file>res/qt-translations/qt_zh_CN.qm</file>
|
||||
<file>res/icons/net-disconnected.svg</file>
|
||||
<file>res/icons/system-search.symbolic.png</file>
|
||||
<file>res/icons/ukui-up-symbolic.svg</file>
|
||||
<file>res/icons/ukui-down-symbolic.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
|
@ -0,0 +1,31 @@
|
|||
#include "ukui-search-dbus-service.h"
|
||||
|
||||
using namespace UkuiSearch;
|
||||
void UkuiSearchDbusServices::showWindow(){
|
||||
qDebug() << "showWindow called";
|
||||
m_mainWindow->bootOptionsFilter("-s");
|
||||
}
|
||||
|
||||
void UkuiSearchDbusServices::searchKeyword(QString keyword)
|
||||
{
|
||||
showWindow();
|
||||
m_mainWindow->setText(keyword);
|
||||
}
|
||||
|
||||
UkuiSearchDbusServices::UkuiSearchDbusServices(MainWindow *m)
|
||||
{
|
||||
m_mainWindow = m;
|
||||
//注册服务
|
||||
QDBusConnection sessionBus = QDBusConnection::sessionBus();
|
||||
QDBusConnection::sessionBus().unregisterService("com.ukui.search.service");
|
||||
if(!sessionBus.registerService("com.ukui.search.service")){
|
||||
qWarning() << "ukui-search dbus register service failed reason:" << sessionBus.lastError();
|
||||
}
|
||||
|
||||
if(!sessionBus.registerObject("/", this, QDBusConnection::ExportAllSlots)){
|
||||
qWarning() << "ukui-search dbus register object failed reason:" << sessionBus.lastError();
|
||||
}
|
||||
}
|
||||
|
||||
UkuiSearchDbusServices::~UkuiSearchDbusServices(){
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef UKUISEARCHDBUSSERVICE_H
|
||||
#define UKUISEARCHDBUSSERVICE_H
|
||||
|
||||
#include <QDBusConnection>
|
||||
#include <QObject>
|
||||
|
||||
#include "mainwindow.h"
|
||||
|
||||
namespace UkuiSearch {
|
||||
|
||||
class UkuiSearchDbusServices: public QObject{
|
||||
Q_OBJECT
|
||||
|
||||
Q_CLASSINFO("D-Bus Interface","org.ukui.search.service")
|
||||
|
||||
public:
|
||||
explicit UkuiSearchDbusServices(MainWindow *m);
|
||||
~UkuiSearchDbusServices();
|
||||
|
||||
public Q_SLOTS:
|
||||
void showWindow();
|
||||
void searchKeyword(QString keyword);
|
||||
|
||||
private:
|
||||
MainWindow *m_mainWindow;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // UKUISEARCHDBUSSERVICE_H
|
|
@ -0,0 +1,111 @@
|
|||
#include "ukui-search-gui.h"
|
||||
#include <QScreen>
|
||||
#include <QTranslator>
|
||||
#include <QLocale>
|
||||
#include <QApplication>
|
||||
#include <QCommandLineParser>
|
||||
#include "plugin-manager.h"
|
||||
#include "search-plugin-manager.h"
|
||||
#include "document.h"
|
||||
|
||||
using namespace UkuiSearch;
|
||||
UkuiSearchGui::UkuiSearchGui(int &argc, char *argv[], const QString &applicationName): QtSingleApplication (applicationName, argc, argv)
|
||||
{
|
||||
qDebug()<<"ukui search gui constructor start" << applicationName;
|
||||
setApplicationVersion(QString("v%1").arg(VERSION));
|
||||
if (!this->isRunning()) {
|
||||
connect(this, &QtSingleApplication::messageReceived, [=](QString msg) {
|
||||
this->parseCmd(msg, true);
|
||||
});
|
||||
|
||||
setQuitOnLastWindowClosed(false);
|
||||
|
||||
// qRegisterMetaType<QPair<QString, QStringList>>("QPair<QString,QStringList>");
|
||||
// qRegisterMetaType<UkuiSearch::Document>("Document");
|
||||
|
||||
|
||||
//load translations.
|
||||
QTranslator *translator = new QTranslator(this);
|
||||
try {
|
||||
if(! translator->load("/usr/share/ukui-search/translations/" + QLocale::system().name())) throw - 1;
|
||||
this->installTranslator(translator);
|
||||
} catch(...) {
|
||||
qDebug() << "Load translations file" << QLocale() << "failed!";
|
||||
}
|
||||
|
||||
QTranslator *qt_translator = new QTranslator(this);
|
||||
try {
|
||||
if(! qt_translator->load(":/res/qt-translations/qt_zh_CN.qm")) throw - 1;
|
||||
this->installTranslator(qt_translator);
|
||||
} catch(...) {
|
||||
qDebug() << "Load translations file" << QLocale() << "failed!";
|
||||
}
|
||||
|
||||
QTranslator *lib_translator = new QTranslator(this);
|
||||
try {
|
||||
if(! lib_translator->load("/usr/share/ukui-search/translations/libukui-search_" + QLocale::system().name())) throw - 1;
|
||||
this->installTranslator(lib_translator);
|
||||
} catch(...) {
|
||||
qDebug() << "Load translations file" << QLocale() << "failed!";
|
||||
}
|
||||
|
||||
qDebug() << "Loading plugins and resources...";
|
||||
SearchPluginManager::getInstance();
|
||||
PluginManager::getInstance();
|
||||
|
||||
m_mainWindow = new UkuiSearch::MainWindow();
|
||||
m_dbusService = new UkuiSearch::UkuiSearchDbusServices(m_mainWindow);
|
||||
qApp->setWindowIcon(QIcon::fromTheme("kylin-search"));
|
||||
this->setActivationWindow(m_mainWindow);
|
||||
}
|
||||
|
||||
//parse cmd
|
||||
qDebug()<<"parse cmd";
|
||||
auto message = this->arguments().join(' ').toUtf8();
|
||||
parseCmd(message, !isRunning());
|
||||
|
||||
qDebug()<<"ukui search gui constructor end";
|
||||
}
|
||||
|
||||
UkuiSearchGui::~UkuiSearchGui()
|
||||
{
|
||||
if(m_mainWindow) {
|
||||
delete m_mainWindow;
|
||||
m_mainWindow = nullptr;
|
||||
}
|
||||
if(m_dbusService) {
|
||||
delete m_dbusService;
|
||||
m_dbusService = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void UkuiSearchGui::parseCmd(QString msg, bool isPrimary)
|
||||
{
|
||||
QCommandLineParser parser;
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
|
||||
QCommandLineOption quitOption(QStringList()<<"q"<<"quit", tr("Quit ukui-search application"));
|
||||
parser.addOption(quitOption);
|
||||
|
||||
QCommandLineOption showOption(QStringList()<<"s"<<"show", tr("Show main window"));
|
||||
parser.addOption(showOption);
|
||||
|
||||
if (isPrimary) {
|
||||
const QStringList args = QString(msg).split(' ');
|
||||
parser.process(args);
|
||||
if(parser.isSet(showOption)) {
|
||||
m_mainWindow->bootOptionsFilter("-s");
|
||||
}
|
||||
if (parser.isSet(quitOption)) {
|
||||
this->quit();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (arguments().count() < 2) {
|
||||
parser.showHelp();
|
||||
}
|
||||
parser.process(arguments());
|
||||
sendMessage(msg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef UKUISEARCHGUI_H
|
||||
#define UKUISEARCHGUI_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "qtsingleapplication.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ukui-search-dbus-service.h"
|
||||
#undef Bool
|
||||
namespace UkuiSearch {
|
||||
class UkuiSearchGui : public QtSingleApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
UkuiSearchGui(int &argc, char *argv[], const QString &applicationName = "ukui-search-gui");
|
||||
~UkuiSearchGui();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void parseCmd(QString msg, bool isPrimary);
|
||||
|
||||
private:
|
||||
MainWindow *m_mainWindow = nullptr;
|
||||
UkuiSearchDbusServices *m_dbusService = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // UKUISEARCHGUI_H
|
|
@ -0,0 +1,342 @@
|
|||
#include "best-list-view.h"
|
||||
#define MAIN_MARGINS 0,0,0,0
|
||||
#define MAIN_SPACING 0
|
||||
#define TITLE_HEIGHT 30
|
||||
#define UNFOLD_LABEL_HEIGHT 30
|
||||
#define VIEW_ICON_SIZE 24
|
||||
|
||||
using namespace UkuiSearch;
|
||||
BestListView::BestListView(QWidget *parent) : QTreeView(parent)
|
||||
{
|
||||
// setStyle(ResultItemStyle::getStyle());
|
||||
this->setFrameShape(QFrame::NoFrame);
|
||||
this->viewport()->setAutoFillBackground(false);
|
||||
this->setIconSize(QSize(VIEW_ICON_SIZE, VIEW_ICON_SIZE));
|
||||
this->setRootIsDecorated(false);
|
||||
this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
this->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
this->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
this->setHeaderHidden(true);
|
||||
m_model = new BestListModel(this);
|
||||
this->setModel(m_model);
|
||||
initConnections();
|
||||
m_styleDelegate = new ResultViewDelegate(this);
|
||||
this->setItemDelegate(m_styleDelegate);
|
||||
}
|
||||
|
||||
bool BestListView::isSelected()
|
||||
{
|
||||
return m_is_selected;
|
||||
}
|
||||
|
||||
int BestListView::showHeight()
|
||||
{
|
||||
int height;
|
||||
int rowheight = this->rowHeight(this->model()->index(0, 0, QModelIndex()));
|
||||
if (this->isExpanded()) {
|
||||
height = m_count * rowheight;
|
||||
} else {
|
||||
int show_count = m_count > NUM_LIMIT_SHOWN_DEFAULT ? NUM_LIMIT_SHOWN_DEFAULT : m_count;
|
||||
height = show_count * rowheight;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
int BestListView::getResultNum()
|
||||
{
|
||||
return m_count;
|
||||
}
|
||||
|
||||
QModelIndex BestListView::getModlIndex(int row, int column)
|
||||
{
|
||||
return this->m_model->index(row, column);
|
||||
}
|
||||
|
||||
SearchPluginIface::ResultInfo BestListView::getIndexResultInfo(QModelIndex &index)
|
||||
{
|
||||
return this->m_model->getInfo(index);
|
||||
}
|
||||
|
||||
const QString BestListView::getPluginInfo(const QModelIndex& index)
|
||||
{
|
||||
return this->m_model->getPluginInfo(index);
|
||||
}
|
||||
|
||||
int BestListView::getResultHeight()
|
||||
{
|
||||
return this->rowHeight(this->model()->index(0, 0, QModelIndex()));
|
||||
}
|
||||
|
||||
void BestListView::clearSelectedRow()
|
||||
{
|
||||
if (!m_is_selected) {
|
||||
this->blockSignals(true);
|
||||
//this->clearSelection();
|
||||
this->setCurrentIndex(QModelIndex());
|
||||
this->blockSignals(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief BestListView::onRowDoubleClickedSlot 处理列表中的双击打开事件
|
||||
* @param index 点击的条目
|
||||
*/
|
||||
void BestListView::onRowDoubleClickedSlot(const QModelIndex &index)
|
||||
{
|
||||
const SearchPluginIface::ResultInfo &info = m_model->getInfo(index);
|
||||
QString plugin_id = m_model->getPluginInfo(index);;
|
||||
SearchPluginIface *plugin = SearchPluginManager::getInstance()->getPlugin(plugin_id);
|
||||
try {
|
||||
if (plugin) {
|
||||
if (!info.actionKey.isEmpty()) {
|
||||
plugin->openAction(0, info.actionKey, info.type);
|
||||
} else {
|
||||
throw -2;
|
||||
}
|
||||
} else {
|
||||
throw -1;
|
||||
}
|
||||
} catch(int e) {
|
||||
qWarning()<<"Open failed, reason="<<e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief BestListView::onRowSelectedSlot 处理列表项选中事件
|
||||
* @param selected
|
||||
* @param deselected
|
||||
*/
|
||||
void BestListView::onRowSelectedSlot(const QModelIndex &index)
|
||||
{
|
||||
if (index.isValid()) {
|
||||
m_is_selected = true;
|
||||
this->setCurrentIndex(index);
|
||||
Q_EMIT this->currentRowChanged(m_model->getPluginInfo(index), m_model->getInfo(index));
|
||||
}
|
||||
}
|
||||
|
||||
void BestListView::onItemListChanged(const int &count)
|
||||
{
|
||||
m_count = count;
|
||||
Q_EMIT this->listLengthChanged(count);
|
||||
}
|
||||
|
||||
void BestListView::setExpanded(const bool &is_expanded)
|
||||
{
|
||||
QModelIndex index = this->currentIndex();
|
||||
m_model->setExpanded(is_expanded);
|
||||
this->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
const bool &BestListView::isExpanded()
|
||||
{
|
||||
return m_model->isExpanded();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief BestListView::onMenuTriggered 点击右键菜单的槽函数
|
||||
* @param action
|
||||
*/
|
||||
void BestListView::onMenuTriggered(QAction *action)
|
||||
{
|
||||
//NEW_TODO 接口调整后需要修改
|
||||
// SearchPluginIface *plugin = SearchPluginManager::getInstance()->getPlugin(m_plugin_id);
|
||||
// if (plugin) {
|
||||
//// plugin->openAction(action->text(), m_model->getKey(this->currentIndex()));
|
||||
// } else {
|
||||
// qWarning()<<"Get plugin failed!";
|
||||
// }
|
||||
}
|
||||
|
||||
void BestListView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
m_tmpCurrentIndex = this->currentIndex();
|
||||
m_tmpMousePressIndex = indexAt(event->pos());
|
||||
if (m_tmpMousePressIndex.isValid() and m_tmpCurrentIndex != m_tmpMousePressIndex) {
|
||||
Q_EMIT this->clicked(m_tmpMousePressIndex);
|
||||
}
|
||||
|
||||
return QTreeView::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void BestListView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
QModelIndex index = indexAt(event->pos());
|
||||
if (index.isValid()) {
|
||||
Q_EMIT this->clicked(index);
|
||||
} else {
|
||||
Q_EMIT this->clicked(this->currentIndex());
|
||||
}
|
||||
return QTreeView::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void BestListView::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
m_tmpCurrentIndex = this->currentIndex();
|
||||
m_tmpMousePressIndex = indexAt(event->pos());
|
||||
if (m_tmpMousePressIndex.isValid() and m_tmpCurrentIndex != m_tmpMousePressIndex) {
|
||||
Q_EMIT this->clicked(m_tmpMousePressIndex);
|
||||
}
|
||||
return QTreeView::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void BestListView::initConnections()
|
||||
{
|
||||
connect(this, &BestListView::startSearch, [ = ](const QString &keyword) {
|
||||
qDebug() << "==========start search!";
|
||||
m_styleDelegate->setSearchKeyword(keyword);
|
||||
m_model->startSearch(keyword);
|
||||
});
|
||||
connect(this, &BestListView::startSearch, m_model, &BestListModel::startSearch);
|
||||
connect(this, &BestListView::clicked, this, &BestListView::onRowSelectedSlot);
|
||||
connect(this, &BestListView::activated, this, &BestListView::onRowDoubleClickedSlot);
|
||||
connect(m_model, &BestListModel::itemListChanged, this, &BestListView::onItemListChanged);
|
||||
connect(this, &BestListView::sendBestListData, this, [=] (const QString &plugin, const SearchPluginIface::ResultInfo&info) {
|
||||
QModelIndex index = this->currentIndex();
|
||||
this->m_model->appendInfo(plugin, info);
|
||||
if (index.isValid()) {
|
||||
this->setCurrentIndex(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BestListWidget::BestListWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
this->initUi();
|
||||
initConnections();
|
||||
}
|
||||
|
||||
QString BestListWidget::getWidgetName()
|
||||
{
|
||||
return m_titleLabel->text();
|
||||
}
|
||||
|
||||
void BestListWidget::setEnabled(const bool &enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
}
|
||||
|
||||
void BestListWidget::clearResult()
|
||||
{
|
||||
this->setVisible(false);
|
||||
this->setFixedHeight(0);
|
||||
}
|
||||
|
||||
int BestListWidget::getResultNum()
|
||||
{
|
||||
return m_bestListView->getResultNum();
|
||||
}
|
||||
|
||||
void BestListWidget::setResultSelection(const QModelIndex &index)
|
||||
{
|
||||
this->m_bestListView->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
void BestListWidget::clearResultSelection()
|
||||
{
|
||||
//this->m_bestListView->selectionModel()->clearSelection();
|
||||
this->m_bestListView->setCurrentIndex(QModelIndex());
|
||||
}
|
||||
|
||||
QModelIndex BestListWidget::getModlIndex(int row, int column)
|
||||
{
|
||||
return this->m_bestListView->getModlIndex(row, column);
|
||||
}
|
||||
|
||||
void BestListWidget::activateIndex()
|
||||
{
|
||||
this->m_bestListView->onRowDoubleClickedSlot(this->m_bestListView->currentIndex());
|
||||
}
|
||||
|
||||
QModelIndex BestListWidget::getCurrentSelection()
|
||||
{
|
||||
return this->m_bestListView->currentIndex();
|
||||
}
|
||||
|
||||
bool BestListWidget::getExpandState()
|
||||
{
|
||||
return m_bestListView->isExpanded();
|
||||
}
|
||||
|
||||
SearchPluginIface::ResultInfo BestListWidget::getIndexResultInfo(QModelIndex &index)
|
||||
{
|
||||
return m_bestListView->getIndexResultInfo(index);
|
||||
}
|
||||
|
||||
const QString BestListWidget::getPluginInfo(const QModelIndex&index)
|
||||
{
|
||||
return this->m_bestListView->getPluginInfo(index);
|
||||
}
|
||||
|
||||
int BestListWidget::getResultHeight()
|
||||
{
|
||||
return this->m_bestListView->getResultHeight();
|
||||
}
|
||||
/**
|
||||
* @brief BestListWidget::expandListSlot 展开列表的槽函数
|
||||
*/
|
||||
void BestListWidget::expandListSlot()
|
||||
{
|
||||
qWarning()<<"List will be expanded!";
|
||||
m_bestListView->setExpanded(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief BestListWidget::reduceListSlot 收起列表的槽函数
|
||||
*/
|
||||
void BestListWidget::reduceListSlot()
|
||||
{
|
||||
qWarning()<<"List will be reduced!";
|
||||
m_bestListView->setExpanded(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief BestListWidget::onListLengthChanged 响应列表长度改变的槽函数
|
||||
*/
|
||||
void BestListWidget::onListLengthChanged(const int &length)
|
||||
{
|
||||
this->setVisible(length > 0);
|
||||
int whole_height = this->isVisible() ? (m_bestListView->showHeight() + TITLE_HEIGHT) : 0;
|
||||
this->setFixedHeight(whole_height);
|
||||
Q_EMIT this->sizeChanged();
|
||||
}
|
||||
|
||||
void BestListWidget::initUi()
|
||||
{
|
||||
m_mainLyt = new QVBoxLayout(this);
|
||||
this->setLayout(m_mainLyt);
|
||||
m_mainLyt->setContentsMargins(MAIN_MARGINS);
|
||||
m_mainLyt->setSpacing(MAIN_SPACING);
|
||||
|
||||
m_titleLabel = new TitleLabel(this);
|
||||
m_titleLabel->setText(tr("Best Matches"));
|
||||
m_titleLabel->setFixedHeight(TITLE_HEIGHT);
|
||||
|
||||
m_bestListView = new BestListView(this);
|
||||
|
||||
m_mainLyt->addWidget(m_titleLabel);
|
||||
m_mainLyt->addWidget(m_bestListView);
|
||||
this->setFixedHeight(m_bestListView->height() + TITLE_HEIGHT);
|
||||
this->setFixedWidth(656);
|
||||
}
|
||||
|
||||
void BestListWidget::initConnections()
|
||||
{
|
||||
connect(this, &BestListWidget::startSearch, m_bestListView, &BestListView::startSearch);
|
||||
connect(this, &BestListWidget::startSearch, m_titleLabel, &TitleLabel::startSearch);
|
||||
connect(this, &BestListWidget::startSearch, m_titleLabel, &TitleLabel::startSearch);
|
||||
connect(this, &BestListWidget::stopSearch, m_titleLabel, &TitleLabel::stopSearch);
|
||||
connect(this, &BestListWidget::sendBestListData, m_bestListView, &BestListView::sendBestListData);
|
||||
connect(m_bestListView, &BestListView::currentRowChanged, this, &BestListWidget::currentRowChanged);
|
||||
connect(this, &BestListWidget::clearSelectedRow, m_bestListView, &BestListView::clearSelectedRow);
|
||||
connect(m_titleLabel, &TitleLabel::showMoreClicked, this, &BestListWidget::expandListSlot);
|
||||
connect(m_titleLabel, &TitleLabel::retractClicked, this, &BestListWidget::reduceListSlot);
|
||||
connect(m_bestListView, &BestListView::listLengthChanged, this, &BestListWidget::onListLengthChanged);
|
||||
connect(m_bestListView, &BestListView::listLengthChanged, m_titleLabel, &TitleLabel::onListLengthChanged);
|
||||
connect(m_bestListView, &BestListView::clicked, this, &BestListWidget::rowClicked);
|
||||
connect(qApp, &QApplication::paletteChanged, this, [ = ]() {
|
||||
int whole_height = this->isVisible() ? m_bestListView->showHeight() + TITLE_HEIGHT : 0;
|
||||
this->setFixedHeight(whole_height);
|
||||
Q_EMIT this->sizeChanged();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
#ifndef BESTLISTVIEW_H
|
||||
#define BESTLISTVIEW_H
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QApplication>
|
||||
#include "best-list-model.h"
|
||||
#include "show-more-label.h"
|
||||
#include "title-label.h"
|
||||
#include "result-view-delegate.h"
|
||||
|
||||
namespace UkuiSearch {
|
||||
|
||||
class BestListView : public QTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
BestListView(QWidget *parent = nullptr);
|
||||
~BestListView() = default;
|
||||
|
||||
bool isSelected();
|
||||
int showHeight();
|
||||
int getResultNum();
|
||||
QModelIndex getModlIndex(int row, int column);
|
||||
SearchPluginIface::ResultInfo getIndexResultInfo(QModelIndex &index);
|
||||
const QString getPluginInfo(const QModelIndex&index);
|
||||
int getResultHeight();
|
||||
|
||||
public Q_SLOTS:
|
||||
void clearSelectedRow();
|
||||
void onRowDoubleClickedSlot(const QModelIndex &);
|
||||
void onRowSelectedSlot(const QModelIndex &index);
|
||||
void onItemListChanged(const int &);
|
||||
void setExpanded(const bool &);
|
||||
const bool &isExpanded();
|
||||
void onMenuTriggered(QAction *);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
void mouseReleaseEvent(QMouseEvent * event);
|
||||
void mouseMoveEvent(QMouseEvent *event);
|
||||
|
||||
private:
|
||||
void initConnections();
|
||||
|
||||
BestListModel * m_model = nullptr;
|
||||
bool m_is_selected = false;
|
||||
ResultViewDelegate * m_styleDelegate = nullptr;
|
||||
int m_count = 0;
|
||||
QModelIndex m_tmpCurrentIndex;
|
||||
QModelIndex m_tmpMousePressIndex;
|
||||
|
||||
Q_SIGNALS:
|
||||
void startSearch(const QString &);
|
||||
void currentRowChanged(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
void sendBestListData(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
void listLengthChanged(const int &);
|
||||
void rowClicked();
|
||||
|
||||
};
|
||||
|
||||
|
||||
class BestListWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
BestListWidget(QWidget *parent = nullptr);
|
||||
~BestListWidget() = default;
|
||||
|
||||
QString getWidgetName();
|
||||
void setEnabled(const bool&);
|
||||
void clearResult();
|
||||
int getResultNum();
|
||||
void setResultSelection(const QModelIndex &index);
|
||||
void clearResultSelection();
|
||||
QModelIndex getModlIndex(int row, int column);
|
||||
void activateIndex();
|
||||
QModelIndex getCurrentSelection();
|
||||
bool getExpandState();
|
||||
SearchPluginIface::ResultInfo getIndexResultInfo(QModelIndex &index);
|
||||
const QString getPluginInfo(const QModelIndex&index);
|
||||
int getResultHeight();
|
||||
|
||||
private:
|
||||
void initUi();
|
||||
void initConnections();
|
||||
|
||||
bool m_enabled = true;
|
||||
QVBoxLayout * m_mainLyt = nullptr;
|
||||
TitleLabel * m_titleLabel = nullptr;
|
||||
BestListView * m_bestListView = nullptr;
|
||||
|
||||
public Q_SLOTS:
|
||||
void expandListSlot();
|
||||
void reduceListSlot();
|
||||
void onListLengthChanged(const int &);
|
||||
|
||||
Q_SIGNALS:
|
||||
void startSearch(const QString &);
|
||||
void stopSearch();
|
||||
void currentRowChanged(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
void sendBestListData(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
void clearSelectedRow();
|
||||
void sizeChanged();
|
||||
void rowClicked();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // BESTLISTVIEW_H
|
|
@ -0,0 +1,261 @@
|
|||
#include "result-view-delegate.h"
|
||||
#include <QPainterPath>
|
||||
using namespace UkuiSearch;
|
||||
static ResultItemStyle *global_instance_of_item_style = nullptr;
|
||||
|
||||
ResultViewDelegate::ResultViewDelegate(QObject *parent) : QStyledItemDelegate(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ResultViewDelegate::setSearchKeyword(const QString ®FindKeyWords)
|
||||
{
|
||||
m_regFindKeyWords.clear();
|
||||
m_regFindKeyWords = regFindKeyWords;
|
||||
}
|
||||
|
||||
QSize ResultViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
QSize size = QStyledItemDelegate::sizeHint(option,index);
|
||||
size.setHeight(size.height() + 10);
|
||||
return size;
|
||||
}
|
||||
|
||||
void ResultViewDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const {
|
||||
QStyleOptionViewItem opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
QStyle *style = opt.widget->style();
|
||||
|
||||
QString text = opt.text;
|
||||
if(text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
opt.text = QString();
|
||||
style->proxy()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); //绘制非文本区域内容
|
||||
|
||||
opt.text = text;
|
||||
QTextDocument doc;
|
||||
doc.setHtml(getHtmlText(painter, opt, index)); //提取富文本
|
||||
QAbstractTextDocumentLayout* layout = doc.documentLayout();
|
||||
const double height = layout->documentSize().height();
|
||||
|
||||
|
||||
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, opt.widget);
|
||||
//使图标和文本间隔与原来保持一致,故文本区域右移4
|
||||
// textRect.adjust(4, 0, 0, 0);
|
||||
double y = textRect.y();
|
||||
y += (textRect.height() - height) / 2;
|
||||
|
||||
QAbstractTextDocumentLayout::PaintContext context;
|
||||
|
||||
QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled
|
||||
? QPalette::Normal : QPalette::Disabled;
|
||||
if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active))
|
||||
cg = QPalette::Inactive;
|
||||
|
||||
if(opt.state & QStyle::State_Selected) {
|
||||
painter->setPen(opt.palette.color(cg, QPalette::HighlightedText));
|
||||
} else {
|
||||
painter->setPen(opt.palette.color(cg, QPalette::Text));
|
||||
}
|
||||
painter->save();
|
||||
painter->translate(QPointF(textRect.x(), y));
|
||||
layout->draw(painter, context); //绘制文本区域内容
|
||||
painter->restore();
|
||||
|
||||
}
|
||||
|
||||
QString ResultViewDelegate::getHtmlText(QPainter *painter, const QStyleOptionViewItem &itemOption, const QModelIndex &index) const
|
||||
{
|
||||
int indexFindLeft = 0;
|
||||
QString indexString = index.model()->data(index, Qt::DisplayRole).toString();
|
||||
QFont ft(painter->font().family(), GlobalSettings::getInstance()->getValue(FONT_SIZE_KEY).toInt());
|
||||
QFontMetrics fm(ft);
|
||||
QString indexColString = fm.elidedText(indexString, Qt::ElideRight, itemOption.rect.width() - 30 - 10); //当字体超过Item的长度时显示为省略号
|
||||
QString htmlString;
|
||||
if((indexColString.toUpper()).contains((m_regFindKeyWords.toUpper()))) {
|
||||
indexFindLeft = indexColString.toUpper().indexOf(m_regFindKeyWords.toUpper()); //得到查找字体在当前整个Item字体中的位置
|
||||
htmlString = escapeHtml(indexColString.left(indexFindLeft)) + "<b>" + escapeHtml(indexColString.mid(indexFindLeft, m_regFindKeyWords.length())) + "</b>" + escapeHtml(indexColString.right(indexColString.length() - indexFindLeft - m_regFindKeyWords.length()));
|
||||
} else {
|
||||
bool boldOpenned = false;
|
||||
for(int i = 0; i < indexColString.length(); i++) {
|
||||
if((m_regFindKeyWords.toUpper()).contains(QString(indexColString.at(i)).toUpper())) {
|
||||
if(! boldOpenned) {
|
||||
boldOpenned = true;
|
||||
htmlString.append(QString("<b>"));
|
||||
}
|
||||
htmlString.append(escapeHtml(QString(indexColString.at(i))));
|
||||
} else {
|
||||
if(boldOpenned) {
|
||||
boldOpenned = false;
|
||||
htmlString.append(QString("</b>"));
|
||||
}
|
||||
htmlString.append(escapeHtml(QString(indexColString.at(i))));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// qDebug()<<indexColString<<"---->"<<htmlString;
|
||||
return "<pre>" + htmlString + "</pre>";
|
||||
}
|
||||
|
||||
QString ResultViewDelegate::escapeHtml(const QString &str) const
|
||||
{
|
||||
QString temp = str;
|
||||
temp.replace("<", "<");
|
||||
temp.replace(">", ">");
|
||||
return temp;
|
||||
}
|
||||
|
||||
ResultItemStyle *ResultItemStyle::getStyle()
|
||||
{
|
||||
if (!global_instance_of_item_style) {
|
||||
global_instance_of_item_style = new ResultItemStyle;
|
||||
}
|
||||
return global_instance_of_item_style;
|
||||
}
|
||||
|
||||
void ResultItemStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
|
||||
{
|
||||
switch (element) {
|
||||
case PE_PanelItemViewItem: {
|
||||
|
||||
bool isIconView = false;
|
||||
auto opt = qstyleoption_cast<const QStyleOptionViewItem *>(option);
|
||||
if (!opt)
|
||||
return;
|
||||
if (opt) {
|
||||
isIconView = (opt->decorationPosition & QStyleOptionViewItem::Top);
|
||||
}
|
||||
|
||||
bool isHover = (option->state & State_MouseOver) && (option->state & ~State_Selected);
|
||||
bool isSelected = option->state & State_Selected;
|
||||
bool enable = option->state & State_Enabled;
|
||||
QColor color = option->palette.color(enable? QPalette::Active: QPalette::Disabled, QPalette::Highlight);
|
||||
|
||||
if (isSelected) {
|
||||
color.setAlpha(255);
|
||||
} else if (isHover) {
|
||||
// color = opt->palette.color(QPalette::Active, QPalette::BrightText);
|
||||
// color.setAlpha(0.05);
|
||||
color = QColor(241, 241, 241);
|
||||
} else {
|
||||
color.setAlpha(0);
|
||||
}
|
||||
QPainterPath path;
|
||||
|
||||
if(opt->viewItemPosition == QStyleOptionViewItem::OnlyOne) {
|
||||
path.addRoundedRect(option->rect, 6, 6);
|
||||
} else if(opt->viewItemPosition == QStyleOptionViewItem::Beginning) {
|
||||
//一个左侧有两个圆角的矩形
|
||||
path.moveTo(option->rect.topLeft() + QPoint(6, 0));
|
||||
path.cubicTo(option->rect.topLeft() + QPoint(6, 0),
|
||||
option->rect.topLeft(), option->rect.topLeft() +
|
||||
QPoint(0, 6));
|
||||
|
||||
path.lineTo(option->rect.bottomLeft() - QPoint(0, 6));
|
||||
path.cubicTo(option->rect.bottomLeft() - QPoint(0, 6),
|
||||
option->rect.bottomLeft() + QPoint(0, 1),
|
||||
option->rect.bottomLeft() + QPoint(6, 1));
|
||||
|
||||
path.lineTo(option->rect.bottomRight() + QPoint(1, 1));
|
||||
path.lineTo(option->rect.topRight()+ QPoint(1, 0));
|
||||
path.lineTo(option->rect.topLeft() + QPoint(6, 0));
|
||||
} else if(opt->viewItemPosition == QStyleOptionViewItem::Middle) {
|
||||
path.addRect(option->rect.adjusted(-1, 0, 1, 0));
|
||||
} else if(opt->viewItemPosition == QStyleOptionViewItem::End) {
|
||||
//一个右侧有两个圆角的矩形
|
||||
path.moveTo(option->rect.topRight() + QPoint(-6, 0));
|
||||
path.cubicTo(option->rect.topRight() + QPoint(-6, 0),
|
||||
option->rect.topRight(),
|
||||
option->rect.topRight() +
|
||||
QPoint(0, 6));
|
||||
|
||||
path.lineTo(option->rect.bottomRight() - QPoint(0, 6));
|
||||
path.cubicTo(option->rect.bottomRight() - QPoint(0, 6),
|
||||
option->rect.bottomRight() + QPoint(0, 1),
|
||||
option->rect.bottomRight() + QPoint(-6, 1));
|
||||
|
||||
path.lineTo(option->rect.bottomLeft() + QPoint(0, 1));
|
||||
path.lineTo(option->rect.topLeft());
|
||||
path.lineTo(option->rect.topRight() + QPoint(-6, 0));
|
||||
} else {
|
||||
// path.addRoundedRect(option->rect, 8, 8);
|
||||
}
|
||||
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(color);
|
||||
painter->drawPath(path);
|
||||
// painter->fillPath(path, painter->brush());
|
||||
|
||||
painter->restore();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
// return QProxyStyle::drawPrimitive(element, option, painter, widget);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ResultItemStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
|
||||
{
|
||||
switch (element) {
|
||||
case CE_ItemViewItem: {
|
||||
if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
|
||||
painter->save();
|
||||
if (painter->clipPath().isEmpty()) {
|
||||
painter->setClipRect(option->rect);
|
||||
}
|
||||
|
||||
QRect checkRect = proxy()->subElementRect(SE_ItemViewItemCheckIndicator, vopt, widget);
|
||||
QRect iconRect = proxy()->subElementRect(SE_ItemViewItemDecoration, vopt, widget);
|
||||
|
||||
// draw the background
|
||||
proxy()->drawPrimitive(PE_PanelItemViewItem, option, painter, widget);
|
||||
|
||||
// draw the check mark
|
||||
if (vopt->features & QStyleOptionViewItem::HasCheckIndicator) {
|
||||
QStyleOptionViewItem option(*vopt);
|
||||
option.rect = checkRect;
|
||||
option.state = option.state & ~QStyle::State_HasFocus;
|
||||
|
||||
switch (vopt->checkState) {
|
||||
case Qt::Unchecked:
|
||||
option.state |= QStyle::State_Off;
|
||||
break;
|
||||
case Qt::PartiallyChecked:
|
||||
option.state |= QStyle::State_NoChange;
|
||||
break;
|
||||
case Qt::Checked:
|
||||
option.state |= QStyle::State_On;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
proxy()->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &option, painter, widget);
|
||||
}
|
||||
|
||||
// draw the icon
|
||||
QIcon::Mode mode = QIcon::Normal;
|
||||
if (!(vopt->state & QStyle::State_Enabled)) {
|
||||
mode = QIcon::Disabled;
|
||||
} else if (vopt->state & QStyle::State_Selected) {
|
||||
mode = QIcon::Selected;
|
||||
}
|
||||
QIcon::State state = vopt->state & QStyle::State_Open ? QIcon::On : QIcon::Off;
|
||||
auto pixmap = vopt->icon.pixmap(vopt->decorationSize, mode, state);
|
||||
|
||||
iconRect.moveLeft(8);
|
||||
QStyle::drawItemPixmap(painter, iconRect, vopt->decorationAlignment, pixmap);
|
||||
painter->restore();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangjiaping <zhangjiaping@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#ifndef RESULTVIEWDELEGATE_H
|
||||
#define RESULTVIEWDELEGATE_H
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QPainter>
|
||||
#include <QStyle>
|
||||
#include <QTextDocument>
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QProxyStyle>
|
||||
#include "global-settings.h"
|
||||
|
||||
namespace UkuiSearch {
|
||||
class ResultViewDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ResultViewDelegate(QObject *parent = nullptr);
|
||||
~ResultViewDelegate() = default;
|
||||
void setSearchKeyword(const QString &);
|
||||
protected:
|
||||
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
|
||||
private:
|
||||
QString m_regFindKeyWords = 0;
|
||||
void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override;
|
||||
QString getHtmlText(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const;
|
||||
QString escapeHtml(const QString&) const;
|
||||
};
|
||||
|
||||
class ResultItemStyle : public QProxyStyle
|
||||
{
|
||||
public:
|
||||
static ResultItemStyle *getStyle();
|
||||
void drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = nullptr) const;
|
||||
void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = nullptr) const override;
|
||||
private:
|
||||
explicit ResultItemStyle() {}
|
||||
~ResultItemStyle() = default;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // RESULTVIEWDELEGATE_H
|
|
@ -0,0 +1,351 @@
|
|||
#include "result-view.h"
|
||||
#define MAIN_MARGINS 0,0,0,0
|
||||
#define MAIN_SPACING 0
|
||||
#define TITLE_HEIGHT 30
|
||||
#define UNFOLD_LABEL_HEIGHT 30
|
||||
#define VIEW_ICON_SIZE 24
|
||||
|
||||
using namespace UkuiSearch;
|
||||
ResultWidget::ResultWidget(const QString &plugin_id, QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
m_plugin_id = plugin_id;
|
||||
this->initUi();
|
||||
initConnections();
|
||||
}
|
||||
|
||||
QString ResultWidget::pluginId()
|
||||
{
|
||||
return m_plugin_id;
|
||||
}
|
||||
|
||||
QString ResultWidget::pluginName()
|
||||
{
|
||||
return m_titleLabel->text();
|
||||
}
|
||||
|
||||
void ResultWidget::setEnabled(const bool &enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
}
|
||||
|
||||
void ResultWidget::clearResult()
|
||||
{
|
||||
this->setVisible(false);
|
||||
this->setFixedHeight(0);
|
||||
}
|
||||
|
||||
int ResultWidget::getResultNum()
|
||||
{
|
||||
return m_resultView->getResultNum();
|
||||
}
|
||||
|
||||
void ResultWidget::setResultSelection(const QModelIndex &index)
|
||||
{
|
||||
//this->m_resultView->selectionModel()->clearSelection();
|
||||
//this->m_resultView->setCurrentIndex(QModelIndex());
|
||||
this->m_resultView->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
void ResultWidget::clearResultSelection()
|
||||
{
|
||||
//this->m_resultView->selectionModel()->clearSelection();
|
||||
this->m_resultView->setCurrentIndex(QModelIndex());
|
||||
}
|
||||
|
||||
QModelIndex ResultWidget::getModlIndex(int row, int column)
|
||||
{
|
||||
return this->m_resultView->getModlIndex(row, column);
|
||||
}
|
||||
|
||||
void ResultWidget::activateIndex()
|
||||
{
|
||||
this->m_resultView->onRowDoubleClickedSlot(this->m_resultView->currentIndex());
|
||||
}
|
||||
|
||||
QModelIndex ResultWidget::getCurrentSelection()
|
||||
{
|
||||
return this->m_resultView->currentIndex();
|
||||
}
|
||||
|
||||
bool ResultWidget::getExpandState()
|
||||
{
|
||||
return m_resultView->isExpanded();
|
||||
}
|
||||
|
||||
SearchPluginIface::ResultInfo ResultWidget::getIndexResultInfo(QModelIndex &index)
|
||||
{
|
||||
return m_resultView->getIndexResultInfo(index);
|
||||
}
|
||||
|
||||
|
||||
void ResultWidget::resetTitleLabel()
|
||||
{
|
||||
Q_EMIT this->m_titleLabel->lableReset();
|
||||
}
|
||||
|
||||
void ResultWidget::setTitileLableHide(bool state)
|
||||
{
|
||||
m_titleLabel->setVisible(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ResultWidget::expandListSlot 展开列表的槽函数
|
||||
*/
|
||||
void ResultWidget::expandListSlot()
|
||||
{
|
||||
qWarning()<<"List will be expanded!";
|
||||
m_resultView->setExpanded(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ResultWidget::reduceListSlot 收起列表的槽函数
|
||||
*/
|
||||
void ResultWidget::reduceListSlot()
|
||||
{
|
||||
qWarning()<<"List will be reduced!";
|
||||
m_resultView->setExpanded(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ResultWidget::onListLengthChanged 响应列表长度改变的槽函数
|
||||
*/
|
||||
void ResultWidget::onListLengthChanged(const int &length)
|
||||
{
|
||||
this->setVisible(length > 0);
|
||||
int whole_height = this->isVisible() ? m_resultView->showHeight() + TITLE_HEIGHT : 0;
|
||||
this->setFixedHeight(whole_height);
|
||||
Q_EMIT this->sizeChanged();
|
||||
}
|
||||
|
||||
void ResultWidget::initUi()
|
||||
{
|
||||
m_mainLyt = new QVBoxLayout(this);
|
||||
this->setLayout(m_mainLyt);
|
||||
m_mainLyt->setContentsMargins(MAIN_MARGINS);
|
||||
m_mainLyt->setSpacing(MAIN_SPACING);
|
||||
|
||||
m_titleLabel = new TitleLabel(this);
|
||||
m_plugin_name = SearchPluginManager::getInstance()->getPlugin(m_plugin_id)->getPluginName();
|
||||
m_titleLabel->setText(m_plugin_name);
|
||||
m_titleLabel->setFixedHeight(TITLE_HEIGHT);
|
||||
|
||||
m_resultView = new ResultView(m_plugin_id, this);
|
||||
|
||||
m_mainLyt->addWidget(m_titleLabel);
|
||||
m_mainLyt->addWidget(m_resultView);
|
||||
this->setFixedHeight(m_resultView->height() + TITLE_HEIGHT);
|
||||
this->setFixedWidth(656);
|
||||
}
|
||||
|
||||
void ResultWidget::initConnections()
|
||||
{
|
||||
connect(this, &ResultWidget::startSearch, m_resultView, &ResultView::startSearch);
|
||||
connect(this, &ResultWidget::startSearch, m_titleLabel, &TitleLabel::startSearch);
|
||||
connect(this, &ResultWidget::stopSearch, m_resultView, &ResultView::stopSearch);
|
||||
connect(this, &ResultWidget::stopSearch, m_titleLabel, &TitleLabel::stopSearch);
|
||||
connect(m_resultView, &ResultView::currentRowChanged, this, &ResultWidget::currentRowChanged);
|
||||
connect(this, &ResultWidget::clearSelectedRow, m_resultView, &ResultView::clearSelectedRow);
|
||||
connect(m_titleLabel, &TitleLabel::showMoreClicked, this, &ResultWidget::expandListSlot);
|
||||
connect(m_titleLabel, &TitleLabel::showMoreClicked, this, &ResultWidget::showMoreClicked);
|
||||
connect(m_titleLabel, &TitleLabel::retractClicked, this, &ResultWidget::reduceListSlot);
|
||||
connect(m_titleLabel, &TitleLabel::retractClicked, this, &ResultWidget::retractClicked);
|
||||
connect(m_resultView, &ResultView::listLengthChanged, this, &ResultWidget::onListLengthChanged);
|
||||
connect(m_resultView, &ResultView::listLengthChanged, m_titleLabel, &TitleLabel::onListLengthChanged);
|
||||
connect(m_resultView, &ResultView::clicked, this, &ResultWidget::rowClicked);
|
||||
connect(qApp, &QApplication::paletteChanged, this, [ = ]() {
|
||||
int whole_height = this->isVisible() ? m_resultView->showHeight() + TITLE_HEIGHT : 0;
|
||||
this->setFixedHeight(whole_height);
|
||||
Q_EMIT this->sizeChanged();
|
||||
});
|
||||
connect(m_resultView, &ResultView::sendBestListData, this, &ResultWidget::sendBestListData);
|
||||
connect(m_resultView, &ResultView::lableReset, m_titleLabel, &TitleLabel::lableReset);
|
||||
connect(this, &ResultWidget::resizeWidth, this, [=] (const int &size) {
|
||||
this->setFixedWidth(size);
|
||||
});
|
||||
}
|
||||
|
||||
ResultView::ResultView(const QString &plugin_id, QWidget *parent) : QTreeView(parent)
|
||||
{
|
||||
// setStyle(ResultItemStyle::getStyle());
|
||||
this->setFrameShape(QFrame::NoFrame);
|
||||
this->viewport()->setAutoFillBackground(false);
|
||||
this->setIconSize(QSize(VIEW_ICON_SIZE, VIEW_ICON_SIZE));
|
||||
this->setRootIsDecorated(false);
|
||||
this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
this->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
this->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
this->setHeaderHidden(true);
|
||||
m_model = new SearchResultModel(plugin_id);
|
||||
this->setModel(m_model);
|
||||
initConnections();
|
||||
m_plugin_id = plugin_id;
|
||||
m_styleDelegate = new ResultViewDelegate(this);
|
||||
this->setItemDelegate(m_styleDelegate);
|
||||
}
|
||||
|
||||
bool ResultView::isSelected()
|
||||
{
|
||||
return m_is_selected;
|
||||
}
|
||||
|
||||
int ResultView::showHeight()
|
||||
{
|
||||
int height;
|
||||
int rowheight = this->rowHeight(this->model()->index(0, 0, QModelIndex()));
|
||||
if (this->isExpanded()) {
|
||||
height = m_count * rowheight;
|
||||
} else {
|
||||
int show_count = m_count > NUM_LIMIT_SHOWN_DEFAULT ? NUM_LIMIT_SHOWN_DEFAULT : m_count;
|
||||
height = show_count * rowheight;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
int ResultView::getResultNum()
|
||||
{
|
||||
return m_count;
|
||||
}
|
||||
|
||||
QModelIndex ResultView::getModlIndex(int row, int column)
|
||||
{
|
||||
return this->m_model->index(row, column);
|
||||
}
|
||||
|
||||
SearchPluginIface::ResultInfo ResultView::getIndexResultInfo(QModelIndex &index)
|
||||
{
|
||||
return this->m_model->getInfo(index);
|
||||
}
|
||||
|
||||
void ResultView::clearSelectedRow()
|
||||
{
|
||||
if (!m_is_selected) {
|
||||
this->blockSignals(true);
|
||||
//this->clearSelection();
|
||||
this->setCurrentIndex(QModelIndex());
|
||||
this->blockSignals(false);
|
||||
} else {
|
||||
m_is_selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ResultView::onRowDoubleClickedSlot 处理列表中的双击打开事件
|
||||
* @param index 点击的条目
|
||||
*/
|
||||
void ResultView::onRowDoubleClickedSlot(const QModelIndex &index)
|
||||
{
|
||||
const SearchPluginIface::ResultInfo &info = m_model->getInfo(index);
|
||||
SearchPluginIface *plugin = SearchPluginManager::getInstance()->getPlugin(m_plugin_id);
|
||||
try {
|
||||
if (plugin) {
|
||||
if (!info.actionKey.isEmpty()) {
|
||||
plugin->openAction(0, info.actionKey, info.type);
|
||||
} else {
|
||||
throw -2;
|
||||
}
|
||||
} else {
|
||||
throw -1;
|
||||
}
|
||||
} catch(int e) {
|
||||
qWarning()<<"Open failed, reason="<<e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ResultView::onRowSelectedSlot 处理列表项选中事件
|
||||
* @param selected
|
||||
* @param deselected
|
||||
*/
|
||||
void ResultView::onRowSelectedSlot(const QModelIndex &index)
|
||||
{
|
||||
if(index.isValid()) {
|
||||
m_is_selected = true;
|
||||
this->setCurrentIndex(index);
|
||||
Q_EMIT this->currentRowChanged(m_plugin_id, m_model->getInfo(index));
|
||||
}
|
||||
}
|
||||
|
||||
void ResultView::onItemListChanged(const int &count)
|
||||
{
|
||||
m_count = count;
|
||||
Q_EMIT this->listLengthChanged(count);
|
||||
QModelIndex index = this->currentIndex();
|
||||
m_model->refresh();
|
||||
this->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
void ResultView::setExpanded(const bool &is_expanded)
|
||||
{
|
||||
QModelIndex index = this->currentIndex();
|
||||
m_model->setExpanded(is_expanded);
|
||||
this->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
const bool &ResultView::isExpanded()
|
||||
{
|
||||
return m_model->isExpanded();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ResultView::onMenuTriggered 点击右键菜单的槽函数
|
||||
* @param action
|
||||
*/
|
||||
void ResultView::onMenuTriggered(QAction *action)
|
||||
{
|
||||
//NEW_TODO 接口调整后需要修改
|
||||
SearchPluginIface *plugin = SearchPluginManager::getInstance()->getPlugin(m_plugin_id);
|
||||
if (plugin) {
|
||||
// plugin->openAction(action->text(), m_model->getKey(this->currentIndex()));
|
||||
} else {
|
||||
qWarning()<<"Get plugin failed!";
|
||||
}
|
||||
}
|
||||
|
||||
void ResultView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
// qDebug() << "source" << event->source();
|
||||
m_tmpCurrentIndex = this->currentIndex();
|
||||
m_tmpMousePressIndex = indexAt(event->pos());
|
||||
if (m_tmpMousePressIndex.isValid() and m_tmpCurrentIndex != m_tmpMousePressIndex) {
|
||||
Q_EMIT this->clicked(m_tmpMousePressIndex);
|
||||
}
|
||||
|
||||
return QTreeView::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void ResultView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
QModelIndex index = indexAt(event->pos());
|
||||
if (index.isValid()) {
|
||||
Q_EMIT this->clicked(index);
|
||||
} else {
|
||||
Q_EMIT this->clicked(this->currentIndex());
|
||||
}
|
||||
return QTreeView::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void ResultView::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
m_tmpCurrentIndex = this->currentIndex();
|
||||
m_tmpMousePressIndex = indexAt(event->pos());
|
||||
if (m_tmpMousePressIndex.isValid() and m_tmpCurrentIndex != m_tmpMousePressIndex and event->source() != Qt::MouseEventSynthesizedByQt) {
|
||||
Q_EMIT this->clicked(m_tmpMousePressIndex);
|
||||
}
|
||||
return QTreeView::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void ResultView::initConnections()
|
||||
{
|
||||
connect(this, &ResultView::startSearch, [ = ](const QString &keyword) {
|
||||
setExpanded(false);
|
||||
Q_EMIT this->lableReset();
|
||||
m_styleDelegate->setSearchKeyword(keyword);
|
||||
m_model->startSearch(keyword);
|
||||
});
|
||||
connect(this, &ResultView::stopSearch, m_model, &SearchResultModel::stopSearch);
|
||||
//connect(this->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ResultView::onRowSelectedSlot);
|
||||
connect(this, &ResultView::clicked, this, &ResultView::onRowSelectedSlot);
|
||||
connect(this, &ResultView::activated, this, &ResultView::onRowDoubleClickedSlot);
|
||||
connect(m_model, &SearchResultModel::itemListChanged, this, &ResultView::onItemListChanged);
|
||||
connect(m_model, &SearchResultModel::sendBestListData, this, &ResultView::sendBestListData);
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
#ifndef RESULTVIEW_H
|
||||
#define RESULTVIEW_H
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QApplication>
|
||||
#include "search-result-model.h"
|
||||
#include "show-more-label.h"
|
||||
#include "title-label.h"
|
||||
#include "result-view-delegate.h"
|
||||
|
||||
namespace UkuiSearch {
|
||||
|
||||
class ResultView : public QTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ResultView(const QString &plugin_id, QWidget *parent = nullptr);
|
||||
~ResultView() = default;
|
||||
bool isSelected();
|
||||
int showHeight();
|
||||
int getResultNum();
|
||||
QModelIndex getModlIndex(int row, int column);
|
||||
SearchPluginIface::ResultInfo getIndexResultInfo(QModelIndex &index);
|
||||
|
||||
public Q_SLOTS:
|
||||
void clearSelectedRow();
|
||||
void onRowDoubleClickedSlot(const QModelIndex &);
|
||||
void onRowSelectedSlot(const QModelIndex &index);
|
||||
void onItemListChanged(const int &);
|
||||
void setExpanded(const bool &);
|
||||
const bool &isExpanded();
|
||||
void onMenuTriggered(QAction *);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
void mouseReleaseEvent(QMouseEvent *event);
|
||||
void mouseMoveEvent(QMouseEvent *event);
|
||||
|
||||
private:
|
||||
void initConnections();
|
||||
SearchResultModel * m_model = nullptr;
|
||||
QString m_plugin_id;
|
||||
bool m_is_selected = false;
|
||||
ResultViewDelegate * m_styleDelegate = nullptr;
|
||||
int m_count = 0;
|
||||
QModelIndex m_tmpCurrentIndex;
|
||||
QModelIndex m_tmpMousePressIndex;
|
||||
|
||||
Q_SIGNALS:
|
||||
void startSearch(const QString &);
|
||||
void stopSearch();
|
||||
void currentRowChanged(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
void sendBestListData(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
void listLengthChanged(const int &);
|
||||
void rowClicked();
|
||||
void lableReset();
|
||||
};
|
||||
|
||||
class ResultWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ResultWidget(const QString &plugin_id, QWidget *parent = nullptr);
|
||||
~ResultWidget() = default;
|
||||
QString pluginId();
|
||||
QString pluginName();
|
||||
void setEnabled(const bool&);
|
||||
void clearResult();
|
||||
int getResultNum();
|
||||
void setResultSelection(const QModelIndex &index);
|
||||
void clearResultSelection();
|
||||
QModelIndex getModlIndex(int row, int column);
|
||||
void activateIndex();
|
||||
QModelIndex getCurrentSelection();
|
||||
bool getExpandState();
|
||||
SearchPluginIface::ResultInfo getIndexResultInfo(QModelIndex &index);
|
||||
void resetTitleLabel();
|
||||
void setTitileLableHide(bool state);
|
||||
|
||||
public Q_SLOTS:
|
||||
void expandListSlot();
|
||||
void reduceListSlot();
|
||||
void onListLengthChanged(const int &);
|
||||
|
||||
private:
|
||||
void initUi();
|
||||
void initConnections();
|
||||
|
||||
QString m_plugin_id;
|
||||
QString m_plugin_name;
|
||||
bool m_enabled = true;
|
||||
QVBoxLayout * m_mainLyt = nullptr;
|
||||
TitleLabel * m_titleLabel = nullptr;
|
||||
ResultView * m_resultView = nullptr;
|
||||
|
||||
Q_SIGNALS:
|
||||
void startSearch(const QString &);
|
||||
void stopSearch();
|
||||
void currentRowChanged(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
void sendBestListData(const QString &, const SearchPluginIface::ResultInfo&);
|
||||
void clearSelectedRow();
|
||||
void sizeChanged();
|
||||
void rowClicked();
|
||||
void resizeWidth(const int &);
|
||||
void showMoreClicked();
|
||||
void retractClicked();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // RESULTVIEW_H
|
|
@ -0,0 +1,13 @@
|
|||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/best-list-view.h \
|
||||
$$PWD/result-view-delegate.h \
|
||||
$$PWD/result-view.h \
|
||||
$$PWD/web-search-view.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/best-list-view.cpp \
|
||||
$$PWD/result-view-delegate.cpp \
|
||||
$$PWD/result-view.cpp \
|
||||
$$PWD/web-search-view.cpp
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (C) 2021, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: jixiaoxu <jixiaoxu@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "web-search-view.h"
|
||||
#define MAIN_MARGINS 0,0,0,0
|
||||
#define MAIN_SPACING 0
|
||||
#define TITLE_HEIGHT 30
|
||||
#define VIEW_ICON_SIZE 24
|
||||
|
||||
using namespace UkuiSearch;
|
||||
WebSearchView::WebSearchView(QWidget *parent) : QTreeView(parent)
|
||||
{
|
||||
this->setFrameShape(QFrame::NoFrame);
|
||||
this->viewport()->setAutoFillBackground(false);
|
||||
this->setRootIsDecorated(false);
|
||||
this->setIconSize(QSize(VIEW_ICON_SIZE, VIEW_ICON_SIZE));
|
||||
this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
this->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
this->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
this->setHeaderHidden(true);
|
||||
m_model = new WebSearchModel(this);
|
||||
this->setModel(m_model);
|
||||
m_styleDelegate = new ResultViewDelegate(this);
|
||||
this->setItemDelegate(m_styleDelegate);
|
||||
}
|
||||
|
||||
bool WebSearchView::isSelected()
|
||||
{
|
||||
return m_is_selected;
|
||||
}
|
||||
|
||||
int WebSearchView::showHeight()
|
||||
{
|
||||
return this->rowHeight(this->model()->index(0, 0, QModelIndex()));
|
||||
}
|
||||
|
||||
QModelIndex WebSearchView::getModlIndex(int row, int column)
|
||||
{
|
||||
return this->m_model->index(row, column);
|
||||
}
|
||||
|
||||
void WebSearchView::clearSelectedRow()
|
||||
{
|
||||
if (!m_is_selected) {
|
||||
this->blockSignals(true);
|
||||
//this->clearSelection();
|
||||
this->setCurrentIndex(QModelIndex());
|
||||
this->blockSignals(false);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSearchView::startSearch(const QString & keyword)
|
||||
{
|
||||
this->m_styleDelegate->setSearchKeyword(keyword);
|
||||
this->m_model->startSearch(keyword);
|
||||
m_keyWord = keyword;
|
||||
}
|
||||
|
||||
void WebSearchView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
QModelIndex index = indexAt(event->pos());
|
||||
if (!index.isValid()) {
|
||||
this->clearSelection();
|
||||
}
|
||||
return QTreeView::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void WebSearchView::LaunchBrowser()
|
||||
{
|
||||
QString address;
|
||||
QString engine = GlobalSettings::getInstance()->getValue("web_engine").toString();
|
||||
if(!engine.isEmpty()) {
|
||||
if(engine == "360") {
|
||||
address = "https://so.com/s?q=" + m_keyWord; //360
|
||||
} else if(engine == "sougou") {
|
||||
address = "https://www.sogou.com/web?query=" + m_keyWord; //搜狗
|
||||
} else {
|
||||
address = "http://baidu.com/s?word=" + m_keyWord; //百度
|
||||
}
|
||||
} else { //默认值
|
||||
address = "http://baidu.com/s?word=" + m_keyWord ; //百度
|
||||
}
|
||||
QDesktopServices::openUrl(address);
|
||||
}
|
||||
|
||||
void WebSearchView::initConnections()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
WebSearchWidget::WebSearchWidget(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
this->initUi();
|
||||
initConnections();
|
||||
}
|
||||
|
||||
QString WebSearchWidget::getWidgetName()
|
||||
{
|
||||
return m_titleLabel->text();
|
||||
}
|
||||
|
||||
void WebSearchWidget::setEnabled(const bool &enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
}
|
||||
|
||||
void WebSearchWidget::clearResultSelection()
|
||||
{
|
||||
this->m_webSearchView->setCurrentIndex(QModelIndex());
|
||||
}
|
||||
|
||||
QModelIndex WebSearchWidget::getModlIndex(int row, int column)
|
||||
{
|
||||
return this->m_webSearchView->getModlIndex(row, column);
|
||||
}
|
||||
|
||||
void WebSearchWidget::setResultSelection(const QModelIndex &index)
|
||||
{
|
||||
this->m_webSearchView->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
void WebSearchWidget::LaunchBrowser()
|
||||
{
|
||||
this->m_webSearchView->LaunchBrowser();
|
||||
}
|
||||
|
||||
void WebSearchWidget::initUi()
|
||||
{
|
||||
m_mainLyt = new QVBoxLayout(this);
|
||||
this->setLayout(m_mainLyt);
|
||||
m_mainLyt->setContentsMargins(MAIN_MARGINS);
|
||||
m_mainLyt->setSpacing(MAIN_SPACING);
|
||||
|
||||
m_titleLabel = new TitleLabel(this);
|
||||
m_titleLabel->setText(tr("Web Page"));
|
||||
m_titleLabel->setFixedHeight(TITLE_HEIGHT);
|
||||
|
||||
m_webSearchView = new WebSearchView(this);
|
||||
|
||||
m_mainLyt->addWidget(m_titleLabel);
|
||||
m_mainLyt->addWidget(m_webSearchView);
|
||||
this->setFixedHeight(m_webSearchView->height() + TITLE_HEIGHT);
|
||||
this->setFixedWidth(656);
|
||||
}
|
||||
|
||||
void WebSearchWidget::initConnections()
|
||||
{
|
||||
connect(this, &WebSearchWidget::startSearch, m_webSearchView, &WebSearchView::startSearch);
|
||||
connect(m_webSearchView, &WebSearchView::clicked, this, [=] () {
|
||||
this->LaunchBrowser();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
#ifndef WEBSEARCHVIEW_H
|
||||
#define WEBSEARCHVIEW_H
|
||||
#include <QTreeView>
|
||||
#include <QListView>
|
||||
#include <QMouseEvent>
|
||||
#include "web-search-model.h"
|
||||
#include "result-view-delegate.h"
|
||||
#include "title-label.h"
|
||||
|
||||
namespace UkuiSearch {
|
||||
class WebSearchView : public QTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
WebSearchView(QWidget *parent = nullptr);
|
||||
~WebSearchView() = default;
|
||||
|
||||
bool isSelected();
|
||||
int showHeight();
|
||||
QModelIndex getModlIndex(int row, int column);
|
||||
void LaunchBrowser();
|
||||
|
||||
public Q_SLOTS:
|
||||
void clearSelectedRow();
|
||||
void startSearch(const QString &);
|
||||
|
||||
protected:
|
||||
void mouseReleaseEvent(QMouseEvent *event);
|
||||
|
||||
private:
|
||||
void initConnections();
|
||||
|
||||
WebSearchModel * m_model = nullptr;
|
||||
bool m_is_selected = false;
|
||||
ResultViewDelegate * m_styleDelegate = nullptr;
|
||||
QString m_keyWord;
|
||||
};
|
||||
|
||||
class WebSearchWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
WebSearchWidget(QWidget *parent = nullptr);
|
||||
~WebSearchWidget() = default;
|
||||
|
||||
QString getWidgetName();
|
||||
void setEnabled(const bool&);
|
||||
void clearResultSelection();
|
||||
QModelIndex getModlIndex(int row, int column);
|
||||
void setResultSelection(const QModelIndex &index);
|
||||
void LaunchBrowser();
|
||||
|
||||
private:
|
||||
void initUi();
|
||||
void initConnections();
|
||||
|
||||
bool m_enabled = true;
|
||||
QVBoxLayout * m_mainLyt = nullptr;
|
||||
QHBoxLayout * m_resultLyt = nullptr;
|
||||
TitleLabel * m_titleLabel = nullptr;
|
||||
WebSearchView * m_webSearchView = nullptr;
|
||||
QLabel * m_queryIcon = nullptr;
|
||||
|
||||
Q_SIGNALS:
|
||||
void startSearch(const QString &);
|
||||
void clearSelectedRow();
|
||||
void rowClicked();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
#endif // WEBSEARCHVIEW_H
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* KWin Style UKUI
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Yue Lan <lanyue@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "xatom-helper.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include <QX11Info>
|
||||
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <NETWM>
|
||||
|
||||
static XAtomHelper *global_instance = nullptr;
|
||||
|
||||
XAtomHelper *XAtomHelper::getInstance() {
|
||||
if(!global_instance)
|
||||
global_instance = new XAtomHelper;
|
||||
return global_instance;
|
||||
}
|
||||
|
||||
bool XAtomHelper::isFrameLessWindow(int winId) {
|
||||
auto hints = getInstance()->getWindowMotifHint(winId);
|
||||
if(hints.flags == MWM_HINTS_DECORATIONS && hints.functions == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XAtomHelper::isWindowDecorateBorderOnly(int winId) {
|
||||
return isWindowMotifHintDecorateBorderOnly(getInstance()->getWindowMotifHint(winId));
|
||||
}
|
||||
|
||||
bool XAtomHelper::isWindowMotifHintDecorateBorderOnly(const MotifWmHints &hint) {
|
||||
bool isDeco = false;
|
||||
if(hint.flags & MWM_HINTS_DECORATIONS && hint.flags != MWM_HINTS_DECORATIONS) {
|
||||
if(hint.decorations == MWM_DECOR_BORDER)
|
||||
isDeco = true;
|
||||
}
|
||||
return isDeco;
|
||||
}
|
||||
|
||||
bool XAtomHelper::isUKUICsdSupported() {
|
||||
// fixme:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XAtomHelper::isUKUIDecorationWindow(int winId) {
|
||||
if(m_ukuiDecorationAtion == None)
|
||||
return false;
|
||||
|
||||
Atom type;
|
||||
int format;
|
||||
ulong nitems;
|
||||
ulong bytes_after;
|
||||
uchar *data;
|
||||
|
||||
bool isUKUIDecoration = false;
|
||||
|
||||
XGetWindowProperty(QX11Info::display(), winId, m_ukuiDecorationAtion,
|
||||
0, LONG_MAX, false,
|
||||
m_ukuiDecorationAtion, &type,
|
||||
&format, &nitems,
|
||||
&bytes_after, &data);
|
||||
|
||||
if(type == m_ukuiDecorationAtion) {
|
||||
if(nitems == 1) {
|
||||
isUKUIDecoration = data[0];
|
||||
}
|
||||
}
|
||||
|
||||
return isUKUIDecoration;
|
||||
}
|
||||
|
||||
UnityCorners XAtomHelper::getWindowBorderRadius(int winId) {
|
||||
UnityCorners corners;
|
||||
|
||||
Atom type;
|
||||
int format;
|
||||
ulong nitems;
|
||||
ulong bytes_after;
|
||||
uchar *data;
|
||||
|
||||
if(m_unityBorderRadiusAtom != None) {
|
||||
XGetWindowProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom,
|
||||
0, LONG_MAX, false,
|
||||
XA_CARDINAL, &type,
|
||||
&format, &nitems,
|
||||
&bytes_after, &data);
|
||||
|
||||
if(type == XA_CARDINAL) {
|
||||
if(nitems == 4) {
|
||||
corners.topLeft = static_cast<ulong>(data[0]);
|
||||
corners.topRight = static_cast<ulong>(data[1 * sizeof(ulong)]);
|
||||
corners.bottomLeft = static_cast<ulong>(data[2 * sizeof(ulong)]);
|
||||
corners.bottomRight = static_cast<ulong>(data[3 * sizeof(ulong)]);
|
||||
}
|
||||
XFree(data);
|
||||
}
|
||||
}
|
||||
|
||||
return corners;
|
||||
}
|
||||
|
||||
void XAtomHelper::setWindowBorderRadius(int winId, const UnityCorners &data) {
|
||||
if(m_unityBorderRadiusAtom == None)
|
||||
return;
|
||||
|
||||
ulong corners[4] = {data.topLeft, data.topRight, data.bottomLeft, data.bottomRight};
|
||||
|
||||
XChangeProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom, XA_CARDINAL,
|
||||
32, XCB_PROP_MODE_REPLACE, (const unsigned char *) &corners, sizeof(corners) / sizeof(corners[0]));
|
||||
}
|
||||
|
||||
void XAtomHelper::setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight) {
|
||||
if(m_unityBorderRadiusAtom == None)
|
||||
return;
|
||||
|
||||
ulong corners[4] = {(ulong)topLeft, (ulong)topRight, (ulong)bottomLeft, (ulong)bottomRight};
|
||||
|
||||
XChangeProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom, XA_CARDINAL,
|
||||
32, XCB_PROP_MODE_REPLACE, (const unsigned char *) &corners, sizeof(corners) / sizeof(corners[0]));
|
||||
}
|
||||
|
||||
void XAtomHelper::setUKUIDecoraiontHint(int winId, bool set) {
|
||||
if(m_ukuiDecorationAtion == None)
|
||||
return;
|
||||
|
||||
XChangeProperty(QX11Info::display(), winId, m_ukuiDecorationAtion, m_ukuiDecorationAtion, 32, XCB_PROP_MODE_REPLACE, (const unsigned char *) &set, 1);
|
||||
}
|
||||
|
||||
void XAtomHelper::setWindowMotifHint(int winId, const MotifWmHints &hints) {
|
||||
if(m_unityBorderRadiusAtom == None)
|
||||
return;
|
||||
|
||||
XChangeProperty(QX11Info::display(), winId, m_motifWMHintsAtom, m_motifWMHintsAtom,
|
||||
32, XCB_PROP_MODE_REPLACE, (const unsigned char *)&hints, sizeof(MotifWmHints) / sizeof(ulong));
|
||||
}
|
||||
|
||||
MotifWmHints XAtomHelper::getWindowMotifHint(int winId) {
|
||||
MotifWmHints hints;
|
||||
|
||||
if(m_unityBorderRadiusAtom == None)
|
||||
return hints;
|
||||
|
||||
uchar *data;
|
||||
Atom type;
|
||||
int format;
|
||||
ulong nitems;
|
||||
ulong bytes_after;
|
||||
|
||||
XGetWindowProperty(QX11Info::display(), winId, m_motifWMHintsAtom,
|
||||
0, sizeof(MotifWmHints) / sizeof(long), false, AnyPropertyType, &type,
|
||||
&format, &nitems, &bytes_after, &data);
|
||||
|
||||
if(type == None) {
|
||||
return hints;
|
||||
} else {
|
||||
hints = *(MotifWmHints *)data;
|
||||
XFree(data);
|
||||
}
|
||||
return hints;
|
||||
}
|
||||
|
||||
XAtomHelper::XAtomHelper(QObject *parent) : QObject(parent) {
|
||||
if(!QX11Info::isPlatformX11())
|
||||
return;
|
||||
|
||||
m_motifWMHintsAtom = XInternAtom(QX11Info::display(), "_MOTIF_WM_HINTS", true);
|
||||
m_unityBorderRadiusAtom = XInternAtom(QX11Info::display(), "_UNITY_GTK_BORDER_RADIUS", false);
|
||||
m_ukuiDecorationAtion = XInternAtom(QX11Info::display(), "_KWIN_UKUI_DECORAION", false);
|
||||
}
|
||||
|
||||
Atom XAtomHelper::registerUKUICsdNetWmSupportAtom() {
|
||||
// fixme:
|
||||
return None;
|
||||
}
|
||||
|
||||
void XAtomHelper::unregisterUKUICsdNetWmSupportAtom() {
|
||||
// fixme:
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* KWin Style UKUI
|
||||
*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Yue Lan <lanyue@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef XATOMHELPER_H
|
||||
#define XATOMHELPER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <NETWM>
|
||||
|
||||
struct UnityCorners {
|
||||
ulong topLeft = 0;
|
||||
ulong topRight = 0;
|
||||
ulong bottomLeft = 0;
|
||||
ulong bottomRight = 0;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
ulong flags = 0;
|
||||
ulong functions = 0;
|
||||
ulong decorations = 0;
|
||||
long input_mode = 0;
|
||||
ulong status = 0;
|
||||
} MotifWmHints, MwmHints;
|
||||
|
||||
#define MWM_HINTS_FUNCTIONS (1L << 0)
|
||||
#define MWM_HINTS_DECORATIONS (1L << 1)
|
||||
#define MWM_HINTS_INPUT_MODE (1L << 2)
|
||||
#define MWM_HINTS_STATUS (1L << 3)
|
||||
|
||||
#define MWM_FUNC_ALL (1L << 0)
|
||||
#define MWM_FUNC_RESIZE (1L << 1)
|
||||
#define MWM_FUNC_MOVE (1L << 2)
|
||||
#define MWM_FUNC_MINIMIZE (1L << 3)
|
||||
#define MWM_FUNC_MAXIMIZE (1L << 4)
|
||||
#define MWM_FUNC_CLOSE (1L << 5)
|
||||
|
||||
#define MWM_DECOR_ALL (1L << 0)
|
||||
#define MWM_DECOR_BORDER (1L << 1)
|
||||
#define MWM_DECOR_RESIZEH (1L << 2)
|
||||
#define MWM_DECOR_TITLE (1L << 3)
|
||||
#define MWM_DECOR_MENU (1L << 4)
|
||||
#define MWM_DECOR_MINIMIZE (1L << 5)
|
||||
#define MWM_DECOR_MAXIMIZE (1L << 6)
|
||||
|
||||
#define MWM_INPUT_MODELESS 0
|
||||
#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1
|
||||
#define MWM_INPUT_SYSTEM_MODAL 2
|
||||
#define MWM_INPUT_FULL_APPLICATION_MODAL 3
|
||||
#define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL
|
||||
|
||||
#define MWM_TEAROFF_WINDOW (1L<<0)
|
||||
|
||||
namespace UKUI {
|
||||
class Decoration;
|
||||
}
|
||||
|
||||
class XAtomHelper : public QObject {
|
||||
// friend class UKUI::Decoration;
|
||||
Q_OBJECT
|
||||
public:
|
||||
static XAtomHelper *getInstance();
|
||||
|
||||
static bool isFrameLessWindow(int winId);
|
||||
|
||||
static bool isWindowDecorateBorderOnly(int winId);
|
||||
static bool isWindowMotifHintDecorateBorderOnly(const MotifWmHints &hint);
|
||||
bool isUKUICsdSupported();
|
||||
bool isUKUIDecorationWindow(int winId);
|
||||
|
||||
UnityCorners getWindowBorderRadius(int winId);
|
||||
void setWindowBorderRadius(int winId, const UnityCorners &data);
|
||||
void setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight);
|
||||
void setUKUIDecoraiontHint(int winId, bool set = true);
|
||||
|
||||
void setWindowMotifHint(int winId, const MotifWmHints &hints);
|
||||
MotifWmHints getWindowMotifHint(int winId);
|
||||
|
||||
private:
|
||||
explicit XAtomHelper(QObject *parent = nullptr);
|
||||
|
||||
Atom registerUKUICsdNetWmSupportAtom();
|
||||
void unregisterUKUICsdNetWmSupportAtom();
|
||||
|
||||
Atom m_motifWMHintsAtom = None;
|
||||
Atom m_unityBorderRadiusAtom = None;
|
||||
Atom m_ukuiDecorationAtion = None;
|
||||
};
|
||||
|
||||
#endif // XATOMHELPER_H
|
|
@ -0,0 +1,7 @@
|
|||
INCLUDEPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/xatom-helper.h \
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/xatom-helper.cpp \
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangzihao <zhangzihao@kylinos.cn>
|
||||
* Modified by: zhangpengfei <zhangpengfei@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#include "chinese-segmentation.h"
|
||||
#include <QFileInfo>
|
||||
#include <QDebug>
|
||||
static ChineseSegmentation *global_instance_chinese_segmentation = nullptr;
|
||||
QMutex ChineseSegmentation::m_mutex;
|
||||
|
||||
ChineseSegmentation::ChineseSegmentation() {
|
||||
const char * const DICT_PATH = "/usr/share/ukui-search/res/dict/jieba.dict.utf8";
|
||||
const char * const HMM_PATH = "/usr/share/ukui-search/res/dict/hmm_model.utf8";
|
||||
const char * const USER_DICT_PATH = "/usr/share/ukui-search/res/dict/user.dict.utf8";
|
||||
const char * const IDF_PATH = "/usr/share/ukui-search/res/dict/idf.utf8";
|
||||
const char * const STOP_WORD_PATH = "/usr/share/ukui-search/res/dict/stop_words.utf8";
|
||||
m_jieba = new cppjieba::Jieba(DICT_PATH,
|
||||
HMM_PATH,
|
||||
USER_DICT_PATH,
|
||||
IDF_PATH,
|
||||
STOP_WORD_PATH,
|
||||
"");
|
||||
}
|
||||
|
||||
ChineseSegmentation::~ChineseSegmentation() {
|
||||
if(m_jieba)
|
||||
delete m_jieba;
|
||||
m_jieba = nullptr;
|
||||
}
|
||||
|
||||
ChineseSegmentation *ChineseSegmentation::getInstance() {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if(!global_instance_chinese_segmentation) {
|
||||
global_instance_chinese_segmentation = new ChineseSegmentation;
|
||||
}
|
||||
return global_instance_chinese_segmentation;
|
||||
}
|
||||
|
||||
QVector<SKeyWord> ChineseSegmentation::callSegement(std::string s) {
|
||||
// std::string s;
|
||||
// s = str.toStdString();
|
||||
// str.squeeze();
|
||||
|
||||
const size_t topk = -1;
|
||||
std::vector<cppjieba::KeyWord> keywordres;
|
||||
ChineseSegmentation::m_jieba->extractor.Extract(s, keywordres, topk);
|
||||
std::string().swap(s);
|
||||
QVector<SKeyWord> vecNeeds;
|
||||
convert(keywordres, vecNeeds);
|
||||
|
||||
keywordres.clear();
|
||||
// keywordres.shrink_to_fit();
|
||||
return vecNeeds;
|
||||
|
||||
}
|
||||
|
||||
std::vector<cppjieba::KeyWord> ChineseSegmentation::callSegementStd(const std::string &str) {
|
||||
|
||||
const size_t topk = -1;
|
||||
std::vector<cppjieba::KeyWord> keywordres;
|
||||
ChineseSegmentation::m_jieba->extractor.Extract(str, keywordres, topk);
|
||||
|
||||
return keywordres;
|
||||
}
|
||||
|
||||
void ChineseSegmentation::convert(std::vector<cppjieba::KeyWord> &keywordres, QVector<SKeyWord> &kw) {
|
||||
for(auto i : keywordres) {
|
||||
SKeyWord temp;
|
||||
temp.word = i.word;
|
||||
temp.offsets = QVector<size_t>::fromStdVector(i.offsets);
|
||||
temp.weight = i.weight;
|
||||
kw.append(temp);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (C) 2020, KylinSoft Co., Ltd.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: zhangzihao <zhangzihao@kylinos.cn>
|
||||
* Modified by: zhangpengfei <zhangpengfei@kylinos.cn>
|
||||
*
|
||||
*/
|
||||
#ifndef CHINESESEGMENTATION_H
|
||||
#define CHINESESEGMENTATION_H
|
||||
|
||||
#include "libchinese-segmentation_global.h"
|
||||
#include "cppjieba/Jieba.hpp"
|
||||
//#include "Logging.hpp"
|
||||
//#include "LocalVector.hpp"
|
||||
//#include "cppjieba/QuerySegment.hpp"
|
||||
#include "cppjieba/KeywordExtractor.hpp"
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
#include <QMutex>
|
||||
|
||||
struct SKeyWord {
|
||||
std::string word;
|
||||
QVector<size_t> offsets;
|
||||
double weight;
|
||||
~SKeyWord() {
|
||||
word = std::move("");
|
||||
offsets.clear();
|
||||
offsets.shrink_to_fit();
|
||||
}
|
||||
};
|
||||
|
||||
class CHINESESEGMENTATION_EXPORT ChineseSegmentation {
|
||||
public:
|
||||
static ChineseSegmentation *getInstance();
|
||||
QVector<SKeyWord> callSegement(std::string s);
|
||||
std::vector<cppjieba::KeyWord> callSegementStd(const std::string& str);
|
||||
|
||||
private:
|
||||
explicit ChineseSegmentation();
|
||||
~ChineseSegmentation();
|
||||
void convert(std::vector<cppjieba::KeyWord>& keywordres, QVector<SKeyWord>& kw);
|
||||
|
||||
private:
|
||||
static QMutex m_mutex;
|
||||
cppjieba::Jieba *m_jieba;
|
||||
|
||||
};
|
||||
|
||||
#endif // CHINESESEGMENTATION_H
|
|
@ -0,0 +1,634 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <QDebug>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "limonp/Md5.hpp"
|
||||
#include "Unicode.hpp"
|
||||
#include "darts.h"
|
||||
|
||||
namespace cppjieba {
|
||||
|
||||
using std::pair;
|
||||
|
||||
struct DatElement {
|
||||
string word;
|
||||
string tag;
|
||||
double weight = 0;
|
||||
|
||||
bool operator < (const DatElement & b) const {
|
||||
if (word == b.word) {
|
||||
return this->weight > b.weight;
|
||||
}
|
||||
|
||||
return this->word < b.word;
|
||||
}
|
||||
};
|
||||
|
||||
struct IdfElement {
|
||||
string word;
|
||||
double idf = 0;
|
||||
|
||||
bool operator < (const IdfElement & b) const {
|
||||
if (word == b.word) {
|
||||
return this->idf > b.idf;
|
||||
}
|
||||
|
||||
return this->word < b.word;
|
||||
}
|
||||
};
|
||||
|
||||
struct PinYinElement
|
||||
{
|
||||
string word;
|
||||
string tag;
|
||||
|
||||
bool operator < (const DatElement & b) const {
|
||||
return this->word < b.word;
|
||||
}
|
||||
};
|
||||
|
||||
inline std::ostream & operator << (std::ostream& os, const DatElement & elem) {
|
||||
return os << "word=" << elem.word << "/tag=" << elem.tag << "/weight=" << elem.weight;
|
||||
}
|
||||
|
||||
struct DatMemElem {
|
||||
double weight = 0.0;
|
||||
char tag[8] = {};
|
||||
|
||||
void SetTag(const string & str) {
|
||||
memset(&tag[0], 0, sizeof(tag));
|
||||
strncpy(&tag[0], str.c_str(), std::min(str.size(), sizeof(tag) - 1));
|
||||
}
|
||||
|
||||
string GetTag() const {
|
||||
return &tag[0];
|
||||
}
|
||||
};
|
||||
|
||||
struct PinYinMemElem {
|
||||
char tag[6] = {};
|
||||
|
||||
void SetTag(const string & str) {
|
||||
memset(&tag[0], 0, sizeof(tag));
|
||||
strncpy(&tag[0], str.c_str(), std::min(str.size(), sizeof(tag) - 1));
|
||||
}
|
||||
|
||||
string GetTag() const {
|
||||
return &tag[0];
|
||||
}
|
||||
};
|
||||
|
||||
inline std::ostream & operator << (std::ostream& os, const DatMemElem & elem) {
|
||||
return os << "/tag=" << elem.GetTag() << "/weight=" << elem.weight;
|
||||
}
|
||||
|
||||
struct DatDag {
|
||||
limonp::LocalVector<pair<size_t, const DatMemElem *> > nexts;
|
||||
double max_weight;
|
||||
int max_next;
|
||||
};
|
||||
|
||||
typedef Darts::DoubleArray JiebaDAT;
|
||||
|
||||
|
||||
struct CacheFileHeader {
|
||||
char md5_hex[32] = {};
|
||||
double min_weight = 0;
|
||||
uint32_t elements_num = 0;
|
||||
uint32_t dat_size = 0;
|
||||
};
|
||||
|
||||
static_assert(sizeof(DatMemElem) == 16, "DatMemElem length invalid");
|
||||
static_assert((sizeof(CacheFileHeader) % sizeof(DatMemElem)) == 0, "DatMemElem CacheFileHeader length equal");
|
||||
|
||||
|
||||
class DatTrie {
|
||||
public:
|
||||
DatTrie() {}
|
||||
~DatTrie() {
|
||||
::munmap(mmap_addr_, mmap_length_);
|
||||
mmap_addr_ = nullptr;
|
||||
mmap_length_ = 0;
|
||||
|
||||
::close(mmap_fd_);
|
||||
mmap_fd_ = -1;
|
||||
}
|
||||
|
||||
const DatMemElem * Find(const string & key) const {
|
||||
JiebaDAT::result_pair_type find_result;
|
||||
dat_.exactMatchSearch(key.c_str(), find_result);
|
||||
|
||||
if ((0 == find_result.length) || (find_result.value < 0) || ((size_t)find_result.value >= elements_num_)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &elements_ptr_[ find_result.value ];
|
||||
}
|
||||
|
||||
const double Find(const string & key, std::size_t length, std::size_t node_pos) const {
|
||||
JiebaDAT::result_pair_type find_result;
|
||||
dat_.exactMatchSearch(key.c_str(), find_result, length, node_pos);
|
||||
|
||||
if ((0 == find_result.length) || (find_result.value < 0) || ((size_t)find_result.value >= elements_num_)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return idf_elements_ptr_[ find_result.value ];
|
||||
}
|
||||
|
||||
const PinYinMemElem * PinYinFind(const string & key) const {
|
||||
JiebaDAT::result_pair_type find_result;
|
||||
dat_.exactMatchSearch(key.c_str(), find_result);
|
||||
|
||||
if ((0 == find_result.length) || (find_result.value < 0) || ((size_t)find_result.value >= elements_num_)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &pinyin_elements_ptr_[ find_result.value ];
|
||||
}
|
||||
|
||||
void Find(RuneStrArray::const_iterator begin, RuneStrArray::const_iterator end,
|
||||
vector<struct DatDag>&res, size_t max_word_len) const {
|
||||
|
||||
res.clear();
|
||||
res.resize(end - begin);
|
||||
|
||||
string text_str;
|
||||
EncodeRunesToString(begin, end, text_str);
|
||||
|
||||
static const size_t max_num = 128;
|
||||
JiebaDAT::result_pair_type result_pairs[max_num] = {};
|
||||
|
||||
for (size_t i = 0, begin_pos = 0; i < size_t(end - begin); i++) {
|
||||
|
||||
std::size_t num_results = dat_.commonPrefixSearch(&text_str[begin_pos], &result_pairs[0], max_num);
|
||||
|
||||
res[i].nexts.push_back(pair<size_t, const DatMemElem *>(i + 1, nullptr));
|
||||
|
||||
for (std::size_t idx = 0; idx < num_results; ++idx) {
|
||||
auto & match = result_pairs[idx];
|
||||
|
||||
if ((match.value < 0) || ((size_t)match.value >= elements_num_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const char_num = Utf8CharNum(&text_str[begin_pos], match.length);
|
||||
|
||||
if (char_num > max_word_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto pValue = &elements_ptr_[match.value];
|
||||
|
||||
if (1 == char_num) {
|
||||
res[i].nexts[0].second = pValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
res[i].nexts.push_back(pair<size_t, const DatMemElem *>(i + char_num, pValue));
|
||||
}
|
||||
|
||||
begin_pos += limonp::UnicodeToUtf8Bytes((begin + i)->rune);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
void Find_Reverse(RuneStrArray::const_iterator begin, RuneStrArray::const_iterator end,
|
||||
vector<struct DatDag>&res, size_t max_word_len) const {
|
||||
|
||||
res.clear();
|
||||
res.resize(end - begin);
|
||||
|
||||
string text_str;
|
||||
EncodeRunesToString(begin, end, text_str);
|
||||
|
||||
static const size_t max_num = 128;
|
||||
JiebaDAT::result_pair_type result_pairs[max_num] = {};
|
||||
|
||||
size_t str_size = end - begin;
|
||||
for (size_t i = 0, begin_pos = text_str.size(); i < str_size; i++) {
|
||||
|
||||
begin_pos -= (end - i - 1)->len;
|
||||
std::size_t num_results = dat_.commonPrefixSearch(&text_str[begin_pos], &result_pairs[0], max_num);
|
||||
res[str_size - i - 1].nexts.push_back(pair<size_t, const DatMemElem *>(str_size - i, nullptr));
|
||||
|
||||
for (std::size_t idx = 0; idx < num_results; ++idx) {
|
||||
auto & match = result_pairs[idx];
|
||||
if ((match.value < 0) || ((size_t)match.value >= elements_num_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const char_num = Utf8CharNum(&text_str[begin_pos], match.length);
|
||||
|
||||
if (char_num > max_word_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto pValue = &elements_ptr_[match.value];
|
||||
|
||||
if (1 == char_num) {
|
||||
res[str_size - i - 1].nexts[0].second = pValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
res[str_size - i - 1].nexts.push_back(pair<size_t, const DatMemElem *>(str_size - 1 - i + char_num, pValue));
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
void Find(RuneStrArray::const_iterator begin, RuneStrArray::const_iterator end,
|
||||
vector<WordRange>& words, size_t max_word_len) const {
|
||||
|
||||
string text_str;
|
||||
EncodeRunesToString(begin, end, text_str);
|
||||
|
||||
static const size_t max_num = 128;
|
||||
JiebaDAT::result_pair_type result_pairs[max_num] = {};//存放字典查询结果
|
||||
size_t str_size = end - begin;
|
||||
double max_weight[str_size];//存放逆向路径最大weight
|
||||
for (size_t i = 0; i<str_size; i++) {
|
||||
max_weight[i] = -3.14e+100;
|
||||
}
|
||||
int max_next[str_size];//存放动态规划后的分词结果
|
||||
memset(max_next,-1,str_size);
|
||||
|
||||
double val(0);
|
||||
for (size_t i = 0, begin_pos = text_str.size(); i < str_size; i++) {
|
||||
size_t nextPos = str_size - i;//逆向计算
|
||||
begin_pos -= (end - i - 1)->len;
|
||||
|
||||
std::size_t num_results = dat_.commonPrefixSearch(&text_str[begin_pos], &result_pairs[0], max_num);
|
||||
if (0 == num_results) {//字典不存在则单独分词
|
||||
val = min_weight_;
|
||||
|
||||
if (nextPos < str_size) {
|
||||
val += max_weight[nextPos];
|
||||
}
|
||||
if ((nextPos <= str_size) && (val > max_weight[nextPos - 1])) {
|
||||
max_weight[nextPos - 1] = val;
|
||||
max_next[nextPos - 1] = nextPos;
|
||||
}
|
||||
} else {//字典存在则根据查询结果数量计算最大概率路径
|
||||
for (std::size_t idx = 0; idx < num_results; ++idx) {
|
||||
auto & match = result_pairs[idx];
|
||||
if ((match.value < 0) || ((size_t)match.value >= elements_num_)) {
|
||||
continue;
|
||||
}
|
||||
auto const char_num = Utf8CharNum(&text_str[begin_pos], match.length);
|
||||
if (char_num > max_word_len) {
|
||||
continue;
|
||||
}
|
||||
auto pValue = &elements_ptr_[match.value];
|
||||
|
||||
val = pValue->weight;
|
||||
if (1 == char_num) {
|
||||
if (nextPos < str_size) {
|
||||
val += max_weight[nextPos];
|
||||
}
|
||||
if ((nextPos <= str_size) && (val > max_weight[nextPos - 1])) {
|
||||
max_weight[nextPos - 1] = val;
|
||||
max_next[nextPos - 1] = nextPos;
|
||||
}
|
||||
} else {
|
||||
if (nextPos - 1 + char_num < str_size) {
|
||||
val += max_weight[nextPos - 1 + char_num];
|
||||
}
|
||||
if ((nextPos - 1 + char_num <= str_size) && (val > max_weight[nextPos - 1])) {
|
||||
max_weight[nextPos - 1] = val;
|
||||
max_next[nextPos - 1] = nextPos - 1 + char_num;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < str_size;) {//统计动态规划结果
|
||||
assert(max_next[i] > i);
|
||||
assert(max_next[i] <= str_size);
|
||||
WordRange wr(begin + i, begin + max_next[i] - 1);
|
||||
words.push_back(wr);
|
||||
i = max_next[i];
|
||||
}
|
||||
}
|
||||
double GetMinWeight() const {
|
||||
return min_weight_;
|
||||
}
|
||||
|
||||
void SetMinWeight(double d) {
|
||||
min_weight_ = d ;
|
||||
}
|
||||
|
||||
bool InitBuildDat(vector<DatElement>& elements, const string & dat_cache_file, const string & md5) {
|
||||
BuildDatCache(elements, dat_cache_file, md5);
|
||||
return InitAttachDat(dat_cache_file, md5);
|
||||
}
|
||||
|
||||
bool InitBuildDat(vector<IdfElement>& elements, const string & dat_cache_file, const string & md5) {
|
||||
BuildDatCache(elements, dat_cache_file, md5);
|
||||
return InitIdfAttachDat(dat_cache_file, md5);
|
||||
}
|
||||
|
||||
bool InitBuildDat(vector<PinYinElement>& elements, const string & dat_cache_file, const string & md5) {
|
||||
BuildDatCache(elements, dat_cache_file, md5);
|
||||
return InitPinYinAttachDat(dat_cache_file, md5);
|
||||
}
|
||||
|
||||
bool InitAttachDat(const string & dat_cache_file, const string & md5) {
|
||||
mmap_fd_ = ::open(dat_cache_file.c_str(), O_RDONLY);
|
||||
|
||||
if (mmap_fd_ < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto seek_off = ::lseek(mmap_fd_, 0, SEEK_END);
|
||||
assert(seek_off >= 0);
|
||||
mmap_length_ = seek_off;
|
||||
|
||||
mmap_addr_ = reinterpret_cast<char *>(mmap(NULL, mmap_length_, PROT_READ, MAP_SHARED, mmap_fd_, 0));
|
||||
assert(MAP_FAILED != mmap_addr_);
|
||||
|
||||
assert(mmap_length_ >= sizeof(CacheFileHeader));
|
||||
CacheFileHeader & header = *reinterpret_cast<CacheFileHeader*>(mmap_addr_);
|
||||
elements_num_ = header.elements_num;
|
||||
min_weight_ = header.min_weight;
|
||||
assert(sizeof(header.md5_hex) == md5.size());
|
||||
|
||||
if (0 != memcmp(&header.md5_hex[0], md5.c_str(), md5.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(mmap_length_ == sizeof(header) + header.elements_num * sizeof(DatMemElem) + header.dat_size * dat_.unit_size());
|
||||
elements_ptr_ = (const DatMemElem *)(mmap_addr_ + sizeof(header));
|
||||
const char * dat_ptr = mmap_addr_ + sizeof(header) + sizeof(DatMemElem) * elements_num_;
|
||||
dat_.set_array(dat_ptr, header.dat_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InitIdfAttachDat(const string & dat_cache_file, const string & md5) {
|
||||
mmap_fd_ = ::open(dat_cache_file.c_str(), O_RDONLY);
|
||||
|
||||
if (mmap_fd_ < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto seek_off = ::lseek(mmap_fd_, 0, SEEK_END);
|
||||
assert(seek_off >= 0);
|
||||
mmap_length_ = seek_off;
|
||||
|
||||
mmap_addr_ = reinterpret_cast<char *>(mmap(NULL, mmap_length_, PROT_READ, MAP_SHARED, mmap_fd_, 0));
|
||||
assert(MAP_FAILED != mmap_addr_);
|
||||
|
||||
assert(mmap_length_ >= sizeof(CacheFileHeader));
|
||||
CacheFileHeader & header = *reinterpret_cast<CacheFileHeader*>(mmap_addr_);
|
||||
elements_num_ = header.elements_num;
|
||||
min_weight_ = header.min_weight;
|
||||
assert(sizeof(header.md5_hex) == md5.size());
|
||||
|
||||
if (0 != memcmp(&header.md5_hex[0], md5.c_str(), md5.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(mmap_length_ == sizeof(header) + header.elements_num * sizeof(double) + header.dat_size * dat_.unit_size());
|
||||
idf_elements_ptr_ = (const double *)(mmap_addr_ + sizeof(header));
|
||||
const char * dat_ptr = mmap_addr_ + sizeof(header) + sizeof(double) * elements_num_;
|
||||
dat_.set_array(dat_ptr, header.dat_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InitPinYinAttachDat(const string & dat_cache_file, const string & md5) {
|
||||
mmap_fd_ = ::open(dat_cache_file.c_str(), O_RDONLY);
|
||||
|
||||
if (mmap_fd_ < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto seek_off = ::lseek(mmap_fd_, 0, SEEK_END);
|
||||
assert(seek_off >= 0);
|
||||
mmap_length_ = seek_off;
|
||||
|
||||
mmap_addr_ = reinterpret_cast<char *>(mmap(NULL, mmap_length_, PROT_READ, MAP_SHARED, mmap_fd_, 0));
|
||||
assert(MAP_FAILED != mmap_addr_);
|
||||
|
||||
assert(mmap_length_ >= sizeof(CacheFileHeader));
|
||||
CacheFileHeader & header = *reinterpret_cast<CacheFileHeader*>(mmap_addr_);
|
||||
elements_num_ = header.elements_num;
|
||||
min_weight_ = header.min_weight;
|
||||
assert(sizeof(header.md5_hex) == md5.size());
|
||||
|
||||
if (0 != memcmp(&header.md5_hex[0], md5.c_str(), md5.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(mmap_length_ == sizeof(header) + header.elements_num * sizeof(PinYinMemElem) + header.dat_size * dat_.unit_size());
|
||||
pinyin_elements_ptr_ = (const PinYinMemElem *)(mmap_addr_ + sizeof(header));
|
||||
const char * dat_ptr = mmap_addr_ + sizeof(header) + sizeof(PinYinMemElem) * elements_num_;
|
||||
dat_.set_array(dat_ptr, header.dat_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void BuildDatCache(vector<DatElement>& elements, const string & dat_cache_file, const string & md5) {
|
||||
std::sort(elements.begin(), elements.end());
|
||||
|
||||
vector<const char*> keys_ptr_vec;
|
||||
vector<int> values_vec;
|
||||
vector<DatMemElem> mem_elem_vec;
|
||||
|
||||
keys_ptr_vec.reserve(elements.size());
|
||||
values_vec.reserve(elements.size());
|
||||
mem_elem_vec.reserve(elements.size());
|
||||
|
||||
CacheFileHeader header;
|
||||
header.min_weight = min_weight_;
|
||||
assert(sizeof(header.md5_hex) == md5.size());
|
||||
memcpy(&header.md5_hex[0], md5.c_str(), md5.size());
|
||||
|
||||
for (size_t i = 0; i < elements.size(); ++i) {
|
||||
keys_ptr_vec.push_back(elements[i].word.data());
|
||||
values_vec.push_back(i);
|
||||
mem_elem_vec.push_back(DatMemElem());
|
||||
auto & mem_elem = mem_elem_vec.back();
|
||||
mem_elem.weight = elements[i].weight;
|
||||
mem_elem.SetTag(elements[i].tag);
|
||||
}
|
||||
|
||||
auto const ret = dat_.build(keys_ptr_vec.size(), &keys_ptr_vec[0], NULL, &values_vec[0]);
|
||||
assert(0 == ret);
|
||||
header.elements_num = mem_elem_vec.size();
|
||||
header.dat_size = dat_.size();
|
||||
|
||||
{
|
||||
string tmp_filepath = string(dat_cache_file) + "_XXXXXX";
|
||||
::umask(S_IWGRP | S_IWOTH);
|
||||
//const int fd =::mkstemp(&tmp_filepath[0]);
|
||||
//原mkstemp用法有误,已修复--jxx20210519
|
||||
const int fd =::mkstemp((char *)tmp_filepath.data());
|
||||
qDebug() << "mkstemp :" << errno << tmp_filepath.data();
|
||||
assert(fd >= 0);
|
||||
::fchmod(fd, 0644);
|
||||
|
||||
auto write_bytes = ::write(fd, (const char *)&header, sizeof(header));
|
||||
write_bytes += ::write(fd, (const char *)&mem_elem_vec[0], sizeof(mem_elem_vec[0]) * mem_elem_vec.size());
|
||||
write_bytes += ::write(fd, dat_.array(), dat_.total_size());
|
||||
|
||||
assert(write_bytes == sizeof(header) + mem_elem_vec.size() * sizeof(mem_elem_vec[0]) + dat_.total_size());
|
||||
::close(fd);
|
||||
|
||||
const auto rename_ret = ::rename(tmp_filepath.c_str(), dat_cache_file.c_str());
|
||||
assert(0 == rename_ret);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildDatCache(vector<IdfElement>& elements, const string & dat_cache_file, const string & md5) {
|
||||
std::sort(elements.begin(), elements.end());
|
||||
|
||||
vector<const char*> keys_ptr_vec;
|
||||
vector<int> values_vec;
|
||||
vector<double> mem_elem_vec;
|
||||
|
||||
keys_ptr_vec.reserve(elements.size());
|
||||
values_vec.reserve(elements.size());
|
||||
mem_elem_vec.reserve(elements.size());
|
||||
|
||||
CacheFileHeader header;
|
||||
header.min_weight = min_weight_;
|
||||
assert(sizeof(header.md5_hex) == md5.size());
|
||||
memcpy(&header.md5_hex[0], md5.c_str(), md5.size());
|
||||
|
||||
for (size_t i = 0; i < elements.size(); ++i) {
|
||||
keys_ptr_vec.push_back(elements[i].word.data());
|
||||
values_vec.push_back(i);
|
||||
mem_elem_vec.push_back(elements[i].idf);
|
||||
}
|
||||
|
||||
auto const ret = dat_.build(keys_ptr_vec.size(), &keys_ptr_vec[0], NULL, &values_vec[0]);
|
||||
assert(0 == ret);
|
||||
header.elements_num = mem_elem_vec.size();
|
||||
header.dat_size = dat_.size();
|
||||
|
||||
{
|
||||
string tmp_filepath = string(dat_cache_file) + "_XXXXXX";
|
||||
::umask(S_IWGRP | S_IWOTH);
|
||||
//const int fd =::mkstemp(&tmp_filepath[0]);
|
||||
//原mkstemp用法有误,已修复--jxx20210519
|
||||
const int fd =::mkstemp((char *)tmp_filepath.data());
|
||||
qDebug() << "mkstemp error:" << errno << tmp_filepath.data();
|
||||
assert(fd >= 0);
|
||||
::fchmod(fd, 0644);
|
||||
|
||||
auto write_bytes = ::write(fd, (const char *)&header, sizeof(header));
|
||||
write_bytes += ::write(fd, (const char *)&mem_elem_vec[0], sizeof(double) * mem_elem_vec.size());
|
||||
write_bytes += ::write(fd, dat_.array(), dat_.total_size());
|
||||
|
||||
assert(write_bytes == sizeof(header) + mem_elem_vec.size() * sizeof(double) + dat_.total_size());
|
||||
::close(fd);
|
||||
|
||||
const auto rename_ret = ::rename(tmp_filepath.c_str(), dat_cache_file.c_str());
|
||||
assert(0 == rename_ret);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildDatCache(vector<PinYinElement>& elements, const string & dat_cache_file, const string & md5) {
|
||||
//std::sort(elements.begin(), elements.end());
|
||||
|
||||
vector<const char*> keys_ptr_vec;
|
||||
vector<int> values_vec;
|
||||
vector<PinYinMemElem> mem_elem_vec;
|
||||
|
||||
keys_ptr_vec.reserve(elements.size());
|
||||
values_vec.reserve(elements.size());
|
||||
mem_elem_vec.reserve(elements.size());
|
||||
|
||||
CacheFileHeader header;
|
||||
header.min_weight = min_weight_;
|
||||
assert(sizeof(header.md5_hex) == md5.size());
|
||||
memcpy(&header.md5_hex[0], md5.c_str(), md5.size());
|
||||
|
||||
for (size_t i = 0; i < elements.size(); ++i) {
|
||||
keys_ptr_vec.push_back(elements[i].word.data());
|
||||
values_vec.push_back(i);
|
||||
mem_elem_vec.push_back(PinYinMemElem());
|
||||
auto & mem_elem = mem_elem_vec.back();
|
||||
mem_elem.SetTag(elements[i].tag);
|
||||
}
|
||||
|
||||
auto const ret = dat_.build(keys_ptr_vec.size(), &keys_ptr_vec[0], NULL, &values_vec[0]);
|
||||
assert(0 == ret);
|
||||
header.elements_num = mem_elem_vec.size();
|
||||
header.dat_size = dat_.size();
|
||||
|
||||
{
|
||||
string tmp_filepath = string(dat_cache_file) + "_XXXXXX";
|
||||
::umask(S_IWGRP | S_IWOTH);
|
||||
//const int fd =::mkstemp(&tmp_filepath[0]);
|
||||
const int fd =::mkstemp((char *)tmp_filepath.data());
|
||||
qDebug() << "mkstemp :" << errno << tmp_filepath.data();
|
||||
assert(fd >= 0);
|
||||
::fchmod(fd, 0644);
|
||||
|
||||
auto write_bytes = ::write(fd, (const char *)&header, sizeof(header));
|
||||
write_bytes += ::write(fd, (const char *)&mem_elem_vec[0], sizeof(mem_elem_vec[0]) * mem_elem_vec.size());
|
||||
write_bytes += ::write(fd, dat_.array(), dat_.total_size());
|
||||
|
||||
assert(write_bytes == sizeof(header) + mem_elem_vec.size() * sizeof(mem_elem_vec[0]) + dat_.total_size());
|
||||
::close(fd);
|
||||
|
||||
const auto rename_ret = ::rename(tmp_filepath.c_str(), dat_cache_file.c_str());
|
||||
assert(0 == rename_ret);
|
||||
}
|
||||
}
|
||||
|
||||
DatTrie(const DatTrie &);
|
||||
DatTrie &operator=(const DatTrie &);
|
||||
|
||||
private:
|
||||
JiebaDAT dat_;
|
||||
const DatMemElem * elements_ptr_ = nullptr;
|
||||
const double * idf_elements_ptr_ = nullptr;
|
||||
const PinYinMemElem * pinyin_elements_ptr_ = nullptr;
|
||||
size_t elements_num_ = 0;
|
||||
double min_weight_ = 0;
|
||||
|
||||
int mmap_fd_ = -1;
|
||||
size_t mmap_length_ = 0;
|
||||
char * mmap_addr_ = nullptr;
|
||||
};
|
||||
|
||||
|
||||
inline string CalcFileListMD5(const string & files_list, size_t & file_size_sum) {
|
||||
limonp::MD5 md5;
|
||||
|
||||
const auto files = limonp::Split(files_list, "|;");
|
||||
file_size_sum = 0;
|
||||
|
||||
for (auto const & local_path : files) {
|
||||
const int fd = ::open(local_path.c_str(), O_RDONLY);
|
||||
if( fd < 0){
|
||||
continue;
|
||||
}
|
||||
auto const len = ::lseek(fd, 0, SEEK_END);
|
||||
if (len > 0) {
|
||||
void * addr = ::mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
|
||||
assert(MAP_FAILED != addr);
|
||||
|
||||
md5.Update((unsigned char *) addr, len);
|
||||
file_size_sum += len;
|
||||
|
||||
::munmap(addr, len);
|
||||
}
|
||||
::close(fd);
|
||||
}
|
||||
|
||||
md5.Final();
|
||||
return string(md5.digestChars);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <stdint.h>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include "limonp/StringUtil.hpp"
|
||||
#include "limonp/Logging.hpp"
|
||||
#include "Unicode.hpp"
|
||||
#include "DatTrie.hpp"
|
||||
#include <QDebug>
|
||||
namespace cppjieba {
|
||||
|
||||
using namespace limonp;
|
||||
|
||||
const double MIN_DOUBLE = -3.14e+100;
|
||||
const double MAX_DOUBLE = 3.14e+100;
|
||||
const size_t DICT_COLUMN_NUM = 3;
|
||||
const char* const UNKNOWN_TAG = "";
|
||||
|
||||
class DictTrie {
|
||||
public:
|
||||
enum UserWordWeightOption {
|
||||
WordWeightMin,
|
||||
WordWeightMedian,
|
||||
WordWeightMax,
|
||||
}; // enum UserWordWeightOption
|
||||
|
||||
DictTrie(const string& dict_path, const string& user_dict_paths = "", const string & dat_cache_path = "",
|
||||
UserWordWeightOption user_word_weight_opt = WordWeightMedian) {
|
||||
Init(dict_path, user_dict_paths, dat_cache_path, user_word_weight_opt);
|
||||
}
|
||||
|
||||
~DictTrie() {}
|
||||
|
||||
const DatMemElem* Find(const string & word) const {
|
||||
return dat_.Find(word);
|
||||
}
|
||||
|
||||
void Find(RuneStrArray::const_iterator begin,
|
||||
RuneStrArray::const_iterator end,
|
||||
vector<struct DatDag>&res,
|
||||
size_t max_word_len = MAX_WORD_LENGTH) const {
|
||||
dat_.Find(begin, end, res, max_word_len);
|
||||
}
|
||||
|
||||
void Find(RuneStrArray::const_iterator begin,
|
||||
RuneStrArray::const_iterator end,
|
||||
vector<WordRange>& words,
|
||||
size_t max_word_len = MAX_WORD_LENGTH) const {
|
||||
dat_.Find(begin, end, words, max_word_len);
|
||||
}
|
||||
|
||||
bool IsUserDictSingleChineseWord(const Rune& word) const {
|
||||
return IsIn(user_dict_single_chinese_word_, word);
|
||||
}
|
||||
|
||||
double GetMinWeight() const {
|
||||
return dat_.GetMinWeight();
|
||||
}
|
||||
|
||||
size_t GetTotalDictSize() const {
|
||||
return total_dict_size_;
|
||||
}
|
||||
|
||||
void InserUserDictNode(const string& line, bool saveNodeInfo = true) {
|
||||
vector<string> buf;
|
||||
DatElement node_info;
|
||||
Split(line, buf, " ");
|
||||
|
||||
if (buf.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
node_info.word = buf[0];
|
||||
node_info.weight = user_word_default_weight_;
|
||||
node_info.tag = UNKNOWN_TAG;
|
||||
|
||||
if (buf.size() == 2) {
|
||||
node_info.tag = buf[1];
|
||||
} else if (buf.size() == 3) {
|
||||
if (freq_sum_ > 0.0) {
|
||||
const int freq = atoi(buf[1].c_str());
|
||||
node_info.weight = log(1.0 * freq / freq_sum_);
|
||||
node_info.tag = buf[2];
|
||||
}
|
||||
}
|
||||
|
||||
if (saveNodeInfo) {
|
||||
static_node_infos_.push_back(node_info);
|
||||
}
|
||||
|
||||
if (Utf8CharNum(node_info.word) == 1) {
|
||||
RuneArray word;
|
||||
|
||||
if (DecodeRunesInString(node_info.word, word)) {
|
||||
user_dict_single_chinese_word_.insert(word[0]);
|
||||
} else {
|
||||
XLOG(ERROR) << "Decode " << node_info.word << " failed.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadUserDict(const string& filePaths, bool saveNodeInfo = true) {
|
||||
vector<string> files = limonp::Split(filePaths, "|;");
|
||||
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
ifstream ifs(files[i].c_str());
|
||||
XCHECK(ifs.is_open()) << "open " << files[i] << " failed";
|
||||
string line;
|
||||
|
||||
for (; getline(ifs, line);) {
|
||||
if (line.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
InserUserDictNode(line, saveNodeInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
void Init(const string& dict_path, const string& user_dict_paths, string dat_cache_path,
|
||||
UserWordWeightOption user_word_weight_opt) {
|
||||
const auto dict_list = dict_path + "|" + user_dict_paths;
|
||||
size_t file_size_sum = 0;
|
||||
const string md5 = CalcFileListMD5(dict_list, file_size_sum);
|
||||
total_dict_size_ = file_size_sum;
|
||||
|
||||
if (dat_cache_path.empty()) {
|
||||
//未指定词库数据文件存储位置的默认存储在tmp目录下--jxx20200519
|
||||
dat_cache_path = /*dict_path*/"/tmp/" + md5 + "." + to_string(user_word_weight_opt) + ".dat_cache";
|
||||
}
|
||||
QString path = QString::fromStdString(dat_cache_path);
|
||||
qDebug() << "#########Dict path:" << path;
|
||||
if (dat_.InitAttachDat(dat_cache_path, md5)) {
|
||||
LoadUserDict(user_dict_paths, false); // for load user_dict_single_chinese_word_;
|
||||
return;
|
||||
}
|
||||
|
||||
LoadDefaultDict(dict_path);
|
||||
freq_sum_ = CalcFreqSum(static_node_infos_);
|
||||
CalculateWeight(static_node_infos_, freq_sum_);
|
||||
double min_weight = 0;
|
||||
SetStaticWordWeights(user_word_weight_opt, min_weight);
|
||||
dat_.SetMinWeight(min_weight);
|
||||
|
||||
LoadUserDict(user_dict_paths);
|
||||
const auto build_ret = dat_.InitBuildDat(static_node_infos_, dat_cache_path, md5);
|
||||
assert(build_ret);
|
||||
vector<DatElement>().swap(static_node_infos_);
|
||||
}
|
||||
|
||||
void LoadDefaultDict(const string& filePath) {
|
||||
ifstream ifs(filePath.c_str());
|
||||
XCHECK(ifs.is_open()) << "open " << filePath << " failed.";
|
||||
string line;
|
||||
vector<string> buf;
|
||||
|
||||
for (; getline(ifs, line);) {
|
||||
Split(line, buf, " ");
|
||||
XCHECK(buf.size() == DICT_COLUMN_NUM) << "split result illegal, line:" << line;
|
||||
DatElement node_info;
|
||||
node_info.word = buf[0];
|
||||
node_info.weight = atof(buf[1].c_str());
|
||||
node_info.tag = buf[2];
|
||||
static_node_infos_.push_back(node_info);
|
||||
}
|
||||
}
|
||||
|
||||
static bool WeightCompare(const DatElement& lhs, const DatElement& rhs) {
|
||||
return lhs.weight < rhs.weight;
|
||||
}
|
||||
|
||||
void SetStaticWordWeights(UserWordWeightOption option, double & min_weight) {
|
||||
XCHECK(!static_node_infos_.empty());
|
||||
vector<DatElement> x = static_node_infos_;
|
||||
sort(x.begin(), x.end(), WeightCompare);
|
||||
if(x.empty()){
|
||||
return;
|
||||
}
|
||||
min_weight = x[0].weight;
|
||||
const double max_weight_ = x[x.size() - 1].weight;
|
||||
const double median_weight_ = x[x.size() / 2].weight;
|
||||
|
||||
switch (option) {
|
||||
case WordWeightMin:
|
||||
user_word_default_weight_ = min_weight;
|
||||
break;
|
||||
|
||||
case WordWeightMedian:
|
||||
user_word_default_weight_ = median_weight_;
|
||||
break;
|
||||
|
||||
default:
|
||||
user_word_default_weight_ = max_weight_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double CalcFreqSum(const vector<DatElement>& node_infos) const {
|
||||
double sum = 0.0;
|
||||
|
||||
for (size_t i = 0; i < node_infos.size(); i++) {
|
||||
sum += node_infos[i].weight;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
void CalculateWeight(vector<DatElement>& node_infos, double sum) const {
|
||||
for (size_t i = 0; i < node_infos.size(); i++) {
|
||||
DatElement& node_info = node_infos[i];
|
||||
assert(node_info.weight > 0.0);
|
||||
node_info.weight = log(double(node_info.weight) / sum);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
vector<DatElement> static_node_infos_;
|
||||
size_t total_dict_size_ = 0;
|
||||
DatTrie dat_;
|
||||
|
||||
double freq_sum_;
|
||||
double user_word_default_weight_;
|
||||
unordered_set<Rune> user_dict_single_chinese_word_;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <cassert>
|
||||
#include "limonp/Logging.hpp"
|
||||
#include "DictTrie.hpp"
|
||||
#include "SegmentBase.hpp"
|
||||
#include "Unicode.hpp"
|
||||
|
||||
namespace cppjieba {
|
||||
class FullSegment: public SegmentBase {
|
||||
public:
|
||||
FullSegment(const DictTrie* dictTrie)
|
||||
: dictTrie_(dictTrie) {
|
||||
assert(dictTrie_);
|
||||
}
|
||||
~FullSegment() { }
|
||||
|
||||
virtual void Cut(RuneStrArray::const_iterator begin,
|
||||
RuneStrArray::const_iterator end,
|
||||
vector<WordRange>& res, bool, size_t) const override {
|
||||
assert(dictTrie_);
|
||||
vector<struct DatDag> dags;
|
||||
dictTrie_->Find(begin, end, dags);
|
||||
size_t max_word_end_pos = 0;
|
||||
|
||||
for (size_t i = 0; i < dags.size(); i++) {
|
||||
for (const auto & kv : dags[i].nexts) {
|
||||
const size_t nextoffset = kv.first - 1;
|
||||
assert(nextoffset < dags.size());
|
||||
const auto wordLen = nextoffset - i + 1;
|
||||
const bool is_not_covered_single_word = ((dags[i].nexts.size() == 1) && (max_word_end_pos <= i));
|
||||
const bool is_oov = (nullptr == kv.second); //Out-of-Vocabulary
|
||||
|
||||
if ((is_not_covered_single_word) || ((not is_oov) && (wordLen >= 2))) {
|
||||
WordRange wr(begin + i, begin + nextoffset);
|
||||
res.push_back(wr);
|
||||
}
|
||||
|
||||
max_word_end_pos = max(max_word_end_pos, nextoffset + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void CutWithSentence(const string& s, RuneStrArray::const_iterator begin, RuneStrArray::const_iterator end, vector<string>& res, bool hmm,
|
||||
size_t) const override {
|
||||
|
||||
}
|
||||
virtual void CutWithSentence(const string& s, RuneStrArray::const_iterator begin, RuneStrArray::const_iterator end, unordered_map<string, KeyWord>& res, bool hmm,
|
||||
size_t) const override {
|
||||
|
||||
}
|
||||
private:
|
||||
const DictTrie* dictTrie_;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
#pragma once
|
||||
|
||||
#include "limonp/StringUtil.hpp"
|
||||
|
||||
namespace cppjieba {
|
||||
|
||||
using namespace limonp;
|
||||
typedef unordered_map<Rune, double> EmitProbMap;
|
||||
|
||||
struct HMMModel {
|
||||
/*
|
||||
* STATUS:
|
||||
* 0: HMMModel::B, 1: HMMModel::E, 2: HMMModel::M, 3:HMMModel::S
|
||||
* */
|
||||
enum {B = 0, E = 1, M = 2, S = 3, STATUS_SUM = 4};
|
||||
|
||||
HMMModel(const string& modelPath) {
|
||||
memset(startProb, 0, sizeof(startProb));
|
||||
memset(transProb, 0, sizeof(transProb));
|
||||
statMap[0] = 'B';
|
||||
statMap[1] = 'E';
|
||||
statMap[2] = 'M';
|
||||
statMap[3] = 'S';
|
||||
emitProbVec.push_back(&emitProbB);
|
||||
emitProbVec.push_back(&emitProbE);
|
||||
emitProbVec.push_back(&emitProbM);
|
||||
emitProbVec.push_back(&emitProbS);
|
||||
LoadModel(modelPath);
|
||||
}
|
||||
~HMMModel() {
|
||||
}
|
||||
void LoadModel(const string& filePath) {
|
||||
ifstream ifile(filePath.c_str());
|
||||
XCHECK(ifile.is_open()) << "open " << filePath << " failed";
|
||||
string line;
|
||||
vector<string> tmp;
|
||||
vector<string> tmp2;
|
||||
//Load startProb
|
||||
XCHECK(GetLine(ifile, line));
|
||||
Split(line, tmp, " ");
|
||||
XCHECK(tmp.size() == STATUS_SUM);
|
||||
|
||||
for (size_t j = 0; j < tmp.size(); j++) {
|
||||
startProb[j] = atof(tmp[j].c_str());
|
||||
}
|
||||
|
||||
//Load transProb
|
||||
for (size_t i = 0; i < STATUS_SUM; i++) {
|
||||
XCHECK(GetLine(ifile, line));
|
||||
Split(line, tmp, " ");
|
||||
XCHECK(tmp.size() == STATUS_SUM);
|
||||
|
||||
for (size_t j = 0; j < tmp.size(); j++) {
|
||||
transProb[i][j] = atof(tmp[j].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
//Load emitProbB
|
||||
XCHECK(GetLine(ifile, line));
|
||||
XCHECK(LoadEmitProb(line, emitProbB));
|
||||
|
||||
//Load emitProbE
|
||||
XCHECK(GetLine(ifile, line));
|
||||
XCHECK(LoadEmitProb(line, emitProbE));
|
||||
|
||||
//Load emitProbM
|
||||
XCHECK(GetLine(ifile, line));
|
||||
XCHECK(LoadEmitProb(line, emitProbM));
|
||||
|
||||
//Load emitProbS
|
||||
XCHECK(GetLine(ifile, line));
|
||||
XCHECK(LoadEmitProb(line, emitProbS));
|
||||
}
|
||||
double GetEmitProb(const EmitProbMap* ptMp, Rune key,
|
||||
double defVal)const {
|
||||
EmitProbMap::const_iterator cit = ptMp->find(key);
|
||||
|
||||
if (cit == ptMp->end()) {
|
||||
return defVal;
|
||||
}
|
||||
|
||||
return cit->second;
|
||||
}
|
||||
bool GetLine(ifstream& ifile, string& line) {
|
||||
while (getline(ifile, line)) {
|
||||
Trim(line);
|
||||
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (StartsWith(line, "#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
bool LoadEmitProb(const string& line, EmitProbMap& mp) {
|
||||
if (line.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<string> tmp, tmp2;
|
||||
RuneArray unicode;
|
||||
Split(line, tmp, ",");
|
||||
|
||||
for (size_t i = 0; i < tmp.size(); i++) {
|
||||
Split(tmp[i], tmp2, ":");
|
||||
|
||||
if (2 != tmp2.size()) {
|
||||
XLOG(ERROR) << "emitProb illegal.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DecodeRunesInString(tmp2[0], unicode) || unicode.size() != 1) {
|
||||
XLOG(ERROR) << "TransCode failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
mp[unicode[0]] = atof(tmp2[1].c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
char statMap[STATUS_SUM];
|
||||
double startProb[STATUS_SUM];
|
||||
double transProb[STATUS_SUM][STATUS_SUM];
|
||||
EmitProbMap emitProbB;
|
||||
EmitProbMap emitProbE;
|
||||
EmitProbMap emitProbM;
|
||||
EmitProbMap emitProbS;
|
||||
vector<EmitProbMap* > emitProbVec;
|
||||
}; // struct HMMModel
|
||||
|
||||
} // namespace cppjieba
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <memory.h>
|
||||
#include <cassert>
|
||||
#include "HMMModel.hpp"
|
||||
#include "SegmentBase.hpp"
|
||||
|
||||
namespace cppjieba {
|
||||
class HMMSegment: public SegmentBase {
|
||||
public:
|
||||
HMMSegment(const HMMModel* model)
|
||||
: model_(model) {
|
||||
}
|
||||
~HMMSegment() { }
|
||||
|
||||
virtual void Cut(RuneStrArray::const_iterator begin, RuneStrArray::const_iterator end, vector<WordRange>& res, bool,
|
||||
size_t) const override {
|
||||
RuneStrArray::const_iterator left = begin;
|
||||
RuneStrArray::const_iterator right = begin;
|
||||
|
||||
while (right != end) {
|
||||
if (right->rune < 0x80) { //asc码
|
||||
if (left != right) {
|
||||
InternalCut(left, right, res);
|
||||
}
|
||||
|
||||
left = right;
|
||||
|
||||
do {
|
||||
right = SequentialLetterRule(left, end);//非英文字符则返回left,否则返回left后非英文字母的位置
|
||||
|
||||
if (right != left) {
|
||||
break;
|
||||
}
|
||||
|
||||
right = NumbersRule(left, end);//非数字则返回left,否则返回left后非数字的位置
|
||||
|
||||
if (right != left) {
|
||||
break;
|
||||
}
|
||||
|
||||
right ++;
|
||||
} while (false);
|
||||
|
||||
WordRange wr(left, right - 1);
|
||||
res.push_back(wr);
|
||||
left = right;
|
||||
} else {
|
||||
right++;
|
||||
}
|
||||
}
|
||||
|
||||
if (left != right) {
|
||||
InternalCut(left, right, res);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void CutWithSentence(const string& s, RuneStrArray::const_iterator begin, RuneStrArray::const_iterator end, vector<string>& res, bool hmm,
|
||||
size_t) const override {
|
||||
|
||||
}
|
||||
virtual void CutWithSentence(const string& s, RuneStrArray::const_iterator begin, RuneStrArray::const_iterator end, unordered_map<string, KeyWord>& res, bool hmm,
|
||||
size_t) const override {
|
||||
|
||||
}
|
||||
private:
|
||||
// sequential letters rule
|
||||
RuneStrArray::const_iterator SequentialLetterRule(RuneStrArray::const_iterator begin,
|
||||
RuneStrArray::const_iterator end) const {
|
||||
Rune x = begin->rune;
|
||||
|
||||
if (('a' <= x && x <= 'z') || ('A' <= x && x <= 'Z')) {
|
||||
begin ++;
|
||||
} else {
|
||||
return begin;
|
||||
}
|
||||
|
||||
while (begin != end) {
|
||||
x = begin->rune;
|
||||
|
||||
if (('a' <= x && x <= 'z') || ('A' <= x && x <= 'Z') || ('0' <= x && x <= '9')) {
|
||||
begin ++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return begin;
|
||||
}
|
||||
//
|
||||
RuneStrArray::const_iterator NumbersRule(RuneStrArray::const_iterator begin, RuneStrArray::const_iterator end) const {
|
||||
Rune x = begin->rune;
|
||||
|
||||
if ('0' <= x && x <= '9') {
|
||||
begin ++;
|
||||
} else {
|
||||
return begin;
|
||||
}
|
||||
|
||||
while (begin != end) {
|
||||
x = begin->rune;
|
||||
|
||||
if (('0' <= x && x <= '9') || x == '.') {
|
||||
begin++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return begin;
|
||||
}
|
||||
void InternalCut(RuneStrArray::const_iterator begin, RuneStrArray::const_iterator end, vector<WordRange>& res) const {
|
||||
vector<size_t> status;
|
||||
Viterbi(begin, end, status);
|
||||
|
||||
RuneStrArray::const_iterator left = begin;
|
||||
RuneStrArray::const_iterator right;
|
||||
|
||||
for (size_t i = 0; i < status.size(); i++) {
|
||||
if (status[i] % 2) { //if (HMMModel::E == status[i] || HMMModel::S == status[i])
|
||||
right = begin + i + 1;
|
||||
WordRange wr(left, right - 1);
|
||||
res.push_back(wr);
|
||||
left = right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Viterbi(RuneStrArray::const_iterator begin,
|
||||
RuneStrArray::const_iterator end,
|
||||
vector<size_t>& status) const {
|
||||
size_t Y = HMMModel::STATUS_SUM;
|
||||
size_t X = end - begin;
|
||||
|
||||
size_t XYSize = X * Y;
|
||||
size_t now, old, stat;
|
||||
double tmp, endE, endS;
|
||||
|
||||
//vector<int> path(XYSize);
|
||||
//vector<double> weight(XYSize);
|
||||
int path[XYSize];
|
||||
double weight[XYSize];
|
||||
|
||||
//start
|
||||
for (size_t y = 0; y < Y; y++) {
|
||||
weight[0 + y * X] = model_->startProb[y] + model_->GetEmitProb(model_->emitProbVec[y], begin->rune, MIN_DOUBLE);
|
||||
path[0 + y * X] = -1;
|
||||
}
|
||||
|
||||
double emitProb;
|
||||
|
||||
for (size_t x = 1; x < X; x++) {
|
||||
for (size_t y = 0; y < Y; y++) {
|
||||
now = x + y * X;
|
||||
weight[now] = MIN_DOUBLE;
|
||||
path[now] = HMMModel::E; // warning
|
||||
emitProb = model_->GetEmitProb(model_->emitProbVec[y], (begin + x)->rune, MIN_DOUBLE);
|
||||
|
||||
for (size_t preY = 0; preY < Y; preY++) {
|
||||
old = x - 1 + preY * X;
|
||||
tmp = weight[old] + model_->transProb[preY][y] + emitProb;
|
||||
|
||||
if (tmp > weight[now]) {
|
||||
weight[now] = tmp;
|
||||
path[now] = preY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endE = weight[X - 1 + HMMModel::E * X];
|
||||
endS = weight[X - 1 + HMMModel::S * X];
|
||||
stat = 0;
|
||||
|
||||
if (endE >= endS) {
|
||||
stat = HMMModel::E;
|
||||
} else {
|
||||
stat = HMMModel::S;
|
||||
}
|
||||
|
||||
status.resize(X);
|
||||
|
||||
for (int x = X - 1 ; x >= 0; x--) {
|
||||
status[x] = stat;
|
||||
stat = path[x + stat * X];
|
||||
}
|
||||
}
|
||||
|
||||
const HMMModel* model_;
|
||||
}; // class HMMSegment
|
||||
|
||||
} // namespace cppjieba
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <stdint.h>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include "limonp/StringUtil.hpp"
|
||||
#include "limonp/Logging.hpp"
|
||||
#include "Unicode.hpp"
|
||||
#include "DatTrie.hpp"
|
||||
#include <QDebug>
|
||||
namespace cppjieba {
|
||||
|
||||
using namespace limonp;
|
||||
|
||||
const size_t IDF_COLUMN_NUM = 2;
|
||||
|
||||
class IdfTrie {
|
||||
public:
|
||||
enum UserWordWeightOption {
|
||||
WordWeightMin,
|
||||
WordWeightMedian,
|
||||
WordWeightMax,
|
||||
}; // enum UserWordWeightOption
|
||||
|
||||
IdfTrie(const string& dict_path, const string & dat_cache_path = "",
|
||||
UserWordWeightOption user_word_weight_opt = WordWeightMedian) {
|
||||
Init(dict_path, dat_cache_path, user_word_weight_opt);
|
||||
}
|
||||
|
||||
~IdfTrie() {}
|
||||
|
||||
double Find(const string & word, std::size_t length = 0, std::size_t node_pos = 0) const {
|
||||
return dat_.Find(word, length, node_pos);
|
||||
}
|
||||
|
||||
size_t GetTotalDictSize() const {
|
||||
return total_dict_size_;
|
||||
}
|
||||
|
||||
private:
|
||||
void Init(const string& dict_path, string dat_cache_path,
|
||||
UserWordWeightOption user_word_weight_opt) {
|
||||
size_t file_size_sum = 0;
|
||||
const string md5 = CalcFileListMD5(dict_path, file_size_sum);
|
||||
total_dict_size_ = file_size_sum;
|
||||
|
||||
if (dat_cache_path.empty()) {
|
||||
//未指定词库数据文件存储位置的默认存储在tmp目录下--jxx20200519
|
||||
dat_cache_path = /*dict_path*/"/tmp/" + md5 + "." + to_string(user_word_weight_opt) + ".dat_cache";
|
||||
}
|
||||
QString path = QString::fromStdString(dat_cache_path);
|
||||
qDebug() << "#########Idf path:" << path;
|
||||
if (dat_.InitIdfAttachDat(dat_cache_path, md5)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LoadDefaultIdf(dict_path);
|
||||
double idf_sum_ = CalcIdfSum(static_node_infos_);
|
||||
assert(static_node_infos_.size());
|
||||
idfAverage_ = idf_sum_ / static_node_infos_.size();
|
||||
assert(idfAverage_ > 0.0);
|
||||
double min_weight = 0;
|
||||
dat_.SetMinWeight(min_weight);
|
||||
|
||||
const auto build_ret = dat_.InitBuildDat(static_node_infos_, dat_cache_path, md5);
|
||||
assert(build_ret);
|
||||
vector<IdfElement>().swap(static_node_infos_);
|
||||
}
|
||||
|
||||
void LoadDefaultIdf(const string& filePath) {
|
||||
ifstream ifs(filePath.c_str());
|
||||
if(not ifs.is_open()){
|
||||
return ;
|
||||
}
|
||||
XCHECK(ifs.is_open()) << "open " << filePath << " failed.";
|
||||
string line;
|
||||
vector<string> buf;
|
||||
size_t lineno = 0;
|
||||
|
||||
for (; getline(ifs, line); lineno++) {
|
||||
if (line.empty()) {
|
||||
XLOG(ERROR) << "lineno: " << lineno << " empty. skipped.";
|
||||
continue;
|
||||
}
|
||||
Split(line, buf, " ");
|
||||
XCHECK(buf.size() == IDF_COLUMN_NUM) << "split result illegal, line:" << line;
|
||||
IdfElement node_info;
|
||||
node_info.word = buf[0];
|
||||
node_info.idf = atof(buf[1].c_str());
|
||||
static_node_infos_.push_back(node_info);
|
||||
}
|
||||
}
|
||||
|
||||
double CalcIdfSum(const vector<IdfElement>& node_infos) const {
|
||||
double sum = 0.0;
|
||||
|
||||
for (size_t i = 0; i < node_infos.size(); i++) {
|
||||
sum += node_infos[i].idf;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
public:
|
||||
double idfAverage_;
|
||||
private:
|
||||
vector<IdfElement> static_node_infos_;
|
||||
size_t total_dict_size_ = 0;
|
||||
DatTrie dat_;
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "QuerySegment.hpp"
|
||||
#include "KeywordExtractor.hpp"
|
||||
|
||||
namespace cppjieba {
|
||||
|
||||
class Jieba {
|
||||
public:
|
||||
Jieba(const string& dict_path,
|
||||
const string& model_path,
|
||||
const string& user_dict_path,
|
||||
const string& idfPath = "",
|
||||
const string& stopWordPath = "",
|
||||
const string& dat_cache_path = "")
|
||||
: dict_trie_(dict_path, user_dict_path, dat_cache_path),
|
||||
model_(model_path),
|
||||
mp_seg_(&dict_trie_),
|
||||
hmm_seg_(&model_),
|
||||
mix_seg_(&dict_trie_, &model_, stopWordPath),
|
||||
full_seg_(&dict_trie_),
|
||||
query_seg_(&dict_trie_, &model_, stopWordPath),
|
||||
extractor(&dict_trie_, &model_, idfPath, dat_cache_path,stopWordPath){ }
|
||||
~Jieba() { }
|
||||
|
||||
void Cut(const string& sentence, vector<string>& words, bool hmm = true) const {
|
||||
mix_seg_.CutToStr(sentence, words, hmm);
|
||||
}
|
||||
void Cut(const string& sentence, vector<Word>& words, bool hmm = true) const {
|
||||
mix_seg_.CutToWord(sentence, words, hmm);
|
||||
}
|
||||
void CutAll(const string& sentence, vector<string>& words) const {
|
||||
full_seg_.CutToStr(sentence, words);
|
||||
}
|
||||
void CutAll(const string& sentence, vector<Word>& words) const {
|
||||
full_seg_.CutToWord(sentence, words);
|
||||
}
|
||||
void CutForSearch(const string& sentence, vector<string>& words, bool hmm = true) const {
|
||||
query_seg_.CutToStr(sentence, words, hmm);
|
||||
}
|
||||
void CutForSearch(const string& sentence, vector<Word>& words, bool hmm = true) const {
|
||||
query_seg_.CutToWord(sentence, words, hmm);
|
||||
}
|
||||
void CutHMM(const string& sentence, vector<string>& words) const {
|
||||
hmm_seg_.CutToStr(sentence, words);
|
||||
}
|
||||
void CutHMM(const string& sentence, vector<Word>& words) const {
|
||||
hmm_seg_.CutToWord(sentence, words);
|
||||
}
|
||||
void CutSmall(const string& sentence, vector<string>& words, size_t max_word_len) const {
|
||||
mp_seg_.CutToStr(sentence, words, false, max_word_len);
|
||||
}
|
||||
void CutSmall(const string& sentence, vector<Word>& words, size_t max_word_len) const {
|
||||
mp_seg_.CutToWord(sentence, words, false, max_word_len);
|
||||
}
|
||||
|
||||
void Tag(const string& sentence, vector<pair<string, string> >& words) const {
|
||||
mix_seg_.Tag(sentence, words);
|
||||
}
|
||||
string LookupTag(const string &str) const {
|
||||
return mix_seg_.LookupTag(str);
|
||||
}
|
||||
bool Find(const string& word) {
|
||||
return nullptr != dict_trie_.Find(word);
|
||||
}
|
||||
|
||||
void ResetSeparators(const string& s) {
|
||||
//TODO
|
||||
mp_seg_.ResetSeparators(s);
|
||||
hmm_seg_.ResetSeparators(s);
|
||||
mix_seg_.ResetSeparators(s);
|
||||
full_seg_.ResetSeparators(s);
|
||||
query_seg_.ResetSeparators(s);
|
||||
}
|
||||
|
||||
const DictTrie* GetDictTrie() const {
|
||||
return &dict_trie_;
|
||||
}
|
||||
|
||||
const HMMModel* GetHMMModel() const {
|
||||
return &model_;
|
||||
}
|
||||
|
||||
private:
|
||||
DictTrie dict_trie_;
|
||||
HMMModel model_;
|
||||
|
||||
// They share the same dict trie and model
|
||||
MPSegment mp_seg_;
|
||||
HMMSegment hmm_seg_;
|
||||
MixSegment mix_seg_;
|
||||
FullSegment full_seg_;
|
||||
QuerySegment query_seg_;
|
||||
|
||||
public:
|
||||
KeywordExtractor extractor;
|
||||
}; // class Jieba
|
||||
|
||||
} // namespace cppjieba
|
||||
|