#include "ToolTipFormatter.h"
#include "ColorCache.h"
-#include "ColorSet.h"
#include "GuiClipboard.h"
-#include "GuiKeySymbol.h"
#include "GuiSelection.h"
#include "GuiView.h"
#include "Menus.h"
#include "CmdDef.h"
#include "Color.h"
#include "Converter.h"
+#include "Cursor.h"
#include "CutAndPaste.h"
#include "ErrorList.h"
#include "Font.h"
#include "insets/InsetText.h"
+#include "support/checksum.h"
#include "support/convert.h"
#include "support/debug.h"
#include "support/ExceptionMessage.h"
+#include "support/environment.h"
#include "support/FileName.h"
#include "support/filetools.h"
#include "support/ForkedCalls.h"
#include "support/lassert.h"
#include "support/lstrings.h"
#include "support/lyxalgo.h" // sorted
+#include "support/textutils.h"
#include "support/Messages.h"
#include "support/os.h"
#include "support/Package.h"
#include "support/TempFile.h"
-#include "support/textutils.h"
#ifdef Q_OS_MAC
#include "support/AppleScript.h"
+#include "support/AppleSupport.h"
#include "support/linkback/LinkBackProxy.h"
#endif
#include <queue>
+#include <tuple>
#include <QByteArray>
+#include <QBitmap>
#include <QDateTime>
+#if QT_VERSION < 0x060000
#include <QDesktopWidget>
-#include <QDir>
+#endif
#include <QEvent>
#include <QFileOpenEvent>
#include <QFileInfo>
#include <QMenuBar>
#include <QMimeData>
#include <QObject>
+#include <QPainter>
#include <QPixmap>
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
+#include <QRandomGenerator>
+#endif
#include <QRegExp>
#include <QSessionManager>
#include <QSettings>
#if (QT_VERSION < 0x050000)
#include <QWindowsMime>
#define QWINDOWSMIME QWindowsMime
+#define QVARIANTTYPE QVariant::Type
+#elif (QT_VERSION >= 0x060000)
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/private/qwindowsmime_p.h>
+#include <QtGui/qpa/qplatformintegration.h>
+#define QWINDOWSMIME QWindowsMime
+#define QVARIANTTYPE QMetaType
+using QWindowsMime = QNativeInterface::Private::QWindowsMime;
+using QWindowsApplication = QNativeInterface::Private::QWindowsApplication;
#else
#include <QWinMime>
#define QWINDOWSMIME QWinMime
+#define QVARIANTTYPE QVariant::Type
#endif
#ifdef Q_CC_GNU
#include <wtypes.h>
#endif
#endif
-#ifdef Q_OS_MAC
+#if defined(Q_OS_MAC) && (QT_VERSION < 0x060000)
#include <QMacPasteboardMime>
#endif // Q_OS_MAC
-#include <boost/crc.hpp>
-
#include <exception>
#include <sstream>
#include <vector>
using namespace lyx::support;
-static void initializeResources()
-{
- static bool initialized = false;
- if (!initialized) {
- Q_INIT_RESOURCE(Resources);
- initialized = true;
- }
-}
-
-
namespace lyx {
frontend::Application * createApplication(int & argc, char * argv[])
AllowSetForegroundWindow(ASFW_ANY);
#endif
+
+#if defined(Q_OS_MAC)
+ int const cursor_time_on = NSTextInsertionPointBlinkPeriodOn();
+ int const cursor_time_off = NSTextInsertionPointBlinkPeriodOff();
+ if (cursor_time_on > 0 && cursor_time_off > 0) {
+ QApplication::setCursorFlashTime(cursor_time_on + cursor_time_off);
+ } else if (cursor_time_on <= 0 && cursor_time_off > 0) {
+ // Off is set and On is undefined of zero
+ QApplication::setCursorFlashTime(0);
+ } else if (cursor_time_off <= 0 && cursor_time_on > 0) {
+ // On is set and Off is undefined of zero
+ QApplication::setCursorFlashTime(0);
+ }
+#endif
+
+// Setup high DPI handling. This is a bit complicated, but will be default in Qt6.
+// macOS does it by itself.
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && !defined(Q_OS_MAC)
+#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
+ // Attribute Qt::AA_EnableHighDpiScaling must be set before QCoreApplication is created
+ if (getEnv("QT_ENABLE_HIGHDPI_SCALING").empty()
+ && getEnv("QT_AUTO_SCREEN_SCALE_FACTOR").empty())
+ QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
+#endif
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ // HighDPI scale factor policy must be set before QGuiApplication is created
+ if (getEnv("QT_SCALE_FACTOR_ROUNDING_POLICY").empty())
+ QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
+#endif
+#endif
+
frontend::GuiApplication * guiApp = new frontend::GuiApplication(argc, argv);
// I'd rather do that in the constructor, but I do not think that
// the palette is accessible there.
}
-QString iconName(FuncRequest const & f, bool unknown)
-{
- initializeResources();
- QString name1;
- QString name2;
- QString path;
- switch (f.action()) {
- case LFUN_MATH_INSERT:
- if (!f.argument().empty()) {
- path = "math/";
- name1 = findImg(toqstr(f.argument()).mid(1));
+namespace {
+
+/* Get aliases for icon name. This allows to avoid duplication of
+ * icons when new versions of functions are introduced for the
+ * toolbar. A good example is the introduction of layout-toggle in
+ * #9864.
+ * The file is parsed by Lexer. Each line is of the form
+ * <original substring> <replacement substring>
+ *
+ * The return value is another icon file name that can be queried.
+ */
+// FIXME: consider using regular expressions.
+QString getAlias(QString name) {
+ static bool has_aliases = false;
+ static vector <pair<QString,QString>> aliases;
+ // Initialize aliases list (once).
+ if (!has_aliases) {
+ FileName alfile = libFileSearch("images", "icon.aliases");
+ if (alfile.exists()) {
+ Lexer lex;
+ lex.setFile(alfile);
+ while(lex.isOK()) {
+ string from, to;
+ lex >> from >> to;
+ if (!from.empty())
+ aliases.push_back({toqstr(from), toqstr(to)});
+ }
}
- break;
- case LFUN_MATH_DELIM:
- case LFUN_MATH_BIGDELIM:
- path = "math/";
- name1 = findImg(toqstr(f.argument()));
- break;
- case LFUN_CALL:
- path = "commands/";
- name1 = toqstr(f.argument());
- break;
- case LFUN_COMMAND_ALTERNATIVES: {
- // use the first of the alternative commands
- docstring firstcom;
- docstring dummy = split(f.argument(), firstcom, ';');
- name1 = toqstr(firstcom);
- name1.replace(' ', '_');
- break;
+ has_aliases = true;
}
- default:
- name2 = toqstr(lyxaction.getActionName(f.action()));
- name1 = name2;
+ // check for the following aliases
+ for (auto const & alias : aliases) {
+ if (name.contains(alias.first))
+ name.replace(alias.first, alias.second);
+ }
+ return name;
+}
+
+} // namespace
+
- if (!f.argument().empty()) {
- name1 = name2 + ' ' + toqstr(f.argument());
+IconInfo iconInfo(FuncRequest const & f, bool unknown, bool rtl)
+{
+ IconInfo res;
+
+ QStringList names;
+ QString lfunname = toqstr(lyxaction.getActionName(f.action()));
+
+ if (!f.argument().empty()) {
+ // if there are arguments, first search an icon which name is the full thing
+ QString name = lfunname + ' ' + toqstr(f.argument());
+ name.replace(' ', '_');
+ name.replace(';', '_');
+ name.replace('\\', "backslash");
+ names << name;
+ QString alias = getAlias(name);
+ if (alias != name)
+ names << alias;
+
+ // then special default icon for some lfuns
+ switch (f.action()) {
+ case LFUN_MATH_INSERT:
+ names << "math/" + findImg(toqstr(f.argument()).mid(1));
+ break;
+ case LFUN_MATH_DELIM:
+ case LFUN_MATH_BIGDELIM:
+ names << "math/" + findImg(toqstr(f.argument()));
+ break;
+ case LFUN_CALL:
+ names << "commands/" + toqstr(f.argument());
+ break;
+ case LFUN_COMMAND_ALTERNATIVES: {
+ // use the first of the alternative commands
+ docstring firstcom;
+ docstring dummy = split(f.argument(), firstcom, ';');
+ QString name1 = toqstr(firstcom);
name1.replace(' ', '_');
+ name1.replace(';', '_');
name1.replace('\\', "backslash");
+ names << name1;
+ break;
+ }
+ default:
+ break;
}
}
+ // next thing to try is function name alone
+ names << lfunname;
+ QString alias = getAlias(lfunname);
+ if (alias != lfunname)
+ names << alias;
+
+ // and finally maybe the unknown icon
+ if (unknown)
+ names << "unknown";
+
+ search_mode const mode = theGuiApp() ? theGuiApp()->imageSearchMode() : support::must_exist;
+ // The folders where icons are searched for
QStringList imagedirs;
- imagedirs << "images/" << "images/ipa/";
- search_mode mode = theGuiApp()->imageSearchMode();
- for (int i = 0; i < imagedirs.size(); ++i) {
- QString imagedir = imagedirs.at(i) + path;
- FileName fname = imageLibFileSearch(imagedir, name1, "svgz,png", mode);
- if (fname.exists())
- return toqstr(fname.absFileName());
-
- fname = imageLibFileSearch(imagedir, name2, "svgz,png", mode);
- if (fname.exists())
- return toqstr(fname.absFileName());
- }
-
- path = ":/images/" + path;
- QDir res(path);
- if (!res.exists()) {
- LYXERR0("Directory " << path << " not found in resource!");
- return QString();
- }
- if (res.exists(name1 + ".svgz"))
- return path + name1 + ".svgz";
- else if (res.exists(name1 + ".png"))
- return path + name1 + ".png";
-
- if (res.exists(name2 + ".svgz"))
- return path + name2 + ".svgz";
- else if (res.exists(name2 + ".png"))
- return path + name2 + ".png";
-
- LYXERR(Debug::GUI, "Cannot find icon with filename "
- << "\"" << name1 << ".{svgz,png}\""
- << " or filename "
- << "\"" << name2 << ".{svgz,png}\""
- << " for command \""
+ imagedirs << "images/ipa/" << "images/";
+ // This is used to search for rtl version of icons which have the +rtl suffix.
+ QStringList suffixes;
+ if (rtl)
+ suffixes << "+rtl";
+ suffixes << QString();
+
+ for (QString const & imagedir : imagedirs)
+ for (QString const & name : names)
+ for (QString const & suffix : suffixes) {
+ QString id = imagedir;
+ FileName fname = imageLibFileSearch(id, name + suffix, "svgz,png", mode);
+ if (fname.exists()) {
+ docstring const fpath = fname.absoluteFilePath();
+ res.filepath = toqstr(fname.absFileName());
+ // these icons are subject to inversion in dark mode
+ res.invert = (contains(fpath, from_ascii("math")) || contains(fpath, from_ascii("ert-insert"))
+ || suffixIs(fname.onlyPath().absoluteFilePath(), from_ascii("ipa")));
+ res.swap = rtl && suffix.isEmpty();
+ return res;
+ }
+ }
+
+ LYXERR(Debug::GUI, "Cannot find icon for command \""
<< lyxaction.getActionName(f.action())
<< '(' << to_utf8(f.argument()) << ")\"");
- if (unknown) {
- QString imagedir = "images/";
- FileName fname = imageLibFileSearch(imagedir, "unknown", "svgz,png", mode);
- if (fname.exists())
- return toqstr(fname.absFileName());
- return QString(":/images/unknown.svgz");
- }
-
- return QString();
+ return res;
}
-bool getPixmap(QPixmap & pixmap, QString const & path)
+QPixmap prepareForDarkMode(QPixmap pixmap)
{
- return pixmap.load(path);
+ QPalette palette = QPalette();
+ QColor text_color = palette.color(QPalette::Active, QPalette::WindowText);
+ QColor bg_color = palette.color(QPalette::Active, QPalette::Window);
+
+ // guess whether we are in dark mode
+ if (text_color.black() > bg_color.black())
+ // not in dark mode, do nothing
+ return pixmap;
+
+ // create a layer with black text turned to QPalette::WindowText
+ QPixmap black_overlay(pixmap.size());
+ black_overlay.fill(text_color);
+ black_overlay.setMask(pixmap.createMaskFromColor(Qt::black, Qt::MaskOutColor));
+
+ // create a layer with blue text turned to lighter blue
+ QPixmap blue_overlay(pixmap.size());
+ QColor math_blue(0, 0, 255);
+ blue_overlay.fill(guiApp->colorCache().get(Color(Color_math)));
+ blue_overlay.setMask(pixmap.createMaskFromColor(math_blue, Qt::MaskOutColor));
+
+ // create a layer with ("latex") red text turned to lighter red
+ QPixmap red_overlay(pixmap.size());
+ QColor math_red(128, 0, 0);
+ red_overlay.fill(guiApp->colorCache().get(Color(Color_latex)));
+ red_overlay.setMask(pixmap.createMaskFromColor(math_red, Qt::MaskOutColor));
+
+ // put layers on top of existing pixmap
+ QPainter painter(&pixmap);
+ painter.drawPixmap(pixmap.rect(), black_overlay);
+ painter.drawPixmap(pixmap.rect(), blue_overlay);
+ painter.drawPixmap(pixmap.rect(), red_overlay);
+
+ return pixmap;
}
QString fpath = toqstr(fname.absFileName());
QPixmap pixmap = QPixmap();
- if (getPixmap(pixmap, fpath)) {
+ if (pixmap.load(fpath)) {
+ if (fpath.contains("math") || fpath.contains("ipa")
+ || fpath.contains("bullets"))
+ return prepareForDarkMode(pixmap);
return pixmap;
}
- QStringList exts = ext.split(",");
- fpath = ":/" + path + name + ".";
- for (int i = 0; i < exts.size(); ++i) {
- if (getPixmap(pixmap, fpath + exts.at(i))) {
- return pixmap;
- }
- }
-
bool const list = ext.contains(",");
- LYXERR0("Cannot load pixmap \""
- << path << name << "." << (list ? "{" : "") << ext
- << (list ? "}" : "") << "\", please verify resource system!");
+ LYXERR(Debug::GUI, "Cannot load pixmap \""
+ << path << "/" << name << "." << (list ? "{" : "") << ext
+ << (list ? "}" : "") << "\".");
return QPixmap();
}
-QIcon getIcon(FuncRequest const & f, bool unknown)
+QIcon getIcon(FuncRequest const & f, bool unknown, bool rtl)
{
#if (QT_VERSION >= 0x040600)
if (lyxrc.use_system_theme_icons) {
}
#endif
- QString icon = iconName(f, unknown);
- if (icon.isEmpty())
+ IconInfo icondata = iconInfo(f, unknown, rtl);
+ if (icondata.filepath.isEmpty())
return QIcon();
//LYXERR(Debug::GUI, "Found icon: " << icon);
QPixmap pixmap = QPixmap();
- if (!getPixmap(pixmap,icon)) {
- LYXERR0("Cannot load icon " << icon << " please verify resource system!");
+ if (!pixmap.load(icondata.filepath)) {
+ LYXERR0("Cannot load icon " << icondata.filepath << ".");
return QIcon();
}
- return QIcon(pixmap);
+ if (icondata.invert)
+ pixmap = prepareForDarkMode(pixmap);
+
+ if (icondata.swap)
+ return QIcon(pixmap.transformed(QTransform().scale(-1, 1)));
+ else
+ return QIcon(pixmap);
}
: QTranslator(parent)
{}
- virtual QString translate(const char * /* context */,
+ QString translate(const char * /* context */,
const char *sourceText,
#if QT_VERSION >= 0x050000
- const char * /* disambiguation */ = 0, int /* n */ = -1) const
+ const char * /* disambiguation */ = nullptr, int /* n */ = -1) const override
#else
- const char * /*comment*/ = 0) const
+ const char * /*comment*/ = 0) const override
#endif
{
// Here we declare the strings that need to be translated from Qt own GUI
//
////////////////////////////////////////////////////////////////////////
-#ifdef Q_OS_MAC
+#if defined(Q_OS_MAC) && (QT_VERSION < 0x060000)
// QMacPasteboardMimeGraphics can only be compiled on Mac.
class QMacPasteboardMimeGraphics : public QMacPasteboardMime
QWindowsMimeMetafile() {}
bool canConvertFromMime(FORMATETC const & /*formatetc*/,
- QMimeData const * /*mimedata*/) const
+ QMimeData const * /*mimedata*/) const override
{
return false;
}
bool canConvertToMime(QString const & mimetype,
- IDataObject * pDataObj) const
+ IDataObject * pDataObj) const override
{
if (mimetype != emfMimeType() && mimetype != wmfMimeType())
return false;
}
bool convertFromMime(FORMATETC const & /*formatetc*/,
- const QMimeData * /*mimedata*/, STGMEDIUM * /*pmedium*/) const
+ const QMimeData * /*mimedata*/, STGMEDIUM * /*pmedium*/) const override
{
return false;
}
QVariant convertToMime(QString const & mimetype, IDataObject * pDataObj,
- QVariant::Type /*preferredType*/) const
+ QVARIANTTYPE /*preferredType*/) const override
{
QByteArray data;
if (!canConvertToMime(mimetype, pDataObj))
QVector<FORMATETC> formatsForMime(QString const & mimetype,
- QMimeData const * /*mimedata*/) const
+ QMimeData const * /*mimedata*/) const override
{
QVector<FORMATETC> formats;
if (mimetype == emfMimeType() || mimetype == wmfMimeType())
return formats;
}
- QString mimeForFormat(FORMATETC const & formatetc) const
+ QString mimeForFormat(FORMATETC const & formatetc) const override
{
switch (formatetc.cfFormat) {
case CF_ENHMETAFILE:
bool started() const {
return started_;
}
- bool eventFilter(QObject *obj, QEvent *event) {
+ bool eventFilter(QObject *obj, QEvent *event) override {
LYXERR(Debug::ACTION, "Event Type: " << event->type());
switch (event->type()) {
case QEvent::Show:
struct GuiApplication::Private
{
- Private(): language_model_(0), meta_fake_bit(NoModifier),
- global_menubar_(0)
+ Private(): language_model_(nullptr), meta_fake_bit(NoModifier),
+ global_menubar_(nullptr)
+ #if (QT_VERSION >= QT_VERSION_CHECK(5, 1, 0))
+ , last_state_(Qt::ApplicationInactive)
+ #endif
{
#if (QT_VERSION < 0x050000) || (QT_VERSION >= 0x050400)
#if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
/// WMF Mime handler for Windows clipboard.
wmf_mime_ = new QWindowsMimeMetafile;
+ #if (QT_VERSION >= 0x060000)
+ win_app_ = dynamic_cast<QWindowsApplication *>
+ (QGuiApplicationPrivate::platformIntegration());
+ win_app_->registerMime(wmf_mime_);
+ #endif
#endif
#endif
initKeySequences(&theTopLevelKeymap());
}
+ #if (QT_VERSION >= 0x060000)
+ #if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
+ ~Private()
+ {
+ win_app_->unregisterMime(wmf_mime_);
+ }
+ #endif
+ #endif
+
void initKeySequences(KeyMap * kb)
{
keyseq = KeySequence(kb, kb);
/// Only used on mac.
QMenuBar * global_menubar_;
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 1, 0))
+ /// Holds previous application state on Mac
+ Qt::ApplicationState last_state_;
+#endif
-#ifdef Q_OS_MAC
+#if defined(Q_OS_MAC) && (QT_VERSION < 0x060000)
/// Linkback mime handler for MacOSX.
QMacPasteboardMimeGraphics mac_pasteboard_mime_;
#endif
#if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
/// WMF Mime handler for Windows clipboard.
QWindowsMimeMetafile * wmf_mime_;
+#if (QT_VERSION >= 0x060000)
+ QWindowsApplication * win_app_;
+#endif
#endif
#endif
GuiApplication::GuiApplication(int & argc, char ** argv)
- : QApplication(argc, argv), current_view_(0),
+ : QApplication(argc, argv), current_view_(nullptr),
d(new GuiApplication::Private)
{
QString app_name = "LyX";
QCoreApplication::setOrganizationName(app_name);
QCoreApplication::setOrganizationDomain("lyx.org");
QCoreApplication::setApplicationName(lyx_package);
-#if QT_VERSION >= 0x050000
+#if QT_VERSION >= 0x050100 && QT_VERSION < 0x060000
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
+#if QT_VERSION >= 0x050700
+ setDesktopFileName(lyx_package);
+#endif
+
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
+ QRandomGenerator(QDateTime::currentDateTime().toSecsSinceEpoch());
+#else
qsrand(QDateTime::currentDateTime().toTime_t());
+#endif
// Install LyX translator for missing Qt translations
installTranslator(&d->gui_trans_);
setQuitOnLastWindowClosed(false);
///
setupApplescript();
+ appleCleanupEditMenu();
+ appleCleanupViewMenu();
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 1, 0))
+ connect(this, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
+ this, SLOT(onApplicationStateChanged(Qt::ApplicationState)));
+#endif
#endif
#if defined(Q_WS_X11) || defined(QPA_XCB)
}
+#if QT_VERSION < 0x050000
+// Emulate platformName() for Qt4
+
+// FIXME: when ditching this method, remove all tests
+// platformName() == "qt4x11"
+// in the code
+QString GuiApplication::platformName() const
+{
+# if defined(Q_WS_X11)
+ // Note that this one does not really exist
+ return "qt4x11";
+# elif defined(Q_OS_MAC)
+ return "cocoa";
+# elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
+ return "windows";
+# else
+ LYXERR0("Unknown platform!");
+ return "unknown";
+# endif
+}
+#endif
+
+
double GuiApplication::pixelRatio() const
{
#if QT_VERSION >= 0x050000
docstring Application::iconName(FuncRequest const & f, bool unknown)
{
- return qstring_to_ucs4(lyx::frontend::iconName(f, unknown));
+ return qstring_to_ucs4(lyx::frontend::iconInfo(f, unknown, false).filepath);
}
{
FuncStatus status;
- BufferView * bv = 0;
- BufferView * doc_bv = 0;
+ BufferView * bv = nullptr;
+ BufferView * doc_bv = nullptr;
if (cmd.action() == LFUN_NOACTION) {
status.message(from_utf8(N_("Nothing to do")));
break;
}
- case LFUN_BIDI: {
- string const dir = cmd.getArg(0);
- string const lfun = cmd.getLongArg(1);
- BufferView const * bv =
- current_view_ ? current_view_->currentBufferView() : nullptr;
- bool rtl = bv ? bv->cursor().innerParagraph().isRTL(bv->buffer().params())
- : layoutDirection() == Qt::RightToLeft;
- if (((rtl && dir != "rtl") || (!rtl && dir != "ltr"))) {
- flag.setUnknown(true);
- flag.setEnabled(false);
- } else {
- FuncRequest func(lyxaction.lookupFunc(lfun));
- func.setOrigin(cmd.origin());
- flag = getStatus(func);
- }
- break;
- }
-
case LFUN_IF_RELATIVES: {
string const lfun = to_utf8(cmd.argument());
BufferView const * bv =
case LFUN_REPEAT:
case LFUN_PREFERENCES_SAVE:
case LFUN_BUFFER_SAVE_AS_DEFAULT:
- case LFUN_DEBUG_LEVEL_SET:
// these are handled in our dispatch()
break;
+ case LFUN_DEBUG_LEVEL_SET: {
+ string bad = Debug::badValue(to_utf8(cmd.argument()));
+ enable = bad.empty();
+ if (!bad.empty())
+ flag.message(bformat(_("Bad debug value `%1$s'."), from_utf8(bad)));
+ break;
+ }
+
case LFUN_WINDOW_CLOSE:
- enable = d->views_.size() > 0;
+ enable = !d->views_.empty();
break;
case LFUN_BUFFER_NEW:
case LFUN_BUFFER_NEW_TEMPLATE:
case LFUN_FILE_OPEN:
+ case LFUN_LYXFILES_OPEN:
case LFUN_HELP_OPEN:
case LFUN_SCREEN_FONT_UPDATE:
case LFUN_SET_COLOR:
{
DispatchResult dr;
- Buffer * buffer = 0;
+ Buffer * buffer = nullptr;
if (cmd.view_origin() && current_view_ != cmd.view_origin()) {
//setCurrentView(cmd.view_origin); //does not work
dr.setError(true);
dr.screenUpdate(Update::FitCursor);
{
- // This handles undo groups automagically
+ // All the code is kept inside the undo group because
+ // updateBuffer can create undo actions (see #11292)
UndoGroupHelper ugh(buffer);
dispatch(cmd, dr);
- // redraw the screen at the end (first of the two drawing steps).
- // This is done unless explicitly requested otherwise.
- // This code is kept inside the undo group because updateBuffer
- // can create undo actions (see #11292)
+ if (dr.screenUpdate() & Update::ForceAll) {
+ for (Buffer const * b : theBufferList())
+ b->changed(true);
+ dr.screenUpdate(dr.screenUpdate() & ~Update::ForceAll);
+ }
+
updateCurrentView(cmd, dr);
}
BufferView * bv = current_view_->currentBufferView();
if (bv) {
- if (dr.needBufferUpdate()) {
+ if (dr.needBufferUpdate() || bv->buffer().needUpdate()) {
bv->cursor().clearBufferUpdate();
bv->buffer().updateBuffer();
}
// if the current buffer is not that one, switch to it.
BufferView * doc_bv = current_view_ ?
- current_view_->documentBufferView() : 0;
- Cursor const * old = doc_bv ? &doc_bv->cursor() : 0;
+ current_view_->documentBufferView() : nullptr;
+ Cursor const * old = doc_bv ? &doc_bv->cursor() : nullptr;
if (!doc_bv || doc_bv->buffer().fileName() != tmp.filename) {
if (switchToBuffer) {
dispatch(FuncRequest(LFUN_BUFFER_SWITCH, file));
void GuiApplication::reconfigure(string const & option)
{
// emit message signal.
- if (current_view_)
+ if (current_view_) {
current_view_->message(_("Running configure..."));
+ current_view_->setCursor(Qt::WaitCursor);
+ }
// Run configure in user lyx directory
string const lock_file = package().getConfigureLockName();
LaTeXPackages::getAvailable();
fileUnlock(fd, lock_file.c_str());
+ if (current_view_)
+ current_view_->unsetCursor();
+
if (ret)
Alert::information(_("System reconfiguration failed"),
_("The system reconfiguration has failed.\n"
// currently at least one view exists but no view has the focus.
// choose the last view to make it current.
// a view without any open document is preferred.
- GuiView * candidate = 0;
+ GuiView * candidate = nullptr;
QHash<int, GuiView *>::const_iterator it = d->views_.begin();
QHash<int, GuiView *>::const_iterator end = d->views_.end();
for (; it != end; ++it) {
break;
case LFUN_WINDOW_CLOSE:
+ // FIXME: this is done also in GuiView::closeBuffer()!
// update bookmark pit of the current buffer before window close
- for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
- gotoBookmark(i+1, false, false);
+ for (size_t i = 1; i < theSession().bookmarks().size(); ++i)
+ gotoBookmark(i, false, false);
// clear the last opened list, because
// maybe this will end the session
theSession().lastOpened().clear();
case LFUN_BUFFER_NEW:
validateCurrentView();
if (!current_view_
- || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) {
+ || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != nullptr)) {
createView(QString(), false); // keep hidden
current_view_->newDocument(to_utf8(cmd.argument()));
current_view_->show();
string const temp = cmd.getArg(1);
validateCurrentView();
if (!current_view_
- || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) {
+ || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != nullptr)) {
createView();
current_view_->newDocument(file, temp, true);
if (!current_view_->documentBufferView())
// current_view_ is not null.
validateCurrentView();
// FIXME: create a new method shared with LFUN_HELP_OPEN.
- string const fname = to_utf8(cmd.argument());
+ string const fname = trim(to_utf8(cmd.argument()), "\"");
bool const is_open = FileName::isAbsolute(fname)
&& theBufferList().getBuffer(FileName(fname));
if (!current_view_
|| (!lyxrc.open_buffers_in_tabs
- && current_view_->documentBufferView() != 0
+ && current_view_->documentBufferView() != nullptr
&& !is_open)) {
// We want the ui session to be saved per document and not per
// window number. The filename crc is a good enough identifier.
- boost::crc_32_type crc;
- crc = for_each(fname.begin(), fname.end(), crc);
- createView(crc.checksum());
+ createView(support::checksum(fname));
current_view_->openDocument(fname);
if (!current_view_->documentBufferView())
current_view_->close();
case LFUN_HELP_OPEN: {
// FIXME: create a new method shared with LFUN_FILE_OPEN.
- if (current_view_ == 0)
+ if (current_view_ == nullptr)
createView();
string const arg = to_utf8(cmd.argument());
if (arg.empty()) {
break;
}
+ case LFUN_LYXFILES_OPEN: {
+ // This is the actual reason for this method (#12106).
+ validateCurrentView();
+ if (!current_view_
+ || (!lyxrc.open_buffers_in_tabs
+ && current_view_->documentBufferView() != nullptr))
+ createView();
+ string arg = to_utf8(cmd.argument());
+ if (arg.empty())
+ // set default
+ arg = "templates";
+ if (arg != "templates" && arg != "examples") {
+ current_view_->message(_("Wrong argument. Must be 'examples' or 'templates'."));
+ break;
+ }
+ lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "lyxfiles " + arg));
+ break;
+ }
+
case LFUN_SET_COLOR: {
string const lyx_name = cmd.getArg(0);
- string const x11_name = cmd.getArg(1);
+ string x11_name = cmd.getArg(1);
+ string x11_darkname = cmd.getArg(2);
if (lyx_name.empty() || x11_name.empty()) {
if (current_view_)
current_view_->message(
- _("Syntax: set-color <lyx_name> <x11_name>"));
+ _("Syntax: set-color <lyx_name> <x11_name> <x11_darkname>"));
break;
}
graphics::GCache::get().changeDisplay(true);
#endif
- if (!lcolor.setColor(lyx_name, x11_name)) {
+ if (x11_darkname.empty() && colorCache().isDarkMode()) {
+ x11_darkname = x11_name;
+ x11_name.clear();
+ }
+ if (!lcolor.setColor(lyx_name, x11_name, x11_darkname)) {
if (current_view_)
current_view_->message(
bformat(_("Set-color \"%1$s\" failed "
actOnUpdatedPrefs(lyxrc_orig, lyxrc);
// If the request comes from the minibuffer, then we can't reset
- // the GUI, since that would destory the minibuffer itself and
+ // the GUI, since that would destroy the minibuffer itself and
// cause a crash, since we are currently in one of the methods of
// GuiCommandBuffer. See bug #8540.
if (cmd.origin() != FuncRequest::COMMANDBUFFER)
lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
break;
- // --- syntax commands ----------------------------
case LFUN_REPEAT: {
// repeat command
string countstr;
string arg = argument;
// FIXME: this LFUN should also work without any view.
Buffer * buffer = (current_view_ && current_view_->documentBufferView())
- ? &(current_view_->documentBufferView()->buffer()) : 0;
+ ? &(current_view_->documentBufferView()->buffer()) : nullptr;
// This handles undo groups automagically
UndoGroupHelper ugh(buffer);
while (!arg.empty()) {
// all of the buffers might be locally hidden. That is, there is no
// active buffer.
if (!view || !view->currentBufferView())
- activeBuffers[view] = 0;
+ activeBuffers[view] = nullptr;
else
activeBuffers[view] = &view->currentBufferView()->buffer();
GuiView * const homeView = currentView();
Buffer * b = theBufferList().first();
- Buffer * nextBuf = 0;
+ Buffer * nextBuf = nullptr;
int numProcessed = 0;
while (true) {
if (b != last)
break;
}
- case LFUN_BIDI: {
- string const lfun = cmd.getLongArg(1);
- FuncRequest func(lyxaction.lookupFunc(cmd.getLongArg(1)));
- func.setOrigin(cmd.origin());
- FuncStatus const stat = getStatus(func);
- if (stat.enabled()) {
- dispatch(func);
- break;
- }
- break;
- }
-
case LFUN_IF_RELATIVES: {
string const lfun = to_utf8(cmd.argument());
FuncRequest func(lyxaction.lookupFunc(lfun));
DocumentClassConstPtr olddc = defaults.params().documentClassPtr();
int const unknown_tokens = defaults.readHeader(lex);
DocumentClassConstPtr newdc = defaults.params().documentClassPtr();
- ErrorList el;
InsetText & theinset = static_cast<InsetText &>(defaults.inset());
- cap::switchBetweenClasses(olddc, newdc, theinset, el);
+ cap::switchBetweenClasses(olddc, newdc, theinset);
if (unknown_tokens != 0) {
lyxerr << "Warning in LFUN_BUFFER_SAVE_AS_DEFAULT!\n"
case LFUN_BOOKMARK_CLEAR:
theSession().bookmarks().clear();
+ dr.screenUpdate(Update::Force);
break;
case LFUN_DEBUG_LEVEL_SET:
// is not terminated when closing the last view.
// Create a new one to be able to dispatch the
// LFUN_DIALOG_SHOW to this view.
- if (current_view_ == 0)
+ if (current_view_ == nullptr)
createView();
}
}
break;
}
+ if (current_view_ && current_view_->isFullScreen()) {
+ if (current_view_->menuBar()->isVisible() && lyxrc.full_screen_menubar)
+ current_view_->menuBar()->hide();
+ }
+
if (cmd.origin() == FuncRequest::LYXSERVER)
updateCurrentView(cmd, dr);
}
}
+void GuiApplication::onPaletteChanged()
+{
+ colorCache().setPalette(palette());
+}
+
+
void GuiApplication::handleKeyFunc(FuncCode action)
{
char_type c = 0;
// seq has been reset at this point
func = seq.addkey(keysym, NoModifier);
- LYXERR(Debug::KEY, " Key (queried) [action=" << func.action() << "]["
+ LYXERR(Debug::KEY, " Key (queried) [action="
+ << lyxaction.getActionName(func.action()) << "]["
<< seq.print(KeySequence::Portable) << ']');
return func.action() != LFUN_UNKNOWN_ACTION;
}
d->cancel_meta_seq.reset();
FuncRequest func = d->cancel_meta_seq.addkey(keysym, state);
- LYXERR(Debug::KEY, "action first set to [" << func.action() << ']');
+ LYXERR(Debug::KEY, "action first set to ["
+ << lyxaction.getActionName(func.action()) << ']');
// When not cancel or meta-fake, do the normal lookup.
// Note how the meta_fake Mod1 bit is OR-ed in and reset afterwards.
if ((func.action() != LFUN_CANCEL) && (func.action() != LFUN_META_PREFIX)) {
// remove Caps Lock and Mod2 as a modifiers
func = d->keyseq.addkey(keysym, (state | d->meta_fake_bit));
- LYXERR(Debug::KEY, "action now set to [" << func.action() << ']');
+ LYXERR(Debug::KEY, "action now set to ["
+ << lyxaction.getActionName(func.action()) << ']');
}
- // Dont remove this unless you know what you are doing.
+ // Don't remove this unless you know what you are doing.
d->meta_fake_bit = NoModifier;
// Can this happen now ?
if (func.action() == LFUN_NOACTION)
func = FuncRequest(LFUN_COMMAND_PREFIX);
- LYXERR(Debug::KEY, " Key [action=" << func.action() << "]["
- << d->keyseq.print(KeySequence::Portable) << ']');
+ LYXERR(Debug::KEY, " Key [action="
+ << lyxaction.getActionName(func.action()) << "]["
+ << d->keyseq.print(KeySequence::Portable) << ']');
// already here we know if it any point in going further
// why not return already here if action == -1 and
// If addkey looked up a command and did not find further commands then
// seq has been reset at this point
func = d->keyseq.addkey(keysym, NoModifier);
- LYXERR(Debug::KEY, "Action now " << func.action());
+ LYXERR(Debug::KEY, "Action now " << lyxaction.getActionName(func.action()));
}
if (func.action() == LFUN_UNKNOWN_ACTION) {
return;
if (d->global_menubar_)
- d->menus_.fillMenuBar(d->global_menubar_, 0, false);
+ d->menus_.fillMenuBar(d->global_menubar_, nullptr, false);
QHash<int, GuiView *>::iterator it;
for (it = d->views_.begin(); it != d->views_.end(); ++it) {
}
+bool GuiApplication::rtlContext() const
+{
+ if (current_view_ && current_view_->currentBufferView()) {
+ BufferView const * bv = current_view_->currentBufferView();
+ return bv->cursor().innerParagraph().isRTL(bv->buffer().params());
+ } else
+ return layoutDirection() == Qt::RightToLeft;
+}
+
+
void GuiApplication::createView(int view_id)
{
createView(QString(), true, view_id);
void GuiApplication::createView(QString const & geometry_arg, bool autoShow,
int view_id)
{
- // release the keyboard which might have been grabed by the global
+ // release the keyboard which might have been grabbed by the global
// menubar on Mac to catch shortcuts even without any GuiView.
if (d->global_menubar_)
d->global_menubar_->releaseKeyboard();
int x, y;
int w, h;
QChar sx, sy;
+#if QT_VERSION < 0x060000
QRegExp re( "[=]*(?:([0-9]+)[xX]([0-9]+)){0,1}[ ]*(?:([+-][0-9]*)){0,1}(?:([+-][0-9]*)){0,1}" );
re.indexIn(geometry_arg);
w = re.cap(1).toInt();
y = re.cap(4).toInt();
sx = re.cap(3).isEmpty() ? '+' : re.cap(3).at(0);
sy = re.cap(4).isEmpty() ? '+' : re.cap(4).at(0);
+#else
+ QRegularExpression re( "[=]*(?:([0-9]+)[xX]([0-9]+)){0,1}[ ]*(?:([+-][0-9]*)){0,1}(?:([+-][0-9]*)){0,1}" );
+ QRegularExpressionMatch match = re.match(geometry_arg);
+ w = match.captured(1).toInt();
+ h = match.captured(2).toInt();
+ x = match.captured(3).toInt();
+ y = match.captured(4).toInt();
+ sx = match.captured(3).isEmpty() ? '+' : match.captured(3).at(0);
+ sy = match.captured(4).isEmpty() ? '+' : match.captured(4).at(0);
+#endif
// Set initial geometry such that we can get the frame size.
view->setGeometry(x, y, w, h);
int framewidth = view->geometry().x() - view->x();
// Negative displacements must be interpreted as distances
// from the right or bottom screen borders.
if (sx == '-' || sy == '-') {
+#if QT_VERSION < 0x060000
QRect rec = QApplication::desktop()->screenGeometry();
+#else
+ QRect rec = QGuiApplication::primaryScreen()->geometry();
+#endif
if (sx == '-')
x += rec.width() - w - framewidth;
if (sy == '-')
}
+bool GuiApplication::needsBackingStore() const
+{
+ /* Qt on macOS and Wayland does not respect the
+ * Qt::WA_OpaquePaintEvent attribute and resets the widget backing
+ * store at each update. Therefore, we use our own backing store
+ * in these two cases. It is also possible to force the use of the
+ * backing store for cases like x11 with transparent WM themes.
+ */
+ return platformName() == "cocoa" || platformName().contains("wayland");
+}
+
+
QList<int> GuiApplication::viewIds() const
{
return d->views_.keys();
// opposite. Therefore, long name should be used without truncation.
// c.f. http://doc.trolltech.com/4.1/qtranslator.html#load
if (!d->qt_trans_.load(language_name,
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ QLibraryInfo::path(QLibraryInfo::TranslationsPath))) {
+#else
QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
+#endif
LYXERR(Debug::LOCALE, "Could not find Qt translations for locale "
<< language_name);
} else {
#if QT_VERSION > 0x040600
setAttribute(Qt::AA_MacDontSwapCtrlAndMeta,lyxrc.mac_dontswap_ctrl_meta);
#endif
-#if QT_VERSION > 0x050100
+#if QT_VERSION >= 0x050000 && QT_VERSION < 0x060000
setAttribute(Qt::AA_UseHighDpiPixmaps,true);
#endif
// Create the global default menubar which is shown for the dialogs
// do not add to the lastfile list since these files are restored from
// last session, and should be already there (regular files), or should
// not be added at all (help files).
- for (size_t i = 0; i < lastopened.size(); ++i) {
- FileName const & file_name = lastopened[i].file_name;
+ for (auto const & last : lastopened) {
+ FileName const & file_name = last.file_name;
if (!current_view_ || (!lyxrc.open_buffers_in_tabs
- && current_view_->documentBufferView() != 0)) {
- boost::crc_32_type crc;
+ && current_view_->documentBufferView() != nullptr)) {
string const & fname = file_name.absFileName();
- crc = for_each(fname.begin(), fname.end(), crc);
- createView(crc.checksum());
+ createView(support::checksum(fname));
}
current_view_->loadDocument(file_name, false);
- if (lastopened[i].active)
+ if (last.active)
active_file = file_name;
}
e->accept();
return true;
#endif
+ case QEvent::ApplicationPaletteChange: {
+ // runtime switch from/to dark mode
+ onPaletteChanged();
+ return QApplication::event(e);
+ }
default:
return QApplication::event(e);
}
return false;
}
+bool GuiApplication::isInDarkMode()
+{
+ return colorCache().isDarkMode();
+}
+
bool GuiApplication::getRgbColor(ColorCode col, RGBColor & rgbcol)
{
bool Application::getRgbColorUncached(ColorCode col, RGBColor & rgbcol)
{
- QColor const qcol(lcolor.getX11Name(col).c_str());
+ QColor const qcol(lcolor.getX11HexName(col).c_str());
if (!qcol.isValid()) {
rgbcol.r = 0;
rgbcol.g = 0;
/** The implementation is required to avoid an application exit
** when session state save is triggered by session manager.
** The default implementation sends a close event to all
- ** visible top level widgets when session managment allows
+ ** visible top level widgets when session management allows
** interaction.
** We are changing that to check the state of each buffer in all
** views and ask the users what to do if buffers are dirty.
if(d->views_.contains(gv->id()) && d->views_.value(gv->id()) == gv) {
d->views_.remove(gv->id());
if (current_view_ == gv)
- current_view_ = 0;
+ current_view_ = nullptr;
}
}
Buffer const * GuiApplication::updateInset(Inset const * inset) const
{
- Buffer const * buf = 0;
+ Buffer const * buf = nullptr;
QHash<int, GuiView *>::const_iterator end = d->views_.end();
for (QHash<int, GuiView *>::iterator it = d->views_.begin(); it != end; ++it) {
if (Buffer const * ptr = (*it)->updateInset(inset))
bool GuiApplication::searchMenu(FuncRequest const & func,
docstring_list & names) const
{
- BufferView * bv = 0;
+ BufferView * bv = nullptr;
if (current_view_)
bv = current_view_->currentBufferView();
return d->menus_.searchMenu(func, names, bv);
static QStringList toolbar_uifiles;
-GuiApplication::ReturnValues GuiApplication::readUIFile(FileName ui_path)
+GuiApplication::ReturnValues GuiApplication::readUIFile(FileName const & ui_path)
{
enum {
ui_menuset = 1,
}
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 1, 0))
+void GuiApplication::onApplicationStateChanged(Qt::ApplicationState state)
+{
+ std::string name = "unknown";
+ switch (state) {
+ case Qt::ApplicationSuspended:
+ name = "ApplicationSuspended";
+ break;
+ case Qt::ApplicationHidden:
+ name = "ApplicationHidden";
+ break;
+ case Qt::ApplicationInactive:
+ name = "ApplicationInactive";
+ break;
+ case Qt::ApplicationActive:
+ name = "ApplicationActive";
+ /// The Dock icon click produces 2 sequential QEvent::ApplicationStateChangeEvent events.
+ /// cmd+tab only one QEvent::ApplicationStateChangeEvent event
+ if (d->views_.empty() && d->last_state_ == state) {
+ LYXERR(Debug::GUI, "Open new window...");
+ createView();
+ }
+ break;
+ }
+ LYXERR(Debug::GUI, "onApplicationStateChanged..." << name);
+ d->last_state_ = state;
+}
+#endif
+
+
void GuiApplication::startLongOperation() {
d->key_checker_.start();
}
// not doing that, maybe because of our
// "persistent selection" implementation
// (see comments in GuiSelection.cpp).
- xcb_selection_notify_event_t nev;
+ // It is expected that every X11 event is
+ // 32 bytes long, even if not all 32 bytes are
+ // needed. See:
+ // https://www.x.org/releases/current/doc/man/man3/xcb_send_event.3.xhtml
+ struct alignas(32) padded_event
+ : xcb_selection_notify_event_t {};
+ padded_event nev = {};
nev.response_type = XCB_SELECTION_NOTIFY;
nev.requestor = srev->requestor;
nev.selection = srev->selection;