diff options
Diffstat (limited to 'tools/linguist')
54 files changed, 4753 insertions, 2070 deletions
diff --git a/tools/linguist/lconvert/main.cpp b/tools/linguist/lconvert/main.cpp index 9d8a644..52a3e56 100644 --- a/tools/linguist/lconvert/main.cpp +++ b/tools/linguist/lconvert/main.cpp @@ -51,21 +51,16 @@ static int usage(const QStringList &args) Q_UNUSED(args); QString loaders; - QString savers; - QString line = QString(QLatin1String(" %1 - %2\n")); - foreach (Translator::FileFormat format, Translator::registeredFileFormats()) { + QString line(QLatin1String(" %1 - %2\n")); + foreach (Translator::FileFormat format, Translator::registeredFileFormats()) loaders += line.arg(format.extension, -5).arg(format.description); - if (format.fileType != Translator::FileFormat::SourceCode) - savers += line.arg(format.extension, -5).arg(format.description); - } qWarning("%s", qPrintable(QString(QLatin1String("\nUsage:\n" " lconvert [options] <infile> [<infile>...]\n\n" "lconvert is part of Qt's Linguist tool chain. It can be used as a\n" - "stand-alone tool to convert translation data files from one of the\n" - "following input formats\n\n%1\n" - "to one of the following output formats\n\n%2\n" - "If multiple input files are specified the translations are merged with\n" + "stand-alone tool to convert and filter translation data files.\n" + "The following file formats are supported:\n\n%1\n" + "If multiple input files are specified, they are merged with\n" "translations from later files taking precedence.\n\n" "Options:\n" " -h\n" @@ -86,20 +81,20 @@ static int usage(const QStringList &args) " --output-format <outformat>\n" " Specify output format. See -if.\n\n" " --input-codec <codec>\n" - " Specify encoding for .qm and .po input files. Default is 'Latin1'\n" - " for .qm and 'UTF-8' for .po files. UTF-8 is always tried as well for\n" - " .qm, corresponding to the possible use of the trUtf8() function.\n\n" + " Specify encoding for QM and PO input files. Default is 'Latin1'\n" + " for QM and 'UTF-8' for PO files. UTF-8 is always tried as well for\n" + " QM, corresponding to the possible use of the trUtf8() function.\n\n" " --output-codec <codec>\n" - " Specify encoding for .po output files. Default is 'UTF-8'.\n\n" + " Specify encoding for PO output files. Default is 'UTF-8'.\n\n" " --drop-tags <regexp>\n" - " Drop named extra tags when writing 'ts' or 'xlf' files.\n" + " Drop named extra tags when writing TS or XLIFF files.\n" " May be specified repeatedly.\n\n" " --drop-translations\n" " Drop existing translations and reset the status to 'unfinished'.\n" " Note: this implies --no-obsolete.\n\n" " --source-language <language>[_<region>]\n" " Specify/override the language of the source strings. Defaults to\n" - " POSIX if not specified and the file does not name it yet.\n" + " POSIX if not specified and the file does not name it yet.\n\n" " --target-language <language>[_<region>]\n" " Specify/override the language of the translation.\n" " The target language is guessed from the file name if this option\n" @@ -108,6 +103,11 @@ static int usage(const QStringList &args) " Drop obsolete messages.\n\n" " --no-finished\n" " Drop finished messages.\n\n" + " --locations {absolute|relative|none}\n" + " Override how source code references are saved in TS files.\n" + " Default is absolute.\n\n" + " --no-ui-lines\n" + " Drop line numbers from references to UI files.\n\n" " --verbose\n" " be a bit more verbose\n\n" "Long options can be specified with only one leading dash, too.\n\n" @@ -115,7 +115,7 @@ static int usage(const QStringList &args) " 0 on success\n" " 1 on command line parse failures\n" " 2 on read failures\n" - " 3 on write failures\n")).arg(loaders).arg(savers))); + " 3 on write failures\n")).arg(loaders))); return 1; } @@ -140,6 +140,8 @@ int main(int argc, char *argv[]) bool noObsolete = false; bool noFinished = false; bool verbose = false; + bool noUiLines = false; + Translator::LocationsType locations = Translator::DefaultLocations; ConversionData cd; Translator tr; @@ -199,6 +201,19 @@ int main(int argc, char *argv[]) noObsolete = true; } else if (args[i] == QLatin1String("-no-finished")) { noFinished = true; + } else if (args[i] == QLatin1String("-locations")) { + if (++i >= args.size()) + return usage(args); + if (args[i] == QLatin1String("none")) + locations = Translator::NoLocations; + else if (args[i] == QLatin1String("relative")) + locations = Translator::RelativeLocations; + else if (args[i] == QLatin1String("absolute")) + locations = Translator::AbsoluteLocations; + else + return usage(args); + } else if (args[i] == QLatin1String("-no-ui-lines")) { + noUiLines = true; } else if (args[i] == QLatin1String("-verbose")) { verbose = true; } else if (args[i].startsWith(QLatin1Char('-'))) { @@ -243,6 +258,10 @@ int main(int argc, char *argv[]) tr.stripFinishedMessages(); if (dropTranslations) tr.dropTranslations(); + if (noUiLines) + tr.dropUiLines(); + if (locations != Translator::DefaultLocations) + tr.setLocationsType(locations); tr.normalizeTranslations(cd); if (!cd.errors().isEmpty()) { diff --git a/tools/linguist/linguist/batchtranslation.ui b/tools/linguist/linguist/batchtranslation.ui index 9e310e0..66cdbbb 100644 --- a/tools/linguist/linguist/batchtranslation.ui +++ b/tools/linguist/linguist/batchtranslation.ui @@ -104,7 +104,7 @@ <item> <widget class="QCheckBox" name="ckTranslateFinished"> <property name="toolTip"> - <string>Note that the modified entries will be reset to unfinished if 'Set translated entries to finished' above is unchecked.</string> + <string>Note that the modified entries will be reset to unfinished if 'Set translated entries to finished' above is unchecked</string> </property> <property name="text"> <string>Translate also finished entries</string> @@ -189,7 +189,7 @@ <item> <widget class="QLabel" name="label"> <property name="text"> - <string>The batch translator will search through the selected phrase books in the order given above.</string> + <string>The batch translator will search through the selected phrase books in the order given above</string> </property> <property name="wordWrap"> <bool>true</bool> diff --git a/tools/linguist/linguist/globals.cpp b/tools/linguist/linguist/globals.cpp new file mode 100644 index 0000000..085686e --- /dev/null +++ b/tools/linguist/linguist/globals.cpp @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Linguist of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "globals.h" + +const QString &settingsPrefix() +{ + static QString prefix = QString(QLatin1String("%1.%2/")) + .arg((QT_VERSION >> 16) & 0xff) + .arg((QT_VERSION >> 8) & 0xff); + return prefix; +} + +QString settingPath(const char *path) +{ + return settingsPrefix() + QLatin1String(path); +} diff --git a/tools/linguist/linguist/globals.h b/tools/linguist/linguist/globals.h new file mode 100644 index 0000000..91201f6 --- /dev/null +++ b/tools/linguist/linguist/globals.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Linguist of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef GLOBALS_H +#define GLOBALS_H + +#include <QString> + +const QString &settingsPrefix(); +QString settingPath(const char *path); + +#endif // GLOBALS_H diff --git a/tools/linguist/linguist/images/minus.png b/tools/linguist/linguist/images/minus.png Binary files differnew file mode 100644 index 0000000..745b445 --- /dev/null +++ b/tools/linguist/linguist/images/minus.png diff --git a/tools/linguist/linguist/images/plus.png b/tools/linguist/linguist/images/plus.png Binary files differnew file mode 100644 index 0000000..ef43788 --- /dev/null +++ b/tools/linguist/linguist/images/plus.png diff --git a/tools/linguist/linguist/linguist.pro b/tools/linguist/linguist/linguist.pro index cea81d4..314163c 100644 --- a/tools/linguist/linguist/linguist.pro +++ b/tools/linguist/linguist/linguist.pro @@ -25,6 +25,7 @@ SOURCES += \ errorsview.cpp \ finddialog.cpp \ formpreviewview.cpp \ + globals.cpp \ main.cpp \ mainwindow.cpp \ messageeditor.cpp \ @@ -48,6 +49,7 @@ HEADERS += \ errorsview.h \ finddialog.h \ formpreviewview.h \ + globals.h \ mainwindow.h \ messageeditor.h \ messageeditorwidgets.h \ diff --git a/tools/linguist/linguist/linguist.qrc b/tools/linguist/linguist/linguist.qrc index 42cf6e3..a43f0ce 100644 --- a/tools/linguist/linguist/linguist.qrc +++ b/tools/linguist/linguist/linguist.qrc @@ -32,6 +32,8 @@ <file>images/up.png</file> <file>images/down.png</file> <file>images/editdelete.png</file> + <file>images/minus.png</file> + <file>images/plus.png</file> <file>images/win/accelerator.png</file> <file>images/win/book.png</file> <file>images/win/doneandnext.png</file> diff --git a/tools/linguist/linguist/linguist.rc b/tools/linguist/linguist/linguist.rc index 865e021..5ff37ca 100644 --- a/tools/linguist/linguist/linguist.rc +++ b/tools/linguist/linguist/linguist.rc @@ -1 +1,32 @@ +#include "winver.h" + IDI_ICON1 ICON DISCARDABLE "linguist.ico" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGS 0x0L + FILEFLAGSMASK 0x3fL + FILEOS 0x00040004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "CompanyName", "Nokia Corporation and/or its subsidiary(-ies)" + VALUE "FileDescription", "Qt Linguist" + VALUE "FileVersion", "1.0.0.0" + VALUE "LegalCopyright", "Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)." + VALUE "InternalName", "linguist" + VALUE "OriginalFilename", "linguist.exe" + VALUE "ProductName", "Qt Linguist" + VALUE "ProductVersion", "1.0.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END diff --git a/tools/linguist/linguist/main.cpp b/tools/linguist/linguist/main.cpp index 6870ee8..609fc88 100644 --- a/tools/linguist/linguist/main.cpp +++ b/tools/linguist/linguist/main.cpp @@ -40,6 +40,7 @@ ****************************************************************************/ #include "mainwindow.h" +#include "globals.h" #include <QtCore/QFile> #include <QtCore/QLibraryInfo> @@ -80,22 +81,23 @@ int main(int argc, char **argv) } QTranslator translator; - translator.load(QLatin1String("linguist_") + QLocale::system().name(), resourceDir); - app.installTranslator(&translator); - QTranslator qtTranslator; - qtTranslator.load(QLatin1String("qt_") + QLocale::system().name(), resourceDir); - app.installTranslator(&qtTranslator); + QString sysLocale = QLocale::system().name(); + if (translator.load(QLatin1String("linguist_") + sysLocale, resourceDir)) { + app.installTranslator(&translator); + if (qtTranslator.load(QLatin1String("qt_") + sysLocale, resourceDir)) + app.installTranslator(&qtTranslator); + else + app.removeTranslator(&translator); + } app.setOrganizationName(QLatin1String("Trolltech")); app.setApplicationName(QLatin1String("Linguist")); - QString keybase(QString::number( (QT_VERSION >> 16) & 0xff ) + - QLatin1Char('.') + QString::number( (QT_VERSION >> 8) & 0xff ) + QLatin1Char('/') ); QSettings config; QWidget tmp; - tmp.restoreGeometry(config.value(keybase + QLatin1String("Geometry/WindowGeometry")).toByteArray()); + tmp.restoreGeometry(config.value(settingPath("Geometry/WindowGeometry")).toByteArray()); QSplashScreen *splash = 0; int screenId = QApplication::desktop()->screenNumber(tmp.geometry().center()); diff --git a/tools/linguist/linguist/mainwindow.cpp b/tools/linguist/linguist/mainwindow.cpp index d6ebf59..c0ec6a2 100644 --- a/tools/linguist/linguist/mainwindow.cpp +++ b/tools/linguist/linguist/mainwindow.cpp @@ -50,6 +50,7 @@ #include "errorsview.h" #include "finddialog.h" #include "formpreviewview.h" +#include "globals.h" #include "messageeditor.h" #include "messagemodel.h" #include "phrasebookbox.h" @@ -81,6 +82,7 @@ #include <QMenuBar> #include <QMessageBox> #include <QPrintDialog> +#include <QPrinter> #include <QProcess> #include <QRegExp> #include <QSettings> @@ -96,14 +98,6 @@ QT_BEGIN_NAMESPACE static const int MessageMS = 2500; -const QString &settingsPrefix() -{ - static QString prefix = QString(QLatin1String("%1.%2/")) - .arg((QT_VERSION >> 16) & 0xff) - .arg((QT_VERSION >> 8) & 0xff); - return prefix; -} - enum Ending { End_None, End_FullStop, @@ -121,13 +115,12 @@ static bool hasFormPreview(const QString &fileName) static Ending ending(QString str, QLocale::Language lang) { str = str.simplified(); - int ch = 0; - if (!str.isEmpty()) - ch = str.right(1)[0].unicode(); + if (str.isEmpty()) + return End_None; - switch (ch) { + switch (str.at(str.length() - 1).unicode()) { case 0x002e: // full stop - if (str.endsWith(QString(QLatin1String("...")))) + if (str.endsWith(QLatin1String("..."))) return End_Ellipsis; else return End_FullStop; @@ -265,6 +258,7 @@ bool FocusWatcher::eventFilter(QObject *, QEvent *event) MainWindow::MainWindow() : QMainWindow(0, Qt::Window), m_assistantProcess(0), + m_printer(0), m_findMatchCase(Qt::CaseInsensitive), m_findIgnoreAccelerators(true), m_findWhere(DataModel::NoLocation), @@ -488,6 +482,10 @@ MainWindow::MainWindow() readConfig(); m_statistics = 0; + connect(m_ui.actionLengthVariants, SIGNAL(toggled(bool)), + m_messageEditor, SLOT(setLengthVariants(bool))); + m_messageEditor->setLengthVariants(m_ui.actionLengthVariants->isChecked()); + m_focusWatcher = new FocusWatcher(m_messageEditor, this); m_contextView->installEventFilter(m_focusWatcher); m_messageView->installEventFilter(m_focusWatcher); @@ -507,6 +505,7 @@ MainWindow::~MainWindow() qDeleteAll(m_phraseBooks); delete m_dataModel; delete m_statistics; + delete m_printer; } void MainWindow::modelCountChanged() @@ -874,15 +873,22 @@ void MainWindow::releaseAll() releaseInternal(i); } +QPrinter *MainWindow::printer() +{ + if (!m_printer) + m_printer = new QPrinter; + return m_printer; +} + void MainWindow::print() { int pageNum = 0; - QPrintDialog dlg(&m_printer, this); + QPrintDialog dlg(printer(), this); if (dlg.exec()) { QApplication::setOverrideCursor(Qt::WaitCursor); - m_printer.setDocName(m_dataModel->condensedSrcFileNames(true)); + printer()->setDocName(m_dataModel->condensedSrcFileNames(true)); statusBar()->showMessage(tr("Printing...")); - PrintOut pout(&m_printer); + PrintOut pout(printer()); for (int i = 0; i < m_dataModel->contextCount(); ++i) { MultiContextItem *mc = m_dataModel->multiContextItem(i); @@ -1233,11 +1239,11 @@ void MainWindow::printPhraseBook(QAction *action) int pageNum = 0; - QPrintDialog dlg(&m_printer, this); + QPrintDialog dlg(printer(), this); if (dlg.exec()) { - m_printer.setDocName(phraseBook->fileName()); + printer()->setDocName(phraseBook->fileName()); statusBar()->showMessage(tr("Printing...")); - PrintOut pout(&m_printer); + PrintOut pout(printer()); pout.setRule(PrintOut::ThinRule); foreach (const Phrase *p, phraseBook->phrases()) { pout.setGuide(p->source()); @@ -1342,17 +1348,13 @@ void MainWindow::about() QString version = tr("Version %1"); version = version.arg(QLatin1String(QT_VERSION_STR)); - // TODO: Remove this variable for 4.6.0. Must keep this way for 4.5.x due to string freeze. - QString edition; - box.setText(tr("<center><img src=\":/images/splash.png\"/></img><p>%1</p></center>" "<p>Qt Linguist is a tool for adding translations to Qt " "applications.</p>" - "<p>%2</p>" "<p>Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)." "</p><p>The program is provided AS IS with NO WARRANTY OF ANY KIND," " INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A" - " PARTICULAR PURPOSE.</p>").arg(version).arg(edition)); + " PARTICULAR PURPOSE.</p>").arg(version)); box.setWindowTitle(QApplication::translate("AboutDialog", "Qt Linguist")); box.setIcon(QMessageBox::NoIcon); @@ -2370,6 +2372,13 @@ void MainWindow::updateDanger(const MultiDataIndex &index, bool verbose) } QStringList translations = m->translations(); + // Truncated variants are permitted to be "denormalized" + for (int i = 0; i < translations.count(); ++i) { + int sep = translations.at(i).indexOf(QChar(Translator::BinaryVariantSeparator)); + if (sep >= 0) + translations[i].truncate(sep); + } + if (m_ui.actionAccelerators->isChecked()) { bool sk = !QKeySequence::mnemonic(source).isEmpty(); bool tk = true; @@ -2498,25 +2507,26 @@ void MainWindow::updateDanger(const MultiDataIndex &index, bool verbose) void MainWindow::readConfig() { - QString keybase = settingsPrefix(); QSettings config; QRect r(pos(), size()); - restoreGeometry(config.value(keybase + QLatin1String("Geometry/WindowGeometry")).toByteArray()); - restoreState(config.value(keybase + QLatin1String("MainWindowState")).toByteArray()); + restoreGeometry(config.value(settingPath("Geometry/WindowGeometry")).toByteArray()); + restoreState(config.value(settingPath("MainWindowState")).toByteArray()); m_ui.actionAccelerators->setChecked( - config.value(keybase + QLatin1String("Validators/Accelerator"), true).toBool()); + config.value(settingPath("Validators/Accelerator"), true).toBool()); m_ui.actionEndingPunctuation->setChecked( - config.value(keybase + QLatin1String("Validators/EndingPunctuation"), true).toBool()); + config.value(settingPath("Validators/EndingPunctuation"), true).toBool()); m_ui.actionPhraseMatches->setChecked( - config.value(keybase + QLatin1String("Validators/PhraseMatch"), true).toBool()); + config.value(settingPath("Validators/PhraseMatch"), true).toBool()); m_ui.actionPlaceMarkerMatches->setChecked( - config.value(keybase + QLatin1String("Validators/PlaceMarkers"), true).toBool()); + config.value(settingPath("Validators/PlaceMarkers"), true).toBool()); + m_ui.actionLengthVariants->setChecked( + config.value(settingPath("Options/LengthVariants"), false).toBool()); recentFiles().readConfig(); - int size = config.beginReadArray(keybase + QLatin1String("OpenedPhraseBooks")); + int size = config.beginReadArray(settingPath("OpenedPhraseBooks")); for (int i = 0; i < size; ++i) { config.setArrayIndex(i); openPhraseBook(config.value(QLatin1String("FileName")).toString()); @@ -2526,23 +2536,24 @@ void MainWindow::readConfig() void MainWindow::writeConfig() { - QString keybase = settingsPrefix(); QSettings config; - config.setValue(keybase + QLatin1String("Geometry/WindowGeometry"), + config.setValue(settingPath("Geometry/WindowGeometry"), saveGeometry()); - config.setValue(keybase + QLatin1String("Validators/Accelerator"), + config.setValue(settingPath("Validators/Accelerator"), m_ui.actionAccelerators->isChecked()); - config.setValue(keybase + QLatin1String("Validators/EndingPunctuation"), + config.setValue(settingPath("Validators/EndingPunctuation"), m_ui.actionEndingPunctuation->isChecked()); - config.setValue(keybase + QLatin1String("Validators/PhraseMatch"), + config.setValue(settingPath("Validators/PhraseMatch"), m_ui.actionPhraseMatches->isChecked()); - config.setValue(keybase + QLatin1String("Validators/PlaceMarkers"), + config.setValue(settingPath("Validators/PlaceMarkers"), m_ui.actionPlaceMarkerMatches->isChecked()); - config.setValue(keybase + QLatin1String("MainWindowState"), + config.setValue(settingPath("Options/LengthVariants"), + m_ui.actionLengthVariants->isChecked()); + config.setValue(settingPath("MainWindowState"), saveState()); recentFiles().writeConfig(); - config.beginWriteArray(keybase + QLatin1String("OpenedPhraseBooks"), + config.beginWriteArray(settingPath("OpenedPhraseBooks"), m_phraseBooks.size()); for (int i = 0; i < m_phraseBooks.size(); ++i) { config.setArrayIndex(i); diff --git a/tools/linguist/linguist/mainwindow.h b/tools/linguist/linguist/mainwindow.h index d3a192f..071f501 100644 --- a/tools/linguist/linguist/mainwindow.h +++ b/tools/linguist/linguist/mainwindow.h @@ -51,7 +51,6 @@ #include <QtCore/QLocale> #include <QtGui/QMainWindow> -#include <QtGui/QPrinter> QT_BEGIN_NAMESPACE @@ -60,6 +59,7 @@ class QAction; class QDialog; class QLabel; class QMenu; +class QPrinter; class QProcess; class QIcon; class QSortFilterProxyModel; @@ -79,8 +79,6 @@ class Statistics; class TranslateDialog; class TranslationSettingsDialog; -const QString &settingsPrefix(); - class MainWindow : public QMainWindow { Q_OBJECT @@ -202,6 +200,8 @@ private: void releaseInternal(int model); void saveInternal(int model); + QPrinter *printer(); + // FIXME: move to DataModel void updateDanger(const MultiDataIndex &index, bool verbose); @@ -228,7 +228,7 @@ private: QList<QHash<QString, QList<Phrase *> > > m_phraseDict; QList<PhraseBook *> m_phraseBooks; QMap<QAction *, PhraseBook *> m_phraseBookMenu[3]; - QPrinter m_printer; + QPrinter *m_printer; FindDialog *m_findDialog; QString m_findText; diff --git a/tools/linguist/linguist/mainwindow.ui b/tools/linguist/linguist/mainwindow.ui index 35bbf40..2856378 100644 --- a/tools/linguist/linguist/mainwindow.ui +++ b/tools/linguist/linguist/mainwindow.ui @@ -60,7 +60,7 @@ <x>0</x> <y>0</y> <width>673</width> - <height>30</height> + <height>28</height> </rect> </property> <widget class="QMenu" name="menuPhrases"> @@ -116,6 +116,7 @@ <addaction name="actionResetSorting"/> <addaction name="actionDisplayGuesses"/> <addaction name="actionStatistics"/> + <addaction name="actionLengthVariants"/> <addaction name="separator"/> <addaction name="menuToolbars"/> <addaction name="menuViewViews"/> @@ -401,7 +402,7 @@ <string>&Prev Unfinished</string> </property> <property name="toolTip"> - <string>Previous unfinished item.</string> + <string>Previous unfinished item</string> </property> <property name="whatsThis"> <string>Move to the previous unfinished item.</string> @@ -418,7 +419,7 @@ <string>&Next Unfinished</string> </property> <property name="toolTip"> - <string>Next unfinished item.</string> + <string>Next unfinished item</string> </property> <property name="whatsThis"> <string>Move to the next unfinished item.</string> @@ -435,7 +436,7 @@ <string>P&rev</string> </property> <property name="toolTip"> - <string>Move to previous item.</string> + <string>Move to previous item</string> </property> <property name="whatsThis"> <string>Move to the previous item.</string> @@ -452,7 +453,7 @@ <string>Ne&xt</string> </property> <property name="toolTip"> - <string>Next item.</string> + <string>Next item</string> </property> <property name="whatsThis"> <string>Move to the next item.</string> @@ -472,7 +473,7 @@ <string>&Done and Next</string> </property> <property name="toolTip"> - <string>Mark item as done and move to the next unfinished item.</string> + <string>Mark item as done and move to the next unfinished item</string> </property> <property name="whatsThis"> <string>Mark this item as done and move to the next unfinished item.</string> @@ -492,7 +493,7 @@ <string>Copy from source text</string> </property> <property name="toolTip"> - <string>Copies the source text into the translation field.</string> + <string>Copies the source text into the translation field</string> </property> <property name="whatsThis"> <string>Copies the source text into the translation field.</string> @@ -512,7 +513,7 @@ <string>&Accelerators</string> </property> <property name="toolTip"> - <string>Toggle the validity check of accelerators.</string> + <string>Toggle the validity check of accelerators</string> </property> <property name="whatsThis"> <string>Toggle the validity check of accelerators, i.e. whether the number of ampersands in the source and translation text is the same. If the check fails, a message is shown in the warnings window.</string> @@ -529,7 +530,7 @@ <string>&Ending Punctuation</string> </property> <property name="toolTip"> - <string>Toggle the validity check of ending punctuation.</string> + <string>Toggle the validity check of ending punctuation</string> </property> <property name="whatsThis"> <string>Toggle the validity check of ending punctuation. If the check fails, a message is shown in the warnings window.</string> @@ -546,7 +547,7 @@ <string>&Phrase matches</string> </property> <property name="toolTip"> - <string>Toggle checking that phrase suggestions are used.</string> + <string>Toggle checking that phrase suggestions are used</string> </property> <property name="whatsThis"> <string>Toggle checking that phrase suggestions are used. If the check fails, a message is shown in the warnings window.</string> @@ -563,7 +564,7 @@ <string>Place &Marker Matches</string> </property> <property name="toolTip"> - <string>Toggle the validity check of place markers.</string> + <string>Toggle the validity check of place markers</string> </property> <property name="whatsThis"> <string>Toggle the validity check of place markers, i.e. whether %1, %2, ... are used consistently in the source text and translation text. If the check fails, a message is shown in the warnings window.</string> @@ -746,7 +747,7 @@ <string>Release As...</string> </property> <property name="whatsThis"> - <string>Create a Qt message file suitable for released applications from the current message file. The filename will automatically be determined from the name of the .ts file.</string> + <string>Create a Qt message file suitable for released applications from the current message file. The filename will automatically be determined from the name of the TS file.</string> </property> </action> <action name="actionFile"> @@ -877,6 +878,14 @@ <string>Ctrl+W</string> </property> </action> + <action name="actionLengthVariants"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Length Variants</string> + </property> + </action> </widget> <resources/> <connections/> diff --git a/tools/linguist/linguist/messageeditor.cpp b/tools/linguist/linguist/messageeditor.cpp index 7748f71..05fc53f 100644 --- a/tools/linguist/linguist/messageeditor.cpp +++ b/tools/linguist/linguist/messageeditor.cpp @@ -39,7 +39,7 @@ ** ****************************************************************************/ -/* TRANSLATOR MsgEdit +/* TRANSLATOR MessageEditor This is the right panel of the main window. */ @@ -88,13 +88,13 @@ MessageEditor::MessageEditor(MultiDataModel *dataModel, QMainWindow *parent) m_dataModel(dataModel), m_currentModel(-1), m_currentNumerus(-1), + m_lengthVariants(false), m_undoAvail(false), m_redoAvail(false), m_cutAvail(false), m_copyAvail(false), - m_sourceSelected(false), - m_pluralSourceSelected(false), - m_currentSelected(false) + m_selectionHolder(0), + m_focusWidget(0) { setObjectName(QLatin1String("scroll area")); @@ -122,6 +122,9 @@ MessageEditor::MessageEditor(MultiDataModel *dataModel, QMainWindow *parent) connect(m_dataModel, SIGNAL(languageChanged(int)), SLOT(setTargetLanguage(int))); + m_tabOrderTimer.setSingleShot(true); + connect(&m_tabOrderTimer, SIGNAL(timeout()), SLOT(reallyFixTabOrder())); + clipboardChanged(); setWhatsThis(tr("This whole panel allows you to view and edit " @@ -145,12 +148,14 @@ void MessageEditor::setupEditorPage() m_source = new FormWidget(tr("Source text"), false); m_source->setHideWhenEmpty(true); m_source->setWhatsThis(tr("This area shows the source text.")); - connect(m_source, SIGNAL(selectionChanged()), SLOT(selectionChanged())); + connect(m_source, SIGNAL(selectionChanged(QTextEdit *)), + SLOT(selectionChanged(QTextEdit *))); m_pluralSource = new FormWidget(tr("Source text (Plural)"), false); m_pluralSource->setHideWhenEmpty(true); m_pluralSource->setWhatsThis(tr("This area shows the plural form of the source text.")); - connect(m_pluralSource, SIGNAL(selectionChanged()), SLOT(selectionChanged())); + connect(m_pluralSource, SIGNAL(selectionChanged(QTextEdit *)), + SLOT(selectionChanged(QTextEdit *))); m_commentText = new FormWidget(tr("Developer comments"), false); m_commentText->setHideWhenEmpty(true); @@ -217,10 +222,13 @@ void MessageEditor::messageModelAppended() ed.transCommentText->setWhatsThis(tr("Here you can enter comments for your own use." " They have no effect on the translated applications.") ); ed.transCommentText->getEditor()->installEventFilter(this); - connect(ed.transCommentText, SIGNAL(selectionChanged()), SLOT(selectionChanged())); - connect(ed.transCommentText, SIGNAL(textChanged()), SLOT(emitTranslatorCommentChanged())); - connect(ed.transCommentText, SIGNAL(textChanged()), SLOT(resetHoverSelection())); + connect(ed.transCommentText, SIGNAL(selectionChanged(QTextEdit *)), + SLOT(selectionChanged(QTextEdit *))); + connect(ed.transCommentText, SIGNAL(textChanged(QTextEdit *)), + SLOT(emitTranslatorCommentChanged(QTextEdit *))); + connect(ed.transCommentText, SIGNAL(textChanged(QTextEdit *)), SLOT(resetHoverSelection())); connect(ed.transCommentText, SIGNAL(cursorPositionChanged()), SLOT(resetHoverSelection())); + fixTabOrder(); QBoxLayout *box = new QVBoxLayout(ed.container); box->setMargin(5); box->addWidget(ed.transCommentText); @@ -251,7 +259,7 @@ void MessageEditor::messageModelDeleted(int model) if (m_currentModel >= 0) { if (m_currentNumerus >= m_editors[m_currentModel].transTexts.size()) m_currentNumerus = m_editors[m_currentModel].transTexts.size() - 1; - activeEditor()->getEditor()->setFocus(); + activeEditor()->setFocus(); } else { m_currentNumerus = -1; } @@ -266,26 +274,64 @@ void MessageEditor::messageModelDeleted(int model) void MessageEditor::addPluralForm(int model, const QString &label, bool writable) { - FormWidget *transEditor = new FormWidget(label, true); - QFont font; - font.setPointSize(static_cast<int>(m_editors[model].fontSize)); - transEditor->getEditor()->setFont(font); + FormMultiWidget *transEditor = new FormMultiWidget(label); + connect(transEditor, SIGNAL(editorCreated(QTextEdit *)), SLOT(editorCreated(QTextEdit *))); transEditor->setEditingEnabled(writable); transEditor->setHideWhenEmpty(!writable); if (!m_editors[model].transTexts.isEmpty()) transEditor->setVisible(false); + transEditor->setMultiEnabled(m_lengthVariants); static_cast<QBoxLayout *>(m_editors[model].container->layout())->insertWidget( m_editors[model].transTexts.count(), transEditor); - transEditor->getEditor()->installEventFilter(this); - connect(transEditor, SIGNAL(selectionChanged()), SLOT(selectionChanged())); - connect(transEditor, SIGNAL(textChanged()), SLOT(emitTranslationChanged())); - connect(transEditor, SIGNAL(textChanged()), SLOT(resetHoverSelection())); + connect(transEditor, SIGNAL(selectionChanged(QTextEdit *)), + SLOT(selectionChanged(QTextEdit *))); + connect(transEditor, SIGNAL(textChanged(QTextEdit *)), + SLOT(emitTranslationChanged(QTextEdit *))); + connect(transEditor, SIGNAL(textChanged(QTextEdit *)), SLOT(resetHoverSelection())); connect(transEditor, SIGNAL(cursorPositionChanged()), SLOT(resetHoverSelection())); m_editors[model].transTexts << transEditor; } +void MessageEditor::editorCreated(QTextEdit *te) +{ + FormMultiWidget *snd = static_cast<FormMultiWidget *>(sender()); + for (int model = 0; ; ++model) { + MessageEditorData med = m_editors.at(model); + if (med.transTexts.contains(snd)) { + QFont font; + font.setPointSize(static_cast<int>(med.fontSize)); + te->setFont(font); + + te->installEventFilter(this); + + fixTabOrder(); + return; + } + } +} + +void MessageEditor::fixTabOrder() +{ + m_tabOrderTimer.start(0); +} + +void MessageEditor::reallyFixTabOrder() +{ + QWidget *prev = this; + foreach (const MessageEditorData &med, m_editors) { + foreach (FormMultiWidget *fmw, med.transTexts) + foreach (QTextEdit *te, fmw->getEditors()) { + setTabOrder(prev, te); + prev = te; + } + QTextEdit *te = med.transCommentText->getEditor(); + setTabOrder(prev, te); + prev = te; + } +} + /*! internal Returns all translations for an item. The number of translations is dependent on if we have a plural form or not. @@ -301,69 +347,53 @@ QStringList MessageEditor::translations(int model) const return translations; } -static bool clearFormSelection(FormWidget *fw, FormWidget *te) +static void clearSelection(QTextEdit *t) { - if (fw != te) { - QTextEdit *t = fw->getEditor(); - bool oldBlockState = t->blockSignals(true); - QTextCursor c = t->textCursor(); - c.clearSelection(); - t->setTextCursor(c); - t->blockSignals(oldBlockState); - return true; - } - return false; + bool oldBlockState = t->blockSignals(true); + QTextCursor c = t->textCursor(); + c.clearSelection(); + t->setTextCursor(c); + t->blockSignals(oldBlockState); } -// Clear the selection for all textedits except the sender -void MessageEditor::selectionChanged() +void MessageEditor::selectionChanged(QTextEdit *te) { - if (!resetSelection(qobject_cast<FormWidget *>(sender()))) + if (te != m_selectionHolder) { + if (m_selectionHolder) + clearSelection(m_selectionHolder); + m_selectionHolder = (te->textCursor().hasSelection() ? te : 0); updateCanCutCopy(); + } } -bool MessageEditor::resetHoverSelection(FormWidget *fw) +void MessageEditor::resetHoverSelection() { - if (m_sourceSelected) { - if (clearFormSelection(m_source, fw)) { - updateCanCutCopy(); - return true; - } - } else if (m_pluralSourceSelected) { - if (clearFormSelection(m_pluralSource, fw)) { - updateCanCutCopy(); - return true; - } - } - return false; + if (m_selectionHolder && + (m_selectionHolder == m_source->getEditor() + || m_selectionHolder == m_pluralSource->getEditor())) + resetSelection(); } -bool MessageEditor::resetSelection(FormWidget *fw) +void MessageEditor::resetSelection() { - if (resetHoverSelection(fw)) - return true; - if (m_currentSelected) { - MessageEditorData &ed = m_editors[m_currentModel]; - FormWidget *cfw = (m_currentNumerus < 0) ? ed.transCommentText - : ed.transTexts[m_currentNumerus]; - if (clearFormSelection(cfw, fw)) { - updateCanCutCopy(); - return true; - } + if (m_selectionHolder) { + clearSelection(m_selectionHolder); + m_selectionHolder = 0; + updateCanCutCopy(); } - return false; } void MessageEditor::activeModelAndNumerus(int *model, int *numerus) const { for (int j = 0; j < m_editors.count(); ++j) { for (int i = 0; i < m_editors[j].transTexts.count(); ++i) - if (m_editors[j].transTexts[i]->getEditor()->hasFocus()) { - *model = j; - *numerus = i; - return; - } - if (m_editors[j].transCommentText->getEditor()->hasFocus()) { + foreach (QTextEdit *te, m_editors[j].transTexts[i]->getEditors()) + if (m_focusWidget == te) { + *model = j; + *numerus = i; + return; + } + if (m_focusWidget == m_editors[j].transCommentText->getEditor()) { *model = j; *numerus = -1; return; @@ -373,43 +403,46 @@ void MessageEditor::activeModelAndNumerus(int *model, int *numerus) const *numerus = -1; } -FormWidget *MessageEditor::activeTranslation() const +QTextEdit *MessageEditor::activeTranslation() const { if (m_currentNumerus < 0) return 0; - return m_editors[m_currentModel].transTexts[m_currentNumerus]; + foreach (QTextEdit *te, m_editors[m_currentModel].transTexts[m_currentNumerus]->getEditors()) + if (te->hasFocus()) + return te; + return 0; // This cannot happen } -FormWidget *MessageEditor::activeOr1stTranslation() const +QTextEdit *MessageEditor::activeOr1stTranslation() const { if (m_currentNumerus < 0) { for (int i = 0; i < m_editors.size(); ++i) if (m_editors[i].container->isVisible() - && !m_editors[i].transTexts[0]->getEditor()->isReadOnly()) - return m_editors[i].transTexts[0]; + && !m_editors[i].transTexts.first()->getEditors().first()->isReadOnly()) + return m_editors[i].transTexts.first()->getEditors().first(); return 0; } - return m_editors[m_currentModel].transTexts[m_currentNumerus]; + return activeTranslation(); } -FormWidget *MessageEditor::activeTransComment() const +QTextEdit *MessageEditor::activeTransComment() const { if (m_currentModel < 0 || m_currentNumerus >= 0) return 0; - return m_editors[m_currentModel].transCommentText; + return m_editors[m_currentModel].transCommentText->getEditor(); } -FormWidget *MessageEditor::activeEditor() const +QTextEdit *MessageEditor::activeEditor() const { - if (FormWidget *fw = activeTransComment()) - return fw; + if (QTextEdit *te = activeTransComment()) + return te; return activeTranslation(); } -FormWidget *MessageEditor::activeOr1stEditor() const +QTextEdit *MessageEditor::activeOr1stEditor() const { - if (FormWidget *fw = activeTransComment()) - return fw; + if (QTextEdit *te = activeTransComment()) + return te; return activeOr1stTranslation(); } @@ -417,25 +450,14 @@ void MessageEditor::setTargetLanguage(int model) { const QStringList &numerusForms = m_dataModel->model(model)->numerusForms(); const QString &langLocalized = m_dataModel->model(model)->localizedLanguage(); - bool added = false; for (int i = 0; i < numerusForms.count(); ++i) { const QString &label = tr("%1 translation (%2)").arg(langLocalized, numerusForms[i]); if (!i) m_editors[model].firstForm = label; - if (i >= m_editors[model].transTexts.count()) { + if (i >= m_editors[model].transTexts.count()) addPluralForm(model, label, m_dataModel->isModelWritable(model)); - QWidget *prev; - if (i > 0) - prev = m_editors[model].transTexts[i - 1]->getEditor(); - else if (model) - prev = m_editors[model - 1].transCommentText->getEditor(); - else - prev = this; - setTabOrder(prev, m_editors[model].transTexts[i]->getEditor()); - added = true; - } else { + else m_editors[model].transTexts[i]->setLabel(label); - } m_editors[model].transTexts[i]->setVisible(!i || m_editors[model].pluralEditMode); m_editors[model].transTexts[i]->setWhatsThis( tr("This is where you can enter or modify" @@ -445,16 +467,15 @@ void MessageEditor::setTargetLanguage(int model) delete m_editors[model].transTexts.takeLast(); m_editors[model].invariantForm = tr("%1 translation").arg(langLocalized); m_editors[model].transCommentText->setLabel(tr("%1 translator comments").arg(langLocalized)); - if (added) - setTabOrder(m_editors[model].transTexts.last()->getEditor(), m_editors[model].transCommentText->getEditor()); } MessageEditorData *MessageEditor::modelForWidget(const QObject *o) { for (int j = 0; j < m_editors.count(); ++j) { for (int i = 0; i < m_editors[j].transTexts.count(); ++i) - if (m_editors[j].transTexts[i]->getEditor() == o) - return &m_editors[j]; + foreach (QTextEdit *te, m_editors[j].transTexts[i]->getEditors()) + if (te == o) + return &m_editors[j]; if (m_editors[j].transCommentText->getEditor() == o) return &m_editors[j]; } @@ -466,7 +487,8 @@ static bool applyFont(MessageEditorData *med) QFont font; font.setPointSize(static_cast<int>(med->fontSize)); for (int i = 0; i < med->transTexts.count(); ++i) - med->transTexts[i]->getEditor()->setFont(font); + foreach (QTextEdit *te, med->transTexts[i]->getEditors()) + te->setFont(font); med->transCommentText->getEditor()->setFont(font); return true; } @@ -529,22 +551,39 @@ bool MessageEditor::eventFilter(QObject *o, QEvent *e) return decFont(modelForWidget(o)); } } else if (e->type() == QEvent::FocusIn) { - int model, numerus; - activeModelAndNumerus(&model, &numerus); - if (model != m_currentModel || numerus != m_currentNumerus) { - resetSelection(); - m_currentModel = model; - m_currentNumerus = numerus; - emit activeModelChanged(activeModel()); - updateBeginFromSource(); - updateUndoRedo(); - updateCanPaste(); - } + QWidget *widget = static_cast<QWidget *>(o); + if (widget != m_focusWidget) + trackFocus(widget); } return QScrollArea::eventFilter(o, e); } +void MessageEditor::grabFocus(QWidget *widget) +{ + if (widget != m_focusWidget) { + widget->setFocus(); + trackFocus(widget); + } +} + +void MessageEditor::trackFocus(QWidget *widget) +{ + m_focusWidget = widget; + + int model, numerus; + activeModelAndNumerus(&model, &numerus); + if (model != m_currentModel || numerus != m_currentNumerus) { + resetSelection(); + m_currentModel = model; + m_currentNumerus = numerus; + emit activeModelChanged(activeModel()); + updateBeginFromSource(); + updateUndoRedo(); + updateCanPaste(); + } +} + void MessageEditor::showNothing() { m_source->clearTranslation(); @@ -552,7 +591,7 @@ void MessageEditor::showNothing() m_commentText->clearTranslation(); for (int j = 0; j < m_editors.count(); ++j) { setEditingEnabled(j, false); - foreach (FormWidget *widget, m_editors[j].transTexts) + foreach (FormMultiWidget *widget, m_editors[j].transTexts) widget->clearTranslation(); m_editors[j].transCommentText->clearTranslation(); } @@ -639,7 +678,7 @@ void MessageEditor::setTranslation(int model, const QString &translation, int nu MessageEditorData &ed = m_editors[model]; if (numerus >= ed.transTexts.count()) numerus = 0; - FormWidget *transForm = ed.transTexts[numerus]; + FormMultiWidget *transForm = ed.transTexts[numerus]; transForm->setTranslation(translation, false); updateBeginFromSource(); @@ -654,8 +693,8 @@ void MessageEditor::setTranslation(int latestModel, const QString &translation) latestModel = m_currentModel; numerus = m_currentNumerus; } - FormWidget *transForm = m_editors[latestModel].transTexts[numerus]; - transForm->getEditor()->setFocus(); + FormMultiWidget *transForm = m_editors[latestModel].transTexts[numerus]; + transForm->getEditors().first()->setFocus(); transForm->setTranslation(translation, true); updateBeginFromSource(); @@ -664,29 +703,37 @@ void MessageEditor::setTranslation(int latestModel, const QString &translation) void MessageEditor::setEditingEnabled(int model, bool enabled) { MessageEditorData &ed = m_editors[model]; - foreach (FormWidget *widget, ed.transTexts) + foreach (FormMultiWidget *widget, ed.transTexts) widget->setEditingEnabled(enabled); ed.transCommentText->setEditingEnabled(enabled); updateCanPaste(); } +void MessageEditor::setLengthVariants(bool on) +{ + m_lengthVariants = on; + foreach (const MessageEditorData &ed, m_editors) + foreach (FormMultiWidget *widget, ed.transTexts) + widget->setMultiEnabled(on); +} + void MessageEditor::undo() { - activeEditor()->getEditor()->document()->undo(); + activeEditor()->document()->undo(); } void MessageEditor::redo() { - activeEditor()->getEditor()->document()->redo(); + activeEditor()->document()->redo(); } void MessageEditor::updateUndoRedo() { bool newUndoAvail = false; bool newRedoAvail = false; - if (FormWidget *fw = activeEditor()) { - QTextDocument *doc = fw->getEditor()->document(); + if (QTextEdit *te = activeEditor()) { + QTextDocument *doc = te->document(); newUndoAvail = doc->isUndoAvailable(); newRedoAvail = doc->isRedoAvailable(); } @@ -704,18 +751,12 @@ void MessageEditor::updateUndoRedo() void MessageEditor::cut() { - QTextEdit *editor = activeEditor()->getEditor(); - if (editor->textCursor().hasSelection()) - editor->cut(); + m_selectionHolder->cut(); } void MessageEditor::copy() { - QTextEdit *te; - if ((te = m_source->getEditor())->textCursor().hasSelection() - || (te = m_pluralSource->getEditor())->textCursor().hasSelection() - || (te = activeEditor()->getEditor())->textCursor().hasSelection()) - te->copy(); + m_selectionHolder->copy(); } void MessageEditor::updateCanCutCopy() @@ -723,19 +764,9 @@ void MessageEditor::updateCanCutCopy() bool newCopyState = false; bool newCutState = false; - m_sourceSelected = m_source->getEditor()->textCursor().hasSelection(); - m_pluralSourceSelected = m_pluralSource->getEditor()->textCursor().hasSelection(); - m_currentSelected = false; - - if (m_sourceSelected || m_pluralSourceSelected) { + if (m_selectionHolder) { newCopyState = true; - } else if (FormWidget *fw = activeEditor()) { - QTextEdit *te = fw->getEditor(); - if (te->textCursor().hasSelection()) { - m_currentSelected = true; - newCopyState = true; - newCutState = !te->isReadOnly(); - } + newCutState = !m_selectionHolder->isReadOnly(); } if (newCopyState != m_copyAvail) { @@ -751,14 +782,14 @@ void MessageEditor::updateCanCutCopy() void MessageEditor::paste() { - activeEditor()->getEditor()->paste(); + activeEditor()->paste(); } void MessageEditor::updateCanPaste() { - FormWidget *fw; + QTextEdit *te; emit pasteAvailable(!m_clipboardEmpty - && (fw = activeEditor()) && !fw->getEditor()->isReadOnly()); + && (te = activeEditor()) && !te->isReadOnly()); } void MessageEditor::clipboardChanged() @@ -773,24 +804,23 @@ void MessageEditor::selectAll() // make sure we don't select the selection of a translator textedit, // if we really want the source text editor to be selected. QTextEdit *te; - FormWidget *fw; if ((te = m_source->getEditor())->underMouse() || (te = m_pluralSource->getEditor())->underMouse() - || ((fw = activeEditor()) && (te = fw->getEditor())->hasFocus())) + || ((te = activeEditor()) && te->hasFocus())) te->selectAll(); } -void MessageEditor::emitTranslationChanged() +void MessageEditor::emitTranslationChanged(QTextEdit *widget) { - static_cast<FormWidget *>(sender())->getEditor()->setFocus(); // DND proofness + grabFocus(widget); // DND proofness updateBeginFromSource(); updateUndoRedo(); emit translationChanged(translations(m_currentModel)); } -void MessageEditor::emitTranslatorCommentChanged() +void MessageEditor::emitTranslatorCommentChanged(QTextEdit *widget) { - static_cast<FormWidget *>(sender())->getEditor()->setFocus(); // DND proofness + grabFocus(widget); // DND proofness updateUndoRedo(); emit translatorCommentChanged(m_editors[m_currentModel].transCommentText->getTranslation()); } @@ -798,11 +828,9 @@ void MessageEditor::emitTranslatorCommentChanged() void MessageEditor::updateBeginFromSource() { bool overwrite = false; - if (FormWidget *transForm = activeTranslation()) { - QTextEdit *activeEditor = transForm->getEditor(); + if (QTextEdit *activeEditor = activeTranslation()) overwrite = !activeEditor->isReadOnly() && activeEditor->toPlainText().trimmed().isEmpty(); - } emit beginFromSourceAvailable(overwrite); } @@ -817,8 +845,8 @@ void MessageEditor::beginFromSource() void MessageEditor::setEditorFocus() { if (!widget()->hasFocus()) - if (FormWidget *transForm = activeOr1stEditor()) - transForm->getEditor()->setFocus(); + if (QTextEdit *activeEditor = activeOr1stEditor()) + activeEditor->setFocus(); } void MessageEditor::setEditorFocus(int model) @@ -828,12 +856,13 @@ void MessageEditor::setEditorFocus(int model) resetSelection(); m_currentNumerus = -1; m_currentModel = -1; + m_focusWidget = 0; emit activeModelChanged(activeModel()); updateBeginFromSource(); updateUndoRedo(); updateCanPaste(); } else { - m_editors[model].transTexts[0]->getEditor()->setFocus(); + m_editors[model].transTexts.first()->getEditors().first()->setFocus(); } } } @@ -844,7 +873,7 @@ bool MessageEditor::focusNextUnfinished(int start) if (m_dataModel->isModelWritable(j)) if (MessageItem *item = m_dataModel->messageItem(m_currentIndex, j)) if (item->type() == TranslatorMessage::Unfinished) { - m_editors[j].transTexts[0]->getEditor()->setFocus(); + m_editors[j].transTexts.first()->getEditors().first()->setFocus(); return true; } return false; diff --git a/tools/linguist/linguist/messageeditor.h b/tools/linguist/linguist/messageeditor.h index 51e9979..b7d2552 100644 --- a/tools/linguist/linguist/messageeditor.h +++ b/tools/linguist/linguist/messageeditor.h @@ -45,6 +45,7 @@ #include "messagemodel.h" #include <QtCore/QLocale> +#include <QtCore/QTimer> #include <QtGui/QFrame> #include <QtGui/QScrollArea> @@ -58,11 +59,12 @@ class QTextEdit; class MessageEditor; class FormatTextEdit; class FormWidget; +class FormMultiWidget; struct MessageEditorData { QWidget *container; FormWidget *transCommentText; - QList<FormWidget*> transTexts; + QList<FormMultiWidget *> transTexts; QString invariantForm; QString firstForm; float fontSize; @@ -108,30 +110,35 @@ public slots: void beginFromSource(); void setEditorFocus(); void setTranslation(int latestModel, const QString &translation); + void setLengthVariants(bool on); private slots: - void selectionChanged(); - bool resetHoverSelection(FormWidget *fw = 0); - void emitTranslationChanged(); - void emitTranslatorCommentChanged(); + void editorCreated(QTextEdit *); + void selectionChanged(QTextEdit *); + void resetHoverSelection(); + void emitTranslationChanged(QTextEdit *); + void emitTranslatorCommentChanged(QTextEdit *); void updateCanPaste(); void clipboardChanged(); void messageModelAppended(); void messageModelDeleted(int model); void allModelsDeleted(); void setTargetLanguage(int model); + void reallyFixTabOrder(); private: void setupEditorPage(); void setEditingEnabled(int model, bool enabled); bool focusNextUnfinished(int start); - bool resetSelection(FormWidget *fw = 0); + void resetSelection(); + void grabFocus(QWidget *widget); + void trackFocus(QWidget *widget); void activeModelAndNumerus(int *model, int *numerus) const; - FormWidget *activeTranslation() const; - FormWidget *activeOr1stTranslation() const; - FormWidget *activeTransComment() const; - FormWidget *activeEditor() const; - FormWidget *activeOr1stEditor() const; + QTextEdit *activeTranslation() const; + QTextEdit *activeOr1stTranslation() const; + QTextEdit *activeTransComment() const; + QTextEdit *activeEditor() const; + QTextEdit *activeOr1stEditor() const; MessageEditorData *modelForWidget(const QObject *o); int activeTranslationNumerus() const; QStringList translations(int model) const; @@ -139,6 +146,7 @@ private: void updateUndoRedo(); void updateCanCutCopy(); void addPluralForm(int model, const QString &label, bool writable); + void fixTabOrder(); QPalette paletteForModel(int model) const; MultiDataModel *m_dataModel; @@ -147,21 +155,24 @@ private: int m_currentModel; int m_currentNumerus; + bool m_lengthVariants; + bool m_undoAvail; bool m_redoAvail; bool m_cutAvail; bool m_copyAvail; - bool m_sourceSelected; - bool m_pluralSourceSelected; - bool m_currentSelected; bool m_clipboardEmpty; + QTextEdit *m_selectionHolder; + QWidget *m_focusWidget; QBoxLayout *m_layout; FormWidget *m_source; FormWidget *m_pluralSource; FormWidget *m_commentText; QList<MessageEditorData> m_editors; + + QTimer m_tabOrderTimer; }; QT_END_NAMESPACE diff --git a/tools/linguist/linguist/messageeditorwidgets.cpp b/tools/linguist/linguist/messageeditorwidgets.cpp index 12d906d..c43bb74 100644 --- a/tools/linguist/linguist/messageeditorwidgets.cpp +++ b/tools/linguist/linguist/messageeditorwidgets.cpp @@ -42,6 +42,8 @@ #include "messageeditorwidgets.h" #include "messagehighlighter.h" +#include <translator.h> + #include <QAbstractTextDocumentLayout> #include <QAction> #include <QApplication> @@ -49,10 +51,12 @@ #include <QDebug> #include <QLayout> #include <QMenu> +#include <QMessageBox> #include <QPainter> #include <QScrollArea> #include <QTextBlock> #include <QTextDocumentFragment> +#include <QToolButton> #include <QVBoxLayout> QT_BEGIN_NAMESPACE @@ -145,16 +149,15 @@ void FormatTextEdit::setEditable(bool editable) void FormatTextEdit::setPlainText(const QString &text, bool userAction) { - bool oldBlockState = false; if (!userAction) { // Prevent contentsChanged signal - oldBlockState = document()->blockSignals(true); + bool oldBlockState = blockSignals(true); document()->setUndoRedoEnabled(false); ExpandingTextEdit::setPlainText(text); // highlighter is out of sync because of blocked signals m_highlighter->rehighlight(); document()->setUndoRedoEnabled(true); - document()->blockSignals(oldBlockState); + blockSignals(oldBlockState); } else { ExpandingTextEdit::setPlainText(text); } @@ -178,11 +181,21 @@ FormWidget::FormWidget(const QString &label, bool isEditable, QWidget *parent) setLayout(layout); - connect(m_editor->document(), SIGNAL(contentsChanged()), SIGNAL(textChanged())); - connect(m_editor, SIGNAL(selectionChanged()), SIGNAL(selectionChanged())); + connect(m_editor, SIGNAL(textChanged()), SLOT(slotTextChanged())); + connect(m_editor, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged())); connect(m_editor, SIGNAL(cursorPositionChanged()), SIGNAL(cursorPositionChanged())); } +void FormWidget::slotTextChanged() +{ + emit textChanged(m_editor); +} + +void FormWidget::slotSelectionChanged() +{ + emit selectionChanged(m_editor); +} + void FormWidget::setTranslation(const QString &text, bool userAction) { m_editor->setPlainText(text, userAction); @@ -198,4 +211,240 @@ void FormWidget::setEditingEnabled(bool enable) } +class ButtonWrapper : public QWidget +{ + // no Q_OBJECT: no need to, and don't want the useless moc file + +public: + ButtonWrapper(QWidget *wrapee, QWidget *relator) : m_wrapee(wrapee) + { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); + QBoxLayout *box = new QVBoxLayout; + box->setMargin(0); + setLayout(box); + box->addWidget(wrapee, 0, Qt::AlignBottom); + if (relator) + relator->installEventFilter(this); + } + +protected: + virtual bool eventFilter(QObject *object, QEvent *event) + { + if (event->type() == QEvent::Resize) { + QWidget *relator = static_cast<QWidget *>(object); + setFixedHeight((relator->height() + layout()->spacing() + m_wrapee->height()) / 2); + } + return false; + } + +private: + QWidget *m_wrapee; +}; + +FormMultiWidget::FormMultiWidget(const QString &label, QWidget *parent) + : QWidget(parent), + m_hideWhenEmpty(false), + m_multiEnabled(false), + m_plusIcon(QIcon(QLatin1String(":/images/plus.png"))), // make static + m_minusIcon(QIcon(QLatin1String(":/images/minus.png"))) +{ + m_label = new QLabel(this); + m_label->setText(label); + + m_plusButtons.append( + new ButtonWrapper(makeButton(m_plusIcon, SLOT(plusButtonClicked())), 0)); +} + +QAbstractButton *FormMultiWidget::makeButton(const QIcon &icon, const char *slot) +{ + QAbstractButton *btn = new QToolButton(this); + btn->setIcon(icon); + btn->setFixedSize(icon.availableSizes().first() /* + something */); + btn->setFocusPolicy(Qt::NoFocus); + connect(btn, SIGNAL(clicked()), slot); + return btn; +} + +void FormMultiWidget::addEditor(int idx) +{ + FormatTextEdit *editor = new FormatTextEdit(this); + m_editors.insert(idx, editor); + + m_minusButtons.insert(idx, makeButton(m_minusIcon, SLOT(minusButtonClicked()))); + m_plusButtons.insert(idx + 1, + new ButtonWrapper(makeButton(m_plusIcon, SLOT(plusButtonClicked())), editor)); + + connect(editor, SIGNAL(textChanged()), SLOT(slotTextChanged())); + connect(editor, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged())); + connect(editor, SIGNAL(cursorPositionChanged()), SIGNAL(cursorPositionChanged())); + editor->installEventFilter(this); + + emit editorCreated(editor); +} + +bool FormMultiWidget::eventFilter(QObject *watched, QEvent *event) +{ + int i = 0; + while (m_editors.at(i) != watched) + if (++i >= m_editors.count()) // Happens when deleting an editor + return false; + if (event->type() == QEvent::FocusOut) { + m_minusButtons.at(i)->setToolTip(QString()); + m_plusButtons.at(i)->setToolTip(QString()); + m_plusButtons.at(i + 1)->setToolTip(QString()); + } else if (event->type() == QEvent::FocusIn) { + m_minusButtons.at(i)->setToolTip(/*: translate, but don't change */ tr("Alt+Delete")); + m_plusButtons.at(i)->setToolTip(/*: translate, but don't change */ tr("Shift+Alt+Insert")); + m_plusButtons.at(i + 1)->setToolTip(/*: translate, but don't change */ tr("Alt+Insert")); + } else if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent *>(event); + if (ke->modifiers() & Qt::AltModifier) { + if (ke->key() == Qt::Key_Delete) { + deleteEditor(i); + return true; + } else if (ke->key() == Qt::Key_Insert) { + if (!(ke->modifiers() & Qt::ShiftModifier)) + ++i; + insertEditor(i); + return true; + } + } + } + return false; +} + +void FormMultiWidget::updateLayout() +{ + delete layout(); + + QGridLayout *layout = new QGridLayout; + layout->setMargin(0); + setLayout(layout); + + bool variants = m_multiEnabled && m_label->isEnabled(); + + layout->addWidget(m_label, 0, 0, 1, variants ? 3 : 1); + + for (int i = 0; i < m_plusButtons.count(); ++i) { + if (variants) + layout->addWidget(m_plusButtons.at(i), 1 + i * 2, 0, 2, 1, Qt::AlignTop); + m_plusButtons.at(i)->setVisible(variants); + } + for (int j = 0; j < m_minusButtons.count(); ++j) { + if (variants) + layout->addWidget(m_minusButtons.at(j), 2 + j * 2, 2, 2, 1, Qt::AlignVCenter); + m_minusButtons.at(j)->setVisible(variants); + } + for (int k = 0; k < m_editors.count(); ++k) + layout->addWidget(m_editors.at(k), 2 + k * 2, variants ? 1 : 0, 2, 1, Qt::AlignVCenter); + + updateGeometry(); +} + +void FormMultiWidget::slotTextChanged() +{ + emit textChanged(static_cast<QTextEdit *>(sender())); +} + +void FormMultiWidget::slotSelectionChanged() +{ + emit selectionChanged(static_cast<QTextEdit *>(sender())); +} + +void FormMultiWidget::setTranslation(const QString &text, bool userAction) +{ + QStringList texts = text.split(QChar(Translator::BinaryVariantSeparator), QString::KeepEmptyParts); + + while (m_editors.count() > texts.count()) { + delete m_minusButtons.takeLast(); + delete m_plusButtons.takeLast(); + delete m_editors.takeLast(); + } + while (m_editors.count() < texts.count()) + addEditor(m_editors.count()); + updateLayout(); + + for (int i = 0; i < texts.count(); ++i) + // XXX this will emit n textChanged signals + m_editors.at(i)->setPlainText(texts.at(i), userAction); + + if (m_hideWhenEmpty) + setHidden(text.isEmpty()); +} + +QString FormMultiWidget::getTranslation() const +{ + QString ret; + for (int i = 0; i < m_editors.count(); ++i) { + if (i) + ret += QChar(Translator::BinaryVariantSeparator); + ret += m_editors.at(i)->toPlainText(); + } + return ret; +} + +void FormMultiWidget::setEditingEnabled(bool enable) +{ + // Use read-only state so that the text can still be copied + for (int i = 0; i < m_editors.count(); ++i) + m_editors.at(i)->setReadOnly(!enable); + m_label->setEnabled(enable); + if (m_multiEnabled) + updateLayout(); +} + +void FormMultiWidget::setMultiEnabled(bool enable) +{ + m_multiEnabled = enable; + if (m_label->isEnabled()) + updateLayout(); +} + +void FormMultiWidget::minusButtonClicked() +{ + int i = 0; + while (m_minusButtons.at(i) != sender()) + ++i; + deleteEditor(i); +} + +void FormMultiWidget::plusButtonClicked() +{ + QWidget *btn = static_cast<QAbstractButton *>(sender())->parentWidget(); + int i = 0; + while (m_plusButtons.at(i) != btn) + ++i; + insertEditor(i); +} + +void FormMultiWidget::deleteEditor(int idx) +{ + if (m_editors.count() == 1) { + // Don't just clear(), so the undo history is not lost + QTextCursor c = m_editors.first()->textCursor(); + c.select(QTextCursor::Document); + c.removeSelectedText(); + } else { + if (!m_editors.at(idx)->toPlainText().isEmpty()) { + if (QMessageBox::question(topLevelWidget(), tr("Confirmation - Qt Linguist"), + tr("Delete non-empty length variant?"), + QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes) + != QMessageBox::Yes) + return; + } + delete m_editors.takeAt(idx); + delete m_minusButtons.takeAt(idx); + delete m_plusButtons.takeAt(idx + 1); + updateLayout(); + emit textChanged(m_editors.at((m_editors.count() == idx) ? idx - 1 : idx)); + } +} + +void FormMultiWidget::insertEditor(int idx) +{ + addEditor(idx); + updateLayout(); + emit textChanged(m_editors.at(idx)); +} + QT_END_NAMESPACE diff --git a/tools/linguist/linguist/messageeditorwidgets.h b/tools/linguist/linguist/messageeditorwidgets.h index 3c3e20a..98bf4d5 100644 --- a/tools/linguist/linguist/messageeditorwidgets.h +++ b/tools/linguist/linguist/messageeditorwidgets.h @@ -42,6 +42,7 @@ #ifndef MESSAGEEDITORWIDGETS_H #define MESSAGEEDITORWIDGETS_H +#include <QIcon> #include <QImage> #include <QLabel> #include <QMap> @@ -51,6 +52,7 @@ QT_BEGIN_NAMESPACE +class QAbstractButton; class QAction; class QContextMenuEvent; class QKeyEvent; @@ -115,16 +117,68 @@ public: FormatTextEdit *getEditor() { return m_editor; } signals: - void textChanged(); - void selectionChanged(); + void textChanged(QTextEdit *); + void selectionChanged(QTextEdit *); void cursorPositionChanged(); +private slots: + void slotSelectionChanged(); + void slotTextChanged(); + private: QLabel *m_label; FormatTextEdit *m_editor; bool m_hideWhenEmpty; }; +/* + Displays text fields & associated label +*/ +class FormMultiWidget : public QWidget +{ + Q_OBJECT +public: + FormMultiWidget(const QString &label, QWidget *parent = 0); + void setLabel(const QString &label) { m_label->setText(label); } + void setTranslation(const QString &text, bool userAction = false); + void clearTranslation() { setTranslation(QString(), false); } + QString getTranslation() const; + void setEditingEnabled(bool enable); + void setMultiEnabled(bool enable); + void setHideWhenEmpty(bool optional) { m_hideWhenEmpty = optional; } + const QList<FormatTextEdit *> &getEditors() const { return m_editors; } + +signals: + void editorCreated(QTextEdit *); + void textChanged(QTextEdit *); + void selectionChanged(QTextEdit *); + void cursorPositionChanged(); + +protected: + bool eventFilter(QObject *watched, QEvent *event); + +private slots: + void slotTextChanged(); + void slotSelectionChanged(); + void minusButtonClicked(); + void plusButtonClicked(); + +private: + void addEditor(int idx); + void updateLayout(); + QAbstractButton *makeButton(const QIcon &icon, const char *slot); + void insertEditor(int idx); + void deleteEditor(int idx); + + QLabel *m_label; + QList<FormatTextEdit *> m_editors; + QList<QWidget *> m_plusButtons; + QList<QAbstractButton *> m_minusButtons; + bool m_hideWhenEmpty; + bool m_multiEnabled; + QIcon m_plusIcon, m_minusIcon; +}; + QT_END_NAMESPACE #endif // MESSAGEEDITORWIDGETS_H diff --git a/tools/linguist/linguist/phrase.cpp b/tools/linguist/linguist/phrase.cpp index 52f5093..c69d712 100644 --- a/tools/linguist/linguist/phrase.cpp +++ b/tools/linguist/linguist/phrase.cpp @@ -152,10 +152,10 @@ bool QphHandler::startElement(const QString & /* namespaceURI */, const QString &qName, const QXmlAttributes &atts) { - if (qName == QString(QLatin1String("QPH"))) { + if (qName == QLatin1String("QPH")) { m_language = atts.value(QLatin1String("language")); m_sourceLanguage = atts.value(QLatin1String("sourcelanguage")); - } else if (qName == QString(QLatin1String("phrase"))) { + } else if (qName == QLatin1String("phrase")) { source.truncate(0); target.truncate(0); definition.truncate(0); @@ -168,13 +168,13 @@ bool QphHandler::endElement(const QString & /* namespaceURI */, const QString & /* localName */, const QString &qName) { - if (qName == QString(QLatin1String("source"))) + if (qName == QLatin1String("source")) source = accum; - else if (qName == QString(QLatin1String("target"))) + else if (qName == QLatin1String("target")) target = accum; - else if (qName == QString(QLatin1String("definition"))) + else if (qName == QLatin1String("definition")) definition = accum; - else if (qName == QString(QLatin1String("phrase"))) + else if (qName == QLatin1String("phrase")) pb->m_phrases.append(new Phrase(source, target, definition, pb)); return true; } diff --git a/tools/linguist/linguist/phrasebookbox.cpp b/tools/linguist/linguist/phrasebookbox.cpp index 74bd862..0a06ff7 100644 --- a/tools/linguist/linguist/phrasebookbox.cpp +++ b/tools/linguist/linguist/phrasebookbox.cpp @@ -56,13 +56,15 @@ QT_BEGIN_NAMESPACE -#define NewPhrase tr("(New Entry)") - PhraseBookBox::PhraseBookBox(PhraseBook *phraseBook, QWidget *parent) : QDialog(parent), m_phraseBook(phraseBook), m_translationSettingsDialog(0) { + +// This definition needs to be within class context for lupdate to find it +#define NewPhrase tr("(New Entry)") + setupUi(this); setWindowTitle(tr("%1[*] - Qt Linguist").arg(m_phraseBook->friendlyPhraseBookName())); setWindowModified(m_phraseBook->isModified()); diff --git a/tools/linguist/linguist/phraseview.cpp b/tools/linguist/linguist/phraseview.cpp index a027712..b26d206 100644 --- a/tools/linguist/linguist/phraseview.cpp +++ b/tools/linguist/linguist/phraseview.cpp @@ -39,6 +39,7 @@ ** ****************************************************************************/ +#include "globals.h" #include "mainwindow.h" #include "messagemodel.h" #include "phrase.h" @@ -61,7 +62,7 @@ static const int MaxCandidates = 5; static QString phraseViewHeaderKey() { - return settingsPrefix() + QLatin1String("PhraseViewHeader"); + return settingPath("PhraseViewHeader"); } PhraseView::PhraseView(MultiDataModel *model, QList<QHash<QString, QList<Phrase *> > > *phraseDict, QWidget *parent) diff --git a/tools/linguist/linguist/recentfiles.cpp b/tools/linguist/linguist/recentfiles.cpp index b352164..4fbd8e8 100644 --- a/tools/linguist/linguist/recentfiles.cpp +++ b/tools/linguist/linguist/recentfiles.cpp @@ -40,6 +40,7 @@ ****************************************************************************/ #include "recentfiles.h" +#include "globals.h" #include <QtCore/QDebug> #include <QtCore/QFileInfo> @@ -49,11 +50,9 @@ QT_BEGIN_NAMESPACE -const QString &settingsPrefix(); - static QString configKey() { - return settingsPrefix() + QLatin1String("RecentlyOpenedFiles"); + return settingPath("RecentlyOpenedFiles"); } diff --git a/tools/linguist/lrelease/lrelease.1 b/tools/linguist/lrelease/lrelease.1 index c170e2a..8f1a3ff 100644 --- a/tools/linguist/lrelease/lrelease.1 +++ b/tools/linguist/lrelease/lrelease.1 @@ -51,10 +51,10 @@ This page documents the tool for the Qt GUI toolkit. .B Lrelease reads a qmake/tmake project file (.pro file) and converts the -translation files (.ts files) specified in it into Qt message files -(.qm files) used by the application to translate. +translation files (TS files) specified in it into Qt message files +(QM files) used by the application to translate. .PP -The .qm file format is a compact binary format that provides +The QM file format is a compact binary format that provides extremely fast lookups for translations and that is used by Qt. .SH OPTIONS .TP @@ -62,7 +62,7 @@ extremely fast lookups for translations and that is used by Qt. Display the usage and exit. .TP .I "-compress" -Compress the .qm files. +Compress the QM files. .TP .I "-nounfinished" Do not include unfinished translations. @@ -72,7 +72,7 @@ If the translated text is the same as the source text, do not include the message. .TP .I "-silent" -Don't explain what is being done. +Do not explain what is being done. .TP .I "-version" Display the version of @@ -105,7 +105,7 @@ generated from gnomovision_dk.ts, gnomovision_fi.ts, gnomovision_no.ts and gnomovision_se.ts, respectively. .PP .B Lrelease -can also be invoked with a list of .ts files to convert: +can also be invoked with a list of TS files to convert: .PP .in +4 .nf diff --git a/tools/linguist/lrelease/main.cpp b/tools/linguist/lrelease/main.cpp index ed29f4b..aa2a673 100644 --- a/tools/linguist/lrelease/main.cpp +++ b/tools/linguist/lrelease/main.cpp @@ -40,7 +40,6 @@ ****************************************************************************/ #include "translator.h" -#include "translatortools.h" #include "proreader.h" #include <QtCore/QCoreApplication> @@ -67,19 +66,21 @@ static void printUsage() " lrelease [options] project-file\n" " lrelease [options] ts-files [-qm qm-file]\n\n" "lrelease is part of Qt's Linguist tool chain. It can be used as a\n" - "stand-alone tool to convert XML based translations files in the .ts\n" - "format into the 'compiled' .qm format used by QTranslator objects.\n\n" + "stand-alone tool to convert XML-based translations files in the TS\n" + "format into the 'compiled' QM format used by QTranslator objects.\n\n" "Options:\n" " -help Display this information and exit\n" + " -idbased\n" + " Use IDs instead of source strings for message keying\n" " -compress\n" - " Compress the .qm files\n" + " Compress the QM files\n" " -nounfinished\n" " Do not include unfinished translations\n" " -removeidentical\n" " If the translated text is the same as\n" " the source text, do not include the message\n" " -silent\n" - " Don't explain what is being done\n" + " Do not explain what is being done\n" " -version\n" " Display the version of lrelease and exit\n" )); @@ -100,7 +101,7 @@ static bool loadTsFile(Translator &tor, const QString &tsFileName, bool /* verbo static bool releaseTranslator(Translator &tor, const QString &qmFileName, bool verbose, bool ignoreUnfinished, - bool removeIdentical, TranslatorSaveMode mode) + bool removeIdentical, bool idBased, TranslatorSaveMode mode) { Translator::reportDuplicates(tor.resolveDuplicates(), qmFileName, verbose); @@ -123,6 +124,7 @@ static bool releaseTranslator(Translator &tor, const QString &qmFileName, tor.normalizeTranslations(cd); cd.m_verbose = verbose; cd.m_ignoreUnfinished = ignoreUnfinished; + cd.m_idBased = idBased; cd.m_saveMode = mode; bool ok = tor.release(&file, cd); file.close(); @@ -138,7 +140,7 @@ static bool releaseTranslator(Translator &tor, const QString &qmFileName, } static bool releaseTsFile(const QString& tsFileName, bool verbose, - bool ignoreUnfinished, bool removeIdentical, TranslatorSaveMode mode) + bool ignoreUnfinished, bool removeIdentical, bool idBased, TranslatorSaveMode mode) { Translator tor; if (!loadTsFile(tor, tsFileName, verbose)) @@ -153,7 +155,7 @@ static bool releaseTsFile(const QString& tsFileName, bool verbose, } qmFileName += QLatin1String(".qm"); - return releaseTranslator(tor, qmFileName, verbose, ignoreUnfinished, removeIdentical, mode); + return releaseTranslator(tor, qmFileName, verbose, ignoreUnfinished, removeIdentical, idBased, mode); } int main(int argc, char **argv) @@ -166,6 +168,7 @@ int main(int argc, char **argv) bool verbose = true; // the default is true starting with Qt 4.2 bool ignoreUnfinished = false; + bool idBased = false; // the default mode is SaveEverything starting with Qt 4.2 TranslatorSaveMode mode = SaveEverything; bool removeIdentical = false; @@ -177,6 +180,9 @@ int main(int argc, char **argv) if (args[i] == QLatin1String("-compress")) { mode = SaveStripped; continue; + } else if (args[i] == QLatin1String("-idbased")) { + idBased = true; + continue; } else if (args[i] == QLatin1String("-nocompress")) { mode = SaveEverything; continue; @@ -234,18 +240,18 @@ int main(int argc, char **argv) qPrintable(args[i])); } else { foreach (const QString &trans, translations) - if (!releaseTsFile(trans, verbose, ignoreUnfinished, removeIdentical, mode)) + if (!releaseTsFile(trans, verbose, ignoreUnfinished, removeIdentical, idBased, mode)) return 1; } } else { qWarning("error: lrelease encountered project file functionality that is currently not supported.\n" - "You might want to consider using .ts files as input instead of a project file.\n" + "You might want to consider using TS files as input instead of a project file.\n" "Try the following syntax:\n" " lrelease [options] ts-files [-qm qm-file]\n"); } } else { if (outputFile.isEmpty()) { - if (!releaseTsFile(args[i], verbose, ignoreUnfinished, removeIdentical, mode)) + if (!releaseTsFile(args[i], verbose, ignoreUnfinished, removeIdentical, idBased, mode)) return 1; } else { if (!loadTsFile(tor, args[i], verbose)) @@ -256,7 +262,7 @@ int main(int argc, char **argv) if (!outputFile.isEmpty()) return releaseTranslator(tor, outputFile, verbose, ignoreUnfinished, - removeIdentical, mode) ? 0 : 1; + removeIdentical, idBased, mode) ? 0 : 1; return 0; } diff --git a/tools/linguist/lupdate/cpp.cpp b/tools/linguist/lupdate/cpp.cpp new file mode 100644 index 0000000..e929c0d --- /dev/null +++ b/tools/linguist/lupdate/cpp.cpp @@ -0,0 +1,1979 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Linguist of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "lupdate.h" + +#include <translator.h> + +#include <QtCore/QDebug> +#include <QtCore/QFileInfo> +#include <QtCore/QStack> +#include <QtCore/QString> +#include <QtCore/QTextCodec> +#include <QtCore/QTextStream> + +#include <ctype.h> // for isXXX() + +QT_BEGIN_NAMESPACE + +/* qmake ignore Q_OBJECT */ + +static const char MagicComment[] = "TRANSLATOR "; + +#define STRING(s) static QString str##s(QLatin1String(#s)) + +//#define DIAGNOSE_RETRANSLATABILITY // FIXME: should make a runtime option of this + +uint qHash(const QStringList &qsl) +{ + uint hash = 0; + foreach (const QString &qs, qsl) { + hash ^= qHash(qs) ^ 0xa09df22f; + hash = (hash << 13) | (hash >> 19); + } + return hash; +} + +struct Namespace { + + Namespace() : + isClass(false), + hasTrFunctions(false), needsTrFunctions(false), complained(false) + {} + + QString name; + QMap<QString, Namespace *> children; + QMap<QString, QStringList> aliases; + QSet<QStringList> usings; + + int fileId; + + bool isClass; + + bool hasTrFunctions; + bool needsTrFunctions; + bool complained; // ... that tr functions are missing. +}; + +typedef QList<Namespace *> NamespaceList; + +struct ParseResults { + + ParseResults() + { + static int nextFileId; + rootNamespace.fileId = nextFileId++; + tor = 0; + } + bool detachNamespace(Namespace **that); + Namespace *include(Namespace *that, const Namespace *other); + void unite(const ParseResults *other); + + Namespace rootNamespace; + Translator *tor; + QSet<QString> allIncludes; +}; + +typedef QHash<QString, const ParseResults *> ParseResultHash; + +class CppFiles { + +public: + static const ParseResults *getResults(const QString &cleanFile); + static void setResults(const QString &cleanFile, const ParseResults *results); + static bool isBlacklisted(const QString &cleanFile); + static void setBlacklisted(const QString &cleanFile); + +private: + static ParseResultHash &parsedFiles(); + static QSet<QString> &blacklistedFiles(); +}; + +class CppParser { + +public: + CppParser(ParseResults *results = 0); + void setInput(const QString &in); + void setInput(QTextStream &ts, const QString &fileName); + void setTranslator(Translator *tor) { results->tor = tor; } + void parse(const QString &initialContext, ConversionData &cd, QSet<QString> &inclusions); + void parseInternal(ConversionData &cd, QSet<QString> &inclusions); + const ParseResults *getResults() const { return results; } + void deleteResults() { delete results; } + + struct SavedState { + QStringList namespaces; + QStack<int> namespaceDepths; + QStringList functionContext; + QString functionContextUnresolved; + QString pendingContext; + }; + +private: + struct IfdefState { + IfdefState() {} + IfdefState(int _braceDepth, int _parenDepth) : + braceDepth(_braceDepth), + parenDepth(_parenDepth), + elseLine(-1) + {} + + SavedState state; + int braceDepth, braceDepth1st; + int parenDepth, parenDepth1st; + int elseLine; + }; + + uint getChar(); + uint getToken(); + bool match(uint t); + bool matchString(QString *s); + bool matchEncoding(bool *utf8); + bool matchInteger(qlonglong *number); + bool matchStringOrNull(QString *s); + bool matchExpression(); + + QString transcode(const QString &str, bool utf8); + void recordMessage( + int line, const QString &context, const QString &text, const QString &comment, + const QString &extracomment, const QString &msgid, const TranslatorMessage::ExtraData &extra, + bool utf8, bool plural); + + void processInclude(const QString &file, ConversionData &cd, + QSet<QString> &inclusions); + + void saveState(SavedState *state); + void loadState(const SavedState *state); + + static QString stringifyNamespace(const NamespaceList &namespaces); + static QStringList stringListifyNamespace(const NamespaceList &namespaces); + void modifyNamespace(NamespaceList *namespaces); + NamespaceList resolveNamespaces(const QStringList &segments); + bool qualifyOne(const NamespaceList &namespaces, int nsIdx, const QString &segment, + NamespaceList *resolved); + bool fullyQualify(const NamespaceList &namespaces, const QStringList &segments, + bool isDeclaration, + NamespaceList *resolved, QStringList *unresolved); + void enterNamespace(NamespaceList *namespaces, const QString &name); + void truncateNamespaces(NamespaceList *namespaces, int lenght); + + enum { + Tok_Eof, Tok_class, Tok_friend, Tok_namespace, Tok_using, Tok_return, + Tok_tr = 10, Tok_trUtf8, Tok_translate, Tok_translateUtf8, Tok_trid, + Tok_Q_OBJECT = 20, Tok_Q_DECLARE_TR_FUNCTIONS, + Tok_Ident, Tok_Comment, Tok_String, Tok_Arrow, Tok_Colon, Tok_ColonColon, + Tok_Equals, + Tok_LeftBrace = 30, Tok_RightBrace, Tok_LeftParen, Tok_RightParen, Tok_Comma, Tok_Semicolon, + Tok_Integer = 40, + Tok_QuotedInclude = 50, Tok_AngledInclude, + Tok_Other = 99 + }; + + // Tokenizer state + QString yyFileName; + int yyCh; + bool yyAtNewline; + bool yyCodecIsUtf8; + bool yyForceUtf8; + QString yyWord; + qlonglong yyInteger; + QStack<IfdefState> yyIfdefStack; + int yyBraceDepth; + int yyParenDepth; + int yyLineNo; + int yyCurLineNo; + int yyBraceLineNo; + int yyParenLineNo; + + // the string to read from and current position in the string + QTextCodec *yySourceCodec; + bool yySourceIsUnicode; + QString yyInStr; + int yyInPos; + + // Parser state + uint yyTok; + + NamespaceList namespaces; + QStack<int> namespaceDepths; + NamespaceList functionContext; + QString functionContextUnresolved; + QString prospectiveContext; + QString pendingContext; + ParseResults *results; + bool directInclude; + + SavedState savedState; + int yyMinBraceDepth; + bool inDefine; +}; + +CppParser::CppParser(ParseResults *_results) +{ + if (_results) { + results = _results; + directInclude = true; + } else { + results = new ParseResults; + directInclude = false; + } + yyInPos = 0; + yyBraceDepth = 0; + yyParenDepth = 0; + yyCurLineNo = 1; + yyBraceLineNo = 1; + yyParenLineNo = 1; + yyAtNewline = true; + yyMinBraceDepth = 0; + inDefine = false; +} + +void CppParser::setInput(const QString &in) +{ + yyInStr = in; + yyFileName = QString(); + yySourceCodec = 0; + yySourceIsUnicode = true; + yyForceUtf8 = true; +} + +void CppParser::setInput(QTextStream &ts, const QString &fileName) +{ + yyInStr = ts.readAll(); + yyFileName = fileName; + yySourceCodec = ts.codec(); + yySourceIsUnicode = yySourceCodec->name().startsWith("UTF-"); + yyForceUtf8 = false; +} + +/* + The first part of this source file is the C++ tokenizer. We skip + most of C++; the only tokens that interest us are defined here. + Thus, the code fragment + + int main() + { + printf("Hello, world!\n"); + return 0; + } + + is broken down into the following tokens (Tok_ omitted): + + Ident Ident LeftParen RightParen + LeftBrace + Ident LeftParen String RightParen Semicolon + return Semicolon + RightBrace. + + The 0 doesn't produce any token. +*/ + +uint CppParser::getChar() +{ + int len = yyInStr.size(); + const ushort *uc = (const ushort *)yyInStr.unicode(); + forever { + if (yyInPos >= len) + return EOF; + uint c = uc[yyInPos++]; + if (c == '\\' && yyInPos < len) { + if (uc[yyInPos] == '\n') { + ++yyCurLineNo; + ++yyInPos; + continue; + } + if (uc[yyInPos] == '\r') { + ++yyCurLineNo; + ++yyInPos; + if (yyInPos < len && uc[yyInPos] == '\n') + ++yyInPos; + continue; + } + } + if (c == '\r') { + if (yyInPos < len && uc[yyInPos] == '\n') + ++yyInPos; + c = '\n'; + ++yyCurLineNo; + yyAtNewline = true; + } else if (c == '\n') { + ++yyCurLineNo; + yyAtNewline = true; + } else if (c != ' ' && c != '\t' && c != '#') { + yyAtNewline = false; + } + return c; + } +} + +STRING(Q_OBJECT); +STRING(Q_DECLARE_TR_FUNCTIONS); +STRING(QT_TR_NOOP); +STRING(QT_TRID_NOOP); +STRING(QT_TRANSLATE_NOOP); +STRING(QT_TRANSLATE_NOOP3); +STRING(QT_TR_NOOP_UTF8); +STRING(QT_TRANSLATE_NOOP_UTF8); +STRING(QT_TRANSLATE_NOOP3_UTF8); +STRING(class); +// QTranslator::findMessage() has the same parameters as QApplication::translate() +STRING(findMessage); +STRING(friend); +STRING(namespace); +STRING(qtTrId); +STRING(return); +STRING(struct); +STRING(TR); +STRING(Tr); +STRING(tr); +STRING(trUtf8); +STRING(translate); +STRING(using); + +uint CppParser::getToken() +{ + restart: + // Failing this assertion would mean losing the preallocated buffer. + Q_ASSERT(yyWord.isDetached()); + yyWord.resize(0); + + while (yyCh != EOF) { + yyLineNo = yyCurLineNo; + + if (yyCh == '#' && yyAtNewline) { + /* + Early versions of lupdate complained about + unbalanced braces in the following code: + + #ifdef ALPHA + while (beta) { + #else + while (gamma) { + #endif + delta; + } + + The code contains, indeed, two opening braces for + one closing brace; yet there's no reason to panic. + + The solution is to remember yyBraceDepth as it was + when #if, #ifdef or #ifndef was met, and to set + yyBraceDepth to that value when meeting #elif or + #else. + */ + do { + yyCh = getChar(); + } while (isspace(yyCh) && yyCh != '\n'); + + switch (yyCh) { + case 'd': // define + // Skip over the name of the define to avoid it being interpreted as c++ code + do { // Rest of "define" + yyCh = getChar(); + if (yyCh == EOF) + return Tok_Eof; + if (yyCh == '\n') + goto restart; + } while (!isspace(yyCh)); + do { // Space beween "define" and macro name + yyCh = getChar(); + if (yyCh == EOF) + return Tok_Eof; + if (yyCh == '\n') + goto restart; + } while (isspace(yyCh)); + do { // Macro name + if (yyCh == '(') { + // Argument list. Follows the name without a space, and no + // paren nesting is possible. + do { + yyCh = getChar(); + if (yyCh == EOF) + return Tok_Eof; + if (yyCh == '\n') + goto restart; + } while (yyCh != ')'); + break; + } + yyCh = getChar(); + if (yyCh == EOF) + return Tok_Eof; + if (yyCh == '\n') + goto restart; + } while (!isspace(yyCh)); + do { // Shortcut the immediate newline case if no comments follow. + yyCh = getChar(); + if (yyCh == EOF) + return Tok_Eof; + if (yyCh == '\n') + goto restart; + } while (isspace(yyCh)); + + saveState(&savedState); + yyMinBraceDepth = yyBraceDepth; + inDefine = true; + goto restart; + case 'i': + yyCh = getChar(); + if (yyCh == 'f') { + // if, ifdef, ifndef + yyIfdefStack.push(IfdefState(yyBraceDepth, yyParenDepth)); + yyCh = getChar(); + } else if (yyCh == 'n') { + // include + do { + yyCh = getChar(); + } while (yyCh != EOF && !isspace(yyCh)); + do { + yyCh = getChar(); + } while (isspace(yyCh)); + int tChar; + if (yyCh == '"') + tChar = '"'; + else if (yyCh == '<') + tChar = '>'; + else + break; + ushort *ptr = (ushort *)yyWord.unicode(); + forever { + yyCh = getChar(); + if (yyCh == EOF || yyCh == '\n') + break; + if (yyCh == tChar) { + yyCh = getChar(); + break; + } + *ptr++ = yyCh; + } + yyWord.resize(ptr - (ushort *)yyWord.unicode()); + return (tChar == '"') ? Tok_QuotedInclude : Tok_AngledInclude; + } + break; + case 'e': + yyCh = getChar(); + if (yyCh == 'l') { + // elif, else + if (!yyIfdefStack.isEmpty()) { + IfdefState &is = yyIfdefStack.top(); + if (is.elseLine != -1) { + if (yyBraceDepth != is.braceDepth1st || yyParenDepth != is.parenDepth1st) + qWarning("%s:%d: Parenthesis/brace mismatch between " + "#if and #else branches; using #if branch\n", + qPrintable(yyFileName), is.elseLine); + } else { + is.braceDepth1st = yyBraceDepth; + is.parenDepth1st = yyParenDepth; + saveState(&is.state); + } + is.elseLine = yyLineNo; + yyBraceDepth = is.braceDepth; + yyParenDepth = is.parenDepth; + } + yyCh = getChar(); + } else if (yyCh == 'n') { + // endif + if (!yyIfdefStack.isEmpty()) { + IfdefState is = yyIfdefStack.pop(); + if (is.elseLine != -1) { + if (yyBraceDepth != is.braceDepth1st || yyParenDepth != is.parenDepth1st) + qWarning("%s:%d: Parenthesis/brace mismatch between " + "#if and #else branches; using #if branch\n", + qPrintable(yyFileName), is.elseLine); + yyBraceDepth = is.braceDepth1st; + yyParenDepth = is.parenDepth1st; + loadState(&is.state); + } + } + yyCh = getChar(); + } + break; + } + // Optimization: skip over rest of preprocessor directive + do { + if (yyCh == '/') { + yyCh = getChar(); + if (yyCh == '/') { + do { + yyCh = getChar(); + } while (yyCh != EOF && yyCh != '\n'); + break; + } else if (yyCh == '*') { + bool metAster = false; + + forever { + yyCh = getChar(); + if (yyCh == EOF) { + qWarning("%s:%d: Unterminated C++ comment\n", + qPrintable(yyFileName), yyLineNo); + break; + } + + if (yyCh == '*') { + metAster = true; + } else if (metAster && yyCh == '/') { + yyCh = getChar(); + break; + } else { + metAster = false; + } + } + } + } else { + yyCh = getChar(); + } + } while (yyCh != '\n' && yyCh != EOF); + yyCh = getChar(); + } else if (isalpha(yyCh) || yyCh == '_') { + ushort *ptr = (ushort *)yyWord.unicode(); + do { + *ptr++ = yyCh; + yyCh = getChar(); + } while (isalnum(yyCh) || yyCh == '_'); + yyWord.resize(ptr - (ushort *)yyWord.unicode()); + + //qDebug() << "IDENT: " << yyWord; + + switch (yyWord.unicode()[0].unicode()) { + case 'Q': + if (yyWord == strQ_OBJECT) + return Tok_Q_OBJECT; + if (yyWord == strQ_DECLARE_TR_FUNCTIONS) + return Tok_Q_DECLARE_TR_FUNCTIONS; + if (yyWord == strQT_TR_NOOP) + return Tok_tr; + if (yyWord == strQT_TRID_NOOP) + return Tok_trid; + if (yyWord == strQT_TRANSLATE_NOOP) + return Tok_translate; + if (yyWord == strQT_TRANSLATE_NOOP3) + return Tok_translate; + if (yyWord == strQT_TR_NOOP_UTF8) + return Tok_trUtf8; + if (yyWord == strQT_TRANSLATE_NOOP_UTF8) + return Tok_translateUtf8; + if (yyWord == strQT_TRANSLATE_NOOP3_UTF8) + return Tok_translateUtf8; + break; + case 'T': + // TR() for when all else fails + if (yyWord == strTR || yyWord == strTr) + return Tok_tr; + break; + case 'c': + if (yyWord == strclass) + return Tok_class; + break; + case 'f': + /* + QTranslator::findMessage() has the same parameters as + QApplication::translate(). + */ + if (yyWord == strfindMessage) + return Tok_translate; + if (yyWord == strfriend) + return Tok_friend; + break; + case 'n': + if (yyWord == strnamespace) + return Tok_namespace; + break; + case 'q': + if (yyWord == strqtTrId) + return Tok_trid; + break; + case 'r': + if (yyWord == strreturn) + return Tok_return; + break; + case 's': + if (yyWord == strstruct) + return Tok_class; + break; + case 't': + if (yyWord == strtr) + return Tok_tr; + if (yyWord == strtrUtf8) + return Tok_trUtf8; + if (yyWord == strtranslate) + return Tok_translate; + break; + case 'u': + if (yyWord == strusing) + return Tok_using; + break; + } + return Tok_Ident; + } else { + switch (yyCh) { + case '\n': + if (inDefine) { + loadState(&savedState); + prospectiveContext.clear(); + yyBraceDepth = yyMinBraceDepth; + yyMinBraceDepth = 0; + inDefine = false; + } + yyCh = getChar(); + break; + case '/': + yyCh = getChar(); + if (yyCh == '/') { + ushort *ptr = (ushort *)yyWord.unicode() + yyWord.length(); + do { + yyCh = getChar(); + if (yyCh == EOF) + break; + *ptr++ = yyCh; + } while (yyCh != '\n'); + yyWord.resize(ptr - (ushort *)yyWord.unicode()); + } else if (yyCh == '*') { + bool metAster = false; + ushort *ptr = (ushort *)yyWord.unicode() + yyWord.length(); + + forever { + yyCh = getChar(); + if (yyCh == EOF) { + qWarning("%s:%d: Unterminated C++ comment\n", + qPrintable(yyFileName), yyLineNo); + return Tok_Comment; + } + *ptr++ = yyCh; + + if (yyCh == '*') + metAster = true; + else if (metAster && yyCh == '/') + break; + else + metAster = false; + } + yyWord.resize(ptr - (ushort *)yyWord.unicode() - 2); + + yyCh = getChar(); + } + return Tok_Comment; + case '"': { + ushort *ptr = (ushort *)yyWord.unicode() + yyWord.length(); + yyCh = getChar(); + while (yyCh != EOF && yyCh != '\n' && yyCh != '"') { + if (yyCh == '\\') { + yyCh = getChar(); + if (yyCh == EOF || yyCh == '\n') + break; + *ptr++ = '\\'; + } + *ptr++ = yyCh; + yyCh = getChar(); + } + yyWord.resize(ptr - (ushort *)yyWord.unicode()); + + if (yyCh != '"') + qWarning("%s:%d: Unterminated C++ string\n", + qPrintable(yyFileName), yyLineNo); + else + yyCh = getChar(); + return Tok_String; + } + case '-': + yyCh = getChar(); + if (yyCh == '>') { + yyCh = getChar(); + return Tok_Arrow; + } + break; + case ':': + yyCh = getChar(); + if (yyCh == ':') { + yyCh = getChar(); + return Tok_ColonColon; + } + return Tok_Colon; + // Incomplete: '<' might be part of '<=' or of template syntax. + // The main intent of not completely ignoring it is to break + // parsing of things like std::cout << QObject::tr() as + // context std::cout::QObject (see Task 161106) + case '=': + yyCh = getChar(); + return Tok_Equals; + case '>': + case '<': + yyCh = getChar(); + return Tok_Other; + case '\'': + yyCh = getChar(); + if (yyCh == '\\') + yyCh = getChar(); + + forever { + if (yyCh == EOF || yyCh == '\n') { + qWarning("%s:%d: Unterminated C++ character\n", + qPrintable(yyFileName), yyLineNo); + break; + } + yyCh = getChar(); + if (yyCh == '\'') { + yyCh = getChar(); + break; + } + } + break; + case '{': + if (yyBraceDepth == 0) + yyBraceLineNo = yyCurLineNo; + yyBraceDepth++; + yyCh = getChar(); + return Tok_LeftBrace; + case '}': + if (yyBraceDepth == yyMinBraceDepth) { + if (!inDefine) + qWarning("%s:%d: Excess closing brace in C++ code" + " (or abuse of the C++ preprocessor)\n", + qPrintable(yyFileName), yyCurLineNo); + // Avoid things getting messed up even more + yyCh = getChar(); + return Tok_Semicolon; + } + yyBraceDepth--; + yyCh = getChar(); + return Tok_RightBrace; + case '(': + if (yyParenDepth == 0) + yyParenLineNo = yyCurLineNo; + yyParenDepth++; + yyCh = getChar(); + return Tok_LeftParen; + case ')': + if (yyParenDepth == 0) + qWarning("%s:%d: Excess closing parenthesis in C++ code" + " (or abuse of the C++ preprocessor)\n", + qPrintable(yyFileName), yyCurLineNo); + else + yyParenDepth--; + yyCh = getChar(); + return Tok_RightParen; + case ',': + yyCh = getChar(); + return Tok_Comma; + case ';': + yyCh = getChar(); + return Tok_Semicolon; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + QByteArray ba; + ba += yyCh; + yyCh = getChar(); + bool hex = yyCh == 'x'; + if (hex) { + ba += yyCh; + yyCh = getChar(); + } + while (hex ? isxdigit(yyCh) : isdigit(yyCh)) { + ba += yyCh; + yyCh = getChar(); + } + bool ok; + yyInteger = ba.toLongLong(&ok); + if (ok) + return Tok_Integer; + break; + } + default: + yyCh = getChar(); + break; + } + } + } + return Tok_Eof; +} + +/* + The second part of this source file are namespace/class related + utilities for the third part. +*/ + +void CppParser::saveState(SavedState *state) +{ + state->namespaces = stringListifyNamespace(namespaces); + state->namespaceDepths = namespaceDepths; + state->functionContext = stringListifyNamespace(functionContext); + state->functionContextUnresolved = functionContextUnresolved; + state->pendingContext = pendingContext; +} + +void CppParser::loadState(const SavedState *state) +{ + namespaces = resolveNamespaces(state->namespaces); + namespaceDepths = state->namespaceDepths; + functionContext = resolveNamespaces(state->functionContext); + functionContextUnresolved = state->functionContextUnresolved; + pendingContext = state->pendingContext; +} + +bool ParseResults::detachNamespace(Namespace **that) +{ + if ((*that)->fileId != rootNamespace.fileId) { + Namespace *newThat = new Namespace; + *newThat = **that; + newThat->fileId = rootNamespace.fileId; + *that = newThat; + return true; + } + return false; +} + +Namespace *ParseResults::include(Namespace *that, const Namespace *other) +{ + Namespace *origThat = that; + foreach (Namespace *otherSub, other->children) { + if (Namespace *thisSub = that->children.value(otherSub->name)) { + // Don't make these cause a detach - it's best + // (though not necessary) if they are shared + thisSub->isClass |= otherSub->isClass; + thisSub->hasTrFunctions |= otherSub->hasTrFunctions; + thisSub->needsTrFunctions |= otherSub->needsTrFunctions; + thisSub->complained |= otherSub->complained; + + if (Namespace *newSub = include(thisSub, otherSub)) { + thisSub = newSub; + detachNamespace(&that); + that->children[thisSub->name] = thisSub; + } + } else { + detachNamespace(&that); + that->children[otherSub->name] = otherSub; + } + } + if ((that->aliases != other->aliases && !other->aliases.isEmpty()) + || (that->usings != other->usings && !other->usings.isEmpty())) { + detachNamespace(&that); + that->aliases.unite(other->aliases); + that->usings.unite(other->usings); + } + return (that != origThat) ? that : 0; +} + +void ParseResults::unite(const ParseResults *other) +{ + allIncludes.unite(other->allIncludes); + include(&rootNamespace, &other->rootNamespace); +} + +void CppParser::modifyNamespace(NamespaceList *namespaces) +{ + Namespace *pns = 0; + int i = namespaces->count(); + forever { + --i; + Namespace *ns = namespaces->at(i); + bool detached = results->detachNamespace(&ns); + if (pns) + ns->children[pns->name] = pns; + if (!detached) // Known to be true for root namespace + return; + pns = ns; + namespaces->replace(i, ns); + } +} + +QString CppParser::stringifyNamespace(const NamespaceList &namespaces) +{ + QString ret; + for (int i = 1; i < namespaces.count(); ++i) { + if (i > 1) + ret += QLatin1String("::"); + ret += namespaces.at(i)->name; + } + return ret; +} + +QStringList CppParser::stringListifyNamespace(const NamespaceList &namespaces) +{ + QStringList ret; + for (int i = 1; i < namespaces.count(); ++i) + ret << namespaces.at(i)->name; + return ret; +} + +// This function is called only with known-existing namespaces +NamespaceList CppParser::resolveNamespaces(const QStringList &segments) +{ + NamespaceList ret; + Namespace *ns = &results->rootNamespace; + ret << ns; + foreach (const QString &seg, segments) { + ns = ns->children.value(seg); + ret << ns; + } + return ret; +} + +bool CppParser::qualifyOne(const NamespaceList &namespaces, int nsIdx, const QString &segment, + NamespaceList *resolved) +{ + const Namespace *ns = namespaces.at(nsIdx); + QMap<QString, Namespace *>::ConstIterator cnsi = ns->children.constFind(segment); + if (cnsi != ns->children.constEnd()) { + *resolved = namespaces.mid(0, nsIdx + 1); + *resolved << *cnsi; + return true; + } + QMap<QString, QStringList>::ConstIterator nsai = ns->aliases.constFind(segment); + if (nsai != ns->aliases.constEnd()) { + *resolved = resolveNamespaces(*nsai); + return true; + } + foreach (const QStringList &use, ns->usings) { + NamespaceList usedNs = resolveNamespaces(use); + if (qualifyOne(usedNs, usedNs.count() - 1, segment, resolved)) + return true; + } + return false; +} + +bool CppParser::fullyQualify(const NamespaceList &namespaces, const QStringList &segments, + bool isDeclaration, + NamespaceList *resolved, QStringList *unresolved) +{ + int nsIdx; + int initSegIdx; + + if (segments.first().isEmpty()) { + // fully qualified + if (segments.count() == 1) { + resolved->clear(); + *resolved << &results->rootNamespace; + return true; + } + initSegIdx = 1; + nsIdx = 0; + } else { + initSegIdx = 0; + nsIdx = namespaces.count() - 1; + } + + do { + if (qualifyOne(namespaces, nsIdx, segments[initSegIdx], resolved)) { + int segIdx = initSegIdx; + while (++segIdx < segments.count()) { + if (!qualifyOne(*resolved, resolved->count() - 1, segments[segIdx], resolved)) { + if (unresolved) + *unresolved = segments.mid(segIdx); + return false; + } + } + return true; + } + } while (!isDeclaration && --nsIdx >= 0); + resolved->clear(); + *resolved << &results->rootNamespace; + if (unresolved) + *unresolved = segments.mid(initSegIdx); + return false; +} + +void CppParser::enterNamespace(NamespaceList *namespaces, const QString &name) +{ + Namespace *ns = namespaces->last()->children.value(name); + if (!ns) { + ns = new Namespace; + ns->fileId = results->rootNamespace.fileId; + ns->name = name; + modifyNamespace(namespaces); + namespaces->last()->children[name] = ns; + } + *namespaces << ns; +} + +void CppParser::truncateNamespaces(NamespaceList *namespaces, int length) +{ + if (namespaces->count() > length) + namespaces->erase(namespaces->begin() + length, namespaces->end()); +} + +/* + Functions for processing include files. +*/ + +ParseResultHash &CppFiles::parsedFiles() +{ + static ParseResultHash parsed; + + return parsed; +} + +QSet<QString> &CppFiles::blacklistedFiles() +{ + static QSet<QString> blacklisted; + + return blacklisted; +} + +const ParseResults *CppFiles::getResults(const QString &cleanFile) +{ + ParseResultHash::ConstIterator it = parsedFiles().find(cleanFile); + if (it == parsedFiles().constEnd()) + return 0; + return *it; +} + +void CppFiles::setResults(const QString &cleanFile, const ParseResults *results) +{ + parsedFiles().insert(cleanFile, results); +} + +bool CppFiles::isBlacklisted(const QString &cleanFile) +{ + return blacklistedFiles().contains(cleanFile); +} + +void CppFiles::setBlacklisted(const QString &cleanFile) +{ + blacklistedFiles().insert(cleanFile); +} + +void CppParser::processInclude(const QString &file, ConversionData &cd, + QSet<QString> &inclusions) +{ + QString cleanFile = QDir::cleanPath(file); + + if (inclusions.contains(cleanFile)) { + qWarning("%s:%d: circular inclusion of %s\n", + qPrintable(yyFileName), yyLineNo, qPrintable(cleanFile)); + return; + } + + // If the #include is in any kind of namespace, has been blacklisted previously, + // or is not a header file (stdc++ extensionless or *.h*), then really include + // it. Otherwise it is safe to process it stand-alone and re-use the parsed + // namespace data for inclusion into other files. + bool isIndirect = false; + if (namespaces.count() == 1 && functionContext.count() == 1 + && functionContextUnresolved.isEmpty() && pendingContext.isEmpty() + && !CppFiles::isBlacklisted(cleanFile)) { + QString fileExt = QFileInfo(cleanFile).suffix(); + if (fileExt.isEmpty() || fileExt.startsWith(QLatin1Char('h'), Qt::CaseInsensitive)) { + + if (results->allIncludes.contains(cleanFile)) + return; + results->allIncludes.insert(cleanFile); + + if (const ParseResults *res = CppFiles::getResults(cleanFile)) { + results->unite(res); + return; + } + + isIndirect = true; + } + } + + QFile f(cleanFile); + if (!f.open(QIODevice::ReadOnly)) { + qWarning("%s:%d: Cannot open %s: %s\n", + qPrintable(yyFileName), yyLineNo, + qPrintable(cleanFile), qPrintable(f.errorString())); + return; + } + + QTextStream ts(&f); + ts.setCodec(yySourceCodec); + ts.setAutoDetectUnicode(true); + + inclusions.insert(cleanFile); + if (isIndirect) { + CppParser parser; + foreach (const QString &projectRoot, cd.m_projectRoots) + if (cleanFile.startsWith(projectRoot)) { + parser.setTranslator(new Translator); + break; + } + parser.setInput(ts, cleanFile); + parser.parse(cd.m_defaultContext, cd, inclusions); + CppFiles::setResults(cleanFile, parser.getResults()); + results->unite(parser.results); + } else { + CppParser parser(results); + parser.namespaces = namespaces; + parser.functionContext = functionContext; + parser.functionContextUnresolved = functionContextUnresolved; + parser.pendingContext = pendingContext; + parser.setInput(ts, cleanFile); + parser.parseInternal(cd, inclusions); + // Don't wreak havoc if not enough braces were found. + truncateNamespaces(&parser.namespaces, namespaces.count()); + truncateNamespaces(&parser.functionContext, functionContext.count()); + // Copy them back - the pointers might have changed. + namespaces = parser.namespaces; + functionContext = parser.functionContext; + // Avoid that messages obtained by direct scanning are used + CppFiles::setBlacklisted(cleanFile); + } + inclusions.remove(cleanFile); +} + +/* + The third part of this source file is the parser. It accomplishes + a very easy task: It finds all strings inside a tr() or translate() + call, and possibly finds out the context of the call. It supports + three cases: (1) the context is specified, as in + FunnyDialog::tr("Hello") or translate("FunnyDialog", "Hello"); + (2) the call appears within an inlined function; (3) the call + appears within a function defined outside the class definition. +*/ + +bool CppParser::match(uint t) +{ + bool matches = (yyTok == t); + if (matches) + yyTok = getToken(); + return matches; +} + +bool CppParser::matchString(QString *s) +{ + bool matches = (yyTok == Tok_String); + s->clear(); + while (yyTok == Tok_String) { + *s += yyWord; + s->detach(); + do { + yyTok = getToken(); + } while (yyTok == Tok_Comment); + } + return matches; +} + +bool CppParser::matchEncoding(bool *utf8) +{ + STRING(QApplication); + STRING(QCoreApplication); + STRING(UnicodeUTF8); + STRING(DefaultCodec); + STRING(CodecForTr); + + if (yyTok != Tok_Ident) + return false; + if (yyWord == strQApplication || yyWord == strQCoreApplication) { + yyTok = getToken(); + if (yyTok == Tok_ColonColon) + yyTok = getToken(); + } + if (yyWord == strUnicodeUTF8) { + *utf8 = true; + yyTok = getToken(); + return true; + } + if (yyWord == strDefaultCodec || yyWord == strCodecForTr) { + *utf8 = false; + yyTok = getToken(); + return true; + } + return false; +} + +bool CppParser::matchInteger(qlonglong *number) +{ + bool matches = (yyTok == Tok_Integer); + if (matches) { + yyTok = getToken(); + *number = yyInteger; + } + return matches; +} + +bool CppParser::matchStringOrNull(QString *s) +{ + bool matches = matchString(s); + qlonglong num = 0; + if (!matches) + matches = matchInteger(&num); + return matches && num == 0; +} + +/* + * match any expression that can return a number, which can be + * 1. Literal number (e.g. '11') + * 2. simple identifier (e.g. 'm_count') + * 3. simple function call (e.g. 'size()' ) + * 4. function call on an object (e.g. 'list.size()') + * 5. function call on an object (e.g. 'list->size()') + * + * Other cases: + * size(2,4) + * list().size() + * list(a,b).size(2,4) + * etc... + */ +bool CppParser::matchExpression() +{ + if (match(Tok_Integer)) + return true; + + int parenlevel = 0; + while (match(Tok_Ident) || parenlevel > 0) { + if (yyTok == Tok_RightParen) { + if (parenlevel == 0) break; + --parenlevel; + yyTok = getToken(); + } else if (yyTok == Tok_LeftParen) { + yyTok = getToken(); + if (yyTok == Tok_RightParen) { + yyTok = getToken(); + } else { + ++parenlevel; + } + } else if (yyTok == Tok_Ident) { + continue; + } else if (yyTok == Tok_Arrow) { + yyTok = getToken(); + } else if (parenlevel == 0) { + return false; + } + } + return true; +} + +QString CppParser::transcode(const QString &str, bool utf8) +{ + static const char tab[] = "abfnrtv"; + static const char backTab[] = "\a\b\f\n\r\t\v"; + const QString in = (!utf8 || yySourceIsUnicode) + ? str : QString::fromUtf8(yySourceCodec->fromUnicode(str).data()); + QString out; + + out.reserve(in.length()); + for (int i = 0; i < in.length();) { + ushort c = in[i++].unicode(); + if (c == '\\') { + if (i >= in.length()) + break; + c = in[i++].unicode(); + + if (c == '\n') + continue; + + if (c == 'x') { + QByteArray hex; + while (i < in.length() && isxdigit((c = in[i].unicode()))) { + hex += c; + i++; + } + out += hex.toUInt(0, 16); + } else if (c >= '0' && c < '8') { + QByteArray oct; + int n = 0; + oct += c; + while (n < 2 && i < in.length() && (c = in[i].unicode()) >= '0' && c < '8') { + i++; + n++; + oct += c; + } + out += oct.toUInt(0, 8); + } else { + const char *p = strchr(tab, c); + out += QChar(QLatin1Char(!p ? c : backTab[p - tab])); + } + } else { + out += c; + } + } + return out; +} + +void CppParser::recordMessage( + int line, const QString &context, const QString &text, const QString &comment, + const QString &extracomment, const QString &msgid, const TranslatorMessage::ExtraData &extra, + bool utf8, bool plural) +{ + TranslatorMessage msg( + transcode(context, utf8), transcode(text, utf8), transcode(comment, utf8), QString(), + yyFileName, line, QStringList(), + TranslatorMessage::Unfinished, plural); + msg.setExtraComment(transcode(extracomment.simplified(), utf8)); + msg.setId(msgid); + msg.setExtras(extra); + if ((utf8 || yyForceUtf8) && !yyCodecIsUtf8 && msg.needs8Bit()) + msg.setUtf8(true); + results->tor->append(msg); +} + +void CppParser::parse(const QString &initialContext, ConversionData &cd, + QSet<QString> &inclusions) +{ + if (results->tor) + yyCodecIsUtf8 = (results->tor->codecName() == "UTF-8"); + + namespaces << &results->rootNamespace; + functionContext = namespaces; + functionContextUnresolved = initialContext; + + parseInternal(cd, inclusions); +} + +void CppParser::parseInternal(ConversionData &cd, QSet<QString> &inclusions) +{ + static QString strColons(QLatin1String("::")); + + QString context; + QString text; + QString comment; + QString extracomment; + QString msgid; + QString sourcetext; + TranslatorMessage::ExtraData extra; + QString prefix; +#ifdef DIAGNOSE_RETRANSLATABILITY + QString functionName; +#endif + int line; + bool utf8; + bool yyTokColonSeen = false; // Start of c'tor's initializer list + + yyWord.reserve(yyInStr.size()); // Rather insane. That's because we do no length checking. + yyCh = getChar(); + yyTok = getToken(); + while (yyTok != Tok_Eof) { + //qDebug() << "TOKEN: " << yyTok; + switch (yyTok) { + case Tok_QuotedInclude: { + text = QDir(QFileInfo(yyFileName).absolutePath()).absoluteFilePath(yyWord); + text.detach(); + if (QFileInfo(text).isFile()) { + processInclude(text, cd, inclusions); + yyTok = getToken(); + break; + } + } + /* fall through */ + case Tok_AngledInclude: { + QStringList cSources = cd.m_allCSources.values(yyWord); + if (!cSources.isEmpty()) { + foreach (const QString &cSource, cSources) + processInclude(cSource, cd, inclusions); + goto incOk; + } + foreach (const QString &incPath, cd.m_includePath) { + text = QDir(incPath).absoluteFilePath(yyWord); + text.detach(); + if (QFileInfo(text).isFile()) { + processInclude(text, cd, inclusions); + goto incOk; + } + } + incOk: + yyTok = getToken(); + break; + } + case Tok_friend: + yyTok = getToken(); + // Ensure that these don't end up being interpreted as forward declarations + // (they are forwards, but with different namespacing). + if (yyTok == Tok_class) + yyTok = getToken(); + break; + case Tok_class: + yyTokColonSeen = false; + /* + Partial support for inlined functions. + */ + yyTok = getToken(); + if (yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0) { + QStringList fct; + do { + /* + This code should execute only once, but we play + safe with impure definitions such as + 'class Q_EXPORT QMessageBox', in which case + 'QMessageBox' is the class name, not 'Q_EXPORT'. + */ + text = yyWord; + text.detach(); + fct = QStringList(text); + yyTok = getToken(); + } while (yyTok == Tok_Ident); + while (yyTok == Tok_ColonColon) { + yyTok = getToken(); + if (yyTok != Tok_Ident) + break; // Oops ... + text = yyWord; + text.detach(); + fct += text; + yyTok = getToken(); + } + if (fct.count() > 1) { + // Forward-declared class definitions can be namespaced + NamespaceList nsl; + if (!fullyQualify(namespaces, fct, true, &nsl, 0)) { + qWarning("%s:%d: Ignoring definition of undeclared qualified class\n", + qPrintable(yyFileName), yyLineNo); + break; + } + namespaceDepths.push(namespaces.count()); + namespaces = nsl; + } else { + namespaceDepths.push(namespaces.count()); + enterNamespace(&namespaces, fct.first()); + } + namespaces.last()->isClass = true; + + while (yyTok == Tok_Comment) + yyTok = getToken(); + if (yyTok == Tok_Colon) { + // Skip any token until '{' since lupdate might do things wrong if it finds + // a '::' token here. + do { + yyTok = getToken(); + } while (yyTok != Tok_LeftBrace && yyTok != Tok_Eof); + } else { + if (yyTok != Tok_LeftBrace) { + // Obviously a forward decl + truncateNamespaces(&namespaces, namespaceDepths.pop()); + break; + } + } + + functionContext = namespaces; + functionContextUnresolved.clear(); // Pointless + prospectiveContext.clear(); + pendingContext.clear(); + } + break; + case Tok_namespace: + yyTokColonSeen = false; + yyTok = getToken(); + if (yyTok == Tok_Ident) { + QString ns = yyWord; + ns.detach(); + yyTok = getToken(); + if (yyTok == Tok_LeftBrace) { + namespaceDepths.push(namespaces.count()); + enterNamespace(&namespaces, ns); + yyTok = getToken(); + } else if (yyTok == Tok_Equals) { + // e.g. namespace Is = OuterSpace::InnerSpace; + QStringList fullName; + yyTok = getToken(); + if (yyTok == Tok_ColonColon) + fullName.append(QString()); + while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) { + if (yyTok == Tok_Ident) { + text = yyWord; + text.detach(); + fullName.append(text); + } + yyTok = getToken(); + } + if (fullName.isEmpty()) + break; + NamespaceList nsl; + if (fullyQualify(namespaces, fullName, false, &nsl, 0)) { + modifyNamespace(&namespaces); + namespaces.last()->aliases.insert(ns, stringListifyNamespace(nsl)); + } + } + } else if (yyTok == Tok_LeftBrace) { + // Anonymous namespace + namespaceDepths.push(namespaces.count()); + yyTok = getToken(); + } + break; + case Tok_using: + yyTok = getToken(); + if (yyTok == Tok_namespace) { + QStringList fullName; + yyTok = getToken(); + if (yyTok == Tok_ColonColon) + fullName.append(QString()); + while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) { + if (yyTok == Tok_Ident) { + text = yyWord; + text.detach(); + fullName.append(text); + } + yyTok = getToken(); + } + NamespaceList nsl; + QStringList unresolved; + if (fullyQualify(namespaces, fullName, false, &nsl, &unresolved)) { + modifyNamespace(&namespaces); + namespaces.last()->usings.insert(stringListifyNamespace(nsl)); + } + } else { + QStringList fullName; + if (yyTok == Tok_ColonColon) + fullName.append(QString()); + while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) { + if (yyTok == Tok_Ident) { + text = yyWord; + text.detach(); + fullName.append(text); + } + yyTok = getToken(); + } + if (fullName.isEmpty()) + break; + NamespaceList nsl; + if (fullyQualify(namespaces, fullName, false, &nsl, 0)) { + modifyNamespace(&namespaces); + namespaces.last()->aliases.insert(nsl.last()->name, stringListifyNamespace(nsl)); + } + } + break; + case Tok_tr: + case Tok_trUtf8: + if (!results->tor) + goto case_default; + if (!sourcetext.isEmpty()) + qWarning("%s:%d: //%% cannot be used with tr() / QT_TR_NOOP(). Ignoring\n", + qPrintable(yyFileName), yyLineNo); + utf8 = (yyTok == Tok_trUtf8); + line = yyLineNo; + yyTok = getToken(); + if (match(Tok_LeftParen) && matchString(&text) && !text.isEmpty()) { + comment.clear(); + bool plural = false; + + if (match(Tok_RightParen)) { + // no comment + } else if (match(Tok_Comma) && matchStringOrNull(&comment)) { //comment + if (match(Tok_RightParen)) { + // ok, + } else if (match(Tok_Comma)) { + plural = true; + } + } + if (!pendingContext.isEmpty()) { + QStringList unresolved; + if (!fullyQualify(namespaces, pendingContext.split(strColons), true, + &functionContext, &unresolved)) { + functionContextUnresolved = unresolved.join(strColons); + qWarning("%s:%d: Qualifying with unknown namespace/class %s::%s\n", + qPrintable(yyFileName), yyLineNo, + qPrintable(stringifyNamespace(functionContext)), + qPrintable(unresolved.first())); + } + pendingContext.clear(); + } + if (prefix.isEmpty()) { + if (functionContextUnresolved.isEmpty()) { + int idx = functionContext.length(); + if (idx < 2) { + qWarning("%s:%d: tr() cannot be called without context\n", + qPrintable(yyFileName), yyLineNo); + break; + } + while (!functionContext.at(idx - 1)->hasTrFunctions) { + if (idx == 1 || !functionContext.at(idx - 2)->isClass) { + idx = functionContext.length(); + if (!functionContext.last()->complained) { + qWarning("%s:%d: Class '%s' lacks Q_OBJECT macro\n", + qPrintable(yyFileName), yyLineNo, + qPrintable(stringifyNamespace(functionContext))); + functionContext.last()->complained = true; + } + break; + } + --idx; + } + context.clear(); + for (int i = 1;;) { + context += functionContext.at(i)->name; + if (++i == idx) + break; + context += strColons; + } + } else { + context = (stringListifyNamespace(functionContext) + << functionContextUnresolved).join(strColons); + } + } else { +#ifdef DIAGNOSE_RETRANSLATABILITY + int last = prefix.lastIndexOf(strColons); + QString className = prefix.mid(last == -1 ? 0 : last + 2); + if (!className.isEmpty() && className == functionName) { + qWarning("%s::%d: It is not recommended to call tr() from within a constructor '%s::%s' ", + qPrintable(yyFileName), yyLineNo, + className.constData(), functionName.constData()); + } +#endif + prefix.chop(2); + NamespaceList nsl; + QStringList unresolved; + if (fullyQualify(functionContext, prefix.split(strColons), false, &nsl, &unresolved)) { + if (!nsl.last()->hasTrFunctions && !nsl.last()->complained) { + qWarning("%s:%d: Class '%s' lacks Q_OBJECT macro\n", + qPrintable(yyFileName), yyLineNo, + qPrintable(stringifyNamespace(nsl))); + nsl.last()->complained = true; + } + context = stringifyNamespace(nsl); + } else { + context = (stringListifyNamespace(nsl) + unresolved).join(strColons); + } + prefix.clear(); + } + + recordMessage(line, context, text, comment, extracomment, msgid, extra, utf8, plural); + } + extracomment.clear(); + msgid.clear(); + extra.clear(); + break; + case Tok_translateUtf8: + case Tok_translate: + if (!results->tor) + goto case_default; + if (!sourcetext.isEmpty()) + qWarning("%s:%d: //%% cannot be used with translate() / QT_TRANSLATE_NOOP(). Ignoring\n", + qPrintable(yyFileName), yyLineNo); + utf8 = (yyTok == Tok_translateUtf8); + line = yyLineNo; + yyTok = getToken(); + if (match(Tok_LeftParen) + && matchString(&context) + && match(Tok_Comma) + && matchString(&text) && !text.isEmpty()) + { + comment.clear(); + bool plural = false; + if (!match(Tok_RightParen)) { + // look for comment + if (match(Tok_Comma) && matchStringOrNull(&comment)) { + if (!match(Tok_RightParen)) { + // look for encoding + if (match(Tok_Comma)) { + if (matchEncoding(&utf8)) { + if (!match(Tok_RightParen)) { + // look for the plural quantifier, + // this can be a number, an identifier or + // a function call, + // so for simplicity we mark it as plural if + // we know we have a comma instead of an + // right parentheses. + plural = match(Tok_Comma); + } + } else { + // This can be a QTranslator::translate("context", + // "source", "comment", n) plural translation + if (matchExpression() && match(Tok_RightParen)) { + plural = true; + } else { + break; + } + } + } else { + break; + } + } + } else { + break; + } + } + recordMessage(line, context, text, comment, extracomment, msgid, extra, utf8, plural); + } + extracomment.clear(); + msgid.clear(); + extra.clear(); + break; + case Tok_trid: + if (!results->tor) + goto case_default; + if (sourcetext.isEmpty()) { + yyTok = getToken(); + } else { + if (!msgid.isEmpty()) + qWarning("%s:%d: //= cannot be used with qtTrId() / QT_TRID_NOOP(). Ignoring\n", + qPrintable(yyFileName), yyLineNo); + //utf8 = false; // Maybe use //%% or something like that + line = yyLineNo; + yyTok = getToken(); + if (match(Tok_LeftParen) && matchString(&msgid) && !msgid.isEmpty()) { + bool plural = match(Tok_Comma); + recordMessage(line, QString(), sourcetext, QString(), extracomment, + msgid, extra, false, plural); + } + sourcetext.clear(); + } + extracomment.clear(); + msgid.clear(); + extra.clear(); + break; + case Tok_Q_DECLARE_TR_FUNCTIONS: + case Tok_Q_OBJECT: + namespaces.last()->hasTrFunctions = true; + yyTok = getToken(); + break; + case Tok_Ident: + prefix += yyWord; + prefix.detach(); + yyTok = getToken(); + if (yyTok != Tok_ColonColon) { + prefix.clear(); + if (yyTok == Tok_Ident && !yyParenDepth) + prospectiveContext.clear(); + } + break; + case Tok_Comment: + if (!results->tor) + goto case_default; + if (yyWord.startsWith(QLatin1Char(':'))) { + yyWord.remove(0, 1); + extracomment += yyWord; + extracomment.detach(); + } else if (yyWord.startsWith(QLatin1Char('='))) { + yyWord.remove(0, 1); + msgid = yyWord.simplified(); + msgid.detach(); + } else if (yyWord.startsWith(QLatin1Char('~'))) { + yyWord.remove(0, 1); + text = yyWord.trimmed(); + int k = text.indexOf(QLatin1Char(' ')); + if (k > -1) + extra.insert(text.left(k), text.mid(k + 1).trimmed()); + text.clear(); + } else if (yyWord.startsWith(QLatin1Char('%'))) { + sourcetext.reserve(sourcetext.length() + yyWord.length()); + ushort *ptr = (ushort *)sourcetext.data() + sourcetext.length(); + int p = 1, c; + forever { + if (p >= yyWord.length()) + break; + c = yyWord.unicode()[p++].unicode(); + if (isspace(c)) + continue; + if (c != '"') { + qWarning("%s:%d: Unexpected character in meta string\n", + qPrintable(yyFileName), yyLineNo); + break; + } + forever { + if (p >= yyWord.length()) { + whoops: + qWarning("%s:%d: Unterminated meta string\n", + qPrintable(yyFileName), yyLineNo); + break; + } + c = yyWord.unicode()[p++].unicode(); + if (c == '"') + break; + if (c == '\\') { + if (p >= yyWord.length()) + goto whoops; + c = yyWord.unicode()[p++].unicode(); + if (c == '\n') + goto whoops; + *ptr++ = '\\'; + } + *ptr++ = c; + } + } + sourcetext.resize(ptr - (ushort *)sourcetext.data()); + } else { + comment = yyWord.simplified(); + if (comment.startsWith(QLatin1String(MagicComment))) { + comment.remove(0, sizeof(MagicComment) - 1); + int k = comment.indexOf(QLatin1Char(' ')); + if (k == -1) { + context = comment; + } else { + context = comment.left(k); + comment.remove(0, k + 1); + recordMessage(yyLineNo, context, QString(), comment, extracomment, + QString(), TranslatorMessage::ExtraData(), false, false); + extracomment.clear(); + results->tor->setExtras(extra); + extra.clear(); + } + } else { + comment.detach(); + } + } + yyTok = getToken(); + break; + case Tok_Arrow: + yyTok = getToken(); + if (yyTok == Tok_tr || yyTok == Tok_trUtf8) + qWarning("%s:%d: Cannot invoke tr() like this\n", + qPrintable(yyFileName), yyLineNo); + break; + case Tok_ColonColon: + if (yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0 && !yyTokColonSeen) + prospectiveContext = prefix; + prefix += strColons; + yyTok = getToken(); +#ifdef DIAGNOSE_RETRANSLATABILITY + if (yyTok == Tok_Ident && yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0) { + functionName = yyWord; + functionName.detach(); + } +#endif + break; + case Tok_RightBrace: + if (yyBraceDepth + 1 == namespaceDepths.count()) { + // class or namespace + Namespace *ns = namespaces.last(); + if (ns->needsTrFunctions && !ns->hasTrFunctions && !ns->complained) { + qWarning("%s:%d: Class '%s' lacks Q_OBJECT macro\n", + qPrintable(yyFileName), yyLineNo, + qPrintable(stringifyNamespace(namespaces))); + ns->complained = true; + } + truncateNamespaces(&namespaces, namespaceDepths.pop()); + } + if (yyBraceDepth == namespaceDepths.count()) { + // function, class or namespace + if (!yyBraceDepth && !directInclude) { + truncateNamespaces(&functionContext, 1); + functionContextUnresolved = cd.m_defaultContext; + } else { + functionContext = namespaces; + functionContextUnresolved.clear(); + } + pendingContext.clear(); + } + // fallthrough + case Tok_Semicolon: + prospectiveContext.clear(); + prefix.clear(); + extracomment.clear(); + msgid.clear(); + extra.clear(); + yyTokColonSeen = false; + yyTok = getToken(); + break; + case Tok_Colon: + if (!prospectiveContext.isEmpty() + && yyBraceDepth == namespaceDepths.count() && yyParenDepth == 0) + pendingContext = prospectiveContext; + yyTokColonSeen = true; + yyTok = getToken(); + break; + case Tok_LeftBrace: + if (!prospectiveContext.isEmpty() + && yyBraceDepth == namespaceDepths.count() + 1 && yyParenDepth == 0) + pendingContext = prospectiveContext; + // fallthrough + case Tok_LeftParen: + case Tok_RightParen: + yyTokColonSeen = false; + yyTok = getToken(); + break; + default: + if (!yyParenDepth) + prospectiveContext.clear(); + case_default: + yyTok = getToken(); + break; + } + } + + if (yyBraceDepth != 0) + qWarning("%s:%d: Unbalanced opening brace in C++ code" + " (or abuse of the C++ preprocessor)\n", + qPrintable(yyFileName), yyBraceLineNo); + else if (yyParenDepth != 0) + qWarning("%s:%d: Unbalanced opening parenthesis in C++ code" + " (or abuse of the C++ preprocessor)\n", + qPrintable(yyFileName), yyParenLineNo); +} + +/* + Fetches tr() calls in C++ code in UI files (inside "<function>" + tag). This mechanism is obsolete. +*/ +void fetchtrInlinedCpp(const QString &in, Translator &translator, const QString &context) +{ + CppParser parser; + parser.setInput(in); + ConversionData cd; + QSet<QString> inclusions; + parser.setTranslator(&translator); + parser.parse(context, cd, inclusions); + parser.deleteResults(); +} + +void loadCPP(Translator &translator, const QStringList &filenames, ConversionData &cd) +{ + QByteArray codecName = cd.m_codecForSource.isEmpty() + ? translator.codecName() : cd.m_codecForSource; + QTextCodec *codec = QTextCodec::codecForName(codecName); + + foreach (const QString &filename, filenames) { + if (CppFiles::getResults(filename) || CppFiles::isBlacklisted(filename)) + continue; + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) { + cd.appendError(QString::fromLatin1("Cannot open %1: %2") + .arg(filename, file.errorString())); + continue; + } + + CppParser parser; + QTextStream ts(&file); + ts.setCodec(codec); + ts.setAutoDetectUnicode(true); + if (ts.codec()->name() == "UTF-16") + translator.setCodecName("System"); + parser.setInput(ts, filename); + Translator *tor = new Translator; + tor->setCodecName(translator.codecName()); + parser.setTranslator(tor); + QSet<QString> inclusions; + parser.parse(cd.m_defaultContext, cd, inclusions); + CppFiles::setResults(filename, parser.getResults()); + } + + foreach (const QString filename, filenames) + if (!CppFiles::isBlacklisted(filename)) + if (Translator *tor = CppFiles::getResults(filename)->tor) + foreach (const TranslatorMessage &msg, tor->messages()) + translator.extend(msg); +} + +QT_END_NAMESPACE diff --git a/tools/linguist/shared/java.cpp b/tools/linguist/lupdate/java.cpp index d98eee6..5f3699b 100644 --- a/tools/linguist/shared/java.cpp +++ b/tools/linguist/lupdate/java.cpp @@ -39,7 +39,9 @@ ** ****************************************************************************/ -#include "translator.h" +#include "lupdate.h" + +#include <translator.h> #include <QtCore/QDebug> #include <QtCore/QFile> @@ -373,22 +375,13 @@ static bool matchString( QString &s ) return true; } -static bool matchInteger( qlonglong *number) -{ - bool matches = (yyTok == Tok_Integer); - if (matches) { - yyTok = getToken(); - *number = yyInteger; - } - return matches; -} - static bool matchStringOrNull(QString &s) { bool matches = matchString(s); if (!matches) { matches = (yyTok == Tok_null); - if (matches) yyTok = getToken(); + if (matches) + yyTok = getToken(); } return matches; } @@ -606,14 +599,18 @@ static void parse( Translator *tor ) } -bool loadJava(Translator &translator, QIODevice &dev, ConversionData &cd) +bool loadJava(Translator &translator, const QString &filename, ConversionData &cd) { - //void LupdateApplication::fetchtr_java( const QString &fileName, Translator *tor, - //const QString &defaultContext, bool mustExist, const QByteArray &codecForSource ) + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) { + cd.appendError(QString::fromLatin1("Cannot open %1: %2") + .arg(filename, file.errorString())); + return false; + } yyDefaultContext = cd.m_defaultContext; yyInPos = -1; - yyFileName = cd.m_sourceFileName; + yyFileName = filename; yyPackage.clear(); yyScope.clear(); yyTok = -1; @@ -621,7 +618,7 @@ bool loadJava(Translator &translator, QIODevice &dev, ConversionData &cd) yyCurLineNo = 0; yyParenLineNo = 1; - QTextStream ts(&dev); + QTextStream ts(&file); QByteArray codecName; if (!cd.m_codecForSource.isEmpty()) codecName = cd.m_codecForSource; @@ -631,7 +628,7 @@ bool loadJava(Translator &translator, QIODevice &dev, ConversionData &cd) ts.setAutoDetectUnicode(true); yyInStr = ts.readAll(); yyInPos = 0; - yyFileName = cd.m_sourceFileName; + yyFileName = filename; yyCurLineNo = 1; yyParenLineNo = 1; yyCh = getChar(); @@ -643,19 +640,4 @@ bool loadJava(Translator &translator, QIODevice &dev, ConversionData &cd) return true; } -int initJava() -{ - Translator::FileFormat format; - format.extension = QLatin1String("java"); - format.fileType = Translator::FileFormat::SourceCode; - format.priority = 0; - format.description = QObject::tr("Java source files"); - format.loader = &loadJava; - format.saver = 0; - Translator::registerFileFormat(format); - return 1; -} - -Q_CONSTRUCTOR_FUNCTION(initJava) - QT_END_NAMESPACE diff --git a/tools/linguist/lupdate/lupdate.1 b/tools/linguist/lupdate/lupdate.1 index 0ebe3bc..8795578 100644 --- a/tools/linguist/lupdate/lupdate.1 +++ b/tools/linguist/lupdate/lupdate.1 @@ -52,12 +52,12 @@ tool for the Qt GUI toolkit. .B Lupdate reads a qmake/tmake project file (.pro file), finds the translatable strings in the specified source, header and interface files, and -updates the translation files (.ts files) specified in it. The +updates the translation files (TS files) specified in it. The translation files are given to the translator who uses .B Qt Linguist to read the files and insert the translations. .PP -The .ts file format is a simple human-readable XML format that can be +The TS file format is a simple human-readable XML format that can be used with version control systems if required. .PP .SH OPTIONS @@ -74,7 +74,7 @@ Default: 'ui,c,c++,cc,cpp,cxx,ch,h,h++,hh,hpp,hxx'. Display the usage and exit. .TP .I "-locations {absolute|relative|none}" -Specify/override how source code references are saved in ts files. +Specify/override how source code references are saved in TS files. Default is absolute. .TP .I "-no-obsolete" @@ -84,7 +84,7 @@ Drop all obsolete strings. Do not recursively scan the following directories. .TP .I "-no-sort" -Do not sort contexts in .ts files. +Do not sort contexts in TS files. .TP .I "-pluralonly" Only include plural form messages. @@ -97,7 +97,7 @@ file syntax but different file suffix Recursively scan the following directories. .TP .I "-silent" -Don't explain what is being done. +Do not explain what is being done. .TP .I "-source-language <language>[_<region>]" Specify/override the language of the source strings. Defaults to @@ -139,8 +139,8 @@ translations will be reused as far as possible, and translated strings that have vanished from the source files are marked obsolete. .PP .B lupdate -can also be invoked with a list of C++ source files, .ui files -and .ts files: +can also be invoked with a list of C++ source files, UI files +and TS files: .PP .in +4 .nf diff --git a/tools/linguist/shared/translatortools.h b/tools/linguist/lupdate/lupdate.h index bba3b3e..73dafb8 100644 --- a/tools/linguist/shared/translatortools.h +++ b/tools/linguist/lupdate/lupdate.h @@ -48,7 +48,9 @@ QT_BEGIN_NAMESPACE +class ConversionData; class QString; +class QStringList; class Translator; class TranslatorMessage; @@ -72,6 +74,12 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(UpdateOptions) Translator merge(const Translator &tor, const Translator &virginTor, UpdateOptions options, QString &err); +void fetchtrInlinedCpp(const QString &in, Translator &translator, const QString &context); +void loadCPP(Translator &translator, const QStringList &filenames, ConversionData &cd); +bool loadJava(Translator &translator, const QString &filename, ConversionData &cd); +bool loadQScript(Translator &translator, const QString &filename, ConversionData &cd); +bool loadUI(Translator &translator, const QString &filename, ConversionData &cd); + QT_END_NAMESPACE #endif diff --git a/tools/linguist/lupdate/lupdate.pro b/tools/linguist/lupdate/lupdate.pro index b05a4ef..ccc2d47 100644 --- a/tools/linguist/lupdate/lupdate.pro +++ b/tools/linguist/lupdate/lupdate.pro @@ -14,9 +14,20 @@ build_all:!build_pass { include(../shared/formats.pri) include(../shared/proparser.pri) -include(../shared/translatortools.pri) -SOURCES += main.cpp +SOURCES += \ + main.cpp \ + merge.cpp \ + ../shared/simtexth.cpp \ + \ + cpp.cpp \ + java.cpp \ + qscript.cpp \ + ui.cpp + +HEADERS += \ + lupdate.h \ + ../shared/simtexth.h DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII diff --git a/tools/linguist/lupdate/main.cpp b/tools/linguist/lupdate/main.cpp index 1ddef30..d38533a 100644 --- a/tools/linguist/lupdate/main.cpp +++ b/tools/linguist/lupdate/main.cpp @@ -39,10 +39,11 @@ ** ****************************************************************************/ -#include "translator.h" -#include "translatortools.h" -#include "profileevaluator.h" -#include "proreader.h" +#include "lupdate.h" + +#include <translator.h> +#include <profileevaluator.h> +#include <proreader.h> #include <QtCore/QCoreApplication> #include <QtCore/QDebug> @@ -84,12 +85,12 @@ static void printUsage() { printOut(QObject::tr( "Usage:\n" - " lupdate [options] [project-file]\n" + " lupdate [options] [project-file]...\n" " lupdate [options] [source-file|path]... -ts ts-files\n\n" - "lupdate is part of Qt's Linguist tool chain. It can be used as a\n" - "stand-alone tool to create XML based translations files in the .ts\n" - "format from translatable messages in C++ and Java source code.\n\n" - "lupdate can also merge such messages into existing .ts files.\n\n" + "lupdate is part of Qt's Linguist tool chain. It extracts translatable\n" + "messages from Qt UI files, C++, Java and JavaScript/QtScript source code.\n" + "Extracted messages are stored in textual translation source files (typically\n" + "Qt TS XML). New and modified messages can be merged into existing TS files.\n\n" "Options:\n" " -help Display this information and exit.\n" " -no-obsolete\n" @@ -103,16 +104,19 @@ static void printUsage() " -silent\n" " Do not explain what is being done.\n" " -no-sort\n" - " Do not sort contexts in .ts files.\n" + " Do not sort contexts in TS files.\n" " -no-recursive\n" " Do not recursively scan the following directories.\n" " -recursive\n" - " Recursively scan the following directories.\n" + " Recursively scan the following directories (default).\n" + " -I <includepath> or -I<includepath>\n" + " Additional location to look for include files.\n" + " May be specified multiple times.\n" " -locations {absolute|relative|none}\n" - " Specify/override how source code references are saved in ts files.\n" + " Specify/override how source code references are saved in TS files.\n" " Default is absolute.\n" " -no-ui-lines\n" - " Do not record line numbers in references to .ui files.\n" + " Do not record line numbers in references to UI files.\n" " -disable-heuristic {sametext|similartext|number}\n" " Disable the named merge heuristic. Can be specified multiple times.\n" " -pro <filename>\n" @@ -223,7 +227,10 @@ int main(int argc, char **argv) QByteArray codecForSource; QStringList tsFileNames; QStringList proFiles; + QMultiHash<QString, QString> allCSources; + QSet<QString> projectRoots; QStringList sourceFiles; + QStringList includePath; QString targetLanguage; QString sourceLanguage; @@ -350,6 +357,18 @@ int main(int argc, char **argv) proFiles += args[i]; numFiles++; continue; + } else if (arg.startsWith(QLatin1String("-I"))) { + if (arg.length() == 2) { + ++i; + if (i == argc) { + qWarning("The -I option should be followed by a path."); + return 1; + } + includePath += args[i]; + } else { + includePath += args[i].mid(2); + } + continue; } else if (arg.startsWith(QLatin1String("-")) && arg != QLatin1String("-")) { qWarning("Unrecognized option '%s'", qPrintable(arg)); return 1; @@ -394,33 +413,46 @@ int main(int argc, char **argv) if (options & Verbose) printOut(QObject::tr("Scanning directory '%1'...").arg(arg)); QDir dir = QDir(fi.filePath()); + projectRoots.insert(dir.absolutePath() + QLatin1Char('/')); if (extensionsNameFilters.isEmpty()) { - extensions = extensions.trimmed(); - // Remove the potential dot in front of each extension - if (extensions.startsWith(QLatin1Char('.'))) - extensions.remove(0,1); - extensions.replace(QLatin1String(",."), QLatin1String(",")); - - extensions.insert(0, QLatin1String("*.")); - extensions.replace(QLatin1Char(','), QLatin1String(",*.")); - extensionsNameFilters = extensions.split(QLatin1Char(',')); + foreach (QString ext, extensions.split(QLatin1Char(','))) { + ext = ext.trimmed(); + if (ext.startsWith(QLatin1Char('.'))) + ext.remove(0,1); + ext.insert(0, QLatin1String("*.")); + extensionsNameFilters << ext; + } } QDir::Filters filters = QDir::Files | QDir::NoSymLinks; QFileInfoList fileinfolist; recursiveFileInfoList(dir, extensionsNameFilters, filters, recursiveScan, &fileinfolist); - QFileInfoList::iterator ii; - QString fn; - for (ii = fileinfolist.begin(); ii != fileinfolist.end(); ++ii) { - // Make sure the path separator is stored with '/' in the ts file - sourceFiles << ii->canonicalFilePath().replace(QLatin1Char('\\'), QLatin1Char('/')); + int scanRootLen = dir.absolutePath().length(); + foreach (const QFileInfo &fi, fileinfolist) { + QString fn = QDir::cleanPath(fi.absoluteFilePath()); + sourceFiles << fn; + + if (!fn.endsWith(QLatin1String(".java")) + && !fn.endsWith(QLatin1String(".ui")) + && !fn.endsWith(QLatin1String(".js")) + && !fn.endsWith(QLatin1String(".qs"))) { + int offset = 0; + int depth = 0; + do { + offset = fn.lastIndexOf(QLatin1Char('/'), offset - 1); + QString ffn = fn.mid(offset + 1); + allCSources.insert(ffn, fn); + } while (++depth < 3 && offset > scanRootLen); + } } } else { - sourceFiles << fi.canonicalFilePath().replace(QLatin1Char('\\'), QLatin1Char('/')); + sourceFiles << QDir::cleanPath(fi.absoluteFilePath());; } } } // for args + foreach (const QString &proFile, proFiles) + projectRoots.insert(QDir::cleanPath(QFileInfo(proFile).absolutePath()) + QLatin1Char('/')); bool firstPass = true; bool fail = false; @@ -428,6 +460,9 @@ int main(int argc, char **argv) ConversionData cd; cd.m_defaultContext = defaultContext; cd.m_noUiLines = options & NoUiLines; + cd.m_projectRoots = projectRoots; + cd.m_includePath = includePath; + cd.m_allCSources = allCSources; QStringList tsFiles = tsFileNames; if (proFiles.count() > 0) { @@ -457,6 +492,8 @@ int main(int argc, char **argv) continue; } + cd.m_includePath += visitor.values(QLatin1String("INCLUDEPATH")); + evaluateProFile(visitor, &variables, pfi.absolutePath()); sourceFiles = variables.value("SOURCES"); @@ -479,30 +516,20 @@ int main(int argc, char **argv) tsFiles += variables.value("TRANSLATIONS"); } + QStringList sourceFilesCpp; for (QStringList::iterator it = sourceFiles.begin(); it != sourceFiles.end(); ++it) { - if (it->endsWith(QLatin1String(".java"), Qt::CaseInsensitive)) { - cd.m_sourceFileName = *it; - fetchedTor.load(*it, cd, QLatin1String("java")); - //fetchtr_java(*it, &fetchedTor, defaultContext, true, codecForSource); - } - else if (it->endsWith(QLatin1String(".jui"), Qt::CaseInsensitive)) { - fetchedTor.load(*it, cd, QLatin1String("jui")); - } - else if (it->endsWith(QLatin1String(".ui"), Qt::CaseInsensitive)) { - fetchedTor.load(*it, cd, QLatin1String("ui")); - //fetchedTor.load(*it + QLatin1String(".h"), cd, QLatin1String("cpp")); - //fetchtr_ui(*it, &fetchedTor, defaultContext, true); - //fetchtr_cpp(*it + QLatin1String(".h"), &fetchedTor, - // defaultContext, false, codecForSource); - } + if (it->endsWith(QLatin1String(".java"), Qt::CaseInsensitive)) + loadJava(fetchedTor, *it, cd); + else if (it->endsWith(QLatin1String(".ui"), Qt::CaseInsensitive) + || it->endsWith(QLatin1String(".jui"), Qt::CaseInsensitive)) + loadUI(fetchedTor, *it, cd); else if (it->endsWith(QLatin1String(".js"), Qt::CaseInsensitive) - || it->endsWith(QLatin1String(".qs"), Qt::CaseInsensitive)) { - fetchedTor.load(*it, cd, QLatin1String("js")); - } else { - fetchedTor.load(*it, cd, QLatin1String("cpp")); - //fetchtr_cpp(*it, &fetchedTor, defaultContext, true, codecForSource); - } + || it->endsWith(QLatin1String(".qs"), Qt::CaseInsensitive)) + loadQScript(fetchedTor, *it, cd); + else + sourceFilesCpp << *it; } + loadCPP(fetchedTor, sourceFilesCpp, cd); if (!cd.error().isEmpty()) printOut(cd.error()); diff --git a/tools/linguist/shared/translatortools.cpp b/tools/linguist/lupdate/merge.cpp index 91298ea..8629e24 100644 --- a/tools/linguist/shared/translatortools.cpp +++ b/tools/linguist/lupdate/merge.cpp @@ -39,7 +39,7 @@ ** ****************************************************************************/ -#include "translatortools.h" +#include "lupdate.h" #include "simtexth.h" #include "translator.h" @@ -193,7 +193,7 @@ static QString translationAttempt(const QString &oldTranslation, */ for (k = 0; k < p; k++) { if (!met[k]) - attempt += QString(QLatin1String(" {")) + newNumbers[k] + QString(QLatin1String("?}")); + attempt += QLatin1String(" {") + newNumbers[k] + QLatin1String("?}"); } /* @@ -205,8 +205,8 @@ static QString translationAttempt(const QString &oldTranslation, for (ell = 0; ell < p; ell++) { if (k != ell && oldNumbers[k] == oldNumbers[ell] && newNumbers[k] < newNumbers[ell]) - attempt += QString(QLatin1String(" {")) + newNumbers[k] + QString(QLatin1String(" or ")) + - newNumbers[ell] + QString(QLatin1String("?}")); + attempt += QLatin1String(" {") + newNumbers[k] + QLatin1String(" or ") + + newNumbers[ell] + QLatin1String("?}"); } } return attempt; diff --git a/tools/linguist/shared/qscript.cpp b/tools/linguist/lupdate/qscript.cpp index 12491bf..2b0b8ad 100644 --- a/tools/linguist/shared/qscript.cpp +++ b/tools/linguist/lupdate/qscript.cpp @@ -752,7 +752,7 @@ const int QScriptGrammar::action_check [] = { #define Q_SCRIPT_REGEXPLITERAL_RULE2 8 -#include "translator.h" +#include <translator.h> #include <QtCore/qdebug.h> #include <QtCore/qnumeric.h> @@ -2356,9 +2356,15 @@ case 94: { } -bool loadQScript(Translator &translator, QIODevice &dev, ConversionData &cd) +bool loadQScript(Translator &translator, const QString &filename, ConversionData &cd) { - QTextStream ts(&dev); + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) { + cd.appendError(QString::fromLatin1("Cannot open %1: %2") + .arg(filename, file.errorString())); + return false; + } + QTextStream ts(&file); QByteArray codecName; if (!cd.m_codecForSource.isEmpty()) codecName = cd.m_codecForSource; @@ -2371,8 +2377,8 @@ bool loadQScript(Translator &translator, QIODevice &dev, ConversionData &cd) QScript::Lexer lexer; lexer.setCode(code, /*lineNumber=*/1); QScriptParser parser; - if (!parser.parse(&lexer, cd.m_sourceFileName, &translator)) { - qWarning("%s:%d: %s", qPrintable(cd.m_sourceFileName), parser.errorLineNumber(), + if (!parser.parse(&lexer, filename, &translator)) { + qWarning("%s:%d: %s", qPrintable(filename), parser.errorLineNumber(), qPrintable(parser.errorMessage())); return false; } @@ -2382,27 +2388,4 @@ bool loadQScript(Translator &translator, QIODevice &dev, ConversionData &cd) return true; } -bool saveQScript(const Translator &translator, QIODevice &dev, ConversionData &cd) -{ - Q_UNUSED(dev); - Q_UNUSED(translator); - cd.appendError(QLatin1String("Cannot save .js files")); - return false; -} - -int initQScript() -{ - Translator::FileFormat format; - format.extension = QLatin1String("js"); - format.fileType = Translator::FileFormat::SourceCode; - format.priority = 0; - format.description = QObject::tr("Qt Script source files"); - format.loader = &loadQScript; - format.saver = &saveQScript; - Translator::registerFileFormat(format); - return 1; -} - -Q_CONSTRUCTOR_FUNCTION(initQScript) - QT_END_NAMESPACE diff --git a/tools/linguist/shared/qscript.g b/tools/linguist/lupdate/qscript.g index 9864cf0..8785f9a 100644 --- a/tools/linguist/shared/qscript.g +++ b/tools/linguist/lupdate/qscript.g @@ -42,6 +42,10 @@ -- ---------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- Process with "qlalr --no-debug --no-lines qscript.g" to update qscript.cpp -- +-------------------------------------------------------------------------------- + %parser QScriptGrammar %merged_output qscript.cpp %expect 3 @@ -81,7 +85,7 @@ %start Program /. -#include "translator.h" +#include <translator.h> #include <QtCore/qdebug.h> #include <QtCore/qnumeric.h> @@ -1986,9 +1990,15 @@ PropertyNameAndValueListOpt: PropertyNameAndValueList ; } -bool loadQScript(Translator &translator, QIODevice &dev, ConversionData &cd) +bool loadQScript(Translator &translator, const QString &filename, ConversionData &cd) { - QTextStream ts(&dev); + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) { + cd.appendError(QString::fromLatin1("Cannot open %1: %2") + .arg(filename, file.errorString())); + return false; + } + QTextStream ts(&file); QByteArray codecName; if (!cd.m_codecForSource.isEmpty()) codecName = cd.m_codecForSource; @@ -2001,8 +2011,8 @@ bool loadQScript(Translator &translator, QIODevice &dev, ConversionData &cd) QScript::Lexer lexer; lexer.setCode(code, /*lineNumber=*/1); QScriptParser parser; - if (!parser.parse(&lexer, cd.m_sourceFileName, &translator)) { - qWarning("%s:%d: %s", qPrintable(cd.m_sourceFileName), parser.errorLineNumber(), + if (!parser.parse(&lexer, filename, &translator)) { + qWarning("%s:%d: %s", qPrintable(filename), parser.errorLineNumber(), qPrintable(parser.errorMessage())); return false; } @@ -2012,28 +2022,5 @@ bool loadQScript(Translator &translator, QIODevice &dev, ConversionData &cd) return true; } -bool saveQScript(const Translator &translator, QIODevice &dev, ConversionData &cd) -{ - Q_UNUSED(dev); - Q_UNUSED(translator); - cd.appendError(QLatin1String("Cannot save .js files")); - return false; -} - -int initQScript() -{ - Translator::FileFormat format; - format.extension = QLatin1String("js"); - format.fileType = Translator::FileFormat::SourceCode; - format.priority = 0; - format.description = QObject::tr("Qt Script source files"); - format.loader = &loadQScript; - format.saver = &saveQScript; - Translator::registerFileFormat(format); - return 1; -} - -Q_CONSTRUCTOR_FUNCTION(initQScript) - QT_END_NAMESPACE ./ diff --git a/tools/linguist/shared/ui.cpp b/tools/linguist/lupdate/ui.cpp index 8dc798a..4c7d7c6 100644 --- a/tools/linguist/shared/ui.cpp +++ b/tools/linguist/lupdate/ui.cpp @@ -39,7 +39,9 @@ ** ****************************************************************************/ -#include "translator.h" +#include "lupdate.h" + +#include <translator.h> #include <QtCore/QDebug> #include <QtCore/QFile> @@ -53,9 +55,6 @@ QT_BEGIN_NAMESPACE -// in cpp.cpp -void fetchtrInlinedCpp(const QString &in, Translator &tor, const QString &context); - class UiReader : public QXmlDefaultHandler { public: @@ -157,7 +156,7 @@ bool UiReader::fatalError(const QXmlParseException &exception) msg.sprintf("XML error: Parse error at line %d, column %d (%s).", exception.lineNumber(), exception.columnNumber(), exception.message().toLatin1().data()); - m_cd.appendError(msg); + m_cd.appendError(msg); return false; } @@ -177,9 +176,16 @@ void UiReader::flush() m_extracomment.clear(); } -bool loadUI(Translator &translator, QIODevice &dev, ConversionData &cd) +bool loadUI(Translator &translator, const QString &filename, ConversionData &cd) { - QXmlInputSource in(&dev); + cd.m_sourceFileName = filename; + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) { + cd.appendError(QString::fromLatin1("Cannot open %1: %2") + .arg(filename, file.errorString())); + return false; + } + QXmlInputSource in(&file); QXmlSimpleReader reader; reader.setFeature(QLatin1String("http://xml.org/sax/features/namespaces"), false); reader.setFeature(QLatin1String("http://xml.org/sax/features/namespace-prefixes"), true); @@ -196,39 +202,4 @@ bool loadUI(Translator &translator, QIODevice &dev, ConversionData &cd) return result; } -bool saveUI(const Translator &translator, QIODevice &dev, ConversionData &cd) -{ - Q_UNUSED(dev); - Q_UNUSED(translator); - cd.appendError(QLatin1String("Cannot save .ui files")); - return false; -} - -int initUI() -{ - Translator::FileFormat format; - - // "real" Qt Designer - format.extension = QLatin1String("ui"); - format.description = QObject::tr("Qt Designer form files"); - format.fileType = Translator::FileFormat::SourceCode; - format.priority = 0; - format.loader = &loadUI; - format.saver = &saveUI; - Translator::registerFileFormat(format); - - // same for jambi - format.extension = QLatin1String("jui"); - format.description = QObject::tr("Qt Jambi form files"); - format.fileType = Translator::FileFormat::SourceCode; - format.priority = 0; - format.loader = &loadUI; - format.saver = &saveUI; - Translator::registerFileFormat(format); - - return 1; -} - -Q_CONSTRUCTOR_FUNCTION(initUI) - QT_END_NAMESPACE diff --git a/tools/linguist/phrasebooks/french.qph b/tools/linguist/phrasebooks/french.qph index f244013..d38da5a 100644 --- a/tools/linguist/phrasebooks/french.qph +++ b/tools/linguist/phrasebooks/french.qph @@ -1,4 +1,5 @@ -<!DOCTYPE QPH><QPH language="fr"> +<!DOCTYPE QPH> +<QPH language="fr"> <phrase> <source>About</source> <target>A propos</target> @@ -1101,4 +1102,228 @@ <source>Yes</source> <target>Oui</target> </phrase> +<phrase> + <source>Split</source> + <target>Scinder</target> +</phrase> +<phrase> + <source>&Edit</source> + <target>&Édition</target> +</phrase> +<phrase> + <source>&Redo</source> + <target>Re&faire</target> +</phrase> +<phrase> + <source>debugger</source> + <target>débogueur</target> +</phrase> +<phrase> + <source>Start Debugger</source> + <target>Lancer le débogueur</target> +</phrase> +<phrase> + <source>Executable:</source> + <target>Exécutable:</target> +</phrase> +<phrase> + <source>Filter:</source> + <target>Filtre:</target> +</phrase> +<phrase> + <source>Clear</source> + <target>Effacer</target> +</phrase> +<phrase> + <source>Host and port:</source> + <target>Hôte et port:</target> +</phrase> +<phrase> + <source>Architecture:</source> + <target>Architecture:</target> +</phrase> +<phrase> + <source>Server start script:</source> + <target>Script de démarrage du serveur:</target> +</phrase> +<phrase> + <source>&Undo</source> + <target>Annu&ler</target> +</phrase> +<phrase> + <source>Add Bookmark</source> + <target>Ajouter un signet</target> +</phrase> +<phrase> + <source>Bookmark:</source> + <target>Signet:</target> +</phrase> +<phrase> + <source>Add in Folder:</source> + <target>Ajouter dans le dossier:</target> +</phrase> +<phrase> + <source>+</source> + <target>+</target> +</phrase> +<phrase> + <source>New Folder</source> + <target>Nouveau dossier</target> +</phrase> +<phrase> + <source>Bookmarks</source> + <target>Signets</target> +</phrase> +<phrase> + <source>Rename Folder</source> + <target>Renommer le dossier</target> +</phrase> +<phrase> + <source>Bookmark</source> + <target>Signet</target> +</phrase> +<phrase> + <source>Remove</source> + <target>Retirer</target> +</phrase> +<phrase> + <source>Delete Folder</source> + <target>Supprimer le dossier</target> +</phrase> +<phrase> + <source>Add</source> + <target>Ajouter</target> +</phrase> +<phrase> + <source>Move Up</source> + <target>Vers le Haut</target> +</phrase> +<phrase> + <source>Move Down</source> + <target>Vers le Bas</target> +</phrase> +<phrase> + <source>Show Bookmark</source> + <target>Afficher le signet</target> +</phrase> +<phrase> + <source>Show Bookmark in New Tab</source> + <target>Afficher le signet dans un nouvel onglet</target> +</phrase> +<phrase> + <source>Delete Bookmark</source> + <target>Supprimer le signet</target> +</phrase> +<phrase> + <source>Rename Bookmark</source> + <target>Renommer le signet</target> +</phrase> +<phrase> + <source>Previous Bookmark</source> + <target>Signet précédent</target> +</phrase> +<phrase> + <source>Next Bookmark</source> + <target>Signet suivant</target> +</phrase> +<phrase> + <source>Condition:</source> + <target>Condition:</target> +</phrase> +<phrase> + <source>Working Directory:</source> + <target>Répertoire de travail:</target> +</phrase> +<phrase> + <source>Environment</source> + <target>Environnement</target> +</phrase> +<phrase> + <source>Arguments</source> + <target>Arguments</target> +</phrase> +<phrase> + <source>Build directory:</source> + <target>Répertoire de compilation:</target> +</phrase> +<phrase> + <source>Path:</source> + <target>Chemin:</target> +</phrase> +<phrase> + <source>General</source> + <target>Général</target> +</phrase> +<phrase> + <source>Username:</source> + <target>Nom d'utilisateur:</target> +</phrase> +<phrase> + <source>User interface</source> + <target>Interface utilisateur</target> +</phrase> +<phrase> + <source>Open Link</source> + <target>Ouvrir le lien</target> +</phrase> +<phrase> + <source> [read only]</source> + <target> [lecture seule]</target> +</phrase> +<phrase> + <source> [directory]</source> + <target> [répertoire]</target> +</phrase> +<phrase> + <source>Close All</source> + <target>Fermer tout</target> +</phrase> +<phrase> + <source>Failed!</source> + <target>Échec!</target> +</phrase> +<phrase> + <source>Proceed</source> + <target>Continuer</target> +</phrase> +<phrase> + <source>Make writable</source> + <target>Rendre inscriptible</target> +</phrase> +<phrase> + <source>Qt Creator</source> + <target>Qt Creator</target> +</phrase> +<phrase> + <source>&File</source> + <target>&Fichier</target> +</phrase> +<phrase> + <source>Activate %1</source> + <target>Activer %1</target> +</phrase> +<phrase> + <source>New Project</source> + <target>Nouveau projet</target> +</phrase> +<phrase> + <source>Close %1</source> + <target>Fermer %1</target> +</phrase> +<phrase> + <source>*</source> + <target>*</target> +</phrase> +<phrase> + <source>&Change</source> + <target>&Modifier</target> +</phrase> +<phrase> + <source>Close Other Editors</source> + <target>Fermer les autres éditeurs</target> +</phrase> +<phrase> + <source>Close All Except %1</source> + <target>Fermer tout sauf %1</target> +</phrase> </QPH> diff --git a/tools/linguist/phrasebooks/polish.qph b/tools/linguist/phrasebooks/polish.qph index 928ffd8..9cedaec 100644 --- a/tools/linguist/phrasebooks/polish.qph +++ b/tools/linguist/phrasebooks/polish.qph @@ -514,7 +514,7 @@ </phrase> <phrase> <source>You need a commercial</source> - <target>Aby móc sprzedawać aplikacjÄ™ utworzone przy pomocy Qt potrzebujesz wersji komercyjnej. ProszÄ™ sprawdzić <a href="http://qt.nokia.com/company/model.html">qt.nokia.com/company/model.html</a> dla poznania sposobu licencjonowania Qt.</target> + <target>Aby móc sprzedawać aplikacjÄ™ utworzone przy pomocy Qt potrzebujesz wersji komercyjnej. ProszÄ™ sprawdzić <a href="http://qt.nokia.com/about">qt.nokia.com/about</a> dla poznania sposobu licencjonowania Qt.</target> </phrase> <phrase> <source>Zoom in</source> diff --git a/tools/linguist/qdoc.conf b/tools/linguist/qdoc.conf index a89fb64..31d44d5 100644 --- a/tools/linguist/qdoc.conf +++ b/tools/linguist/qdoc.conf @@ -3,7 +3,7 @@ DOCDIRS = $QTDIR/tools/linguist/doc EXAMPLEDIRS = $QTDIR/tools/linguist/tutorial OUTPUTDIR = $QTDIR/tools/linguist/doc/html BASE = file:$QTDIR/tools/linguist/doc/html/ -COMPANY = Trolltech +COMPANY = Nokia Corporation and/or its subsidiary(-ies) PRODUCT = Qt Linguist VERSIONSYM = QT_VERSION_STR DEFINE = QT_QDOC QT_.*_SUPPORT _WS_.*_ diff --git a/tools/linguist/shared/abstractproitemvisitor.h b/tools/linguist/shared/abstractproitemvisitor.h index cb61d06..e6031b3 100644 --- a/tools/linguist/shared/abstractproitemvisitor.h +++ b/tools/linguist/shared/abstractproitemvisitor.h @@ -49,19 +49,23 @@ QT_BEGIN_NAMESPACE struct AbstractProItemVisitor { virtual ~AbstractProItemVisitor() {} - virtual bool visitBeginProBlock(ProBlock *block) = 0; - virtual bool visitEndProBlock(ProBlock *block) = 0; - virtual bool visitBeginProVariable(ProVariable *variable) = 0; - virtual bool visitEndProVariable(ProVariable *variable) = 0; + virtual ProItem::ProItemReturn visitBeginProBlock(ProBlock *block) = 0; + virtual void visitEndProBlock(ProBlock *block) = 0; - virtual bool visitBeginProFile(ProFile *value) = 0; - virtual bool visitEndProFile(ProFile *value) = 0; + virtual ProItem::ProItemReturn visitProLoopIteration() = 0; + virtual void visitProLoopCleanup() = 0; - virtual bool visitProValue(ProValue *value) = 0; - virtual bool visitProFunction(ProFunction *function) = 0; - virtual bool visitProOperator(ProOperator *function) = 0; - virtual bool visitProCondition(ProCondition *function) = 0; + virtual void visitBeginProVariable(ProVariable *variable) = 0; + virtual void visitEndProVariable(ProVariable *variable) = 0; + + virtual ProItem::ProItemReturn visitBeginProFile(ProFile *value) = 0; + virtual ProItem::ProItemReturn visitEndProFile(ProFile *value) = 0; + + virtual void visitProValue(ProValue *value) = 0; + virtual ProItem::ProItemReturn visitProFunction(ProFunction *function) = 0; + virtual void visitProOperator(ProOperator *function) = 0; + virtual void visitProCondition(ProCondition *function) = 0; }; QT_END_NAMESPACE diff --git a/tools/linguist/shared/cpp.cpp b/tools/linguist/shared/cpp.cpp deleted file mode 100644 index 4c33e05..0000000 --- a/tools/linguist/shared/cpp.cpp +++ /dev/null @@ -1,1098 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of the Qt Linguist of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** No Commercial Usage -** This file contains pre-release code and may not be distributed. -** You may use this file in accordance with the terms and conditions -** contained in the Technology Preview License Agreement accompanying -** this package. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain -** additional rights. These rights are described in the Nokia Qt LGPL -** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this -** package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -** -** -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "translator.h" - -#include <QtCore/QDebug> -#include <QtCore/QStack> -#include <QtCore/QString> -#include <QtCore/QTextCodec> -#include <QtCore/QTextStream> - -#include <ctype.h> // for isXXX() - -QT_BEGIN_NAMESPACE - -/* qmake ignore Q_OBJECT */ - -static const char MagicComment[] = "TRANSLATOR "; - -static QSet<QString> needs_Q_OBJECT; -static QSet<QString> lacks_Q_OBJECT; - -static const int yyIdentMaxLen = 128; -static const int yyCommentMaxLen = 65536; -static const int yyStringMaxLen = 65536; - -#define STRINGIFY_INTERNAL(x) #x -#define STRINGIFY(x) STRINGIFY_INTERNAL(x) -#define STRING(s) static QString str##s(QLatin1String(STRINGIFY(s))) - -//#define DIAGNOSE_RETRANSLATABILITY -/* - The first part of this source file is the C++ tokenizer. We skip - most of C++; the only tokens that interest us are defined here. - Thus, the code fragment - - int main() - { - printf("Hello, world!\n"); - return 0; - } - - is broken down into the following tokens (Tok_ omitted): - - Ident Ident LeftParen RightParen - LeftBrace - Ident LeftParen String RightParen Semicolon - return Semicolon - RightBrace. - - The 0 doesn't produce any token. -*/ - -enum { - Tok_Eof, Tok_class, Tok_namespace, Tok_return, - Tok_tr = 10, Tok_trUtf8, Tok_translate, Tok_translateUtf8, - Tok_Q_OBJECT = 20, Tok_Q_DECLARE_TR_FUNCTIONS, - Tok_Ident, Tok_Comment, Tok_String, Tok_Arrow, Tok_Colon, Tok_ColonColon, - Tok_Equals, - Tok_LeftBrace = 30, Tok_RightBrace, Tok_LeftParen, Tok_RightParen, Tok_Comma, Tok_Semicolon, - Tok_Integer = 40, - Tok_Other -}; - -/* - The tokenizer maintains the following global variables. The names - should be self-explanatory. -*/ -static QString yyFileName; -static int yyCh; -static bool yyCodecIsUtf8; -static bool yyForceUtf8; -static QString yyIdent; -static QString yyComment; -static QString yyString; -static qlonglong yyInteger; -static QStack<int> yySavedBraceDepth; -static QStack<int> yySavedParenDepth; -static int yyBraceDepth; -static int yyParenDepth; -static int yyLineNo; -static int yyCurLineNo; -static int yyBraceLineNo; -static int yyParenLineNo; -static bool yyTokColonSeen = false; - -// the string to read from and current position in the string -static QTextCodec *yySourceCodec; -static bool yySourceIsUnicode; -static QString yyInStr; -static int yyInPos; - -static uint getChar() -{ - forever { - if (yyInPos >= yyInStr.size()) - return EOF; - uint c = yyInStr[yyInPos++].unicode(); - if (c == '\\' && yyInPos < yyInStr.size()) { - if (yyInStr[yyInPos].unicode() == '\n') { - ++yyCurLineNo; - ++yyInPos; - continue; - } - if (yyInStr[yyInPos].unicode() == '\r') { - ++yyCurLineNo; - ++yyInPos; - if (yyInPos < yyInStr.size() && yyInStr[yyInPos].unicode() == '\n') - ++yyInPos; - continue; - } - } - if (c == '\r') { - if (yyInPos < yyInStr.size() && yyInStr[yyInPos].unicode() == '\n') - ++yyInPos; - c = '\n'; - ++yyCurLineNo; - } else if (c == '\n') { - ++yyCurLineNo; - } - return c; - } -} - -static uint getToken() -{ - yyIdent.clear(); - yyComment.clear(); - yyString.clear(); - - while (yyCh != EOF) { - yyLineNo = yyCurLineNo; - - if (isalpha(yyCh) || yyCh == '_') { - do { - yyIdent += yyCh; - yyCh = getChar(); - } while (isalnum(yyCh) || yyCh == '_'); - - //qDebug() << "IDENT: " << yyIdent; - - switch (yyIdent.at(0).unicode()) { - case 'Q': - if (yyIdent == QLatin1String("Q_OBJECT")) - return Tok_Q_OBJECT; - if (yyIdent == QLatin1String("Q_DECLARE_TR_FUNCTIONS")) - return Tok_Q_DECLARE_TR_FUNCTIONS; - if (yyIdent == QLatin1String("QT_TR_NOOP")) - return Tok_tr; - if (yyIdent == QLatin1String("QT_TRANSLATE_NOOP")) - return Tok_translate; - if (yyIdent == QLatin1String("QT_TRANSLATE_NOOP3")) - return Tok_translate; - if (yyIdent == QLatin1String("QT_TR_NOOP_UTF8")) - return Tok_trUtf8; - if (yyIdent == QLatin1String("QT_TRANSLATE_NOOP_UTF8")) - return Tok_translateUtf8; - if (yyIdent == QLatin1String("QT_TRANSLATE_NOOP3_UTF8")) - return Tok_translateUtf8; - break; - case 'T': - // TR() for when all else fails - if (yyIdent.compare(QLatin1String("TR"), Qt::CaseInsensitive) == 0) { - return Tok_tr; - } - break; - case 'c': - if (yyIdent == QLatin1String("class")) - return Tok_class; - break; - case 'f': - /* - QTranslator::findMessage() has the same parameters as - QApplication::translate(). - */ - if (yyIdent == QLatin1String("findMessage")) - return Tok_translate; - break; - case 'n': - if (yyIdent == QLatin1String("namespace")) - return Tok_namespace; - break; - case 'r': - if (yyIdent == QLatin1String("return")) - return Tok_return; - break; - case 's': - if (yyIdent == QLatin1String("struct")) - return Tok_class; - break; - case 't': - if (yyIdent == QLatin1String("tr")) { - return Tok_tr; - } - if (yyIdent == QLatin1String("trUtf8")) { - return Tok_trUtf8; - } - if (yyIdent == QLatin1String("translate")) { - return Tok_translate; - } - } - return Tok_Ident; - } else { - switch (yyCh) { - case '#': - /* - Early versions of lupdate complained about - unbalanced braces in the following code: - - #ifdef ALPHA - while (beta) { - #else - while (gamma) { - #endif - delta; - } - - The code contains, indeed, two opening braces for - one closing brace; yet there's no reason to panic. - - The solution is to remember yyBraceDepth as it was - when #if, #ifdef or #ifndef was met, and to set - yyBraceDepth to that value when meeting #elif or - #else. - */ - do { - yyCh = getChar(); - } while (isspace(yyCh) && yyCh != '\n'); - - switch (yyCh) { - case 'i': - yyCh = getChar(); - if (yyCh == 'f') { - // if, ifdef, ifndef - yySavedBraceDepth.push(yyBraceDepth); - yySavedParenDepth.push(yyParenDepth); - } - break; - case 'e': - yyCh = getChar(); - if (yyCh == 'l') { - // elif, else - if (!yySavedBraceDepth.isEmpty()) { - yyBraceDepth = yySavedBraceDepth.top(); - yyParenDepth = yySavedParenDepth.top(); - } - } else if (yyCh == 'n') { - // endif - if (!yySavedBraceDepth.isEmpty()) { - yySavedBraceDepth.pop(); - yySavedParenDepth.pop(); - } - } - } - while (isalnum(yyCh) || yyCh == '_') - yyCh = getChar(); - break; - case '/': - yyCh = getChar(); - if (yyCh == '/') { - do { - yyCh = getChar(); - if (yyCh == EOF) - break; - yyComment.append(yyCh); - } while (yyCh != '\n'); - } else if (yyCh == '*') { - bool metAster = false; - bool metAsterSlash = false; - - while (!metAsterSlash) { - yyCh = getChar(); - if (yyCh == EOF) { - qWarning("%s: Unterminated C++ comment starting at" - " line %d\n", - qPrintable(yyFileName), yyLineNo); - return Tok_Comment; - } - yyComment.append(yyCh); - - if (yyCh == '*') - metAster = true; - else if (metAster && yyCh == '/') - metAsterSlash = true; - else - metAster = false; - } - yyCh = getChar(); - yyComment.chop(2); - } - return Tok_Comment; - case '"': - yyCh = getChar(); - while (yyCh != EOF && yyCh != '\n' && yyCh != '"') { - if (yyCh == '\\') { - yyCh = getChar(); - if (yyString.size() < yyStringMaxLen) { - yyString.append(QLatin1Char('\\')); - yyString.append(yyCh); - } - } else { - if (yyString.size() < yyStringMaxLen) - yyString.append(yyCh); - } - yyCh = getChar(); - } - - if (yyCh != '"') - qWarning("%s:%d: Unterminated C++ string", - qPrintable(yyFileName), yyLineNo); - - if (yyCh == EOF) - return Tok_Eof; - yyCh = getChar(); - return Tok_String; - case '-': - yyCh = getChar(); - if (yyCh == '>') { - yyCh = getChar(); - return Tok_Arrow; - } - break; - case ':': - yyCh = getChar(); - if (yyCh == ':') { - yyCh = getChar(); - return Tok_ColonColon; - } - return Tok_Colon; - // Incomplete: '<' might be part of '<=' or of template syntax. - // The main intent of not completely ignoring it is to break - // parsing of things like std::cout << QObject::tr() as - // context std::cout::QObject (see Task 161106) - case '=': - yyCh = getChar(); - return Tok_Equals; - case '>': - case '<': - yyCh = getChar(); - return Tok_Other; - case '\'': - yyCh = getChar(); - if (yyCh == '\\') - yyCh = getChar(); - - do { - yyCh = getChar(); - } while (yyCh != EOF && yyCh != '\''); - yyCh = getChar(); - break; - case '{': - if (yyBraceDepth == 0) - yyBraceLineNo = yyCurLineNo; - yyBraceDepth++; - yyCh = getChar(); - return Tok_LeftBrace; - case '}': - if (yyBraceDepth == 0) - yyBraceLineNo = yyCurLineNo; - yyBraceDepth--; - yyCh = getChar(); - return Tok_RightBrace; - case '(': - if (yyParenDepth == 0) - yyParenLineNo = yyCurLineNo; - yyParenDepth++; - yyCh = getChar(); - return Tok_LeftParen; - case ')': - if (yyParenDepth == 0) - yyParenLineNo = yyCurLineNo; - yyParenDepth--; - yyCh = getChar(); - return Tok_RightParen; - case ',': - yyCh = getChar(); - return Tok_Comma; - case ';': - yyCh = getChar(); - return Tok_Semicolon; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - QByteArray ba; - ba += yyCh; - yyCh = getChar(); - bool hex = yyCh == 'x'; - if (hex) { - ba += yyCh; - yyCh = getChar(); - } - while (hex ? isxdigit(yyCh) : isdigit(yyCh)) { - ba += yyCh; - yyCh = getChar(); - } - bool ok; - yyInteger = ba.toLongLong(&ok); - if (ok) - return Tok_Integer; - break; - } - default: - yyCh = getChar(); - break; - } - } - } - return Tok_Eof; -} - -/* - The second part of this source file is the parser. It accomplishes - a very easy task: It finds all strings inside a tr() or translate() - call, and possibly finds out the context of the call. It supports - three cases: (1) the context is specified, as in - FunnyDialog::tr("Hello") or translate("FunnyDialog", "Hello"); - (2) the call appears within an inlined function; (3) the call - appears within a function defined outside the class definition. -*/ - -static uint yyTok; - -static bool match(uint t) -{ - bool matches = (yyTok == t); - if (matches) - yyTok = getToken(); - return matches; -} - -static bool matchString(QString *s) -{ - bool matches = (yyTok == Tok_String); - s->clear(); - while (yyTok == Tok_String) { - *s += yyString; - do { - yyTok = getToken(); - } while (yyTok == Tok_Comment); - } - return matches; -} - -static bool matchEncoding(bool *utf8) -{ - STRING(QApplication); - STRING(QCoreApplication); - STRING(UnicodeUTF8); - STRING(DefaultCodec); - STRING(CodecForTr); - - if (yyTok != Tok_Ident) - return false; - if (yyIdent == strQApplication || yyIdent == strQCoreApplication) { - yyTok = getToken(); - if (yyTok == Tok_ColonColon) - yyTok = getToken(); - } - if (yyIdent == strUnicodeUTF8) { - *utf8 = true; - yyTok = getToken(); - return true; - } - if (yyIdent == strDefaultCodec || yyIdent == strCodecForTr) { - *utf8 = false; - yyTok = getToken(); - return true; - } - return false; -} - -static bool matchInteger(qlonglong *number) -{ - bool matches = (yyTok == Tok_Integer); - if (matches) { - yyTok = getToken(); - *number = yyInteger; - } - return matches; -} - -static bool matchStringOrNull(QString *s) -{ - bool matches = matchString(s); - qlonglong num = 0; - if (!matches) - matches = matchInteger(&num); - return matches && num == 0; -} - -/* - * match any expression that can return a number, which can be - * 1. Literal number (e.g. '11') - * 2. simple identifier (e.g. 'm_count') - * 3. simple function call (e.g. 'size()' ) - * 4. function call on an object (e.g. 'list.size()') - * 5. function call on an object (e.g. 'list->size()') - * - * Other cases: - * size(2,4) - * list().size() - * list(a,b).size(2,4) - * etc... - */ -static bool matchExpression() -{ - if (match(Tok_Integer)) - return true; - - int parenlevel = 0; - while (match(Tok_Ident) || parenlevel > 0) { - if (yyTok == Tok_RightParen) { - if (parenlevel == 0) break; - --parenlevel; - yyTok = getToken(); - } else if (yyTok == Tok_LeftParen) { - yyTok = getToken(); - if (yyTok == Tok_RightParen) { - yyTok = getToken(); - } else { - ++parenlevel; - } - } else if (yyTok == Tok_Ident) { - continue; - } else if (yyTok == Tok_Arrow) { - yyTok = getToken(); - } else if (parenlevel == 0) { - return false; - } - } - return true; -} - -static QStringList resolveNamespaces( - const QStringList &namespaces, const QHash<QString, QStringList> &namespaceAliases) -{ - static QString strColons(QLatin1String("::")); - - QStringList ns; - foreach (const QString &cns, namespaces) { - ns << cns; - ns = namespaceAliases.value(ns.join(strColons), ns); - } - return ns; -} - -static QStringList getFullyQualifiedNamespaceName( - const QSet<QString> &allNamespaces, const QStringList &namespaces, - const QHash<QString, QStringList> &namespaceAliases, - const QStringList &segments) -{ - static QString strColons(QLatin1String("::")); - - if (segments.first().isEmpty()) { - // fully qualified - QStringList segs = segments; - segs.removeFirst(); - return resolveNamespaces(segs, namespaceAliases); - } else { - for (int n = namespaces.count(); --n >= -1; ) { - QStringList ns; - for (int i = 0; i <= n; ++i) // Note: n == -1 possible - ns << namespaces[i]; - foreach (const QString &cns, segments) { - ns << cns; - ns = namespaceAliases.value(ns.join(strColons), ns); - } - if (allNamespaces.contains(ns.join(strColons))) - return ns; - } - - // Fallback when the namespace was declared in a header, etc. - QStringList ns = namespaces; - ns += segments; - return ns; - } -} - -static QString getFullyQualifiedClassName( - const QSet<QString> &allClasses, const QStringList &namespaces, - const QHash<QString, QStringList> &namespaceAliases, - const QString &ident, bool hasPrefix) -{ - static QString strColons(QLatin1String("::")); - - QString context = ident; - QStringList segments = context.split(strColons); - if (segments.first().isEmpty()) { - // fully qualified - segments.removeFirst(); - context = resolveNamespaces(segments, namespaceAliases).join(strColons); - } else { - for (int n = namespaces.count(); --n >= -1; ) { - QStringList ns; - for (int i = 0; i <= n; ++i) // Note: n == -1 possible - ns.append(namespaces[i]); - foreach (const QString &cns, segments) { - ns.append(cns); - ns = namespaceAliases.value(ns.join(strColons), ns); - } - QString nctx = ns.join(strColons); - if (allClasses.contains(nctx)) { - context = nctx; - goto gotit; - } - } - - if (!hasPrefix && namespaces.count()) - context = namespaces.join(strColons) + strColons + context; - } -gotit: - //qDebug() << "CLASSES:" << allClasses << "NAMEPACES:" << namespaces - // << "IDENT:" << ident << "CONTEXT:" << context; - return context; -} - - -static QString transcode(const QString &str, bool utf8) -{ - static const char tab[] = "abfnrtv"; - static const char backTab[] = "\a\b\f\n\r\t\v"; - const QString in = (!utf8 || yySourceIsUnicode) - ? str : QString::fromUtf8(yySourceCodec->fromUnicode(str).data()); - QString out; - - out.reserve(in.length()); - for (int i = 0; i < in.length();) { - ushort c = in[i++].unicode(); - if (c == '\\') { - if (i >= in.length()) - break; - c = in[i++].unicode(); - - if (c == '\n') - continue; - - if (c == 'x') { - QByteArray hex; - while (i < in.length() && isxdigit((c = in[i].unicode()))) { - hex += c; - i++; - } - out += hex.toUInt(0, 16); - } else if (c >= '0' && c < '8') { - QByteArray oct; - int n = 0; - oct += c; - while (n < 2 && i < in.length() && (c = in[i].unicode()) >= '0' && c < '8') { - i++; - n++; - oct += c; - } - out += oct.toUInt(0, 8); - } else { - const char *p = strchr(tab, c); - out += QChar(QLatin1Char(!p ? c : backTab[p - tab])); - } - } else { - out += c; - } - } - return out; -} - -static void recordMessage( - Translator *tor, int line, const QString &context, const QString &text, const QString &comment, - const QString &extracomment, bool utf8, bool plural) -{ - TranslatorMessage msg( - transcode(context, utf8), transcode(text, utf8), transcode(comment, utf8), QString(), - yyFileName, line, QStringList(), - TranslatorMessage::Unfinished, plural); - msg.setExtraComment(transcode(extracomment.simplified(), utf8)); - if ((utf8 || yyForceUtf8) && !yyCodecIsUtf8 && msg.needs8Bit()) - msg.setUtf8(true); - tor->extend(msg); -} - -static void parse(Translator *tor, const QString &initialContext, const QString &defaultContext) -{ - static QString strColons(QLatin1String("::")); - - QMap<QString, QString> qualifiedContexts; - QSet<QString> allClasses; - QSet<QString> allNamespaces; - QHash<QString, QStringList> namespaceAliases; - QStringList namespaces; - QString context; - QString text; - QString comment; - QString extracomment; - QString functionContext = initialContext; - QString prefix; -#ifdef DIAGNOSE_RETRANSLATABILITY - QString functionName; -#endif - int line; - bool utf8 = false; - bool missing_Q_OBJECT = false; - - yyTok = getToken(); - while (yyTok != Tok_Eof) { - //qDebug() << "TOKEN: " << yyTok; - switch (yyTok) { - case Tok_class: - yyTokColonSeen = false; - /* - Partial support for inlined functions. - */ - yyTok = getToken(); - if (yyBraceDepth == namespaces.count() && yyParenDepth == 0) { - QStringList fct; - do { - /* - This code should execute only once, but we play - safe with impure definitions such as - 'class Q_EXPORT QMessageBox', in which case - 'QMessageBox' is the class name, not 'Q_EXPORT'. - */ - fct = QStringList(yyIdent); - yyTok = getToken(); - } while (yyTok == Tok_Ident); - while (yyTok == Tok_ColonColon) { - yyTok = getToken(); - if (yyTok != Tok_Ident) - break; // Oops ... - fct += yyIdent; - yyTok = getToken(); - } - functionContext = resolveNamespaces(namespaces + fct, namespaceAliases).join(strColons); - allClasses.insert(functionContext); - - if (yyTok == Tok_Colon) { - missing_Q_OBJECT = true; - // Skip any token until '{' since lupdate might do things wrong if it finds - // a '::' token here. - do { - yyTok = getToken(); - } while (yyTok != Tok_LeftBrace && yyTok != Tok_Eof); - } else { - //functionContext = defaultContext; - } - } - break; - case Tok_namespace: - yyTokColonSeen = false; - yyTok = getToken(); - if (yyTok == Tok_Ident) { - QString ns = yyIdent; - yyTok = getToken(); - if (yyTok == Tok_LeftBrace) { - if (yyBraceDepth == namespaces.count() + 1) { - namespaces.append(ns); - allNamespaces.insert(namespaces.join(strColons)); - } - } else if (yyTok == Tok_Equals) { - // e.g. namespace Is = OuterSpace::InnerSpace; - QStringList alias = namespaces; - alias.append(ns); - QStringList fullName; - yyTok = getToken(); - if (yyTok == Tok_ColonColon) - fullName.append(QString()); - while (yyTok == Tok_ColonColon || yyTok == Tok_Ident) { - if (yyTok == Tok_Ident) - fullName.append(yyIdent); - yyTok = getToken(); - } - namespaceAliases[alias.join(strColons)] = - getFullyQualifiedNamespaceName(allNamespaces, namespaces, namespaceAliases, fullName); - } - } - break; - case Tok_tr: - case Tok_trUtf8: - utf8 = (yyTok == Tok_trUtf8); - line = yyLineNo; - yyTok = getToken(); - if (match(Tok_LeftParen) && matchString(&text) && !text.isEmpty()) { - comment.clear(); - bool plural = false; - - if (match(Tok_RightParen)) { - // no comment - } else if (match(Tok_Comma) && matchStringOrNull(&comment)) { //comment - if (match(Tok_RightParen)) { - // ok, - } else if (match(Tok_Comma)) { - plural = true; - } - } - if (prefix.isEmpty()) { - context = functionContext; - } else { -#ifdef DIAGNOSE_RETRANSLATABILITY - int last = prefix.lastIndexOf(strColons); - QString className = prefix.mid(last == -1 ? 0 : last + 2); - if (!className.isEmpty() && className == functionName) { - qWarning("%s::%d: It is not recommended to call tr() from within a constructor '%s::%s' ", - qPrintable(yyFileName), yyLineNo, - className.constData(), functionName.constData()); - } -#endif - prefix.chop(2); - context = getFullyQualifiedClassName(allClasses, namespaces, namespaceAliases, prefix, true); - } - prefix.clear(); - if (qualifiedContexts.contains(context)) - context = qualifiedContexts[context]; - - if (!text.isEmpty()) - recordMessage(tor, line, context, text, comment, extracomment, utf8, plural); - - if (lacks_Q_OBJECT.contains(context)) { - qWarning("%s:%d: Class '%s' lacks Q_OBJECT macro", - qPrintable(yyFileName), yyLineNo, - qPrintable(context)); - lacks_Q_OBJECT.remove(context); - } else { - needs_Q_OBJECT.insert(context); - } - } - extracomment.clear(); - break; - case Tok_translateUtf8: - case Tok_translate: - utf8 = (yyTok == Tok_translateUtf8); - line = yyLineNo; - yyTok = getToken(); - if (match(Tok_LeftParen) - && matchString(&context) - && match(Tok_Comma) - && matchString(&text)) - { - comment.clear(); - bool plural = false; - if (!match(Tok_RightParen)) { - // look for comment - if (match(Tok_Comma) && matchStringOrNull(&comment)) { - if (!match(Tok_RightParen)) { - // look for encoding - if (match(Tok_Comma)) { - if (matchEncoding(&utf8)) { - if (!match(Tok_RightParen)) { - // look for the plural quantifier, - // this can be a number, an identifier or - // a function call, - // so for simplicity we mark it as plural if - // we know we have a comma instead of an - // right parentheses. - plural = match(Tok_Comma); - } - } else { - // This can be a QTranslator::translate("context", - // "source", "comment", n) plural translation - if (matchExpression() && match(Tok_RightParen)) { - plural = true; - } else { - break; - } - } - } else { - break; - } - } - } else { - break; - } - } - if (!text.isEmpty()) - recordMessage(tor, line, context, text, comment, extracomment, utf8, plural); - } - extracomment.clear(); - break; - case Tok_Q_DECLARE_TR_FUNCTIONS: - case Tok_Q_OBJECT: - missing_Q_OBJECT = false; - yyTok = getToken(); - break; - case Tok_Ident: - prefix += yyIdent; - yyTok = getToken(); - if (yyTok != Tok_ColonColon) - prefix.clear(); - break; - case Tok_Comment: - if (yyComment.startsWith(QLatin1Char(':'))) { - yyComment.remove(0, 1); - extracomment.append(yyComment); - } else { - comment = yyComment.simplified(); - if (comment.startsWith(QLatin1String(MagicComment))) { - comment.remove(0, sizeof(MagicComment) - 1); - int k = comment.indexOf(QLatin1Char(' ')); - if (k == -1) { - context = comment; - } else { - context = comment.left(k); - comment.remove(0, k + 1); - recordMessage(tor, yyLineNo, context, QString(), comment, extracomment, false, false); - } - - /* - Provide a backdoor for people using "using - namespace". See the manual for details. - */ - k = 0; - while ((k = context.indexOf(strColons, k)) != -1) { - qualifiedContexts.insert(context.mid(k + 2), context); - k++; - } - } - } - yyTok = getToken(); - break; - case Tok_Arrow: - yyTok = getToken(); - if (yyTok == Tok_tr || yyTok == Tok_trUtf8) - qWarning("%s:%d: Cannot invoke tr() like this", - qPrintable(yyFileName), yyLineNo); - break; - case Tok_ColonColon: - if (yyBraceDepth == namespaces.count() && yyParenDepth == 0 && !yyTokColonSeen) - functionContext = getFullyQualifiedClassName(allClasses, namespaces, namespaceAliases, prefix, false); - prefix += strColons; - yyTok = getToken(); -#ifdef DIAGNOSE_RETRANSLATABILITY - if (yyTok == Tok_Ident && yyBraceDepth == namespaces.count() && yyParenDepth == 0) - functionName = yyIdent; -#endif - break; - case Tok_RightBrace: - case Tok_Semicolon: - prefix.clear(); - extracomment.clear(); - yyTokColonSeen = false; - if (yyBraceDepth >= 0 && yyBraceDepth + 1 == namespaces.count()) - namespaces.removeLast(); - if (yyBraceDepth == namespaces.count()) { - if (missing_Q_OBJECT) { - if (needs_Q_OBJECT.contains(functionContext)) { - qWarning("%s:%d: Class '%s' lacks Q_OBJECT macro", - qPrintable(yyFileName), yyLineNo, - qPrintable(functionContext)); - } else { - lacks_Q_OBJECT.insert(functionContext); - } - } - functionContext = defaultContext; - missing_Q_OBJECT = false; - } - yyTok = getToken(); - break; - case Tok_Colon: - yyTokColonSeen = true; - yyTok = getToken(); - break; - case Tok_LeftParen: - case Tok_RightParen: - case Tok_LeftBrace: - yyTokColonSeen = false; - yyTok = getToken(); - break; - default: - yyTok = getToken(); - break; - } - } - - if (yyBraceDepth != 0) - qWarning("%s:%d: Unbalanced braces in C++ code (or abuse of the C++" - " preprocessor)\n", - qPrintable(yyFileName), yyBraceLineNo); - else if (yyParenDepth != 0) - qWarning("%s:%d: Unbalanced parentheses in C++ code (or abuse of the C++" - " preprocessor)\n", - qPrintable(yyFileName), yyParenLineNo); -} - -/* - Fetches tr() calls in C++ code in UI files (inside "<function>" - tag). This mechanism is obsolete. -*/ -void fetchtrInlinedCpp(const QString &in, Translator &translator, const QString &context) -{ - yyInStr = in; - yyInPos = 0; - yyFileName = QString(); - yyCodecIsUtf8 = (translator.codecName() == "UTF-8"); - yyForceUtf8 = true; - yySourceIsUnicode = true; - yySavedBraceDepth.clear(); - yySavedParenDepth.clear(); - yyBraceDepth = 0; - yyParenDepth = 0; - yyCurLineNo = 1; - yyBraceLineNo = 1; - yyParenLineNo = 1; - yyCh = getChar(); - - parse(&translator, context, QString()); -} - - -bool loadCPP(Translator &translator, QIODevice &dev, ConversionData &cd) -{ - QString defaultContext = cd.m_defaultContext; - - yyCodecIsUtf8 = (translator.codecName() == "UTF-8"); - yyForceUtf8 = false; - QTextStream ts(&dev); - QByteArray codecName = cd.m_codecForSource.isEmpty() - ? translator.codecName() : cd.m_codecForSource; - ts.setCodec(QTextCodec::codecForName(codecName)); - ts.setAutoDetectUnicode(true); - yySourceCodec = ts.codec(); - if (yySourceCodec->name() == "UTF-16") - translator.setCodecName("System"); - yySourceIsUnicode = yySourceCodec->name().startsWith("UTF-"); - yyInStr = ts.readAll(); - yyInPos = 0; - yyFileName = cd.m_sourceFileName; - yySavedBraceDepth.clear(); - yySavedParenDepth.clear(); - yyBraceDepth = 0; - yyParenDepth = 0; - yyCurLineNo = 1; - yyBraceLineNo = 1; - yyParenLineNo = 1; - yyCh = getChar(); - - parse(&translator, defaultContext, defaultContext); - - return true; -} - -int initCPP() -{ - Translator::FileFormat format; - format.extension = QLatin1String("cpp"); - format.fileType = Translator::FileFormat::SourceCode; - format.priority = 0; - format.description = QObject::tr("C++ source files"); - format.loader = &loadCPP; - format.saver = 0; - Translator::registerFileFormat(format); - return 1; -} - -Q_CONSTRUCTOR_FUNCTION(initCPP) - -QT_END_NAMESPACE diff --git a/tools/linguist/shared/formats.pri b/tools/linguist/shared/formats.pri index 9c8072b..985f6db 100644 --- a/tools/linguist/shared/formats.pri +++ b/tools/linguist/shared/formats.pri @@ -19,8 +19,4 @@ SOURCES += \ $$PWD/qph.cpp \ $$PWD/po.cpp \ $$PWD/ts.cpp \ - $$PWD/ui.cpp \ - $$PWD/cpp.cpp \ - $$PWD/java.cpp \ - $$PWD/qscript.cpp \ $$PWD/xliff.cpp diff --git a/tools/linguist/shared/make-qscript.sh b/tools/linguist/shared/make-qscript.sh deleted file mode 100755 index 42cab7a..0000000 --- a/tools/linguist/shared/make-qscript.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -me=$(dirname $0) -mkdir -p $me/out -(cd $me/out && ${QLALR-qlalr} --no-debug --troll --no-lines ../qscript.g) - -for f in $me/out/*.{h,cpp}; do - n=$(basename $f) - p4 open $me/../$n - cp $f $me/../$n -done - -p4 revert -a $me/../... -p4 diff -du $me/../... diff --git a/tools/linguist/shared/po.cpp b/tools/linguist/shared/po.cpp index ab5c0d2..6f06d58 100644 --- a/tools/linguist/shared/po.cpp +++ b/tools/linguist/shared/po.cpp @@ -396,7 +396,10 @@ bool loadPO(Translator &translator, QIODevice &dev, ConversionData &cd) const QString prefix = QLatin1String(isObsolete ? "#~ " : ""); while (true) { int idx = line.indexOf(QLatin1Char(' '), prefix.length()); - item.msgStr.append(slurpEscapedString(lines, l, idx, prefix, cd)); + QString str = slurpEscapedString(lines, l, idx, prefix, cd); + str.replace(QChar(Translator::TextVariantSeparator), + QChar(Translator::BinaryVariantSeparator)); + item.msgStr.append(str); if (l + 1 >= lines.size() || !isTranslationLine(lines.at(l + 1))) break; ++l; @@ -636,8 +639,11 @@ bool savePO(const Translator &translator, QIODevice &dev, ConversionData &cd) out << poEscapedString(prefix, QLatin1String("msgid_plural"), noWrap, plural); const QStringList &translations = msg.translations(); for (int i = 0; i != translations.size(); ++i) { + QString str = translations.at(i); + str.replace(QChar(Translator::BinaryVariantSeparator), + QChar(Translator::TextVariantSeparator)); out << poEscapedString(prefix, QString::fromLatin1("msgstr[%1]").arg(i), noWrap, - translations.at(i)); + str); } } first = false; diff --git a/tools/linguist/shared/profileevaluator.cpp b/tools/linguist/shared/profileevaluator.cpp index 7425afd..67b393c 100644 --- a/tools/linguist/shared/profileevaluator.cpp +++ b/tools/linguist/shared/profileevaluator.cpp @@ -44,6 +44,7 @@ #include "proitems.h" #include <QtCore/QByteArray> +#include <QtCore/QDateTime> #include <QtCore/QDebug> #include <QtCore/QDir> #include <QtCore/QFile> @@ -56,6 +57,15 @@ #include <QtCore/QStringList> #include <QtCore/QTextStream> +#ifdef Q_OS_UNIX +#include <unistd.h> +#include <sys/utsname.h> +#else +#include <Windows.h> +#endif +#include <stdio.h> +#include <stdlib.h> + #ifdef Q_OS_WIN32 #define QT_POPEN _popen #define QT_PCLOSE _pclose @@ -68,6 +78,58 @@ QT_BEGIN_NAMESPACE /////////////////////////////////////////////////////////////////////// // +// Option +// +/////////////////////////////////////////////////////////////////////// + +QString +Option::fixString(QString string, uchar flags) +{ + // XXX Ripped out caching, so this will be slow. Should not matter for current uses. + + //fix the environment variables + if (flags & Option::FixEnvVars) { + int rep; + QRegExp reg_variableName(QLatin1String("\\$\\(.*\\)")); + reg_variableName.setMinimal(true); + while ((rep = reg_variableName.indexIn(string)) != -1) + string.replace(rep, reg_variableName.matchedLength(), + QString::fromLocal8Bit(qgetenv(string.mid(rep + 2, reg_variableName.matchedLength() - 3).toLatin1().constData()).constData())); + } + + //canonicalize it (and treat as a path) + if (flags & Option::FixPathCanonicalize) { +#if 0 + string = QFileInfo(string).canonicalFilePath(); +#endif + string = QDir::cleanPath(string); + } + + if (string.length() > 2 && string[0].isLetter() && string[1] == QLatin1Char(':')) + string[0] = string[0].toLower(); + + //fix separators + Q_ASSERT(!((flags & Option::FixPathToLocalSeparators) && (flags & Option::FixPathToTargetSeparators))); + if (flags & Option::FixPathToLocalSeparators) { +#if defined(Q_OS_WIN32) + string = string.replace(QLatin1Char('/'), QLatin1Char('\\')); +#else + string = string.replace(QLatin1Char('\\'), QLatin1Char('/')); +#endif + } else if (flags & Option::FixPathToTargetSeparators) { + string = string.replace(QLatin1Char('/'), Option::dir_sep) + .replace(QLatin1Char('\\'), Option::dir_sep); + } + + if ((string.startsWith(QLatin1Char('"')) && string.endsWith(QLatin1Char('"'))) || + (string.startsWith(QLatin1Char('\'')) && string.endsWith(QLatin1Char('\'')))) + string = string.mid(1, string.length() - 2); + + return string; +} + +/////////////////////////////////////////////////////////////////////// +// // ProFileEvaluator::Private // /////////////////////////////////////////////////////////////////////// @@ -77,6 +139,12 @@ class ProFileEvaluator::Private : public AbstractProItemVisitor public: Private(ProFileEvaluator *q_); + ProFileEvaluator *q; + int m_lineNo; // Error reporting + bool m_verbose; + + /////////////// Reading pro file + bool read(ProFile *pro); ProBlock *currentBlock(); @@ -89,73 +157,126 @@ public: void leaveScope(); void finalizeBlock(); + QStack<ProBlock *> m_blockstack; + ProBlock *m_block; + + ProItem *m_commentItem; + QString m_proitem; + QString m_pendingComment; + bool m_syntaxError; + bool m_contNextLine; + bool m_inQuote; + int m_parens; + + /////////////// Evaluating pro file contents + // implementation of AbstractProItemVisitor - bool visitBeginProBlock(ProBlock *block); - bool visitEndProBlock(ProBlock *block); - bool visitBeginProVariable(ProVariable *variable); - bool visitEndProVariable(ProVariable *variable); - bool visitBeginProFile(ProFile *value); - bool visitEndProFile(ProFile *value); - bool visitProValue(ProValue *value); - bool visitProFunction(ProFunction *function); - bool visitProOperator(ProOperator *oper); - bool visitProCondition(ProCondition *condition); + ProItem::ProItemReturn visitBeginProBlock(ProBlock *block); + void visitEndProBlock(ProBlock *block); + ProItem::ProItemReturn visitProLoopIteration(); + void visitProLoopCleanup(); + void visitBeginProVariable(ProVariable *variable); + void visitEndProVariable(ProVariable *variable); + ProItem::ProItemReturn visitBeginProFile(ProFile *value); + ProItem::ProItemReturn visitEndProFile(ProFile *value); + void visitProValue(ProValue *value); + ProItem::ProItemReturn visitProFunction(ProFunction *function); + void visitProOperator(ProOperator *oper); + void visitProCondition(ProCondition *condition); QStringList valuesDirect(const QString &variableName) const { return m_valuemap[variableName]; } QStringList values(const QString &variableName) const; QStringList values(const QString &variableName, const ProFile *pro) const; + QStringList values(const QString &variableName, const QHash<QString, QStringList> &place, + const ProFile *pro) const; QString propertyValue(const QString &val) const; bool isActiveConfig(const QString &config, bool regex = false); QStringList expandVariableReferences(const QString &value); + void doVariableReplace(QString *str); QStringList evaluateExpandFunction(const QString &function, const QString &arguments); QString format(const char *format) const; QString currentFileName() const; - QString getcwd() const; + QString currentDirectory() const; ProFile *currentProFile() const; - bool evaluateConditionalFunction(const QString &function, const QString &arguments, bool *result); - bool evaluateFile(const QString &fileName, bool *result); - bool evaluateFeatureFile(const QString &fileName, bool *result); + ProItem::ProItemReturn evaluateConditionalFunction(const QString &function, const QString &arguments); + bool evaluateFile(const QString &fileName); + bool evaluateFeatureFile(const QString &fileName); - QStringList qmakeFeaturePaths(); + static inline ProItem::ProItemReturn returnBool(bool b) + { return b ? ProItem::ReturnTrue : ProItem::ReturnFalse; } - ProFileEvaluator *q; + QStringList evaluateFunction(ProBlock *funcPtr, const QStringList &argumentsList, bool *ok); - QStack<ProBlock *> m_blockstack; - ProBlock *m_block; + QStringList qmakeFeaturePaths(); - ProItem *m_commentItem; - QString m_proitem; - QString m_pendingComment; - bool m_syntaxError; - bool m_contNextLine; - bool m_condition; - bool m_invertNext; + struct State { + bool condition; + bool prevCondition; + } m_sts; + bool m_invertNext; // Short-lived, so not in State + int m_skipLevel; + bool m_cumulative; + bool m_isFirstVariableValue; QString m_lastVarName; ProVariable::VariableOperator m_variableOperator; - int m_lineNo; // Error reporting + QString m_origfile; QString m_oldPath; // To restore the current path to the path QStack<ProFile*> m_profileStack; // To handle 'include(a.pri), so we can track back to 'a.pro' when finished with 'a.pri' + struct ProLoop { + QString variable; + QStringList oldVarVal; + QStringList list; + int index; + bool infinite; + }; + QStack<ProLoop> m_loopStack; + + // we need the following two variables for handling + // CONFIG = foo bar $$CONFIG + QHash<QString, QStringList> m_tempValuemap; // used while evaluating (variable operator value1 value2 ...) + QHash<const ProFile*, QHash<QString, QStringList> > m_tempFilevaluemap; // used while evaluating (variable operator value1 value2 ...) QHash<QString, QStringList> m_valuemap; // VariableName must be us-ascii, the content however can be non-us-ascii. QHash<const ProFile*, QHash<QString, QStringList> > m_filevaluemap; // Variables per include file QHash<QString, QString> m_properties; - QString m_origfile; + QString m_outputDir; + + bool m_definingTest; + QString m_definingFunc; + QHash<QString, ProBlock *> m_testFunctions; + QHash<QString, ProBlock *> m_replaceFunctions; + QStringList m_returnValue; + QStack<QHash<QString, QStringList> > m_valuemapStack; + QStack<QHash<const ProFile*, QHash<QString, QStringList> > > m_filevaluemapStack; int m_prevLineNo; // Checking whether we're assigning the same TARGET ProFile *m_prevProFile; // See m_prevLineNo - - bool m_verbose; }; +Q_DECLARE_TYPEINFO(ProFileEvaluator::Private::State, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(ProFileEvaluator::Private::ProLoop, Q_MOVABLE_TYPE); + ProFileEvaluator::Private::Private(ProFileEvaluator *q_) : q(q_) { + // Global parser state m_prevLineNo = 0; m_prevProFile = 0; + + // Configuration, more or less m_verbose = true; + m_cumulative = true; + + // Evaluator state + m_sts.condition = false; + m_sts.prevCondition = false; + m_invertNext = false; + m_skipLevel = 0; + m_isFirstVariableValue = true; + m_definingFunc.clear(); } bool ProFileEvaluator::Private::read(ProFile *pro) @@ -166,8 +287,11 @@ bool ProFileEvaluator::Private::read(ProFile *pro) return false; } + // Parser state m_block = 0; m_commentItem = 0; + m_inQuote = false; + m_parens = 0; m_contNextLine = false; m_syntaxError = false; m_lineNo = 1; @@ -191,71 +315,84 @@ bool ProFileEvaluator::Private::parseLine(const QString &line0) if (m_blockstack.isEmpty()) return false; - ushort quote = 0; - int parens = 0; - bool contNextLine = false; + int parens = m_parens; + bool inQuote = m_inQuote; + bool escaped = false; QString line = line0.simplified(); for (int i = 0; !m_syntaxError && i < line.length(); ++i) { ushort c = line.at(i).unicode(); - if (quote && c == quote) - quote = 0; - else if (c == '(') - ++parens; - else if (c == ')') - --parens; - else if (c == '"' && (i == 0 || line.at(i - 1).unicode() != '\\')) - quote = c; - else if (!parens && !quote) { - if (c == '#') { - insertComment(line.mid(i + 1)); - contNextLine = m_contNextLine; - break; - } - if (c == '\\' && i >= line.count() - 1) { - updateItem(); - contNextLine = true; - continue; - } - if (m_block && (m_block->blockKind() & ProBlock::VariableKind)) { - if (c == ' ') - updateItem(); - else - m_proitem += c; - continue; - } - if (c == ':') { - enterScope(false); - continue; - } - if (c == '{') { - enterScope(true); - continue; - } - if (c == '}') { - leaveScope(); + if (c == '#') { // Yep - no escaping possible + insertComment(line.mid(i + 1)); + escaped = m_contNextLine; + break; + } + if (!escaped) { + if (c == '\\') { + escaped = true; + m_proitem += c; continue; - } - if (c == '=') { - insertVariable(line, &i); + } else if (c == '"') { + inQuote = !inQuote; + m_proitem += c; continue; } - if (c == '|' || c == '!') { - insertOperator(c); - continue; + } else { + escaped = false; + } + if (!inQuote) { + if (c == '(') { + ++parens; + } else if (c == ')') { + --parens; + } else if (!parens) { + if (m_block && (m_block->blockKind() & ProBlock::VariableKind)) { + if (c == ' ') + updateItem(); + else + m_proitem += c; + continue; + } + if (c == ':') { + enterScope(false); + continue; + } + if (c == '{') { + enterScope(true); + continue; + } + if (c == '}') { + leaveScope(); + continue; + } + if (c == '=') { + insertVariable(line, &i); + continue; + } + if (c == '|' || c == '!') { + insertOperator(c); + continue; + } } } m_proitem += c; } - m_contNextLine = contNextLine; - - if (!m_syntaxError) { + m_inQuote = inQuote; + m_parens = parens; + m_contNextLine = escaped; + if (escaped) { + m_proitem.chop(1); updateItem(); - if (!m_contNextLine) + return true; + } else { + if (!m_syntaxError) { + updateItem(); finalizeBlock(); + return true; + } + return false; } - return !m_syntaxError; } void ProFileEvaluator::Private::finalizeBlock() @@ -451,85 +588,220 @@ void ProFileEvaluator::Private::updateItem() } -bool ProFileEvaluator::Private::visitBeginProBlock(ProBlock *block) +ProItem::ProItemReturn ProFileEvaluator::Private::visitBeginProBlock(ProBlock *block) { - if (block->blockKind() == ProBlock::ScopeKind) { - m_invertNext = false; - m_condition = false; + if (block->blockKind() & ProBlock::ScopeContentsKind) { + if (!m_definingFunc.isEmpty()) { + if (!m_skipLevel || m_cumulative) { + QHash<QString, ProBlock *> *hash = + (m_definingTest ? &m_testFunctions : &m_replaceFunctions); + if (ProBlock *def = hash->value(m_definingFunc)) + def->deref(); + hash->insert(m_definingFunc, block); + block->ref(); + block->setBlockKind(block->blockKind() | ProBlock::FunctionBodyKind); + } + m_definingFunc.clear(); + return ProItem::ReturnSkip; + } else if (!(block->blockKind() & ProBlock::FunctionBodyKind)) { + if (!m_sts.condition) + ++m_skipLevel; + else + Q_ASSERT(!m_skipLevel); + } + } else { + if (!m_skipLevel) { + if (m_sts.condition) { + m_sts.prevCondition = true; + m_sts.condition = false; + } + } else { + Q_ASSERT(!m_sts.condition); + } } - return true; + return ProItem::ReturnTrue; } -bool ProFileEvaluator::Private::visitEndProBlock(ProBlock *block) +void ProFileEvaluator::Private::visitEndProBlock(ProBlock *block) { - Q_UNUSED(block); - return true; + if ((block->blockKind() & ProBlock::ScopeContentsKind) + && !(block->blockKind() & ProBlock::FunctionBodyKind)) { + if (m_skipLevel) { + Q_ASSERT(!m_sts.condition); + --m_skipLevel; + } else if (!(block->blockKind() & ProBlock::SingleLine)) { + // Conditionals contained inside this block may have changed the state. + // So we reset it here to make an else following us do the right thing. + m_sts.condition = true; + } + } +} + +ProItem::ProItemReturn ProFileEvaluator::Private::visitProLoopIteration() +{ + ProLoop &loop = m_loopStack.top(); + + if (loop.infinite) { + if (!loop.variable.isEmpty()) + m_valuemap[loop.variable] = QStringList(QString::number(loop.index++)); + if (loop.index > 1000) { + q->errorMessage(format("ran into infinite loop (> 1000 iterations).")); + return ProItem::ReturnFalse; + } + } else { + QString val; + do { + if (loop.index >= loop.list.count()) + return ProItem::ReturnFalse; + val = loop.list.at(loop.index++); + } while (val.isEmpty()); // stupid, but qmake is like that + m_valuemap[loop.variable] = QStringList(val); + } + return ProItem::ReturnTrue; +} + +void ProFileEvaluator::Private::visitProLoopCleanup() +{ + ProLoop &loop = m_loopStack.top(); + m_valuemap[loop.variable] = loop.oldVarVal; + m_loopStack.pop_back(); } -bool ProFileEvaluator::Private::visitBeginProVariable(ProVariable *variable) +void ProFileEvaluator::Private::visitBeginProVariable(ProVariable *variable) { m_lastVarName = variable->variable(); m_variableOperator = variable->variableOperator(); - return true; + m_isFirstVariableValue = true; + m_tempValuemap = m_valuemap; + m_tempFilevaluemap = m_filevaluemap; } -bool ProFileEvaluator::Private::visitEndProVariable(ProVariable *variable) +void ProFileEvaluator::Private::visitEndProVariable(ProVariable *variable) { Q_UNUSED(variable); + m_valuemap = m_tempValuemap; + m_filevaluemap = m_tempFilevaluemap; m_lastVarName.clear(); - return true; } -bool ProFileEvaluator::Private::visitProOperator(ProOperator *oper) +void ProFileEvaluator::Private::visitProOperator(ProOperator *oper) { m_invertNext = (oper->operatorKind() == ProOperator::NotOperator); - return true; } -bool ProFileEvaluator::Private::visitProCondition(ProCondition *cond) +void ProFileEvaluator::Private::visitProCondition(ProCondition *cond) { - if (!m_condition) { - if (m_invertNext) - m_condition |= !isActiveConfig(cond->text(), true); - else - m_condition |= isActiveConfig(cond->text(), true); + if (!m_skipLevel) { + if (!cond->text().compare(QLatin1String("else"), Qt::CaseInsensitive)) { + m_sts.condition = !m_sts.prevCondition; + } else { + m_sts.prevCondition = false; + if (!m_sts.condition && isActiveConfig(cond->text(), true) ^ m_invertNext) + m_sts.condition = true; + } } - return true; + m_invertNext = false; } -bool ProFileEvaluator::Private::visitBeginProFile(ProFile * pro) +ProItem::ProItemReturn ProFileEvaluator::Private::visitBeginProFile(ProFile * pro) { PRE(pro); - bool ok = true; m_lineNo = pro->lineNumber(); + if (m_origfile.isEmpty()) + m_origfile = pro->fileName(); if (m_oldPath.isEmpty()) { // change the working directory for the initial profile we visit, since // that is *the* profile. All the other times we reach this function will be due to // include(file) or load(file) + m_oldPath = QDir::currentPath(); + m_profileStack.push(pro); - ok = QDir::setCurrent(pro->directoryName()); - } - if (m_origfile.isEmpty()) - m_origfile = pro->fileName(); + const QString mkspecDirectory = propertyValue(QLatin1String("QMAKE_MKSPECS")); + if (!mkspecDirectory.isEmpty()) { + bool cumulative = m_cumulative; + m_cumulative = false; + // This is what qmake does, everything set in the mkspec is also set + // But this also creates a lot of problems + evaluateFile(mkspecDirectory + QLatin1String("/default/qmake.conf")); + evaluateFile(mkspecDirectory + QLatin1String("/features/default_pre.prf")); + m_cumulative = cumulative; + } - return ok; + return returnBool(QDir::setCurrent(pro->directoryName())); + } + + return ProItem::ReturnTrue; } -bool ProFileEvaluator::Private::visitEndProFile(ProFile * pro) +ProItem::ProItemReturn ProFileEvaluator::Private::visitEndProFile(ProFile * pro) { PRE(pro); - bool ok = true; m_lineNo = pro->lineNumber(); if (m_profileStack.count() == 1 && !m_oldPath.isEmpty()) { + const QString &mkspecDirectory = propertyValue(QLatin1String("QMAKE_MKSPECS")); + if (!mkspecDirectory.isEmpty()) { + bool cumulative = m_cumulative; + m_cumulative = false; + + evaluateFile(mkspecDirectory + QLatin1String("/features/default_post.prf")); + + QSet<QString> processed; + forever { + bool finished = true; + QStringList configs = valuesDirect(QLatin1String("CONFIG")); + for (int i = configs.size() - 1; i >= 0; --i) { + const QString config = configs[i].toLower(); + if (!processed.contains(config)) { + processed.insert(config); + if (evaluateFile(mkspecDirectory + QLatin1String("/features/") + + config + QLatin1String(".prf"))) { + finished = false; + break; + } + } + } + if (finished) + break; + } + + foreach (ProBlock *itm, m_replaceFunctions) + itm->deref(); + m_replaceFunctions.clear(); + foreach (ProBlock *itm, m_testFunctions) + itm->deref(); + m_testFunctions.clear(); + + m_cumulative = cumulative; + } + m_profileStack.pop(); - ok = QDir::setCurrent(m_oldPath); + return returnBool(QDir::setCurrent(m_oldPath)); + } + + return ProItem::ReturnTrue; +} + +static void replaceInList(QStringList *varlist, + const QRegExp ®exp, const QString &replace, bool global) +{ + for (QStringList::Iterator varit = varlist->begin(); varit != varlist->end(); ) { + if ((*varit).contains(regexp)) { + (*varit).replace(regexp, replace); + if ((*varit).isEmpty()) + varit = varlist->erase(varit); + else + ++varit; + if(!global) + break; + } else { + ++varit; + } } - return ok; } -bool ProFileEvaluator::Private::visitProValue(ProValue *value) +void ProFileEvaluator::Private::visitProValue(ProValue *value) { PRE(value); m_lineNo = value->lineNumber(); @@ -546,8 +818,8 @@ bool ProFileEvaluator::Private::visitProValue(ProValue *value) if (varName == QLatin1String("TARGET") && m_lineNo == m_prevLineNo && currentProFile() == m_prevProFile) { - QStringList targets = m_valuemap.value(QLatin1String("TARGET")); - m_valuemap.remove(QLatin1String("TARGET")); + QStringList targets = m_tempValuemap.value(QLatin1String("TARGET")); + m_tempValuemap.remove(QLatin1String("TARGET")); QStringList lastTarget(targets.takeLast()); lastTarget << v.join(QLatin1String(" ")); targets.push_back(lastTarget.join(QLatin1String(" "))); @@ -557,38 +829,59 @@ bool ProFileEvaluator::Private::visitProValue(ProValue *value) m_prevProFile = currentProFile(); switch (m_variableOperator) { - case ProVariable::UniqueAddOperator: // * - insertUnique(&m_valuemap, varName, v, true); - insertUnique(&m_filevaluemap[currentProFile()], varName, v, true); - break; case ProVariable::SetOperator: // = - case ProVariable::AddOperator: // + - insertUnique(&m_valuemap, varName, v, false); - insertUnique(&m_filevaluemap[currentProFile()], varName, v, false); + if (!m_cumulative) { + if (!m_skipLevel) { + if (m_isFirstVariableValue) { + m_tempValuemap[varName] = v; + m_tempFilevaluemap[currentProFile()][varName] = v; + } else { // handle lines "CONFIG = foo bar" + m_tempValuemap[varName] += v; + m_tempFilevaluemap[currentProFile()][varName] += v; + } + } + } else { + // We are greedy for values. + m_tempValuemap[varName] += v; + m_tempFilevaluemap[currentProFile()][varName] += v; + } + break; + case ProVariable::UniqueAddOperator: // *= + if (!m_skipLevel || m_cumulative) { + insertUnique(&m_tempValuemap, varName, v); + insertUnique(&m_tempFilevaluemap[currentProFile()], varName, v); + } break; - case ProVariable::RemoveOperator: // - - // fix me: interaction between AddOperator and RemoveOperator - insertUnique(&m_valuemap, varName.prepend(QLatin1Char('-')), v, false); - insertUnique(&m_filevaluemap[currentProFile()], - varName.prepend(QLatin1Char('-')), v, false); + case ProVariable::AddOperator: // += + if (!m_skipLevel || m_cumulative) { + m_tempValuemap[varName] += v; + m_tempFilevaluemap[currentProFile()][varName] += v; + } break; - case ProVariable::ReplaceOperator: // ~ + case ProVariable::RemoveOperator: // -= + if (!m_cumulative) { + if (!m_skipLevel) { + removeEach(&m_tempValuemap, varName, v); + removeEach(&m_tempFilevaluemap[currentProFile()], varName, v); + } + } else { + // We are stingy with our values, too. + } + break; + case ProVariable::ReplaceOperator: // ~= { // DEFINES ~= s/a/b/?[gqi] -/* Create a superset by executing replacement + adding items that have changed - to original list. We're not sure if this is really the right approach, so for - the time being we will just do nothing ... - + doVariableReplace(&val); + if (val.length() < 4 || val[0] != QLatin1Char('s')) { + q->logMessage(format("the ~= operator can handle only the s/// function.")); + break; + } QChar sep = val.at(1); QStringList func = val.split(sep); if (func.count() < 3 || func.count() > 4) { - q->logMessage(format("'~= operator '(function s///) expects 3 or 4 arguments.")); - return false; - } - if (func[0] != QLatin1String("s")) { - q->logMessage(format("~= operator can only handle s/// function.")); - return false; + q->logMessage(format("the s/// function expects 3 or 4 arguments.")); + break; } bool global = false, quote = false, case_sense = false; @@ -604,40 +897,41 @@ bool ProFileEvaluator::Private::visitProValue(ProValue *value) QRegExp regexp(pattern, case_sense ? Qt::CaseSensitive : Qt::CaseInsensitive); - QStringList replaceList = replaceInList(m_valuemap.value(varName), regexp, replace, - global); - // Add changed entries to list - foreach (const QString &entry, replaceList) - if (!m_valuemap.value(varName).contains(entry)) - insertUnique(&m_valuemap, varName, QStringList() << entry, false); - - replaceList = replaceInList(m_filevaluemap[currentProFile()].value(varName), regexp, - replace, global); - foreach (const QString &entry, replaceList) - if (!m_filevaluemap[currentProFile()].value(varName).contains(entry)) - insertUnique(&m_filevaluemap[currentProFile()], varName, - QStringList() << entry, false); */ + if (!m_skipLevel || m_cumulative) { + // We could make a union of modified and unmodified values, + // but this will break just as much as it fixes, so leave it as is. + replaceInList(&m_tempValuemap[varName], regexp, replace, global); + replaceInList(&m_tempFilevaluemap[currentProFile()][varName], regexp, replace, global); + } } break; } - return true; + m_isFirstVariableValue = false; } -bool ProFileEvaluator::Private::visitProFunction(ProFunction *func) +ProItem::ProItemReturn ProFileEvaluator::Private::visitProFunction(ProFunction *func) { - m_lineNo = func->lineNumber(); - bool result = true; - bool ok = true; - QString text = func->text(); - int lparen = text.indexOf(QLatin1Char('(')); - int rparen = text.lastIndexOf(QLatin1Char(')')); - Q_ASSERT(lparen < rparen); - - QString arguments = text.mid(lparen + 1, rparen - lparen - 1); - QString funcName = text.left(lparen); - ok &= evaluateConditionalFunction(funcName.trimmed(), arguments, &result); - return ok; + // Make sure that called subblocks don't inherit & destroy the state + bool invertThis = m_invertNext; + m_invertNext = false; + if (!m_skipLevel) + m_sts.prevCondition = false; + if (m_cumulative || !m_sts.condition) { + QString text = func->text(); + int lparen = text.indexOf(QLatin1Char('(')); + int rparen = text.lastIndexOf(QLatin1Char(')')); + Q_ASSERT(lparen < rparen); + QString arguments = text.mid(lparen + 1, rparen - lparen - 1); + QString funcName = text.left(lparen); + m_lineNo = func->lineNumber(); + ProItem::ProItemReturn result = evaluateConditionalFunction(funcName.trimmed(), arguments); + if (result != ProItem::ReturnFalse && result != ProItem::ReturnTrue) + return result; + if (!m_skipLevel && ((result == ProItem::ReturnTrue) ^ invertThis)) + m_sts.condition = true; + } + return ProItem::ReturnTrue; } @@ -645,7 +939,7 @@ QStringList ProFileEvaluator::Private::qmakeFeaturePaths() { QStringList concat; { - const QString base_concat = QDir::separator() + QString(QLatin1String("features")); + const QString base_concat = QDir::separator() + QLatin1String("features"); concat << base_concat + QDir::separator() + QLatin1String("mac"); concat << base_concat + QDir::separator() + QLatin1String("macx"); concat << base_concat + QDir::separator() + QLatin1String("unix"); @@ -654,7 +948,7 @@ QStringList ProFileEvaluator::Private::qmakeFeaturePaths() concat << base_concat + QDir::separator() + QLatin1String("qnx6"); concat << base_concat; } - const QString mkspecs_concat = QDir::separator() + QString(QLatin1String("mkspecs")); + const QString mkspecs_concat = QDir::separator() + QLatin1String("mkspecs"); QStringList feature_roots; QByteArray mkspec_path = qgetenv("QMAKEFEATURES"); if (!mkspec_path.isNull()) @@ -757,12 +1051,17 @@ QString ProFileEvaluator::Private::currentFileName() const return QString(); } -QString ProFileEvaluator::Private::getcwd() const +QString ProFileEvaluator::Private::currentDirectory() const { ProFile *cur = m_profileStack.top(); return cur->directoryName(); } +void ProFileEvaluator::Private::doVariableReplace(QString *str) +{ + *str = expandVariableReferences(*str).join(QString(Option::field_sep)); +} + QStringList ProFileEvaluator::Private::expandVariableReferences(const QString &str) { QStringList ret; @@ -977,10 +1276,49 @@ bool ProFileEvaluator::Private::isActiveConfig(const QString &config, bool regex return false; } +QStringList ProFileEvaluator::Private::evaluateFunction( + ProBlock *funcPtr, const QStringList &argumentsList, bool *ok) +{ + bool oki; + QStringList ret; + + if (m_valuemapStack.count() >= 100) { + q->errorMessage(format("ran into infinite recursion (depth > 100).")); + oki = false; + } else { + State sts = m_sts; + m_valuemapStack.push(m_valuemap); + m_filevaluemapStack.push(m_filevaluemap); + + QStringList args; + for (int i = 0; i < argumentsList.count(); ++i) { + QStringList theArgs = expandVariableReferences(argumentsList[i]); + args += theArgs; + m_valuemap[QString::number(i+1)] = theArgs; + } + m_valuemap[QLatin1String("ARGS")] = args; + oki = (funcPtr->Accept(this) != ProItem::ReturnFalse); // True || Return + ret = m_returnValue; + m_returnValue.clear(); + + m_valuemap = m_valuemapStack.pop(); + m_filevaluemap = m_filevaluemapStack.pop(); + m_sts = sts; + } + if (ok) + *ok = oki; + if (oki) + return ret; + return QStringList(); +} + QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &func, const QString &arguments) { QStringList argumentsList = split_arg_list(arguments); + if (ProBlock *funcPtr = m_replaceFunctions.value(func, 0)) + return evaluateFunction(funcPtr, argumentsList, 0); + QStringList args; for (int i = 0; i < argumentsList.count(); ++i) args += expandVariableReferences(argumentsList[i]).join(Option::field_sep); @@ -991,35 +1329,34 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun E_UPPER, E_LOWER, E_FILES, E_PROMPT, E_RE_ESCAPE, E_REPLACE }; - static QHash<QString, int> *expands = 0; - if (!expands) { - expands = new QHash<QString, int>; - expands->insert(QLatin1String("member"), E_MEMBER); //v (implemented) - expands->insert(QLatin1String("first"), E_FIRST); //v - expands->insert(QLatin1String("last"), E_LAST); //v - expands->insert(QLatin1String("cat"), E_CAT); - expands->insert(QLatin1String("fromfile"), E_FROMFILE); - expands->insert(QLatin1String("eval"), E_EVAL); - expands->insert(QLatin1String("list"), E_LIST); - expands->insert(QLatin1String("sprintf"), E_SPRINTF); - expands->insert(QLatin1String("join"), E_JOIN); //v - expands->insert(QLatin1String("split"), E_SPLIT); //v - expands->insert(QLatin1String("basename"), E_BASENAME); //v - expands->insert(QLatin1String("dirname"), E_DIRNAME); //v - expands->insert(QLatin1String("section"), E_SECTION); - expands->insert(QLatin1String("find"), E_FIND); - expands->insert(QLatin1String("system"), E_SYSTEM); //v - expands->insert(QLatin1String("unique"), E_UNIQUE); - expands->insert(QLatin1String("quote"), E_QUOTE); //v - expands->insert(QLatin1String("escape_expand"), E_ESCAPE_EXPAND); - expands->insert(QLatin1String("upper"), E_UPPER); - expands->insert(QLatin1String("lower"), E_LOWER); - expands->insert(QLatin1String("re_escape"), E_RE_ESCAPE); - expands->insert(QLatin1String("files"), E_FILES); - expands->insert(QLatin1String("prompt"), E_PROMPT); - expands->insert(QLatin1String("replace"), E_REPLACE); + static QHash<QString, int> expands; + if (expands.isEmpty()) { + expands.insert(QLatin1String("member"), E_MEMBER); + expands.insert(QLatin1String("first"), E_FIRST); + expands.insert(QLatin1String("last"), E_LAST); + expands.insert(QLatin1String("cat"), E_CAT); + expands.insert(QLatin1String("fromfile"), E_FROMFILE); // implementation disabled (see comment below) + expands.insert(QLatin1String("eval"), E_EVAL); + expands.insert(QLatin1String("list"), E_LIST); + expands.insert(QLatin1String("sprintf"), E_SPRINTF); + expands.insert(QLatin1String("join"), E_JOIN); + expands.insert(QLatin1String("split"), E_SPLIT); + expands.insert(QLatin1String("basename"), E_BASENAME); + expands.insert(QLatin1String("dirname"), E_DIRNAME); + expands.insert(QLatin1String("section"), E_SECTION); + expands.insert(QLatin1String("find"), E_FIND); + expands.insert(QLatin1String("system"), E_SYSTEM); + expands.insert(QLatin1String("unique"), E_UNIQUE); + expands.insert(QLatin1String("quote"), E_QUOTE); + expands.insert(QLatin1String("escape_expand"), E_ESCAPE_EXPAND); + expands.insert(QLatin1String("upper"), E_UPPER); + expands.insert(QLatin1String("lower"), E_LOWER); + expands.insert(QLatin1String("re_escape"), E_RE_ESCAPE); + expands.insert(QLatin1String("files"), E_FILES); + expands.insert(QLatin1String("prompt"), E_PROMPT); // interactive, so cannot be implemented + expands.insert(QLatin1String("replace"), E_REPLACE); } - ExpandFunc func_t = ExpandFunc(expands->value(func.toLower())); + ExpandFunc func_t = ExpandFunc(expands.value(func.toLower())); QStringList ret; @@ -1065,6 +1402,16 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun } break; } + case E_SPRINTF: + if(args.count() < 1) { + q->logMessage(format("sprintf(format, ...) requires at least one argument")); + } else { + QString tmp = args.at(0); + for (int i = 1; i < args.count(); ++i) + tmp = tmp.arg(args.at(i)); + ret = split_value_list(tmp); + } + break; case E_JOIN: { if (args.count() < 1 || args.count() > 4) { q->logMessage(format("join(var, glue, before, after) requires one to four arguments.")); @@ -1084,9 +1431,9 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun } case E_SPLIT: { if (args.count() != 2) { - q->logMessage(format("split(var, sep) requires two arguments")); + q->logMessage(format("split(var, sep) requires one or two arguments")); } else { - QString sep = args.at(1); + const QString &sep = (args.count() == 2) ? args[1] : QString(Option::field_sep); foreach (const QString &var, values(args.first())) foreach (const QString &splt, var.split(sep)) ret.append(splt); @@ -1157,8 +1504,82 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun } break; } - case E_SYSTEM: { - if (m_condition) { + case E_CAT: + if (args.count() < 1 || args.count() > 2) { + q->logMessage(format("cat(file, singleline=true) requires one or two arguments.")); + } else { + QString file = args[0]; + file = Option::fixPathToLocalOS(file); + + bool singleLine = true; + if (args.count() > 1) + singleLine = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive)); + + QFile qfile(file); + if (qfile.open(QIODevice::ReadOnly)) { + QTextStream stream(&qfile); + while (!stream.atEnd()) { + ret += split_value_list(stream.readLine().trimmed()); + if (!singleLine) + ret += QLatin1String("\n"); + } + qfile.close(); + } + } + break; +#if 0 // Used only by Qt's configure for caching + case E_FROMFILE: + if (args.count() != 2) { + q->logMessage(format("fromfile(file, variable) requires two arguments.")); + } else { + QString file = args[0], seek_variableName = args[1]; + + ProFile pro(Option::fixPathToLocalOS(file)); + + ProFileEvaluator visitor; + visitor.setVerbose(m_verbose); + visitor.setCumulative(m_cumulative); + + if (!visitor.queryProFile(&pro)) + break; + + if (!visitor.accept(&pro)) + break; + + ret = visitor.values(seek_variableName); + } + break; +#endif + case E_EVAL: { + if (args.count() != 1) { + q->logMessage(format("eval(variable) requires one argument")); + + } else { + ret += values(args.at(0)); + } + break; } + case E_LIST: { + static int x = 0; + QString tmp; + tmp.sprintf(".QMAKE_INTERNAL_TMP_variableName_%d", x++); + ret = QStringList(tmp); + QStringList lst; + foreach (const QString &arg, args) + lst += split_value_list(arg); + m_valuemap[tmp] = lst; + break; } + case E_FIND: + if (args.count() != 2) { + q->logMessage(format("find(var, str) requires two arguments.")); + } else { + QRegExp regx(args[1]); + foreach (const QString &val, values(args.first())) + if (regx.indexIn(val) != -1) + ret += val; + } + break; + case E_SYSTEM: + if (!m_skipLevel) { if (args.count() < 1 || args.count() > 2) { q->logMessage(format("system(execute) requires one or two arguments.")); } else { @@ -1166,7 +1587,7 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun FILE *proc = QT_POPEN(args[0].toLatin1(), "r"); bool singleLine = true; if (args.count() > 1) - singleLine = (args[1].toLower() == QLatin1String("true")); + singleLine = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive)); QString output; while (proc && !feof(proc)) { int read_in = int(fread(buff, 1, 255, proc)); @@ -1184,13 +1605,114 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun QT_PCLOSE(proc); } } - break; } + break; + case E_UNIQUE: + if(args.count() != 1) { + q->logMessage(format("unique(var) requires one argument.")); + } else { + foreach (const QString &var, values(args.first())) + if (!ret.contains(var)) + ret.append(var); + } + break; case E_QUOTE: for (int i = 0; i < args.count(); ++i) ret += QStringList(args.at(i)); break; + case E_ESCAPE_EXPAND: + for (int i = 0; i < args.size(); ++i) { + QChar *i_data = args[i].data(); + int i_len = args[i].length(); + for (int x = 0; x < i_len; ++x) { + if (*(i_data+x) == QLatin1Char('\\') && x < i_len-1) { + if (*(i_data+x+1) == QLatin1Char('\\')) { + ++x; + } else { + struct { + char in, out; + } mapped_quotes[] = { + { 'n', '\n' }, + { 't', '\t' }, + { 'r', '\r' }, + { 0, 0 } + }; + for (int i = 0; mapped_quotes[i].in; ++i) { + if (*(i_data+x+1) == QLatin1Char(mapped_quotes[i].in)) { + *(i_data+x) = QLatin1Char(mapped_quotes[i].out); + if (x < i_len-2) + memmove(i_data+x+1, i_data+x+2, (i_len-x-2)*sizeof(QChar)); + --i_len; + break; + } + } + } + } + } + ret.append(QString(i_data, i_len)); + } + break; + case E_RE_ESCAPE: + for (int i = 0; i < args.size(); ++i) + ret += QRegExp::escape(args[i]); + break; + case E_UPPER: + case E_LOWER: + for (int i = 0; i < args.count(); ++i) + if (func_t == E_UPPER) + ret += args[i].toUpper(); + else + ret += args[i].toLower(); + break; + case E_FILES: + if (args.count() != 1 && args.count() != 2) { + q->logMessage(format("files(pattern, recursive=false) requires one or two arguments")); + } else { + bool recursive = false; + if (args.count() == 2) + recursive = (!args[1].compare(QLatin1String("true"), Qt::CaseInsensitive) || args[1].toInt()); + QStringList dirs; + QString r = Option::fixPathToLocalOS(args[0]); + int slash = r.lastIndexOf(QDir::separator()); + if (slash != -1) { + dirs.append(r.left(slash)); + r = r.mid(slash+1); + } else { + dirs.append(QString()); + } + + const QRegExp regex(r, Qt::CaseSensitive, QRegExp::Wildcard); + for (int d = 0; d < dirs.count(); d++) { + QString dir = dirs[d]; + if (!dir.isEmpty() && !dir.endsWith(Option::dir_sep)) + dir += QLatin1Char('/'); + + QDir qdir(dir); + for (int i = 0; i < (int)qdir.count(); ++i) { + if (qdir[i] == QLatin1String(".") || qdir[i] == QLatin1String("..")) + continue; + QString fname = dir + qdir[i]; + if (QFileInfo(fname).isDir()) { + if (recursive) + dirs.append(fname); + } + if (regex.exactMatch(qdir[i])) + ret += fname; + } + } + } + break; + case E_REPLACE: + if(args.count() != 3 ) { + q->logMessage(format("replace(var, before, after) requires three arguments")); + } else { + const QRegExp before(args[1]); + const QString after(args[2]); + foreach (QString val, values(args.first())) + ret += val.replace(before, after); + } + break; case 0: - q->logMessage(format("'%1' is not a function").arg(func)); + q->logMessage(format("'%1' is not a recognized replace function").arg(func)); break; default: q->logMessage(format("Function '%1' is not implemented").arg(func)); @@ -1200,64 +1722,323 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun return ret; } -bool ProFileEvaluator::Private::evaluateConditionalFunction(const QString &function, - const QString &arguments, bool *result) +ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction( + const QString &function, const QString &arguments) { QStringList argumentsList = split_arg_list(arguments); + + if (ProBlock *funcPtr = m_testFunctions.value(function, 0)) { + bool ok; + QStringList ret = evaluateFunction(funcPtr, argumentsList, &ok); + if (ok) { + if (ret.isEmpty()) { + return ProItem::ReturnTrue; + } else { + if (ret.first() != QLatin1String("false")) { + if (ret.first() == QLatin1String("true")) { + return ProItem::ReturnTrue; + } else { + bool ok; + int val = ret.first().toInt(&ok); + if (ok) { + if (val) + return ProItem::ReturnTrue; + } else { + q->logMessage(format("Unexpected return value from test '%1': %2") + .arg(function).arg(ret.join(QLatin1String(" :: ")))); + } + } + } + } + } + return ProItem::ReturnFalse; + } + QString sep; sep.append(Option::field_sep); - QStringList args; for (int i = 0; i < argumentsList.count(); ++i) args += expandVariableReferences(argumentsList[i]).join(sep); - enum ConditionFunc { CF_CONFIG = 1, CF_CONTAINS, CF_COUNT, CF_EXISTS, CF_INCLUDE, - CF_LOAD, CF_ISEMPTY, CF_SYSTEM, CF_MESSAGE}; - - static QHash<QString, int> *functions = 0; - if (!functions) { - functions = new QHash<QString, int>; - functions->insert(QLatin1String("load"), CF_LOAD); //v - functions->insert(QLatin1String("include"), CF_INCLUDE); //v - functions->insert(QLatin1String("message"), CF_MESSAGE); //v - functions->insert(QLatin1String("warning"), CF_MESSAGE); //v - functions->insert(QLatin1String("error"), CF_MESSAGE); //v + enum TestFunc { T_REQUIRES=1, T_GREATERTHAN, T_LESSTHAN, T_EQUALS, + T_EXISTS, T_EXPORT, T_CLEAR, T_UNSET, T_EVAL, T_CONFIG, T_SYSTEM, + T_RETURN, T_BREAK, T_NEXT, T_DEFINED, T_CONTAINS, T_INFILE, + T_COUNT, T_ISEMPTY, T_INCLUDE, T_LOAD, T_DEBUG, T_MESSAGE, T_IF, + T_FOR, T_DEFINE_TEST, T_DEFINE_REPLACE }; + + static QHash<QString, int> functions; + if (functions.isEmpty()) { + functions.insert(QLatin1String("requires"), T_REQUIRES); + functions.insert(QLatin1String("greaterThan"), T_GREATERTHAN); + functions.insert(QLatin1String("lessThan"), T_LESSTHAN); + functions.insert(QLatin1String("equals"), T_EQUALS); + functions.insert(QLatin1String("isEqual"), T_EQUALS); + functions.insert(QLatin1String("exists"), T_EXISTS); + functions.insert(QLatin1String("export"), T_EXPORT); + functions.insert(QLatin1String("clear"), T_CLEAR); + functions.insert(QLatin1String("unset"), T_UNSET); + functions.insert(QLatin1String("eval"), T_EVAL); + functions.insert(QLatin1String("CONFIG"), T_CONFIG); + functions.insert(QLatin1String("if"), T_IF); + functions.insert(QLatin1String("isActiveConfig"), T_CONFIG); + functions.insert(QLatin1String("system"), T_SYSTEM); + functions.insert(QLatin1String("return"), T_RETURN); + functions.insert(QLatin1String("break"), T_BREAK); + functions.insert(QLatin1String("next"), T_NEXT); + functions.insert(QLatin1String("defined"), T_DEFINED); + functions.insert(QLatin1String("contains"), T_CONTAINS); + functions.insert(QLatin1String("infile"), T_INFILE); + functions.insert(QLatin1String("count"), T_COUNT); + functions.insert(QLatin1String("isEmpty"), T_ISEMPTY); + functions.insert(QLatin1String("load"), T_LOAD); //v + functions.insert(QLatin1String("include"), T_INCLUDE); //v + functions.insert(QLatin1String("debug"), T_DEBUG); + functions.insert(QLatin1String("message"), T_MESSAGE); //v + functions.insert(QLatin1String("warning"), T_MESSAGE); //v + functions.insert(QLatin1String("error"), T_MESSAGE); //v + functions.insert(QLatin1String("for"), T_FOR); //v + functions.insert(QLatin1String("defineTest"), T_DEFINE_TEST); //v + functions.insert(QLatin1String("defineReplace"), T_DEFINE_REPLACE); //v } - bool cond = false; - bool ok = true; - - ConditionFunc func_t = (ConditionFunc)functions->value(function); + TestFunc func_t = (TestFunc)functions.value(function); switch (func_t) { - case CF_CONFIG: { + case T_DEFINE_TEST: + m_definingTest = true; + goto defineFunc; + case T_DEFINE_REPLACE: + m_definingTest = false; + defineFunc: + if (args.count() != 1) { + q->logMessage(format("%s(function) requires one argument.").arg(function)); + return ProItem::ReturnFalse; + } + m_definingFunc = args.first(); + return ProItem::ReturnTrue; + case T_DEFINED: + if (args.count() < 1 || args.count() > 2) { + q->logMessage(format("defined(function, [\"test\"|\"replace\"])" + " requires one or two arguments.")); + return ProItem::ReturnFalse; + } + if (args.count() > 1) { + if (args[1] == QLatin1String("test")) + return returnBool(m_testFunctions.contains(args[0])); + else if (args[1] == QLatin1String("replace")) + return returnBool(m_replaceFunctions.contains(args[0])); + q->logMessage(format("defined(function, type):" + " unexpected type [%1].\n").arg(args[1])); + return ProItem::ReturnFalse; + } + return returnBool(m_replaceFunctions.contains(args[0]) + || m_testFunctions.contains(args[0])); + case T_RETURN: + m_returnValue = args; + // It is "safe" to ignore returns - due to qmake brokeness + // they cannot be used to terminate loops anyway. + if (m_skipLevel || m_cumulative) + return ProItem::ReturnTrue; + if (m_valuemapStack.isEmpty()) { + q->logMessage(format("unexpected return().")); + return ProItem::ReturnFalse; + } + return ProItem::ReturnReturn; + case T_EXPORT: + if (m_skipLevel && !m_cumulative) + return ProItem::ReturnTrue; + if (args.count() != 1) { + q->logMessage(format("export(variable) requires one argument.")); + return ProItem::ReturnFalse; + } + for (int i = 0; i < m_valuemapStack.size(); ++i) { + m_valuemapStack[i][args[0]] = m_valuemap[args[0]]; + m_filevaluemapStack[i][currentProFile()][args[0]] = + m_filevaluemap[currentProFile()][args[0]]; + } + return ProItem::ReturnTrue; +#if 0 + case T_INFILE: + case T_REQUIRES: + case T_EVAL: +#endif + case T_FOR: { + if (m_cumulative) // This is a no-win situation, so just pretend it's no loop + return ProItem::ReturnTrue; + if (m_skipLevel) + return ProItem::ReturnFalse; + if (args.count() > 2 || args.count() < 1) { + q->logMessage(format("for({var, list|var, forever|ever})" + " requires one or two arguments.")); + return ProItem::ReturnFalse; + } + ProLoop loop; + loop.infinite = false; + loop.index = 0; + QString it_list; + if (args.count() == 1) { + doVariableReplace(&args[0]); + it_list = args[0]; + if (args[0] != QLatin1String("ever")) { + q->logMessage(format("for({var, list|var, forever|ever})" + " requires one or two arguments.")); + return ProItem::ReturnFalse; + } + it_list = QLatin1String("forever"); + } else { + loop.variable = args[0]; + loop.oldVarVal = m_valuemap.value(loop.variable); + doVariableReplace(&args[1]); + it_list = args[1]; + } + loop.list = m_valuemap[it_list]; + if (loop.list.isEmpty()) { + if (it_list == QLatin1String("forever")) { + loop.infinite = true; + } else { + int dotdot = it_list.indexOf(QLatin1String("..")); + if (dotdot != -1) { + bool ok; + int start = it_list.left(dotdot).toInt(&ok); + if (ok) { + int end = it_list.mid(dotdot+2).toInt(&ok); + if (ok) { + if (start < end) { + for (int i = start; i <= end; i++) + loop.list << QString::number(i); + } else { + for (int i = start; i >= end; i--) + loop.list << QString::number(i); + } + } + } + } + } + } + m_loopStack.push(loop); + m_sts.condition = true; + return ProItem::ReturnLoop; + } + case T_BREAK: + if (m_skipLevel) + return ProItem::ReturnFalse; + if (!m_loopStack.isEmpty()) + return ProItem::ReturnBreak; + // ### missing: breaking out of multiline blocks + q->logMessage(format("unexpected break().")); + return ProItem::ReturnFalse; + case T_NEXT: + if (m_skipLevel) + return ProItem::ReturnFalse; + if (!m_loopStack.isEmpty()) + return ProItem::ReturnNext; + q->logMessage(format("unexpected next().")); + return ProItem::ReturnFalse; + case T_IF: { + if (args.count() != 1) { + q->logMessage(format("if(condition) requires one argument.")); + return ProItem::ReturnFalse; + } + QString cond = args.first(); + bool escaped = false; // This is more than qmake does + bool quoted = false; + bool ret = true; + bool orOp = false; + bool invert = false; + bool isFunc = false; + int parens = 0; + QString test; + test.reserve(20); + QString args; + args.reserve(50); + const QChar *d = cond.unicode(); + const QChar *ed = d + cond.length(); + while (d < ed) { + ushort c = (d++)->unicode(); + if (!escaped) { + if (c == '\\') { + escaped = true; + args += c; // Assume no-one quotes the test name + continue; + } else if (c == '"') { + quoted = !quoted; + args += c; // Ditto + continue; + } + } else { + escaped = false; + } + if (quoted) { + args += c; // Ditto + } else { + bool isOp = false; + if (c == '(') { + isFunc = true; + if (parens) + args += c; + ++parens; + } else if (c == ')') { + --parens; + if (parens) + args += c; + } else if (!parens) { + if (c == ':' || c == '|') + isOp = true; + else if (c == '!') + invert = true; + else + test += c; + } else { + args += c; + } + if (!parens && (isOp || d == ed)) { + // Yes, qmake doesn't shortcut evaluations here. We can't, either, + // as some test functions have side effects. + bool success; + if (isFunc) { + success = evaluateConditionalFunction(test, args); + } else { + success = isActiveConfig(test, true); + } + success ^= invert; + if (orOp) + ret |= success; + else + ret &= success; + orOp = (c == '|'); + invert = false; + isFunc = false; + test.clear(); + args.clear(); + } + } + } + return returnBool(ret); + } + case T_CONFIG: { if (args.count() < 1 || args.count() > 2) { q->logMessage(format("CONFIG(config) requires one or two arguments.")); - ok = false; - break; + return ProItem::ReturnFalse; } if (args.count() == 1) { //cond = isActiveConfig(args.first()); XXX - break; + return ProItem::ReturnFalse; } const QStringList mutuals = args[1].split(QLatin1Char('|')); const QStringList &configs = valuesDirect(QLatin1String("CONFIG")); - for (int i = configs.size() - 1 && ok; i >= 0; i--) { + for (int i = configs.size() - 1; i >= 0; i--) { for (int mut = 0; mut < mutuals.count(); mut++) { if (configs[i] == mutuals[mut].trimmed()) { - cond = (configs[i] == args[0]); - goto done_T_CONFIG; + return returnBool(configs[i] == args[0]); } } } - done_T_CONFIG: - break; + return ProItem::ReturnFalse; } - case CF_CONTAINS: { + case T_CONTAINS: { if (args.count() < 2 || args.count() > 3) { q->logMessage(format("contains(var, val) requires two or three arguments.")); - ok = false; - break; + return ProItem::ReturnFalse; } QRegExp regx(args[1]); @@ -1266,8 +2047,7 @@ bool ProFileEvaluator::Private::evaluateConditionalFunction(const QString &funct for (int i = 0; i < l.size(); ++i) { const QString val = l[i]; if (regx.exactMatch(val) || val == args[1]) { - cond = true; - break; + return ProItem::ReturnTrue; } } } else { @@ -1276,178 +2056,322 @@ bool ProFileEvaluator::Private::evaluateConditionalFunction(const QString &funct const QString val = l[i]; for (int mut = 0; mut < mutuals.count(); mut++) { if (val == mutuals[mut].trimmed()) { - cond = (regx.exactMatch(val) || val == args[1]); - goto done_T_CONTAINS; + return returnBool(regx.exactMatch(val) || val == args[1]); } } } } - done_T_CONTAINS: - break; + return ProItem::ReturnFalse; } - case CF_COUNT: { + case T_COUNT: { if (args.count() != 2 && args.count() != 3) { - q->logMessage(format("count(var, count) requires two or three arguments.")); - ok = false; - break; + q->logMessage(format("count(var, count, op=\"equals\") requires two or three arguments.")); + return ProItem::ReturnFalse; } if (args.count() == 3) { QString comp = args[2]; if (comp == QLatin1String(">") || comp == QLatin1String("greaterThan")) { - cond = values(args.first()).count() > args[1].toInt(); + return returnBool(values(args.first()).count() > args[1].toInt()); } else if (comp == QLatin1String(">=")) { - cond = values(args.first()).count() >= args[1].toInt(); + return returnBool(values(args.first()).count() >= args[1].toInt()); } else if (comp == QLatin1String("<") || comp == QLatin1String("lessThan")) { - cond = values(args.first()).count() < args[1].toInt(); + return returnBool(values(args.first()).count() < args[1].toInt()); } else if (comp == QLatin1String("<=")) { - cond = values(args.first()).count() <= args[1].toInt(); - } else if (comp == QLatin1String("equals") || comp == QLatin1String("isEqual") || comp == QLatin1String("=") || comp == QLatin1String("==")) { - cond = values(args.first()).count() == args[1].toInt(); + return returnBool(values(args.first()).count() <= args[1].toInt()); + } else if (comp == QLatin1String("equals") || comp == QLatin1String("isEqual") + || comp == QLatin1String("=") || comp == QLatin1String("==")) { + return returnBool(values(args.first()).count() == args[1].toInt()); } else { - ok = false; q->logMessage(format("unexpected modifier to count(%2)").arg(comp)); + return ProItem::ReturnFalse; } - break; } - cond = values(args.first()).count() == args[1].toInt(); - break; + return returnBool(values(args.first()).count() == args[1].toInt()); + } + case T_GREATERTHAN: + case T_LESSTHAN: { + if (args.count() != 2) { + q->logMessage(format("%1(variable, value) requires two arguments.").arg(function)); + return ProItem::ReturnFalse; + } + QString rhs(args[1]), lhs(values(args[0]).join(QString(Option::field_sep))); + bool ok; + int rhs_int = rhs.toInt(&ok); + if (ok) { // do integer compare + int lhs_int = lhs.toInt(&ok); + if (ok) { + if (func_t == T_GREATERTHAN) + return returnBool(lhs_int > rhs_int); + return returnBool(lhs_int < rhs_int); + } + } + if (func_t == T_GREATERTHAN) + return returnBool(lhs > rhs); + return returnBool(lhs < rhs); + } + case T_EQUALS: + if (args.count() != 2) { + q->logMessage(format("%1(variable, value) requires two arguments.").arg(function)); + return ProItem::ReturnFalse; + } + return returnBool(values(args[0]).join(QString(Option::field_sep)) == args[1]); + case T_CLEAR: { + if (m_skipLevel && !m_cumulative) + return ProItem::ReturnFalse; + if (args.count() != 1) { + q->logMessage(format("%1(variable) requires one argument.").arg(function)); + return ProItem::ReturnFalse; + } + QHash<QString, QStringList>::Iterator it = m_valuemap.find(args[0]); + if (it == m_valuemap.end()) + return ProItem::ReturnFalse; + it->clear(); + return ProItem::ReturnTrue; + } + case T_UNSET: { + if (m_skipLevel && !m_cumulative) + return ProItem::ReturnFalse; + if (args.count() != 1) { + q->logMessage(format("%1(variable) requires one argument.").arg(function)); + return ProItem::ReturnFalse; + } + QHash<QString, QStringList>::Iterator it = m_valuemap.find(args[0]); + if (it == m_valuemap.end()) + return ProItem::ReturnFalse; + m_valuemap.erase(it); + return ProItem::ReturnTrue; } - case CF_INCLUDE: { + case T_INCLUDE: { + if (m_skipLevel && !m_cumulative) + return ProItem::ReturnFalse; QString parseInto; - if (args.count() == 2) { + // the third optional argument to include() controls warnings + // and is not used here + if ((args.count() == 2) || (args.count() == 3)) { parseInto = args[1]; } else if (args.count() != 1) { - q->logMessage(format("include(file) requires one or two arguments.")); - ok = false; - break; + q->logMessage(format("include(file) requires one, two or three arguments.")); + return ProItem::ReturnFalse; } QString fileName = args.first(); // ### this breaks if we have include(c:/reallystupid.pri) but IMHO that's really bad style. - QDir currentProPath(getcwd()); + QDir currentProPath(currentDirectory()); fileName = QDir::cleanPath(currentProPath.absoluteFilePath(fileName)); - ok = evaluateFile(fileName, &ok); - break; + State sts = m_sts; + bool ok = evaluateFile(fileName); + m_sts = sts; + return returnBool(ok); } - case CF_LOAD: { + case T_LOAD: { + if (m_skipLevel && !m_cumulative) + return ProItem::ReturnFalse; QString parseInto; bool ignore_error = false; if (args.count() == 2) { QString sarg = args[1]; - ignore_error = (sarg.toLower() == QLatin1String("true") || sarg.toInt()); + ignore_error = (!sarg.compare(QLatin1String("true"), Qt::CaseInsensitive) || sarg.toInt()); } else if (args.count() != 1) { q->logMessage(format("load(feature) requires one or two arguments.")); - ok = false; - break; + return ProItem::ReturnFalse; } - ok = evaluateFeatureFile( args.first(), &cond); - break; + // XXX ignore_error unused + return returnBool(evaluateFeatureFile(args.first())); } - case CF_MESSAGE: { + case T_DEBUG: + // Yup - do nothing. Nothing is going to enable debug output anyway. + return ProItem::ReturnFalse; + case T_MESSAGE: { if (args.count() != 1) { q->logMessage(format("%1(message) requires one argument.").arg(function)); - ok = false; - break; + return ProItem::ReturnFalse; } - QString msg = args.first(); - if (function == QLatin1String("error")) { - QStringList parents; - foreach (ProFile *proFile, m_profileStack) - parents.append(proFile->fileName()); - if (!parents.isEmpty()) - parents.takeLast(); - if (parents.isEmpty()) - q->fileMessage(format("Project ERROR: %1").arg(msg)); - else - q->fileMessage(format("Project ERROR: %1. File was included from: '%2'") - .arg(msg).arg(parents.join(QLatin1String("', '")))); - } else { - q->fileMessage(format("Project MESSAGE: %1").arg(msg)); - } - break; + QString msg = fixEnvVariables(args.first()); + q->fileMessage(QString::fromLatin1("Project %1: %2").arg(function.toUpper(), msg)); + // ### Consider real termination in non-cumulative mode + return returnBool(function != QLatin1String("error")); } - case CF_SYSTEM: { +#if 0 // Way too dangerous to enable. + case T_SYSTEM: { if (args.count() != 1) { q->logMessage(format("system(exec) requires one argument.")); - ok = false; - break; + ProItem::ReturnFalse; } - ok = system(args.first().toLatin1().constData()) == 0; - break; + return returnBool(system(args.first().toLatin1().constData()) == 0); } - case CF_ISEMPTY: { +#endif + case T_ISEMPTY: { if (args.count() != 1) { q->logMessage(format("isEmpty(var) requires one argument.")); - ok = false; - break; + return ProItem::ReturnFalse; } QStringList sl = values(args.first()); if (sl.count() == 0) { - cond = true; + return ProItem::ReturnTrue; } else if (sl.count() > 0) { QString var = sl.first(); - cond = (var.isEmpty()); + if (var.isEmpty()) + return ProItem::ReturnTrue; } - break; + return ProItem::ReturnFalse; } - case CF_EXISTS: { + case T_EXISTS: { if (args.count() != 1) { q->logMessage(format("exists(file) requires one argument.")); - ok = false; - break; + return ProItem::ReturnFalse; } QString file = args.first(); - - file = QDir::cleanPath(file); + file = Option::fixPathToLocalOS(file); if (QFile::exists(file)) { - cond = true; - break; + return ProItem::ReturnTrue; } //regular expression I guess - QString dirstr = getcwd(); + QString dirstr = currentDirectory(); int slsh = file.lastIndexOf(Option::dir_sep); if (slsh != -1) { dirstr = file.left(slsh+1); file = file.right(file.length() - slsh - 1); } - cond = QDir(dirstr).entryList(QStringList(file)).count(); + if (file.contains(QLatin1Char('*')) || file.contains(QLatin1Char('?'))) + if (!QDir(dirstr).entryList(QStringList(file)).isEmpty()) + return ProItem::ReturnTrue; - break; + return ProItem::ReturnFalse; } + case 0: + q->logMessage(format("'%1' is not a recognized test function").arg(function)); + return ProItem::ReturnFalse; + default: + q->logMessage(format("Function '%1' is not implemented").arg(function)); + return ProItem::ReturnFalse; } +} - if (result) - *result = cond; +QStringList ProFileEvaluator::Private::values(const QString &variableName, + const QHash<QString, QStringList> &place, + const ProFile *pro) const +{ + if (variableName == QLatin1String("LITERAL_WHITESPACE")) //a real space in a token + return QStringList(QLatin1String("\t")); + if (variableName == QLatin1String("LITERAL_DOLLAR")) //a real $ + return QStringList(QLatin1String("$")); + if (variableName == QLatin1String("LITERAL_HASH")) //a real # + return QStringList(QLatin1String("#")); + if (variableName == QLatin1String("OUT_PWD")) //the out going dir + return QStringList(m_outputDir); + if (variableName == QLatin1String("PWD") || //current working dir (of _FILE_) + variableName == QLatin1String("IN_PWD")) + return QStringList(currentDirectory()); + if (variableName == QLatin1String("DIR_SEPARATOR")) + return QStringList(Option::dir_sep); + if (variableName == QLatin1String("DIRLIST_SEPARATOR")) + return QStringList(Option::dirlist_sep); + if (variableName == QLatin1String("_LINE_")) //parser line number + return QStringList(QString::number(m_lineNo)); + if (variableName == QLatin1String("_FILE_")) //parser file; qmake is a bit weird here + return QStringList(m_profileStack.size() == 1 ? pro->fileName() : QFileInfo(pro->fileName()).fileName()); + if (variableName == QLatin1String("_DATE_")) //current date/time + return QStringList(QDateTime::currentDateTime().toString()); + if (variableName == QLatin1String("_PRO_FILE_")) + return QStringList(m_origfile); + if (variableName == QLatin1String("_PRO_FILE_PWD_")) + return QStringList(QFileInfo(m_origfile).absolutePath()); + if (variableName == QLatin1String("_QMAKE_CACHE_")) + return QStringList(); // FIXME? + if (variableName.startsWith(QLatin1String("QMAKE_HOST."))) { + QString ret, type = variableName.mid(11); +#if defined(Q_OS_WIN32) + if (type == QLatin1String("os")) { + ret = QLatin1String("Windows"); + } else if (type == QLatin1String("name")) { + DWORD name_length = 1024; + wchar_t name[1024]; + if (GetComputerName(name, &name_length)) + ret = QString::fromWCharArray(name); + } else if (type == QLatin1String("version") || type == QLatin1String("version_string")) { + QSysInfo::WinVersion ver = QSysInfo::WindowsVersion; + if (type == QLatin1String("version")) + ret = QString::number(ver); + else if (ver == QSysInfo::WV_Me) + ret = QLatin1String("WinMe"); + else if (ver == QSysInfo::WV_95) + ret = QLatin1String("Win95"); + else if (ver == QSysInfo::WV_98) + ret = QLatin1String("Win98"); + else if (ver == QSysInfo::WV_NT) + ret = QLatin1String("WinNT"); + else if (ver == QSysInfo::WV_2000) + ret = QLatin1String("Win2000"); + else if (ver == QSysInfo::WV_2000) + ret = QLatin1String("Win2003"); + else if (ver == QSysInfo::WV_XP) + ret = QLatin1String("WinXP"); + else if (ver == QSysInfo::WV_VISTA) + ret = QLatin1String("WinVista"); + else + ret = QLatin1String("Unknown"); + } else if (type == QLatin1String("arch")) { + SYSTEM_INFO info; + GetSystemInfo(&info); + switch(info.wProcessorArchitecture) { +#ifdef PROCESSOR_ARCHITECTURE_AMD64 + case PROCESSOR_ARCHITECTURE_AMD64: + ret = QLatin1String("x86_64"); + break; +#endif + case PROCESSOR_ARCHITECTURE_INTEL: + ret = QLatin1String("x86"); + break; + case PROCESSOR_ARCHITECTURE_IA64: +#ifdef PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 + case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: +#endif + ret = QLatin1String("IA64"); + break; + default: + ret = QLatin1String("Unknown"); + break; + } + } +#elif defined(Q_OS_UNIX) + struct utsname name; + if (!uname(&name)) { + if (type == QLatin1String("os")) + ret = QString::fromLatin1(name.sysname); + else if (type == QLatin1String("name")) + ret = QString::fromLatin1(name.nodename); + else if (type == QLatin1String("version")) + ret = QString::fromLatin1(name.release); + else if (type == QLatin1String("version_string")) + ret = QString::fromLatin1(name.version); + else if (type == QLatin1String("arch")) + ret = QString::fromLatin1(name.machine); + } +#endif + return QStringList(ret); + } - return ok; + QStringList result = place[variableName]; + if (result.isEmpty()) { + if (variableName == QLatin1String("TARGET")) { + result.append(QFileInfo(m_origfile).baseName()); + } else if (variableName == QLatin1String("TEMPLATE")) { + result.append(QLatin1String("app")); + } else if (variableName == QLatin1String("QMAKE_DIR_SEP")) { + result.append(Option::dirlist_sep); + } + } + return result; } QStringList ProFileEvaluator::Private::values(const QString &variableName) const { - if (variableName == QLatin1String("TARGET")) { - QStringList list = m_valuemap.value(variableName); - if (!m_origfile.isEmpty()) - list.append(QFileInfo(m_origfile).baseName()); - return list; - } - if (variableName == QLatin1String("PWD")) { - return QStringList(getcwd()); - } - return m_valuemap.value(variableName); + return values(variableName, m_valuemap, currentProFile()); } QStringList ProFileEvaluator::Private::values(const QString &variableName, const ProFile *pro) const { - if (variableName == QLatin1String("TARGET")) { - QStringList list = m_filevaluemap[pro].value(variableName); - if (!m_origfile.isEmpty()) - list.append(QFileInfo(m_origfile).baseName()); - return list; - } - if (variableName == QLatin1String("PWD")) { - return QStringList(QFileInfo(pro->fileName()).absoluteFilePath()); - } - return m_filevaluemap[pro].value(variableName); + return values(variableName, m_filevaluemap[pro], pro); } ProFile *ProFileEvaluator::parsedProFile(const QString &fileName) @@ -1473,27 +2397,21 @@ void ProFileEvaluator::releaseParsedProFile(ProFile *proFile) delete proFile; } -bool ProFileEvaluator::Private::evaluateFile(const QString &fileName, bool *result) +bool ProFileEvaluator::Private::evaluateFile(const QString &fileName) { - bool ok = true; ProFile *pro = q->parsedProFile(fileName); if (pro) { m_profileStack.push(pro); - ok = pro->Accept(this); + bool ok = (pro->Accept(this) == ProItem::ReturnTrue); m_profileStack.pop(); q->releaseParsedProFile(pro); - - if (result) - *result = true; + return ok; } else { - if (result) - *result = false; + return false; } - - return ok; } -bool ProFileEvaluator::Private::evaluateFeatureFile(const QString &fileName, bool *result) +bool ProFileEvaluator::Private::evaluateFeatureFile(const QString &fileName) { QString fn; foreach (const QString &path, qmakeFeaturePaths()) { @@ -1508,7 +2426,13 @@ bool ProFileEvaluator::Private::evaluateFeatureFile(const QString &fileName, boo break; } } - return fn.isEmpty() ? false : evaluateFile(fn, result); + if (fn.isEmpty()) + return false; + bool cumulative = m_cumulative; + m_cumulative = false; + bool ok = evaluateFile(fn); + m_cumulative = cumulative; + return ok; } QString ProFileEvaluator::Private::format(const char *fmt) const @@ -1542,14 +2466,23 @@ bool ProFileEvaluator::contains(const QString &variableName) const return d->m_valuemap.contains(variableName); } +inline QStringList fixEnvVariables(const QStringList &x) +{ + QStringList ret; + foreach (const QString &str, x) + ret << Option::fixString(str, Option::FixEnvVars); + return ret; +} + + QStringList ProFileEvaluator::values(const QString &variableName) const { - return d->values(variableName); + return fixEnvVariables(d->values(variableName)); } QStringList ProFileEvaluator::values(const QString &variableName, const ProFile *pro) const { - return d->values(variableName, pro); + return fixEnvVariables(d->values(variableName, pro)); } QStringList ProFileEvaluator::absolutePathValues( @@ -1609,12 +2542,14 @@ ProFileEvaluator::TemplateType ProFileEvaluator::templateType() { QStringList templ = values(QLatin1String("TEMPLATE")); if (templ.count() >= 1) { - QString t = templ.last().toLower(); - if (t == QLatin1String("app")) + const QString &t = templ.last(); + if (!t.compare(QLatin1String("app"), Qt::CaseInsensitive)) return TT_Application; - if (t == QLatin1String("lib")) + if (!t.compare(QLatin1String("lib"), Qt::CaseInsensitive)) return TT_Library; - if (t == QLatin1String("subdirs")) + if (!t.compare(QLatin1String("script"), Qt::CaseInsensitive)) + return TT_Script; + if (!t.compare(QLatin1String("subdirs"), Qt::CaseInsensitive)) return TT_Subdirs; } return TT_Unknown; @@ -1658,18 +2593,20 @@ void ProFileEvaluator::addProperties(const QHash<QString, QString> &properties) void ProFileEvaluator::logMessage(const QString &message) { - if (d->m_verbose) + if (d->m_verbose && !d->m_skipLevel) qWarning("%s", qPrintable(message)); } void ProFileEvaluator::fileMessage(const QString &message) { - qWarning("%s", qPrintable(message)); + if (!d->m_skipLevel) + qWarning("%s", qPrintable(message)); } void ProFileEvaluator::errorMessage(const QString &message) { - qWarning("%s", qPrintable(message)); + if (!d->m_skipLevel) + qWarning("%s", qPrintable(message)); } void ProFileEvaluator::setVerbose(bool on) @@ -1677,4 +2614,14 @@ void ProFileEvaluator::setVerbose(bool on) d->m_verbose = on; } +void ProFileEvaluator::setCumulative(bool on) +{ + d->m_cumulative = on; +} + +void ProFileEvaluator::setOutputDir(const QString &dir) +{ + d->m_outputDir = dir; +} + QT_END_NAMESPACE diff --git a/tools/linguist/shared/profileevaluator.h b/tools/linguist/shared/profileevaluator.h index 43a9ce4..7f947e0 100644 --- a/tools/linguist/shared/profileevaluator.h +++ b/tools/linguist/shared/profileevaluator.h @@ -50,15 +50,27 @@ #include <QtCore/QStringList> #include <QtCore/QStack> +#if (!defined(__GNUC__) || __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 3)) && !defined(__SUNPRO_CC) +# define HAVE_TEMPLATE_CLASS_FRIENDS +#endif + QT_BEGIN_NAMESPACE class ProFileEvaluator { +#ifdef HAVE_TEMPLATE_CLASS_FRIENDS +private: +#else +public: +#endif + class Private; + public: enum TemplateType { TT_Unknown = 0, TT_Application, TT_Library, + TT_Script, TT_Subdirs }; @@ -67,7 +79,9 @@ public: ProFileEvaluator::TemplateType templateType(); virtual bool contains(const QString &variableName) const; - void setVerbose(bool on); + void setVerbose(bool on); // Default is false + void setCumulative(bool on); // Default is true! + void setOutputDir(const QString &dir); // Default is empty bool queryProFile(ProFile *pro); bool accept(ProFile *pro); @@ -90,8 +104,11 @@ public: virtual void fileMessage(const QString &msg); // error() and message() from .pro file private: - class Private; Private *d; + +#ifdef HAVE_TEMPLATE_CLASS_FRIENDS + template<typename T> friend class QTypeInfo; +#endif }; QT_END_NAMESPACE diff --git a/tools/linguist/shared/proitems.cpp b/tools/linguist/shared/proitems.cpp index 83a31bb..e1563d5 100644 --- a/tools/linguist/shared/proitems.cpp +++ b/tools/linguist/shared/proitems.cpp @@ -58,15 +58,21 @@ QString ProItem::comment() const } // --------------- ProBlock ---------------- + ProBlock::ProBlock(ProBlock *parent) { m_blockKind = 0; m_parent = parent; + m_refCount = 1; } ProBlock::~ProBlock() { - qDeleteAll(m_proitems); + foreach (ProItem *itm, m_proitems) + if (itm->kind() == BlockKind) + static_cast<ProBlock *>(itm)->deref(); + else + delete itm; } void ProBlock::appendItem(ProItem *proitem) @@ -109,14 +115,37 @@ ProItem::ProItemKind ProBlock::kind() const return ProItem::BlockKind; } -bool ProBlock::Accept(AbstractProItemVisitor *visitor) -{ - visitor->visitBeginProBlock(this); - foreach (ProItem *item, m_proitems) { - if (!item->Accept(visitor)) - return false; +ProItem::ProItemReturn ProBlock::Accept(AbstractProItemVisitor *visitor) +{ + if (visitor->visitBeginProBlock(this) == ReturnSkip) + return ReturnTrue; + ProItemReturn rt = ReturnTrue; + for (int i = 0; i < m_proitems.count(); ++i) { + rt = m_proitems.at(i)->Accept(visitor); + if (rt != ReturnTrue && rt != ReturnFalse) { + if (rt == ReturnLoop) { + rt = ReturnTrue; + while (visitor->visitProLoopIteration()) + for (int j = i; ++j < m_proitems.count(); ) { + rt = m_proitems.at(j)->Accept(visitor); + if (rt != ReturnTrue && rt != ReturnFalse) { + if (rt == ReturnNext) { + rt = ReturnTrue; + break; + } + if (rt == ReturnBreak) + rt = ReturnTrue; + goto do_break; + } + } + do_break: + visitor->visitProLoopCleanup(); + } + break; + } } - return visitor->visitEndProBlock(this); + visitor->visitEndProBlock(this); + return rt; } // --------------- ProVariable ---------------- @@ -148,14 +177,13 @@ QString ProVariable::variable() const return m_variable; } -bool ProVariable::Accept(AbstractProItemVisitor *visitor) +ProItem::ProItemReturn ProVariable::Accept(AbstractProItemVisitor *visitor) { visitor->visitBeginProVariable(this); - foreach (ProItem *item, m_proitems) { - if (!item->Accept(visitor)) - return false; - } - return visitor->visitEndProVariable(this); + foreach (ProItem *item, m_proitems) + item->Accept(visitor); // cannot fail + visitor->visitEndProVariable(this); + return ReturnTrue; } // --------------- ProValue ---------------- @@ -190,9 +218,10 @@ ProItem::ProItemKind ProValue::kind() const return ProItem::ValueKind; } -bool ProValue::Accept(AbstractProItemVisitor *visitor) +ProItem::ProItemReturn ProValue::Accept(AbstractProItemVisitor *visitor) { - return visitor->visitProValue(this); + visitor->visitProValue(this); + return ReturnTrue; } // --------------- ProFunction ---------------- @@ -216,7 +245,7 @@ ProItem::ProItemKind ProFunction::kind() const return ProItem::FunctionKind; } -bool ProFunction::Accept(AbstractProItemVisitor *visitor) +ProItem::ProItemReturn ProFunction::Accept(AbstractProItemVisitor *visitor) { return visitor->visitProFunction(this); } @@ -242,9 +271,10 @@ ProItem::ProItemKind ProCondition::kind() const return ProItem::ConditionKind; } -bool ProCondition::Accept(AbstractProItemVisitor *visitor) +ProItem::ProItemReturn ProCondition::Accept(AbstractProItemVisitor *visitor) { - return visitor->visitProCondition(this); + visitor->visitProCondition(this); + return ReturnTrue; } // --------------- ProOperator ---------------- @@ -268,9 +298,10 @@ ProItem::ProItemKind ProOperator::kind() const return ProItem::OperatorKind; } -bool ProOperator::Accept(AbstractProItemVisitor *visitor) +ProItem::ProItemReturn ProOperator::Accept(AbstractProItemVisitor *visitor) { - return visitor->visitProOperator(this); + visitor->visitProOperator(this); + return ReturnTrue; } // --------------- ProFile ---------------- @@ -315,13 +346,12 @@ bool ProFile::isModified() const return m_modified; } -bool ProFile::Accept(AbstractProItemVisitor *visitor) +ProItem::ProItemReturn ProFile::Accept(AbstractProItemVisitor *visitor) { - visitor->visitBeginProFile(this); - foreach (ProItem *item, m_proitems) { - if (!item->Accept(visitor)) - return false; - } + ProItemReturn rt; + if ((rt = visitor->visitBeginProFile(this)) != ReturnTrue) + return rt; + ProBlock::Accept(visitor); // cannot fail return visitor->visitEndProFile(this); } diff --git a/tools/linguist/shared/proitems.h b/tools/linguist/shared/proitems.h index 5e36629..7ad1bb8 100644 --- a/tools/linguist/shared/proitems.h +++ b/tools/linguist/shared/proitems.h @@ -60,6 +60,16 @@ public: BlockKind }; + enum ProItemReturn { + ReturnFalse, + ReturnTrue, + ReturnBreak, + ReturnNext, + ReturnLoop, + ReturnSkip, + ReturnReturn + }; + ProItem() : m_lineNumber(0) {} virtual ~ProItem() {} @@ -68,7 +78,7 @@ public: void setComment(const QString &comment); QString comment() const; - virtual bool Accept(AbstractProItemVisitor *visitor) = 0; + virtual ProItemReturn Accept(AbstractProItemVisitor *visitor) = 0; int lineNumber() const { return m_lineNumber; } void setLineNumber(int lineNumber) { m_lineNumber = lineNumber; } @@ -86,7 +96,8 @@ public: ScopeContentsKind = 0x02, VariableKind = 0x04, ProFileKind = 0x08, - SingleLine = 0x10 + FunctionBodyKind = 0x10, + SingleLine = 0x80 }; ProBlock(ProBlock *parent); @@ -102,14 +113,18 @@ public: void setParent(ProBlock *parent); ProBlock *parent() const; + void ref() { ++m_refCount; } + void deref() { if (!--m_refCount) delete this; } + ProItem::ProItemKind kind() const; - virtual bool Accept(AbstractProItemVisitor *visitor); + virtual ProItemReturn Accept(AbstractProItemVisitor *visitor); protected: QList<ProItem *> m_proitems; private: ProBlock *m_parent; int m_blockKind; + int m_refCount; }; class ProVariable : public ProBlock @@ -131,7 +146,7 @@ public: void setVariable(const QString &name); QString variable() const; - virtual bool Accept(AbstractProItemVisitor *visitor); + virtual ProItemReturn Accept(AbstractProItemVisitor *visitor); private: VariableOperator m_variableKind; QString m_variable; @@ -150,7 +165,7 @@ public: ProItem::ProItemKind kind() const; - virtual bool Accept(AbstractProItemVisitor *visitor); + virtual ProItemReturn Accept(AbstractProItemVisitor *visitor); private: QString m_value; ProVariable *m_variable; @@ -166,7 +181,7 @@ public: ProItem::ProItemKind kind() const; - virtual bool Accept(AbstractProItemVisitor *visitor); + virtual ProItemReturn Accept(AbstractProItemVisitor *visitor); private: QString m_text; }; @@ -181,7 +196,7 @@ public: ProItem::ProItemKind kind() const; - virtual bool Accept(AbstractProItemVisitor *visitor); + virtual ProItemReturn Accept(AbstractProItemVisitor *visitor); private: QString m_text; }; @@ -201,7 +216,7 @@ public: ProItem::ProItemKind kind() const; - virtual bool Accept(AbstractProItemVisitor *visitor); + virtual ProItemReturn Accept(AbstractProItemVisitor *visitor); private: OperatorKind m_operatorKind; }; @@ -219,7 +234,7 @@ public: void setModified(bool modified); bool isModified() const; - virtual bool Accept(AbstractProItemVisitor *visitor); + virtual ProItemReturn Accept(AbstractProItemVisitor *visitor); private: QString m_fileName; diff --git a/tools/linguist/shared/proparserutils.h b/tools/linguist/shared/proparserutils.h index 6e19236..8dd8c6d 100644 --- a/tools/linguist/shared/proparserutils.h +++ b/tools/linguist/shared/proparserutils.h @@ -93,6 +93,25 @@ struct Option Option::qmakespec = QString::fromLatin1(qgetenv("QMAKESPEC").data()); Option::field_sep = QLatin1Char(' '); } + + enum StringFixFlags { + FixNone = 0x00, + FixEnvVars = 0x01, + FixPathCanonicalize = 0x02, + FixPathToLocalSeparators = 0x04, + FixPathToTargetSeparators = 0x08 + }; + static QString fixString(QString string, uchar flags); + + inline static QString fixPathToLocalOS(const QString &in, bool fix_env = true, bool canonical = true) + { + uchar flags = FixPathToLocalSeparators; + if (fix_env) + flags |= FixEnvVars; + if (canonical) + flags |= FixPathCanonicalize; + return fixString(in, flags); + } }; #if defined(Q_OS_WIN32) Option::TARG_MODE Option::target_mode = Option::TARG_WIN_MODE; @@ -110,17 +129,20 @@ QString Option::dir_sep; QChar Option::field_sep; static void insertUnique(QHash<QString, QStringList> *map, - const QString &key, const QStringList &value, bool unique = true) + const QString &key, const QStringList &value) { QStringList &sl = (*map)[key]; - if (!unique) { - sl += value; - } else { - for (int i = 0; i < value.count(); ++i) { - if (!sl.contains(value.at(i))) - sl.append(value.at(i)); - } - } + foreach (const QString &str, value) + if (!sl.contains(str)) + sl.append(str); +} + +static void removeEach(QHash<QString, QStringList> *map, + const QString &key, const QStringList &value) +{ + QStringList &sl = (*map)[key]; + foreach (const QString &str, value) + sl.removeAll(str); } /* @@ -148,7 +170,12 @@ static QStringList replaceInList(const QStringList &varList, const QRegExp ®e } */ -inline QStringList splitPathList(const QString paths) +inline QString fixEnvVariables(const QString &x) +{ + return Option::fixString(x, Option::FixEnvVars); +} + +inline QStringList splitPathList(const QString &paths) { return paths.split(Option::dirlist_sep); } @@ -255,7 +282,7 @@ static QStringList split_value_list(const QString &vals, bool do_semicolon=false static QStringList qmake_mkspec_paths() { QStringList ret; - const QString concat = QDir::separator() + QString(QLatin1String("mkspecs")); + const QString concat = QDir::separator() + QLatin1String("mkspecs"); QByteArray qmakepath = qgetenv("QMAKEPATH"); if (!qmakepath.isEmpty()) { const QStringList lst = splitPathList(QString::fromLocal8Bit(qmakepath)); diff --git a/tools/linguist/shared/qm.cpp b/tools/linguist/shared/qm.cpp index 55d1504..489279f 100644 --- a/tools/linguist/shared/qm.cpp +++ b/tools/linguist/shared/qm.cpp @@ -173,6 +173,7 @@ public: bool save(QIODevice *iod); void insert(const TranslatorMessage &msg, bool forceComment); + void insertIdBased(const TranslatorMessage &message); void squeeze(TranslatorSaveMode mode); @@ -238,17 +239,13 @@ Prefix Releaser::commonPrefix(const ByteTranslatorMessage &m1, const ByteTransla void Releaser::writeMessage(const ByteTranslatorMessage &msg, QDataStream &stream, TranslatorSaveMode mode, Prefix prefix) const { - for (int i = 0; i < msg.translations().count(); ++i) { - QString str = msg.translations().at(i); - str.replace(QChar(Translator::DefaultVariantSeparator), - QChar(Translator::InternalVariantSeparator)); - stream << quint8(Tag_Translation) << str; - } + for (int i = 0; i < msg.translations().count(); ++i) + stream << quint8(Tag_Translation) << msg.translations().at(i); if (mode == SaveEverything) prefix = HashContextSourceTextComment; - // lrelease produces "wrong" .qm files for QByteArrays that are .isNull(). + // lrelease produces "wrong" QM files for QByteArrays that are .isNull(). switch (prefix) { default: case HashContextSourceTextComment: @@ -440,6 +437,16 @@ void Releaser::insert(const TranslatorMessage &message, bool forceComment) insertInternal(message, forceComment, false); } +void Releaser::insertIdBased(const TranslatorMessage &message) +{ + QStringList tlns = message.translations(); + for (int i = 0; i < tlns.size(); ++i) + if (tlns.at(i).isEmpty()) + tlns[i] = message.sourceText(); + ByteTranslatorMessage bmsg("", originalBytes(message.id(), false), "", tlns); + m_messages.insert(bmsg, 0); +} + void Releaser::setNumerusRules(const QByteArray &rules) { m_numerusRules = rules; @@ -593,8 +600,6 @@ bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd) str[i] = QChar((str.at(i).unicode() >> 8) + ((str.at(i).unicode() << 8) & 0xff00)); } - str.replace(QChar(Translator::InternalVariantSeparator), - QChar(Translator::DefaultVariantSeparator)); translations << str; m += len; break; @@ -696,11 +701,17 @@ static bool saveQM(const Translator &translator, QIODevice &dev, ConversionData int finished = 0; int unfinished = 0; int untranslated = 0; + int missingIds = 0; + int droppedData = 0; for (int i = 0; i != translator.messageCount(); ++i) { const TranslatorMessage &msg = translator.message(i); TranslatorMessage::Type typ = msg.type(); if (typ != TranslatorMessage::Obsolete) { + if (cd.m_idBased && msg.id().isEmpty()) { + ++missingIds; + continue; + } if (typ == TranslatorMessage::Unfinished) { if (msg.translation().isEmpty()) { ++untranslated; @@ -713,19 +724,34 @@ static bool saveQM(const Translator &translator, QIODevice &dev, ConversionData } else { ++finished; } - // Drop the comment in (context, sourceText, comment), - // unless the context is empty, - // unless (context, sourceText, "") already exists or - // unless we already dropped the comment of (context, - // sourceText, comment0). - bool forceComment = - msg.comment().isEmpty() - || msg.context().isEmpty() - || translator.contains(msg.context(), msg.sourceText(), QString()); - releaser.insert(msg, forceComment); + if (cd.m_idBased) { + if (!msg.context().isEmpty() || !msg.comment().isEmpty()) + ++droppedData; + releaser.insertIdBased(msg); + } else { + // Drop the comment in (context, sourceText, comment), + // unless the context is empty, + // unless (context, sourceText, "") already exists or + // unless we already dropped the comment of (context, + // sourceText, comment0). + bool forceComment = + msg.comment().isEmpty() + || msg.context().isEmpty() + || translator.contains(msg.context(), msg.sourceText(), QString()); + releaser.insert(msg, forceComment); + } } } + if (missingIds) + cd.appendError(QCoreApplication::translate("LRelease", + "Dropped %n message(s) which had no ID.", 0, + QCoreApplication::CodecForTr, missingIds)); + if (droppedData) + cd.appendError(QCoreApplication::translate("LRelease", + "Excess context/disambiguation dropped from %n message(s).", 0, + QCoreApplication::CodecForTr, droppedData)); + releaser.squeeze(cd.m_saveMode); bool saved = releaser.save(&dev); if (saved && cd.isVerbose()) { diff --git a/tools/linguist/shared/qph.cpp b/tools/linguist/shared/qph.cpp index dcc48eb..78a8cbb 100644 --- a/tools/linguist/shared/qph.cpp +++ b/tools/linguist/shared/qph.cpp @@ -54,33 +54,19 @@ QT_BEGIN_NAMESPACE class QPHReader : public QXmlStreamReader { public: - QPHReader(QIODevice &dev, ConversionData &cd) - : QXmlStreamReader(&dev), m_cd(cd) + QPHReader(QIODevice &dev) + : QXmlStreamReader(&dev) {} // the "real thing" bool read(Translator &translator); private: - bool elementStarts(const QString &str) const - { - return isStartElement() && name() == str; - } - bool isWhiteSpace() const { return isCharacters() && text().toString().trimmed().isEmpty(); } - // needed to expand <byte ... /> - QString readContents(); - // needed to join <lengthvariant>s - QString readTransContents(); - - void handleError(); - - ConversionData &m_cd; - enum DataField { NoField, SourceField, TargetField, DefinitionField }; DataField m_currentField; QString m_currentSource; @@ -113,6 +99,8 @@ bool QPHReader::read(Translator &translator) else if (m_currentField == DefinitionField) m_currentDefinition += text(); } else if (isEndElement() && name() == QLatin1String("phrase")) { + m_currentTarget.replace(QChar(Translator::TextVariantSeparator), + QChar(Translator::BinaryVariantSeparator)); TranslatorMessage msg; msg.setSourceText(m_currentSource); msg.setTranslation(m_currentTarget); @@ -126,10 +114,10 @@ bool QPHReader::read(Translator &translator) return true; } -static bool loadQPH(Translator &translator, QIODevice &dev, ConversionData &cd) +static bool loadQPH(Translator &translator, QIODevice &dev, ConversionData &) { translator.setLocationsType(Translator::NoLocations); - QPHReader reader(dev, cd); + QPHReader reader(dev); return reader.read(translator); } @@ -173,7 +161,10 @@ static bool saveQPH(const Translator &translator, QIODevice &dev, ConversionData foreach (const TranslatorMessage &msg, translator.messages()) { t << "<phrase>\n"; t << " <source>" << protect(msg.sourceText()) << "</source>\n"; - t << " <target>" << protect(msg.translations().join(QLatin1String("@"))) + QString str = msg.translations().join(QLatin1String("@")); + str.replace(QChar(Translator::BinaryVariantSeparator), + QChar(Translator::TextVariantSeparator)); + t << " <target>" << protect(str) << "</target>\n"; if (!msg.context().isEmpty() || !msg.comment().isEmpty()) t << " <definition>" << msg.context() << msg.comment() diff --git a/tools/linguist/shared/translator.cpp b/tools/linguist/shared/translator.cpp index 7babe7a..494cdea 100644 --- a/tools/linguist/shared/translator.cpp +++ b/tools/linguist/shared/translator.cpp @@ -415,6 +415,26 @@ void Translator::dropTranslations() } } +void Translator::dropUiLines() +{ + QString uiXt = QLatin1String(".ui"); + QString juiXt = QLatin1String(".jui"); + for (TMM::Iterator it = m_messages.begin(); it != m_messages.end(); ++it) { + QHash<QString, int> have; + QList<TranslatorMessage::Reference> refs; + foreach (const TranslatorMessage::Reference &itref, it->allReferences()) { + const QString &fn = itref.fileName(); + if (fn.endsWith(uiXt) || fn.endsWith(juiXt)) { + if (++have[fn] == 1) + refs.append(TranslatorMessage::Reference(fn, -1)); + } else { + refs.append(itref); + } + } + it->setReferences(refs); + } +} + QSet<TranslatorMessagePtr> Translator::resolveDuplicates() { QSet<TranslatorMessagePtr> dups; diff --git a/tools/linguist/shared/translator.h b/tools/linguist/shared/translator.h index ec8d4f2..6c1cd52 100644 --- a/tools/linguist/shared/translator.h +++ b/tools/linguist/shared/translator.h @@ -47,7 +47,9 @@ #include <QDir> #include <QList> #include <QLocale> +#include <QMultiHash> #include <QString> +#include <QSet> QT_BEGIN_NAMESPACE @@ -63,6 +65,7 @@ public: m_ignoreUnfinished(false), m_sortContexts(false), m_noUiLines(false), + m_idBased(false), m_saveMode(SaveEverything) {} @@ -86,13 +89,17 @@ public: QString m_sourceFileName; QString m_targetFileName; QDir m_sourceDir; - QDir m_targetDir; // FIXME: TS spefic + QDir m_targetDir; // FIXME: TS specific + QSet<QString> m_projectRoots; + QMultiHash<QString, QString> m_allCSources; + QStringList m_includePath; QStringList m_dropTags; // tags to be dropped QStringList m_errors; bool m_verbose; bool m_ignoreUnfinished; bool m_sortContexts; bool m_noUiLines; + bool m_idBased; TranslatorSaveMode m_saveMode; }; @@ -128,6 +135,7 @@ public: void stripNonPluralForms(); void stripIdenticalSourceTranslations(); void dropTranslations(); + void dropUiLines(); void makeFileNamesAbsolute(const QDir &originalPath); QSet<TranslatorMessagePtr> resolveDuplicates(); static void reportDuplicates(const QSet<TranslatorMessagePtr> &dupes, @@ -139,7 +147,7 @@ public: QString languageCode() const { return m_language; } QString sourceLanguageCode() const { return m_sourceLanguage; } - enum LocationsType { NoLocations, RelativeLocations, AbsoluteLocations }; + enum LocationsType { DefaultLocations, NoLocations, RelativeLocations, AbsoluteLocations }; void setLocationsType(LocationsType lt) { m_locationsType = lt; } LocationsType locationsType() const { return m_locationsType; } @@ -179,15 +187,15 @@ public: QString description; // human-readable description LoadFunction loader; SaveFunction saver; - enum FileType { SourceCode, TranslationSource, TranslationBinary } fileType; + enum FileType { TranslationSource, TranslationBinary } fileType; int priority; // 0 = highest, -1 = invisible }; static void registerFileFormat(const FileFormat &format); static QList<FileFormat> ®isteredFileFormats(); - enum VariantSeparators { - DefaultVariantSeparator = 0x2762, // some weird character nobody ever heard of :-D - InternalVariantSeparator = 0x9c // unicode "STRING TERMINATOR" + enum { + TextVariantSeparator = 0x2762, // some weird character nobody ever heard of :-D + BinaryVariantSeparator = 0x9c // unicode "STRING TERMINATOR" }; private: diff --git a/tools/linguist/shared/translatortools.pri b/tools/linguist/shared/translatortools.pri deleted file mode 100644 index 2b6de8c..0000000 --- a/tools/linguist/shared/translatortools.pri +++ /dev/null @@ -1,11 +0,0 @@ - - -INCLUDEPATH *= $$PWD - -SOURCES += \ - $$PWD/translatortools.cpp \ - $$PWD/simtexth.cpp - -HEADERS += \ - $$PWD/translatortools.h - diff --git a/tools/linguist/shared/ts.cpp b/tools/linguist/shared/ts.cpp index 3350940..a7229a3 100644 --- a/tools/linguist/shared/ts.cpp +++ b/tools/linguist/shared/ts.cpp @@ -197,7 +197,7 @@ QString TSReader::readTransContents() // ignore these, just whitespace } else if (elementStarts(strlengthvariant)) { if (!result.isEmpty()) - result += QChar(Translator::DefaultVariantSeparator); + result += QChar(Translator::BinaryVariantSeparator); result += readContents(); } else { handleError(); @@ -514,7 +514,7 @@ static void writeExtras(QTextStream &t, const char *indent, static void writeVariants(QTextStream &t, const char *indent, const QString &input) { int offset; - if ((offset = input.indexOf(QChar(Translator::DefaultVariantSeparator))) >= 0) { + if ((offset = input.indexOf(QChar(Translator::BinaryVariantSeparator))) >= 0) { t << " variants=\"yes\">"; int start = 0; forever { @@ -524,7 +524,7 @@ static void writeVariants(QTextStream &t, const char *indent, const QString &inp if (offset == input.length()) break; start = offset + 1; - offset = input.indexOf(QChar(Translator::DefaultVariantSeparator), start); + offset = input.indexOf(QChar(Translator::BinaryVariantSeparator), start); if (offset < 0) offset = input.length(); } diff --git a/tools/linguist/shared/ts.dtd b/tools/linguist/shared/ts.dtd index cfbc786..4d2cdeb 100644 --- a/tools/linguist/shared/ts.dtd +++ b/tools/linguist/shared/ts.dtd @@ -34,7 +34,7 @@ version CDATA #IMPLIED sourcelanguage CDATA #IMPLIED language CDATA #IMPLIED> -<!-- The encoding to use in the .qm file by default. Default is ISO-8859-1. --> +<!-- The encoding to use in the QM file by default. Default is ISO-8859-1. --> <!ELEMENT defaultcodec (#PCDATA) > <!ELEMENT context (name?, comment?, (context|message)+) > <!ATTLIST context @@ -54,7 +54,7 @@ <!ELEMENT message (location*, source?, oldsource?, comment?, oldcomment?, extracomment?, translatorcomment?, translation?, userdata?, extra-**) > <!-- ! If utf8 is true, the defaultcodec is overridden and the message is encoded - ! in UTF-8 in the .qm file. + ! in UTF-8 in the QM file. --> <!ATTLIST message id CDATA #IMPLIED @@ -70,7 +70,7 @@ ! is omitted, the "current" one is used. For the 1st location in a message, ! "current" is the filename used for the 1st location of the previous message. ! For subsequent locations, it is the filename used for the previous location. - ! A single .ts file has either all absolute or all relative locations. + ! A single TS file has either all absolute or all relative locations. --> <!ATTLIST location filename CDATA #IMPLIED @@ -100,5 +100,14 @@ --> <!ELEMENT numerusform (#PCDATA|byte|lengthvariant)* > <!ATTLIST numerusform + plurality (nullar|singular|dual|trial|paucal|greaterpaucal|plural|greaterplural) #IMPLIED> variants (yes|no) "no"> <!ELEMENT lengthvariant %evilstring; > +<!-- + ! The translation variants have a priority between 1 ("highest") and 9 ("lowest") + ! Typically longer translations get a higher priority. + ! If omitted, the order of appearance of the variants in the TS files is used. + --> +<!ATTLIST lengthvariant + priority (1|2|3|4|5|6|7|8|9) #IMPLIED> + diff --git a/tools/linguist/shared/xliff.cpp b/tools/linguist/shared/xliff.cpp index a3c424d..1285b44 100644 --- a/tools/linguist/shared/xliff.cpp +++ b/tools/linguist/shared/xliff.cpp @@ -302,6 +302,8 @@ static void writeTransUnits(QTextStream &ts, const TranslatorMessage &msg, const QString translation; if (transit != transend) { translation = *transit; + translation.replace(QChar(Translator::BinaryVariantSeparator), + QChar(Translator::TextVariantSeparator)); ++transit; puttrans = true; } @@ -596,8 +598,11 @@ bool XLIFFHandler::endElement(const QString &namespaceURI, const QString& localN m_sources.append(accum); } } else if (localName == QLatin1String("target")) { - if (popContext(XC_restype_translation)) + if (popContext(XC_restype_translation)) { + accum.replace(QChar(Translator::TextVariantSeparator), + QChar(Translator::BinaryVariantSeparator)); m_translations.append(accum); + } } else if (localName == QLatin1String("context-group")) { if (popContext(XC_context_group)) { m_refs.append(TranslatorMessage::Reference( |