]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt/qt_helpers.cpp
No need (any longer?) to create a new view for lyxfiles-open
[lyx.git] / src / frontends / qt / qt_helpers.cpp
index d3da0ee7dea70816cc277eda00b6d019f2025c44..37e5ed5ba6b3897c2939af58263b3f108b3de4bb 100644 (file)
@@ -5,7 +5,7 @@
  *
  * \author Dekel Tsur
  * \author Jürgen Spitzmüller
- * \author Richard Heck
+ * \author Richard Kimberly Heck
  *
  * Full author contact details are available in file CREDITS.
  */
 
 #include "qt_helpers.h"
 
-#include "FileDialog.h"
+#include "Format.h"
 #include "LengthCombo.h"
+#include "LyXRC.h"
 
 #include "frontends/alert.h"
 
-#include "BufferParams.h"
-#include "FloatList.h"
-#include "Language.h"
-#include "TextClass.h"
-
 #include "support/convert.h"
 #include "support/debug.h"
 #include "support/gettext.h"
-#include "support/Length.h"
 #include "support/lstrings.h"
-#include "support/lyxalgo.h"
-#include "support/os.h"
 #include "support/Package.h"
 #include "support/PathChanger.h"
 #include "support/Systemcall.h"
 #include <QApplication>
 #include <QCheckBox>
 #include <QComboBox>
+#include <QDesktopServices>
+#include <QDir>
+#include <QInputDialog>
 #include <QLineEdit>
+#include <QMessageBox>
 #include <QLocale>
 #include <QPalette>
+#include <QPushButton>
 #include <QSet>
+#include <QSettings>
 #include <QTextLayout>
 #include <QTextDocument>
 #include <QToolTip>
+#include <QUrl>
 
 #include <algorithm>
 #include <fstream>
@@ -52,8 +52,7 @@
 
 // for FileFilter.
 // FIXME: Remove
-#include "support/regex.h"
-
+#include <regex>
 
 using namespace std;
 using namespace lyx::support;
@@ -105,6 +104,9 @@ string widgetsToLength(QLineEdit const * input, LengthCombo const * combo)
        // Don't return unit-from-choice if the input(field) contains a unit
        if (isValidGlueLength(fromqstr(length)))
                return fromqstr(length);
+       // Also try with localized version
+       if (isValidGlueLength(fromqstr(unlocLengthString(length))))
+               return fromqstr(unlocLengthString(length));
 
        Length::UNIT const unit = combo->currentLengthItem();
 
@@ -121,6 +123,9 @@ Length widgetsToLength(QLineEdit const * input, QComboBox const * combo)
        // don't return unit-from-choice if the input(field) contains a unit
        if (isValidGlueLength(fromqstr(length)))
                return Length(fromqstr(length));
+       // Also try with localized version
+       if (isValidGlueLength(fromqstr(unlocLengthString(length))))
+               return Length(fromqstr(unlocLengthString(length)));
 
        Length::UNIT unit = Length::UNIT_NONE;
        QString const item = combo->currentText();
@@ -161,7 +166,7 @@ void lengthToWidgets(QLineEdit * input, LengthCombo * combo,
        } else if (!isValidLength(len) && !isStrDbl(len)) {
                // use input field only for gluelengths
                combo->setCurrentItem(defaultUnit);
-               input->setText(toqstr(len));
+               input->setText(locLengthString(toqstr(len)));
        } else {
                lengthToWidgets(input, combo, Length(len), defaultUnit);
        }
@@ -191,7 +196,7 @@ string widgetToDoubleStr(QLineEdit const * input)
 }
 
 
-void doubleToWidget(QLineEdit * input, double const & value, char f, int prec)
+void doubleToWidget(QLineEdit * input, double value, char f, int prec)
 {
        QLocale loc;
        loc.setNumberOptions(QLocale::OmitGroupSeparator);
@@ -229,11 +234,24 @@ bool ColorSorter(ColorCode lhs, ColorCode rhs)
 void setValid(QWidget * widget, bool valid)
 {
        if (valid) {
-               widget->setPalette(QPalette());
+               if (qobject_cast<QCheckBox*>(widget) != nullptr)
+                       // Check boxes need to be treated differenty, see
+                       // https://forum.qt.io/topic/93253/
+                       widget->setStyleSheet("");
+               else
+                       widget->setPalette(QPalette());
        } else {
-               QPalette pal = widget->palette();
-               pal.setColor(QPalette::Active, QPalette::Foreground, QColor(255, 0, 0));
-               widget->setPalette(pal);
+               if (qobject_cast<QCheckBox*>(widget) != nullptr) {
+                       // Check boxes need to be treated differenty, see
+                       // https://forum.qt.io/topic/93253/
+                       if (qobject_cast<QCheckBox*>(widget)->isChecked())
+                               widget->setStyleSheet("QCheckBox:unchecked{ color: red; }QCheckBox:checked{ color: red; }");
+               } else {
+                       QPalette pal = widget->palette();
+                       pal.setColor(QPalette::Active, QPalette::WindowText, QColor(255, 0, 0));
+                       pal.setColor(QPalette::Active, QPalette::Text, QColor(255, 0, 0));
+                       widget->setPalette(pal);
+               }
        }
 }
 
@@ -251,6 +269,8 @@ void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
        QPalette pal = QApplication::palette();
        QPalette newpal(pal.color(QPalette::Active, QPalette::HighlightedText),
                        pal.color(QPalette::Active, QPalette::Highlight));
+       newpal.setColor(QPalette::WindowText,
+                       pal.color(QPalette::Active, QPalette::HighlightedText));
        for (QWidget * w : highlighted)
                w->setPalette(newpal);
        for (QWidget * w : plain)
@@ -258,22 +278,120 @@ void setMessageColour(list<QWidget *> highlighted, list<QWidget *> plain)
 }
 
 
-/// wrapper to hide the change of method name to setSectionResizeMode
-void setSectionResizeMode(QHeaderView * view,
-    int logicalIndex, QHeaderView::ResizeMode mode) {
-#if (QT_VERSION >= 0x050000)
-       view->setSectionResizeMode(logicalIndex, mode);
-#else
-       view->setResizeMode(logicalIndex, mode);
-#endif
-}
+void showDirectory(FileName const & directory)
+{
+       if (!directory.exists())
+               return;
+       QUrl qurl(QUrl::fromLocalFile(QDir::toNativeSeparators(toqstr(directory.absFileName()))));
+       // Give hints in case of bugs
+       if (!qurl.isValid()) {
+               frontend::Alert::error(_("Invalid URL"),
+                       bformat(_("The URL `%1$s' could not be resolved."),
+                               qstring_to_ucs4(qurl.toString())));
+               return;
 
-void setSectionResizeMode(QHeaderView * view, QHeaderView::ResizeMode mode) {
-#if (QT_VERSION >= 0x050000)
-       view->setSectionResizeMode(mode);
-#else
-       view->setResizeMode(mode);
-#endif
+       }
+       if (!QDesktopServices::openUrl(qurl))
+               frontend::Alert::error(_("URL could not be accessed"),
+                       bformat(_("The URL `%1$s' could not be opened although it exists!"),
+                               qstring_to_ucs4(qurl.toString())));
+}
+
+void showTarget(string const & target_in, Buffer const & buf)
+{
+       LYXERR(Debug::INSETS, "Showtarget: " << target_in << "\n");
+
+       string target = target_in;
+       string const & docpath = buf.absFileName();
+       vector<string> targets;
+
+       bool const is_external = prefixIs(target, "EXTERNAL ");
+       if (is_external) {
+               if (!lyxrc.citation_search)
+                       return;
+               string tmp, tar;
+               tar = split(target, tmp, ' ');
+               string const scriptcmd = subst(lyxrc.citation_search_view, "$${python}", os::python());
+               string const command = scriptcmd + " " + tar;
+               cmd_ret const ret = runCommand(commandPrep(command));
+               if (!ret.valid) {
+                       // Script failed
+                       frontend::Alert::error(_("Could not open file"),
+                               _("The lyxpaperview script failed."));
+                       return;
+               }
+               // lyxpaperview returns a \n-separated list of paths
+               targets = getVectorFromString(rtrim(ret.result, "\n"), "\n");
+               if (targets.empty()) {
+                       frontend::Alert::error(_("Could not open file"),
+                               bformat(_("No file was found using the pattern `%1$s'."),
+                                       from_utf8(tar)));
+                       return;
+               }
+       }
+       if (prefixIs(target, "file://")) {
+               // file might have a \n-separated list of paths
+               targets = getVectorFromString(target, "\n");
+       }
+       if (!targets.empty()) {
+               if (targets.size() > 1) {
+                       QStringList files;
+                       for (auto const & t : targets)
+                               files << toqstr(t);
+                       bool ok;
+                       QString file = QInputDialog::getItem(nullptr, qt_("Multiple files found!"),
+                                                            qt_("Select the file that should be opened:"),
+                                                            files, 0, false, &ok);
+                       if (!ok || file.isEmpty())
+                               return;
+                       target = fromqstr(file);
+               } else
+                       target = targets.front();
+       }
+       // security measure: ask user before opening if document is not marked trusted.
+       QSettings settings;
+       if (!settings.value("trusted documents/" + toqstr(docpath), false).toBool()) {
+               QCheckBox * dontShowAgainCB = new QCheckBox();
+               dontShowAgainCB->setText(qt_("&Trust this document and do not ask me again!"));
+               dontShowAgainCB->setToolTip(qt_("If you check this, LyX will open all targets without asking for the given document in the future."));
+               docstring const warn = bformat(_("LyX wants to open the following target in an external application:\n\n"
+                                                "%1$s\n\n"
+                                                "Be aware that this might entail security infringements!\n\n"
+                                                "Only do this if you trust the origin of the document and the target of the link!\n\n"
+                                                "How do you want to proceed?"), from_utf8(target));
+               QMessageBox box(QMessageBox::Warning, qt_("Open external target?"), toqstr(warn),
+                               QMessageBox::NoButton, qApp->focusWidget());
+               QPushButton * openButton = box.addButton(qt_("&Open Target"), QMessageBox::ActionRole);
+               box.addButton(QMessageBox::Abort);
+               box.setCheckBox(dontShowAgainCB);
+               box.setDefaultButton(QMessageBox::Abort);
+               box.exec();
+               if (box.clickedButton() != openButton)
+                       return;
+               if (dontShowAgainCB->isChecked())
+                       settings.setValue("trusted documents/"
+                               + toqstr(docpath), true);
+       }
+
+       bool success = false;
+       QUrl url = is_external
+               ? QUrl::fromLocalFile(toqstr(target))
+               : QUrl(toqstr(target), QUrl::TolerantMode);
+       if (url.isLocalFile()) {
+               // For local files, we use our own viewers
+               // (QDesktopServices employs xdg-open which
+               // does not yet work everywhere)
+               FileName fn(fromqstr(url.path()));
+               string const format = theFormats().getFormatFromFile(fn);
+               success = theFormats().view(buf, fn, format);
+       } else
+               // For external files, we rely on QDesktopServices
+               success =  QDesktopServices::openUrl(url);
+
+       if (!success)
+               frontend::Alert::error(_("Could not open file"),
+                       bformat(_("The target `%1$s' could not be resolved."),
+                               from_utf8(target)));
 }
 } // namespace frontend
 
@@ -335,11 +453,11 @@ QStringList texFileList(QString const & filename)
                        set.insert(qfile);
        }
 
-        // remove duplicates
+       // remove duplicates
 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
-        return QList<QString>(set.begin(), set.end());
+       return QList<QString>(set.begin(), set.end());
 #else
-        return QList<QString>::fromSet(set);
+       return QList<QString>::fromSet(set);
 #endif
 }
 
@@ -438,10 +556,10 @@ static string const convert_brace_glob(string const & glob)
 {
        // Matches " *.{abc,def,ghi}", storing "*." as group 1 and
        // "abc,def,ghi" as group 2, while allowing spaces in group 2.
-       static lyx::regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
+       static regex const glob_re(" *([^ {]*)\\{([^}]+)\\}");
        // Matches "abc" and "abc,", storing "abc" as group 1,
        // while ignoring surrounding spaces.
-       static lyx::regex const block_re(" *([^ ,}]+) *,? *");
+       static regex const block_re(" *([^ ,}]+) *,? *");
 
        string pattern;
 
@@ -551,29 +669,23 @@ struct FileFilterList
        std::vector<Filter> filters_;
 };
 
-
 FileFilterList::FileFilterList(docstring const & qt_style_filter)
 {
        // FIXME UNICODE
        string const filter = to_utf8(qt_style_filter)
                + (qt_style_filter.empty() ? string() : ";;")
-               + to_utf8(_("All Files "))
-#if defined(_WIN32)
-               + ("(*.*)");
-#else
-               + ("(*)");
-#endif
+               + to_utf8(_("All Files")) + " " + fromqstr(wildcardAllFiles());
 
        // Split data such as "TeX documents (*.tex);;LyX Documents (*.lyx)"
        // into individual filters.
-       static lyx::regex const separator_re(";;");
+       static regex const separator_re(";;");
 
        string::const_iterator it = filter.begin();
        string::const_iterator const end = filter.end();
        while (true) {
                match_results<string::const_iterator> what;
 
-               if (!lyx::regex_search(it, end, what, separator_re)) {
+               if (!regex_search(it, end, what, separator_re)) {
                        parse_filter(string(it, end));
                        break;
                }
@@ -592,10 +704,10 @@ void FileFilterList::parse_filter(string const & filter)
 {
        // Matches "TeX documents (plain) (*.tex)",
        // storing "TeX documents (plain) " as group 1 and "*.tex" as group 2.
-       static lyx::regex const filter_re("(.*)\\(([^()]+)\\) *$");
+       static regex const filter_re("(.*)\\(([^()]+)\\) *$");
 
        match_results<string::const_iterator> what;
-       if (!lyx::regex_search(filter, what, filter_re)) {
+       if (!regex_search(filter, what, filter_re)) {
                // Just a glob, no description.
                filters_.push_back(Filter(docstring(), trim(filter)));
        } else {
@@ -607,6 +719,16 @@ void FileFilterList::parse_filter(string const & filter)
 }
 
 
+QString wildcardAllFiles()
+{
+#if defined(_WIN32)
+               return "(*.*)";
+#else
+               return "(*)";
+#endif
+}
+
+
 /** \returns the equivalent of the string passed in
  *  although any brace expressions are expanded.
  *  (E.g. "*.{png,jpg}" -> "*.png *.jpg")
@@ -627,7 +749,7 @@ QStringList fileFilters(QString const & desc)
 }
 
 
-QString formatToolTip(QString text, int em)
+QString formatToolTip(QString text, int width)
 {
        // 1. QTooltip activates word wrapping only if mightBeRichText()
        //    is true. So we convert the text to rich text.
@@ -644,7 +766,11 @@ QString formatToolTip(QString text, int em)
                text = Qt::convertFromPlainText(text, Qt::WhiteSpaceNormal);
        // Compute desired width in pixels
        QFont const font = QToolTip::font();
-       int const px_width = em * QFontMetrics(font).width("M");
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
+       int const px_width = width * QFontMetrics(font).horizontalAdvance("M");
+#else
+       int const px_width = width * QFontMetrics(font).width("M");
+#endif
        // Determine the ideal width of the tooltip
        QTextDocument td("");
        td.setHtml(text);
@@ -660,12 +786,12 @@ QString formatToolTip(QString text, int em)
 }
 
 
-QString qtHtmlToPlainText(QString const & html)
+QString qtHtmlToPlainText(QString const & text)
 {
-       if (!Qt::mightBeRichText(html))
-               return html;
+       if (!Qt::mightBeRichText(text))
+               return text;
        QTextDocument td;
-       td.setHtml(html);
+       td.setHtml(text);
        return td.toPlainText();
 }