Compare commits

...

9 Commits
0.9.0 ... 081

Author SHA1 Message Date
Laurent Trinques
264392cd0e Update SingleApplication to upstream master 2021-03-02 14:06:19 +01:00
Laurent Trinques
f53e429771 Upgrade pugixml XML parser to 1.11 release
https://pugixml.org/docs/manual.html#v1.11
2021-03-02 14:05:29 +01:00
joshua
a6e55e1918 Fix wrong element type 2021-02-24 20:51:33 +01:00
joshua
3f586b0b8e Fix wrong element type 2021-02-24 20:51:03 +01:00
Laurent Trinques
8ccfb93e89 DiagramView::mouseMoveEvent remove "DEV" in toolTip message mouse
postion
2021-02-24 19:44:12 +01:00
Lars Biskupek
54e2af4fb2 Tab-stop definitions inserted where appropriate
Tab stop definitions inserted in some dialogs, so that the order of tab-stops-moves corresponds as closely as possible to the fields' position in the window.
2021-02-24 19:43:37 +01:00
Lars Biskupek
3e95b51af6 Modifications to SaveFile-Dialog for "Save As PDF"
Fixed a typo that prevented existing PDF files from being displayed in SaveFileDialog for PDFs.

The way the file name for the PDF is generated has changed. If the project has already been saved, the PDF has the same file name (with .pdf of course); If not, the file name is generated from the project title (= same behavior as Save as - dialog for a .qet project file).
2021-02-24 19:43:04 +01:00
joshua
fb58ecacfc Fix typo
Fixed a typo that resulted in existing PDF files not being displayed in
the dialog box "Save As PDF".
Thanks Bisku
2021-02-24 19:42:26 +01:00
Pawel Śmiech
8053303ce5 Polish translation updated
Signed-off-by: Pawel Śmiech <pawel@localhost.localdomain>
2021-02-24 19:41:27 +01:00
43 changed files with 15697 additions and 15183 deletions

View File

@@ -1,12 +1,79 @@
Changelog Changelog
========= =========
If by accident I have forgotten to credit someone in the CHANGELOG, email me and I will fix it.
__3.2.0__
---------
* Added support for Qt 6 - _Jonas Kvinge_
* Fixed warning in `Qt 5.9` with `min`/`max` functions on Windows - _Nick Korotysh_
* Fix return value of connectToPrimary() when connect is successful - _Jonas Kvinge_
* Fix build issue with MinGW GCC pedantic mode - _Iakov Kirilenko_
* Fixed conversion from `int` to `quint32` and Clang Tidy warnings - _Hennadii Chernyshchyk_
__3.1.5__
---------
* Improved library stability in edge cases and very rapid process initialisation
* Fixed Bug where the shared memory block may have been modified without a lock
* Fixed Bug causing `instanceStarted()` to not get emitted when a second instance
has been started before the primary has initiated it's `QLocalServer`.
__3.1.4__
---------
* Officially supporting and build-testing against Qt 5.15
* Fixed an MSVC C4996 warning that suggests using `strncpy_s`.
_Hennadii Chernyshchyk_
__3.1.3.1__
---------
* CMake build system improvements
* Fixed Clang Tidy warnings
_Hennadii Chernyshchyk_
__3.1.3__
---------
* Improved `CMakeLists.txt`
_Hennadii Chernyshchyk_
__3.1.2__
---------
* Fix a crash when exiting an application on Android and iOS
_Emeric Grange_
__3.1.1a__
----------
* Added currentUser() method that returns the user the current instance is running as.
_Leander Schulten_
__3.1.0a__
----------
* Added primaryUser() method that returns the user the primary instance is running as.
__3.0.19__
----------
* Fixed code warning for depricated functions in Qt 5.10 related to `QTime` and `qrand()`.
_Hennadii Chernyshchyk_
_Anton Filimonov_
_Jonas Kvinge_
__3.0.18__ __3.0.18__
---------- ----------
* Fallback to standard QApplication class on iOS and Android systems where * Fallback to standard QApplication class on iOS and Android systems where
the library is not supported. the library is not supported.
* Added Build CI tests to verify the library builds successfully on Linux, Windows and MacOS across multiple Qt versions. * Added Build CI tests to verify the library builds successfully on Linux, Windows and MacOS across multiple Qt versions.
_Anton Filimonov_ _Anton Filimonov_

View File

@@ -1,43 +1,40 @@
cmake_minimum_required(VERSION 3.1.0) cmake_minimum_required(VERSION 3.7.0)
project(SingleApplication) project(SingleApplication LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC 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 add_library(${PROJECT_NAME} STATIC
singleapplication.cpp singleapplication.cpp
singleapplication_p.cpp singleapplication_p.cpp
) )
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
if(NOT QT_DEFAULT_MAJOR_VERSION)
set(QT_DEFAULT_MAJOR_VERSION 5 CACHE STRING "Qt version to use (5 or 6), defaults to 5")
endif()
# Find dependencies # Find dependencies
find_package(Qt5Network) set(QT_COMPONENTS Core Network)
if(QAPPLICATION_CLASS STREQUAL QApplication) set(QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Core Qt${QT_DEFAULT_MAJOR_VERSION}::Network)
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) if(QAPPLICATION_CLASS STREQUAL QApplication)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets) list(APPEND QT_COMPONENTS Widgets)
list(APPEND QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Widgets)
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication) elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Gui) list(APPEND QT_COMPONENTS Gui)
list(APPEND QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Gui)
else() else()
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core) set(QAPPLICATION_CLASS QCoreApplication)
endif() endif()
find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS ${QT_COMPONENTS} REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC ${QT_LIBRARIES})
if(WIN32) if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE advapi32) target_link_libraries(${PROJECT_NAME} PRIVATE advapi32)
endif() endif()
target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) Itay Grudev 2015 - 2016 Copyright (c) Itay Grudev 2015 - 2020
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,7 +1,8 @@
SingleApplication SingleApplication
================= =================
[![CI](https://github.com/itay-grudev/SingleApplication/workflows/CI:%20Build%20Test/badge.svg)](https://github.com/itay-grudev/SingleApplication/actions)
This is a replacement of the QtSingleApplication for `Qt5`. This is a replacement of the QtSingleApplication for `Qt5` and `Qt6`.
Keeps the Primary Instance of your Application and kills each subsequent Keeps the Primary Instance of your Application and kills each subsequent
instances. It can (if enabled) spawn secondary (non-related to the primary) instances. It can (if enabled) spawn secondary (non-related to the primary)
@@ -15,18 +16,6 @@ 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` default). Further usage is similar to the use of the `Q[Core|Gui]Application`
classes. 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 You can use the library as if you use any other `QCoreApplication` derived
class: class:
@@ -43,8 +32,7 @@ int main( int argc, char* argv[] )
``` ```
To include the library files I would recommend that you add it as a git 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 submodule to your project. Here is how:
how:
```bash ```bash
git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication
@@ -66,13 +54,27 @@ Then include the subdirectory in your `CMakeLists.txt` project file.
```cmake ```cmake
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
add_subdirectory(src/third-party/singleapplication) add_subdirectory(src/third-party/singleapplication)
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
``` ```
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.
Also don't forget to specify which `QCoreApplication` class your app is using if it Also don't forget to specify which `QCoreApplication` class your app is using if it
is not `QCoreApplication` as in examples above. is not `QCoreApplication` as in examples above.
The `Instance Started` signal The `Instance Started` signal
------------------------ -----------------------------
The SingleApplication class implements a `instanceStarted()` signal. You can The SingleApplication class implements a `instanceStarted()` signal. You can
bind to that signal to raise your application's window when a new instance had bind to that signal to raise your application's window when a new instance had
@@ -137,13 +139,22 @@ app.isSecondary();
*__Note:__ If your Primary Instance is terminated a newly launched instance *__Note:__ If your Primary Instance is terminated a newly launched instance
will replace the Primary one even if the Secondary flag has been set.* will replace the Primary one even if the Secondary flag has been set.*
Examples
--------
There are three examples provided in this repository:
* Basic example that prevents a secondary instance from starting [`examples/basic`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/basic)
* An example of a graphical application raising it's parent window [`examples/calculator`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/calculator)
* A console application sending the primary instance it's command line parameters [`examples/sending_arguments`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/sending_arguments)
API API
--- ---
### Members ### Members
```cpp ```cpp
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100 ) SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100, QString userData = QString() )
``` ```
Depending on whether `allowSecondary` is set, this constructor may terminate Depending on whether `allowSecondary` is set, this constructor may terminate
@@ -152,7 +163,7 @@ can be specified to set whether the SingleApplication block should work
user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be
used to notify the primary instance whenever a secondary instance had been used to notify the primary instance whenever a secondary instance had been
started (disabled by default). `timeout` specifies the maximum time in started (disabled by default). `timeout` specifies the maximum time in
milliseconds to wait for blocking operations. milliseconds to wait for blocking operations. Setting `userData` provides additional data that will isolate this instance from other instances that do not have the same (or any) user data set.
*__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it *__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it
recognizes.* recognizes.*
@@ -204,6 +215,22 @@ qint64 SingleApplication::primaryPid()
Returns the process ID (PID) of the primary instance. Returns the process ID (PID) of the primary instance.
---
```cpp
QString SingleApplication::primaryUser()
```
Returns the username the primary instance is running as.
---
```cpp
QString SingleApplication::currentUser()
```
Returns the username the current instance is running as.
### Signals ### Signals
```cpp ```cpp

View File

@@ -0,0 +1 @@
#include "singleapplication.h"

View File

@@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.7.0)
project(basic LANGUAGES CXX)
# SingleApplication base class
set(QAPPLICATION_CLASS QCoreApplication)
add_subdirectory(../.. SingleApplication)
add_executable(basic main.cpp)
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)

0
SingleApplication/examples/basic/basic.pro Normal file → Executable file
View File

2
SingleApplication/examples/basic/main.cpp Normal file → Executable file
View File

@@ -5,5 +5,7 @@ int main(int argc, char *argv[])
// Allow secondary instances // Allow secondary instances
SingleApplication app( argc, argv ); SingleApplication app( argc, argv );
qWarning() << "Started a new instance";
return app.exec(); return app.exec();
} }

View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.7.0)
project(calculator LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
# SingleApplication base class
set(QAPPLICATION_CLASS QApplication)
add_subdirectory(../.. SingleApplication)
find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Core REQUIRED)
add_executable(${PROJECT_NAME}
button.h
calculator.h
button.cpp
calculator.cpp
main.cpp
)
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)

View File

@@ -82,27 +82,27 @@ Calculator::Calculator(QWidget *parent)
digitButtons[i] = createButton(QString::number(i), SLOT(digitClicked())); digitButtons[i] = createButton(QString::number(i), SLOT(digitClicked()));
} }
Button *pointButton = createButton(tr("."), SLOT(pointClicked())); Button *pointButton = createButton(".", SLOT(pointClicked()));
Button *changeSignButton = createButton(tr("\302\261"), SLOT(changeSignClicked())); Button *changeSignButton = createButton("\302\261", SLOT(changeSignClicked()));
Button *backspaceButton = createButton(tr("Backspace"), SLOT(backspaceClicked())); Button *backspaceButton = createButton("Backspace", SLOT(backspaceClicked()));
Button *clearButton = createButton(tr("Clear"), SLOT(clear())); Button *clearButton = createButton("Clear", SLOT(clear()));
Button *clearAllButton = createButton(tr("Clear All"), SLOT(clearAll())); Button *clearAllButton = createButton("Clear All", SLOT(clearAll()));
Button *clearMemoryButton = createButton(tr("MC"), SLOT(clearMemory())); Button *clearMemoryButton = createButton("MC", SLOT(clearMemory()));
Button *readMemoryButton = createButton(tr("MR"), SLOT(readMemory())); Button *readMemoryButton = createButton("MR", SLOT(readMemory()));
Button *setMemoryButton = createButton(tr("MS"), SLOT(setMemory())); Button *setMemoryButton = createButton("MS", SLOT(setMemory()));
Button *addToMemoryButton = createButton(tr("M+"), SLOT(addToMemory())); Button *addToMemoryButton = createButton("M+", SLOT(addToMemory()));
Button *divisionButton = createButton(tr("\303\267"), SLOT(multiplicativeOperatorClicked())); Button *divisionButton = createButton("\303\267", SLOT(multiplicativeOperatorClicked()));
Button *timesButton = createButton(tr("\303\227"), SLOT(multiplicativeOperatorClicked())); Button *timesButton = createButton("\303\227", SLOT(multiplicativeOperatorClicked()));
Button *minusButton = createButton(tr("-"), SLOT(additiveOperatorClicked())); Button *minusButton = createButton("-", SLOT(additiveOperatorClicked()));
Button *plusButton = createButton(tr("+"), SLOT(additiveOperatorClicked())); Button *plusButton = createButton("+", SLOT(additiveOperatorClicked()));
Button *squareRootButton = createButton(tr("Sqrt"), SLOT(unaryOperatorClicked())); Button *squareRootButton = createButton("Sqrt", SLOT(unaryOperatorClicked()));
Button *powerButton = createButton(tr("x\302\262"), SLOT(unaryOperatorClicked())); Button *powerButton = createButton("x\302\262", SLOT(unaryOperatorClicked()));
Button *reciprocalButton = createButton(tr("1/x"), SLOT(unaryOperatorClicked())); Button *reciprocalButton = createButton("1/x", SLOT(unaryOperatorClicked()));
Button *equalButton = createButton(tr("="), SLOT(equalClicked())); Button *equalButton = createButton("=", SLOT(equalClicked()));
//! [4] //! [4]
//! [5] //! [5]
@@ -140,7 +140,7 @@ Calculator::Calculator(QWidget *parent)
mainLayout->addWidget(equalButton, 5, 5); mainLayout->addWidget(equalButton, 5, 5);
setLayout(mainLayout); setLayout(mainLayout);
setWindowTitle(tr("Calculator")); setWindowTitle("Calculator");
} }
//! [6] //! [6]
@@ -169,15 +169,15 @@ void Calculator::unaryOperatorClicked()
double operand = display->text().toDouble(); double operand = display->text().toDouble();
double result = 0.0; double result = 0.0;
if (clickedOperator == tr("Sqrt")) { if (clickedOperator == "Sqrt") {
if (operand < 0.0) { if (operand < 0.0) {
abortOperation(); abortOperation();
return; return;
} }
result = std::sqrt(operand); result = std::sqrt(operand);
} else if (clickedOperator == tr("x\302\262")) { } else if (clickedOperator == "x\302\262") {
result = std::pow(operand, 2.0); result = std::pow(operand, 2.0);
} else if (clickedOperator == tr("1/x")) { } else if (clickedOperator == "1/x") {
if (operand == 0.0) { if (operand == 0.0) {
abortOperation(); abortOperation();
return; return;
@@ -287,7 +287,7 @@ void Calculator::pointClicked()
if (waitingForOperand) if (waitingForOperand)
display->setText("0"); display->setText("0");
if (!display->text().contains('.')) if (!display->text().contains('.'))
display->setText(display->text() + tr(".")); display->setText(display->text() + ".");
waitingForOperand = false; waitingForOperand = false;
} }
//! [22] //! [22]
@@ -299,7 +299,7 @@ void Calculator::changeSignClicked()
double value = text.toDouble(); double value = text.toDouble();
if (value > 0.0) { if (value > 0.0) {
text.prepend(tr("-")); text.prepend("-");
} else if (value < 0.0) { } else if (value < 0.0) {
text.remove(0, 1); text.remove(0, 1);
} }
@@ -383,20 +383,20 @@ Button *Calculator::createButton(const QString &text, const char *member)
void Calculator::abortOperation() void Calculator::abortOperation()
{ {
clearAll(); clearAll();
display->setText(tr("####")); display->setText("####");
} }
//! [36] //! [36]
//! [38] //! [38]
bool Calculator::calculate(double rightOperand, const QString &pendingOperator) bool Calculator::calculate(double rightOperand, const QString &pendingOperator)
{ {
if (pendingOperator == tr("+")) { if (pendingOperator == "+") {
sumSoFar += rightOperand; sumSoFar += rightOperand;
} else if (pendingOperator == tr("-")) { } else if (pendingOperator == "-") {
sumSoFar -= rightOperand; sumSoFar -= rightOperand;
} else if (pendingOperator == tr("\303\227")) { } else if (pendingOperator == "\303\227") {
factorSoFar *= rightOperand; factorSoFar *= rightOperand;
} else if (pendingOperator == tr("\303\267")) { } else if (pendingOperator == "\303\267") {
if (rightOperand == 0.0) if (rightOperand == 0.0)
return false; return false;
factorSoFar /= rightOperand; factorSoFar /= rightOperand;

View File

@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.7.0)
project(sending_arguments LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
# SingleApplication base class
set(QAPPLICATION_CLASS QCoreApplication)
add_subdirectory(../.. SingleApplication)
find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Core REQUIRED)
add_executable(${PROJECT_NAME}
main.cpp
messagereceiver.cpp
messagereceiver.h
main.cpp
)
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)

3
SingleApplication/examples/sending_arguments/main.cpp Normal file → Executable file
View File

@@ -11,6 +11,9 @@ int main(int argc, char *argv[])
// If this is a secondary instance // If this is a secondary instance
if( app.isSecondary() ) { if( app.isSecondary() ) {
app.sendMessage( app.arguments().join(' ').toUtf8() ); app.sendMessage( app.arguments().join(' ').toUtf8() );
qDebug() << "App already running.";
qDebug() << "Primary instance PID: " << app.primaryPid();
qDebug() << "Primary instance user: " << app.primaryUser();
return 0; return 0;
} else { } else {
QObject::connect( QObject::connect(

View File

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -20,176 +20,255 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE. // THE SOFTWARE.
#include <QtCore/QTime> #include <QtCore/QElapsedTimer>
#include <QtCore/QThread>
#include <QtCore/QDateTime>
#include <QtCore/QByteArray> #include <QtCore/QByteArray>
#include <QtCore/QSharedMemory> #include <QtCore/QSharedMemory>
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) // ### Qt 6: remove
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 5.10 or later")
#endif
#include <QRandomGenerator>
#endif
#include "singleapplication.h" #include "singleapplication.h"
#include "singleapplication_p.h" #include "singleapplication_p.h"
/** /**
@brief Constructor. Checks and fires up LocalServer or closes the program * @brief Constructor. Checks and fires up LocalServer or closes the program
if another instance already exists * if another instance already exists
@param argc * @param argc
@param argv * @param argv
@param {bool} allowSecondaryInstances * @param allowSecondary Whether to enable secondary instance support
*/ * @param options Optional flags to toggle specific behaviour
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout ) * @param timeout Maximum time blocking functions are allowed during app load
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) ) */
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData )
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
{ {
Q_D(SingleApplication); Q_D( SingleApplication );
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
// On Android and iOS since the library is not supported fallback to // On Android and iOS since the library is not supported fallback to
// standard QApplication behaviour by simply returning at this point. // standard QApplication behaviour by simply returning at this point.
qWarning() << "SingleApplication is not supported on Android and iOS systems."; qWarning() << "SingleApplication is not supported on Android and iOS systems.";
return; return;
#endif #endif
// Store the current mode of the program // Store the current mode of the program
d->options = options; d->options = options;
// Generating an application ID used for identifying the shared memory // Add any unique user data
// block and QLocalServer if ( ! userData.isEmpty() )
d->genBlockServerName(); d->addAppData( userData );
// Generating an application ID used for identifying the shared memory
// block and QLocalServer
d->genBlockServerName();
// To mitigate QSharedMemory issues with large amount of processes
// attempting to attach at the same time
SingleApplicationPrivate::randomSleep();
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
// By explicitly attaching it and then deleting it we make sure that the // By explicitly attaching it and then deleting it we make sure that the
// memory is deleted even after the process has crashed on Unix. // memory is deleted even after the process has crashed on Unix.
d->memory = new QSharedMemory( d->blockServerName ); d->memory = new QSharedMemory( d->blockServerName );
d->memory->attach(); d->memory->attach();
delete d->memory; delete d->memory;
#endif #endif
// Guarantee thread safe behaviour with a shared memory block. // Guarantee thread safe behaviour with a shared memory block.
d->memory = new QSharedMemory( d->blockServerName ); d->memory = new QSharedMemory( d->blockServerName );
// Create a shared memory block // Create a shared memory block
if( d->memory->create( sizeof( InstancesInfo ) ) ) { if( d->memory->create( sizeof( InstancesInfo ) )){
// Initialize the shared memory block // Initialize the shared memory block
d->memory->lock(); if( ! d->memory->lock() ){
d->initializeMemoryBlock(); qCritical() << "SingleApplication: Unable to lock memory block after create.";
d->memory->unlock(); abortSafely();
} else { }
// Attempt to attach to the memory segment d->initializeMemoryBlock();
if( ! d->memory->attach() ) { } else {
qCritical() << "SingleApplication: Unable to attach to shared memory block."; if( d->memory->error() == QSharedMemory::AlreadyExists ){
qCritical() << d->memory->errorString(); // Attempt to attach to the memory segment
delete d; if( ! d->memory->attach() ){
::exit( EXIT_FAILURE ); qCritical() << "SingleApplication: Unable to attach to shared memory block.";
} abortSafely();
} }
if( ! d->memory->lock() ){
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
abortSafely();
}
} else {
qCritical() << "SingleApplication: Unable to create block.";
abortSafely();
}
}
InstancesInfo* inst = static_cast<InstancesInfo*>( d->memory->data() ); auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
QElapsedTimer time; QElapsedTimer time;
time.start(); time.start();
// Make sure the shared memory block is initialised and in consistent state // Make sure the shared memory block is initialised and in consistent state
while( true ) { while( true ){
d->memory->lock(); // If the shared memory block's checksum is valid continue
if( d->blockChecksum() == inst->checksum ) break;
if( d->blockChecksum() == inst->checksum ) break; // If more than 5s have elapsed, assume the primary instance crashed and
// assume it's position
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();
}
if( time.elapsed() > 5000 ) { // Otherwise wait for a random period and try again. The random sleep here
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure."; // limits the probability of a collision between two racing apps and
d->initializeMemoryBlock(); // allows the app to initialise faster
} if( ! d->memory->unlock() ){
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
qDebug() << d->memory->errorString();
}
SingleApplicationPrivate::randomSleep();
if( ! d->memory->lock() ){
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
abortSafely();
}
}
d->memory->unlock(); if( inst->primary == false ){
d->startPrimary();
if( ! d->memory->unlock() ){
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
qDebug() << d->memory->errorString();
}
return;
}
// Random sleep here limits the probability of a collision between two racing apps // Check if another instance can be started
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) // ### Qt 6: remove if( allowSecondary ){
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() ); d->startSecondary();
QThread::sleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ) ); if( d->options & Mode::SecondaryNotification ){
#else d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
#if TODO_LIST }
#pragma message("@TODO remove code for QT 5.10 or later") if( ! d->memory->unlock() ){
#endif qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
quint32 value = QRandomGenerator::global()->generate(); qDebug() << d->memory->errorString();
QThread::sleep(8 + static_cast<unsigned long>(value / RAND_MAX * 10)); }
#endif return;
} }
if( inst->primary == false) { if( ! d->memory->unlock() ){
d->startPrimary(); qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
d->memory->unlock(); qDebug() << d->memory->errorString();
return; }
}
// Check if another instance can be started d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
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(); delete d;
d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance ); ::exit( EXIT_SUCCESS );
}
delete d; SingleApplication::~SingleApplication()
{
::exit( EXIT_SUCCESS ); Q_D( SingleApplication );
delete d;
} }
/** /**
@brief Destructor * Checks if the current application instance is primary.
*/ * @return Returns true if the instance is primary, false otherwise.
SingleApplication::~SingleApplication() */
bool SingleApplication::isPrimary() const
{ {
Q_D(SingleApplication); Q_D( const SingleApplication );
delete d; return d->server != nullptr;
} }
bool SingleApplication::isPrimary() /**
* Checks if the current application instance is secondary.
* @return Returns true if the instance is secondary, false otherwise.
*/
bool SingleApplication::isSecondary() const
{ {
Q_D(SingleApplication); Q_D( const SingleApplication );
return d->server != nullptr; return d->server == nullptr;
} }
bool SingleApplication::isSecondary() /**
* Allows you to identify an instance by returning unique consecutive instance
* ids. It is reset when the first (primary) instance of your app starts and
* only incremented afterwards.
* @return Returns a unique instance id.
*/
quint32 SingleApplication::instanceId() const
{ {
Q_D(SingleApplication); Q_D( const SingleApplication );
return d->server == nullptr; return d->instanceNumber;
} }
quint32 SingleApplication::instanceId() /**
* Returns the OS PID (Process Identifier) of the process running the primary
* instance. Especially useful when SingleApplication is coupled with OS.
* specific APIs.
* @return Returns the primary instance PID.
*/
qint64 SingleApplication::primaryPid() const
{ {
Q_D(SingleApplication); Q_D( const SingleApplication );
return d->instanceNumber; return d->primaryPid();
} }
qint64 SingleApplication::primaryPid() /**
* Returns the username the primary instance is running as.
* @return Returns the username the primary instance is running as.
*/
QString SingleApplication::primaryUser() const
{ {
Q_D(SingleApplication); Q_D( const SingleApplication );
return d->primaryPid(); return d->primaryUser();
} }
bool SingleApplication::sendMessage( QByteArray message, int timeout ) /**
* Returns the username the current instance is running as.
* @return Returns the username the current instance is running as.
*/
QString SingleApplication::currentUser() const
{ {
Q_D(SingleApplication); return SingleApplicationPrivate::getUsername();
}
// Nobody to connect to
if( isPrimary() ) return false; /**
* Sends message to the Primary Instance.
// Make sure the socket is connected * @param message The message to send.
d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ); * @param timeout the maximum timeout in milliseconds for blocking functions.
* @return true if the message was sent successfuly, false otherwise.
d->socket->write( message ); */
bool dataWritten = d->socket->waitForBytesWritten( timeout ); bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
d->socket->flush(); {
return dataWritten; Q_D( SingleApplication );
// Nobody to connect to
if( isPrimary() ) return false;
// Make sure the socket is connected
if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
return false;
d->socket->write( message );
bool dataWritten = d->socket->waitForBytesWritten( timeout );
d->socket->flush();
return dataWritten;
}
/**
* Cleans up the shared memory block and exits with a failure.
* This function halts program execution.
*/
void SingleApplication::abortSafely()
{
Q_D( SingleApplication );
qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString();
delete d;
::exit( EXIT_FAILURE );
}
QStringList SingleApplication::userData() const
{
Q_D( const SingleApplication );
return d->appData();
} }

View File

@@ -25,7 +25,6 @@
#include <QtCore/QtGlobal> #include <QtCore/QtGlobal>
#include <QtNetwork/QLocalSocket> #include <QtNetwork/QLocalSocket>
#include <QElapsedTimer>
#ifndef QAPPLICATION_CLASS #ifndef QAPPLICATION_CLASS
#define QAPPLICATION_CLASS QCoreApplication #define QAPPLICATION_CLASS QCoreApplication
@@ -36,99 +35,118 @@
class SingleApplicationPrivate; class SingleApplicationPrivate;
/** /**
@brief The SingleApplication class handles multiple instances of the same * @brief The SingleApplication class handles multiple instances of the same
Application * Application
@see QCoreApplication * @see QCoreApplication
*/ */
class SingleApplication : public QAPPLICATION_CLASS class SingleApplication : public QAPPLICATION_CLASS
{ {
Q_OBJECT Q_OBJECT
typedef QAPPLICATION_CLASS app_t; using app_t = QAPPLICATION_CLASS;
public: public:
/** /**
@brief Mode of operation of SingleApplication. * @brief Mode of operation of SingleApplication.
Whether the block should be user-wide or system-wide and whether the * Whether the block should be user-wide or system-wide and whether the
primary instance should be notified when a secondary instance had been * primary instance should be notified when a secondary instance had been
started. * started.
@note Operating system can restrict the shared memory blocks to the same * @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 * user, in which case the User/System modes will have no effect and the
block will be user wide. * block will be user wide.
@enum * @enum
*/ */
enum Mode { enum Mode {
User = 1 << 0, User = 1 << 0,
System = 1 << 1, System = 1 << 1,
SecondaryNotification = 1 << 2, SecondaryNotification = 1 << 2,
ExcludeAppVersion = 1 << 3, ExcludeAppVersion = 1 << 3,
ExcludeAppPath = 1 << 4 ExcludeAppPath = 1 << 4
}; };
Q_DECLARE_FLAGS(Options, Mode) Q_DECLARE_FLAGS(Options, Mode)
/** /**
@brief Intitializes a SingleApplication instance with argc command line * @brief Intitializes a SingleApplication instance with argc command line
arguments in argv * arguments in argv
@arg {int &} argc - Number of arguments in argv * @arg {int &} argc - Number of arguments in argv
@arg {const char *[]} argv - Supplied command line arguments * @arg {const char *[]} argv - Supplied command line arguments
@arg {bool} allowSecondary - Whether to start the instance as secondary * @arg {bool} allowSecondary - Whether to start the instance as secondary
if there is already a primary instance. * if there is already a primary instance.
@arg {Mode} mode - Whether for the SingleApplication block to be applied * @arg {Mode} mode - Whether for the SingleApplication block to be applied
User wide or System wide. * User wide or System wide.
@arg {int} timeout - Timeout to wait in milliseconds. * @arg {int} timeout - Timeout to wait in milliseconds.
@note argc and argv may be changed as Qt removes arguments that it * @note argc and argv may be changed as Qt removes arguments that it
recognizes * recognizes
@note Mode::SecondaryNotification only works if set on both the primary * @note Mode::SecondaryNotification only works if set on both the primary
instance and the secondary instance. * instance and the secondary instance.
@note The timeout is just a hint for the maximum time of blocking * @note The timeout is just a hint for the maximum time of blocking
operations. It does not guarantee that the SingleApplication * operations. It does not guarantee that the SingleApplication
initialisation will be completed in given time, though is a good hint. * initialisation will be completed in given time, though is a good hint.
Usually 4*timeout would be the worst case (fail) scenario. * Usually 4*timeout would be the worst case (fail) scenario.
@see See the corresponding QAPPLICATION_CLASS constructor for reference * @see See the corresponding QAPPLICATION_CLASS constructor for reference
*/ */
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 ); explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} );
~SingleApplication(); ~SingleApplication() override;
/** /**
@brief Returns if the instance is the primary instance * @brief Returns if the instance is the primary instance
@returns {bool} * @returns {bool}
*/ */
bool isPrimary(); bool isPrimary() const;
/** /**
@brief Returns if the instance is a secondary instance * @brief Returns if the instance is a secondary instance
@returns {bool} * @returns {bool}
*/ */
bool isSecondary(); bool isSecondary() const;
/** /**
@brief Returns a unique identifier for the current instance * @brief Returns a unique identifier for the current instance
@returns {qint32} * @returns {qint32}
*/ */
quint32 instanceId(); quint32 instanceId() const;
/** /**
@brief Returns the process ID (PID) of the primary instance * @brief Returns the process ID (PID) of the primary instance
@returns {qint64} * @returns {qint64}
*/ */
qint64 primaryPid(); qint64 primaryPid() const;
/** /**
@brief Sends a message to the primary instance. Returns true on success. * @brief Returns the username of the user running the primary instance
@param {int} timeout - Timeout for connecting * @returns {QString}
@returns {bool} */
@note sendMessage() will return false if invoked from the primary QString primaryUser() const;
instance.
*/ /**
bool sendMessage( QByteArray message, int timeout = 100 ); * @brief Returns the username of the current user
* @returns {QString}
Q_SIGNALS: */
void instanceStarted(); QString currentUser() const;
void receivedMessage( quint32 instanceId, QByteArray message );
/**
private: * @brief Sends a message to the primary instance. Returns true on success.
SingleApplicationPrivate *d_ptr; * @param {int} timeout - Timeout for connecting
Q_DECLARE_PRIVATE(SingleApplication) * @returns {bool}
* @note sendMessage() will return false if invoked from the primary
* instance.
*/
bool sendMessage( const QByteArray &message, int timeout = 100 );
/**
* @brief Get the set user data.
* @returns {QStringList}
*/
QStringList userData() const;
Q_SIGNALS:
void instanceStarted();
void receivedMessage( quint32 instanceId, QByteArray message );
private:
SingleApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleApplication)
void abortSafely();
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options) Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)

View File

@@ -1,7 +1,8 @@
QT += core network QT += core network
CONFIG += c++17 CONFIG += c++11
HEADERS += $$PWD/singleapplication.h \ HEADERS += $$PWD/SingleApplication \
$$PWD/singleapplication.h \
$$PWD/singleapplication_p.h $$PWD/singleapplication_p.h
SOURCES += $$PWD/singleapplication.cpp \ SOURCES += $$PWD/singleapplication.cpp \
$$PWD/singleapplication_p.cpp $$PWD/singleapplication_p.cpp

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -33,412 +33,454 @@
#include <cstddef> #include <cstddef>
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QtCore/QThread>
#include <QtCore/QByteArray> #include <QtCore/QByteArray>
#include <QtCore/QDataStream> #include <QtCore/QDataStream>
#include <QtCore/QElapsedTimer>
#include <QtCore/QCryptographicHash> #include <QtCore/QCryptographicHash>
#include <QtNetwork/QLocalServer> #include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket> #include <QtNetwork/QLocalSocket>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
#include <QtCore/QRandomGenerator>
#else
#include <QtCore/QDateTime>
#endif
#include "singleapplication.h" #include "singleapplication.h"
#include "singleapplication_p.h" #include "singleapplication_p.h"
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
#include <pwd.h> #include <pwd.h>
#endif #endif
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <windows.h> #ifndef NOMINMAX
#include <lmcons.h> #define NOMINMAX 1
#endif
#include <windows.h>
#include <lmcons.h>
#endif #endif
SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr ) SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
: q_ptr( q_ptr ) : q_ptr( q_ptr )
{ {
server = nullptr; server = nullptr;
socket = nullptr; socket = nullptr;
memory = nullptr; memory = nullptr;
instanceNumber = -1; instanceNumber = 0;
} }
SingleApplicationPrivate::~SingleApplicationPrivate() SingleApplicationPrivate::~SingleApplicationPrivate()
{ {
if( socket != nullptr ) { if( socket != nullptr ){
socket->close(); socket->close();
delete socket; delete socket;
} }
memory->lock(); if( memory != nullptr ){
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data()); memory->lock();
if( server != nullptr ) { auto *inst = static_cast<InstancesInfo*>(memory->data());
server->close(); if( server != nullptr ){
delete server; server->close();
inst->primary = false; delete server;
inst->primaryPid = -1; inst->primary = false;
inst->checksum = blockChecksum(); inst->primaryPid = -1;
} inst->primaryUser[0] = '\0';
memory->unlock(); inst->checksum = blockChecksum();
}
memory->unlock();
delete memory; delete memory;
}
}
QString SingleApplicationPrivate::getUsername()
{
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if( GetUserNameW( username, &usernameLength ) )
return QString::fromWCharArray( username );
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
#else
return qEnvironmentVariable( "USERNAME" );
#endif
#endif
#ifdef Q_OS_UNIX
QString username;
uid_t uid = geteuid();
struct passwd *pw = getpwuid( uid );
if( pw )
username = QString::fromLocal8Bit( pw->pw_name );
if ( username.isEmpty() ){
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
username = QString::fromLocal8Bit( qgetenv( "USER" ) );
#else
username = qEnvironmentVariable( "USER" );
#endif
}
return username;
#endif
} }
void SingleApplicationPrivate::genBlockServerName() void SingleApplicationPrivate::genBlockServerName()
{ {
QCryptographicHash appData( QCryptographicHash::Sha256 ); QCryptographicHash appData( QCryptographicHash::Sha256 );
appData.addData( "SingleApplication", 17 ); appData.addData( "SingleApplication", 17 );
appData.addData( SingleApplication::app_t::applicationName().toUtf8() ); appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
appData.addData( SingleApplication::app_t::organizationName().toUtf8() ); appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() ); appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ) { if ( ! appDataList.isEmpty() )
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() ); appData.addData( appDataList.join( "" ).toUtf8() );
}
if( ! (options & SingleApplication::Mode::ExcludeAppPath) ) { if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
}
if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() ); appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
#else #else
appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() ); appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
#endif #endif
} }
// User level block requires a user specific data in the hash // User level block requires a user specific data in the hash
if( options & SingleApplication::Mode::User ) { if( options & SingleApplication::Mode::User ){
#ifdef Q_OS_WIN appData.addData( getUsername().toUtf8() );
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
}
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
// server naming requirements. // server naming requirements.
blockServerName = appData.result().toBase64().replace("/", "_"); blockServerName = appData.result().toBase64().replace("/", "_");
} }
void SingleApplicationPrivate::initializeMemoryBlock() void SingleApplicationPrivate::initializeMemoryBlock() const
{ {
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() ); auto *inst = static_cast<InstancesInfo*>( memory->data() );
inst->primary = false; inst->primary = false;
inst->secondary = 0; inst->secondary = 0;
inst->primaryPid = -1; inst->primaryPid = -1;
inst->checksum = blockChecksum(); inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
} }
void SingleApplicationPrivate::startPrimary() void SingleApplicationPrivate::startPrimary()
{ {
Q_Q(SingleApplication); // Reset the number of connections
auto *inst = static_cast <InstancesInfo*>( memory->data() );
// Successful creation means that no main process exists inst->primary = true;
// So we start a QLocalServer to listen for connections inst->primaryPid = QCoreApplication::applicationPid();
QLocalServer::removeServer( blockServerName ); qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
server = new QLocalServer(); inst->checksum = blockChecksum();
instanceNumber = 0;
// 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 // Restrict access to the socket according to the
// SingleApplication::Mode::User flag on User level or no restrictions // SingleApplication::Mode::User flag on User level or no restrictions
if( options & SingleApplication::Mode::User ) { if( options & SingleApplication::Mode::User ){
server->setSocketOptions( QLocalServer::UserAccessOption ); server->setSocketOptions( QLocalServer::UserAccessOption );
} else { } else {
server->setSocketOptions( QLocalServer::WorldAccessOption ); server->setSocketOptions( QLocalServer::WorldAccessOption );
} }
server->listen( blockServerName ); server->listen( blockServerName );
QObject::connect( QObject::connect(
server, server,
&QLocalServer::newConnection, &QLocalServer::newConnection,
this, this,
&SingleApplicationPrivate::slotConnectionEstablished &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::startSecondary()
{ {
auto *inst = static_cast <InstancesInfo*>( memory->data() );
inst->secondary += 1;
inst->checksum = blockChecksum();
instanceNumber = inst->secondary;
} }
void SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType ) bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
{ {
// Connect to the Local Server of the Primary Instance if not already QElapsedTimer time;
// connected. time.start();
if( socket == nullptr ) {
socket = new QLocalSocket();
}
// If already connected - we are done; // Connect to the Local Server of the Primary Instance if not already
if( socket->state() == QLocalSocket::ConnectedState ) // connected.
return; if( socket == nullptr ){
socket = new QLocalSocket();
}
// If not connect if( socket->state() == QLocalSocket::ConnectedState ) return true;
if( socket->state() == QLocalSocket::UnconnectedState ||
socket->state() == QLocalSocket::ClosingState ) {
socket->connectToServer( blockServerName );
}
// Wait for being connected if( socket->state() != QLocalSocket::ConnectedState ){
if( socket->state() == QLocalSocket::ConnectingState ) {
socket->waitForConnected( msecs );
}
// Initialisation message according to the SingleApplication protocol while( true ){
if( socket->state() == QLocalSocket::ConnectedState ) { randomSleep();
// Notify the parent that a new instance had been started;
QByteArray initMsg; if( socket->state() != QLocalSocket::ConnectingState )
QDataStream writeStream(&initMsg, QIODevice::WriteOnly); socket->connectToServer( blockServerName );
if( socket->state() == QLocalSocket::ConnectingState ){
socket->waitForConnected( static_cast<int>(msecs - time.elapsed()) );
}
// If connected break out of the loop
if( socket->state() == QLocalSocket::ConnectedState ) break;
// If elapsed time since start is longer than the method timeout return
if( time.elapsed() >= msecs ) return false;
}
}
// Initialisation message according to the SingleApplication protocol
QByteArray initMsg;
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
writeStream.setVersion(QDataStream::Qt_5_6); writeStream.setVersion(QDataStream::Qt_5_6);
#endif #endif
writeStream << blockServerName.toLatin1(); writeStream << blockServerName.toLatin1();
writeStream << static_cast<quint8>(connectionType); writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber; writeStream << instanceNumber;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
quint16 checksum =
qChecksum(
initMsg.constData(),
static_cast<quint32>(initMsg.length()));
#else #else
#if TODO_LIST quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
#pragma message("@TODO remove code for QT 6 or later")
#endif #endif
quint16 checksum = writeStream << checksum;
qChecksum(
QByteArrayView(
initMsg.constData(),
static_cast<quint32>(initMsg.length())));
#endif
writeStream << checksum;
// The header indicates the message length that follows // The header indicates the message length that follows
QByteArray header; QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly); QDataStream headerStream(&header, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
headerStream.setVersion(QDataStream::Qt_5_6); headerStream.setVersion(QDataStream::Qt_5_6);
#endif #endif
headerStream << static_cast <quint64>( initMsg.length() ); headerStream << static_cast <quint64>( initMsg.length() );
socket->write( header ); socket->write( header );
socket->write( initMsg ); socket->write( initMsg );
socket->flush(); bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) );
socket->waitForBytesWritten( msecs ); socket->flush();
} return result;
} }
quint16 SingleApplicationPrivate::blockChecksum() quint16 SingleApplicationPrivate::blockChecksum() const
{ {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return qChecksum( quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)));
static_cast <const char *>( memory->data() ),
offsetof( InstancesInfo, checksum )
);
#else #else
#if TODO_LIST quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum));
#pragma message("@TODO remove code for QT 6 or later")
#endif
return qChecksum(
QByteArrayView(
static_cast <const char *>( memory->data() ),
offsetof( InstancesInfo, checksum )));
#endif #endif
return checksum;
} }
qint64 SingleApplicationPrivate::primaryPid() qint64 SingleApplicationPrivate::primaryPid() const
{ {
qint64 pid; qint64 pid;
memory->lock(); memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() ); auto *inst = static_cast<InstancesInfo*>( memory->data() );
pid = inst->primaryPid; pid = inst->primaryPid;
memory->unlock(); memory->unlock();
return pid; return pid;
}
QString SingleApplicationPrivate::primaryUser() const
{
QByteArray username;
memory->lock();
auto *inst = static_cast<InstancesInfo*>( memory->data() );
username = inst->primaryUser;
memory->unlock();
return QString::fromUtf8( username );
} }
/** /**
@brief Executed when a connection has been made to the LocalServer * @brief Executed when a connection has been made to the LocalServer
*/ */
void SingleApplicationPrivate::slotConnectionEstablished() void SingleApplicationPrivate::slotConnectionEstablished()
{ {
QLocalSocket *nextConnSocket = server->nextPendingConnection(); QLocalSocket *nextConnSocket = server->nextPendingConnection();
connectionMap.insert(nextConnSocket, ConnectionInfo()); connectionMap.insert(nextConnSocket, ConnectionInfo());
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
[nextConnSocket, this]() { [nextConnSocket, this](){
auto &info = connectionMap[nextConnSocket]; auto &info = connectionMap[nextConnSocket];
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId ); Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
} }
); );
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
[nextConnSocket, this](){
connectionMap.remove(nextConnSocket);
nextConnSocket->deleteLater();
}
);
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, QObject::connect(nextConnSocket, &QLocalSocket::destroyed,
[nextConnSocket, this]() { [nextConnSocket, this](){
auto &info = connectionMap[nextConnSocket]; connectionMap.remove(nextConnSocket);
switch(info.stage) { }
case StageHeader: );
readInitMessageHeader(nextConnSocket);
break; QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
case StageBody: [nextConnSocket, this](){
readInitMessageBody(nextConnSocket); auto &info = connectionMap[nextConnSocket];
break; switch(info.stage){
case StageConnected: case StageHeader:
Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId ); readInitMessageHeader(nextConnSocket);
break; break;
default: case StageBody:
break; readInitMessageBody(nextConnSocket);
}; break;
} case StageConnected:
); Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
break;
default:
break;
};
}
);
} }
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock ) void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
{ {
if (!connectionMap.contains( sock )) { if (!connectionMap.contains( sock )){
return; return;
} }
if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ) { if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){
return; return;
} }
QDataStream headerStream( sock ); QDataStream headerStream( sock );
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
headerStream.setVersion( QDataStream::Qt_5_6 ); headerStream.setVersion( QDataStream::Qt_5_6 );
#endif #endif
// Read the header to know the message length // Read the header to know the message length
quint64 msgLen = 0; quint64 msgLen = 0;
headerStream >> msgLen; headerStream >> msgLen;
ConnectionInfo &info = connectionMap[sock]; ConnectionInfo &info = connectionMap[sock];
info.stage = StageBody; info.stage = StageBody;
info.msgLen = msgLen; info.msgLen = msgLen;
if ( sock->bytesAvailable() >= (qint64) msgLen ) { if ( sock->bytesAvailable() >= (qint64) msgLen ){
readInitMessageBody( sock ); readInitMessageBody( sock );
} }
} }
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock ) void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
{ {
Q_Q(SingleApplication); Q_Q(SingleApplication);
if (!connectionMap.contains( sock )) { if (!connectionMap.contains( sock )){
return; return;
} }
ConnectionInfo &info = connectionMap[sock]; ConnectionInfo &info = connectionMap[sock];
if( sock->bytesAvailable() < ( qint64 )info.msgLen ) { if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
return; return;
} }
// Read the message body // Read the message body
QByteArray msgBytes = sock->read(info.msgLen); QByteArray msgBytes = sock->read(info.msgLen);
QDataStream readStream(msgBytes); QDataStream readStream(msgBytes);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
readStream.setVersion( QDataStream::Qt_5_6 ); readStream.setVersion( QDataStream::Qt_5_6 );
#endif #endif
// server name // server name
QByteArray latin1Name; QByteArray latin1Name;
readStream >> latin1Name; readStream >> latin1Name;
// connection type // connection type
ConnectionType connectionType = InvalidConnection; ConnectionType connectionType = InvalidConnection;
quint8 connTypeVal = InvalidConnection; quint8 connTypeVal = InvalidConnection;
readStream >> connTypeVal; readStream >> connTypeVal;
connectionType = static_cast <ConnectionType>( connTypeVal ); connectionType = static_cast <ConnectionType>( connTypeVal );
// instance id // instance id
quint32 instanceId = 0; quint32 instanceId = 0;
readStream >> instanceId; readStream >> instanceId;
// checksum // checksum
quint16 msgChecksum = 0; quint16 msgChecksum = 0;
readStream >> msgChecksum; readStream >> msgChecksum;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const quint16 actualChecksum = const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
qChecksum(
msgBytes.constData(),
static_cast<quint32>( msgBytes.length() - sizeof( quint16 ) ) );
#else #else
#if TODO_LIST const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
#pragma message("@TODO remove code for QT 6 or later")
#endif #endif
const quint16 actualChecksum =
qChecksum(
QByteArrayView(
msgBytes.constData(),
static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
#endif
bool isValid = readStream.status() == QDataStream::Ok &&
QLatin1String(latin1Name) == blockServerName &&
msgChecksum == actualChecksum;
if( !isValid ) { bool isValid = readStream.status() == QDataStream::Ok &&
sock->close(); QLatin1String(latin1Name) == blockServerName &&
return; msgChecksum == actualChecksum;
}
info.instanceId = instanceId; if( !isValid ){
info.stage = StageConnected; sock->close();
return;
}
if( connectionType == NewInstance || info.instanceId = instanceId;
( connectionType == SecondaryInstance && info.stage = StageConnected;
options & SingleApplication::Mode::SecondaryNotification ) )
{
Q_EMIT q->instanceStarted();
}
if (sock->bytesAvailable() > 0) { if( connectionType == NewInstance ||
Q_EMIT this->slotDataAvailable( sock, instanceId ); ( 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 ) void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
{ {
Q_Q(SingleApplication); Q_Q(SingleApplication);
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() ); Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
} }
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId ) void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
{ {
if( closedSocket->bytesAvailable() > 0 ) if( closedSocket->bytesAvailable() > 0 )
Q_EMIT slotDataAvailable( closedSocket, instanceId ); Q_EMIT slotDataAvailable( closedSocket, instanceId );
}
void SingleApplicationPrivate::randomSleep()
{
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
#else
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
#endif
}
void SingleApplicationPrivate::addAppData(const QString &data)
{
appDataList.push_back(data);
}
QStringList SingleApplicationPrivate::appData() const
{
return appDataList;
} }

View File

@@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2016 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -38,62 +38,67 @@
#include "singleapplication.h" #include "singleapplication.h"
struct InstancesInfo { struct InstancesInfo {
bool primary; bool primary;
quint32 secondary; quint32 secondary;
qint64 primaryPid; qint64 primaryPid;
quint16 checksum; char primaryUser[128];
quint16 checksum; // Must be the last field
}; };
struct ConnectionInfo { struct ConnectionInfo {
explicit ConnectionInfo() : qint64 msgLen = 0;
msgLen(0), instanceId(0), stage(0) {} quint32 instanceId = 0;
qint64 msgLen; quint8 stage = 0;
quint32 instanceId;
quint8 stage;
}; };
class SingleApplicationPrivate : public QObject { class SingleApplicationPrivate : public QObject {
Q_OBJECT Q_OBJECT
public: public:
enum ConnectionType : quint8 { enum ConnectionType : quint8 {
InvalidConnection = 0, InvalidConnection = 0,
NewInstance = 1, NewInstance = 1,
SecondaryInstance = 2, SecondaryInstance = 2,
Reconnect = 3 Reconnect = 3
}; };
enum ConnectionStage : quint8 { enum ConnectionStage : quint8 {
StageHeader = 0, StageHeader = 0,
StageBody = 1, StageBody = 1,
StageConnected = 2, StageConnected = 2,
}; };
Q_DECLARE_PUBLIC(SingleApplication) Q_DECLARE_PUBLIC(SingleApplication)
SingleApplicationPrivate( SingleApplication *q_ptr ); SingleApplicationPrivate( SingleApplication *q_ptr );
~SingleApplicationPrivate(); ~SingleApplicationPrivate() override;
void genBlockServerName(); static QString getUsername();
void initializeMemoryBlock(); void genBlockServerName();
void startPrimary(); void initializeMemoryBlock() const;
void startSecondary(); void startPrimary();
void connectToPrimary(int msecs, ConnectionType connectionType ); void startSecondary();
quint16 blockChecksum(); bool connectToPrimary( int msecs, ConnectionType connectionType );
qint64 primaryPid(); quint16 blockChecksum() const;
void readInitMessageHeader(QLocalSocket *socket); qint64 primaryPid() const;
void readInitMessageBody(QLocalSocket *socket); QString primaryUser() const;
void readInitMessageHeader(QLocalSocket *socket);
SingleApplication *q_ptr; void readInitMessageBody(QLocalSocket *socket);
QSharedMemory *memory; static void randomSleep();
QLocalSocket *socket; void addAppData(const QString &data);
QLocalServer *server; QStringList appData() const;
quint32 instanceNumber;
QString blockServerName; SingleApplication *q_ptr;
SingleApplication::Options options; QSharedMemory *memory;
QMap<QLocalSocket*, ConnectionInfo> connectionMap; QLocalSocket *socket;
QLocalServer *server;
public Q_SLOTS: quint32 instanceNumber;
void slotConnectionEstablished(); QString blockServerName;
void slotDataAvailable( QLocalSocket*, quint32 ); SingleApplication::Options options;
void slotClientConnectionClosed( QLocalSocket*, quint32 ); QMap<QLocalSocket*, ConnectionInfo> connectionMap;
QStringList appDataList;
public Q_SLOTS:
void slotConnectionEstablished();
void slotDataAvailable( QLocalSocket*, quint32 );
void slotClientConnectionClosed( QLocalSocket*, quint32 );
}; };
#endif // SINGLEAPPLICATION_P_H #endif // SINGLEAPPLICATION_P_H

View File

@@ -1,4 +1,4 @@
<definition hotspot_y="52" version="0.51" hotspot_x="56" link_type="simple" width="70" type="element" height="100"> <definition hotspot_y="52" version="0.51" hotspot_x="56" link_type="master" width="70" type="element" height="100">
<uuid uuid="{2d6c186b-0578-4682-90c9-f77843432e9f}"/> <uuid uuid="{2d6c186b-0578-4682-90c9-f77843432e9f}"/>
<names> <names>
<name lang="cs">Motorový spouštěč 1P+N</name> <name lang="cs">Motorový spouštěč 1P+N</name>
@@ -12,6 +12,9 @@
<name lang="ar">قاطع مغناطيسي-حراري GV</name> <name lang="ar">قاطع مغناطيسي-حراري GV</name>
<name lang="hu">Motorvédő kapcsoló 1 pólusú GV</name> <name lang="hu">Motorvédő kapcsoló 1 pólusú GV</name>
</names> </names>
<kindInformations>
<kindInformation name="type" show="1">protection</kindInformation>
</kindInformations>
<informations></informations> <informations></informations>
<description> <description>
<polygon x1="0" y1="14" x5="0" y4="4" y5="4" x6="0" y7="-25" x2="0" y6="-6" y2="10" y3="10" x7="-7" antialias="true" x3="5" closed="false" x4="5" style="line-style:normal;line-weight:normal;filling:none;color:black"/> <polygon x1="0" y1="14" x5="0" y4="4" y5="4" x6="0" y7="-25" x2="0" y6="-6" y2="10" y3="10" x7="-7" antialias="true" x3="5" closed="false" x4="5" style="line-style:normal;line-weight:normal;filling:none;color:black"/>

View File

@@ -1,4 +1,4 @@
<definition hotspot_y="52" width="70" version="0.51" type="element" height="100" hotspot_x="56" link_type="simple"> <definition hotspot_y="52" width="70" version="0.51" type="element" height="100" hotspot_x="56" link_type="master">
<uuid uuid="{ec5fa3cd-6769-4a35-aaa9-6bc5ba4fc779}"/> <uuid uuid="{ec5fa3cd-6769-4a35-aaa9-6bc5ba4fc779}"/>
<names> <names>
<name lang="fr">Disjoncteur Magnéto-thermique GV 2P</name> <name lang="fr">Disjoncteur Magnéto-thermique GV 2P</name>
@@ -8,6 +8,9 @@
<name lang="cs">Motorový spouštěč 2P</name> <name lang="cs">Motorový spouštěč 2P</name>
<name lang="hu">Motorvédő kapcsoló 2 pólusú GV</name> <name lang="hu">Motorvédő kapcsoló 2 pólusú GV</name>
</names> </names>
<kindInformations>
<kindInformation name="type" show="1">protection</kindInformation>
</kindInformations>
<informations></informations> <informations></informations>
<description> <description>
<polygon y2="10" x3="-15" y4="4" x2="-20" y3="10" x5="-20" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="-20" x4="-15" closed="false" y1="14" y6="-6" x7="-27" y5="4" antialias="true" y7="-25" x6="-20"/> <polygon y2="10" x3="-15" y4="4" x2="-20" y3="10" x5="-20" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="-20" x4="-15" closed="false" y1="14" y6="-6" x7="-27" y5="4" antialias="true" y7="-25" x6="-20"/>

View File

@@ -1,4 +1,4 @@
<definition hotspot_y="52" version="0.51" hotspot_x="56" link_type="simple" width="110" type="element" height="100"> <definition hotspot_y="52" version="0.51" hotspot_x="56" link_type="master" width="110" type="element" height="100">
<uuid uuid="{2424a938-847f-447f-a4ad-d9b6ef730fd9}"/> <uuid uuid="{2424a938-847f-447f-a4ad-d9b6ef730fd9}"/>
<names> <names>
<name lang="de">Motorschutzschalter</name> <name lang="de">Motorschutzschalter</name>
@@ -12,6 +12,9 @@
<name lang="ar">قاطع مغناطيسي-حراري GV</name> <name lang="ar">قاطع مغناطيسي-حراري GV</name>
<name lang="hu">Motorvédő kapcsoló 3F + N pólusú</name> <name lang="hu">Motorvédő kapcsoló 3F + N pólusú</name>
</names> </names>
<kindInformations>
<kindInformation name="type" show="1">protection</kindInformation>
</kindInformations>
<informations></informations> <informations></informations>
<description> <description>
<line end1="none" x1="40" y1="39" x2="40" length1="1.5" y2="28" antialias="false" end2="none" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black"/> <line end1="none" x1="40" y1="39" x2="40" length1="1.5" y2="28" antialias="false" end2="none" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black"/>

View File

@@ -1,4 +1,4 @@
<definition hotspot_y="50" width="110" version="0.51" type="element" height="100" hotspot_x="56" link_type="simple"> <definition hotspot_y="50" width="110" version="0.51" type="element" height="100" hotspot_x="56" link_type="master">
<uuid uuid="{686e19c6-6bce-4ced-8c01-2d7f58a78386}"/> <uuid uuid="{686e19c6-6bce-4ced-8c01-2d7f58a78386}"/>
<names> <names>
<name lang="el">Θερμομαγνητικό</name> <name lang="el">Θερμομαγνητικό</name>
@@ -12,6 +12,9 @@
<name lang="de">Motorschutzschalter</name> <name lang="de">Motorschutzschalter</name>
<name lang="hu">Motorvédő kapcsoló 4 pólusú GV</name> <name lang="hu">Motorvédő kapcsoló 4 pólusú GV</name>
</names> </names>
<kindInformations>
<kindInformation name="type" show="1">protection</kindInformation>
</kindInformations>
<informations></informations> <informations></informations>
<description> <description>
<line y2="28" end1="none" length2="1.5" x2="40" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="40" length1="1.5" y1="39" end2="none" antialias="false"/> <line y2="28" end1="none" length2="1.5" x2="40" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="40" length1="1.5" y1="39" end2="none" antialias="false"/>

View File

@@ -1,4 +1,4 @@
<definition type="element" link_type="simple" hotspot_x="31" hotspot_y="4" version="0.5" width="90" height="70"> <definition type="element" link_type="master" hotspot_x="31" hotspot_y="4" version="0.5" width="90" height="70">
<uuid uuid="{98ADF831-42F0-4EDE-9267-6733EEBEAF62}"/><names> <uuid uuid="{98ADF831-42F0-4EDE-9267-6733EEBEAF62}"/><names>
<name lang="en">Motor circuit breaker</name> <name lang="en">Motor circuit breaker</name>
<name lang="es">Disyuntor termico magnetico 3P mando manual con auto retorno y boton de desenclave</name> <name lang="es">Disyuntor termico magnetico 3P mando manual con auto retorno y boton de desenclave</name>
@@ -10,6 +10,9 @@
<name lang="pl">Wyłącznik silnikowy</name> <name lang="pl">Wyłącznik silnikowy</name>
<name lang="hu">Motorvédő kapcsoló 3 pólusú</name> <name lang="hu">Motorvédő kapcsoló 3 pólusú</name>
</names> </names>
<kindInformations>
<kindInformation name="type" show="1">protection</kindInformation>
</kindInformations>
<informations>Author: The QElectroTech team <informations>Author: The QElectroTech team
License: see http://qelectrotech.org/wiki/doc/elements_license</informations> License: see http://qelectrotech.org/wiki/doc/elements_license</informations>
<description> <description>

View File

@@ -1,4 +1,4 @@
<definition type="element" link_type="simple" hotspot_x="33" hotspot_y="5" version="0.5" width="110" height="70"> <definition type="element" link_type="master" hotspot_x="33" hotspot_y="5" version="0.5" width="110" height="70">
<uuid uuid="{C33FE39E-B2EE-4EC0-BC3F-4EB76E9BCEA0}"/><names> <uuid uuid="{C33FE39E-B2EE-4EC0-BC3F-4EB76E9BCEA0}"/><names>
<name lang="en">Motor circuit breaker with neutral</name> <name lang="en">Motor circuit breaker with neutral</name>
<name lang="es">Disyuntor termico magnetico 3P+N mando manual con auto retorno y boton de desenclave</name> <name lang="es">Disyuntor termico magnetico 3P+N mando manual con auto retorno y boton de desenclave</name>
@@ -10,6 +10,9 @@
<name lang="pl">Wyłącznik silnikowy</name> <name lang="pl">Wyłącznik silnikowy</name>
<name lang="hu">Motorvédő kapcsoló 3F + N pólusú</name> <name lang="hu">Motorvédő kapcsoló 3F + N pólusú</name>
</names> </names>
<kindInformations>
<kindInformation name="type" show="1">protection</kindInformation>
</kindInformations>
<informations>Author: The QElectroTech team <informations>Author: The QElectroTech team
License: see http://qelectrotech.org/wiki/doc/elements_license</informations> License: see http://qelectrotech.org/wiki/doc/elements_license</informations>
<description> <description>

View File

@@ -1,4 +1,4 @@
<definition hotspot_y="50" version="0.51" hotspot_x="56" link_type="simple" width="70" type="element" height="110"> <definition hotspot_y="50" version="0.51" hotspot_x="56" link_type="master" width="70" type="element" height="110">
<uuid uuid="{68830151-5901-40f6-b94d-dc68b85165a2}"/> <uuid uuid="{68830151-5901-40f6-b94d-dc68b85165a2}"/>
<names> <names>
<name lang="fr">Disjoncteur differentiel 1P+N</name> <name lang="fr">Disjoncteur differentiel 1P+N</name>
@@ -8,6 +8,9 @@
<name lang="cs">Jističochránič 2P</name> <name lang="cs">Jističochránič 2P</name>
<name lang="hu">Differenciál-védőkapcsoló 1P+N termikus és zárlatvédelemmel</name> <name lang="hu">Differenciál-védőkapcsoló 1P+N termikus és zárlatvédelemmel</name>
</names> </names>
<kindInformations>
<kindInformation name="type" show="1">protection</kindInformation>
</kindInformations>
<informations></informations> <informations></informations>
<description> <description>
<line end1="none" x1="-29.6203" y1="28" x2="10" length1="1.5" y2="28" antialias="false" end2="none" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black"/> <line end1="none" x1="-29.6203" y1="28" x2="10" length1="1.5" y2="28" antialias="false" end2="none" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black"/>

View File

@@ -1,4 +1,4 @@
<definition hotspot_y="50" version="0.51" hotspot_x="56" link_type="simple" width="110" type="element" height="110"> <definition hotspot_y="50" version="0.51" hotspot_x="56" link_type="master" width="110" type="element" height="110">
<uuid uuid="{3fa48846-98c0-43e1-8a96-330d4dc11ceb}"/> <uuid uuid="{3fa48846-98c0-43e1-8a96-330d4dc11ceb}"/>
<names> <names>
<name lang="cs">Jističochránič 3P+N</name> <name lang="cs">Jističochránič 3P+N</name>
@@ -12,6 +12,9 @@
<name lang="ar">قاطع مغناطيسي-حراري GV</name> <name lang="ar">قاطع مغناطيسي-حراري GV</name>
<name lang="hu">Differenciál-védőkapcsoló 3P+N termikus és zárlatvédelemmel</name> <name lang="hu">Differenciál-védőkapcsoló 3P+N termikus és zárlatvédelemmel</name>
</names> </names>
<kindInformations>
<kindInformation name="type" show="1">protection</kindInformation>
</kindInformations>
<informations></informations> <informations></informations>
<description> <description>
<line end1="none" x1="-29.6203" y1="28" x2="49.5887" length1="1.5" y2="28" antialias="false" end2="none" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black"/> <line end1="none" x1="-29.6203" y1="28" x2="49.5887" length1="1.5" y2="28" antialias="false" end2="none" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black"/>

View File

@@ -1,4 +1,4 @@
<definition hotspot_y="50" version="0.51" hotspot_x="56" link_type="simple" width="70" type="element" height="110"> <definition hotspot_y="50" version="0.51" hotspot_x="56" link_type="master" width="70" type="element" height="110">
<uuid uuid="{20f65431-6f1f-4e09-9c73-68a1431c40a1}"/> <uuid uuid="{20f65431-6f1f-4e09-9c73-68a1431c40a1}"/>
<names> <names>
<name lang="fr">Disjoncteur differentiel 2P</name> <name lang="fr">Disjoncteur differentiel 2P</name>
@@ -8,6 +8,9 @@
<name lang="cs">Jističochránič 2P</name> <name lang="cs">Jističochránič 2P</name>
<name lang="hu">Differenciál-védőkapcsoló 2P termikus és zárlatvédelemmel</name> <name lang="hu">Differenciál-védőkapcsoló 2P termikus és zárlatvédelemmel</name>
</names> </names>
<kindInformations>
<kindInformation name="type" show="1">protection</kindInformation>
</kindInformations>
<informations></informations> <informations></informations>
<description> <description>
<polygon x1="-20" y1="14" x5="-20" y4="4" y5="4" x6="-20" y7="-25" x2="-20" y6="-6" y2="10" y3="10" x7="-27" antialias="true" x3="-15" closed="false" x4="-15" style="line-style:normal;line-weight:normal;filling:none;color:black"/> <polygon x1="-20" y1="14" x5="-20" y4="4" y5="4" x6="-20" y7="-25" x2="-20" y6="-6" y2="10" y3="10" x7="-27" antialias="true" x3="-15" closed="false" x4="-15" style="line-style:normal;line-weight:normal;filling:none;color:black"/>

View File

@@ -1,4 +1,4 @@
<definition hotspot_y="50" version="0.51" hotspot_x="56" link_type="simple" width="90" type="element" height="110"> <definition hotspot_y="50" version="0.51" hotspot_x="56" link_type="master" width="90" type="element" height="110">
<uuid uuid="{1e746e43-1906-4ea0-99a6-261e3c862dba}"/> <uuid uuid="{1e746e43-1906-4ea0-99a6-261e3c862dba}"/>
<names> <names>
<name lang="cs">Jističochránič 3P</name> <name lang="cs">Jističochránič 3P</name>
@@ -12,6 +12,9 @@
<name lang="ar">قاطع مغناطيسي-حراري GV</name> <name lang="ar">قاطع مغناطيسي-حراري GV</name>
<name lang="hu">Differenciál-védőkapcsoló 3P termikus és zárlatvédelemmel</name> <name lang="hu">Differenciál-védőkapcsoló 3P termikus és zárlatvédelemmel</name>
</names> </names>
<kindInformations>
<kindInformation name="type" show="1">protection</kindInformation>
</kindInformations>
<informations></informations> <informations></informations>
<description> <description>
<text text="I>" size="9" y="25" x="-24"/> <text text="I>" size="9" y="25" x="-24"/>

View File

@@ -1,4 +1,4 @@
<definition hotspot_y="50" version="0.51" hotspot_x="56" link_type="simple" width="110" type="element" height="110"> <definition hotspot_y="50" version="0.51" hotspot_x="56" link_type="master" width="110" type="element" height="110">
<uuid uuid="{fee3faef-5f4a-4a0b-bf0e-b6e93d9f9425}"/> <uuid uuid="{fee3faef-5f4a-4a0b-bf0e-b6e93d9f9425}"/>
<names> <names>
<name lang="de">Motorschutzschalter</name> <name lang="de">Motorschutzschalter</name>
@@ -12,6 +12,9 @@
<name lang="ar">قاطع مغناطيسي-حراري GV</name> <name lang="ar">قاطع مغناطيسي-حراري GV</name>
<name lang="hu">Differenciál-védőkapcsoló 4P termikus és zárlatvédelemmel</name> <name lang="hu">Differenciál-védőkapcsoló 4P termikus és zárlatvédelemmel</name>
</names> </names>
<kindInformations>
<kindInformation name="type" show="1">protection</kindInformation>
</kindInformations>
<informations></informations> <informations></informations>
<description> <description>
<polygon x1="-20" y1="14" x5="-20" y4="4" y5="4" x6="-20" y7="-25" x2="-20" y6="-6" y2="10" y3="10" x7="-27" antialias="true" x3="-15" closed="false" x4="-15" style="line-style:normal;line-weight:normal;filling:none;color:black"/> <polygon x1="-20" y1="14" x5="-20" y4="4" y5="4" x6="-20" y7="-25" x2="-20" y6="-6" y2="10" y3="10" x7="-27" antialias="true" x3="-15" closed="false" x4="-15" style="line-style:normal;line-weight:normal;filling:none;color:black"/>

Binary file not shown.

View File

@@ -826,7 +826,7 @@ Uwaga: te opcje nie pozwalają na zablokowanie automatycznej numeracji tylko ust
<message> <message>
<location filename="../sources/ui/conductorpropertieswidget.ui" line="58"/> <location filename="../sources/ui/conductorpropertieswidget.ui" line="58"/>
<source>Couleur du texte:</source> <source>Couleur du texte:</source>
<translation>Kolor tekstu</translation> <translation>Kolor tekstu:</translation>
</message> </message>
<message> <message>
<location filename="../sources/ui/conductorpropertieswidget.ui" line="151"/> <location filename="../sources/ui/conductorpropertieswidget.ui" line="151"/>
@@ -2600,7 +2600,7 @@ Wszystkie elementy i podkatalogi znajdujące się w tym katalogu zostaną usuni
<message> <message>
<location filename="../sources/editor/ui/ellipseeditor.ui" line="23"/> <location filename="../sources/editor/ui/ellipseeditor.ui" line="23"/>
<source>Centre :</source> <source>Centre :</source>
<translation>Środek</translation> <translation>Środek:</translation>
</message> </message>
<message> <message>
<location filename="../sources/editor/ui/ellipseeditor.ui" line="30"/> <location filename="../sources/editor/ui/ellipseeditor.ui" line="30"/>
@@ -5005,7 +5005,7 @@ Poniższe zmienne są zgodne:
<message> <message>
<location filename="../sources/print/projectprintwindow.ui" line="225"/> <location filename="../sources/print/projectprintwindow.ui" line="225"/>
<source>Si cette option est cochée, les marges de la feuille seront ignorées et toute sa surface sera utilisée pour l&apos;impression. Cela peut ne pas être supporté par votre imprimante.</source> <source>Si cette option est cochée, les marges de la feuille seront ignorées et toute sa surface sera utilisée pour l&apos;impression. Cela peut ne pas être supporté par votre imprimante.</source>
<translation>Jeżeli ta opcja jest zaznaczona, marginesy arkusza zostaną zignorowane, a cała jego powierzchnia zostanie wykorzystana do drukowania. Może to nie być obsługiwane przez Twoją drukarkę. </translation> <translation>Jeżeli ta opcja jest zaznaczona, marginesy arkusza zostaną zignorowane, a cała jego powierzchnia zostanie wykorzystana do drukowania. Może to nie być obsługiwane przez Twoją drukarkę.</translation>
</message> </message>
<message> <message>
<location filename="../sources/print/projectprintwindow.ui" line="259"/> <location filename="../sources/print/projectprintwindow.ui" line="259"/>
@@ -5117,7 +5117,7 @@ Poniższe zmienne są zgodne:
<message> <message>
<location filename="../sources/print/projectprintwindow.cpp" line="669"/> <location filename="../sources/print/projectprintwindow.cpp" line="669"/>
<source>Exporter sous : </source> <source>Exporter sous : </source>
<translation>Eksportuj jako:</translation> <translation>Eksportuj jako: </translation>
</message> </message>
<message> <message>
<location filename="../sources/print/projectprintwindow.cpp" line="669"/> <location filename="../sources/print/projectprintwindow.cpp" line="669"/>
@@ -6670,7 +6670,8 @@ Put DXFtoQET.exe binary on C:\Users\user_name\AppData\Roaming\qet\ directory
Odwiedź https://download.tuxfamily.org/qet/builds/dxf_to_elmt/ Odwiedź https://download.tuxfamily.org/qet/builds/dxf_to_elmt/
&gt;&gt; Instalacja w Windows &gt;&gt; Instalacja w Windows
Przenieś DXFtoQET.exe binary do C:\Users\user_name\AppData\Roaming\qet\ directory</translation> Przenieś DXFtoQET.exe binary do C:\Users\user_name\AppData\Roaming\qet\ directory
</translation>
</message> </message>
<message> <message>
<location filename="../sources/editor/qetelementeditor.cpp" line="169"/> <location filename="../sources/editor/qetelementeditor.cpp" line="169"/>
@@ -6684,7 +6685,8 @@ Put DXFtoQET.app binary on /Users/user_name/.qet/ directory
Odwiedź https://download.tuxfamily.org/qet/builds/dxf_to_elmt/ Odwiedź https://download.tuxfamily.org/qet/builds/dxf_to_elmt/
&gt;&gt; Instalacja w macOSX &gt;&gt; Instalacja w macOSX
Przenieś DXFtoQET.app binary do /Users/user_name/.qet/ directory</translation> Przenieś DXFtoQET.app binary do /Users/user_name/.qet/ directory
</translation>
</message> </message>
<message> <message>
<location filename="../sources/editor/qetelementeditor.cpp" line="175"/> <location filename="../sources/editor/qetelementeditor.cpp" line="175"/>
@@ -6700,7 +6702,8 @@ Odwiedź https://download.tuxfamily.org/qet/builds/dxf_to_elmt/
&gt;&gt; Instalacja w Linux &gt;&gt; Instalacja w Linux
Preznieś DXFtoQET binary do twojego /home/user_name/.qet/ directory Preznieś DXFtoQET binary do twojego /home/user_name/.qet/ directory
make it executable : chmod +x ./DXFtoQE</translation> make it executable : chmod +x ./DXFtoQE
</translation>
</message> </message>
<message> <message>
<location filename="../sources/editor/qetelementeditor.cpp" line="295"/> <location filename="../sources/editor/qetelementeditor.cpp" line="295"/>
@@ -8773,14 +8776,14 @@ Czy chcesz ją zastąpić?</translation>
<source>Les information à afficher sont supérieurs à la quantité maximal pouvant être affiché par les tableaux. <source>Les information à afficher sont supérieurs à la quantité maximal pouvant être affiché par les tableaux.
Veuillez ajouter un nouveau tableau ou regler les tableaux existant afin d&apos;afficher l&apos;integralité des informations.</source> Veuillez ajouter un nouveau tableau ou regler les tableaux existant afin d&apos;afficher l&apos;integralité des informations.</source>
<translation>Wyświetlane informacje większe niż maksymalna liczba, którą można wyświetlić w tabelach. <translation>Wyświetlane informacje większe niż maksymalna liczba, którą można wyświetlić w tabelach.
Dodaj nową tabelę lub dostosuj istniejące tabele, aby wyświetlić wszystkie informacje. </translation> Dodaj nową tabelę lub dostosuj istniejące tabele, aby wyświetlić wszystkie informacje.</translation>
</message> </message>
<message> <message>
<location filename="../sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp" line="118"/> <location filename="../sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp" line="118"/>
<source>Les information à afficher sont supérieurs à la quantité maximal pouvant être affiché par le tableau. <source>Les information à afficher sont supérieurs à la quantité maximal pouvant être affiché par le tableau.
Veuillez ajouter un nouveau tableau ou regler le tableau existant afin d&apos;afficher l&apos;integralité des informations.</source> Veuillez ajouter un nouveau tableau ou regler le tableau existant afin d&apos;afficher l&apos;integralité des informations.</source>
<translation>Wyświetlane informacje większe niż maksymalna liczba, którą można wyświetlić w tabeli. <translation>Wyświetlane informacje większe niż maksymalna liczba, którą można wyświetlić w tabeli.
Dodaj nową tabelę lub dostosuj istniejącą, aby wyświetlić wszystkie informacje. </translation> Dodaj nową tabelę lub dostosuj istniejącą, aby wyświetlić wszystkie informacje.</translation>
</message> </message>
<message> <message>
<location filename="../sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp" line="121"/> <location filename="../sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp" line="121"/>

View File

@@ -501,7 +501,7 @@ void DiagramView::mousePressEvent(QMouseEvent *e)
*/ */
void DiagramView::mouseMoveEvent(QMouseEvent *e) void DiagramView::mouseMoveEvent(QMouseEvent *e)
{ {
setToolTip(tr("(Dev) X: %1 Y: %2").arg(e->pos().x()).arg(e->pos().y())); setToolTip(tr("X: %1 Y: %2").arg(e->pos().x()).arg(e->pos().y()));
if (m_event_interface && m_event_interface->mouseMoveEvent(e)) return; if (m_event_interface && m_event_interface->mouseMoveEvent(e)) return;
// Drag the view // Drag the view

View File

@@ -95,13 +95,13 @@ void ProjectPrintWindow::launchDialog(QETProject *project, QPrinter::OutputForma
QString ProjectPrintWindow::docName(QETProject *project) QString ProjectPrintWindow::docName(QETProject *project)
{ {
QString doc_name; QString doc_name;
if (!project->title().isEmpty()) { if (!project->filePath().isEmpty()) {
doc_name = project->title(); doc_name = QFileInfo(project->filePath()).baseName();
} else if (!project->filePath().isEmpty()) { } else if (!project->title().isEmpty()) {
doc_name = QFileInfo(project->filePath()).baseName(); doc_name = project->title();
doc_name = QET::stringToFileName(doc_name);
} }
doc_name = QET::stringToFileName(doc_name);
if (doc_name.isEmpty()) { if (doc_name.isEmpty()) {
doc_name = tr("projet", "string used to generate a filename"); doc_name = tr("projet", "string used to generate a filename");
} }
@@ -666,7 +666,7 @@ QList<Diagram *> ProjectPrintWindow::selectedDiagram() const
void ProjectPrintWindow::exportToPDF() void ProjectPrintWindow::exportToPDF()
{ {
auto file_name = QFileDialog::getSaveFileName(this, tr("Exporter sous : "), m_printer->outputFileName(), tr("Fichier (*.pdf")); auto file_name = QFileDialog::getSaveFileName(this, tr("Exporter sous : "), m_printer->outputFileName(), tr("Fichier (*.pdf)"));
if (file_name.isEmpty()) { if (file_name.isEmpty()) {
return; return;
} }

View File

@@ -1,74 +1,77 @@
/** /**
* pugixml parser - version 1.10 * pugixml parser - version 1.11
* -------------------------------------------------------- * --------------------------------------------------------
* Copyright (C) 2006-2019, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Copyright (C) 2006-2020, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
* Report bugs and download new versions at https://pugixml.org/ * Report bugs and download new versions at https://pugixml.org/
* *
* This library is distributed under the MIT License. See notice at the end * This library is distributed under the MIT License. See notice at the end
* of this file. * of this file.
* *
* This work is based on the pugxml parser, which is: * This work is based on the pugxml parser, which is:
* Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net)
*/ */
#ifndef HEADER_PUGICONFIG_HPP #ifndef HEADER_PUGICONFIG_HPP
#define HEADER_PUGICONFIG_HPP #define HEADER_PUGICONFIG_HPP
// Uncomment this to enable wchar_t mode // Uncomment this to enable wchar_t mode
// #define PUGIXML_WCHAR_MODE // #define PUGIXML_WCHAR_MODE
// Uncomment this to enable compact mode // Uncomment this to enable compact mode
// #define PUGIXML_COMPACT // #define PUGIXML_COMPACT
// Uncomment this to disable XPath // Uncomment this to disable XPath
// #define PUGIXML_NO_XPATH // #define PUGIXML_NO_XPATH
// Uncomment this to disable STL // Uncomment this to disable STL
// #define PUGIXML_NO_STL // #define PUGIXML_NO_STL
// Uncomment this to disable exceptions // Uncomment this to disable exceptions
// #define PUGIXML_NO_EXCEPTIONS // #define PUGIXML_NO_EXCEPTIONS
// Set this to control attributes for public classes/functions, i.e.: // Set this to control attributes for public classes/functions, i.e.:
// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL // #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL
// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL // #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL
// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall // #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall
// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead // In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead
// Tune these constants to adjust memory-related behavior // Tune these constants to adjust memory-related behavior
// #define PUGIXML_MEMORY_PAGE_SIZE 32768 // #define PUGIXML_MEMORY_PAGE_SIZE 32768
// #define PUGIXML_MEMORY_OUTPUT_STACK 10240 // #define PUGIXML_MEMORY_OUTPUT_STACK 10240
// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096 // #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096
// Uncomment this to switch to header-only version // Tune this constant to adjust max nesting for XPath queries
// #define PUGIXML_HEADER_ONLY // #define PUGIXML_XPATH_DEPTH_LIMIT 1024
// Uncomment this to enable long long support // Uncomment this to switch to header-only version
// #define PUGIXML_HAS_LONG_LONG // #define PUGIXML_HEADER_ONLY
#endif // Uncomment this to enable long long support
// #define PUGIXML_HAS_LONG_LONG
/**
* Copyright (c) 2006-2019 Arseny Kapoulkine #endif
*
* Permission is hereby granted, free of charge, to any person /**
* obtaining a copy of this software and associated documentation * Copyright (c) 2006-2020 Arseny Kapoulkine
* files (the "Software"), to deal in the Software without *
* restriction, including without limitation the rights to use, * Permission is hereby granted, free of charge, to any person
* copy, modify, merge, publish, distribute, sublicense, and/or sell * obtaining a copy of this software and associated documentation
* copies of the Software, and to permit persons to whom the * files (the "Software"), to deal in the Software without
* Software is furnished to do so, subject to the following * restriction, including without limitation the rights to use,
* conditions: * copy, modify, merge, publish, distribute, sublicense, and/or sell
* * copies of the Software, and to permit persons to whom the
* The above copyright notice and this permission notice shall be * Software is furnished to do so, subject to the following
* included in all copies or substantial portions of the Software. * conditions:
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * The above copyright notice and this permission notice shall be
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * included in all copies or substantial portions of the Software.
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND *
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* OTHER DEALINGS IN THE SOFTWARE. * 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.
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -98,6 +98,14 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<tabstops>
<tabstop>m_colums_count_sp</tabstop>
<tabstop>m_columns_width_sp</tabstop>
<tabstop>m_rows_count_sp</tabstop>
<tabstop>m_rows_height_sp</tabstop>
<tabstop>m_display_columns_cb</tabstop>
<tabstop>m_display_rows_cb</tabstop>
</tabstops>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View File

@@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>662</width> <width>666</width>
<height>418</height> <height>418</height>
</rect> </rect>
</property> </property>
@@ -581,6 +581,41 @@
<header>kcolorbutton.h</header> <header>kcolorbutton.h</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>tabWidget_2</tabstop>
<tabstop>m_multiwires_gb</tabstop>
<tabstop>m_one_text_per_folio_cb</tabstop>
<tabstop>m_text_size_sb</tabstop>
<tabstop>m_formula_le</tabstop>
<tabstop>m_text_le</tabstop>
<tabstop>m_show_text_cb</tabstop>
<tabstop>m_text_color_kpb</tabstop>
<tabstop>m_available_autonum_cb</tabstop>
<tabstop>m_edit_autonum_pb</tabstop>
<tabstop>m_function_le</tabstop>
<tabstop>m_tension_protocol_le</tabstop>
<tabstop>m_wire_color_le</tabstop>
<tabstop>m_wire_section_le</tabstop>
<tabstop>m_cable_le</tabstop>
<tabstop>m_bus_le</tabstop>
<tabstop>m_verti_cb</tabstop>
<tabstop>m_horiz_cb</tabstop>
<tabstop>m_singlewire_gb</tabstop>
<tabstop>m_phase_sb</tabstop>
<tabstop>m_update_preview_pb</tabstop>
<tabstop>m_neutral_cb</tabstop>
<tabstop>m_earth_cb</tabstop>
<tabstop>m_phase_cb</tabstop>
<tabstop>m_phase_slider</tabstop>
<tabstop>m_pen_cb</tabstop>
<tabstop>m_color_kpb</tabstop>
<tabstop>m_color_2_gb</tabstop>
<tabstop>m_dash_size_sb</tabstop>
<tabstop>m_color_2_kpb</tabstop>
<tabstop>m_cond_size_sb</tabstop>
<tabstop>m_line_style_cb</tabstop>
</tabstops>
<resources> <resources>
<include location="../../qelectrotech.qrc"/> <include location="../../qelectrotech.qrc"/>
</resources> </resources>

View File

@@ -17,7 +17,7 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>5</number>
</property> </property>
<widget class="QWidget" name="tab_3"> <widget class="QWidget" name="tab_3">
<attribute name="title"> <attribute name="title">
@@ -811,19 +811,35 @@ Vous pouvez spécifier ici la valeur par défaut de ce champ pour les éléments
</layout> </layout>
</widget> </widget>
<tabstops> <tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>m_use_system_color_cb</tabstop> <tabstop>m_use_system_color_cb</tabstop>
<tabstop>m_use_windows_mode_rb</tabstop>
<tabstop>m_use_tab_mode_rb</tabstop>
<tabstop>m_use_gesture_trackpad</tabstop> <tabstop>m_use_gesture_trackpad</tabstop>
<tabstop>m_zoom_out_beyond_folio</tabstop> <tabstop>m_zoom_out_beyond_folio</tabstop>
<tabstop>m_use_windows_mode_rb</tabstop>
<tabstop>m_use_tab_mode_rb</tabstop>
<tabstop>m_save_label_paste</tabstop>
<tabstop>m_use_folio_label</tabstop>
<tabstop>m_export_terminal</tabstop>
<tabstop>m_border_0</tabstop>
<tabstop>m_autosave_sb</tabstop>
<tabstop>m_common_elmt_path_cb</tabstop> <tabstop>m_common_elmt_path_cb</tabstop>
<tabstop>m_custom_elmt_path_cb</tabstop> <tabstop>m_custom_elmt_path_cb</tabstop>
<tabstop>m_custom_tbt_path_cb</tabstop>
<tabstop>m_highlight_integrated_elements</tabstop> <tabstop>m_highlight_integrated_elements</tabstop>
<tabstop>m_default_elements_info</tabstop> <tabstop>m_default_elements_info</tabstop>
<tabstop>m_lang_cb</tabstop> <tabstop>m_lang_cb</tabstop>
<tabstop>m_dyn_text_font_pb</tabstop> <tabstop>m_dyn_text_font_pb</tabstop>
<tabstop>m_dyn_text_rotation_sb</tabstop> <tabstop>m_dyn_text_rotation_sb</tabstop>
<tabstop>m_dyn_text_width_sb</tabstop> <tabstop>m_dyn_text_width_sb</tabstop>
<tabstop>m_indi_text_font_pb</tabstop>
<tabstop>m_indi_text_rotation_sb</tabstop>
<tabstop>m_font_pb</tabstop>
<tabstop>DiagramEditor_xGrid_sb</tabstop>
<tabstop>DiagramEditor_yGrid_sb</tabstop>
<tabstop>DiagramEditor_xKeyGrid_sb</tabstop>
<tabstop>DiagramEditor_yKeyGrid_sb</tabstop>
<tabstop>DiagramEditor_xKeyGridFine_sb</tabstop>
<tabstop>DiagramEditor_yKeyGridFine_sb</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections/> <connections/>

View File

@@ -29,7 +29,7 @@
<string/> <string/>
</property> </property>
<property name="icon"> <property name="icon">
<iconset> <iconset resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/folder-open.png</normaloff>:/ico/16x16/folder-open.png</iconset> <normaloff>:/ico/16x16/folder-open.png</normaloff>:/ico/16x16/folder-open.png</iconset>
</property> </property>
</widget> </widget>
@@ -40,13 +40,21 @@
<string/> <string/>
</property> </property>
<property name="icon"> <property name="icon">
<iconset> <iconset resource="../../qelectrotech.qrc">
<normaloff>:/ico/16x16/document-save.png</normaloff>:/ico/16x16/document-save.png</iconset> <normaloff>:/ico/16x16/document-save.png</normaloff>:/ico/16x16/document-save.png</iconset>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<resources/> <tabstops>
<tabstop>m_combo_box</tabstop>
<tabstop>m_load_pb</tabstop>
<tabstop>m_line_edit</tabstop>
<tabstop>m_save_pb</tabstop>
</tabstops>
<resources>
<include location="../../qelectrotech.qrc"/>
</resources>
<connections/> <connections/>
</ui> </ui>

View File

@@ -163,6 +163,16 @@ Veuillez utiliser l'éditeur avancé pour cela.</string>
</item> </item>
</layout> </layout>
</widget> </widget>
<tabstops>
<tabstop>m_line_edit</tabstop>
<tabstop>m_font_pb</tabstop>
<tabstop>m_advanced_editor_pb</tabstop>
<tabstop>m_x_sb</tabstop>
<tabstop>m_y_sb</tabstop>
<tabstop>m_angle_sb</tabstop>
<tabstop>m_size_sb</tabstop>
<tabstop>m_break_html_pb</tabstop>
</tabstops>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View File

@@ -256,6 +256,15 @@
<header>kcolorbutton.h</header> <header>kcolorbutton.h</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<tabstops>
<tabstop>m_style_cb</tabstop>
<tabstop>m_size_dsb</tabstop>
<tabstop>m_color_kpb</tabstop>
<tabstop>m_brush_style_cb</tabstop>
<tabstop>m_brush_color_kpb</tabstop>
<tabstop>m_lock_pos_cb</tabstop>
<tabstop>m_close_polygon</tabstop>
</tabstops>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View File

@@ -2,6 +2,14 @@
<ui version="4.0"> <ui version="4.0">
<class>XRefPropertiesWidget</class> <class>XRefPropertiesWidget</class>
<widget class="QWidget" name="XRefPropertiesWidget"> <widget class="QWidget" name="XRefPropertiesWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>376</width>
<height>531</height>
</rect>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
@@ -265,6 +273,20 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<tabstops>
<tabstop>m_type_cb</tabstop>
<tabstop>m_snap_to_cb</tabstop>
<tabstop>m_offset_sb</tabstop>
<tabstop>m_xrefpos_cb</tabstop>
<tabstop>m_display_has_contacts_rb</tabstop>
<tabstop>m_display_has_cross_rb</tabstop>
<tabstop>m_master_le</tabstop>
<tabstop>m_slave_le</tabstop>
<tabstop>m_show_power_cb</tabstop>
<tabstop>m_power_prefix_le</tabstop>
<tabstop>m_delay_prefix_le</tabstop>
<tabstop>m_switch_prefix_le</tabstop>
</tabstops>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>