diff --git a/MSVC.md b/MSVC.md index b5e1e4fad..dc6db0c6d 100644 --- a/MSVC.md +++ b/MSVC.md @@ -142,7 +142,7 @@ There go to Qt directory and after that run configure - configure -debug-and-release -opensource -confirm-license -static -I "D:\TBuild\Libraries\OpenSSL-Win32\include" -L "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib" -l Gdi32 -opengl desktop -openssl-linked OPENSSL_LIBS_DEBUG="D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\ssleay32MTd.lib D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\libeay32MTd.lib" OPENSSL_LIBS_RELEASE="D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\ssleay32MT.lib D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\libeay32MT.lib" -mp -nomake examples -nomake tests -platform win32-msvc2013 + configure -debug-and-release -opensource -confirm-license -static -I "D:\TBuild\Libraries\OpenSSL-Win32\include" -L "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib" -l Gdi32 -no-opengl -openssl-linked OPENSSL_LIBS_DEBUG="D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\ssleay32MTd.lib D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\libeay32MTd.lib" OPENSSL_LIBS_RELEASE="D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\ssleay32MT.lib D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\libeay32MT.lib" -mp -nomake examples -nomake tests -platform win32-msvc2013 to configure Qt build. After configuration is complete run diff --git a/Telegram.sln b/Telegram.sln index a59887f8a..d33570a0c 100644 --- a/Telegram.sln +++ b/Telegram.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30110.0 +VisualStudioVersion = 12.0.30501.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Telegram", "Telegram\Telegram.vcxproj", "{B12702AD-ABFB-343A-A199-8E24837244A3}" ProjectSection(ProjectDependencies) = postProject @@ -16,8 +16,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MetaEmoji", "Telegram\MetaE EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Updater", "Telegram\Updater.vcxproj", "{6B4BA3BE-7B15-4B4C-B200-81ABFDEF2C76}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Prepare", "Telegram\Prepare.vcxproj", "{88AB1138-143A-4CFB-A0E6-79B646B5E1B0}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MetaLang", "Telegram\MetaLang.vcxproj", "{E417CAA4-259B-4C99-88E3-805F1300E8EB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2F863EAD-33C9-4014-A573-93F085BA9CB1}" @@ -71,15 +69,6 @@ Global {6B4BA3BE-7B15-4B4C-B200-81ABFDEF2C76}.Release|Win32.ActiveCfg = Release|Win32 {6B4BA3BE-7B15-4B4C-B200-81ABFDEF2C76}.Release|Win32.Build.0 = Release|Win32 {6B4BA3BE-7B15-4B4C-B200-81ABFDEF2C76}.Release|x64.ActiveCfg = Release|Win32 - {88AB1138-143A-4CFB-A0E6-79B646B5E1B0}.Debug|Win32.ActiveCfg = Debug|Win32 - {88AB1138-143A-4CFB-A0E6-79B646B5E1B0}.Debug|Win32.Build.0 = Debug|Win32 - {88AB1138-143A-4CFB-A0E6-79B646B5E1B0}.Debug|x64.ActiveCfg = Debug|Win32 - {88AB1138-143A-4CFB-A0E6-79B646B5E1B0}.Deploy|Win32.ActiveCfg = Deploy|Win32 - {88AB1138-143A-4CFB-A0E6-79B646B5E1B0}.Deploy|Win32.Build.0 = Deploy|Win32 - {88AB1138-143A-4CFB-A0E6-79B646B5E1B0}.Deploy|x64.ActiveCfg = Release|Win32 - {88AB1138-143A-4CFB-A0E6-79B646B5E1B0}.Release|Win32.ActiveCfg = Release|Win32 - {88AB1138-143A-4CFB-A0E6-79B646B5E1B0}.Release|Win32.Build.0 = Release|Win32 - {88AB1138-143A-4CFB-A0E6-79B646B5E1B0}.Release|x64.ActiveCfg = Release|Win32 {E417CAA4-259B-4C99-88E3-805F1300E8EB}.Debug|Win32.ActiveCfg = Debug|Win32 {E417CAA4-259B-4C99-88E3-805F1300E8EB}.Debug|Win32.Build.0 = Debug|Win32 {E417CAA4-259B-4C99-88E3-805F1300E8EB}.Debug|x64.ActiveCfg = Debug|Win32 diff --git a/Telegram/Prepare.vcxproj b/Telegram/Prepare.vcxproj deleted file mode 100644 index 7dc896256..000000000 --- a/Telegram/Prepare.vcxproj +++ /dev/null @@ -1,126 +0,0 @@ - - - - - Debug - Win32 - - - Deploy - Win32 - - - Release - Win32 - - - - - - - - - - {88AB1138-143A-4CFB-A0E6-79B646B5E1B0} - Qt4VSv1.0 - - - - Application - v120_xp - - - Application - v120_xp - - - Application - v120_xp - - - - - - - - - - - - - - - - <_ProjectFileVersion>11.0.61030.0 - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(SolutionDir)$(Platform)\$(Configuration)IntermediatePrepare\ - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(SolutionDir)$(Platform)\$(Configuration)IntermediatePrepare\ - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(SolutionDir)$(Platform)\$(Configuration)IntermediatePrepare\ - - - - UNICODE;WIN32;WIN64;QT_CORE_LIB;%(PreprocessorDefinitions) - Disabled - ProgramDatabase - MultiThreadedDebug - .;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;%(AdditionalIncludeDirectories) - false - - - Console - $(OutDir)\$(ProjectName).exe - $(QTDIR)\lib;%(AdditionalLibraryDirectories) - kernel32.lib;user32.lib;shell32.lib;uuid.lib;ole32.lib;advapi32.lib;ws2_32.lib;qtmaind.lib;Qt5Cored.lib;%(AdditionalDependencies) - true - - - - - UNICODE;WIN32;WIN64;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;%(PreprocessorDefinitions) - - MultiThreaded - .;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;%(AdditionalIncludeDirectories) - false - - - Console - $(OutDir)\$(ProjectName).exe - $(QTDIR)\lib;%(AdditionalLibraryDirectories) - kernel32.lib;user32.lib;shell32.lib;uuid.lib;ole32.lib;advapi32.lib;ws2_32.lib;qtmain.lib;Qt5Core.lib;%(AdditionalDependencies) - false - - - - - UNICODE;WIN32;WIN64;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;%(PreprocessorDefinitions) - - - MultiThreaded - .;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;%(AdditionalIncludeDirectories) - false - - - Console - $(OutDir)\$(ProjectName).exe - $(QTDIR)\lib;%(AdditionalLibraryDirectories) - kernel32.lib;user32.lib;shell32.lib;uuid.lib;ole32.lib;advapi32.lib;ws2_32.lib;qtmain.lib;Qt5Core.lib;%(AdditionalDependencies) - false - - - - - - - - - - - \ No newline at end of file diff --git a/Telegram/Prepare.vcxproj.filters b/Telegram/Prepare.vcxproj.filters deleted file mode 100644 index c4a106555..000000000 --- a/Telegram/Prepare.vcxproj.filters +++ /dev/null @@ -1,33 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;cxx;c;def - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h - - - {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} - qrc;* - false - - - {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} - moc;h;cpp - False - - - - - Source Files - - - - - Header Files - - - \ No newline at end of file diff --git a/Telegram/PrepareWin.bat b/Telegram/PrepareWin.bat index 8a019fba8..23c286dc6 100644 --- a/Telegram/PrepareWin.bat +++ b/Telegram/PrepareWin.bat @@ -1,8 +1,9 @@ @echo OFF -set "AppVersionStrSmall=0.7.16" -set "AppVersionStr=0.7.16" -set "AppVersionStrFull=0.7.16.0" +set "AppVersion=7017" +set "AppVersionStrSmall=0.7.17" +set "AppVersionStr=0.7.17" +set "AppVersionStrFull=0.7.17.0" set "DevChannel=0" if %DevChannel% neq 0 goto preparedev @@ -22,6 +23,10 @@ echo. echo Preparing version %AppVersionStr%%DevPostfix%.. echo. +if exist ..\Win32\Deploy\deploy\%AppVersionStr%\ goto error_exist1 +if exist ..\Win32\Deploy\deploy\%AppVersionStr%.dev\ goto error_exist2 +if exist ..\Win32\Deploy\tupdate%AppVersion% goto error_exist3 + set "PATH=%PATH%;C:\Program Files\7-Zip;C:\Program Files (x86)\Inno Setup 5" cd ..\Win32\Deploy @@ -37,12 +42,21 @@ if %errorlevel% neq 0 goto error1 call ..\..\..\TelegramPrivate\Sign.bat tsetup.%AppVersionStr%%DevPostfix%.exe if %errorlevel% neq 0 goto error1 -call Prepare.exe -path Telegram.exe -path Updater.exe %DevParam% +call Packer.exe -version %AppVersion% -path Telegram.exe -path Updater.exe %DevParam% if %errorlevel% neq 0 goto error1 +if not exist deploy mkdir deploy +mkdir deploy\%AppVersionStr%%DevPostfix% +mkdir deploy\%AppVersionStr%%DevPostfix%\Telegram + +move Telegram.exe deploy\%AppVersionStr%%DevPostfix%\Telegram\ +move Updater.exe deploy\%AppVersionStr%%DevPostfix%\ +move Telegram.pdb deploy\%AppVersionStr%%DevPostfix%\ +move Updater.pdb deploy\%AppVersionStr%%DevPostfix%\ +move tsetup.%AppVersionStr%%DevPostfix%.exe deploy\%AppVersionStr%%DevPostfix%\ +move tupdate%AppVersion% deploy\%AppVersionStr%%DevPostfix%\ + cd deploy\%AppVersionStr%%DevPostfix% -mkdir Telegram -move Telegram.exe Telegram\ 7z a -mx9 tportable.%AppVersionStr%%DevPostfix%.zip Telegram\ if %errorlevel% neq 0 goto error2 @@ -60,4 +74,16 @@ cd ..\..\Telegram echo ERROR occured! exit /b %errorlevel% +:error_exist1 +echo Deploy folder for version %AppVersionStr% already exists! +exit /b 1 + +:error_exist2 +echo Deploy folder for version %AppVersionStr%.dev already exists! +exit /b 1 + +:error_exist3 +echo Update file for version %AppVersion% already exists! +exit /b 1 + :eof diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp index deb0ed572..bd1a7df10 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -253,7 +253,12 @@ int main(int argc, char *argv[]) size_t compressedLen = compressed.size() - hSize; size_t outPropsSize = LZMA_PROPS_SIZE; - int res = LzmaCompress((uchar*)(compressed.data() + hSize), &compressedLen, (const uchar*)(result.constData()), result.size(), (uchar*)(compressed.data() + hSigLen + hShaLen), &outPropsSize, 9, 64 * 1024 * 1024, 0, 0, 0, 0, 0); + uchar *_dest = (uchar*)(compressed.data() + hSize); + size_t *_destLen = &compressedLen; + const uchar *_src = (const uchar*)(result.constData()); + size_t _srcLen = result.size(); + uchar *_outProps = (uchar*)(compressed.data() + hSigLen + hShaLen); + int res = LzmaCompress(_dest, _destLen, _src, _srcLen, _outProps, &outPropsSize, 9, 64 * 1024 * 1024, 4, 0, 2, 273, 2); if (res != SZ_OK) { cout << "Error in compression: " << res << "\n"; return -1; diff --git a/Telegram/SourceFiles/_other/prepare.cpp b/Telegram/SourceFiles/_other/prepare.cpp deleted file mode 100644 index 4c05acb57..000000000 --- a/Telegram/SourceFiles/_other/prepare.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014 John Preston, https://desktop.telegram.org -*/ -#include "prepare.h" - -bool DevChannel = false; - -int prepare(QFileInfo f, QStringList paths) { - if (paths.isEmpty()) { - cout << "No -path args were passed :(\n"; - return -1; - } - - int lastVersion = 0; - QString lastVersionStr; - QFileInfo last; - QFileInfoList l = f.absoluteDir().entryInfoList(QDir::Files); - for (QFileInfoList::iterator i = l.begin(), e = l.end(); i != e; ++i) { - QRegularExpressionMatch m = QRegularExpression("/tsetup\\.((\\d+)\\.(\\d+)\\.(\\d+))(?:\\.dev)?\\.exe$").match(i->absoluteFilePath()); - if (!m.hasMatch()) continue; - - int version = m.captured(2).toInt() * 1000000 + m.captured(3).toInt() * 1000 + m.captured(4).toInt(); - if (version > lastVersion) { - lastVersion = version; - lastVersionStr = m.captured(1); - last = *i; - } - } - - if (!lastVersion) { - cout << "No tsetup.X.Y.Z.exe or tsetup.X.Y.Z.dev.exe found :(\n"; - return -1; - } - - cout << "Last version: " << (lastVersionStr + (DevChannel ? ".dev" : "")).toUtf8().constData() << " (" << lastVersion << "), executing packer..\n"; - - QDir dir("deploy/" + lastVersionStr + (DevChannel ? ".dev" : "")); - if (dir.exists()) { - cout << "Version " << (lastVersionStr + (DevChannel ? ".dev" : "")).toUtf8().constData() << " already exists in /deploy..\n"; - return -1; - } else if (QDir("deploy/" + lastVersionStr + (DevChannel ? "" : ".dev")).exists()) { - cout << "Version " << (lastVersionStr + (DevChannel ? "" : ".dev")).toUtf8().constData() << " already exists in /deploy..\n"; - return -1; - } - - QString packer = QString("Packer.exe -version %1").arg(lastVersion); - for (QStringList::iterator i = paths.begin(), e = paths.end(); i != e; ++i) { - packer += " -path " + *i; - } - if (DevChannel) packer += " -dev"; - - int res = system(packer.toUtf8().constData()); - - if (res) return res; - - dir.mkpath("."); - - paths.push_back("Telegram.pdb"); - paths.push_back("Updater.pdb"); - paths.push_back("tsetup." + lastVersionStr + (DevChannel ? ".dev" : "") + ".exe"); - paths.push_back(QString("tupdate%1").arg(lastVersion)); - for (QStringList::iterator i = paths.begin(), e = paths.end(); i != e; ++i) { - if (!QFile::copy(*i, "deploy/" + lastVersionStr + (DevChannel ? ".dev" : "") + "/" + *i)) { - cout << "Could not copy " << i->toUtf8().constData() << " to deploy/" << (lastVersionStr + (DevChannel ? ".dev" : "")).toUtf8().constData() << "\n"; - return -1; - } - cout << "Copied " << i->toUtf8().constData() << "..\n"; - } - for (QStringList::iterator i = paths.begin(), e = paths.end(); i != e; ++i) { - QFile::remove(*i); - } - - cout << "Update created in deploy/" << (lastVersionStr + (DevChannel ? ".dev" : "")).toUtf8().constData() << "\n"; - - return 0; -} - -int main(int argc, char *argv[]) -{ - QFileInfo f(argv[0]); - - QStringList paths; - for (int i = 1; i < argc; ++i) { - if (string(argv[i]) == "-path" && i + 1 < argc) { - paths.push_back(QString(argv[i + 1])); - } else if (string(argv[i]) == "-dev") { - DevChannel = true; - } - } - int res = prepare(f, paths); - return res; -} diff --git a/Telegram/SourceFiles/_other/prepare.h b/Telegram/SourceFiles/_other/prepare.h deleted file mode 100644 index b12c1a645..000000000 --- a/Telegram/SourceFiles/_other/prepare.h +++ /dev/null @@ -1,37 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014 John Preston, https://desktop.telegram.org -*/ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -using std::string; -using std::wstring; -using std::cout; \ No newline at end of file diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index be5f8cc57..b894d74ca 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -22,7 +22,6 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "pspecific.h" #include "fileuploader.h" #include "mainwidget.h" -#include "supporttl.h" #include "lang.h" #include "boxes/confirmbox.h" @@ -674,7 +673,6 @@ void Application::startApp() { window->init(); DEBUG_LOG(("Application Info: window created..")); - readSupportTemplates(); MTP::start(); @@ -706,8 +704,8 @@ void Application::startApp() { QString versionFeatures; if (DevChannel && Local::oldMapVersion() < 7015) { versionFeatures = QString::fromUtf8("\xe2\x80\x94 Some translation keys added\n\xe2\x80\x94 Linux: fixed semibold font\n\xe2\x80\x94 Linux: tray icon should work better in non-Unity environments"); - } else if (!DevChannel && Local::oldMapVersion() < 7016) { - versionFeatures = lang(lng_new_version7016).trimmed(); + } else if (!DevChannel && Local::oldMapVersion() < 7017) { + versionFeatures = lang(lng_new_version_minor).trimmed(); } if (!versionFeatures.isEmpty()) { versionFeatures = lng_new_version_wrap(lt_version, QString::fromStdWString(AppVersionStr), lt_changes, versionFeatures, lt_link, qsl("https://desktop.telegram.org/#changelog")); diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index b42b299a4..410f8824a 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -17,8 +17,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org */ #pragma once -static const int32 AppVersion = 7016; -static const wchar_t *AppVersionStr = L"0.7.16"; +static const int32 AppVersion = 7017; +static const wchar_t *AppVersionStr = L"0.7.17"; static const bool DevChannel = false; static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)"; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index bcc7f40e3..dc169aa8a 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -26,7 +26,6 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "window.h" #include "fileuploader.h" -#include "supporttl.h" #include "localstorage.h" @@ -1585,7 +1584,6 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent) connect(&_attachPhoto, SIGNAL(clicked()), this, SLOT(onPhotoSelect())); connect(&_field, SIGNAL(submitted(bool)), this, SLOT(onSend(bool))); connect(&_field, SIGNAL(cancelled()), this, SLOT(onCancel())); - connect(&_field, SIGNAL(tabbed()), this, SLOT(onFieldTabbed())); connect(&_field, SIGNAL(resized()), this, SLOT(onFieldResize())); connect(&_field, SIGNAL(focused()), this, SLOT(onFieldFocused())); connect(&imageLoader, SIGNAL(imageReady()), this, SLOT(onPhotoReady())); @@ -3429,49 +3427,6 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) { } } -void HistoryWidget::onFieldTabbed() { - QString v = _field.getText(), t = supportTemplate(v); - if (!t.isEmpty()) { - bool isImg = t.startsWith(qsl("img:")), isFile = t.startsWith(qsl("file:")), isContact = t.startsWith(qsl("contact:")); - if (isImg || isFile) { - QString fname = t.mid(isImg ? 4 : 5).trimmed(), text; - int32 lineEnd = fname.indexOf(QChar('\n')); - if (lineEnd > 0) { - text = fname.mid(lineEnd + 1).trimmed(); - fname = fname.mid(0, lineEnd).trimmed(); - } - if (isImg) { - QImage img(cWorkingDir() + fname); - if (!img.isNull()) { - setFieldText(text); - uploadImage(img, !text.isEmpty()); - } - } else { - setFieldText(text); - uploadFile(cWorkingDir() + fname, !text.isEmpty()); - } - } else if (isContact) { - QString contact = t.mid(8).trimmed(), text; - int32 lineEnd = contact.indexOf(QChar('\n')); - if (lineEnd > 0) { - text = contact.mid(lineEnd + 1).trimmed(); - contact = contact.mid(0, lineEnd).trimmed(); - } - QStringList data = contact.split(QChar(' ')); - if (data.size() > 1) { - setFieldText(text); - QString phone = data.at(0).trimmed(), fname = data.at(1).trimmed(), lname = (data.size() > 2) ? static_cast(data.mid(2)).join(QChar(' ')).trimmed() : QString(); - shareContactConfirmation(phone, fname, lname, !text.isEmpty()); - } - } else { - setFieldText(t); - QTextCursor c = _field.textCursor(); - c.movePosition(QTextCursor::End); - _field.setTextCursor(c); - } - } -} - void HistoryWidget::onStickerSend(DocumentData *sticker) { if (!hist || !sticker) return; diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index c0215fe9d..951fea0a7 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -382,7 +382,6 @@ public slots: void activate(); void onTextChange(); - void onFieldTabbed(); void onStickerSend(DocumentData *sticker); void onVisibleChanged(); diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 02b3303d4..8128abe63 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -69,8 +69,10 @@ _saveMsgStarted(0), _saveMsgOpacity(0) setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_TranslucentBackground, true); setMouseTracking(true); - hide(); + hide(); + createWinId(); + connect(&_close, SIGNAL(clicked()), this, SLOT(onClose())); connect(&_save, SIGNAL(clicked()), this, SLOT(onDownload())); connect(&_forward, SIGNAL(clicked()), this, SLOT(onForward())); @@ -528,7 +530,7 @@ void MediaView::showPhoto(PhotoData *photo) { void MediaView::paintEvent(QPaintEvent *e) { QPainter p(this); QRect r(e->rect()); - + QPainter::CompositionMode m = p.compositionMode(); p.setCompositionMode(QPainter::CompositionMode_Source); diff --git a/Telegram/SourceFiles/mtproto/mtpCoreTypes.h b/Telegram/SourceFiles/mtproto/mtpCoreTypes.h index 451f220ec..a9d1916dc 100644 --- a/Telegram/SourceFiles/mtproto/mtpCoreTypes.h +++ b/Telegram/SourceFiles/mtproto/mtpCoreTypes.h @@ -20,6 +20,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "types.h" #include +#undef min +#undef max + //#define DEBUG_MTPPRIME #ifdef DEBUG_MTPPRIME diff --git a/Telegram/SourceFiles/pspecific_wnd.cpp b/Telegram/SourceFiles/pspecific_wnd.cpp index ba86a81ee..03f8bd0fc 100644 --- a/Telegram/SourceFiles/pspecific_wnd.cpp +++ b/Telegram/SourceFiles/pspecific_wnd.cpp @@ -1786,7 +1786,7 @@ bool psSkipAudioNotify() { bool psSkipDesktopNotify() { QUERY_USER_NOTIFICATION_STATE state; if (useShellapi && SUCCEEDED(shQueryUserNotificationState(&state))) { - if (state == QUNS_PRESENTATION_MODE || state == QUNS_RUNNING_D3D_FULL_SCREEN || state == QUNS_BUSY) return true; + if (state == QUNS_PRESENTATION_MODE || state == QUNS_RUNNING_D3D_FULL_SCREEN/* || state == QUNS_BUSY*/) return true; } return false; } diff --git a/Telegram/SourceFiles/supporttl.cpp b/Telegram/SourceFiles/supporttl.cpp deleted file mode 100644 index bea949fd8..000000000 --- a/Telegram/SourceFiles/supporttl.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014 John Preston, https://desktop.telegram.org -*/ -#include "stdafx.h" -#include "supporttl.h" - -namespace { - typedef QMap SupportTemplates; - SupportTemplates _supportTemplates; - - void saveTemplate(QStringList &keys, QString &value) { - if (!keys.isEmpty() && !value.isEmpty()) { - if (value.at(value.size() - 1) == '\n') { - value = value.mid(0, value.size() - 1); - } - for (QStringList::const_iterator i = keys.cbegin(), e = keys.cend(); i != e; ++i) { - _supportTemplates[textSearchKey(*i)] = value; - } - } - value = QString(); - } -} - -void readSupportTemplates() { - QStringList files(cWorkingDir() + qsl("support_tl.txt")); - QDir supp(cWorkingDir() + qsl("tsupport")); - if (supp.exists()) { - QStringList all = supp.entryList(QDir::Files); - for (QStringList::const_iterator i = all.cbegin(), e = all.cend(); i != e; ++i) { - if (i->startsWith(qsl("tl_"))) { - files.push_back(cWorkingDir() + qsl("tsupport/") + *i); - } - } - } - - typedef QList TemplatesLines; - enum ReadingState { - ReadingNone = 0, - ReadingKeys = 1, - ReadingValue = 2, - ReadingMoreValue = 3, - }; - - for (QStringList::const_iterator i = files.cbegin(), e = files.cend(); i != e; ++i) { - QFile f(*i); - if (!f.open(QIODevice::ReadOnly)) continue; - - TemplatesLines lines = f.readAll().split('\n'); - - f.close(); - - ReadingState state = ReadingNone; - QStringList keys; - QString value; - for (TemplatesLines::const_iterator i = lines.cbegin(), e = lines.cend(); i != e; ++i) { - QString line = QString::fromUtf8(*i).trimmed(); - QRegularExpressionMatch m = QRegularExpression(qsl("^\\{([A-Z_]+)\\}$")).match(line); - if (m.hasMatch()) { - saveTemplate(keys, value); - - QString token = m.captured(1); - if (token == qsl("KEYS")) { - keys.clear(); - state = ReadingKeys; - } else if (token == qsl("VALUE")) { - state = ReadingValue; - } else { - keys.clear(); - state = ReadingNone; - } - continue; - } - - switch (state) { - case ReadingKeys: - if (!line.isEmpty()) { - keys.push_back(line); - } - break; - - case ReadingMoreValue: - value += '\n'; - case ReadingValue: - value += line; - state = ReadingMoreValue; - break; - } - } - saveTemplate(keys, value); - } -} - -const QString &supportTemplate(const QString &key) { - SupportTemplates::const_iterator i = _supportTemplates.constFind(textSearchKey(key)); - if (i != _supportTemplates.cend()) { - return *i; - } - - static const QString _tmp; - return _tmp; -} diff --git a/Telegram/SourceFiles/supporttl.h b/Telegram/SourceFiles/supporttl.h deleted file mode 100644 index 974a82595..000000000 --- a/Telegram/SourceFiles/supporttl.h +++ /dev/null @@ -1,21 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014 John Preston, https://desktop.telegram.org -*/ -#pragma once - -void readSupportTemplates(); -const QString &supportTemplate(const QString &key); diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp index 622b09c6e..f9099a1f6 100644 --- a/Telegram/SourceFiles/window.cpp +++ b/Telegram/SourceFiles/window.cpp @@ -329,7 +329,7 @@ NotifyWindow::~NotifyWindow() { if (App::wnd()) App::wnd()->notifyShowNext(this); } -Window::Window(QWidget *parent) : PsMainWindow(parent), _serviceHistoryRequest(0), +Window::Window(QWidget *parent) : PsMainWindow(parent), _serviceHistoryRequest(0), title(0), intro(0), main(0), settings(0), layerBG(0), _isActive(false), _topWidget(0), _connecting(0), _clearManager(0), dragging(false), _inactivePress(false), _mediaView(0) { @@ -1034,6 +1034,8 @@ TitleWidget *Window::getTitle() { } void Window::resizeEvent(QResizeEvent *e) { + if (!title) return; + bool wideMode = (width() >= st::wideModeWidth); if (wideMode != cWideMode()) { cSetWideMode(wideMode); diff --git a/Telegram/Telegram.plist b/Telegram/Telegram.plist index 15c3decb9..c293b73f9 100644 --- a/Telegram/Telegram.plist +++ b/Telegram/Telegram.plist @@ -11,7 +11,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.7.16 + 0.7.17 CFBundleSignature ???? CFBundleURLTypes diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro index e63f92455..43dd72f21 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -99,7 +99,6 @@ SOURCES += \ ./SourceFiles/mainwidget.cpp \ ./SourceFiles/settings.cpp \ ./SourceFiles/settingswidget.cpp \ - ./SourceFiles/supporttl.cpp \ ./SourceFiles/sysbuttons.cpp \ ./SourceFiles/title.cpp \ ./SourceFiles/types.cpp \ @@ -179,7 +178,6 @@ HEADERS += \ ./SourceFiles/settings.h \ ./SourceFiles/settingswidget.h \ ./SourceFiles/style.h \ - ./SourceFiles/supporttl.h \ ./SourceFiles/sysbuttons.h \ ./SourceFiles/title.h \ ./SourceFiles/types.h \ diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc index c0e5eac93..9720c09f6 100644 Binary files a/Telegram/Telegram.rc and b/Telegram/Telegram.rc differ diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index b9b00a9a0..e58af3357 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -914,7 +914,6 @@ Create Create - @@ -1749,7 +1748,6 @@ - Moc%27ing sysbuttons.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp @@ -1819,4 +1817,4 @@ - \ No newline at end of file + diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 2a3ce6228..9960516b1 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -150,9 +150,6 @@ gui - - Source Files - gui @@ -839,9 +836,6 @@ gui - - Source Files - gui @@ -1079,4 +1073,4 @@ langs - \ No newline at end of file + diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index 443f232df..50876c8d9 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -127,7 +127,6 @@ 830CB6F547B8C80A569A0271 /* addparticipantbox.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 40E88FBA282991C8CBDFE94D /* addparticipantbox.cpp */; settings = {ATTRIBUTES = (); }; }; 832C50BFD7D09AF042A51D4F /* flatbutton.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = F1A04BDB750C2AE652797B04 /* flatbutton.cpp */; settings = {ATTRIBUTES = (); }; }; 8771A8C96E9C391044035D99 /* OpenGL.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = D4B32C2222F82AC56BADEB21 /* OpenGL.framework */; }; - 878DCAF0C5B0DB2EB4EDFD3D /* supporttl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 0B2F770F53FBB0DBF738D89B /* supporttl.cpp */; settings = {ATTRIBUTES = (); }; }; 8883FF366F2623E89D90A9E6 /* qgenericbearer in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 9A55B8F7C143D66AD9EAE304 /* qgenericbearer */; }; 89ADB41E48A3B5E24ABB626C /* profilewidget.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = CF32DF59C7823E4F3397EF3C /* profilewidget.cpp */; settings = {ATTRIBUTES = (); }; }; 8B22E794EFF0EAFF964A3043 /* introsteps.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = C34459FA465B57DF4DB80D12 /* introsteps.cpp */; settings = {ATTRIBUTES = (); }; }; @@ -311,7 +310,6 @@ 098EA7CE256AAFAE4A17EB77 /* introcode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = introcode.h; path = SourceFiles/intro/introcode.h; sourceTree = ""; }; 09FD01F2BD652EB838A296D8 /* application.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = application.h; path = SourceFiles/application.h; sourceTree = ""; }; 0ABCEA8D0DD45589040B0AF2 /* /usr/local/Qt-5.4.0/mkspecs/common/unix.conf */ = {isa = PBXFileReference; lastKnownFileType = text; path = "/usr/local/Qt-5.4.0/mkspecs/common/unix.conf"; sourceTree = ""; }; - 0B2F770F53FBB0DBF738D89B /* supporttl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = supporttl.cpp; path = SourceFiles/supporttl.cpp; sourceTree = ""; }; 0BDE09020E45EFA57DCB2E25 /* photosendbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = photosendbox.h; path = SourceFiles/boxes/photosendbox.h; sourceTree = ""; }; 0C0DC15EB416789673526AA5 /* moc_emojibox.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = moc_emojibox.cpp; path = GeneratedFiles/Debug/moc_emojibox.cpp; sourceTree = ""; }; 0CAA815FFFEDCD84808E11F5 /* logs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = logs.h; path = SourceFiles/logs.h; sourceTree = ""; }; @@ -387,7 +385,6 @@ 3F08D430CEC8D2117735CCB4 /* /usr/local/Qt-5.4.0/mkspecs/modules/qt_plugin_qmldbg_tcp_qtdeclarative.pri */ = {isa = PBXFileReference; lastKnownFileType = text; path = "/usr/local/Qt-5.4.0/mkspecs/modules/qt_plugin_qmldbg_tcp_qtdeclarative.pri"; sourceTree = ""; }; 40E88FBA282991C8CBDFE94D /* addparticipantbox.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = addparticipantbox.cpp; path = SourceFiles/boxes/addparticipantbox.cpp; sourceTree = ""; }; 420A06A32B66D250142B4B6D /* style_core.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = style_core.cpp; path = SourceFiles/gui/style_core.cpp; sourceTree = ""; }; - 42FA90460B1A91C414157EAC /* supporttl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = supporttl.h; path = SourceFiles/supporttl.h; sourceTree = ""; }; 45B95DB3B70B47A910FC847B /* /usr/local/Qt-5.4.0/mkspecs/common/gcc-base.conf */ = {isa = PBXFileReference; lastKnownFileType = text; path = "/usr/local/Qt-5.4.0/mkspecs/common/gcc-base.conf"; sourceTree = ""; }; 45DB132B756499D4DF38430E /* /usr/local/Qt-5.4.0/mkspecs/modules/qt_plugin_qjp2.pri */ = {isa = PBXFileReference; lastKnownFileType = text; path = "/usr/local/Qt-5.4.0/mkspecs/modules/qt_plugin_qjp2.pri"; sourceTree = ""; }; 4604687EBA85611C9E8A9CDF /* button.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = button.h; path = SourceFiles/gui/button.h; sourceTree = ""; }; @@ -891,7 +888,6 @@ 047DAFB0A7DE92C63033A43C /* mainwidget.cpp */, 8A28F7789408AA839F48A5F2 /* settings.cpp */, 8CCCACE96535180FEB557712 /* settingswidget.cpp */, - 0B2F770F53FBB0DBF738D89B /* supporttl.cpp */, 6B90F69947805586A6FAE80E /* sysbuttons.cpp */, 2BB2A1BB8DB0993F78F4E3C7 /* title.cpp */, 7D075A915E8739C1B6BC5F43 /* types.cpp */, @@ -927,7 +923,6 @@ 2EA58EF6CDF368B0132BAEB9 /* settings.h */, F80095A026AF9453E9C2B8BD /* settingswidget.h */, C913E6A1001E07EE7C13CE93 /* style.h */, - 42FA90460B1A91C414157EAC /* supporttl.h */, 3BE70E2A82DC2BF402165ED5 /* sysbuttons.h */, 4D504A849F15EB58E53A4E5F /* title.h */, 1DC02F674A7192FF8BE391A7 /* types.h */, @@ -1476,7 +1471,6 @@ E3194392BD6D0726F75FA72E /* mainwidget.cpp in Compile Sources */, DF36EA42D67ED39E58CB7DF9 /* settings.cpp in Compile Sources */, B99CCE43EEFCD3E18F6D16D1 /* settingswidget.cpp in Compile Sources */, - 878DCAF0C5B0DB2EB4EDFD3D /* supporttl.cpp in Compile Sources */, B8DA82DA1B195A933A0805E7 /* sysbuttons.cpp in Compile Sources */, 5CE57D44510AB2A11886AB52 /* title.cpp in Compile Sources */, 4078D5D614EB3ECF7F1848C7 /* types.cpp in Compile Sources */, @@ -1633,7 +1627,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.7.16; + CURRENT_PROJECT_VERSION = 0.7.17; DEBUG_INFORMATION_FORMAT = dwarf; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -1651,7 +1645,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 0.7.16; + CURRENT_PROJECT_VERSION = 0.7.17; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_OPTIMIZATION_LEVEL = fast; GCC_PREFIX_HEADER = ./SourceFiles/stdafx.h; @@ -1677,10 +1671,10 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.7.16; + CURRENT_PROJECT_VERSION = 0.7.17; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_COMPATIBILITY_VERSION = 0.7; - DYLIB_CURRENT_VERSION = 0.7.16; + DYLIB_CURRENT_VERSION = 0.7.17; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ""; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; @@ -1818,10 +1812,10 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.7.16; + CURRENT_PROJECT_VERSION = 0.7.17; DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_COMPATIBILITY_VERSION = 0.7; - DYLIB_CURRENT_VERSION = 0.7.16; + DYLIB_CURRENT_VERSION = 0.7.17; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ""; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; diff --git a/Telegram/Version.sh b/Telegram/Version.sh index c71253925..44d172e2f 100755 --- a/Telegram/Version.sh +++ b/Telegram/Version.sh @@ -1,2 +1,2 @@ -echo 7016 0.7.16 0 +echo 7017 0.7.17 0 # AppVersion AppVersionStr DevChannel diff --git a/Telegram/_qt_5_4_0_patch.diff b/Telegram/_qt_5_4_0_patch.diff index 3f09d4b51..3d7210e5f 100644 --- a/Telegram/_qt_5_4_0_patch.diff +++ b/Telegram/_qt_5_4_0_patch.diff @@ -137,6 +137,31 @@ index 312320c..5e82318 100644 } // Make sure we're inside the viewport. +diff --git a/qtbase/src/gui/text/qtextlayout.cpp b/qtbase/src/gui/text/qtextlayout.cpp +index 1ac50d3..3c88caa 100644 +--- a/qtbase/src/gui/text/qtextlayout.cpp ++++ b/qtbase/src/gui/text/qtextlayout.cpp +@@ -643,7 +643,10 @@ int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const + while (oldPos < len && !attributes[oldPos].graphemeBoundary) + oldPos++; + } else { +- if (oldPos < len && d->atWordSeparator(oldPos)) { ++ while (oldPos < len && d->atSpace(oldPos)) ++ oldPos++; ++ ++ if (oldPos < len && d->atWordSeparator(oldPos)) { + oldPos++; + while (oldPos < len && d->atWordSeparator(oldPos)) + oldPos++; +@@ -651,8 +654,6 @@ int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const + while (oldPos < len && !d->atSpace(oldPos) && !d->atWordSeparator(oldPos)) + oldPos++; + } +- while (oldPos < len && d->atSpace(oldPos)) +- oldPos++; + } + + return oldPos; diff --git a/qtbase/src/gui/text/qtextlayout.h b/qtbase/src/gui/text/qtextlayout.h index 1e0ab9b..47972d3 100644 --- a/qtbase/src/gui/text/qtextlayout.h @@ -647,7 +672,7 @@ index f1f472b..97819dd 100644 { m_data.setSelectedNameFilter(f); // Dialog cannot be updated at run-time. diff --git a/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp b/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp -index ff9ad18..ba423b4 100644 +index ff9ad18..3fd0848 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp +++ b/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp @@ -537,17 +537,16 @@ static inline int toKeyOrUnicode(int vk, int scancode, unsigned char *kbdBuffer, @@ -678,6 +703,18 @@ index ff9ad18..ba423b4 100644 code = unicodeBuffer[0].toUpper().unicode(); // Qt::Key_*'s are not encoded below 0x20, so try again, and DEL keys (0x7f) is encoded with a +@@ -833,7 +832,10 @@ bool QWindowsKeyMapper::translateMultimediaKeyEventInternal(QWindow *window, con + + const int qtKey = CmdTbl[cmd]; + sendExtendedPressRelease(receiver, qtKey, Qt::KeyboardModifier(state), 0, 0, 0); +- return true; ++ // QTBUG-43343: Make sure to return false if Qt does not handle the key, otherwise, ++ // the keys are not passed to the active media player. ++ const QKeySequence sequence(Qt::Modifier(state) + qtKey); ++ return QGuiApplicationPrivate::instance()->shortcutMap.hasShortcutForKeySequence(sequence); + #else + Q_UNREACHABLE(); + return false; diff --git a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp index 8a80729..16fda26 100644 --- a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp diff --git a/Telegram/_qt_5_4_0_patch/qtbase/src/gui/text/qtextlayout.cpp b/Telegram/_qt_5_4_0_patch/qtbase/src/gui/text/qtextlayout.cpp new file mode 100644 index 000000000..3c88caa39 --- /dev/null +++ b/Telegram/_qt_5_4_0_patch/qtbase/src/gui/text/qtextlayout.cpp @@ -0,0 +1,2905 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextlayout.h" +#include "qtextengine_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include "qtextdocument_p.h" +#include "qtextformat_p.h" +#include "qpainterpath.h" +#include "qglyphrun.h" +#include "qglyphrun_p.h" +#include "qrawfont.h" +#include "qrawfont_p.h" +#include + +#include + +#include "qfontengine_p.h" +#include + +QT_BEGIN_NAMESPACE + +#define ObjectSelectionBrush (QTextFormat::ForegroundBrush + 1) +#define SuppressText 0x5012 +#define SuppressBackground 0x513 + +/*! + \class QTextLayout::FormatRange + \reentrant + + \brief The QTextLayout::FormatRange structure is used to apply extra formatting information + for a specified area in the text layout's content. + \inmodule QtGui + + \sa QTextLayout::setAdditionalFormats(), QTextLayout::draw() +*/ + +/*! + \variable QTextLayout::FormatRange::start + Specifies the beginning of the format range within the text layout's text. +*/ + +/*! + \variable QTextLayout::FormatRange::length + Specifies the numer of characters the format range spans. +*/ + +/*! + \variable QTextLayout::FormatRange::format + Specifies the format to apply. +*/ + +/*! + \class QTextInlineObject + \reentrant + + \brief The QTextInlineObject class represents an inline object in + a QAbstractTextDocumentLayout and its implementations. + \inmodule QtGui + + \ingroup richtext-processing + + Normally, you do not need to create a QTextInlineObject. It is + used by QAbstractTextDocumentLayout to handle inline objects when + implementing a custom layout. + + The inline object has various attributes that can be set, for + example using, setWidth(), setAscent(), and setDescent(). The + rectangle it occupies is given by rect(), and its direction by + textDirection(). Its position in the text layout is given by + textPosition(), and its format is given by format(). +*/ + +/*! + \fn QTextInlineObject::QTextInlineObject(int i, QTextEngine *e) + \internal + + Creates a new inline object for the item at position \a i in the + text engine \a e. +*/ + +/*! + \fn QTextInlineObject::QTextInlineObject() + + \internal +*/ + +/*! + \fn bool QTextInlineObject::isValid() const + + Returns \c true if this inline object is valid; otherwise returns + false. +*/ + +/*! + Returns the inline object's rectangle. + + \sa ascent(), descent(), width() +*/ +QRectF QTextInlineObject::rect() const +{ + QScriptItem& si = eng->layoutData->items[itm]; + return QRectF(0, -si.ascent.toReal(), si.width.toReal(), si.height().toReal()); +} + +/*! + Returns the inline object's width. + + \sa ascent(), descent(), rect() +*/ +qreal QTextInlineObject::width() const +{ + return eng->layoutData->items[itm].width.toReal(); +} + +/*! + Returns the inline object's ascent. + + \sa descent(), width(), rect() +*/ +qreal QTextInlineObject::ascent() const +{ + return eng->layoutData->items[itm].ascent.toReal(); +} + +/*! + Returns the inline object's descent. + + \sa ascent(), width(), rect() +*/ +qreal QTextInlineObject::descent() const +{ + return eng->layoutData->items[itm].descent.toReal(); +} + +/*! + Returns the inline object's total height. This is equal to + ascent() + descent() + 1. + + \sa ascent(), descent(), width(), rect() +*/ +qreal QTextInlineObject::height() const +{ + return eng->layoutData->items[itm].height().toReal(); +} + +/*! + Sets the inline object's width to \a w. + + \sa width(), ascent(), descent(), rect() +*/ +void QTextInlineObject::setWidth(qreal w) +{ + eng->layoutData->items[itm].width = QFixed::fromReal(w); +} + +/*! + Sets the inline object's ascent to \a a. + + \sa ascent(), setDescent(), width(), rect() +*/ +void QTextInlineObject::setAscent(qreal a) +{ + eng->layoutData->items[itm].ascent = QFixed::fromReal(a); +} + +/*! + Sets the inline object's descent to \a d. + + \sa descent(), setAscent(), width(), rect() +*/ +void QTextInlineObject::setDescent(qreal d) +{ + eng->layoutData->items[itm].descent = QFixed::fromReal(d); +} + +/*! + The position of the inline object within the text layout. +*/ +int QTextInlineObject::textPosition() const +{ + return eng->layoutData->items[itm].position; +} + +/*! + Returns an integer describing the format of the inline object + within the text layout. +*/ +int QTextInlineObject::formatIndex() const +{ + return eng->formatIndex(&eng->layoutData->items[itm]); +} + +/*! + Returns format of the inline object within the text layout. +*/ +QTextFormat QTextInlineObject::format() const +{ + return eng->format(&eng->layoutData->items[itm]); +} + +/*! + Returns if the object should be laid out right-to-left or left-to-right. +*/ +Qt::LayoutDirection QTextInlineObject::textDirection() const +{ + return (eng->layoutData->items[itm].analysis.bidiLevel % 2 ? Qt::RightToLeft : Qt::LeftToRight); +} + +/*! + \class QTextLayout + \reentrant + + \brief The QTextLayout class is used to lay out and render text. + \inmodule QtGui + + \ingroup richtext-processing + + It offers many features expected from a modern text layout + engine, including Unicode compliant rendering, line breaking and + handling of cursor positioning. It can also produce and render + device independent layout, something that is important for WYSIWYG + applications. + + The class has a rather low level API and unless you intend to + implement your own text rendering for some specialized widget, you + probably won't need to use it directly. + + QTextLayout can be used with both plain and rich text. + + QTextLayout can be used to create a sequence of QTextLine + instances with given widths and can position them independently + on the screen. Once the layout is done, these lines can be drawn + on a paint device. + + The text to be laid out can be provided in the constructor or set with + setText(). + + The layout can be seen as a sequence of QTextLine objects; use createLine() + to create a QTextLine instance, and lineAt() or lineForTextPosition() to retrieve + created lines. + + Here is a code snippet that demonstrates the layout phase: + \snippet code/src_gui_text_qtextlayout.cpp 0 + + The text can then be rendered by calling the layout's draw() function: + \snippet code/src_gui_text_qtextlayout.cpp 1 + + For a given position in the text you can find a valid cursor position with + isValidCursorPosition(), nextCursorPosition(), and previousCursorPosition(). + + The QTextLayout itself can be positioned with setPosition(); it has a + boundingRect(), and a minimumWidth() and a maximumWidth(). + + \sa QStaticText +*/ + +/*! + \enum QTextLayout::CursorMode + + \value SkipCharacters + \value SkipWords +*/ + +/*! + \fn QTextEngine *QTextLayout::engine() const + \internal + + Returns the text engine used to render the text layout. +*/ + +/*! + Constructs an empty text layout. + + \sa setText() +*/ +QTextLayout::QTextLayout() +{ d = new QTextEngine(); } + +/*! + Constructs a text layout to lay out the given \a text. +*/ +QTextLayout::QTextLayout(const QString& text) +{ + d = new QTextEngine(); + d->text = text; +} + +/*! + Constructs a text layout to lay out the given \a text with the specified + \a font. + + All the metric and layout calculations will be done in terms of + the paint device, \a paintdevice. If \a paintdevice is 0 the + calculations will be done in screen metrics. +*/ +QTextLayout::QTextLayout(const QString& text, const QFont &font, QPaintDevice *paintdevice) +{ + QFont f(font); + if (paintdevice) + f = QFont(font, paintdevice); + d = new QTextEngine((text.isNull() ? (const QString&)QString::fromLatin1("") : text), f); +} + +/*! + \internal + Constructs a text layout to lay out the given \a block. +*/ +QTextLayout::QTextLayout(const QTextBlock &block) +{ + d = new QTextEngine(); + d->block = block; +} + +/*! + Destructs the layout. +*/ +QTextLayout::~QTextLayout() +{ + if (!d->stackEngine) + delete d; +} + +#ifndef QT_NO_RAWFONT +/*! + \internal + Sets a raw font, to be used with QTextLayout::glyphRuns. + Note that this only supports the needs of WebKit. + Use of this function with e.g. QTextLayout::draw will result + in undefined behaviour. +*/ +void QTextLayout::setRawFont(const QRawFont &rawFont) +{ + d->rawFont = rawFont; + d->useRawFont = true; + d->resetFontEngineCache(); +} +#endif + +/*! + Sets the layout's font to the given \a font. The layout is + invalidated and must be laid out again. + + \sa font() +*/ +void QTextLayout::setFont(const QFont &font) +{ + d->fnt = font; +#ifndef QT_NO_RAWFONT + d->useRawFont = false; +#endif + d->resetFontEngineCache(); +} + +/*! + Returns the current font that is used for the layout, or a default + font if none is set. + + \sa setFont() +*/ +QFont QTextLayout::font() const +{ + return d->font(); +} + +/*! + Sets the layout's text to the given \a string. The layout is + invalidated and must be laid out again. + + Notice that when using this QTextLayout as part of a QTextDocument this + method will have no effect. + + \sa text() +*/ +void QTextLayout::setText(const QString& string) +{ + d->invalidate(); + d->clearLineData(); + d->text = string; +} + +/*! + Returns the layout's text. + + \sa setText() +*/ +QString QTextLayout::text() const +{ + return d->text; +} + +/*! + Sets the text option structure that controls the layout process to the + given \a option. + + \sa textOption() +*/ +void QTextLayout::setTextOption(const QTextOption &option) +{ + d->option = option; +} + +/*! + Returns the current text option used to control the layout process. + + \sa setTextOption() +*/ +const QTextOption &QTextLayout::textOption() const +{ + return d->option; +} + +/*! + Sets the \a position and \a text of the area in the layout that is + processed before editing occurs. The layout is + invalidated and must be laid out again. + + \sa preeditAreaPosition(), preeditAreaText() +*/ +void QTextLayout::setPreeditArea(int position, const QString &text) +{ + if (d->preeditAreaPosition() == position && d->preeditAreaText() == text) + return; + d->setPreeditArea(position, text); + + if (d->block.docHandle()) + d->block.docHandle()->documentChange(d->block.position(), d->block.length()); +} + +/*! + Returns the position of the area in the text layout that will be + processed before editing occurs. + + \sa preeditAreaText() +*/ +int QTextLayout::preeditAreaPosition() const +{ + return d->preeditAreaPosition(); +} + +/*! + Returns the text that is inserted in the layout before editing occurs. + + \sa preeditAreaPosition() +*/ +QString QTextLayout::preeditAreaText() const +{ + return d->preeditAreaText(); +} + + +/*! + Sets the additional formats supported by the text layout to \a formatList. + The formats are applied with preedit area text in place. + + \sa additionalFormats(), clearAdditionalFormats() +*/ +void QTextLayout::setAdditionalFormats(const QList &formatList) +{ + d->setFormats(formatList); + + if (d->block.docHandle()) + d->block.docHandle()->documentChange(d->block.position(), d->block.length()); +} + +/*! + Returns the list of additional formats supported by the text layout. + + \sa setAdditionalFormats(), clearAdditionalFormats() +*/ +QList QTextLayout::additionalFormats() const +{ + return d->formats(); +} + +/*! + Clears the list of additional formats supported by the text layout. + + \sa additionalFormats(), setAdditionalFormats() +*/ +void QTextLayout::clearAdditionalFormats() +{ + setAdditionalFormats(QList()); +} + +/*! + Enables caching of the complete layout information if \a enable is + true; otherwise disables layout caching. Usually + QTextLayout throws most of the layouting information away after a + call to endLayout() to reduce memory consumption. If you however + want to draw the laid out text directly afterwards enabling caching + might speed up drawing significantly. + + \sa cacheEnabled() +*/ +void QTextLayout::setCacheEnabled(bool enable) +{ + d->cacheGlyphs = enable; +} + +/*! + Returns \c true if the complete layout information is cached; otherwise + returns \c false. + + \sa setCacheEnabled() +*/ +bool QTextLayout::cacheEnabled() const +{ + return d->cacheGlyphs; +} + +/*! + Sets the visual cursor movement style to the given \a style. If the + QTextLayout is backed by a document, you can ignore this and use the option + in QTextDocument, this option is for widgets like QLineEdit or custom + widgets without a QTextDocument. Default value is Qt::LogicalMoveStyle. + + \sa cursorMoveStyle() +*/ +void QTextLayout::setCursorMoveStyle(Qt::CursorMoveStyle style) +{ + d->visualMovement = style == Qt::VisualMoveStyle; +} + +/*! + The cursor movement style of this QTextLayout. The default is + Qt::LogicalMoveStyle. + + \sa setCursorMoveStyle() +*/ +Qt::CursorMoveStyle QTextLayout::cursorMoveStyle() const +{ + return d->visualMovement ? Qt::VisualMoveStyle : Qt::LogicalMoveStyle; +} + +/*! + Begins the layout process. + + \sa endLayout() +*/ +void QTextLayout::beginLayout() +{ +#ifndef QT_NO_DEBUG + if (d->layoutData && d->layoutData->layoutState == QTextEngine::InLayout) { + qWarning("QTextLayout::beginLayout: Called while already doing layout"); + return; + } +#endif + d->invalidate(); + d->clearLineData(); + d->itemize(); + d->layoutData->layoutState = QTextEngine::InLayout; +} + +/*! + Ends the layout process. + + \sa beginLayout() +*/ +void QTextLayout::endLayout() +{ +#ifndef QT_NO_DEBUG + if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) { + qWarning("QTextLayout::endLayout: Called without beginLayout()"); + return; + } +#endif + int l = d->lines.size(); + if (l && d->lines.at(l-1).length < 0) { + QTextLine(l-1, d).setNumColumns(INT_MAX); + } + d->layoutData->layoutState = QTextEngine::LayoutEmpty; + if (!d->cacheGlyphs) + d->freeMemory(); +} + +/*! + \since 4.4 + + Clears the line information in the layout. After having called + this function, lineCount() returns 0. +*/ +void QTextLayout::clearLayout() +{ + d->clearLineData(); +} + +/*! + Returns the next valid cursor position after \a oldPos that + respects the given cursor \a mode. + Returns value of \a oldPos, if \a oldPos is not a valid cursor position. + + \sa isValidCursorPosition(), previousCursorPosition() +*/ +int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const +{ + const QCharAttributes *attributes = d->attributes(); + int len = d->block.isValid() ? d->block.length() - 1 + : d->layoutData->string.length(); + Q_ASSERT(len <= d->layoutData->string.length()); + if (!attributes || oldPos < 0 || oldPos >= len) + return oldPos; + + if (mode == SkipCharacters) { + oldPos++; + while (oldPos < len && !attributes[oldPos].graphemeBoundary) + oldPos++; + } else { + while (oldPos < len && d->atSpace(oldPos)) + oldPos++; + + if (oldPos < len && d->atWordSeparator(oldPos)) { + oldPos++; + while (oldPos < len && d->atWordSeparator(oldPos)) + oldPos++; + } else { + while (oldPos < len && !d->atSpace(oldPos) && !d->atWordSeparator(oldPos)) + oldPos++; + } + } + + return oldPos; +} + +/*! + Returns the first valid cursor position before \a oldPos that + respects the given cursor \a mode. + Returns value of \a oldPos, if \a oldPos is not a valid cursor position. + + \sa isValidCursorPosition(), nextCursorPosition() +*/ +int QTextLayout::previousCursorPosition(int oldPos, CursorMode mode) const +{ + const QCharAttributes *attributes = d->attributes(); + int len = d->block.isValid() ? d->block.length() - 1 + : d->layoutData->string.length(); + Q_ASSERT(len <= d->layoutData->string.length()); + if (!attributes || oldPos <= 0 || oldPos > len) + return oldPos; + + if (mode == SkipCharacters) { + oldPos--; + while (oldPos && !attributes[oldPos].graphemeBoundary) + oldPos--; + } else { + while (oldPos && d->atSpace(oldPos-1)) + oldPos--; + + if (oldPos && d->atWordSeparator(oldPos-1)) { + oldPos--; + while (oldPos && d->atWordSeparator(oldPos-1)) + oldPos--; + } else { + while (oldPos && !d->atSpace(oldPos-1) && !d->atWordSeparator(oldPos-1)) + oldPos--; + } + } + + return oldPos; +} + +/*! + Returns the cursor position to the right of \a oldPos, next to it. + It's dependent on the visual position of characters, after bi-directional + reordering. + + \sa leftCursorPosition(), nextCursorPosition() +*/ +int QTextLayout::rightCursorPosition(int oldPos) const +{ + int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Right); +// qDebug("%d -> %d", oldPos, newPos); + return newPos; +} + +/*! + Returns the cursor position to the left of \a oldPos, next to it. + It's dependent on the visual position of characters, after bi-directional + reordering. + + \sa rightCursorPosition(), previousCursorPosition() +*/ +int QTextLayout::leftCursorPosition(int oldPos) const +{ + int newPos = d->positionAfterVisualMovement(oldPos, QTextCursor::Left); +// qDebug("%d -> %d", oldPos, newPos); + return newPos; +} + +/*!/ + Returns \c true if position \a pos is a valid cursor position. + + In a Unicode context some positions in the text are not valid + cursor positions, because the position is inside a Unicode + surrogate or a grapheme cluster. + + A grapheme cluster is a sequence of two or more Unicode characters + that form one indivisible entity on the screen. For example the + latin character `\unicode{0xC4}' can be represented in Unicode by two + characters, `A' (0x41), and the combining diaresis (0x308). A text + cursor can only validly be positioned before or after these two + characters, never between them since that wouldn't make sense. In + indic languages every syllable forms a grapheme cluster. +*/ +bool QTextLayout::isValidCursorPosition(int pos) const +{ + const QCharAttributes *attributes = d->attributes(); + if (!attributes || pos < 0 || pos > (int)d->layoutData->string.length()) + return false; + return attributes[pos].graphemeBoundary; +} + +/*! + Returns a new text line to be laid out if there is text to be + inserted into the layout; otherwise returns an invalid text line. + + The text layout creates a new line object that starts after the + last line in the layout, or at the beginning if the layout is empty. + The layout maintains an internal cursor, and each line is filled + with text from the cursor position onwards when the + QTextLine::setLineWidth() function is called. + + Once QTextLine::setLineWidth() is called, a new line can be created and + filled with text. Repeating this process will lay out the whole block + of text contained in the QTextLayout. If there is no text left to be + inserted into the layout, the QTextLine returned will not be valid + (isValid() will return false). +*/ +QTextLine QTextLayout::createLine() +{ +#ifndef QT_NO_DEBUG + if (!d->layoutData || d->layoutData->layoutState == QTextEngine::LayoutEmpty) { + qWarning("QTextLayout::createLine: Called without layouting"); + return QTextLine(); + } +#endif + if (d->layoutData->layoutState == QTextEngine::LayoutFailed) + return QTextLine(); + + int l = d->lines.size(); + if (l && d->lines.at(l-1).length < 0) { + QTextLine(l-1, d).setNumColumns(INT_MAX); + } + int from = l > 0 ? d->lines.at(l-1).from + d->lines.at(l-1).length + d->lines.at(l-1).trailingSpaces : 0; + int strlen = d->layoutData->string.length(); + if (l && from >= strlen) { + if (!d->lines.at(l-1).length || d->layoutData->string.at(strlen - 1) != QChar::LineSeparator) + return QTextLine(); + } + + QScriptLine line; + line.from = from; + line.length = -1; + line.justified = false; + line.gridfitted = false; + + d->lines.append(line); + return QTextLine(l, d); +} + +/*! + Returns the number of lines in this text layout. + + \sa lineAt() +*/ +int QTextLayout::lineCount() const +{ + return d->lines.size(); +} + +/*! + Returns the \a{i}-th line of text in this text layout. + + \sa lineCount(), lineForTextPosition() +*/ +QTextLine QTextLayout::lineAt(int i) const +{ + return i < lineCount() ? QTextLine(i, d) : QTextLine(); +} + +/*! + Returns the line that contains the cursor position specified by \a pos. + + \sa isValidCursorPosition(), lineAt() +*/ +QTextLine QTextLayout::lineForTextPosition(int pos) const +{ + int lineNum = d->lineNumberForTextPosition(pos); + return lineNum >= 0 ? lineAt(lineNum) : QTextLine(); +} + +/*! + \since 4.2 + + The global position of the layout. This is independent of the + bounding rectangle and of the layout process. + + \sa setPosition() +*/ +QPointF QTextLayout::position() const +{ + return d->position; +} + +/*! + Moves the text layout to point \a p. + + \sa position() +*/ +void QTextLayout::setPosition(const QPointF &p) +{ + d->position = p; +} + +/*! + The smallest rectangle that contains all the lines in the layout. +*/ +QRectF QTextLayout::boundingRect() const +{ + if (d->lines.isEmpty()) + return QRectF(); + + QFixed xmax, ymax; + QFixed xmin = d->lines.at(0).x; + QFixed ymin = d->lines.at(0).y; + + for (int i = 0; i < d->lines.size(); ++i) { + const QScriptLine &si = d->lines[i]; + xmin = qMin(xmin, si.x); + ymin = qMin(ymin, si.y); + QFixed lineWidth = si.width < QFIXED_MAX ? qMax(si.width, si.textWidth) : si.textWidth; + xmax = qMax(xmax, si.x+lineWidth); + // ### shouldn't the ascent be used in ymin??? + ymax = qMax(ymax, si.y+si.height().ceil()); + } + return QRectF(xmin.toReal(), ymin.toReal(), (xmax-xmin).toReal(), (ymax-ymin).toReal()); +} + +/*! + The minimum width the layout needs. This is the width of the + layout's smallest non-breakable substring. + + \warning This function only returns a valid value after the layout + has been done. + + \sa maximumWidth() +*/ +qreal QTextLayout::minimumWidth() const +{ + return d->minWidth.toReal(); +} + +/*! + The maximum width the layout could expand to; this is essentially + the width of the entire text. + + \warning This function only returns a valid value after the layout + has been done. + + \sa minimumWidth() +*/ +qreal QTextLayout::maximumWidth() const +{ + return d->maxWidth.toReal(); +} + + +/*! + \internal +*/ +void QTextLayout::setFlags(int flags) +{ + if (flags & Qt::TextJustificationForced) { + d->option.setAlignment(Qt::AlignJustify); + d->forceJustification = true; + } + + if (flags & (Qt::TextForceLeftToRight|Qt::TextForceRightToLeft)) { + d->ignoreBidi = true; + d->option.setTextDirection((flags & Qt::TextForceLeftToRight) ? Qt::LeftToRight : Qt::RightToLeft); + } +} + +static void addSelectedRegionsToPath(QTextEngine *eng, int lineNumber, const QPointF &pos, QTextLayout::FormatRange *selection, + QPainterPath *region, QRectF boundingRect) +{ + const QScriptLine &line = eng->lines[lineNumber]; + + QTextLineItemIterator iterator(eng, lineNumber, pos, selection); + + + + const qreal selectionY = pos.y() + line.y.toReal(); + const qreal lineHeight = line.height().toReal(); + + QFixed lastSelectionX = iterator.x; + QFixed lastSelectionWidth; + + while (!iterator.atEnd()) { + iterator.next(); + + QFixed selectionX, selectionWidth; + if (iterator.getSelectionBounds(&selectionX, &selectionWidth)) { + if (selectionX == lastSelectionX + lastSelectionWidth) { + lastSelectionWidth += selectionWidth; + continue; + } + + if (lastSelectionWidth > 0) { + QRectF rect = boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight); + rect.moveLeft(qFloor(rect.left())); + rect.moveTop(qFloor(rect.top())); + region->addRect(rect); + } + + lastSelectionX = selectionX; + lastSelectionWidth = selectionWidth; + } + } + if (lastSelectionWidth > 0) { + QRectF rect = boundingRect & QRectF(lastSelectionX.toReal(), selectionY, lastSelectionWidth.toReal(), lineHeight); + rect.moveLeft(qFloor(rect.left())); + rect.moveTop(qFloor(rect.top())); + region->addRect(rect); + } +} + +static inline QRectF clipIfValid(const QRectF &rect, const QRectF &clip) +{ + return clip.isValid() ? (rect & clip) : rect; +} + + +/*! + Returns the glyph indexes and positions for all glyphs corresponding to the \a length characters + starting at the position \a from in this QTextLayout. This is an expensive function, and should + not be called in a time sensitive context. + + If \a from is less than zero, then the glyph run will begin at the first character in the + layout. If \a length is less than zero, it will span the entire string from the start position. + + \since 4.8 + + \sa draw(), QPainter::drawGlyphRun() +*/ +#if !defined(QT_NO_RAWFONT) +QList QTextLayout::glyphRuns(int from, int length) const +{ + if (from < 0) + from = 0; + if (length < 0) + length = text().length(); + + QHash, QGlyphRun> glyphRunHash; + for (int i=0; ilines.size(); ++i) { + if (d->lines[i].from > from + length) + break; + else if (d->lines[i].from + d->lines[i].length >= from) { + QList glyphRuns = QTextLine(i, d).glyphRuns(from, length); + + for (int j = 0; j < glyphRuns.size(); j++) { + const QGlyphRun &glyphRun = glyphRuns.at(j); + QRawFont rawFont = glyphRun.rawFont(); + + QFontEngine *fontEngine = rawFont.d->fontEngine; + QGlyphRun::GlyphRunFlags flags = glyphRun.flags(); + QPair key(fontEngine, int(flags)); + // merge the glyph runs using the same font + if (glyphRunHash.contains(key)) { + QGlyphRun &oldGlyphRun = glyphRunHash[key]; + + QVector indexes = oldGlyphRun.glyphIndexes(); + QVector positions = oldGlyphRun.positions(); + + indexes += glyphRun.glyphIndexes(); + positions += glyphRun.positions(); + + oldGlyphRun.setGlyphIndexes(indexes); + oldGlyphRun.setPositions(positions); + } else { + glyphRunHash[key] = glyphRun; + } + } + } + } + + return glyphRunHash.values(); +} +#endif // QT_NO_RAWFONT + +/*! + Draws the whole layout on the painter \a p at the position specified by \a pos. + The rendered layout includes the given \a selections and is clipped within + the rectangle specified by \a clip. +*/ +void QTextLayout::draw(QPainter *p, const QPointF &pos, const QVector &selections, const QRectF &clip) const +{ + if (d->lines.isEmpty()) + return; + + if (!d->layoutData) + d->itemize(); + + QPointF position = pos + d->position; + + QFixed clipy = (INT_MIN/256); + QFixed clipe = (INT_MAX/256); + if (clip.isValid()) { + clipy = QFixed::fromReal(clip.y() - position.y()); + clipe = clipy + QFixed::fromReal(clip.height()); + } + + int firstLine = 0; + int lastLine = d->lines.size(); + for (int i = 0; i < d->lines.size(); ++i) { + QTextLine l(i, d); + const QScriptLine &sl = d->lines[i]; + + if (sl.y > clipe) { + lastLine = i; + break; + } + if ((sl.y + sl.height()) < clipy) { + firstLine = i; + continue; + } + } + + QPainterPath excludedRegion; + QPainterPath textDoneRegion; + for (int i = 0; i < selections.size(); ++i) { + FormatRange selection = selections.at(i); + const QBrush bg = selection.format.background(); + + QPainterPath region; + region.setFillRule(Qt::WindingFill); + + for (int line = firstLine; line < lastLine; ++line) { + const QScriptLine &sl = d->lines[line]; + QTextLine tl(line, d); + + QRectF lineRect(tl.naturalTextRect()); + lineRect.translate(position); + lineRect.adjust(0, 0, d->leadingSpaceWidth(sl).toReal(), 0); + + bool isLastLineInBlock = (line == d->lines.size()-1); + int sl_length = sl.length + (isLastLineInBlock? 1 : 0); // the infamous newline + + + if (sl.from > selection.start + selection.length || sl.from + sl_length <= selection.start) + continue; // no actual intersection + + const bool selectionStartInLine = sl.from <= selection.start; + const bool selectionEndInLine = selection.start + selection.length < sl.from + sl_length; + + if (sl.length && (selectionStartInLine || selectionEndInLine)) { + addSelectedRegionsToPath(d, line, position, &selection, ®ion, clipIfValid(lineRect, clip)); + } else { + region.addRect(clipIfValid(lineRect, clip)); + } + + if (selection.format.boolProperty(QTextFormat::FullWidthSelection)) { + QRectF fullLineRect(tl.rect()); + fullLineRect.translate(position); + fullLineRect.setRight(QFIXED_MAX); + if (!selectionEndInLine) + region.addRect(clipIfValid(QRectF(lineRect.topRight(), fullLineRect.bottomRight()), clip)); + if (!selectionStartInLine) + region.addRect(clipIfValid(QRectF(fullLineRect.topLeft(), lineRect.bottomLeft()), clip)); + } else if (!selectionEndInLine + && isLastLineInBlock + &&!(d->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) { + region.addRect(clipIfValid(QRectF(lineRect.right(), lineRect.top(), + lineRect.height()/4, lineRect.height()), clip)); + } + + } + { + const QPen oldPen = p->pen(); + const QBrush oldBrush = p->brush(); + + p->setPen(selection.format.penProperty(QTextFormat::OutlinePen)); + p->setBrush(selection.format.brushProperty(QTextFormat::BackgroundBrush)); + p->drawPath(region); + + p->setPen(oldPen); + p->setBrush(oldBrush); + } + + + + bool hasText = (selection.format.foreground().style() != Qt::NoBrush); + bool hasBackground= (selection.format.background().style() != Qt::NoBrush); + + if (hasBackground) { + selection.format.setProperty(ObjectSelectionBrush, selection.format.property(QTextFormat::BackgroundBrush)); + // don't just clear the property, set an empty brush that overrides a potential + // background brush specified in the text + selection.format.setProperty(QTextFormat::BackgroundBrush, QBrush()); + selection.format.clearProperty(QTextFormat::OutlinePen); + } + + selection.format.setProperty(SuppressText, !hasText); + + if (hasText && !hasBackground && !(textDoneRegion & region).isEmpty()) + continue; + + p->save(); + p->setClipPath(region, Qt::IntersectClip); + + for (int line = firstLine; line < lastLine; ++line) { + QTextLine l(line, d); + l.draw(p, position, &selection); + } + p->restore(); + + if (hasText) { + textDoneRegion += region; + } else { + if (hasBackground) + textDoneRegion -= region; + } + + excludedRegion += region; + } + + QPainterPath needsTextButNoBackground = excludedRegion - textDoneRegion; + if (!needsTextButNoBackground.isEmpty()){ + p->save(); + p->setClipPath(needsTextButNoBackground, Qt::IntersectClip); + FormatRange selection; + selection.start = 0; + selection.length = INT_MAX; + selection.format.setProperty(SuppressBackground, true); + for (int line = firstLine; line < lastLine; ++line) { + QTextLine l(line, d); + l.draw(p, position, &selection); + } + p->restore(); + } + + if (!excludedRegion.isEmpty()) { + p->save(); + QPainterPath path; + QRectF br = boundingRect().translated(position); + br.setRight(QFIXED_MAX); + if (!clip.isNull()) + br = br.intersected(clip); + path.addRect(br); + path -= excludedRegion; + p->setClipPath(path, Qt::IntersectClip); + } + + for (int i = firstLine; i < lastLine; ++i) { + QTextLine l(i, d); + l.draw(p, position); + } + if (!excludedRegion.isEmpty()) + p->restore(); + + + if (!d->cacheGlyphs) + d->freeMemory(); +} + +/*! + \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition) const + \overload + + Draws a text cursor with the current pen at the given \a position using the + \a painter specified. + The corresponding position within the text is specified by \a cursorPosition. +*/ +void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition) const +{ + drawCursor(p, pos, cursorPosition, 1); +} + +/*! + \fn void QTextLayout::drawCursor(QPainter *painter, const QPointF &position, int cursorPosition, int width) const + + Draws a text cursor with the current pen and the specified \a width at the given \a position using the + \a painter specified. + The corresponding position within the text is specified by \a cursorPosition. +*/ +void QTextLayout::drawCursor(QPainter *p, const QPointF &pos, int cursorPosition, int width) const +{ + if (d->lines.isEmpty()) + return; + + if (!d->layoutData) + d->itemize(); + + QPointF position = pos + d->position; + + cursorPosition = qBound(0, cursorPosition, d->layoutData->string.length()); + int line = d->lineNumberForTextPosition(cursorPosition); + if (line < 0) + line = 0; + if (line >= d->lines.size()) + return; + + QTextLine l(line, d); + const QScriptLine &sl = d->lines[line]; + + qreal x = position.x() + l.cursorToX(cursorPosition); + + int itm; + + if (d->visualCursorMovement()) { + if (cursorPosition == sl.from + sl.length) + cursorPosition--; + itm = d->findItem(cursorPosition); + } else + itm = d->findItem(cursorPosition - 1); + + QFixed base = sl.base(); + QFixed descent = sl.descent; + bool rightToLeft = d->isRightToLeft(); + if (itm >= 0) { + const QScriptItem &si = d->layoutData->items.at(itm); + if (si.ascent > 0) + base = si.ascent; + if (si.descent > 0) + descent = si.descent; + rightToLeft = si.analysis.bidiLevel % 2; + } + qreal y = position.y() + (sl.y + sl.base() - base).toReal(); + bool toggleAntialiasing = !(p->renderHints() & QPainter::Antialiasing) + && (p->transform().type() > QTransform::TxTranslate); + if (toggleAntialiasing) + p->setRenderHint(QPainter::Antialiasing); + p->fillRect(QRectF(x, y, qreal(width), (base + descent).toReal()), p->pen().brush()); + if (toggleAntialiasing) + p->setRenderHint(QPainter::Antialiasing, false); + if (d->layoutData->hasBidi) { + const int arrow_extent = 4; + int sign = rightToLeft ? -1 : 1; + p->drawLine(QLineF(x, y, x + (sign * arrow_extent/2), y + arrow_extent/2)); + p->drawLine(QLineF(x, y+arrow_extent, x + (sign * arrow_extent/2), y + arrow_extent/2)); + } + return; +} + +/*! + \class QTextLine + \reentrant + + \brief The QTextLine class represents a line of text inside a QTextLayout. + \inmodule QtGui + + \ingroup richtext-processing + + A text line is usually created by QTextLayout::createLine(). + + After being created, the line can be filled using the setLineWidth() + or setNumColumns() functions. A line has a number of attributes including the + rectangle it occupies, rect(), its coordinates, x() and y(), its + textLength(), width() and naturalTextWidth(), and its ascent() and descent() + relative to the text. The position of the cursor in terms of the + line is available from cursorToX() and its inverse from + xToCursor(). A line can be moved with setPosition(). +*/ + +/*! + \enum QTextLine::Edge + + \value Leading + \value Trailing +*/ + +/*! + \enum QTextLine::CursorPosition + + \value CursorBetweenCharacters + \value CursorOnCharacter +*/ + +/*! + \fn QTextLine::QTextLine(int line, QTextEngine *e) + \internal + + Constructs a new text line using the line at position \a line in + the text engine \a e. +*/ + +/*! + \fn QTextLine::QTextLine() + + Creates an invalid line. +*/ + +/*! + \fn bool QTextLine::isValid() const + + Returns \c true if this text line is valid; otherwise returns \c false. +*/ + +/*! + \fn int QTextLine::lineNumber() const + + Returns the position of the line in the text engine. +*/ + + +/*! + Returns the line's bounding rectangle. + + \sa x(), y(), textLength(), width() +*/ +QRectF QTextLine::rect() const +{ + const QScriptLine& sl = eng->lines[index]; + return QRectF(sl.x.toReal(), sl.y.toReal(), sl.width.toReal(), sl.height().toReal()); +} + +/*! + Returns the rectangle covered by the line. +*/ +QRectF QTextLine::naturalTextRect() const +{ + const QScriptLine& sl = eng->lines[index]; + QFixed x = sl.x + eng->alignLine(sl); + + QFixed width = sl.textWidth; + if (sl.justified) + width = sl.width; + + return QRectF(x.toReal(), sl.y.toReal(), width.toReal(), sl.height().toReal()); +} + +/*! + Returns the line's x position. + + \sa rect(), y(), textLength(), width() +*/ +qreal QTextLine::x() const +{ + return eng->lines[index].x.toReal(); +} + +/*! + Returns the line's y position. + + \sa x(), rect(), textLength(), width() +*/ +qreal QTextLine::y() const +{ + return eng->lines[index].y.toReal(); +} + +/*! + Returns the line's width as specified by the layout() function. + + \sa naturalTextWidth(), x(), y(), textLength(), rect() +*/ +qreal QTextLine::width() const +{ + return eng->lines[index].width.toReal(); +} + + +/*! + Returns the line's ascent. + + \sa descent(), height() +*/ +qreal QTextLine::ascent() const +{ + return eng->lines[index].ascent.toReal(); +} + +/*! + Returns the line's descent. + + \sa ascent(), height() +*/ +qreal QTextLine::descent() const +{ + return eng->lines[index].descent.toReal(); +} + +/*! + Returns the line's height. This is equal to ascent() + descent() + if leading is not included. If leading is included, this equals to + ascent() + descent() + leading(). + + \sa ascent(), descent(), leading(), setLeadingIncluded() +*/ +qreal QTextLine::height() const +{ + return eng->lines[index].height().ceil().toReal(); +} + +/*! + \since 4.6 + + Returns the line's leading. + + \sa ascent(), descent(), height() +*/ +qreal QTextLine::leading() const +{ + return eng->lines[index].leading.toReal(); +} + +/*! + \since 4.6 + + Includes positive leading into the line's height if \a included is true; + otherwise does not include leading. + + By default, leading is not included. + + Note that negative leading is ignored, it must be handled + in the code using the text lines by letting the lines overlap. + + \sa leadingIncluded() + +*/ +void QTextLine::setLeadingIncluded(bool included) +{ + eng->lines[index].leadingIncluded= included; + +} + +/*! + \since 4.6 + + Returns \c true if positive leading is included into the line's height; + otherwise returns \c false. + + By default, leading is not included. + + \sa setLeadingIncluded() +*/ +bool QTextLine::leadingIncluded() const +{ + return eng->lines[index].leadingIncluded; +} + +/*! + Returns the width of the line that is occupied by text. This is + always \<= to width(), and is the minimum width that could be used + by layout() without changing the line break position. +*/ +qreal QTextLine::naturalTextWidth() const +{ + return eng->lines[index].textWidth.toReal(); +} + +/*! + \since 4.7 + Returns the horizontal advance of the text. The advance of the text + is the distance from its position to the next position at which + text would naturally be drawn. + + By adding the advance to the position of the text line and using this + as the position of a second text line, you will be able to position + the two lines side-by-side without gaps in-between. +*/ +qreal QTextLine::horizontalAdvance() const +{ + return eng->lines[index].textAdvance.toReal(); +} + +/*! + Lays out the line with the given \a width. The line is filled from + its starting position with as many characters as will fit into + the line. In case the text cannot be split at the end of the line, + it will be filled with additional characters to the next whitespace + or end of the text. +*/ +void QTextLine::setLineWidth(qreal width) +{ + QScriptLine &line = eng->lines[index]; + if (!eng->layoutData) { + qWarning("QTextLine: Can't set a line width while not layouting."); + return; + } + + if (width > QFIXED_MAX) + width = QFIXED_MAX; + + line.width = QFixed::fromReal(width); + if (line.length + && line.textWidth <= line.width + && line.from + line.length == eng->layoutData->string.length()) + // no need to do anything if the line is already layouted and the last one. This optimization helps + // when using things in a single line layout. + return; + line.length = 0; + line.textWidth = 0; + + layout_helper(INT_MAX); +} + +/*! + Lays out the line. The line is filled from its starting position + with as many characters as are specified by \a numColumns. In case + the text cannot be split until \a numColumns characters, the line + will be filled with as many characters to the next whitespace or + end of the text. +*/ +void QTextLine::setNumColumns(int numColumns) +{ + QScriptLine &line = eng->lines[index]; + line.width = QFIXED_MAX; + line.length = 0; + line.textWidth = 0; + layout_helper(numColumns); +} + +/*! + Lays out the line. The line is filled from its starting position + with as many characters as are specified by \a numColumns. In case + the text cannot be split until \a numColumns characters, the line + will be filled with as many characters to the next whitespace or + end of the text. The provided \a alignmentWidth is used as reference + width for alignment. +*/ +void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth) +{ + QScriptLine &line = eng->lines[index]; + line.width = QFixed::fromReal(alignmentWidth); + line.length = 0; + line.textWidth = 0; + layout_helper(numColumns); +} + +#if 0 +#define LB_DEBUG qDebug +#else +#define LB_DEBUG if (0) qDebug +#endif + +namespace { + + struct LineBreakHelper + { + LineBreakHelper() + : glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(0), logClusters(0), + manualWrap(false), whiteSpaceOrObject(true) + { + } + + + QScriptLine tmpData; + QScriptLine spaceData; + + QGlyphLayout glyphs; + + int glyphCount; + int maxGlyphs; + int currentPosition; + glyph_t previousGlyph; + + QFixed minw; + QFixed softHyphenWidth; + QFixed rightBearing; + QFixed minimumRightBearing; + + QFontEngine *fontEngine; + const unsigned short *logClusters; + + bool manualWrap; + bool whiteSpaceOrObject; + + bool checkFullOtherwiseExtend(QScriptLine &line); + + QFixed calculateNewWidth(const QScriptLine &line) const { + return line.textWidth + tmpData.textWidth + spaceData.textWidth + softHyphenWidth + - qMin(rightBearing, QFixed()); + } + + inline glyph_t currentGlyph() const + { + Q_ASSERT(currentPosition > 0); + Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs); + + return glyphs.glyphs[logClusters[currentPosition - 1]]; + } + + inline void saveCurrentGlyph() + { + previousGlyph = 0; + if (currentPosition > 0 && + logClusters[currentPosition - 1] < glyphs.numGlyphs) { + previousGlyph = currentGlyph(); // needed to calculate right bearing later + } + } + + inline void adjustRightBearing(glyph_t glyph) + { + qreal rb; + fontEngine->getGlyphBearings(glyph, 0, &rb); + rightBearing = qMin(QFixed(), QFixed::fromReal(rb)); + } + + inline void adjustRightBearing() + { + if (currentPosition <= 0) + return; + adjustRightBearing(currentGlyph()); + } + + inline void adjustPreviousRightBearing() + { + if (previousGlyph > 0) + adjustRightBearing(previousGlyph); + } + + inline void resetRightBearing() + { + rightBearing = QFixed(1); // Any positive number is defined as invalid since only + // negative right bearings are interesting to us. + } + }; + +inline bool LineBreakHelper::checkFullOtherwiseExtend(QScriptLine &line) +{ + LB_DEBUG("possible break width %f, spacew=%f", tmpData.textWidth.toReal(), spaceData.textWidth.toReal()); + + QFixed newWidth = calculateNewWidth(line); + if (line.length && !manualWrap && (newWidth > line.width || glyphCount > maxGlyphs)) + return true; + + minw = qMax(minw, tmpData.textWidth); + line += tmpData; + line.textWidth += spaceData.textWidth; + + line.length += spaceData.length; + tmpData.textWidth = 0; + tmpData.length = 0; + spaceData.textWidth = 0; + spaceData.length = 0; + + return false; +} + +} // anonymous namespace + + +static inline void addNextCluster(int &pos, int end, QScriptLine &line, int &glyphCount, + const QScriptItem ¤t, const unsigned short *logClusters, + const QGlyphLayout &glyphs) +{ + int glyphPosition = logClusters[pos]; + do { // got to the first next cluster + ++pos; + ++line.length; + } while (pos < end && logClusters[pos] == glyphPosition); + do { // calculate the textWidth for the rest of the current cluster. + if (!glyphs.attributes[glyphPosition].dontPrint) + line.textWidth += glyphs.advances[glyphPosition]; + ++glyphPosition; + } while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart); + + Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition); + + ++glyphCount; +} + + +// fill QScriptLine +void QTextLine::layout_helper(int maxGlyphs) +{ + QScriptLine &line = eng->lines[index]; + line.length = 0; + line.trailingSpaces = 0; + line.textWidth = 0; + line.hasTrailingSpaces = false; + + if (!eng->layoutData->items.size() || line.from >= eng->layoutData->string.length()) { + line.setDefaultHeight(eng); + return; + } + + Q_ASSERT(line.from < eng->layoutData->string.length()); + + LineBreakHelper lbh; + + lbh.maxGlyphs = maxGlyphs; + + QTextOption::WrapMode wrapMode = eng->option.wrapMode(); + bool breakany = (wrapMode == QTextOption::WrapAnywhere); + lbh.manualWrap = (wrapMode == QTextOption::ManualWrap || wrapMode == QTextOption::NoWrap); + + int item = -1; + int newItem = eng->findItem(line.from); + Q_ASSERT(newItem >= 0); + + LB_DEBUG("from: %d: item=%d, total %d, width available %f", line.from, newItem, eng->layoutData->items.size(), line.width.toReal()); + + Qt::Alignment alignment = eng->option.alignment(); + + const QCharAttributes *attributes = eng->attributes(); + if (!attributes) + return; + lbh.currentPosition = line.from; + int end = 0; + lbh.logClusters = eng->layoutData->logClustersPtr; + lbh.previousGlyph = 0; + + while (newItem < eng->layoutData->items.size()) { + lbh.resetRightBearing(); + lbh.softHyphenWidth = 0; + if (newItem != item) { + item = newItem; + const QScriptItem ¤t = eng->layoutData->items[item]; + if (!current.num_glyphs) { + eng->shape(item); + attributes = eng->attributes(); + if (!attributes) + return; + lbh.logClusters = eng->layoutData->logClustersPtr; + } + lbh.currentPosition = qMax(line.from, current.position); + end = current.position + eng->length(item); + lbh.glyphs = eng->shapedGlyphs(¤t); + QFontEngine *fontEngine = eng->fontEngine(current); + if (lbh.fontEngine != fontEngine) { + lbh.fontEngine = fontEngine; + lbh.minimumRightBearing = qMin(QFixed(), + QFixed::fromReal(fontEngine->minRightBearing())); + } + } + const QScriptItem ¤t = eng->layoutData->items[item]; + + lbh.tmpData.leading = qMax(lbh.tmpData.leading + lbh.tmpData.ascent, + current.leading + current.ascent) - qMax(lbh.tmpData.ascent, + current.ascent); + lbh.tmpData.ascent = qMax(lbh.tmpData.ascent, current.ascent); + lbh.tmpData.descent = qMax(lbh.tmpData.descent, current.descent); + + if (current.analysis.flags == QScriptAnalysis::Tab && (alignment & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignCenter | Qt::AlignJustify))) { + lbh.whiteSpaceOrObject = true; + if (lbh.checkFullOtherwiseExtend(line)) + goto found; + + QFixed x = line.x + line.textWidth + lbh.tmpData.textWidth + lbh.spaceData.textWidth; + QFixed tabWidth = eng->calculateTabWidth(item, x); + + lbh.spaceData.textWidth += tabWidth; + lbh.spaceData.length++; + newItem = item + 1; + + QFixed averageCharWidth = eng->fontEngine(current)->averageCharWidth(); + lbh.glyphCount += qRound(tabWidth / averageCharWidth); + + if (lbh.checkFullOtherwiseExtend(line)) + goto found; + } else if (current.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator) { + lbh.whiteSpaceOrObject = true; + // if the line consists only of the line separator make sure + // we have a sane height + if (!line.length && !lbh.tmpData.length) + line.setDefaultHeight(eng); + if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators) { + if (lbh.checkFullOtherwiseExtend(line)) + goto found; + + addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount, + current, lbh.logClusters, lbh.glyphs); + } else { + lbh.tmpData.length++; + lbh.adjustPreviousRightBearing(); + } + line += lbh.tmpData; + goto found; + } else if (current.analysis.flags == QScriptAnalysis::Object) { + lbh.whiteSpaceOrObject = true; + lbh.tmpData.length++; + + if (eng->block.docHandle()) { + QTextInlineObject inlineObject(item, eng); + eng->docLayout()->positionInlineObject(inlineObject, eng->block.position() + current.position, inlineObject.format()); + } + + lbh.tmpData.textWidth += current.width; + + newItem = item + 1; + ++lbh.glyphCount; + if (lbh.checkFullOtherwiseExtend(line)) + goto found; + } else if (attributes[lbh.currentPosition].whiteSpace) { + lbh.whiteSpaceOrObject = true; + while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace) + addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount, + current, lbh.logClusters, lbh.glyphs); + + if (!lbh.manualWrap && lbh.spaceData.textWidth > line.width) { + lbh.spaceData.textWidth = line.width; // ignore spaces that fall out of the line. + goto found; + } + } else { + lbh.whiteSpaceOrObject = false; + bool sb_or_ws = false; + lbh.saveCurrentGlyph(); + do { + addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount, + current, lbh.logClusters, lbh.glyphs); + + // This is a hack to fix a regression caused by the introduction of the + // whitespace flag to non-breakable spaces and will cause the non-breakable + // spaces to behave as in previous Qt versions in the line breaking algorithm. + // The line breaks do not currently follow the Unicode specs, but fixing this would + // require refactoring the code and would cause behavioral regressions. + bool isBreakableSpace = lbh.currentPosition < eng->layoutData->string.length() + && attributes[lbh.currentPosition].whiteSpace + && eng->layoutData->string.at(lbh.currentPosition).decompositionTag() != QChar::NoBreak; + + if (lbh.currentPosition >= eng->layoutData->string.length() + || isBreakableSpace + || attributes[lbh.currentPosition].lineBreak) { + sb_or_ws = true; + break; + } else if (breakany && attributes[lbh.currentPosition].graphemeBoundary) { + break; + } + } while (lbh.currentPosition < end); + lbh.minw = qMax(lbh.tmpData.textWidth, lbh.minw); + + if (lbh.currentPosition > 0 && lbh.currentPosition < end + && attributes[lbh.currentPosition].lineBreak + && eng->layoutData->string.at(lbh.currentPosition - 1).unicode() == QChar::SoftHyphen) { + // if we are splitting up a word because of + // a soft hyphen then we ... + // + // a) have to take the width of the soft hyphen into + // account to see if the first syllable(s) /and/ + // the soft hyphen fit into the line + // + // b) if we are so short of available width that the + // soft hyphen is the first breakable position, then + // we don't want to show it. However we initially + // have to take the width for it into account so that + // the text document layout sees the overflow and + // switch to break-anywhere mode, in which we + // want the soft-hyphen to slip into the next line + // and thus become invisible again. + // + if (line.length) + lbh.softHyphenWidth = lbh.glyphs.advances[lbh.logClusters[lbh.currentPosition - 1]]; + else if (breakany) + lbh.tmpData.textWidth += lbh.glyphs.advances[lbh.logClusters[lbh.currentPosition - 1]]; + } + + // The actual width of the text needs to take the right bearing into account. The + // right bearing is left-ward, which means that if the rightmost pixel is to the right + // of the advance of the glyph, the bearing will be negative. We flip the sign + // for the code to be more readable. Logic borrowed from qfontmetrics.cpp. + // We ignore the right bearing if the minimum negative bearing is too little to + // expand the text beyond the edge. + if (sb_or_ws|breakany) { + QFixed rightBearing = lbh.rightBearing; // store previous right bearing + if (lbh.calculateNewWidth(line) - lbh.minimumRightBearing > line.width) + lbh.adjustRightBearing(); + if (lbh.checkFullOtherwiseExtend(line)) { + // we are too wide, fix right bearing + if (rightBearing <= 0) + lbh.rightBearing = rightBearing; // take from cache + else + lbh.adjustPreviousRightBearing(); + + if (!breakany) { + line.textWidth += lbh.softHyphenWidth; + } + + goto found; + } + } + lbh.saveCurrentGlyph(); + } + if (lbh.currentPosition == end) + newItem = item + 1; + } + LB_DEBUG("reached end of line"); + lbh.checkFullOtherwiseExtend(line); +found: + if (lbh.rightBearing > 0 && !lbh.whiteSpaceOrObject) // If right bearing has not yet been adjusted + lbh.adjustRightBearing(); + line.textAdvance = line.textWidth; + line.textWidth -= qMin(QFixed(), lbh.rightBearing); + + if (line.length == 0) { + LB_DEBUG("no break available in line, adding temp: length %d, width %f, space: length %d, width %f", + lbh.tmpData.length, lbh.tmpData.textWidth.toReal(), + lbh.spaceData.length, lbh.spaceData.textWidth.toReal()); + line += lbh.tmpData; + } + + LB_DEBUG("line length = %d, ascent=%f, descent=%f, textWidth=%f (spacew=%f)", line.length, line.ascent.toReal(), + line.descent.toReal(), line.textWidth.toReal(), lbh.spaceData.width.toReal()); + LB_DEBUG(" : '%s'", eng->layoutData->string.mid(line.from, line.length).toUtf8().data()); + + if (lbh.manualWrap) { + eng->minWidth = qMax(eng->minWidth, line.textWidth); + eng->maxWidth = qMax(eng->maxWidth, line.textWidth); + } else { + eng->minWidth = qMax(eng->minWidth, lbh.minw); + eng->maxWidth += line.textWidth; + } + + if (line.textWidth > 0 && item < eng->layoutData->items.size()) + eng->maxWidth += lbh.spaceData.textWidth; + if (eng->option.flags() & QTextOption::IncludeTrailingSpaces) + line.textWidth += lbh.spaceData.textWidth; + if (lbh.spaceData.length) { + line.trailingSpaces = lbh.spaceData.length; + line.hasTrailingSpaces = true; + } + + line.justified = false; + line.gridfitted = false; + + if (eng->option.wrapMode() == QTextOption::WrapAtWordBoundaryOrAnywhere) { + if ((lbh.maxGlyphs != INT_MAX && lbh.glyphCount > lbh.maxGlyphs) + || (lbh.maxGlyphs == INT_MAX && line.textWidth > line.width)) { + + eng->option.setWrapMode(QTextOption::WrapAnywhere); + line.length = 0; + line.textWidth = 0; + layout_helper(lbh.maxGlyphs); + eng->option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + } + } +} + +/*! + Moves the line to position \a pos. +*/ +void QTextLine::setPosition(const QPointF &pos) +{ + eng->lines[index].x = QFixed::fromReal(pos.x()); + eng->lines[index].y = QFixed::fromReal(pos.y()); +} + +/*! + Returns the line's position relative to the text layout's position. +*/ +QPointF QTextLine::position() const +{ + return QPointF(eng->lines[index].x.toReal(), eng->lines[index].y.toReal()); +} + +// ### DOC: I have no idea what this means/does. +// You create a text layout with a string of text. Once you laid +// it out, it contains a number of QTextLines. from() returns the position +// inside the text string where this line starts. If you e.g. has a +// text of "This is a string", laid out into two lines (the second +// starting at the word 'a'), layout.lineAt(0).from() == 0 and +// layout.lineAt(1).from() == 8. +/*! + Returns the start of the line from the beginning of the string + passed to the QTextLayout. +*/ +int QTextLine::textStart() const +{ + return eng->lines[index].from; +} + +/*! + Returns the length of the text in the line. + + \sa naturalTextWidth() +*/ +int QTextLine::textLength() const +{ + if (eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators + && eng->block.isValid() && index == eng->lines.count()-1) { + return eng->lines[index].length - 1; + } + return eng->lines[index].length + eng->lines[index].trailingSpaces; +} + +static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r) +{ + QBrush c = chf.foreground(); + if (c.style() == Qt::NoBrush) { + p->setPen(defaultPen); + } + + QBrush bg = chf.background(); + if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool()) + p->fillRect(QRectF(qFloor(r.x()), qFloor(r.y()), r.width(), r.height()), bg); + if (c.style() != Qt::NoBrush) { + p->setPen(QPen(c, 0)); + } + +} + +#if !defined(QT_NO_RAWFONT) +static QGlyphRun glyphRunWithInfo(QFontEngine *fontEngine, + const QGlyphLayout &glyphLayout, + const QPointF &pos, + const QGlyphRun::GlyphRunFlags &flags, + const QFixed &selectionX, + const QFixed &selectionWidth, + int glyphsStart, + int glyphsEnd, + unsigned short *logClusters, + int textPosition, + int textLength) +{ + Q_ASSERT(logClusters != 0); + + QGlyphRun glyphRun; + + QGlyphRunPrivate *d = QGlyphRunPrivate::get(glyphRun); + + int rangeStart = textPosition; + while (*logClusters != glyphsStart && rangeStart < textPosition + textLength) { + ++logClusters; + ++rangeStart; + } + + int rangeEnd = rangeStart; + while (*logClusters != glyphsEnd && rangeEnd < textPosition + textLength) { + ++logClusters; + ++rangeEnd; + } + + d->textRangeStart = rangeStart; + d->textRangeEnd = rangeEnd; + + // Make a font for this particular engine + QRawFont font; + QRawFontPrivate *fontD = QRawFontPrivate::get(font); + fontD->fontEngine = fontEngine; + fontD->thread = QThread::currentThread(); + fontD->fontEngine->ref.ref(); + QVarLengthArray glyphsArray; + QVarLengthArray positionsArray; + + QTextItem::RenderFlags renderFlags; + if (flags.testFlag(QGlyphRun::Overline)) + renderFlags |= QTextItem::Overline; + if (flags.testFlag(QGlyphRun::Underline)) + renderFlags |= QTextItem::Underline; + if (flags.testFlag(QGlyphRun::StrikeOut)) + renderFlags |= QTextItem::StrikeOut; + if (flags.testFlag(QGlyphRun::RightToLeft)) + renderFlags |= QTextItem::RightToLeft; + + fontEngine->getGlyphPositions(glyphLayout, QTransform(), renderFlags, glyphsArray, + positionsArray); + Q_ASSERT(glyphsArray.size() == positionsArray.size()); + + qreal fontHeight = font.ascent() + font.descent(); + qreal minY = 0; + qreal maxY = 0; + QVector glyphs; + QVector positions; + for (int i=0; i QTextLine::glyphRuns(int from, int length) const +{ + const QScriptLine &line = eng->lines[index]; + + if (line.length == 0) + return QList(); + + if (from < 0) + from = textStart(); + + if (length < 0) + length = textLength(); + + if (length == 0) + return QList(); + + QTextLayout::FormatRange selection; + selection.start = from; + selection.length = length; + + QTextLineItemIterator iterator(eng, index, QPointF(), &selection); + qreal y = line.y.toReal() + line.base().toReal(); + QList glyphRuns; + while (!iterator.atEnd()) { + QScriptItem &si = iterator.next(); + if (si.analysis.flags >= QScriptAnalysis::TabOrObject) + continue; + + if (from >= 0 && length >= 0 && (from >= iterator.itemEnd || from + length <= iterator.itemStart)) + continue; + + QPointF pos(iterator.x.toReal(), y); + + QFont font; + QGlyphRun::GlyphRunFlags flags; + if (!eng->useRawFont) { + font = eng->font(si); + if (font.overline()) + flags |= QGlyphRun::Overline; + if (font.underline()) + flags |= QGlyphRun::Underline; + if (font.strikeOut()) + flags |= QGlyphRun::StrikeOut; + } + + bool rtl = false; + if (si.analysis.bidiLevel % 2) { + flags |= QGlyphRun::RightToLeft; + rtl = true; + } + + int relativeFrom = qMax(iterator.itemStart, from) - si.position; + int relativeTo = qMin(iterator.itemEnd, from + length) - 1 - si.position; + + unsigned short *logClusters = eng->logClusters(&si); + int glyphsStart = logClusters[relativeFrom]; + int glyphsEnd = (relativeTo == iterator.itemLength) ? si.num_glyphs - 1 : logClusters[relativeTo]; + // the glyph index right next to the requested range + int nextGlyphIndex = (relativeTo < iterator.itemLength - 1) ? logClusters[relativeTo + 1] : si.num_glyphs; + if (nextGlyphIndex - 1 > glyphsEnd) + glyphsEnd = nextGlyphIndex - 1; + bool startsInsideLigature = relativeFrom > 0 && logClusters[relativeFrom - 1] == glyphsStart; + bool endsInsideLigature = nextGlyphIndex == glyphsEnd; + + int itemGlyphsStart = logClusters[iterator.itemStart - si.position]; + int itemGlyphsEnd = logClusters[iterator.itemEnd - 1 - si.position]; + + QGlyphLayout glyphLayout = eng->shapedGlyphs(&si); + + // Calculate new x position of glyph layout for a subset. This becomes somewhat complex + // when we're breaking a RTL script item, since the expected position passed into + // getGlyphPositions() is the left-most edge of the left-most glyph in an RTL run. + if (relativeFrom != (iterator.itemStart - si.position) && !rtl) { + for (int i=itemGlyphsStart; iglyphsEnd; --i) { + QFixed justification = QFixed::fromFixed(glyphLayout.justifications[i].space_18d6); + pos.rx() += (glyphLayout.advances[i] + justification).toReal(); + } + } + + glyphLayout = glyphLayout.mid(glyphsStart, glyphsEnd - glyphsStart + 1); + + QFixed x; + QFixed width; + iterator.getSelectionBounds(&x, &width); + + if (glyphLayout.numGlyphs > 0) { + QFontEngine *mainFontEngine; +#ifndef QT_NO_RAWFONT + if (eng->useRawFont && eng->rawFont.isValid()) + mainFontEngine= eng->fontEngine(si); + else +#endif + mainFontEngine = font.d->engineForScript(si.analysis.script); + + if (mainFontEngine->type() == QFontEngine::Multi) { + QFontEngineMulti *multiFontEngine = static_cast(mainFontEngine); + int end = rtl ? glyphLayout.numGlyphs : 0; + int start = rtl ? end : 0; + int which = glyphLayout.glyphs[rtl ? start - 1 : end] >> 24; + for (; (rtl && start > 0) || (!rtl && end < glyphLayout.numGlyphs); + rtl ? --start : ++end) { + const int e = glyphLayout.glyphs[rtl ? start - 1 : end] >> 24; + if (e == which) + continue; + + QGlyphLayout subLayout = glyphLayout.mid(start, end - start); + multiFontEngine->ensureEngineAt(which); + + QGlyphRun::GlyphRunFlags subFlags = flags; + if (start == 0 && startsInsideLigature) + subFlags |= QGlyphRun::SplitLigature; + + glyphRuns.append(glyphRunWithInfo(multiFontEngine->engine(which), + subLayout, + pos, + subFlags, + x, + width, + glyphsStart + start, + glyphsStart + end, + logClusters, + iterator.itemStart, + iterator.itemLength)); + for (int i = 0; i < subLayout.numGlyphs; ++i) { + QFixed justification = QFixed::fromFixed(subLayout.justifications[i].space_18d6); + pos.rx() += (subLayout.advances[i] + justification).toReal(); + } + + if (rtl) + end = start; + else + start = end; + which = e; + } + + QGlyphLayout subLayout = glyphLayout.mid(start, end - start); + multiFontEngine->ensureEngineAt(which); + + QGlyphRun::GlyphRunFlags subFlags = flags; + if ((start == 0 && startsInsideLigature) || endsInsideLigature) + subFlags |= QGlyphRun::SplitLigature; + + QGlyphRun glyphRun = glyphRunWithInfo(multiFontEngine->engine(which), + subLayout, + pos, + subFlags, + x, + width, + glyphsStart + start, + glyphsStart + end, + logClusters, + iterator.itemStart, + iterator.itemLength); + if (!glyphRun.isEmpty()) + glyphRuns.append(glyphRun); + } else { + if (startsInsideLigature || endsInsideLigature) + flags |= QGlyphRun::SplitLigature; + QGlyphRun glyphRun = glyphRunWithInfo(mainFontEngine, + glyphLayout, + pos, + flags, + x, + width, + glyphsStart, + glyphsEnd, + logClusters, + iterator.itemStart, + iterator.itemLength); + if (!glyphRun.isEmpty()) + glyphRuns.append(glyphRun); + } + } + } + + return glyphRuns; +} +#endif // QT_NO_RAWFONT + +/*! + \fn void QTextLine::draw(QPainter *painter, const QPointF &position, const QTextLayout::FormatRange *selection) const + + Draws a line on the given \a painter at the specified \a position. + The \a selection is reserved for internal use. +*/ +void QTextLine::draw(QPainter *p, const QPointF &pos, const QTextLayout::FormatRange *selection) const +{ +#ifndef QT_NO_RAWFONT + // Not intended to work with rawfont + Q_ASSERT(!eng->useRawFont); +#endif + const QScriptLine &line = eng->lines[index]; + QPen pen = p->pen(); + + bool noText = (selection && selection->format.property(SuppressText).toBool()); + + if (!line.length) { + if (selection + && selection->start <= line.from + && selection->start + selection->length > line.from) { + + const qreal lineHeight = line.height().toReal(); + QRectF r(pos.x() + line.x.toReal(), pos.y() + line.y.toReal(), + lineHeight / 2, QFontMetrics(eng->font()).width(QLatin1Char(' '))); + setPenAndDrawBackground(p, QPen(), selection->format, r); + p->setPen(pen); + } + return; + } + + + QTextLineItemIterator iterator(eng, index, pos, selection); + QFixed lineBase = line.base(); + eng->clearDecorations(); + eng->enableDelayDecorations(); + + const QFixed y = QFixed::fromReal(pos.y()) + line.y + lineBase; + + bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors); + while (!iterator.atEnd()) { + QScriptItem &si = iterator.next(); + + if (selection && selection->start >= 0 && iterator.isOutsideSelection()) + continue; + + if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator + && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators)) + continue; + + QFixed itemBaseLine = y; + QFont f = eng->font(si); + QTextCharFormat format; + + if (eng->hasFormats() || selection) { + format = eng->format(&si); + if (suppressColors) { + format.clearForeground(); + format.clearBackground(); + format.clearProperty(QTextFormat::TextUnderlineColor); + } + if (selection) + format.merge(selection->format); + + setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(), + iterator.itemWidth.toReal(), line.height().toReal())); + + QTextCharFormat::VerticalAlignment valign = format.verticalAlignment(); + if (valign == QTextCharFormat::AlignSuperScript || valign == QTextCharFormat::AlignSubScript) { + QFontEngine *fe = f.d->engineForScript(si.analysis.script); + QFixed height = fe->ascent() + fe->descent(); + if (valign == QTextCharFormat::AlignSubScript) + itemBaseLine += height / 6; + else if (valign == QTextCharFormat::AlignSuperScript) + itemBaseLine -= height / 2; + } + } + + if (si.analysis.flags >= QScriptAnalysis::TabOrObject) { + + if (eng->hasFormats()) { + p->save(); + if (si.analysis.flags == QScriptAnalysis::Object && eng->block.docHandle()) { + QFixed itemY = y - si.ascent; + if (format.verticalAlignment() == QTextCharFormat::AlignTop) { + itemY = y - lineBase; + } + + QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal()); + + eng->docLayout()->drawInlineObject(p, itemRect, + QTextInlineObject(iterator.item, eng), + si.position + eng->block.position(), + format); + if (selection) { + QBrush bg = format.brushProperty(ObjectSelectionBrush); + if (bg.style() != Qt::NoBrush) { + QColor c = bg.color(); + c.setAlpha(128); + p->fillRect(itemRect, c); + } + } + } else { // si.isTab + QFont f = eng->font(si); + QTextItemInt gf(si, &f, format); + gf.chars = 0; + gf.num_chars = 0; + gf.width = iterator.itemWidth; + QPainterPrivate::get(p)->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf, eng); + if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) { + QChar visualTab(0x2192); + int w = QFontMetrics(f).width(visualTab); + qreal x = iterator.itemWidth.toReal() - w; // Right-aligned + if (x < 0) + p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(), + iterator.itemWidth.toReal(), line.height().toReal()), + Qt::IntersectClip); + else + x /= 2; // Centered + p->drawText(QPointF(iterator.x.toReal() + x, + y.toReal()), visualTab); + } + + } + p->restore(); + } + + continue; + } + + unsigned short *logClusters = eng->logClusters(&si); + QGlyphLayout glyphs = eng->shapedGlyphs(&si); + + QTextItemInt gf(glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart), + &f, eng->layoutData->string.unicode() + iterator.itemStart, + iterator.itemEnd - iterator.itemStart, eng->fontEngine(si), format); + gf.logClusters = logClusters + iterator.itemStart - si.position; + gf.width = iterator.itemWidth; + gf.justified = line.justified; + gf.initWithScriptItem(si); + + Q_ASSERT(gf.fontEngine); + + QPointF pos(iterator.x.toReal(), itemBaseLine.toReal()); + if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) { + QPainterPath path; + path.setFillRule(Qt::WindingFill); + + if (gf.glyphs.numGlyphs) + gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags); + if (gf.flags) { + const QFontEngine *fe = gf.fontEngine; + const qreal lw = fe->lineThickness().toReal(); + if (gf.flags & QTextItem::Underline) { + qreal offs = fe->underlinePosition().toReal(); + path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw); + } + if (gf.flags & QTextItem::Overline) { + qreal offs = fe->ascent().toReal() + 1; + path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw); + } + if (gf.flags & QTextItem::StrikeOut) { + qreal offs = fe->ascent().toReal() / 3; + path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw); + } + } + + p->save(); + p->setRenderHint(QPainter::Antialiasing); + //Currently QPen with a Qt::NoPen style still returns a default + //QBrush which != Qt::NoBrush so we need this specialcase to reset it + if (p->pen().style() == Qt::NoPen) + p->setBrush(Qt::NoBrush); + else + p->setBrush(p->pen().brush()); + + p->setPen(format.textOutline()); + p->drawPath(path); + p->restore(); + } else { + if (noText) + gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be + QPainterPrivate::get(p)->drawTextItem(pos, gf, eng); + } + + if (si.analysis.flags == QScriptAnalysis::Space + && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) { + QBrush c = format.foreground(); + if (c.style() != Qt::NoBrush) + p->setPen(c.color()); + QChar visualSpace((ushort)0xb7); + p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace); + p->setPen(pen); + } + } + eng->drawDecorations(p); + + if (eng->hasFormats()) + p->setPen(pen); +} + +/*! + \fn int QTextLine::cursorToX(int cursorPos, Edge edge) const + + \overload +*/ + +/*! + Converts the cursor position \a cursorPos to the corresponding x position + inside the line, taking account of the \a edge. + + If \a cursorPos is not a valid cursor position, the nearest valid + cursor position will be used instead, and cpos will be modified to + point to this valid cursor position. + + \sa xToCursor() +*/ +qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const +{ + if (!eng->layoutData) + eng->itemize(); + + const QScriptLine &line = eng->lines[index]; + bool lastLine = index >= eng->lines.size() - 1; + + QFixed x = line.x; + x += eng->alignLine(line) - eng->leadingSpaceWidth(line); + + if (!index && !eng->layoutData->items.size()) { + *cursorPos = 0; + return x.toReal(); + } + + int lineEnd = line.from + line.length + line.trailingSpaces; + int pos = qBound(0, *cursorPos, lineEnd); + int itm; + const QCharAttributes *attributes = eng->attributes(); + if (!attributes) { + *cursorPos = 0; + return x.toReal(); + } + while (pos < lineEnd && !attributes[pos].graphemeBoundary) + pos++; + if (pos == lineEnd) { + // end of line ensure we have the last item on the line + itm = eng->findItem(pos-1); + } + else + itm = eng->findItem(pos); + if (itm < 0) { + *cursorPos = 0; + return x.toReal(); + } + eng->shapeLine(line); + + const QScriptItem *si = &eng->layoutData->items[itm]; + if (!si->num_glyphs) + eng->shape(itm); + pos -= si->position; + + QGlyphLayout glyphs = eng->shapedGlyphs(si); + unsigned short *logClusters = eng->logClusters(si); + Q_ASSERT(logClusters); + + int l = eng->length(itm); + if (pos > l) + pos = l; + if (pos < 0) + pos = 0; + + int glyph_pos = pos == l ? si->num_glyphs : logClusters[pos]; + if (edge == Trailing && glyph_pos < si->num_glyphs) { + // trailing edge is leading edge of next cluster + glyph_pos++; + while (glyph_pos < si->num_glyphs && !glyphs.attributes[glyph_pos].clusterStart) + glyph_pos++; + } + + bool reverse = eng->layoutData->items[itm].analysis.bidiLevel % 2; + + + // add the items left of the cursor + + int firstItem = eng->findItem(line.from); + int lastItem = eng->findItem(lineEnd - 1); + int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0; + + QVarLengthArray visualOrder(nItems); + QVarLengthArray levels(nItems); + for (int i = 0; i < nItems; ++i) + levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel; + QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data()); + + for (int i = 0; i < nItems; ++i) { + int item = visualOrder[i]+firstItem; + if (item == itm) + break; + QScriptItem &si = eng->layoutData->items[item]; + if (!si.num_glyphs) + eng->shape(item); + + if (si.analysis.flags >= QScriptAnalysis::TabOrObject) { + x += si.width; + continue; + } + int start = qMax(line.from, si.position); + int end = qMin(lineEnd, si.position + eng->length(item)); + + logClusters = eng->logClusters(&si); + + int gs = logClusters[start-si.position]; + int ge = (end == si.position + eng->length(item)) ? si.num_glyphs-1 : logClusters[end-si.position-1]; + + QGlyphLayout glyphs = eng->shapedGlyphs(&si); + + while (gs <= ge) { + x += glyphs.effectiveAdvance(gs); + ++gs; + } + } + + logClusters = eng->logClusters(si); + glyphs = eng->shapedGlyphs(si); + if (si->analysis.flags >= QScriptAnalysis::TabOrObject) { + if (pos == (reverse ? 0 : l)) + x += si->width; + } else { + bool rtl = eng->isRightToLeft(); + bool visual = eng->visualCursorMovement(); + int end = qMin(lineEnd, si->position + l) - si->position; + if (reverse) { + int glyph_end = end == l ? si->num_glyphs : logClusters[end]; + int glyph_start = glyph_pos; + if (visual && !rtl && !(lastLine && itm == (visualOrder[nItems - 1] + firstItem))) + glyph_start++; + for (int i = glyph_end - 1; i >= glyph_start; i--) + x += glyphs.effectiveAdvance(i); + } else { + int start = qMax(line.from - si->position, 0); + int glyph_start = logClusters[start]; + int glyph_end = glyph_pos; + if (!visual || !rtl || (lastLine && itm == visualOrder[0] + firstItem)) + glyph_end--; + for (int i = glyph_start; i <= glyph_end; i++) + x += glyphs.effectiveAdvance(i); + } + x += eng->offsetInLigature(si, pos, end, glyph_pos); + } + + if (eng->option.wrapMode() != QTextOption::NoWrap && x > line.x + line.width) + x = line.x + line.width; + if (eng->option.wrapMode() != QTextOption::NoWrap && x < 0) + x = 0; + + *cursorPos = pos + si->position; + return x.toReal(); +} + +/*! + \fn int QTextLine::xToCursor(qreal x, CursorPosition cpos) const + + Converts the x-coordinate \a x, to the nearest matching cursor + position, depending on the cursor position type, \a cpos. + Note that result cursor position includes possible preedit area text. + + \sa cursorToX() +*/ +int QTextLine::xToCursor(qreal _x, CursorPosition cpos) const +{ + QFixed x = QFixed::fromReal(_x); + const QScriptLine &line = eng->lines[index]; + bool lastLine = index >= eng->lines.size() - 1; + int lineNum = index; + + if (!eng->layoutData) + eng->itemize(); + + int line_length = textLength(); + + if (!line_length) + return line.from; + + int firstItem = eng->findItem(line.from); + int lastItem = eng->findItem(line.from + line_length - 1); + int nItems = (firstItem >= 0 && lastItem >= firstItem)? (lastItem-firstItem+1) : 0; + + if (!nItems) + return 0; + + x -= line.x; + x -= eng->alignLine(line); +// qDebug("xToCursor: x=%f, cpos=%d", x.toReal(), cpos); + + QVarLengthArray visualOrder(nItems); + QVarLengthArray levels(nItems); + for (int i = 0; i < nItems; ++i) + levels[i] = eng->layoutData->items[i+firstItem].analysis.bidiLevel; + QTextEngine::bidiReorder(nItems, levels.data(), visualOrder.data()); + + bool visual = eng->visualCursorMovement(); + if (x <= 0) { + // left of first item + int item = visualOrder[0]+firstItem; + QScriptItem &si = eng->layoutData->items[item]; + if (!si.num_glyphs) + eng->shape(item); + int pos = si.position; + if (si.analysis.bidiLevel % 2) + pos += eng->length(item); + pos = qMax(line.from, pos); + pos = qMin(line.from + line_length, pos); + return pos; + } else if (x < line.textWidth + || (line.justified && x < line.width)) { + // has to be in one of the runs + QFixed pos; + bool rtl = eng->isRightToLeft(); + + eng->shapeLine(line); + QVector insertionPoints; + if (visual && rtl) + eng->insertionPointsForLine(lineNum, insertionPoints); + int nchars = 0; + for (int i = 0; i < nItems; ++i) { + int item = visualOrder[i]+firstItem; + QScriptItem &si = eng->layoutData->items[item]; + int item_length = eng->length(item); +// qDebug(" item %d, visual %d x_remain=%f", i, item, x.toReal()); + + int start = qMax(line.from - si.position, 0); + int end = qMin(line.from + line_length - si.position, item_length); + + unsigned short *logClusters = eng->logClusters(&si); + + int gs = logClusters[start]; + int ge = (end == item_length ? si.num_glyphs : logClusters[end]) - 1; + QGlyphLayout glyphs = eng->shapedGlyphs(&si); + + QFixed item_width = 0; + if (si.analysis.flags >= QScriptAnalysis::TabOrObject) { + item_width = si.width; + } else { + int g = gs; + while (g <= ge) { + item_width += glyphs.effectiveAdvance(g); + ++g; + } + } +// qDebug(" start=%d, end=%d, gs=%d, ge=%d item_width=%f", start, end, gs, ge, item_width.toReal()); + + if (pos + item_width < x) { + pos += item_width; + nchars += end; + continue; + } +// qDebug(" inside run"); + if (si.analysis.flags >= QScriptAnalysis::TabOrObject) { + if (cpos == QTextLine::CursorOnCharacter) + return si.position; + bool left_half = (x - pos) < item_width/2; + + if (bool(si.analysis.bidiLevel % 2) != left_half) + return si.position; + return si.position + 1; + } + + int glyph_pos = -1; + QFixed edge; + // has to be inside run + if (cpos == QTextLine::CursorOnCharacter) { + if (si.analysis.bidiLevel % 2) { + pos += item_width; + glyph_pos = gs; + while (gs <= ge) { + if (glyphs.attributes[gs].clusterStart) { + if (pos < x) + break; + glyph_pos = gs; + edge = pos; + } + pos -= glyphs.effectiveAdvance(gs); + ++gs; + } + } else { + glyph_pos = gs; + while (gs <= ge) { + if (glyphs.attributes[gs].clusterStart) { + if (pos > x) + break; + glyph_pos = gs; + edge = pos; + } + pos += glyphs.effectiveAdvance(gs); + ++gs; + } + } + } else { + QFixed dist = INT_MAX/256; + if (si.analysis.bidiLevel % 2) { + if (!visual || rtl || (lastLine && i == nItems - 1)) { + pos += item_width; + while (gs <= ge) { + if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) { + glyph_pos = gs; + edge = pos; + dist = qAbs(x-pos); + } + pos -= glyphs.effectiveAdvance(gs); + ++gs; + } + } else { + while (ge >= gs) { + if (glyphs.attributes[ge].clusterStart && qAbs(x-pos) < dist) { + glyph_pos = ge; + edge = pos; + dist = qAbs(x-pos); + } + pos += glyphs.effectiveAdvance(ge); + --ge; + } + } + } else { + if (!visual || !rtl || (lastLine && i == 0)) { + while (gs <= ge) { + if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) { + glyph_pos = gs; + edge = pos; + dist = qAbs(x-pos); + } + pos += glyphs.effectiveAdvance(gs); + ++gs; + } + } else { + QFixed oldPos = pos; + while (gs <= ge) { + pos += glyphs.effectiveAdvance(gs); + if (glyphs.attributes[gs].clusterStart && qAbs(x-pos) < dist) { + glyph_pos = gs; + edge = pos; + dist = qAbs(x-pos); + } + ++gs; + } + pos = oldPos; + } + } + if (qAbs(x-pos) < dist) { + if (visual) { + if (!rtl && i < nItems - 1) { + nchars += end; + continue; + } + if (rtl && nchars > 0) + return insertionPoints[lastLine ? nchars : nchars - 1]; + } + return eng->positionInLigature(&si, end, x, pos, -1, + cpos == QTextLine::CursorOnCharacter); + } + } + Q_ASSERT(glyph_pos != -1); + return eng->positionInLigature(&si, end, x, edge, glyph_pos, + cpos == QTextLine::CursorOnCharacter); + } + } + // right of last item +// qDebug() << "right of last"; + int item = visualOrder[nItems-1]+firstItem; + QScriptItem &si = eng->layoutData->items[item]; + if (!si.num_glyphs) + eng->shape(item); + int pos = si.position; + if (!(si.analysis.bidiLevel % 2)) + pos += eng->length(item); + pos = qMax(line.from, pos); + + int maxPos = line.from + line_length; + + // except for the last line we assume that the + // character between lines is a space and we want + // to position the cursor to the left of that + // character. + // ###### breaks with japanese for example + if (this->index < eng->lines.count() - 1) + --maxPos; + + pos = qMin(pos, maxPos); + return pos; +} + +QT_END_NAMESPACE diff --git a/Telegram/_qt_5_4_0_patch/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp b/Telegram/_qt_5_4_0_patch/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp index ba423b430..3fd084829 100644 --- a/Telegram/_qt_5_4_0_patch/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp +++ b/Telegram/_qt_5_4_0_patch/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp @@ -832,7 +832,10 @@ bool QWindowsKeyMapper::translateMultimediaKeyEventInternal(QWindow *window, con const int qtKey = CmdTbl[cmd]; sendExtendedPressRelease(receiver, qtKey, Qt::KeyboardModifier(state), 0, 0, 0); - return true; + // QTBUG-43343: Make sure to return false if Qt does not handle the key, otherwise, + // the keys are not passed to the active media player. + const QKeySequence sequence(Qt::Modifier(state) + qtKey); + return QGuiApplicationPrivate::instance()->shortcutMap.hasShortcutForKeySequence(sequence); #else Q_UNREACHABLE(); return false;