"Fossies" - the Fresh Open Source Software Archive

Member "texstudio-2.12.22/src/buildmanager.cpp" (15 Jan 2020, 99697 Bytes) of package /linux/misc/texstudio-2.12.22.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "buildmanager.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.12.20_vs_2.12.22.

    1 #include "buildmanager.h"
    2 
    3 #include "smallUsefulFunctions.h"
    4 #include "configmanagerinterface.h"
    5 #include "utilsSystem.h"
    6 #include "execprogram.h"
    7 
    8 #include "userquickdialog.h"
    9 
   10 #ifdef Q_OS_WIN32
   11 #include "windows.h"
   12 #endif
   13 
   14 #ifdef Q_OS_WIN32
   15 #define ON_WIN(x) x
   16 #define ON_NIX(x)
   17 #else
   18 #define ON_WIN(x)
   19 #define ON_NIX(x) x
   20 #endif
   21 
   22 static const QString DEPRECACTED_TMX_INTERNAL_PDF_VIEWER = "tmx://internal-pdf-viewer";
   23 
   24 const QString BuildManager::TXS_CMD_PREFIX = "txs:///";
   25 
   26 int BuildManager::autoRerunLatex = 5;
   27 bool BuildManager::showLogInCaseOfCompileError = true;
   28 bool BuildManager::m_replaceEnvironmentVariables = true;
   29 bool BuildManager::m_interpetCommandDefinitionInMagicComment = true;
   30 bool BuildManager::m_supportShellStyleLiteralQuotes = true;
   31 bool BuildManager::singleViewerInstance = false;
   32 QString BuildManager::autoRerunCommands;
   33 QString BuildManager::additionalSearchPaths, BuildManager::additionalPdfPaths, BuildManager::additionalLogPaths;
   34 
   35 // *INDENT-OFF* (astyle-config)
   36 #define CMD_DEFINE(up, id) const QString BuildManager::CMD_##up = BuildManager::TXS_CMD_PREFIX + #id;
   37 CMD_DEFINE(LATEX, latex) CMD_DEFINE(PDFLATEX, pdflatex) CMD_DEFINE(XELATEX, xelatex) CMD_DEFINE(LUALATEX, lualatex) CMD_DEFINE(LATEXMK, latexmk)
   38 CMD_DEFINE(VIEW_DVI, view-dvi) CMD_DEFINE(VIEW_PS, view-ps) CMD_DEFINE(VIEW_PDF, view-pdf) CMD_DEFINE(VIEW_LOG, view-log)
   39 CMD_DEFINE(DVIPNG, dvipng) CMD_DEFINE(DVIPS, dvips) CMD_DEFINE(DVIPDF, dvipdf) CMD_DEFINE(PS2PDF, ps2pdf) CMD_DEFINE(GS, gs) CMD_DEFINE(MAKEINDEX, makeindex) CMD_DEFINE(TEXINDY, texindy) CMD_DEFINE(MAKEGLOSSARIES, makeglossaries) CMD_DEFINE(METAPOST, metapost) CMD_DEFINE(ASY, asy) CMD_DEFINE(BIBTEX, bibtex) CMD_DEFINE(BIBTEX8, bibtex8) CMD_DEFINE(BIBER, biber) CMD_DEFINE(SVN, svn) CMD_DEFINE(SVNADMIN, svnadmin)
   40 CMD_DEFINE(COMPILE, compile) CMD_DEFINE(VIEW, view) CMD_DEFINE(BIBLIOGRAPHY, bibliography) CMD_DEFINE(INDEX, index) CMD_DEFINE(GLOSSARY, glossary) CMD_DEFINE(QUICK, quick) CMD_DEFINE(RECOMPILE_BIBLIOGRAPHY, recompile-bibliography)
   41 CMD_DEFINE(VIEW_PDF_INTERNAL, view-pdf-internal) CMD_DEFINE(CONDITIONALLY_RECOMPILE_BIBLIOGRAPHY, conditionally-recompile-bibliography)
   42 CMD_DEFINE(INTERNAL_PRE_COMPILE, internal-pre-compile)
   43 #undef CMD_DEFINE
   44 // *INDENT-ON* (astyle-config)
   45 
   46 //! These commands should not consist of a command list, but rather a single command.
   47 //! Otherwise surpising side effects can happen, see https://sourceforge.net/p/texstudio/bugs/2119/
   48 const QStringList atomicCommands = QStringList() << "txs:///latex" << "txs:///pdflatex" << "txs:///xelatex"<< "txs:///lualatex" << "txs:///latexmk";
   49 
   50 QString searchBaseCommand(const QString &cmd, QString options);
   51 QString getCommandLineViewDvi();
   52 QString getCommandLineViewPs();
   53 QString getCommandLineViewPdfExternal();
   54 QString getCommandLineGhostscript();
   55 
   56 CommandInfo::CommandInfo(): user(false), meta(false), rerunCompiler(false), guessFunc(nullptr) {}
   57 
   58 QString CommandInfo::guessCommandLine() const
   59 {
   60     if (guessFunc) {
   61         QString temp = (*guessFunc)();
   62         if (!temp.isEmpty()) return temp;
   63     }
   64 
   65     if (!baseName.isEmpty()) {
   66         //search it
   67         QString bestCommand = searchBaseCommand(baseName, defaultArgs);
   68         if (!bestCommand.isEmpty()) return bestCommand;
   69     }
   70 
   71     if (metaSuggestionList.size() > 0)
   72         return metaSuggestionList.first();
   73 
   74     return "";
   75 }
   76 
   77 void CommandInfo::setCommandLine(const QString &cmdString)
   78 {
   79     if (cmdString == "<default>") commandLine = guessCommandLine();
   80     if (cmdString == BuildManager::tr("<unknown>")) commandLine = "";
   81     else {
   82         //force commands to include options (e.g. file name)
   83         QString trimmed = cmdString.trimmed();
   84         QString unquote = trimmed;
   85         if (trimmed.startsWith('"') && trimmed.endsWith('"')) unquote = trimmed.mid(1, trimmed.length() - 2);
   86         if (baseName != "" &&
   87                 ((unquote == baseName) ||
   88                  (   (unquote.endsWith(QDir::separator() + baseName) || unquote.endsWith("/" + baseName))
   89                      && (!unquote.contains(" ") || (!unquote.contains('"') && unquote != trimmed)) //spaces mean options, if not everything is quoted
   90                      && (QFileInfo(unquote).exists())
   91                  )
   92                 )) {
   93             commandLine = cmdString + " " + defaultArgs;
   94         } else {
   95             commandLine = cmdString;
   96         }
   97     }
   98 }
   99 
  100 QString CommandInfo::getPrettyCommand() const
  101 {
  102     if (commandLine.isEmpty() && metaSuggestionList.isEmpty()) return BuildManager::tr("<unknown>");
  103     else return commandLine;
  104 }
  105 
  106 QString CommandInfo::getProgramName(const QString &commandLine)
  107 {
  108     QString cmdStr = commandLine.trimmed();
  109     int p = -1;
  110     if (cmdStr.startsWith('"')) p = cmdStr.indexOf('"', 1) + 1;
  111     else if (cmdStr.contains(' ')) p = cmdStr.indexOf(' ');
  112     if (p == -1) p = cmdStr.length(); //indexOf failed if it returns -1
  113     return cmdStr.mid(0, p);
  114 }
  115 
  116 QString CommandInfo::getProgramNameUnquoted(const QString &commandLine)
  117 {
  118     QString cmdStr = getProgramName(commandLine);
  119     if (cmdStr.startsWith('"') && cmdStr.endsWith('"'))
  120         cmdStr = cmdStr.mid(1, cmdStr.length() - 2);
  121     return cmdStr;
  122 }
  123 
  124 QString CommandInfo::getProgramName() const
  125 {
  126     return getProgramName(commandLine);
  127 }
  128 
  129 QString CommandInfo::getProgramNameUnquoted() const
  130 {
  131     return getProgramNameUnquoted(commandLine);
  132 }
  133 
  134 ExpandingOptions::ExpandingOptions(const QFileInfo &mainFile, const QFileInfo &currentFile, const int currentLine): mainFile(mainFile), currentFile(currentFile), currentLine(currentLine), nestingDeep(0), canceled(false)
  135 {
  136     override.removeAll = false;
  137 }
  138 
  139 /*
  140 QString BuildManager::getLatexCommandExecutable(LatexCommand cmd){
  141  QString cmdStr = getLatexCommand(cmd).trimmed();
  142  int p=-1;
  143  if (cmdStr.startsWith('"')) p = cmdStr.indexOf('"',1)+1;
  144  else if (cmdStr.contains(' ')) p = cmdStr.indexOf(' ')+1;
  145  if (p==-1) p = cmdStr.length(); //indexOf failed if it returns -1
  146  return cmdStr.mid(0, p);
  147 }*/
  148 
  149 CommandToRun::CommandToRun() {}
  150 CommandToRun::CommandToRun(const QString &cmd): command(cmd) {}
  151 
  152 QString BuildManager::chainCommands(const QString &a)
  153 {
  154     return a;
  155 }
  156 
  157 QString BuildManager::chainCommands(const QString &a, const QString &b)
  158 {
  159     return a + "|" + b;
  160 }
  161 
  162 QString BuildManager::chainCommands(const QString &a, const QString &b, const QString &c)
  163 {
  164     return a + "|" + b + "|" + c;
  165 }
  166 
  167 QString BuildManager::chainCommands(const QString &a, const QString &b, const QString &c, const QString &d)
  168 {
  169     return a + "|" + b + "|" + c + "|" + d;
  170 }
  171 
  172 /** splits a string into options. Splitting occurs at spaces, except in quotes. **/
  173 QStringList BuildManager::splitOptions(const QString &s)
  174 {
  175     QStringList options;
  176     QChar c;
  177     bool inQuote = false;
  178     int start = 0;
  179     int i;
  180     for (i = 0; i < s.length(); i++) {
  181         c = s[i];
  182         if (inQuote) {
  183             if (c == '"' && s[i - 1] != '\\') {
  184                 inQuote = false;
  185             }
  186         } else {
  187             if (c == '"') {
  188                 inQuote = true;
  189             } else if (c == ' ') {
  190                 if (start == i) start = i + 1; // multiple spaces;
  191                 else {
  192                     options << dequoteStr(s.mid(start, i - start));
  193                     start = i + 1;
  194                 }
  195             }
  196         }
  197     }
  198     if (start < i) options << dequoteStr(s.mid(start, i - start));
  199     return options;
  200 }
  201 
  202 QHash<QString, QString> getEnvVariables(bool uppercaseNames)
  203 {
  204     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
  205     QHash<QString, QString> result;
  206     foreach (const QString &name, envKeys(env)) {
  207         if (uppercaseNames) {
  208             result.insert(name.toUpper(), env.value(name));
  209         } else {
  210             result.insert(name, env.value(name));
  211         }
  212     }
  213     return result;
  214 }
  215 
  216 QString BuildManager::replaceEnvironmentVariables(const QString &s)
  217 {
  218 #ifdef Q_OS_WIN
  219     Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
  220 #else
  221     Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive;
  222 #endif
  223     static QHash<QString, QString> envVariables = getEnvVariables(caseSensitivity == Qt::CaseInsensitive);  // environment variables can be static because they do not change during program execution.
  224     return replaceEnvironmentVariables(s, envVariables, caseSensitivity == Qt::CaseInsensitive);
  225 }
  226 
  227 /*!
  228  * Replace environment variables in the string.
  229  */
  230 QString BuildManager::replaceEnvironmentVariables(const QString &s, const QHash<QString, QString> &variables, bool compareNamesToUpper)
  231 {
  232     QString result(s);
  233 #ifdef Q_OS_WIN
  234     QRegExp rxEnvVar("%([\\w()]+)%");  // word and brackets between %...%
  235 #else
  236     QRegExp rxEnvVar("\\$(\\w+)");
  237 #endif
  238     int i = 0;
  239     while (i >= 0 && i < result.length()) {
  240         i = result.indexOf(rxEnvVar, i);
  241         if (i >= 0) {
  242             QString varName = rxEnvVar.cap(1);
  243             if (compareNamesToUpper) {
  244                 varName = varName.toUpper();
  245             }
  246             QString varContent = variables.value(varName, "");
  247             result.replace(rxEnvVar.cap(0), varContent);
  248             i += varContent.length();
  249         }
  250     }
  251     return result;
  252 }
  253 
  254 /*!
  255  * returns paths with replaced [txs-app-dir], [txs-config-dir] and optionally with replaced environment variables
  256  * paths may be an arbitrary string, in particular a single path or a list of paths
  257  */
  258 QString BuildManager::resolvePaths(QString paths)
  259 {
  260     paths = ConfigManagerInterface::getInstance()->parseDir(paths);
  261     if (m_replaceEnvironmentVariables)
  262         return replaceEnvironmentVariables(paths);
  263     else
  264         return paths;
  265 }
  266 
  267 BuildManager::BuildManager(): processWaitedFor(nullptr)
  268 #ifdef Q_OS_WIN32
  269     , pidInst(0)
  270 #endif
  271 {
  272     initDefaultCommandNames();
  273     connect(this, SIGNAL(commandLineRequested(QString, QString *, bool *)), SLOT(commandLineRequestedDefault(QString, QString *, bool *)));
  274 }
  275 
  276 BuildManager::~BuildManager()
  277 {
  278     //remove preview file names
  279     foreach (const QString &elem, previewFileNames)
  280         removePreviewFiles(elem);
  281 #ifdef Q_OS_WIN32
  282     if (pidInst) DdeUninitialize(pidInst);
  283 #endif
  284 }
  285 
  286 void BuildManager::initDefaultCommandNames()
  287 {
  288     REQUIRE (commands.isEmpty());
  289 
  290     //id, platform-independent command, display name, arguments
  291     registerCommand("latex",       "latex",        "LaTeX",       "-src -interaction=nonstopmode %.tex", "Tools/Latex");
  292     registerCommand("pdflatex",    "pdflatex",     "PdfLaTeX",    "-synctex=1 -interaction=nonstopmode %.tex", "Tools/Pdflatex");
  293     registerCommand("xelatex",     "xelatex",      "XeLaTeX",     "-synctex=1 -interaction=nonstopmode %.tex", "");
  294     registerCommand("lualatex",    "lualatex",     "LuaLaTeX",    "-synctex=1 -interaction=nonstopmode %.tex", "");
  295     registerCommand("view-dvi",    "",             tr("DVI Viewer"), "%.dvi", "Tools/Dvi", &getCommandLineViewDvi);
  296     registerCommand("view-ps",     "",             tr("PS Viewer"), "%.ps", "Tools/Ps", &getCommandLineViewPs);
  297     registerCommand("view-pdf-external", "",        tr("External PDF Viewer"), "%.pdf", "Tools/Pdf", &getCommandLineViewPdfExternal);
  298     registerCommand("dvips",       "dvips",        "DviPs",       "-o %.ps %.dvi", "Tools/Dvips");
  299     registerCommand("dvipng",      "dvipng",       "DviPng",      "-T tight -D 120 %.dvi", "Tools/Dvipng");
  300     registerCommand("ps2pdf",      "ps2pdf",       "Ps2Pdf",      "%.ps", "Tools/Ps2pdf");
  301     registerCommand("dvipdf",      "dvipdfmx",       "DviPdf",      "%.dvi", "Tools/Dvipdf");
  302     registerCommand("bibtex",      "bibtex",       "BibTeX",       ON_WIN("%") ON_NIX("%.aux"),  "Tools/Bibtex"); //miktex bibtex will stop (appears like crash in txs) if .aux is attached
  303     registerCommand("bibtex8",     "bibtex8",      "BibTeX 8-Bit", ON_WIN("%") ON_NIX("%.aux"));
  304     registerCommand("biber",       "biber",        "Biber" ,       "%"); //todo: correct parameter?
  305     registerCommand("makeindex",   "makeindex",    "Makeindex",   "%.idx", "Tools/Makeindex");
  306     registerCommand("texindy",     "texindy",      "Texindy", "%.idx");
  307     registerCommand("makeglossaries", "makeglossaries", "Makeglossaries", "%");
  308     registerCommand("metapost",    "mpost",        "Metapost",    "-interaction=nonstopmode ?me)", "Tools/Metapost");
  309     registerCommand("asy",         "asy",          "Asymptote",   "?m*.asy", "Tools/Asy");
  310     registerCommand("gs",          "gs;mgs",       "Ghostscript", "\"?am.ps\"", "Tools/Ghostscript", &getCommandLineGhostscript);
  311     registerCommand("latexmk",     "latexmk",      "Latexmk",     "-pdf -silent -synctex=1 %");
  312 
  313 
  314     QStringList descriptionList;
  315     descriptionList << tr("Compile & View") << tr("PS Chain") << tr("DVI Chain") << tr("PDF Chain") << tr("DVI->PDF Chain") << tr("DVI->PS->PDF Chain") << tr("Asymptote DVI Chain") << tr("Asymptote PDF Chain");
  316     registerCommand("quick", tr("Build & View"), QStringList() << "txs:///compile | txs:///view" << "txs:///ps-chain" << "txs:///dvi-chain" << "txs:///pdf-chain" << "txs:///dvi-pdf-chain" << "txs:///dvi-ps-pdf-chain" << "txs:///asy-dvi-chain" << "txs:///asy-pdf-chain" /* too long breaks design<< "latex -interaction=nonstopmode %.tex|bibtex %.aux|latex -interaction=nonstopmode %.tex|latex -interaction=nonstopmode %.tex| txs:///view-dvi"*/, "Tools/Userquick", true, descriptionList);
  317 
  318     descriptionList.clear();
  319     descriptionList << tr("PdfLaTeX") << tr("LaTeX") << tr("XeLaTeX") << tr("LuaLaTeX") << tr("Latexmk");
  320     registerCommand("compile", tr("Default Compiler"), QStringList() << "txs:///pdflatex" << "txs:///latex" << "txs:///xelatex" << "txs:///lualatex" << "txs:///latexmk", "", true, descriptionList);
  321     descriptionList.clear();
  322     descriptionList << tr("PDF Viewer") << tr("DVI Viewer") << tr("PS Viewer");
  323     registerCommand("view", tr("Default Viewer"), QStringList() << "txs:///view-pdf" << "txs:///view-dvi" << "txs:///view-ps", "", true, descriptionList);
  324     descriptionList.clear();
  325     descriptionList << tr("Internal PDF Viewer (Embedded)") << tr("Internal PDF Viewer (Windowed)")  << tr("External PDF Viewer");
  326     registerCommand("view-pdf", tr("PDF Viewer"), QStringList() << "txs:///view-pdf-internal --embedded" << "txs:///view-pdf-internal" << "txs:///view-pdf-external", "", true, descriptionList);
  327     descriptionList.clear();
  328     descriptionList << tr("BibTeX") << tr("BibTeX 8-Bit") << tr("Biber");
  329     registerCommand("bibliography", tr("Default Bibliography Tool"), QStringList() << "txs:///bibtex" << "txs:///bibtex8" << "txs:///biber", "", true, descriptionList);
  330     descriptionList.clear();
  331     descriptionList << tr("BibTeX") << tr("BibTeX 8-Bit") << tr("Biber");
  332     registerCommand("index", tr("Default Index Tool"), QStringList() << "txs:///makeindex" << "txs:///texindy", "", true, descriptionList);
  333     descriptionList.clear();
  334     descriptionList << tr("Makeglossaries");
  335     registerCommand("glossary", tr("Default Glossary Tool"), QStringList() << "txs:///makeglossaries", "", true, descriptionList);
  336 
  337     registerCommand("ps-chain", tr("PS Chain"), QStringList() << "txs:///latex | txs:///dvips | txs:///view-ps");
  338     registerCommand("dvi-chain", tr("DVI Chain"), QStringList() << "txs:///latex | txs:///view-dvi");
  339     registerCommand("pdf-chain", tr("PDF Chain"), QStringList() << "txs:///pdflatex | txs:///view-pdf");
  340     registerCommand("dvi-pdf-chain", tr("DVI->PDF Chain"), QStringList() << "txs:///latex | txs:///dvipdf | txs:///view-pdf");
  341     registerCommand("dvi-ps-pdf-chain", tr("DVI->PS->PDF Chain"), QStringList() << "txs:///latex | txs:///dvips | txs:///ps2pdf | txs:///view-pdf");
  342     registerCommand("asy-dvi-chain", tr("Asymptote DVI Chain"), QStringList() << "txs:///latex | txs:///asy | txs:///latex | txs:///view-dvi");
  343     registerCommand("asy-pdf-chain", tr("Asymptote PDF Chain"), QStringList() << "txs:///pdflatex | txs:///asy | txs:///pdflatex | txs:///view-pdf");
  344 
  345     registerCommand("pre-compile", tr("Precompile"), QStringList() << "", "Tools/Precompile");
  346     registerCommand("internal-pre-compile", tr("Internal Precompile"), QStringList() << "txs:///pre-compile | txs:///conditionally-recompile-bibliography");
  347     registerCommand("recompile-bibliography", tr("Recompile Bibliography"), QStringList() << "txs:///compile | txs:///bibliography | txs:///compile");
  348 
  349 
  350     registerCommand("svn",         "svn",          "SVN",         "", "Tools/SVN");
  351     registerCommand("svnadmin",    "svnadmin",     "SVNADMIN",    "", "Tools/SVNADMIN");
  352 
  353     internalCommands << CMD_VIEW_PDF_INTERNAL << CMD_CONDITIONALLY_RECOMPILE_BIBLIOGRAPHY << CMD_VIEW_LOG;
  354 }
  355 
  356 void BuildManager::checkOSXElCapitanDeprecatedPaths(QSettings &settings, const QStringList &commands)
  357 {
  358 #ifdef Q_OS_MAC
  359 #if QT_VERSION >= 0x050500
  360     if (QSysInfo::MacintoshVersion == QSysInfo::MV_10_11) {
  361 #else  // older Qt versions don't know MV_10_11
  362     if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_10) {
  363 #endif
  364         ConfigManagerInterface *config = ConfigManagerInterface::getInstance();
  365         if (!config->getOption("Tools/CheckOSXElCapitanDeprecatedPaths", true).toBool()) {
  366             return;
  367         }
  368         bool oldPathsFound = false;
  369         foreach (const QString &id, commands) {
  370             QString cmd = settings.value(id).toString();
  371             if (cmd.contains("/usr/texbin/")) {
  372                 oldPathsFound = true;
  373                 break;
  374             }
  375         }
  376         if (!oldPathsFound) {
  377             return;
  378         }
  379         QString newPath = "/Library/TeX/texbin/";
  380         QString info(tr("OSX 10.11 does not allow applications to write there anymore. Therefore,\n"
  381                         "recent versions of MacTeX changed the bin path to /Library/TeX/texbin/\n"
  382                         "\n"
  383                         "Do you want TeXstudio to change all command paths from /usr/texbin/ to\n"
  384                         "%1?"));
  385         if (QDir("/Library/TeX/texbin/").exists()) {
  386             info = info.arg("/Library/TeX/texbin/");
  387         } else if (QDir("/usr/local/texbin/").exists()) {
  388             info = info.arg("/usr/local/texbin/");
  389             newPath = "/usr/local/texbin/";
  390         } else {
  391             info = tr("OSX 10.11 does not allow applications to write there anymore. You may\n"
  392                       "need to update MacTeX to version 2015.\n"
  393                       "\n"
  394                       "Afterwards, MacTeX programs will be located at /Library/TeX/texbin/\n"
  395                       "\n"
  396                       "Do you want TeXstudio to change all command paths from /usr/texbin/ to\n"
  397                       "/Library/TeX/texbin/?");
  398         }
  399         QMessageBox msgBox(QApplication::activeWindow());
  400         msgBox.setWindowTitle(TEXSTUDIO);
  401         msgBox.setIcon(QMessageBox::Warning);
  402         msgBox.setText(tr("Some of your commands are refering to locations in /usr/texbin/"));
  403         msgBox.setInformativeText(info);
  404         msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
  405         msgBox.setDefaultButton(QMessageBox::Yes);
  406         emit hideSplash();  // make sure the message box not hidden by the splash screen
  407         int ret = msgBox.exec();
  408         if (ret == QMessageBox::Yes) {
  409             config->setOption("Tools/CheckOSXElCapitanDeprecatedPaths", false);
  410             foreach (const QString &id, commands) {
  411                 QString cmd = settings.value(id).toString();
  412                 if (cmd.contains("/usr/texbin/")) {
  413                     cmd.replace("/usr/texbin/", newPath);
  414                 }
  415                 settings.setValue(id, cmd);
  416             }
  417         } else if (ret == QMessageBox::No) {
  418             config->setOption("Tools/CheckOSXElCapitanDeprecatedPaths", false);
  419         }
  420     }
  421 #else
  422     Q_UNUSED(settings)
  423     Q_UNUSED(commands)
  424 #endif
  425 }
  426 
  427 CommandInfo &BuildManager::registerCommand(const QString &id, const QString &basename, const QString &displayName, const QString &args, const QString &oldConfig, const GuessCommandLineFunc guessFunc, bool user )
  428 {
  429     CommandInfo ci;
  430     ci.id = id;
  431     ci.baseName = basename;
  432     ci.displayName = displayName;
  433     ci.defaultArgs = args;
  434     ci.deprecatedConfigName = oldConfig;
  435     ci.guessFunc = guessFunc;
  436     ci.user = user;
  437     if (!user) commandSortingsOrder << id;
  438     return commands.insert(id, ci).value();
  439 }
  440 
  441 CommandInfo &BuildManager::registerCommand(const QString &id, const QString &displayname, const QStringList &alternatives, const QString &oldConfig, const bool metaCommand, const QStringList simpleDescriptions)
  442 {
  443     CommandInfo ci;
  444     ci.id = id;
  445     ci.displayName = displayname;
  446     ci.metaSuggestionList = alternatives;
  447     ci.simpleDescriptionList = simpleDescriptions;
  448     ci.meta = metaCommand;
  449     ci.deprecatedConfigName = oldConfig;
  450     commandSortingsOrder << id;
  451     return commands.insert(id, ci).value();
  452 }
  453 
  454 QString BuildManager::getCommandLine(const QString &id, bool *user)
  455 {
  456     QString result;
  457     emit commandLineRequested(id.trimmed(), &result, user);
  458     return result;
  459 }
  460 
  461 QStringList BuildManager::parseExtendedCommandLine(QString str, const QFileInfo &mainFile, const QFileInfo &currentFile, int currentline)
  462 {
  463     ConfigManagerInterface *config = ConfigManagerInterface::getInstance();
  464     str = config->parseDir(str);
  465     if (m_replaceEnvironmentVariables) {
  466         str = replaceEnvironmentVariables(str);
  467     }
  468     // need to reformat literal quotes before the file insertion logic, because ?a"
  469     // might be extended to "C:\somepath\" which would then be misinterpreted as an
  470     // literal quote at the end.
  471     if (config->getOption("Tools/SupportShellStyleLiteralQuotes", true).toBool()) {
  472         str = ProcessX::reformatShellLiteralQuotes(str);
  473     }
  474 
  475     str = str + " "; //end character  so str[i++] is always defined
  476     QStringList result;
  477     result.append("");
  478     for (int i = 0; i < str.size(); i++) {
  479         QString add;
  480         if (str.at(i) == QChar('%')) {
  481             if (str.at(i + 1) == QChar('%')) add = str.at(++i);
  482             else add = "\"" + mainFile.completeBaseName() + "\"";
  483         } else if (str.at(i) == QChar('@')) {
  484             if (str.at(i + 1) == QChar('@')) add = str.at(++i);
  485             else add = QString::number(currentline);
  486         } else if (str.at(i) == QChar('?')) {
  487             if (str.at(++i) == QChar('?')) add = "?";
  488             else {
  489                 QString command, commandRem;
  490                 QString *createCommand = &command;
  491                 int endMode = 0;
  492                 bool fullSearch = false;
  493                 while (i < str.size()) {
  494                     if (str.at(i) == QChar(')')) {
  495                         endMode = 1;
  496                         break;
  497                     } else if (str.at(i) == QChar(' ') || str.at(i) == QChar('\t')) {
  498                         endMode = 2;
  499                         break;
  500                     } else if (str.at(i) == QChar('\"')) {
  501                         endMode = 3;
  502                         break;
  503                     } else if (str.at(i) == QChar('.') && !fullSearch) {
  504                         endMode = 4;
  505                         break;
  506                     } else if (str.at(i) == QChar('*')) {
  507                         fullSearch = true;
  508                         createCommand = &commandRem;
  509                     }
  510                     (*createCommand) += str.at(i);
  511                     i++;
  512                 }
  513                 QFileInfo selectedFile = parseExtendedSelectFile(command, mainFile, currentFile);
  514                 bool absPath = command.startsWith('a');
  515                 //check only sane commands
  516                 if (command == "ame")
  517                     command = QDir::toNativeSeparators(selectedFile.absoluteFilePath());
  518                 else if (command == "am") {
  519                     command = QDir::toNativeSeparators(selectedFile.absoluteFilePath());
  520                     if (selectedFile.suffix() != "") command.chop(1 + selectedFile.suffix().length());
  521                 } else if (command == "a") {
  522                     command = QDir::toNativeSeparators(selectedFile.absolutePath());
  523                     if (!command.endsWith(QDir::separator())) command += QDir::separator();
  524                 } else if (command == "rme")
  525                     command = QDir::toNativeSeparators(mainFile.dir().relativeFilePath(selectedFile.absoluteFilePath()));
  526                 else if (command == "rm") {
  527                     command = QDir::toNativeSeparators(mainFile.dir().relativeFilePath(selectedFile.absoluteFilePath()));
  528                     if (selectedFile.suffix() != "") command.chop(1 + selectedFile.suffix().length());
  529                 } else if (command == "r") {
  530                     command = QDir::toNativeSeparators(mainFile.dir().relativeFilePath(selectedFile.absolutePath()));
  531                     if (command == "") command = ".";
  532                     if (!command.endsWith(QDir::separator())) command += QDir::separator();
  533                 } else if (command == "me") command = selectedFile.fileName();
  534                 else if (command == "m") command = selectedFile.completeBaseName();
  535                 else if (command == "e") command = selectedFile.suffix();
  536                 else if (command.isEmpty() && !commandRem.isEmpty()); //empty search
  537                 else continue; //invalid command
  538 
  539                 command.append(commandRem);
  540                 switch (endMode) {
  541                 case 2:
  542                     command += " ";
  543                     break;
  544                 case 3:
  545                     command = "\"" + command + "\"";
  546                     break;
  547                 case 4:
  548                     command += ".";
  549                     break;
  550                 default:
  551                     ;
  552                 }
  553                 if (!fullSearch) add = command;
  554                 else {
  555                     QDir dir(QFileInfo(mainFile).absoluteDir());
  556                     if (command.contains("/")) command = command.mid(command.lastIndexOf("/") + 1);
  557                     if (command.contains(QDir::separator())) command = command.mid(command.lastIndexOf(QDir::separator()) + 1);
  558                     QStringList commands = QDir(dir).entryList(QStringList() << command.trimmed(), QDir::Files);
  559                     QString mid;
  560                     if (absPath) {
  561                         mid = QDir::toNativeSeparators(dir.canonicalPath());
  562                         if (!mid.endsWith('/') && !mid.endsWith(QDir::separator())) mid += QDir::separator();
  563                     }
  564                     QStringList oldCommands = result;
  565                     result.clear();
  566                     for (int i = 0; i < oldCommands.size(); i++)
  567                         for (int j = 0; j < commands.size(); j++)
  568                             result.append(oldCommands[i] + mid + commands[j]);
  569                 }
  570             }
  571         } else add = str.at(i);
  572         if (!add.isEmpty())
  573             for (int i = 0; i < result.size(); i++)
  574                 result[i] += add;
  575     }
  576     //  QMessageBox::information(0,"",str+"->"+result,0);
  577     for (int i = 0; i < result.size(); i++) result[i] = result[i].trimmed(); //remove useless characters
  578     return result;
  579 }
  580 
  581 /*
  582  * Select a file which provides the pathname parts used by the "ame" expansions. Currently we can select
  583  * one of the following files:
  584  *
  585  * - Master (root) .tex file (default).
  586  * - Current .tex file. Selected by the c: prefix.
  587  * - A file with the same complete basename as the master file and a chosen extension. The search for this
  588  *   file is done in the master file directory and then the extra PDF directories. Selected by the p{ext}:
  589  *   prefix.
  590  *
  591  * TODO: If selector ?p{ext}: is not flexible enough then maybe we should implement another selector:
  592  * ?f{regexp_with_basename}:
  593  *
  594  * It will be processed like this:
  595  *
  596  * 1. regexp_with_basename undergoes %-token replacement
  597  *    %m is replaced by the complete basename of the master file.
  598  *    %% is replaced by %
  599  * 2. The expression from 1 is used to search the master file directory, the current file
  600  *    directory and the extra PDF directories.
  601  * 3. If step 2 finds a matching file it is used as a selected file. If step 2 does not
  602  *    find a file, then some reasonable default is used.
  603  */
  604 QFileInfo BuildManager::parseExtendedSelectFile(QString &command, const QFileInfo &mainFile, const QFileInfo &currentFile)
  605 {
  606     QFileInfo selectedFile;
  607     QRegExp rxPdf("^p\\{([^{}]+)\\}:");
  608 
  609     if (command.startsWith("c:")) {
  610         selectedFile = currentFile.fileName().isEmpty() ? mainFile : currentFile;
  611         command = command.mid(2);
  612     } else if (rxPdf.indexIn(command) != -1) {
  613         QString compiledFilename = mainFile.completeBaseName() + '.' + rxPdf.cap(1);
  614         QString compiledFound = findCompiledFile(compiledFilename, mainFile);
  615         selectedFile = QFileInfo(compiledFound);
  616         command = command.mid(rxPdf.matchedLength());
  617     } else {
  618         selectedFile = mainFile;
  619     }
  620     return selectedFile;
  621 }
  622 
  623 /*!
  624  * \brief extracts the
  625  * \param s
  626  * \param stdOut output parameter
  627  * \param stdErr output parameter
  628  * \return a copy of s truncated to the first occurrence of an output redirection
  629  */
  630 QString BuildManager::extractOutputRedirection(const QString &commandLine, QString &stdOut, QString &stdErr)
  631 {
  632     QStringList args = ::extractOutputRedirection(tokenizeCommandLine(commandLine), stdOut, stdErr);
  633     if (stdOut.isEmpty() && stdErr.isEmpty()) {
  634         return commandLine;
  635     } else {
  636         return args.join(" ");
  637     }
  638 }
  639 
  640 QString addPathDelimeter(const QString &a)
  641 {
  642     return ((a.endsWith("/") || a.endsWith("\\")) ? a : (a + QDir::separator()));
  643 }
  644 
  645 QString BuildManager::findFileInPath(QString fileName)
  646 {
  647     foreach (QString path, getEnvironmentPathList()) {
  648         path = addPathDelimeter(path);
  649         if (QFileInfo(path + fileName).exists()) return (path + fileName);
  650     }
  651     return "";
  652 }
  653 
  654 #ifdef Q_OS_WIN32
  655 typedef BOOL (__stdcall *AssocQueryStringAFunc)(DWORD, DWORD, const char *, const char *, char *, DWORD *);
  656 QString W32_FileAssociation(QString ext)
  657 {
  658     if (ext == "") return "";
  659     if (ext[0] != QChar('.')) ext = '.' + ext;
  660     QString result = "";
  661     QByteArray ba = ext.toLocal8Bit();
  662     HMODULE mod = LoadLibraryA("shlwapi.dll");
  663     AssocQueryStringAFunc assoc = (AssocQueryStringAFunc)(GetProcAddress(mod, "AssocQueryStringA"));
  664     if (assoc) {
  665         const DWORD ASSOCSTR_COMMAND = 1;
  666         char buf[1024];
  667         DWORD buflen = 1023;
  668         if (assoc(0, ASSOCSTR_COMMAND, ba.data(), "open", &buf[0], &buflen) == S_OK) {
  669             buf[buflen] = 0;
  670             result = QString::fromLatin1(buf);
  671             result.replace("%1", "?am" + ext);
  672             //QMessageBox::information(0,result,result,0);
  673         }
  674     }
  675     FreeLibrary(mod);
  676     return result;
  677 }
  678 
  679 QStringList getProgramFilesPaths()
  680 {
  681     QStringList res;
  682     QString a = getenv("PROGRAMFILES");
  683     if (!a.isEmpty()) res << addPathDelimeter(a);
  684     a = getenv("PROGRAMFILES(X86)");
  685     if (!a.isEmpty()) res << addPathDelimeter(a);
  686     if (a != "C:/Program Files" && QDir("C:/Program Files").exists()) res << "C:/Program Files/";
  687     if (a != "C:/Program Files (x86)" && QDir("C:/Program Files (x86)").exists()) res << "C:/Program Files (x86)/";
  688     if (a + " (x86)" != "C:/Program Files (x86)" && QDir(a + " (x86)").exists()) res << (a + " (x86)");
  689     return res;
  690 }
  691 
  692 /*!
  693  * \return the Uninstall string of the program from the registry
  694  *
  695  * Note: This won't get the path of 64bit installations when TXS running as a 32bit app due to wow6432 regristry redirection
  696  * http://www.qtcentre.org/threads/36966-QSettings-on-64bit-Machine
  697  * http://stackoverflow.com/questions/25392251/qsettings-registry-and-redirect-on-regedit-64bit-wow6432
  698  * No workaround known, except falling back to the native Windows API. For the moment we'll rely on alternative detection methods.
  699  */
  700 QString getUninstallString(const QString &program) {
  701     foreach (const QString &baseKey, QStringList() << "HKEY_LOCAL_MACHINE" << "HKEY_CURRENT_USER") {
  702         QSettings base(baseKey, QSettings::NativeFormat);
  703         QString s = base.value("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + program + "\\UninstallString").toString();
  704         if (!s.isEmpty())
  705             return s;
  706     }
  707     return QString();
  708 }
  709 
  710 /*!
  711  * \return an existing subdir of the from path\subDirFilter\subSubDir.
  712  *
  713  * Example:
  714  * QStringList searchPaths = QStringList() << "C:\\" << "D:\\"
  715  * findSubdir(searchPaths, "*miktex*", "miktex\\bin\\")
  716  * will work for "C:\\MikTeX\\miktex\\bin\\" or "D:\\MikTeX 2.9\\miktex\\bin"
  717  */
  718 QString findSubDir(const QStringList &searchPaths, const QString &subDirFilter, const QString &subSubDir) {
  719     qDebug() << searchPaths;
  720     foreach (const QString &path, searchPaths) {
  721         foreach (const QString &dir, QDir(path).entryList(QStringList(subDirFilter), QDir::AllDirs, QDir::Time)) {
  722             QDir fullPath(addPathDelimeter(path) + addPathDelimeter(dir) + subSubDir);
  723             if (fullPath.exists())
  724                 return fullPath.absolutePath();
  725         }
  726     }
  727     return QString();
  728 }
  729 
  730 /*!
  731  * Returns the MikTeX bin path.
  732  * This should not be called directly but only through getMiKTeXBinPath() to prevent multiple searches.
  733  */
  734 QString getMiKTeXBinPathInternal()
  735 {
  736     // search the registry
  737     QString mikPath = getUninstallString("MiKTeX 2.9");
  738     // Note: this does currently not work for MikTeX 64bit because of registry redirection (also we would have to parse the
  739     // uninstall string there for the directory). For the moment we'll fall back to other detection methods.
  740     if (!mikPath.isEmpty() && QDir(addPathDelimeter(mikPath) + "miktex\\bin\\").exists()) {
  741         mikPath = addPathDelimeter(mikPath) + "miktex\\bin\\";
  742     }
  743 
  744     // search the PATH
  745     if (mikPath.isEmpty()) {
  746         foreach (QString path, getEnvironmentPathList()) {
  747             path = addPathDelimeter(path);
  748             if ((path.endsWith("\\miktex\\bin\\x64\\") || path.endsWith("\\miktex\\bin\\")) && QDir(path).exists())
  749                 return path;
  750          }
  751     }
  752 
  753     // search all program file paths
  754     if (mikPath.isEmpty()) {
  755         mikPath = QDir::toNativeSeparators(findSubDir(getProgramFilesPaths(), "*miktex*", "miktex\\bin\\"));
  756     }
  757 
  758     // search a fixed list of additional locations
  759     if (mikPath.isEmpty()) {
  760         static const QStringList candidates = QStringList() << "C:\\miktex\\miktex\\bin"
  761                                                             << "C:\\tex\\texmf\\miktex\\bin"
  762                                                             << "C:\\miktex\\bin"
  763                                                             << QString(qgetenv("LOCALAPPDATA")) + "\\Programs\\MiKTeX 2.9\\miktex\\bin";
  764         foreach (const QString &path, candidates)
  765             if (QDir(path).exists()) {
  766                 mikPath = path;
  767                 break;
  768             }
  769     }
  770 
  771     if(!mikPath.endsWith("\\")){
  772         mikPath.append("\\");
  773     }
  774     // post-process to detect 64bit installation
  775     if (!mikPath.isEmpty()) {
  776         if (QDir(mikPath + "x64\\").exists())
  777             return mikPath + "x64\\";
  778         else
  779             return mikPath;
  780     }
  781     return "";
  782 }
  783 
  784 static QString miktexBinPath = "<search>";
  785 /*!
  786  * \return the MikTeX bin path. This uses caching so that the search is only performed once per session.
  787  */
  788 QString getMiKTeXBinPath()
  789 {
  790     if (miktexBinPath == "<search>") miktexBinPath = getMiKTeXBinPathInternal();
  791     return miktexBinPath;
  792 }
  793 
  794 /*!
  795  * Returns the TeXlive bin path.
  796  * This should not be called directly but only through getTeXLiveWinBinPath() to prevent multiple searches.
  797  */
  798 QString getTeXLiveWinBinPathInternal()
  799 {
  800     //check for uninstall entry
  801     foreach (const QString &baseKey, QStringList() << "HKEY_CURRENT_USER" << "HKEY_LOCAL_MACHINE") {
  802         QSettings reg(baseKey + "\\Software", QSettings::NativeFormat);
  803         QString uninstall;
  804         for (int v = 2017; v > 2008; v--) {
  805             uninstall = reg.value(QString("microsoft/windows/currentversion/uninstall/TeXLive%1/UninstallString").arg(v), "").toString();
  806             if (!uninstall.isEmpty()) {
  807                 int p = uninstall.indexOf("\\tlpkg\\", 0, Qt::CaseInsensitive);
  808                 QString path = p > 0 ? uninstall.left(p) : "";
  809                 if (QDir(path + "\\bin\\win32").exists())
  810                     return path + "\\bin\\win32\\";
  811             }
  812         }
  813     }
  814     //check for path
  815     QString pdftex = BuildManager::findFileInPath("pdftex.exe");
  816     int p = pdftex.indexOf("\\bin\\", 0, Qt::CaseInsensitive);
  817     if (p <= 0) return "";
  818     QString path = pdftex.left(p);
  819     if (!QFileInfo(path + "\\release-texlive.txt").exists()) return "";
  820     return path + "\\bin\\win32\\";
  821 }
  822 
  823 static QString texliveWinBinPath = "<search>";
  824 /*!
  825  * \return the TeXlive bin path on windows. This uses caching so that the search is only performed once per session.
  826  */
  827 QString getTeXLiveWinBinPath()
  828 {
  829     if (texliveWinBinPath == "<search>") texliveWinBinPath = getTeXLiveWinBinPathInternal();
  830     return texliveWinBinPath;
  831 }
  832 
  833 
  834 QString findGhostscriptDLL()   //called dll, may also find an exe
  835 {
  836     //registry
  837     foreach (QString program, QStringList() << "GPL Ghostscript" << "AFPL Ghostscript")
  838         foreach(QString hkeyBase, QStringList() << "HKEY_CURRENT_USER" << "HKEY_LOCAL_MACHINE") {
  839             QSettings reg(hkeyBase + "\\Software\\" + program, QSettings::NativeFormat);
  840             QStringList version = reg.childGroups();
  841             if (version.empty()) continue;
  842             version.sort();
  843             for (int i = version.size() - 1; i >= 0; i--) {
  844                 QString dll = reg.value(version[i] + "/GS_DLL", "").toString();
  845                 if (!dll.isEmpty()) return dll;
  846             }
  847         }
  848     //environment
  849     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
  850     if (env.contains("GS_DLL")) {
  851         return env.value("GS_DLL");
  852     }
  853     //file search
  854     foreach (const QString &p, getProgramFilesPaths())
  855         if (QDir(p + "gs").exists())
  856             foreach (const QString &gsv, QDir(p + "gs").entryList(QStringList() << "gs*.*", QDir::Dirs, QDir::Time)) {
  857                 QString x = p + "gs/" + gsv + "/bin/gswin32c.exe";
  858                 if (QFile::exists(x)) return x;
  859             }
  860     return "";
  861 }
  862 #endif
  863 
  864 QString searchBaseCommand(const QString &cmd, QString options)
  865 {
  866     foreach(QString command, cmd.split(";")) {
  867         QString fileName = command   ON_WIN(+ ".exe");
  868         if (!options.startsWith(" ")) options = " " + options;
  869         if (!BuildManager::findFileInPath(fileName).isEmpty())
  870             return fileName + options; //found in path
  871         else {
  872             //platform dependent mess
  873 #ifdef Q_OS_WIN32
  874             //Windows MikTex
  875             QString mikPath = getMiKTeXBinPath();
  876             if (!mikPath.isEmpty() && QFileInfo(mikPath + fileName).exists())
  877                 return "\"" + mikPath + fileName + "\" " + options; //found
  878             //Windows TeX Live
  879             QString livePath = getTeXLiveWinBinPath();
  880             if (!livePath.isEmpty() && QFileInfo(livePath + fileName).exists())
  881                 return "\"" + livePath + fileName + "\" " + options; //found
  882 #endif
  883 #ifdef Q_OS_MAC
  884             QStringList paths;
  885             paths << "/usr/bin/texbin/" << "/usr/local/bin/" << "/usr/texbin/" << "/Library/TeX/texbin/" << "/Library/TeX/local/bin/" ;
  886             paths << "/usr/local/teTeX/bin/i386-apple-darwin-current/" << "/usr/local/teTeX/bin/powerpc-apple-darwin-current/" << "/usr/local/teTeX/bin/x86_64-apple-darwin-current/";
  887 
  888             for (int i = 2013; i >= 2007; i--) {
  889                 //paths << QString("/usr/texbin MACTEX/TEXLIVE%1").arg(i); from texmaker comment
  890                 paths << QString("/usr/local/texlive/%1/bin/x86_64-darwin/").arg(i);
  891                 paths << QString("/usr/local/texlive/%1/bin/i386-darwin/").arg(i);
  892                 paths << QString("/usr/local/texlive/%1/bin/powerpc-darwin/").arg(i);
  893             }
  894             foreach (const QString &p, paths)
  895                 if (QFileInfo(p + fileName).exists()) {
  896                     if (cmd == "makeglossaries") {
  897                         // workaround: makeglossaries calls makeindex or xindy and therefore has to be run in an environment that has these commands on the path
  898                         return QString("sh -c \"PATH=$PATH:%1; %2%3\"").arg(p).arg(fileName).arg(options);
  899                     } else {
  900                         return p + fileName + options;
  901                     }
  902                 }
  903 #endif
  904         }
  905     }
  906     return "";
  907 }
  908 
  909 ExpandedCommands BuildManager::expandCommandLine(const QString &str, ExpandingOptions &options)
  910 {
  911     QRegExp re(QRegExp::escape(TXS_CMD_PREFIX) + "([^/ [{]+)(/?)((\\[[^\\]*]+\\]|\\{[^}]*\\})*) ?(.*)");
  912 
  913     options.nestingDeep++;
  914     if (options.canceled) return ExpandedCommands();
  915     if (options.nestingDeep > maxExpandingNestingDeep) {
  916         if (!UtilsUi::txsConfirmWarning(tr("The command has been expanded to %1 levels. Do you want to continue expanding \"%2\"?").arg(options.nestingDeep).arg(str))) {
  917             options.canceled = true;
  918             return ExpandedCommands();
  919         }
  920     }
  921 
  922     ExpandedCommands res;
  923     QStringList splitted = str.split("|");
  924     foreach (const QString &split, splitted) { //todo: ignore | in strings
  925         QString subcmd = split.trimmed();
  926 
  927         if (!subcmd.startsWith(TXS_CMD_PREFIX))  {
  928             RunCommandFlags  flags = getSingleCommandFlags(subcmd);
  929 
  930             if (options.override.removeAll)
  931                 subcmd = CommandInfo::getProgramName(subcmd);
  932             if (!options.override.append.isEmpty())
  933                 subcmd += " " + options.override.append.join(" "); //todo: simplify spaces???
  934             //Regexp matching parameters
  935             //Unescaped: .*(-abc(=([^ ]*|"([^"]|\"([^"])*\")*"))?).*
  936             //Doesn't support nesting deeper than \"
  937             static QString parameterMatching = "(=([^ ]*|\"([^\"]|\\\"([^\"])*\\\")*\"))?";
  938             for (int i = 0; i < options.override.remove.size(); i++) {
  939                 const QString &rem = options.override.remove[i];
  940                 QRegExp removalRegex(" (-?" + QRegExp::escape(rem) + (rem.contains("=") ? "" : parameterMatching) + ")");
  941                 subcmd.replace(removalRegex, " ");
  942             }
  943             for (int i = 0; i < options.override.replace.size(); i++) {
  944                 const QString &rem = options.override.replace[i].first;
  945                 QRegExp replaceRegex(" (-?" + QRegExp::escape(rem) + parameterMatching + ")");
  946                 int pos = replaceRegex.indexIn(subcmd);
  947                 QString rep = " " + rem + options.override.replace[i].second;
  948                 if (pos < 0) subcmd.insert(CommandInfo::getProgramName(subcmd).length(), rep);
  949                 else {
  950                     subcmd.replace(pos, replaceRegex.matchedLength(), rep);
  951                     pos += rep.length();
  952                     int newpos;
  953                     while ( (newpos = replaceRegex.indexIn(subcmd, pos)) >= 0)
  954                         subcmd.replace(newpos, replaceRegex.matchedLength(), " ");
  955                 }
  956             }
  957 
  958             foreach (const QString &c, parseExtendedCommandLine(subcmd, options.mainFile, options.currentFile, options.currentLine)) {
  959                 CommandToRun temp(c);
  960                 temp.flags = flags;
  961                 res.commands << temp;
  962             }
  963         } else if (re.exactMatch(subcmd)) {
  964             const QString &cmdName = re.cap(1);
  965             const QString &slash = re.cap(2);
  966             QString modifiers = re.cap(3);
  967             QString parameters = re.cap(5);
  968             if (slash != "/" && !modifiers.isEmpty()) {
  969                 UtilsUi::txsInformation(tr("You have used txs:///command[... or txs:///command{... modifiers, but we only support modifiers of the form txs:///command/[... or txs:///command/{... with an slash suffix to keep the syntax purer."));
  970                 modifiers.clear();
  971             }
  972             if (options.override.removeAll) {
  973                 parameters.clear();
  974                 modifiers.clear();
  975             }
  976 
  977             bool user;
  978             QString cmd = getCommandLine(cmdName, &user);
  979             if (cmd.isEmpty()) {
  980                 if (options.nestingDeep == 1) UtilsUi::txsWarning(tr("Command %1 not defined").arg(subcmd));
  981                 else if (cmdName != "pre-compile") qDebug() << tr("Command %1 not defined").arg(subcmd); //pre-compile is expecte
  982                 if (cmdName != "pre-compile") {
  983                     res.commands << CommandToRun(""); // add empty command to provoke an error on higher level. Otherwise the missing of the command is simply ignoed e.g. txs:/quick without empty pdflatex
  984                     res.primaryCommand = "";
  985                 }
  986                 continue;
  987             }
  988 
  989             int space = cmd.indexOf(' ');
  990             if (space == -1) space = cmd.size();
  991             if (cmd.startsWith(TXS_CMD_PREFIX) && internalCommands.contains(cmd.left(space))) {
  992                 QStringList exp=parseExtendedCommandLine(cmd, options.mainFile, options.currentFile, options.currentLine);
  993                 res.commands << CommandToRun(exp.first()+" "+parameters);
  994                 res.commands.last().parentCommand = res.commands.last().command;
  995                 if (user) res.commands.last().flags |= RCF_CHANGE_PDF;
  996                 continue;
  997             }
  998 
  999             //parse command modifiers
 1000             bool removeAllActivated = false;
 1001             int replacePrepended = 0, removePrepended = 0;
 1002             if (!modifiers.isEmpty()) {
 1003                 //matching combinations like [-abc][-foo=bar]{-xasa...}
 1004                 QRegExp modifierRegexp("^((\\[([^=\\]]+)(=[^\\]]+)?\\])|(\\{([^}]*)\\}))");
 1005                 while (modifierRegexp.indexIn(modifiers) >= 0) {
 1006                     if (!modifierRegexp.cap(3).isEmpty()) {
 1007                         replacePrepended++;
 1008                         options.override.replace.prepend(QPair<QString, QString>(modifierRegexp.cap(3), modifierRegexp.cap(4)));
 1009                         //qDebug() << "replace >" << options.override.replace.first().first << "< with >"<<options.override.replace.first().second<<"<";
 1010                     } else if (!modifierRegexp.cap(5).isEmpty()) {
 1011                         if (modifierRegexp.cap(6).isEmpty()) {
 1012                             removeAllActivated = true; // qDebug() << "remove all";
 1013                         } else {
 1014                             removePrepended++;
 1015                             options.override.remove.prepend(modifierRegexp.cap(6));
 1016                             //qDebug() << "remove >" << options.override.remove.first() << "<";
 1017                         }
 1018                     }
 1019                     modifiers.remove(0, modifierRegexp.matchedLength());
 1020                 }
 1021             }
 1022             if (removeAllActivated) options.override.removeAll = true;
 1023             if (!parameters.isEmpty()) options.override.append.prepend(parameters);
 1024             //todo /(masterfile,currentfile) modifier ?
 1025 
 1026             //recurse
 1027             ExpandedCommands ecNew = expandCommandLine(cmd, options);
 1028             if (ecNew.commands.length() > 1 && atomicCommands.contains(cmd)) {
 1029                 UtilsUi::txsWarning(QString(tr("The command %1 is expected to be atomic. However, it is currently "
 1030                                       "defined as a command-chain containing %2 commands. This is beyond "
 1031                                       "the specification and may lead to surprising side-effects.\n\n"
 1032                                       "Please change your configuration and define command lists only at "
 1033                                       "'Options -> Configure TeXstudio -> Build' not at "
 1034                                       "'Options -> Configure TeXstudio -> Commands'.")).arg(cmd).arg(ecNew.commands.length()));
 1035             }
 1036             QList<CommandToRun> &newPart = ecNew.commands;
 1037 
 1038             //clean up modifiers
 1039             if (removeAllActivated) options.override.removeAll = false;
 1040             if (!parameters.isEmpty()) options.override.append.removeFirst();
 1041             for (; replacePrepended > 0; replacePrepended--) options.override.replace.removeFirst();
 1042             for (; removePrepended > 0; removePrepended--) options.override.remove.removeFirst();
 1043 
 1044             if (newPart.isEmpty()) continue;
 1045 
 1046             if (commands.value(cmdName).rerunCompiler)
 1047                 for (int i = 0; i < newPart.size(); i++)
 1048                     newPart[i].flags |= RCF_RERUN;
 1049 
 1050             for (int i = 0; i < newPart.size(); i++)
 1051                 if (newPart[i].parentCommand.isEmpty()) {
 1052                     newPart[i].parentCommand = cmdName;
 1053                     if (user) {
 1054                         newPart[i].flags |= RCF_SHOW_STDOUT;
 1055                         newPart[i].flags |= RCF_CHANGE_PDF;
 1056                     }
 1057                 }
 1058 
 1059             if (splitted.size() == 1)
 1060                 res.primaryCommand = cmdName;
 1061 
 1062             res.commands << newPart;
 1063         } else UtilsUi::txsWarning(tr("Failed to understand command %1").arg(subcmd));
 1064     }
 1065     options.nestingDeep--;
 1066     return res;
 1067 }
 1068 
 1069 bool similarCommandInList(const QString &cmd, const QStringList &list)
 1070 {
 1071     if (list.contains(cmd)) return true;
 1072     //compare the executable base name with all base names in the list
 1073     //(could be made faster by caching the list base names, but it is not really an issue)
 1074     QString fullCmd = CommandInfo::getProgramNameUnquoted(cmd).replace(QDir::separator(), '/');
 1075 #ifdef Q_OS_WIN
 1076     fullCmd = fullCmd.toLower();
 1077     if (fullCmd.endsWith(".exe")) fullCmd = fullCmd.left(fullCmd.length() - 4);
 1078 #endif
 1079     int lastPathSep = fullCmd.lastIndexOf('/');
 1080     QString relCmd = lastPathSep < 0 ? fullCmd : fullCmd.mid(lastPathSep + 1);
 1081     foreach (const QString &listCmd, list) {
 1082         QString fullListCmd = CommandInfo::getProgramNameUnquoted(listCmd).replace(QDir::separator(), '/');
 1083 #ifdef Q_OS_WIN
 1084         fullListCmd = fullListCmd.toLower();
 1085         if (fullListCmd.endsWith(".exe")) fullListCmd = fullListCmd.left(fullListCmd.length() - 4);
 1086 #endif
 1087         if (fullCmd == fullListCmd) return true;
 1088         int lastPathSepListCmd = fullListCmd.lastIndexOf('/');
 1089         QString relListCmd = lastPathSepListCmd < 0 ? fullListCmd : fullListCmd.mid(lastPathSepListCmd + 1);
 1090         if (lastPathSep < 0 || lastPathSepListCmd < 0) //do not compare relative, if both are absolute paths
 1091             if (relCmd == relListCmd) return true;
 1092     }
 1093     return false;
 1094 }
 1095 
 1096 RunCommandFlags BuildManager::getSingleCommandFlags(const QString &subcmd) const
 1097 {
 1098     int result = 0;
 1099     if (similarCommandInList(subcmd, latexCommands)) result |= RCF_COMPILES_TEX;
 1100     if (similarCommandInList(subcmd, pdfCommands)) result |= RCF_CHANGE_PDF;
 1101     if (similarCommandInList(subcmd, rerunnableCommands)) result |= RCF_RERUNNABLE;
 1102     if (stdoutCommands.contains(subcmd)) result |= RCF_SHOW_STDOUT;
 1103     bool isAcrobat = false;
 1104 #ifdef Q_OS_WIN
 1105     isAcrobat = subcmd.contains("Acrobat.exe") || subcmd.contains("AcroRd32.exe");
 1106 #endif
 1107 
 1108     if (viewerCommands.contains(subcmd) && !isAcrobat && singleViewerInstance) result |= RCF_SINGLE_INSTANCE;
 1109     return static_cast<RunCommandFlags>(result);
 1110 }
 1111 
 1112 bool BuildManager::hasCommandLine(const QString &program)
 1113 {
 1114     for (CommandMapping::const_iterator it = commands.constBegin(), end = commands.constEnd(); it != end; ++it)
 1115         if (it.value().commandLine == program) return true;
 1116     return false;
 1117 }
 1118 
 1119 #if defined(Q_OS_MAC)
 1120 
 1121 QString getCommandLineViewDvi()
 1122 {
 1123     return "open %.dvi > /dev/null";
 1124 }
 1125 
 1126 QString getCommandLineViewPs()
 1127 {
 1128     return "open %.ps > /dev/null";
 1129 }
 1130 
 1131 QString getCommandLineViewPdfExternal()
 1132 {
 1133     return "open %.pdf > /dev/null";
 1134 }
 1135 
 1136 QString getCommandLineGhostscript()
 1137 {
 1138     return "";
 1139 }
 1140 
 1141 #elif defined(Q_OS_WIN32)
 1142 
 1143 QString getCommandLineViewDvi()
 1144 {
 1145     const QString yapOptions = " -1 -s @?\"c:ame \"?am.dvi\"";
 1146     QString def = W32_FileAssociation(".dvi");
 1147     if (!def.isEmpty()) {
 1148         if (def.contains("yap.exe")) {
 1149             def = def.trimmed();
 1150             if (def.endsWith("\"?am.dvi\"")) {
 1151                 def.replace("\"?am.dvi\"", yapOptions);
 1152             } else if (def.endsWith("?am.dvi")) {
 1153                 def.replace("?am.dvi", yapOptions);
 1154             } else if (def.endsWith(" /dde")) {
 1155                 def.replace(" /dde", yapOptions);
 1156             }
 1157         }
 1158         return def;
 1159     }
 1160     def = searchBaseCommand("yap", yapOptions); //miktex
 1161     if (!def.isEmpty()) return def;
 1162     def = searchBaseCommand("dviout", "%.dvi"); //texlive
 1163     if (!def.isEmpty()) return def;
 1164 
 1165     if (QFileInfo("C:/texmf/miktex/bin/yap.exe").exists())
 1166         return "C:/texmf/miktex/bin/yap.exe " + yapOptions;
 1167 
 1168     return "";
 1169 }
 1170 
 1171 QString getCommandLineViewPs()
 1172 {
 1173     QString def = W32_FileAssociation(".ps");
 1174     if (!def.isEmpty())
 1175         return def;
 1176 
 1177     QString livePath = getTeXLiveWinBinPath();
 1178     if (!livePath.isEmpty())
 1179         if (QFileInfo(livePath + "psv.exe").exists())
 1180             return "\"" + livePath + "psv.exe\"  \"?am.ps\"";
 1181 
 1182 
 1183     QString gsDll = findGhostscriptDLL().replace("/", "\\"); //gsview contains gs so x
 1184     int pos;
 1185     while ((pos = gsDll.lastIndexOf("\\")) > -1) {
 1186         gsDll = gsDll.mid(0, pos + 1);
 1187         if (QFileInfo(gsDll + "gsview32.exe").exists())
 1188             return "\"" + gsDll + "gsview32.exe\" -e \"?am.ps\"";
 1189         if (QFileInfo(gsDll + "gsview.exe").exists())
 1190             return "\"" + gsDll + "gsview.exe\" -e \"?am.ps\"";
 1191         gsDll = gsDll.mid(0, pos);
 1192     }
 1193 
 1194     foreach (const QString &p, getProgramFilesPaths())
 1195         if (QFile::exists(p + "Ghostgum/gsview/gsview32.exe"))
 1196             return "\"" + p + "Ghostgum/gsview/gsview32.exe\" -e \"?am.ps\"";
 1197     return "";
 1198 }
 1199 
 1200 QString getCommandLineViewPdfExternal()
 1201 {
 1202     QString def = W32_FileAssociation(".pdf");
 1203     if (!def.isEmpty())
 1204         return def;
 1205 
 1206     foreach (const QString &p, getProgramFilesPaths())
 1207         if (QDir(p + "Adobe").exists())
 1208             foreach (const QString &rv, QDir(p + "Adobe").entryList(QStringList() << "Reader*", QDir::Dirs, QDir::Time)) {
 1209                 QString x = p + "Adobe/" + rv + "/Reader/AcroRd32.exe";
 1210                 if (QFile::exists(x)) return "\"" + x + "\" \"?am.pdf\"";
 1211             }
 1212     return "";
 1213 }
 1214 
 1215 QString getCommandLineGhostscript()
 1216 {
 1217     const QString gsArgs = " \"?am.ps\"";
 1218     QString livePath = getTeXLiveWinBinPath();
 1219     if (!livePath.isEmpty()) {
 1220         if (QFileInfo(livePath + "rungs.exe").exists())
 1221             return "\"" + livePath + "rungs.exe\"" + gsArgs;
 1222         if (QFileInfo(livePath + "rungs.bat").exists()) //tl 2008 (?)
 1223             return "\"" + livePath + "rungs.bat\"" + gsArgs;
 1224     }
 1225     QString dll = findGhostscriptDLL().replace("gsdll32.dll", "gswin32c.exe", Qt::CaseInsensitive);
 1226     if (dll.endsWith("gswin32c.exe")) return "\"" + dll + "\"" + gsArgs;
 1227     else if (QFileInfo("C:/Program Files/gs/gs8.64/bin/gswin32c.exe").exists())  //old behaviour
 1228         return "\"C:/Program Files/gs/gs8.64/bin/gswin32c.exe\"" + gsArgs;
 1229     else if (QFileInfo("C:/Program Files/gs/gs8.63/bin/gswin32c.exe").exists())  //old behaviour
 1230         return "\"C:/Program Files/gs/gs8.63/bin/gswin32c.exe\"" + gsArgs;
 1231     else if (QFileInfo("C:/Program Files/gs/gs8.61/bin/gswin32c.exe").exists())
 1232         return "\"C:/Program Files/gs/gs8.61/bin/gswin32c.exe\"" + gsArgs;
 1233     return "";
 1234 }
 1235 
 1236 #elif defined(Q_WS_X11) || defined(Q_OS_LINUX)
 1237 
 1238 // xdvi %.dvi  -sourceposition @:%.tex
 1239 // kdvi "file:%.dvi#src:@ %.tex"
 1240 QString getCommandLineViewDvi()
 1241 {
 1242     return "xdg-open %.dvi > /dev/null";
 1243 }
 1244 
 1245 QString getCommandLineViewPs()
 1246 {
 1247     return "xdg-open %.ps > /dev/null";
 1248 }
 1249 
 1250 QString getCommandLineViewPdfExternal()
 1251 {
 1252     return "xdg-open %.pdf > /dev/null";
 1253 }
 1254 
 1255 QString getCommandLineGhostscript()
 1256 {
 1257     return "";
 1258 }
 1259 
 1260 #else
 1261 
 1262 #warning Unrecognized OS. Default viewers will probably be wrong
 1263 
 1264 QString getCommandLineViewDvi()
 1265 {
 1266     return "xdvi %.dvi > /dev/null";
 1267 }
 1268 
 1269 QString getCommandLineViewPs()
 1270 {
 1271     return "gv %.ps > /dev/null";
 1272 }
 1273 
 1274 QString getCommandLineViewPdfExternal()
 1275 {
 1276     return "xpdf %.pdf > /dev/null";
 1277 }
 1278 
 1279 QString getCommandLineGhostscript()
 1280 {
 1281     return "";
 1282 }
 1283 
 1284 #endif
 1285 
 1286 
 1287 bool BuildManager_hadSuccessfulProcessStart;
 1288 
 1289 void BuildManager::registerOptions(ConfigManagerInterface &cmi)
 1290 {
 1291     cmi.registerOption("Tools/Quick Mode", &deprecatedQuickmode, -1);
 1292     cmi.registerOption("Tools/Max Expanding Nesting Deep", &maxExpandingNestingDeep, 10);
 1293     Q_ASSERT(sizeof(dvi2pngMode) == sizeof(int));
 1294     cmi.registerOption("Tools/Dvi2Png Mode", reinterpret_cast<int *>(&dvi2pngMode), 3);
 1295     cmi.registerOption("Files/Save Files Before Compiling", reinterpret_cast<int *>(&saveFilesBeforeCompiling), static_cast<int>(SFBC_ONLY_NAMED));
 1296     cmi.registerOption("Preview/Remove Beamer Class", &previewRemoveBeamer, true);
 1297     cmi.registerOption("Preview/Precompile Preamble", &previewPrecompilePreamble, true);
 1298 
 1299     cmi.registerOption("Tools/Automatic Rerun Commands", &autoRerunCommands, "compile|latex|pdflatex|lualatex|xelatex");
 1300 
 1301     cmi.registerOption("User/ToolNames", &deprecatedUserToolNames, QStringList());
 1302     cmi.registerOption("User/Tools", &deprecatedUserToolCommands, QStringList());
 1303 
 1304     cmi.registerOption("Tools/Display Names", &userToolDisplayNames, QStringList());
 1305     cmi.registerOption("Tools/User Order", &userToolOrder, QStringList());
 1306     cmi.registerOption("Tools/Preview Compile Time Out", &previewCompileTimeOut, 15000); //hidden option, 15s predefined
 1307 
 1308     cmi.registerOption("Tools/Had Successful Process Start", &BuildManager_hadSuccessfulProcessStart, false);
 1309 }
 1310 
 1311 void removeDuplicateUserTools(QStringList &userToolOrder, QStringList &userToolDisplayNames)
 1312 {
 1313     // workaround to cleanup duplicates in usertools https://sourceforge.net/p/texstudio/discussion/907839/
 1314     // needed for some time even after that fix will be in place to catch the duplicates already created by previous versions
 1315     int i = 0;
 1316     QSet<QString> visitedTools;
 1317     while (i < qMin(userToolOrder.size(), userToolDisplayNames.size())) {
 1318         QString tool = userToolOrder[i];
 1319         if (visitedTools.contains(tool)) {
 1320             userToolOrder.removeAt(i);
 1321             userToolDisplayNames.removeAt(i);
 1322         } else {
 1323             visitedTools.insert(tool);
 1324             i++;
 1325         }
 1326     }
 1327 }
 1328 
 1329 void BuildManager::readSettings(QSettings &settings)
 1330 {
 1331     QStringList rerunCommandsUnexpanded = autoRerunCommands.split("|");
 1332     for (int i = 0; i < rerunCommandsUnexpanded.size(); i++)
 1333         if (rerunCommandsUnexpanded[i].startsWith(TXS_CMD_PREFIX))
 1334             rerunCommandsUnexpanded[i] = rerunCommandsUnexpanded[i].mid(TXS_CMD_PREFIX.size());
 1335 
 1336     removeDuplicateUserTools(userToolOrder, userToolDisplayNames);
 1337     settings.beginGroup("Tools");
 1338     settings.beginGroup("Commands");
 1339     QStringList cmds = settings.childKeys();
 1340 
 1341     checkOSXElCapitanDeprecatedPaths(settings, cmds);
 1342 
 1343     foreach (const QString &id, cmds) {
 1344         QString cmd = settings.value(id).toString();
 1345         CommandMapping::iterator it = commands.find(id);
 1346         if (it == commands.end()) {
 1347             // command not known by default -> user command
 1348             QString displayName(id);
 1349             int idx = userToolOrder.indexOf(id);
 1350             if (idx >= 0 && idx < userToolDisplayNames.length()) {
 1351                 displayName = userToolDisplayNames[idx];
 1352             }
 1353             registerCommand(id, "", displayName, "", "", nullptr, true).commandLine = cmd;
 1354         } else {
 1355             // default command
 1356             it.value().commandLine = cmd;
 1357         }
 1358     }
 1359     settings.endGroup();
 1360     settings.endGroup();
 1361 
 1362     //import old or choose default
 1363     for (CommandMapping::iterator it = commands.begin(), end = commands.end(); it != end; ++it) {
 1364         CommandInfo &cmd = it.value();
 1365         cmd.rerunCompiler = rerunCommandsUnexpanded.contains(cmd.id);
 1366         if (!cmd.commandLine.isEmpty()) continue;
 1367         if (!cmd.deprecatedConfigName.isEmpty()) {
 1368             QString import = settings.value(it.value().deprecatedConfigName).toString();
 1369             if (cmd.id == "quick") {
 1370                 if (deprecatedQuickmode == 8)
 1371                     cmd.commandLine = import;
 1372             } else if (cmd.id == "view-pdf-external") {
 1373                 if (import.startsWith(DEPRECACTED_TMX_INTERNAL_PDF_VIEWER)) {
 1374                     import.remove(0, DEPRECACTED_TMX_INTERNAL_PDF_VIEWER.length() + 1);
 1375                     cmd.commandLine = import;
 1376                     //commands.find("view-pdf").value().commandLine = CMD_VIEW_PDF_INTERNAL + " --embedded";
 1377                 } else {
 1378                     cmd.commandLine = import;
 1379                     //commands.find("view-pdf").value().commandLine = CMD_VIEW_PDF_INTERNAL + " --embedded";
 1380                 }
 1381             } else cmd.commandLine = import;
 1382         }
 1383         if (!cmd.commandLine.isEmpty()) continue;
 1384         if (cmd.id == "quick") {
 1385             if (deprecatedQuickmode >= 1 && deprecatedQuickmode < cmd.metaSuggestionList.size() )
 1386                 cmd.commandLine = cmd.metaSuggestionList[deprecatedQuickmode];
 1387             continue;
 1388         }
 1389         cmd.commandLine = cmd.guessCommandLine();
 1390     }
 1391     if (commands.value("quick").commandLine.isEmpty()) {
 1392         //Choose suggestion that actually exists
 1393         CommandInfo &quick = commands.find("quick").value();
 1394         for (int i = 0; i < quick.metaSuggestionList.size() - 1; i++) {
 1395             QString referenced = quick.metaSuggestionList[i];
 1396             if (referenced.isEmpty()) continue;
 1397             QStringList subCommands = referenced.split("|");
 1398             bool hasAll = true;
 1399             foreach (const QString &s, subCommands) {
 1400                 QString trimmed = s.trimmed();
 1401                 trimmed.remove(0, TXS_CMD_PREFIX.length());
 1402                 if (commands.value(trimmed).commandLine.isEmpty()) {
 1403                     hasAll = false;
 1404                     break;
 1405                 }
 1406             }
 1407             if (hasAll) {
 1408                 quick.commandLine = quick.metaSuggestionList[i];
 1409                 break;
 1410             }
 1411         }
 1412         deprecatedQuickmode = -2;
 1413     }
 1414     //import old
 1415     for (int i = 0; i < qMin(deprecatedUserToolNames.size(), deprecatedUserToolCommands.size()); i++)
 1416         if (!deprecatedUserToolNames[i].endsWith("!!!CONVERTED!!!")) {
 1417             QString cmd = deprecatedUserToolCommands[i];
 1418             cmd.replace(DEPRECACTED_TMX_INTERNAL_PDF_VIEWER, CMD_VIEW_PDF_INTERNAL);
 1419             CommandInfo &ci = registerCommand(QString("user%1").arg(i), "", deprecatedUserToolNames[i], "", "", nullptr, true);
 1420             ci.commandLine = cmd;
 1421             userToolOrder << ci.id;
 1422             userToolDisplayNames << ci.displayName;
 1423             deprecatedUserToolNames[i] = deprecatedUserToolNames[i] + "!!!CONVERTED!!!";
 1424         }
 1425     //import very old
 1426     for (int i = 1; i <= 5; i++) {
 1427         QString temp = settings.value(QString("User/Tool%1").arg(i), "").toString();
 1428         if (!temp.isEmpty()) {
 1429             temp.replace(DEPRECACTED_TMX_INTERNAL_PDF_VIEWER, CMD_VIEW_PDF_INTERNAL);
 1430             CommandInfo &ci = registerCommand(QString("userold%1").arg(i), "", settings.value(QString("User/ToolName%1").arg(i)).toString(), "", "", nullptr, true);
 1431             ci.commandLine  = temp;
 1432             userToolOrder << ci.id;
 1433             userToolDisplayNames << ci.displayName;
 1434             settings.remove(QString("User/Tool%1").arg(i));
 1435             settings.remove(QString("User/ToolName%1").arg(i));
 1436         }
 1437     }
 1438     int md = dvi2pngMode;
 1439 #ifdef NO_POPPLER_PREVIEW
 1440     if (md == DPM_EMBEDDED_PDF)
 1441         md = -1;
 1442 #endif
 1443     if (md < 0) {
 1444         if (isCommandDirectlyDefined(CMD_DVIPNG)) dvi2pngMode = DPM_DVIPNG; //best/fastest mode
 1445         else if (isCommandDirectlyDefined(CMD_DVIPS) && isCommandDirectlyDefined(CMD_GS)) dvi2pngMode = DPM_DVIPS_GHOSTSCRIPT; //compatible mode
 1446         else dvi2pngMode = DPM_DVIPNG; //won't work
 1447     }
 1448 
 1449     setAllCommands(commands, userToolOrder);
 1450 }
 1451 
 1452 void BuildManager::saveSettings(QSettings &settings)
 1453 {
 1454     QStringList order = getCommandsOrder();
 1455     userToolOrder.clear();
 1456     userToolDisplayNames.clear();
 1457     settings.beginGroup("Tools");
 1458     settings.beginGroup("Commands");
 1459     settings.remove("");
 1460     QStringList rerunCmds;
 1461     for (int i = 0; i < order.size(); i++) {
 1462         CommandMapping::iterator it = commands.find(order[i]);
 1463         if (it == commands.end()) continue;
 1464         settings.setValue(it->id, it->commandLine);
 1465         if (it->user) {
 1466             userToolDisplayNames << it->displayName;
 1467             userToolOrder << it->id;
 1468         }
 1469         if (it->rerunCompiler) {
 1470             rerunCmds << it->id;
 1471         }
 1472     }
 1473     autoRerunCommands = rerunCmds.join("|");
 1474     settings.endGroup();
 1475     settings.endGroup();
 1476 }
 1477 
 1478 void BuildManager::checkLatexConfiguration(bool &noWarnAgain)
 1479 {
 1480     if (commands.contains("pdflatex") && commands["pdflatex"].commandLine.isEmpty()) {
 1481         QString message = tr("No LaTeX distribution was found on your system. As a result, the corresponding commands are not configured. This means, that you cannot compile your documents to the desired output format (e.g. pdf).");
 1482 
 1483 #ifdef Q_OS_WIN
 1484         message += "<br><br>"
 1485                    + tr("Popular LaTeX distributions on windows are %1 and %2.").arg("<a href='http://miktex.org/'>MikTeX</a>").arg("<a href='https://www.tug.org/texlive/'>TeXLive</a>")
 1486                    + "<br><br>"
 1487                    + tr("If you intend to work with LaTeX, you'll most certainly want to install one of those.");
 1488 #elif defined(Q_OS_MAC)
 1489         message += "<br><br>"
 1490                    + tr("A popular LaTeX distribution on OSX is %1.").arg("<a href='https://tug.org/mactex/'>MacTeX</a>")
 1491                    + "<br><br>"
 1492                    + tr("If you intend to work with LaTeX, you'll most certainly want to install it.");
 1493 #else
 1494         message += "<br><br>"
 1495                    + tr("If you intend to work with LaTeX, you'll most certainly want to install a LaTeX distribution.");
 1496 #endif
 1497         UtilsUi::txsWarning(message, noWarnAgain);
 1498     }
 1499 }
 1500 
 1501 bool BuildManager::runCommand(const QString &unparsedCommandLine, const QFileInfo &mainFile, const QFileInfo &currentFile, int currentLine, QString *buffer, QTextCodec *codecForBuffer )
 1502 {
 1503     if (waitingForProcess()) return false;
 1504 
 1505     emit clearLogs();
 1506 
 1507     if (unparsedCommandLine.isEmpty()) {
 1508         emit processNotification(tr("Error: No command given"));
 1509         return false;
 1510     }
 1511     ExpandingOptions options(mainFile, currentFile, currentLine);
 1512     ExpandedCommands expansion = expandCommandLine(unparsedCommandLine, options);
 1513     if (options.canceled) return false;
 1514     if (!checkExpandedCommands(expansion)) return false;
 1515 
 1516     bool latexCompiled = false, pdfChanged = false;
 1517     for (int i = 0; i < expansion.commands.size(); i++) {
 1518         latexCompiled |= expansion.commands[i].flags & RCF_COMPILES_TEX;
 1519         pdfChanged |= expansion.commands[i].flags & RCF_CHANGE_PDF;
 1520         if (buffer || i != expansion.commands.size() - 1)
 1521             expansion.commands[i].flags |= RCF_WAITFORFINISHED; // don't let buffer be destroyed before command is finished
 1522     }
 1523     if (latexCompiled) {
 1524         ExpandedCommands temp = expandCommandLine(CMD_INTERNAL_PRE_COMPILE, options);
 1525         for (int i = temp.commands.size() - 1; i >= 0; i--) expansion.commands.prepend(temp.commands[i]);
 1526     }
 1527 
 1528     bool asyncPdf = !(expansion.commands.last().flags & RCF_WAITFORFINISHED) && (expansion.commands.last().flags & RCF_CHANGE_PDF);
 1529 
 1530     emit beginRunningCommands(expansion.primaryCommand, latexCompiled, pdfChanged, asyncPdf);
 1531     bool result = runCommandInternal(expansion, mainFile, buffer, codecForBuffer);
 1532     emit endRunningCommands(expansion.primaryCommand, latexCompiled, pdfChanged, asyncPdf);
 1533     return result;
 1534 }
 1535 
 1536 bool BuildManager::checkExpandedCommands(const ExpandedCommands &expansion)
 1537 {
 1538     if (expansion.commands.isEmpty()) {
 1539         emit processNotification(tr("Error: No command expanded"));
 1540         if (!BuildManager_hadSuccessfulProcessStart) {
 1541             emit processNotification("<br>" + tr("<b>Make sure that you have installed a (La)TeX distribution</b> e.g. MiKTeX or TeX Live, and have set the correct paths to this distribution on the command configuration page.<br>"
 1542                                                  "A (La)TeX editor like TeXstudio cannot work without the (La)TeX commands provided by such a distribution."));
 1543         }
 1544         return false;
 1545     }
 1546 
 1547     // check if one command in the list is empty (expansion produced an error, e.g. txs:quick and compile is undefined
 1548     foreach (const CommandToRun elem, expansion.commands) {
 1549         if (elem.command.isEmpty()) {
 1550             emit processNotification(tr("Error: One command expansion invalid.") +
 1551                                      QString("\n    %1: %2").arg(tr("Parent Command"), elem.parentCommand) +
 1552                                      QString("\n    %1: %2").arg(tr("Primary Command"), expansion.primaryCommand));
 1553             if (!BuildManager_hadSuccessfulProcessStart) {
 1554                 emit processNotification("<br>" + tr("<b>Make sure that you have installed a (La)TeX distribution</b> e.g. MiKTeX or TeX Live, and have set the correct paths to this distribution on the command configuration page.<br>"
 1555                                                      "A (La)TeX editor like TeXstudio cannot work without the (La)TeX commands provided by such a distribution."));
 1556             }
 1557             return false;
 1558         }
 1559     }
 1560     return true;
 1561 }
 1562 
 1563 bool BuildManager::runCommandInternal(const ExpandedCommands &expandedCommands, const QFileInfo &mainFile, QString *buffer, QTextCodec *codecForBuffer)
 1564 {
 1565     const QList<CommandToRun> &commands = expandedCommands.commands;
 1566 
 1567     int remainingReRunCount = autoRerunLatex;
 1568     for (int i = 0; i < commands.size(); i++) {
 1569         CommandToRun cur = commands[i];
 1570         if (testAndRunInternalCommand(cur.command, mainFile))
 1571             continue;
 1572 
 1573         bool singleInstance = cur.flags & RCF_SINGLE_INSTANCE;
 1574         if (singleInstance && runningCommands.contains(cur.command)) continue;
 1575         bool latexCompiler = cur.flags & RCF_COMPILES_TEX;
 1576         bool lastCommandToRun = i == commands.size() - 1;
 1577         bool waitForCommand = latexCompiler || (!lastCommandToRun && !singleInstance) || cur.flags & RCF_WAITFORFINISHED;
 1578 
 1579         ProcessX *p = newProcessInternal(cur.command, mainFile, singleInstance);
 1580         REQUIRE_RET(p, false);
 1581         p->subCommandName = cur.parentCommand;
 1582         p->subCommandPrimary = expandedCommands.primaryCommand;
 1583         p->subCommandFlags = cur.flags;
 1584         connect(p, SIGNAL(finished(int)), SLOT(emitEndRunningSubCommandFromProcessX(int)));
 1585 
 1586 
 1587         p->setStdoutBuffer(buffer);
 1588         p->setStdoutCodec(codecForBuffer);
 1589 
 1590         emit beginRunningSubCommand(p, expandedCommands.primaryCommand, cur.parentCommand, cur.flags);
 1591 
 1592         if (!waitForCommand) connect(p, SIGNAL(finished(int)), p, SLOT(deleteLater()));
 1593 
 1594         p->startCommand();
 1595         if (!p->waitForStarted(1000)) return false;
 1596 
 1597         if (latexCompiler || (!lastCommandToRun && !singleInstance) )
 1598             if (!waitForProcess(p)) {
 1599                 p->deleteLater();
 1600                 return false;
 1601             }
 1602 
 1603         if (waitForCommand) { //what is this? does not really make any sense (waiting is done in the block above) and breaks multiple single-instance pdf viewer calls (30 sec delay)
 1604             p->waitForFinished();
 1605             p->deleteLater();
 1606         }
 1607 
 1608         bool rerunnable = (cur.flags & RCF_RERUN) && (cur.flags & RCF_RERUNNABLE);
 1609         if (rerunnable || latexCompiler) {
 1610             LatexCompileResult result = LCR_NORMAL;
 1611             emit latexCompiled(&result);
 1612             if (result == LCR_ERROR) return false;
 1613             if (result == LCR_NORMAL || !rerunnable) continue;
 1614             if (remainingReRunCount <= 0) continue; //do not abort since the rerun condition might have been trigged accidentally
 1615             if (result == LCR_RERUN_WITH_BIBLIOGRAPHY) {
 1616                 QString tempWaitForFinished; //if it does not wait on bibtex it will fail
 1617                 runCommand(CMD_BIBLIOGRAPHY, mainFile, mainFile, 0, &tempWaitForFinished);
 1618                 remainingReRunCount--;
 1619             }
 1620             REQUIRE_RET(result == LCR_RERUN || result == LCR_RERUN_WITH_BIBLIOGRAPHY, false);
 1621             remainingReRunCount--;
 1622             i--; //rerun
 1623             //qDebug() << "rerun";
 1624         }
 1625 
 1626     }
 1627     return true;
 1628 }
 1629 
 1630 void BuildManager::emitEndRunningSubCommandFromProcessX(int)
 1631 {
 1632     ProcessX *p = qobject_cast<ProcessX *>(sender());
 1633     REQUIRE(p); //p can be NULL (although sender() is not null) ! If multiple single instance viewers are in a command. Why? should not happen
 1634     emit endRunningSubCommand(p, p->subCommandPrimary, p->subCommandName, p->subCommandFlags);
 1635 }
 1636 
 1637 
 1638 ProcessX *BuildManager::firstProcessOfDirectExpansion(const QString &command, const QFileInfo &mainFile, const QFileInfo &currentFile, int currentLine,bool nonstop)
 1639 {
 1640     ExpandingOptions options(mainFile, currentFile, currentLine);
 1641     if(nonstop){
 1642         options.nestingDeep=1; // tweak to avoid pop-up error messages
 1643     }
 1644     ExpandedCommands expansion = expandCommandLine(command, options);
 1645     if (options.canceled) return nullptr;
 1646 
 1647     if (expansion.commands.isEmpty()) { return nullptr; }
 1648 
 1649     foreach(CommandToRun elem,expansion.commands) {
 1650         if(elem.command.isEmpty()) {
 1651             return nullptr; // error in command expansion
 1652         }
 1653     }
 1654 
 1655     return newProcessInternal(expansion.commands.first().command, mainFile);
 1656 }
 1657 
 1658 //don't use this
 1659 ProcessX *BuildManager::newProcessInternal(const QString &cmd, const QFileInfo &mainFile, bool singleInstance)
 1660 {
 1661     if (singleInstance && runningCommands.contains(cmd))
 1662         return nullptr;
 1663 
 1664     ProcessX *proc = new ProcessX(this, cmd, mainFile.absoluteFilePath());
 1665     connect(proc, SIGNAL(processNotification(QString)), SIGNAL(processNotification(QString)));
 1666     if (singleInstance) {
 1667         connect(proc, SIGNAL(finished(int)), SLOT(singleInstanceCompleted(int))); //will free proc after the process has ended
 1668         runningCommands.insert(cmd, proc);
 1669     }
 1670     if (!mainFile.fileName().isEmpty())
 1671         proc->setWorkingDirectory(mainFile.absolutePath());
 1672     if (cmd.startsWith(TXS_CMD_PREFIX))
 1673         connect(proc, SIGNAL(startedX()), SLOT(runInternalCommandThroughProcessX()));
 1674 
 1675     updatePathSettings(proc, resolvePaths(additionalSearchPaths));
 1676     return proc;
 1677 }
 1678 
 1679 bool BuildManager::waitForProcess(ProcessX *p)
 1680 {
 1681     REQUIRE_RET(p, false);
 1682     REQUIRE_RET(!processWaitedFor, false);
 1683     // Waiting on a Qt event loop avoids spinlock and high CPU usage, and allows user interaction
 1684     // and UI responsiveness while compiling.
 1685     // We have to check the process running state before we start waiting for processFinished
 1686     // because it is possible that the process has already ended and we would wait forever.
 1687     // We have to start listening for processFinished before we check the running state in
 1688     // order to avoid a race condition.
 1689     QEventLoop loop;
 1690     connect(p, SIGNAL(processFinished()), &loop, SLOT(quit()));
 1691     if (p->isRunning()) {
 1692         processWaitedFor = p;
 1693         emit buildRunning(true);
 1694         loop.exec(); //exec will delay execution until the signal has arrived
 1695         emit buildRunning(false);
 1696         processWaitedFor = nullptr;
 1697     }
 1698     return true;
 1699 }
 1700 
 1701 bool BuildManager::waitingForProcess() const
 1702 {
 1703     return processWaitedFor;
 1704 }
 1705 
 1706 void BuildManager::killCurrentProcess()
 1707 {
 1708     if (!processWaitedFor) return;
 1709     processWaitedFor->kill();
 1710     processWaitedFor = nullptr;
 1711 }
 1712 
 1713 QString BuildManager::createTemporaryFileName()
 1714 {
 1715     QTemporaryFile *temp = new QTemporaryFile(QDir::tempPath () + "/texstudio_XXXXXX.tex");
 1716     temp->open();
 1717     temp->setAutoRemove(false);
 1718     QString tempName = temp->fileName();
 1719     delete temp;
 1720     return tempName;
 1721 }
 1722 
 1723 void addLaTeXInputPaths(ProcessX *p, const QStringList &paths)
 1724 {
 1725     if (paths.isEmpty()) return;
 1726     static const QString SEP = ON_WIN(";") ON_NIX(":");
 1727     static const QStringList envNames = QStringList() << "TEXINPUTS" << "BIBINPUTS" << "BSTINPUTS" << "MFINPUTS" << "MPINPUTS" << "TFMFONTS";
 1728     QString addPath = paths.join(SEP) + SEP + "." + SEP;
 1729     QStringList env = p->environment();
 1730     env << QProcess::systemEnvironment();
 1731     foreach (const QString &envname, envNames) {
 1732         bool found = false;
 1733         for (int i = 0; i < env.size(); i++)
 1734             if (env[i].startsWith(envname + "=")) {
 1735                 found = true;
 1736                 env[i] = env[i] + SEP + addPath;
 1737                 break;
 1738             }
 1739         if (!found)
 1740             env.append(envname + "=" + addPath);
 1741     }
 1742     p->setOverrideEnvironment(env);
 1743 }
 1744 
 1745 //there are 3 ways to generate a preview png:
 1746 //1. latex is called => dvipng is called after latex finished and converts the dvi
 1747 //2. latex is called and dvipng --follow is called at the same time, and will manage the wait time on its own
 1748 //3. latex is called => dvips converts .dvi to .ps => ghostscript is called and created final png
 1749 //Then ghostscript to convert it to
 1750 void BuildManager::preview(const QString &preamble, const PreviewSource &source, const QString &masterFile, QTextCodec *outputCodec)
 1751 {
 1752     QString tempPath = QDir::tempPath() + QDir::separator() + "." + QDir::separator();
 1753 
 1754     //process preamble
 1755     QString preamble_mod = preamble;
 1756     static const QRegExp beamerClass("^(\\s*%[^\\n]*\\n)*\\s*\\\\documentclass(\\[[^\\]]*\\])?\\{beamer\\}"); //detect the usage of the beamer class
 1757     if (previewRemoveBeamer && preamble_mod.contains(beamerClass)) {
 1758         //dvipng is very slow (>14s) and ghostscript is slow (1.4s) when handling beamer documents,
 1759         //after setting the class to article dvipng runs in 77ms
 1760         preamble_mod.remove(beamerClass);
 1761         preamble_mod.insert(0, "\\documentclass{article}\n\\usepackage{beamerarticle}");
 1762     }
 1763 
 1764     QString masterDir = QFileInfo(masterFile).dir().absolutePath();
 1765     QStringList addPaths;
 1766     addPaths << masterDir;
 1767     if (preamble_mod.contains("\\usepackage{import}")) {
 1768         QStringList imports = regExpFindAllMatches(preamble_mod, QRegExp("\\\\subimport\\{([^}\n]*)\\}\\{[^}\n]*\\}"), 1);
 1769         imports.sort();
 1770         for (int i = imports.size() - 1; i > 0; i--)
 1771             if (imports[i] == imports[i - 1]) imports.removeAt(i);
 1772         foreach (const QString &dir, imports)
 1773             addPaths << masterDir + QDir::separator() + dir;
 1774     }
 1775 
 1776     QString preambleFormatFile;
 1777     if (previewPrecompilePreamble) {
 1778         preambleFormatFile = preambleHash.value(preamble_mod, "");
 1779         if (preambleFormatFile != "<failed>") {
 1780             if (!preambleFormatFile.isEmpty())
 1781                 if (!QFile::exists(tempPath + preambleFormatFile + ".fmt"))
 1782                     preambleFormatFile = "";
 1783             if (preambleFormatFile.isEmpty()) {
 1784                 //write preamble
 1785                 QTemporaryFile *tf = new QTemporaryFile(tempPath + "hXXXXXX.tex");
 1786                 REQUIRE(tf);
 1787                 tf->open();
 1788                 QTextStream out(tf);
 1789                 if (outputCodec) out.setCodec(outputCodec);
 1790                 out << preamble_mod;
 1791                 tf->setAutoRemove(false);
 1792                 tf->close();
 1793 
 1794                 //compile
 1795                 QFileInfo fi(*tf);
 1796                 preambleFormatFile = fi.completeBaseName();
 1797                 previewFileNames.append(fi.absoluteFilePath());
 1798                 ProcessX *p = nullptr;
 1799                 if (dvi2pngMode == DPM_EMBEDDED_PDF) {
 1800                     p = newProcessInternal(QString("%1 -interaction=nonstopmode -ini \"&pdflatex %3 \\dump\"").arg(getCommandInfo(CMD_PDFLATEX).getProgramName()).arg(preambleFormatFile), tf->fileName()); //no delete! goes automatically
 1801                 } else {
 1802                     p = newProcessInternal(QString("%1 -interaction=nonstopmode -ini \"&latex %3 \\dump\"").arg(getCommandInfo(CMD_LATEX).getProgramName()).arg(preambleFormatFile), tf->fileName()); //no delete! goes automatically
 1803                 }
 1804 
 1805                 REQUIRE(p);
 1806                 addLaTeXInputPaths(p, addPaths);
 1807                 p->setProperty("preamble", preamble_mod);
 1808                 p->setProperty("preambleFile", preambleFormatFile);
 1809                 connect(p, SIGNAL(finished(int)), this, SLOT(preamblePrecompileCompleted(int)));
 1810                 connect(p, SIGNAL(finished(int)), p, SLOT(deleteLater()));
 1811                 tf->setParent(p); //free file when process is deleted
 1812 
 1813                 p->startCommand();
 1814 
 1815                 if (p->waitForStarted()) {
 1816                     if (p->waitForFinished(800)) {
 1817                         if (p->exitStatus() == QProcess::NormalExit && p->exitCode() == 0) {
 1818                             preambleHash.insert(preamble_mod, preambleFormatFile);
 1819                         } else {
 1820                             preambleHash.insert(preamble_mod, "<failed>");
 1821                             preambleFormatFile = "";
 1822                         }
 1823                     } else
 1824                         preambleFormatFile = ""; //wait + normal compile while waiting
 1825 
 1826                 } else preambleFormatFile = ""; //compiling failed
 1827                 //delete tf; // tex file needs to be freed
 1828             }
 1829         } else preambleFormatFile = "";
 1830     }
 1831 
 1832     // write to temp file
 1833     // (place /./ after the temporary directory because it fails otherwise with qt4.3 on win and the tempdir "t:")
 1834     QTemporaryFile *tf = new QTemporaryFile(tempPath + "XXXXXX.tex");
 1835     if (!tf) return;
 1836     tf->open();
 1837 
 1838     QTextStream out(tf);
 1839     if (outputCodec) out.setCodec(outputCodec);
 1840     if (preambleFormatFile.isEmpty()) out << preamble_mod;
 1841     else out << "%&" << preambleFormatFile << "\n";
 1842     out << "\n\\begin{document}\n" << source.text << "\n\\end{document}\n";
 1843     // prepare commands/filenames
 1844     QFileInfo fi(*tf);
 1845     QString ffn = fi.absoluteFilePath();
 1846     previewFileNames.append(ffn);
 1847     previewFileNameToSource.insert(ffn, source);
 1848     tf->setAutoRemove(false);
 1849     tf->close();
 1850     delete tf; // tex file needs to be freed
 1851     ProcessX *p1 = nullptr;
 1852     if (dvi2pngMode == DPM_EMBEDDED_PDF) {
 1853         // start conversion
 1854         // tex -> dvi
 1855         p1 = firstProcessOfDirectExpansion(CMD_PDFLATEX, QFileInfo(ffn)); //no delete! goes automatically
 1856     } else {
 1857         // start conversion
 1858         // tex -> dvi
 1859         p1 = firstProcessOfDirectExpansion(CMD_LATEX, QFileInfo(ffn)); //no delete! goes automatically
 1860     }
 1861     if (!p1) return; // command failed, not set ?
 1862     addLaTeXInputPaths(p1, addPaths);
 1863     connect(p1, SIGNAL(finished(int)), this, SLOT(latexPreviewCompleted(int)));
 1864     p1->startCommand();
 1865     QTimer::singleShot(previewCompileTimeOut, p1, SLOT(kill()));
 1866 
 1867     if (dvi2pngMode == DPM_DVIPNG_FOLLOW) {
 1868         p1->waitForStarted();
 1869         // dvi -> png
 1870         //follow mode is a tricky features which allows dvipng to run while tex isn't finished
 1871         ProcessX *p2 = firstProcessOfDirectExpansion("txs:///dvipng/[--follow]", ffn);
 1872         if (!p2) return; // command failed, not set ?
 1873         p1->setProperty("proc",QVariant::fromValue(p2));
 1874         connect(p1,SIGNAL(finished(int)),this,SLOT(PreviewLatexCompleted(int)));
 1875         if (!p1->overrideEnvironment().isEmpty()) p2->setOverrideEnvironment(p1->overrideEnvironment());
 1876         connect(p2, SIGNAL(finished(int)), this, SLOT(conversionPreviewCompleted(int)));
 1877         p2->startCommand();
 1878     }
 1879 }
 1880 
 1881 void BuildManager::clearPreviewPreambleCache()
 1882 {
 1883     QHash<QString, QString>::const_iterator it = preambleHash.constBegin();
 1884     while (it != preambleHash.constEnd()) {
 1885         removePreviewFiles(it.value());
 1886         previewFileNames.removeAll(it.value());
 1887         ++it;
 1888     }
 1889     preambleHash.clear();
 1890 }
 1891 
 1892 bool BuildManager::isCommandDirectlyDefined(const QString &id) const
 1893 {
 1894     if (id.startsWith(TXS_CMD_PREFIX)) return isCommandDirectlyDefined(id.mid(TXS_CMD_PREFIX.length()));
 1895     if (internalCommands.contains(TXS_CMD_PREFIX + id)) return true;
 1896     return !commands.value(id).commandLine.isEmpty();
 1897 }
 1898 
 1899 CommandInfo BuildManager::getCommandInfo(const QString &id) const
 1900 {
 1901     if (id.startsWith(TXS_CMD_PREFIX)) return getCommandInfo(id.mid(TXS_CMD_PREFIX.length()));
 1902     CommandMapping::const_iterator it = commands.constFind(id);
 1903     if (it == commands.end()) return CommandInfo();
 1904     return *it;
 1905 }
 1906 
 1907 QString BuildManager::editCommandList(const QString &list, const QString &excludeId)
 1908 {
 1909     QStringList ids = commandSortingsOrder, names, commands;
 1910     ids << userToolOrder;
 1911     ids.insert(ids.indexOf("view-pdf-external"), CMD_VIEW_PDF_INTERNAL);
 1912     ids << CMD_CONDITIONALLY_RECOMPILE_BIBLIOGRAPHY;
 1913     ids.removeAll(excludeId);
 1914     ids.removeAll(TXS_CMD_PREFIX + excludeId);
 1915     for (int i = 0; i < ids.size(); i++) {
 1916         CommandInfo ci = getCommandInfo(ids[i]);
 1917         names << (ci.displayName.isEmpty() ? ids[i] : ci.displayName);
 1918         if (names.last() == CMD_VIEW_PDF_INTERNAL) names.last() = tr("Internal Pdf Viewer");
 1919         commands << (ci.commandLine.isEmpty() ? ids[i] : ci.commandLine);
 1920         if (!ids[i].startsWith(TXS_CMD_PREFIX)) ids[i] = TXS_CMD_PREFIX + ids[i];
 1921     }
 1922 
 1923     UserQuickDialog uqd(nullptr, ids, names, commands);
 1924     uqd.setCommandList(list);
 1925     if (uqd.exec() == QDialog::Accepted) return uqd.getCommandList();
 1926     else return list;
 1927 }
 1928 
 1929 CommandMapping BuildManager::getAllCommands()
 1930 {
 1931     return commands;
 1932 }
 1933 
 1934 QStringList BuildManager::getCommandsOrder()
 1935 {
 1936     QStringList order = commandSortingsOrder;
 1937     order << userToolOrder;
 1938     foreach (const QString &more, commands.keys())
 1939         if (!order.contains(more)) order << more;
 1940     return order;
 1941 }
 1942 
 1943 void BuildManager::setAllCommands(const CommandMapping &cmds, const QStringList &userOrder)
 1944 {
 1945     this->commands = cmds;
 1946     this->userToolOrder = userOrder;
 1947 
 1948     for (CommandMapping::iterator it = commands.begin(), end = commands.end(); it != end; ++it)
 1949         if (it.value().commandLine == tr("<unknown>"))
 1950             it.value().commandLine = "";
 1951 
 1952     static QStringList latexCommandsUnexpanded, rerunnableCommandsUnexpanded, pdfCommandsUnexpanded, stdoutCommandsUnexpanded, viewerCommandsUnexpanded;
 1953     ConfigManagerInterface::getInstance()->registerOption("Tools/Kind/LaTeX", &latexCommandsUnexpanded, QStringList() << "latex" << "pdflatex" << "xelatex" << "lualatex" << "latexmk" << "compile");
 1954     ConfigManagerInterface::getInstance()->registerOption("Tools/Kind/Rerunnable", &rerunnableCommandsUnexpanded, QStringList() << "latex" << "pdflatex" << "xelatex" << "lualatex");
 1955     ConfigManagerInterface::getInstance()->registerOption("Tools/Kind/Pdf", &pdfCommandsUnexpanded, QStringList() << "pdflatex" << "xelatex" << "lualatex" << "latexmk" << "dvipdf" << "ps2pdf");
 1956     ConfigManagerInterface::getInstance()->registerOption("Tools/Kind/Stdout", &stdoutCommandsUnexpanded, QStringList() << "bibtex" << "biber" << "bibtex8" << "bibliography");
 1957     ConfigManagerInterface::getInstance()->registerOption("Tools/Kind/Viewer", &viewerCommandsUnexpanded, QStringList() << "view-pdf" << "view-ps" << "view-dvi" << "view-pdf-internal" << "view-pdf-external" << "view");
 1958 
 1959     QList<QStringList *> lists = QList<QStringList *>() << &latexCommands << &rerunnableCommands << &pdfCommands << &stdoutCommands << &viewerCommands;
 1960     QList<QStringList *> listsUnexpanded = QList<QStringList *>() << &latexCommandsUnexpanded << &rerunnableCommandsUnexpanded << &pdfCommandsUnexpanded << &stdoutCommandsUnexpanded << &viewerCommandsUnexpanded;
 1961     Q_ASSERT(lists.size() == listsUnexpanded.size());
 1962     for (int i = 0; i < lists.size(); i++) {
 1963         QStringList *sl = lists[i];
 1964         *sl = *listsUnexpanded.at(i);
 1965         for (int i = 0; i < sl->size(); i++) {
 1966             Q_ASSERT(commands.contains((*sl)[i]) || (*sl)[i] == "view-pdf-internal");
 1967             (*sl)[i] = getCommandInfo((*sl)[i]).commandLine.trimmed();
 1968         }
 1969     }
 1970 }
 1971 
 1972 /*!
 1973  * Returns a best guess compiler for a string given in "% !TeX TS-program = [program]" \ "% !TeX program = [program]"
 1974  */
 1975 QString BuildManager::guessCompilerFromProgramMagicComment(const QString &program)
 1976 {
 1977     if (program == "latex") return BuildManager::CMD_LATEX;
 1978     if (program == "pdflatex") return BuildManager::CMD_PDFLATEX;
 1979     if (program == "xelatex") return BuildManager::CMD_XELATEX;
 1980     if (program == "luatex" || program == "lualatex") return BuildManager::CMD_LUALATEX;
 1981     if (program.startsWith("user")){
 1982         bool user;
 1983         QString cmd=getCommandLine(program,&user);
 1984         if(user){
 1985             return cmd;
 1986         }
 1987     }
 1988     return QString();
 1989 
 1990 }
 1991 
 1992 /*!
 1993  * Returns a best guess viewer for a string given in "% !TeX TS-program = [program]" \ "% !TeX program = [program]"
 1994  */
 1995 QString BuildManager::guessViewerFromProgramMagicComment(const QString &program)
 1996 {
 1997     if (program == "latex")
 1998         return BuildManager::CMD_VIEW_DVI;
 1999     else if (program == "pdflatex" || program == "xelatex" || program == "luatex" || program == "lualatex") {
 2000         return CMD_VIEW_PDF;
 2001     }
 2002     return QString();
 2003 }
 2004 
 2005 void BuildManager::singleInstanceCompleted(int status)
 2006 {
 2007     Q_UNUSED(status)
 2008     QObject *s = sender();
 2009     REQUIRE(s);
 2010     for (QMap<QString, ProcessX *>::iterator it = runningCommands.begin(), end = runningCommands.end(); it != end;)
 2011         if (it.value() == s)
 2012             it = runningCommands.erase(it);
 2013         else ++it;
 2014 }
 2015 
 2016 void BuildManager::preamblePrecompileCompleted(int status)
 2017 {
 2018     Q_UNUSED(status)
 2019     QProcess *p = qobject_cast<QProcess *>(sender());
 2020     REQUIRE(p);
 2021     if (p->exitCode() != 0 || p->exitStatus() != QProcess::NormalExit) {
 2022         preambleHash.insert(p->property("preamble").toString(), "<failed>");
 2023     } else
 2024         preambleHash.insert(p->property("preamble").toString(), p->property("preambleFile").toString());
 2025 }
 2026 
 2027 //latex has finished the dvi creation
 2028 //now either dvips or dvipng is necessary if not already running
 2029 void BuildManager::latexPreviewCompleted(int status)
 2030 {
 2031     Q_UNUSED(status)
 2032     if (dvi2pngMode == DPM_DVIPNG) {
 2033         ProcessX *p1 = qobject_cast<ProcessX *> (sender());
 2034         if (!p1) return;
 2035         // dvi -> png
 2036         ProcessX *p2 = firstProcessOfDirectExpansion(CMD_DVIPNG, p1->getFile(),QFileInfo(),0,true);
 2037         if (!p2) return; //dvipng does not work
 2038         //REQUIRE(p2);
 2039         if (!p1->overrideEnvironment().isEmpty()) p2->setOverrideEnvironment(p1->overrideEnvironment());
 2040         connect(p2, SIGNAL(finished(int)), this, SLOT(conversionPreviewCompleted(int)));
 2041         p2->startCommand();
 2042     }
 2043     if (dvi2pngMode == DPM_DVIPS_GHOSTSCRIPT) {
 2044         ProcessX *p1 = qobject_cast<ProcessX *> (sender());
 2045         if (!p1) return;
 2046         // dvi -> ps
 2047         ProcessX *p2 = firstProcessOfDirectExpansion("txs:///dvips/[-E]", p1->getFile(),QFileInfo(),0,true);
 2048         if (!p2) return; //dvips does not work
 2049         //REQUIRE(p2);
 2050         if (!p1->overrideEnvironment().isEmpty()) p2->setOverrideEnvironment(p1->overrideEnvironment());
 2051         connect(p2, SIGNAL(finished(int)), this, SLOT(dvi2psPreviewCompleted(int)));
 2052         p2->startCommand();
 2053     }
 2054     if (dvi2pngMode == DPM_EMBEDDED_PDF) {
 2055         ProcessX *p1 = qobject_cast<ProcessX *> (sender());
 2056         if (!p1) return;
 2057         QString processedFile = p1->getFile();
 2058         if (processedFile.endsWith(".tex"))
 2059             processedFile = QDir::fromNativeSeparators(parseExtendedCommandLine("?am.tex", processedFile).first());
 2060             // TODO: fromNativeSeparators is a workaround to fix bug
 2061             // yields different dir separators depending on the context. This should be fixed (which direction?).
 2062             // Test (on win): switch preview between dvipng and pdflatex
 2063         QString fn = parseExtendedCommandLine("?am).pdf", processedFile).first();
 2064         if (QFileInfo(fn).exists()) {
 2065             emit previewAvailable(fn, previewFileNameToSource[processedFile]);
 2066         }
 2067     }
 2068 }
 2069 
 2070 //dvi to ps conversion is finished, call ghostscript to make a useable png from it
 2071 void BuildManager::dvi2psPreviewCompleted(int status)
 2072 {
 2073     Q_UNUSED(status)
 2074     ProcessX *p2 = qobject_cast<ProcessX *> (sender());
 2075     if (!p2) return;
 2076     // ps -> png, ghostscript is quite, safe, will create 24-bit png
 2077     QString filePs = parseExtendedCommandLine("?am.ps", p2->getFile()).first();
 2078     ProcessX *p3 = firstProcessOfDirectExpansion("txs:///gs/[-q][-dSAFER][-dBATCH][-dNOPAUSE][-sDEVICE=png16m][-dEPSCrop][-sOutputFile=\"?am)1.png\"]", filePs,QFileInfo(),0,true);
 2079     if (!p3) return; //gs does not work
 2080     if (!p2->overrideEnvironment().isEmpty()) p3->setOverrideEnvironment(p2->overrideEnvironment());
 2081     connect(p3, SIGNAL(finished(int)), this, SLOT(conversionPreviewCompleted(int)));
 2082     p3->startCommand();
 2083 }
 2084 void BuildManager::PreviewLatexCompleted(int status){
 2085     if(status!=0){
 2086         // latex compile failed, kill dvipng
 2087         ProcessX *p1 = qobject_cast<ProcessX *> (sender());
 2088         ProcessX *p2=p1->property("proc").value<ProcessX *>();
 2089         p2->terminate();
 2090     }
 2091 }
 2092 
 2093 void BuildManager::conversionPreviewCompleted(int status)
 2094 {
 2095     Q_UNUSED(status)
 2096     ProcessX *p2 = qobject_cast<ProcessX *> (sender());
 2097     if (!p2) return;
 2098     // put image in preview
 2099     QString processedFile = p2->getFile();
 2100     if (processedFile.endsWith(".ps")) processedFile = parseExtendedCommandLine("?am.tex", processedFile).first();
 2101     QString fn = parseExtendedCommandLine("?am)1.png", processedFile).first();
 2102     if (QFileInfo(fn).exists())
 2103         emit previewAvailable(fn, previewFileNameToSource[processedFile]);
 2104 }
 2105 
 2106 void BuildManager::commandLineRequestedDefault(const QString &cmdId, QString *result, bool *user)
 2107 {
 2108     if (user) *user = false;
 2109     if (!result) return;
 2110     int space = cmdId.indexOf(' ');
 2111     if (space == -1) space = cmdId.size();
 2112     if (internalCommands.contains(cmdId.left(space)) || internalCommands.contains(TXS_CMD_PREFIX + cmdId.left(space))) {
 2113         *result = cmdId;
 2114         if (!result->startsWith(TXS_CMD_PREFIX)) *result = TXS_CMD_PREFIX + *result;
 2115         return;
 2116     }
 2117     CommandMapping::iterator it = commands.find(cmdId);
 2118     if (it != commands.end()) {
 2119         *result = it->commandLine;
 2120         if (user) *user = it->user;
 2121     }
 2122 }
 2123 
 2124 void BuildManager::runInternalCommandThroughProcessX()
 2125 {
 2126     ProcessX *p = qobject_cast<ProcessX *>(sender());
 2127     REQUIRE(p);
 2128     REQUIRE(p->getCommandLine().startsWith(TXS_CMD_PREFIX));
 2129     testAndRunInternalCommand(p->getCommandLine(), p->getFile());
 2130 }
 2131 
 2132 bool BuildManager::testAndRunInternalCommand(const QString &cmd, const QFileInfo &mainFile)
 2133 {
 2134     int space = cmd.indexOf(' ');
 2135     QString cmdId, options;
 2136     if (space == -1 ) cmdId = cmd;
 2137     else {
 2138         cmdId = cmd.left(space);
 2139         options = cmd.mid(space + 1);
 2140     }
 2141     if (internalCommands.contains(cmdId)) {
 2142         emit runInternalCommand(cmdId, mainFile, options);
 2143         return true;
 2144     }
 2145     return false;
 2146 }
 2147 
 2148 QString BuildManager::findFile(const QString &defaultName, const QStringList &searchPaths, bool mostRecent)
 2149 {
 2150     //TODO: merge with findResourceFile
 2151     QFileInfo base(defaultName);
 2152     QFileInfo* mr = nullptr;
 2153     if (base.exists()) {
 2154         if (mostRecent)
 2155             mr = new QFileInfo(base);
 2156         else
 2157             return defaultName;
 2158     }
 2159 
 2160     foreach (QString p, searchPaths) {
 2161         QFileInfo fi;
 2162         if (p.startsWith('/') || p.startsWith("\\\\") || (p.length() > 2 && p[1] == ':' && (p[2] == '\\' || p[2] == '/'))) {
 2163             fi = QFileInfo(QDir(p), base.fileName());
 2164         } else {
 2165             // ?? seems a bit weird: if p is not an absolute path, then interpret p as directory
 2166             // e.g. default = /my/filename.tex
 2167             //    p = foo
 2168             // -->  /my/foo/filename.tex
 2169             // TODO: do we want/use this anywere or can it be removed?
 2170             QString absPath = base.absolutePath() + "/";
 2171             QString baseName = "/" + base.fileName();
 2172             fi = QFileInfo(absPath + p + baseName);
 2173         }
 2174         if (fi.exists()) {
 2175             if (mostRecent) {
 2176                 if (mr == nullptr || mr->lastModified() < fi.lastModified()) {
 2177                     if (mr != nullptr)
 2178                         delete mr;
 2179                     mr = new QFileInfo(fi);
 2180                 }
 2181             } else
 2182                 return fi.absoluteFilePath();
 2183         }
 2184     }
 2185     if (mostRecent && mr != nullptr) {
 2186         QString result = mr->absoluteFilePath();
 2187         delete mr;
 2188         return result;
 2189     } else {
 2190         return "";
 2191     }
 2192 }
 2193 
 2194 QString BuildManager::findCompiledFile(const QString &compiledFilename, const QFileInfo &mainFile)
 2195 {
 2196     QStringList searchPaths;
 2197     QString foundPathname;
 2198 
 2199     searchPaths << mainFile.absolutePath();
 2200     searchPaths << splitPaths(resolvePaths(additionalPdfPaths));
 2201     foundPathname = findFile(mainFile.absolutePath()+QDir::separator()+compiledFilename, searchPaths, true);
 2202     if (foundPathname == "") {
 2203         // If searched filename is relative prepend the mainFile directory
 2204         // so PDF viewer shows a reasonable error message
 2205         foundPathname = QFileInfo(mainFile.absoluteDir(), compiledFilename).absoluteFilePath();
 2206     }
 2207     return (foundPathname);
 2208 }
 2209 
 2210 void BuildManager::removePreviewFiles(QString elem)
 2211 {
 2212     QDir currentDir(QFileInfo(elem).absoluteDir());
 2213     elem = QFileInfo(elem).completeBaseName();
 2214     QStringList files;
 2215     files = currentDir.entryList(QStringList(elem + "*"),
 2216                                  QDir::Files | QDir::NoSymLinks);
 2217     foreach (const QString &file, files)
 2218         QFile::remove(currentDir.absolutePath() + "/" + file);
 2219 }
 2220 
 2221 //DDE things
 2222 #ifdef Q_OS_WIN32
 2223 #include "windows.h"
 2224 bool BuildManager::executeDDE(QString ddePseudoURL)
 2225 {
 2226     //parse URL
 2227     if (ddePseudoURL.startsWith("dde:///")) ddePseudoURL.remove(0, 7);
 2228     else if (ddePseudoURL.startsWith("dde://")) {
 2229         UtilsUi::txsInformation(tr("You have used a dde:// command with two slashes, which is deprecated. Please change it to a triple slash command dde:/// by adding another slash."));
 2230         ddePseudoURL.remove(0, 6);
 2231     } else return false;
 2232 
 2233     if (ddePseudoURL.length() < 3) return false;
 2234     QString serviceEXEPath;
 2235     if (ddePseudoURL[1] == ':' || (ddePseudoURL[0] == '"' && ddePseudoURL[2] == ':')) { //extended dde of format dde:///<path>:control/commands
 2236         int pathLength = ddePseudoURL.indexOf(':', 3);
 2237         serviceEXEPath = ddePseudoURL.left(pathLength);
 2238         ddePseudoURL.remove(0, pathLength + 1);
 2239     }
 2240 
 2241     int slash = ddePseudoURL.indexOf("/");
 2242     if (slash == -1) return false;
 2243     QString service = ddePseudoURL.left(slash);
 2244     ddePseudoURL.remove(0, slash + 1);
 2245     slash = ddePseudoURL.indexOf("/");
 2246     if (slash == -1) return false;
 2247     QString topic = ddePseudoURL.left(slash);
 2248     ddePseudoURL.remove(0, slash + 1);
 2249     QStringList commands = ddePseudoURL.split("[", QString::SkipEmptyParts);
 2250     if (commands.isEmpty()) return false;
 2251 
 2252 
 2253     //connect to server/topic
 2254 
 2255     if (pidInst == 0)
 2256         if (DdeInitializeA(&pidInst, NULL, APPCLASS_STANDARD | APPCMD_CLIENTONLY | CBF_SKIP_ALLNOTIFICATIONS, 0L) != DMLERR_NO_ERROR)
 2257             return false;
 2258 
 2259     QCoreApplication::processEvents();
 2260 
 2261     HSZ hszService = DdeCreateStringHandleA(pidInst, service.toLocal8Bit().data() , CP_WINANSI);
 2262     if (!hszService) return false;
 2263     HSZ hszTopic = DdeCreateStringHandleA(pidInst, topic.toLocal8Bit().data(), CP_WINANSI);
 2264     if (!hszTopic) {
 2265         DdeFreeStringHandle(pidInst, hszService);
 2266         return false;
 2267     }
 2268     HCONV hConv = DdeConnect(pidInst, hszService, hszTopic, NULL);
 2269     if (!hConv && !serviceEXEPath.isEmpty()) {
 2270         if (!serviceEXEPath.contains('"') && serviceEXEPath.contains(' ') && QFileInfo(serviceEXEPath).exists())
 2271             serviceEXEPath = "\"" + serviceEXEPath + "\"";
 2272         //connecting failed; start the service if necessary
 2273         QProcess *p = new QProcess(QCoreApplication::instance()); //application is parent, to close the service if txs is closed
 2274         p->start(serviceEXEPath);
 2275         if (p->waitForStarted(5000)) {
 2276             connect(p, SIGNAL(finished(int)), p, SLOT(deleteLater())); //will free proc after the process has ended
 2277             //try again to connect (repeatedly 2s long)
 2278             DWORD startTime = GetTickCount();
 2279             while (!hConv && GetTickCount() - startTime < 1000) {
 2280                 hConv = DdeConnect(pidInst, hszService, hszTopic, NULL);
 2281                 Sleep(100);
 2282             }
 2283         } else delete p;
 2284     }
 2285 
 2286     QCoreApplication::processEvents();
 2287 
 2288     DdeFreeStringHandle(pidInst, hszService);
 2289     DdeFreeStringHandle(pidInst, hszTopic);
 2290     if (!hConv) return false;
 2291 
 2292     //execute requests
 2293     foreach (const QString s, commands) {
 2294         QString temp = ("[" + s.trimmed());
 2295         QByteArray ba = temp.toLocal8Bit();
 2296         HDDEDATA req = DdeCreateDataHandle(pidInst, (LPBYTE) ba.data(), ba.size() + 1, 0, 0, CF_TEXT, 0);
 2297         if (req) {
 2298             HDDEDATA recData = DdeClientTransaction((BYTE *)req, (DWORD) - 1, hConv, 0, 0, XTYP_EXECUTE, 1000, 0);
 2299             DdeFreeDataHandle(req);
 2300             if (recData) DdeFreeDataHandle(recData);
 2301         }
 2302         //else QMessageBox::information(0,"TeXstudio",QObject::tr("DDE command %1 failed").arg("["+s),0); //break; send all commands
 2303     }
 2304 
 2305     QCoreApplication::processEvents();
 2306 
 2307     //disconnect
 2308     DdeDisconnect(hConv);
 2309 
 2310     QCoreApplication::processEvents();
 2311 
 2312     return true;
 2313 }
 2314 #endif
 2315 
 2316 ProcessX::ProcessX(BuildManager *parent, const QString &assignedCommand, const QString &fileToCompile):
 2317     QProcess(parent), cmd(assignedCommand.trimmed()), file(fileToCompile), isStarted(false), ended(false), stderrEnabled(true), stdoutEnabled(true), stdoutEnabledOverrideOn(false), stdoutBuffer(nullptr), stdoutCodec(nullptr)
 2318 {
 2319 
 2320     QString stdoutRedirection, stderrRedirection;
 2321     cmd = BuildManager::extractOutputRedirection(cmd, stdoutRedirection, stderrRedirection);
 2322     if (stdoutRedirection == "/dev/null" || stdoutRedirection == "nul") {
 2323         stdoutEnabled = false;
 2324     } else if (stdoutRedirection == "txs:///messages") {
 2325         stdoutEnabledOverrideOn = true;
 2326     } else if (!stdoutRedirection.isEmpty()) {
 2327         parent->processNotification(tr("The specified stdout redirection is not supported: \"%1\". Please see the manual for details.").arg("> " + stdoutRedirection));
 2328     }
 2329     if (stderrRedirection == "/dev/null" || stderrRedirection == "nul") {
 2330         stderrEnabled = false;
 2331     } else if (stderrRedirection == "txs:///messages") {
 2332         // nothing to do because stderr goes to messages by default
 2333     } else if (stderrRedirection == "&1") {
 2334         stderrEnabled = stdoutEnabled || stdoutEnabledOverrideOn;
 2335     } else if (!stderrRedirection.isEmpty()) {
 2336         parent->processNotification(tr("The specified stderr redirection is not supported: \"%1\". Please see the manual for details.").arg("2> " + stderrRedirection));
 2337     }
 2338     connect(this, SIGNAL(started()), SLOT(onStarted()));
 2339     connect(this, SIGNAL(finished(int)), SLOT(onFinished(int)));
 2340     connect(this, SIGNAL(error(QProcess::ProcessError)), SLOT(onError(QProcess::ProcessError)));
 2341 }
 2342 
 2343 /*!
 2344  * Reformats shell-style literal quotes (\") to QProcess-style literal quotes (""")
 2345  * e.g. "Epic 12\" singles" -> "Epic 12""" singles"
 2346  */
 2347 QString ProcessX::reformatShellLiteralQuotes(QString cmd)
 2348 {
 2349     return cmd.replace("\\\"", "\"\"\"");
 2350 }
 2351 
 2352 void ProcessX::startCommand()
 2353 {
 2354     ended = false;
 2355 
 2356 #ifdef Q_OS_WIN32
 2357     if (cmd.startsWith("dde://") || cmd.startsWith("dde:///")) {
 2358         onStarted();
 2359         BuildManager *manager = qobject_cast<BuildManager *>(parent());
 2360         if (!manager) {
 2361             emit finished(1);
 2362             emit finished(1, NormalExit);
 2363             return;
 2364         }
 2365         bool ok = manager->executeDDE(cmd);
 2366         emit finished(ok ? 0 : 1);
 2367         emit finished(ok ? 0 : 1, NormalExit);
 2368         return;
 2369     }
 2370 #endif
 2371 
 2372     if (cmd.startsWith("txs:///")) {
 2373         onStarted();
 2374         emit startedX();
 2375         emit finished(0);
 2376         emit finished(0, NormalExit);
 2377         return;
 2378     }
 2379     if (stdoutEnabled || stdoutBuffer)
 2380         connect(this, SIGNAL(readyReadStandardOutput()), this, SLOT(readFromStandardOutput()));
 2381     if (stderrEnabled)
 2382         connect(this, SIGNAL(readyReadStandardError()), this, SLOT(readFromStandardError()));
 2383 
 2384     ExecProgram execProgram(cmd, BuildManager::resolvePaths(BuildManager::additionalSearchPaths));
 2385     execProgram.execAndNoWait(*this);
 2386 
 2387     if (error() == FailedToStart || error() == Crashed)
 2388         isStarted = ended = true; //prevent call of waitForStarted, if it failed to start (see QTBUG-33021)
 2389 
 2390 #ifdef PROFILE_PROCESSES
 2391     connect(this, SIGNAL(finished(int)), SLOT(finished()));
 2392     time.start();
 2393 #endif
 2394 }
 2395 
 2396 bool ProcessX::waitForStarted(int timeOut)
 2397 {
 2398     if (isStarted) return true;
 2399     return QProcess::waitForStarted(timeOut);
 2400 }
 2401 
 2402 const QString &ProcessX::getFile()
 2403 {
 2404     return file;
 2405 }
 2406 const QString &ProcessX::getCommandLine()
 2407 {
 2408     return cmd;
 2409 }
 2410 
 2411 bool ProcessX::showStdout() const
 2412 {
 2413     return stdoutEnabled;
 2414 }
 2415 
 2416 void ProcessX::setShowStdout(bool show)
 2417 {
 2418     if (stdoutEnabledOverrideOn) show = true;
 2419     stdoutEnabled = show;
 2420 }
 2421 
 2422 QString *ProcessX::getStdoutBuffer()
 2423 {
 2424     return stdoutBuffer;
 2425 }
 2426 
 2427 void ProcessX::setStdoutBuffer(QString *buffer)
 2428 {
 2429     stdoutBuffer = buffer;
 2430 }
 2431 
 2432 void ProcessX::setStdoutCodec(QTextCodec *codec)
 2433 {
 2434     stdoutCodec = codec;
 2435 }
 2436 
 2437 bool ProcessX::showStderr() const
 2438 {
 2439     return stderrEnabled;
 2440 }
 2441 
 2442 void ProcessX::setShowStderr(bool show)
 2443 {
 2444     stderrEnabled = show;
 2445 }
 2446 
 2447 void ProcessX::setOverrideEnvironment(const QStringList &env)
 2448 {
 2449     overriddenEnvironment = env;
 2450     setEnvironment(env);
 2451 }
 2452 
 2453 const QStringList &ProcessX::overrideEnvironment()
 2454 {
 2455     return overriddenEnvironment;
 2456 }
 2457 
 2458 
 2459 int ProcessX::exitStatus() const
 2460 {
 2461     return QProcess::exitStatus();
 2462 }
 2463 
 2464 int ProcessX::exitCode() const
 2465 {
 2466     return QProcess::exitCode();
 2467 }
 2468 
 2469 QString ProcessX::readAllStandardOutputStr()
 2470 {
 2471     return stdoutCodec ? stdoutCodec->toUnicode(QProcess::readAllStandardOutput()) : QString::fromLocal8Bit(QProcess::readAllStandardOutput());
 2472 }
 2473 
 2474 QString ProcessX::readAllStandardErrorStr()
 2475 {
 2476     return stdoutCodec ? stdoutCodec->toUnicode(QProcess::readAllStandardError()) : QString::fromLocal8Bit(QProcess::readAllStandardError());
 2477 }
 2478 
 2479 bool ProcessX::waitForFinished( int msecs )
 2480 {
 2481     return QProcess::waitForFinished(msecs);
 2482 }
 2483 
 2484 bool ProcessX::isRunning() const
 2485 {
 2486     return isStarted && !ended;
 2487 }
 2488 
 2489 void ProcessX::onStarted()
 2490 {
 2491     if (isStarted) return; //why am I called twice?
 2492     isStarted = true;
 2493     BuildManager_hadSuccessfulProcessStart = true;
 2494     emit startedX();
 2495     emit processNotification(tr("Process started: %1").arg(cmd));
 2496 }
 2497 
 2498 void ProcessX::onError(ProcessError error)
 2499 {
 2500     if (error == FailedToStart) {
 2501         emit processNotification(tr("Error: Could not start the command: %1").arg(cmd));
 2502 
 2503         if (!BuildManager_hadSuccessfulProcessStart)
 2504             emit processNotification("<br>" + tr("<b>Make sure that you have installed a (La)TeX distribution</b> e.g. MiKTeX or TeX Live, and have set the correct paths to this distribution on the command configuration page.<br>"
 2505                                                  "A (La)TeX editor like TeXstudio cannot work without the (La)TeX commands provided by such a distribution."));
 2506 
 2507     } else if (error == Crashed)
 2508         emit processNotification(tr("Error: Command crashed: %1").arg(cmd));
 2509 }
 2510 
 2511 void ProcessX::onFinished(int error)
 2512 {
 2513     if (cmd.contains("AcroRd32.exe") && error == 1) error = 0; // fix for Adobe Reader: It returns 1 on success
 2514     if (error) {
 2515         emit processNotification(tr("Process exited with error(s)"));
 2516         readFromStandardError(true);
 2517     } else {
 2518         emit processNotification(tr("Process exited normally"));
 2519         readFromStandardOutput();
 2520         readFromStandardError();
 2521     }
 2522     ended = true;
 2523     emit processFinished();
 2524 }
 2525 
 2526 #ifdef PROFILE_PROCESSES
 2527 void ProcessX::finished()
 2528 {
 2529     qDebug() << "Process: " << qPrintable(cmd) << "  Running time: " << time.elapsed();
 2530 }
 2531 #endif
 2532 
 2533 void ProcessX::readFromStandardOutput()
 2534 {
 2535     if (!stdoutEnabled && !stdoutBuffer) return;
 2536     QString t = readAllStandardOutputStr().trimmed();
 2537     if (stdoutBuffer) stdoutBuffer->append(t);
 2538     emit standardOutputRead(t);
 2539 }
 2540 
 2541 void ProcessX::readFromStandardError(bool force)
 2542 {
 2543     if (!stderrEnabled && !force) return;
 2544     QString t = readAllStandardErrorStr().simplified();
 2545     emit standardErrorRead(t);
 2546 }