diff --git a/qelectrotech.pro b/qelectrotech.pro index fc9a3bf08..0afad9c58 100644 --- a/qelectrotech.pro +++ b/qelectrotech.pro @@ -76,6 +76,7 @@ HEADERS += aboutqet.h \ qet.h \ qetapp.h \ qetdiagrameditor.h \ + qetsingleapplication.h \ qgimanager.h \ terminal.h \ editor/arceditor.h \ @@ -142,6 +143,7 @@ SOURCES += aboutqet.cpp \ qet.cpp \ qetapp.cpp \ qetdiagrameditor.cpp \ + qetsingleapplication.cpp \ qgimanager.cpp \ terminal.cpp \ editor/arceditor.cpp \ @@ -171,7 +173,7 @@ SOURCES += aboutqet.cpp \ RESOURCES += qelectrotech.qrc TRANSLATIONS += lang/qet_en.ts lang/qt_fr.ts RC_FILE = ico/windows_icon/application_icon/qelectrotech.rc -QT += xml svg +QT += xml svg network CONFIG += debug_and_release warn_on TARGET = qelectrotech diff --git a/qetapp.cpp b/qetapp.cpp index 5b46e9681..f5e95ddd0 100644 --- a/qetapp.cpp +++ b/qetapp.cpp @@ -32,13 +32,37 @@ QString QETApp::diagram_texts_font = QString(); @param argc Nombre d'arguments passes a l'application @param argv Arguments passes a l'application */ -QETApp::QETApp(int &argc, char **argv) : QApplication(argc, argv) { +QETApp::QETApp(int &argc, char **argv) : + QETSingleApplication(argc, argv, QString("qelectrotech-" + QETApp::userName())) +{ // selectionne le langage du systeme QString system_language = QLocale::system().name().left(2); setLanguage(system_language); - // parse les arguments - foreach(QString argument, arguments()) { + // booleen indiquant si l'application va se terminer immediatement apres un court traitement + bool must_exit = false; + + // parse les arguments en + QStringList files; // liste des fichiers + QStringList options; // liste des options + + // recupere les arguments + QStringList arguments_list(arguments()); + arguments_list.pop_front(); // ignore le premier (= chemin de l'executable) + + // separe les fichiers des options + foreach(QString argument, arguments_list) { + QFileInfo argument_info(argument); + if (argument_info.exists()) { + // on exprime les chemins des fichiers en absolu + files << argument_info.canonicalFilePath(); + } else { + options << argument; + } + } + + // parse les options + foreach(QString argument, options) { #ifdef QET_ALLOW_OVERRIDE_CED_OPTION QString ced_arg("--common-elements-dir="); if (argument.startsWith(ced_arg)) { @@ -53,7 +77,7 @@ QETApp::QETApp(int &argc, char **argv) : QApplication(argc, argv) { overrideConfigDir(cd_value); } #endif - bool must_exit = false; + if (argument == QString("--help")) { printHelp(); must_exit = true; @@ -64,11 +88,23 @@ QETApp::QETApp(int &argc, char **argv) : QApplication(argc, argv) { printLicense(); must_exit = true; } - if (must_exit) { - std::exit(EXIT_SUCCESS); - } } + if (!must_exit && isRunning()) { + QStringList abs_arg_list(options); + abs_arg_list << files; + + // envoie les arguments a l'instance deja existante + must_exit = sendMessage("launched-with-args: " + abs_arg_list.join(" ")); + } + + if (must_exit) { + std::exit(EXIT_SUCCESS); + } + + // prise en compte des messages des autres instances + connect(this, SIGNAL(messageAvailable(QString)), this, SLOT(messageReceived(const QString&))); + // nettoyage avant de quitter l'application connect(this, SIGNAL(aboutToQuit()), this, SLOT(cleanup())); @@ -125,10 +161,6 @@ QETApp::QETApp(int &argc, char **argv) : QApplication(argc, argv) { diagram_texts_font = qet_settings -> value("diagramfont", "Sans Serif").toString(); // Creation et affichage d'un editeur de schema - QStringList files; - foreach(QString argument, arguments()) { - if (QFileInfo(argument).exists()) files << argument; - } new QETDiagramEditor(files); buildSystemTrayMenu(); } @@ -227,6 +259,17 @@ void QETApp::newElementEditor() { new QETElementEditor(); } +/** + @return le nom de l'utilisateur courant +*/ +QString QETApp::userName() { +#ifndef Q_OS_WIN32 + return(QString(getenv("USER"))); +#else + return(QString(getenv("USERNAME"))); +#endif +} + /** Renvoie le dossier des elements communs, c-a-d le chemin du dossier dans lequel QET doit chercher les definitions XML des elements de la collection QET. @@ -471,6 +514,58 @@ void QETApp::checkRemainingWindows() { sleep = !sleep; } +/** + Gere les messages recus + @param message Message recu +*/ +void QETApp::messageReceived(const QString &message) { + if (message.startsWith("launched-with-args: ")) { + QString my_message(message.mid(20)); + QStringList files_list = my_message.split(' '); + openFiles(files_list); + } +} + +/** + Ouvre une liste de fichiers. + Les fichiers sont ouverts dans le premier editeur de schemas visible venu. + Sinon, le premier editeur de schemas existant venu devient visible et est + utilise. S'il n'y a aucun editeur de schemas ouvert, un nouveau est cree et + utilise. + @param files_list Liste des fichiers a ouvrir +*/ +void QETApp::openFiles(const QStringList &files_list) { + if (files_list.isEmpty()) return; + + // liste des editeurs de schema ouverts + QList diagrams_editors = diagramEditors(); + + // s'il y a des editeur de schemas ouvert, on cherche ceux qui sont visibles + if (diagrams_editors.count()) { + QList visible_diagrams_editors; + foreach(QETDiagramEditor *de, diagrams_editors) { + if (de -> isVisible()) visible_diagrams_editors << de; + } + + // on choisit soit le premier visible soit le premier tout court + QETDiagramEditor *de_open; + if (visible_diagrams_editors.count()) { + de_open = visible_diagrams_editors.first(); + } else { + de_open = diagrams_editors.first(); + de_open -> setVisible(true); + } + + // ouvre les fichiers dans l'editeur ainsi choisi + foreach(QString file, files_list) { + de_open -> openAndAddDiagram(file); + } + } else { + // cree un nouvel editeur qui ouvrira les fichiers + new QETDiagramEditor(files_list); + } +} + /** @param window fenetre dont il faut trouver les barres d'outils et dock flottants @return les barres d'outils et dock flottants de la fenetre @@ -553,8 +648,9 @@ void QETApp::fetchWindowStats(const QList &diagrams, const Q every_editor_reduced = every_element_reduced && every_diagram_reduced; } +#ifdef Q_OS_DARWIN /** - Gere les evenement + Gere les evenements, en particulier l'evenement FileOpen sous MacOs. @param e Evenement a gerer */ bool QETApp::event(QEvent *e) { @@ -562,30 +658,13 @@ bool QETApp::event(QEvent *e) { if (e -> type() == QEvent::FileOpen) { // nom du fichier a ouvrir QString filename = static_cast(e) -> file(); - // liste des editeurs de schema ouverts - QList diagrams_editors = diagramEditors(); - if (diagrams_editors.count()) { - // s'il y a des editeur de schemas ouvert, on cherche ceux qui sont visibles - QList visible_diagrams_editors; - foreach(QETDiagramEditor *de, diagrams_editors) { - if (de -> isVisible()) visible_diagrams_editors << de; - } - // on choisit soit le premier visible soit le premier tout court - QETDiagramEditor *de_open; - if (visible_diagrams_editors.count()) { - de_open = visible_diagrams_editors.first(); - } else { - de_open = diagrams_editors.first(); - de_open -> setVisible(true); - } - } else { - new QETDiagramEditor(QStringList() << filename); - } + openFiles(QStringList() << filename); return(true); } else { return(QApplication::event(e)); } } +#endif /** Affiche l'aide et l'usage sur la sortie standard diff --git a/qetapp.h b/qetapp.h index cd172105d..7dcd5829a 100644 --- a/qetapp.h +++ b/qetapp.h @@ -17,7 +17,7 @@ */ #ifndef QET_APP_H #define QET_APP_H -#include +#include "qetsingleapplication.h" #include #include class QETDiagramEditor; @@ -26,7 +26,7 @@ class QETElementEditor; Cette classe represente l'application QElectroTech. */ -class QETApp : public QApplication { +class QETApp : public QETSingleApplication { Q_OBJECT // constructeurs, destructeur public: @@ -43,6 +43,7 @@ class QETApp : public QApplication { static void printVersion(); static void printLicense(); + static QString userName(); static QString commonElementsDir(); static QString customElementsDir(); static QString configDir(); @@ -66,7 +67,9 @@ class QETApp : public QApplication { static QString diagramTextsFont(); protected: +#ifdef Q_OS_DARWIN bool event(QEvent *); +#endif // attributs private: @@ -109,6 +112,8 @@ class QETApp : public QApplication { void invertMainWindowVisibility(QWidget *); void quitQET(); void checkRemainingWindows(); + void messageReceived(const QString &); + void openFiles(const QStringList &); // methodes privees private slots: diff --git a/qetdiagrameditor.cpp b/qetdiagrameditor.cpp index e465c6a57..8f7aafdd5 100644 --- a/qetdiagrameditor.cpp +++ b/qetdiagrameditor.cpp @@ -569,6 +569,16 @@ bool QETDiagramEditor::openDiagram() { open_dialog_dir.absolutePath(), tr("Sch\351mas QElectroTech (*.qet);;Fichiers XML (*.xml);;Tous les fichiers (*)") ); + + return(openAndAddDiagram(nom_fichier)); +} + +/** + Cette methode ouvre un fichier. + @param nom_fichier Chemin du fichier a ouvrir + @return true si l'ouverture a reussi, false sinon +*/ +bool QETDiagramEditor::openAndAddDiagram(const QString &nom_fichier) { if (nom_fichier.isEmpty()) return(false); open_dialog_dir = QDir(nom_fichier); diff --git a/qetdiagrameditor.h b/qetdiagrameditor.h index a525ae751..3c1446998 100644 --- a/qetdiagrameditor.h +++ b/qetdiagrameditor.h @@ -63,6 +63,7 @@ class QETDiagramEditor : public QMainWindow { bool save(); bool newDiagram(); bool openDiagram(); + bool openAndAddDiagram(const QString &); bool closeDiagram(); void slot_editInfos(); void slot_cut(); diff --git a/qetsingleapplication.cpp b/qetsingleapplication.cpp new file mode 100644 index 000000000..2d9135de1 --- /dev/null +++ b/qetsingleapplication.cpp @@ -0,0 +1,120 @@ +/* + Copyright 2006-2008 Xavier Guerrin + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTAvBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#include "qetsingleapplication.h" +#include + +const int QETSingleApplication::timeout_ = 10000; + +/** + Constructeur + @param argc Nombre d'arguments passes au programme par le systeme + @param argv Tableau des arguments passes au programme par le systeme + @param unique_key Cle unique +*/ +QETSingleApplication::QETSingleApplication(int &argc, char **argv, const QString unique_key) : + QApplication(argc, argv), + unique_key_(unique_key) +{ + // verifie s'il y a un segment de memoire partage correspondant a la cle unique + shared_memory_.setKey(unique_key_); + if (shared_memory_.attach()) { + // oui : l'application est deja en cours d'execution + is_running_ = true; + } else { + // non : il s'agit du premier demarrage de l'application pour cette cle unique + is_running_ = false; + + // initialisation du segment de memoire partage + if (!shared_memory_.create(1)) { + qDebug() << "Impossible de cr\351er l'instance unique."; + qDebug() << "Used key is:" << unique_key_; + return; + } + + // initialisation d'un serveur local pour recevoir les messages des autres instances + local_server_ = new QLocalServer(this); + connect(local_server_, SIGNAL(newConnection()), this, SLOT(receiveMessage())); + // la cle unique est egalement utilise pour le serveur + local_server_ -> listen(unique_key_); + } +} + +/** + Destructeur +*/ +QETSingleApplication::~QETSingleApplication() { +} + +/** + Slot gerant la reception des messages. + Lorsque l'application recoit un message, ce slot emet le signal + messageAvailable avec le message recu. +*/ +void QETSingleApplication::receiveMessage() { + QLocalSocket *local_socket = local_server_ -> nextPendingConnection(); + if (!local_socket -> waitForReadyRead(timeout_)) { + qDebug() << local_socket -> errorString().toLatin1(); + qDebug() << "Used key is:" << unique_key_; + return; + } + QByteArray byteArray = local_socket -> readAll(); + QString message = QString::fromUtf8(byteArray.constData()); + emit(messageAvailable(message)); + local_socket -> disconnectFromServer(); +} + +/** + @return true si l'application est deja en cours d'execution +*/ +bool QETSingleApplication::isRunning() { + return(is_running_); +} + +/** + Envoie un message a l'application. Si celle-ci n'est pas en cours + d'execution, cette methode ne fait rien. + @param message Message a transmettre a l'application + @return true si le message a ete tranmis, false sinon +*/ +bool QETSingleApplication::sendMessage(const QString &message) { + // l'application doit etre en cours d'execution + if (!is_running_) { + return(false); + } + + // se connecte a l'application, avec gestion du timeout + QLocalSocket local_socket(this); + local_socket.connectToServer(unique_key_, QIODevice::WriteOnly); + if (!local_socket.waitForConnected(timeout_)) { + qDebug() << local_socket.errorString().toLatin1(); + qDebug() << "Used key is:" << unique_key_; + return(false); + } + + // envoi du message, avec gestion du timeout + local_socket.write(message.toUtf8()); + if (!local_socket.waitForBytesWritten(timeout_)) { + qDebug() << local_socket.errorString().toLatin1(); + qDebug() << "Used key is:" << unique_key_; + return(false); + } + + // deconnexion + local_socket.disconnectFromServer(); + return(true); +} diff --git a/qetsingleapplication.h b/qetsingleapplication.h new file mode 100644 index 000000000..347c8062a --- /dev/null +++ b/qetsingleapplication.h @@ -0,0 +1,56 @@ +/* + Copyright 2006-2008 Xavier Guerrin + This file is part of QElectroTech. + + QElectroTech is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + QElectroTech is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with QElectroTech. If not, see . +*/ +#ifndef QET_SINGLE_APPLICATION_H +#define QET_SINGLE_APPLICATION_H +#include +#include +#include +/** + Cette classe represente une application Qt ne s'executant qu'en un seul + exemplaire en fonction d'une cle unique (de type QString). +*/ +class QETSingleApplication : public QApplication { + Q_OBJECT + // constructeurs, destructeur + public: + QETSingleApplication(int &, char **, const QString); + virtual ~QETSingleApplication(); + + private: + QETSingleApplication(const QETSingleApplication &); + + // methodes + public: + bool isRunning(); + bool sendMessage(const QString &); + + public slots: + void receiveMessage(); + + signals: + void messageAvailable(QString); + + // attributs + private: + bool is_running_; + QString unique_key_; + QSharedMemory shared_memory_; + QLocalServer *local_server_; + static const int timeout_; +}; +#endif