Compare commits

..

29 Commits

Author SHA1 Message Date
John Preston
de16a66a4a Alpha version 1.2.2: Fix build for Xcode. 2017-12-16 21:09:37 +04:00
John Preston
b0f191515a Alpha version 1.2.2.
- Grouped photos and videos are displayed as albums.
2017-12-16 20:52:41 +04:00
John Preston
89ccaccb88 Display right edited badge in group with caption. 2017-12-16 20:50:43 +04:00
John Preston
1f070da202 Recount grouping after leader caption edit. 2017-12-16 20:50:43 +04:00
John Preston
963e969d2a Fix selected messages copy with grouping. 2017-12-16 20:50:43 +04:00
John Preston
4734700ac5 Improve opening history with one loaded message.
When we add just one last item, like we do while loading dialogs,
we want to remove a single added grouped media, otherwise it will
jump once we open the message history (first we show only that
media, then we load the rest of the group and show the group).

That way when we open the message history we show nothing until a
whole history part is loaded, it certainly will contain the group.
2017-12-16 20:50:43 +04:00
John Preston
d9da2edd7c Improve grouped media display. 2017-12-16 20:50:43 +04:00
John Preston
6d48ca850e Correct reply/forward/delete for grouped items. 2017-12-16 20:50:43 +04:00
John Preston
3e7ac7eb26 Use first media caption for group caption. 2017-12-16 20:50:43 +04:00
John Preston
520a644150 Fix drag by date of grouped media. 2017-12-16 20:50:43 +04:00
John Preston
3a56b7cabd Forward grouped items. Fast share grouped items. 2017-12-16 20:50:43 +04:00
John Preston
efa72578cd Fix grouped media display in MediaView. 2017-12-16 20:50:43 +04:00
John Preston
b6087ce7ce Select/forward/delete group of messages. 2017-12-16 20:50:42 +04:00
John Preston
537400d8b2 Enable distinct selecting of grouped media. 2017-12-16 20:50:42 +04:00
John Preston
4c9931ab02 Support grouped media rendering. 2017-12-16 20:50:42 +04:00
John Preston
0a4038d061 Fix build with TDESKTOP_DISABLE_CRASH_REPORTS.
Regression was introduced in 97c15865a5.

Fixes #4173.
2017-12-13 00:25:14 +04:00
Christoph
2d5188b968 Remove wrong alpha version
Fix #4171
2017-12-12 21:43:01 +04:00
John Preston
4bab7583ba Version 1.2.1.
- Bug fixes and other minor improvements.
2017-12-12 18:56:38 +04:00
John Preston
b2f29b674d Send audio files with correct attributes.
Regression was introduced in 8b69e6ab99.

Fixes #4163.
2017-12-12 18:56:38 +04:00
John Preston
574f4a73cb Add some checks to video sound stream decoding. 2017-12-12 18:56:37 +04:00
John Preston
05e3ddce0c Fix userpic removing.
Regression was introduced in 68009b6fba.

Fixes #4152.
2017-12-12 18:56:37 +04:00
John Preston
3c101b0a50 Remove limit on chats list width.
Fixes #4146.
2017-12-12 18:56:37 +04:00
John Preston
e998bd0b3f Parse command line natively on Windows.
Use CommandLineToArgvW() so that unicode arguments are preserved.
This will fix path arguments with unicode symbols in them.
2017-12-12 18:56:37 +04:00
John Preston
251176df47 Move relaunch / update logic to Core::Launcher.
Also pass "-workdir" argument through relaunch / update.

Fixes #4149.
2017-12-12 18:56:36 +04:00
John Preston
97c15865a5 Move some code around.
Move logs:SignalHandlers to core/crash_reports:CrashReports.
Move all pre-launch windows to core/crash_report_window module.
Move some global code to core/launcher:Launcher.
It should replace settings / platform_specific module in some way.
2017-12-12 16:47:32 +04:00
John Preston
9d4558de2b Fix build in Visual Studio 15.5.1.
Looks like compiler had some regressions when updating from 15.4.5.

Range-V3-VS2015 also needs to cherry-pick this commit:
https://github.com/ericniebler/range-v3/commit/9f990c48d0
See https://github.com/Microsoft/Range-V3-VS2015/issues/26
2017-12-12 12:25:54 +04:00
John Preston
38f7f48c17 Open links in AboutBox without confirmation.
Fixes #4148.
2017-12-12 12:25:54 +04:00
John Preston
9534121676 Fix issue number :/ Fixes #4150. 2017-12-11 15:19:13 +04:00
John Preston
10b76d921b Fix window scaling issue on macOS.
Fixes #4149.
2017-12-11 15:06:05 +04:00
106 changed files with 7465 additions and 3594 deletions

View File

@@ -936,6 +936,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_stickers_group_from_featured" = "Choose from trending stickers";
"lng_in_dlg_photo" = "Photo";
"lng_in_dlg_album" = "Album";
"lng_in_dlg_video" = "Video";
"lng_in_dlg_audio_file" = "Audio file";
"lng_in_dlg_contact" = "Contact";

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="1.2.0.0" />
Version="1.2.2.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,0,0
PRODUCTVERSION 1,2,0,0
FILEVERSION 1,2,2,0
PRODUCTVERSION 1,2,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -52,10 +52,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "1.2.0.0"
VALUE "FileVersion", "1.2.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.2.0.0"
VALUE "ProductVersion", "1.2.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,0,0
PRODUCTVERSION 1,2,0,0
FILEVERSION 1,2,2,0
PRODUCTVERSION 1,2,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -43,10 +43,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "1.2.0.0"
VALUE "FileVersion", "1.2.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.2.0.0"
VALUE "ProductVersion", "1.2.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -22,7 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
bool _debug = false;
wstring updaterName, updaterDir, updateTo, exeName;
wstring updaterName, updaterDir, updateTo, exeName, customWorkingDir, customKeyFile;
bool equal(const wstring &a, const wstring &b) {
return !_wcsicmp(a.c_str(), b.c_str());
@@ -356,6 +356,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
args = CommandLineToArgvW(GetCommandLine(), &argsCount);
if (args) {
for (int i = 1; i < argsCount; ++i) {
writeLog(std::wstring(L"Argument: ") + args[i]);
if (equal(args[i], L"-update")) {
needupdate = true;
} else if (equal(args[i], L"-autostart")) {
@@ -368,17 +369,25 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
} else if (equal(args[i], L"-testmode")) {
testmode = true;
} else if (equal(args[i], L"-writeprotected") && ++i < argsCount) {
writeLog(std::wstring(L"Argument: ") + args[i]);
writeprotected = true;
updateTo = args[i];
for (int i = 0, l = updateTo.size(); i < l; ++i) {
if (updateTo[i] == L'/') {
updateTo[i] = L'\\';
for (int j = 0, l = updateTo.size(); j < l; ++j) {
if (updateTo[j] == L'/') {
updateTo[j] = L'\\';
}
}
} else if (equal(args[i], L"-workdir") && ++i < argsCount) {
writeLog(std::wstring(L"Argument: ") + args[i]);
customWorkingDir = args[i];
} else if (equal(args[i], L"-key") && ++i < argsCount) {
writeLog(std::wstring(L"Argument: ") + args[i]);
customKeyFile = args[i];
} else if (equal(args[i], L"-exename") && ++i < argsCount) {
writeLog(std::wstring(L"Argument: ") + args[i]);
exeName = args[i];
for (int i = 0, l = exeName.size(); i < l; ++i) {
if (exeName[i] == L'/' || exeName[i] == L'\\') {
for (int j = 0, l = exeName.size(); j < l; ++j) {
if (exeName[j] == L'/' || exeName[j] == L'\\') {
exeName = L"Telegram.exe";
break;
}
@@ -391,6 +400,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
if (needupdate) writeLog(L"Need to update!");
if (autostart) writeLog(L"From autostart!");
if (writeprotected) writeLog(L"Write Protected folder!");
if (!customWorkingDir.empty()) writeLog(L"Will pass custom working dir: " + customWorkingDir);
updaterName = args[0];
writeLog(L"Updater name is: " + updaterName);
@@ -428,6 +438,13 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
if (debug) targs += L" -debug";
if (startintray) targs += L" -startintray";
if (testmode) targs += L" -testmode";
if (!customWorkingDir.empty()) {
targs += L" -workdir \"" + customWorkingDir + L"\"";
}
if (!customKeyFile.empty()) {
targs += L" -key \"" + customKeyFile + L"\"";
}
writeLog(L"Result arguments: " + targs);
bool executed = false;
if (writeprotected) { // run un-elevated

View File

@@ -344,9 +344,16 @@ string CurrentExecutablePath(int argc, char *argv[]) {
}
int main(int argc, char *argv[]) {
bool needupdate = true, autostart = false, debug = false, tosettings = false, startintray = false, testmode = false;
bool needupdate = true;
bool autostart = false;
bool debug = false;
bool tosettings = false;
bool startintray = false;
bool testmode = false;
bool customWorkingDir = false;
char *key = 0, *crashreport = 0;
char *key = 0;
char *workdir = 0;
for (int i = 1; i < argc; ++i) {
if (equal(argv[i], "-noupdate")) {
needupdate = false;
@@ -360,12 +367,12 @@ int main(int argc, char *argv[]) {
testmode = true;
} else if (equal(argv[i], "-tosettings")) {
tosettings = true;
} else if (equal(argv[i], "-workdir_custom")) {
customWorkingDir = true;
} else if (equal(argv[i], "-key") && ++i < argc) {
key = argv[i];
} else if (equal(argv[i], "-workpath") && ++i < argc) {
workDir = argv[i];
} else if (equal(argv[i], "-crashreport") && ++i < argc) {
crashreport = argv[i];
workDir = workdir = argv[i];
} else if (equal(argv[i], "-exename") && ++i < argc) {
exeName = argv[i];
} else if (equal(argv[i], "-exepath") && ++i < argc) {
@@ -401,6 +408,8 @@ int main(int argc, char *argv[]) {
}
if (needupdate) {
if (workDir.empty()) { // old app launched, update prepared in tupdates/ready (not in tupdates/temp)
customWorkingDir = false;
writeLog("No workdir, trying to figure it out");
struct passwd *pw = getpwuid(getuid());
if (pw && pw->pw_dir && strlen(pw->pw_dir)) {
@@ -446,22 +455,30 @@ int main(int argc, char *argv[]) {
string fullBinaryPath = exePath + exeName;
strcpy(path, fullBinaryPath.c_str());
char *args[MaxArgsCount] = {0}, p_noupdate[] = "-noupdate", p_autostart[] = "-autostart", p_debug[] = "-debug", p_tosettings[] = "-tosettings", p_key[] = "-key", p_startintray[] = "-startintray", p_testmode[] = "-testmode";
char *args[MaxArgsCount] = { 0 };
char p_noupdate[] = "-noupdate";
char p_autostart[] = "-autostart";
char p_debug[] = "-debug";
char p_tosettings[] = "-tosettings";
char p_key[] = "-key";
char p_startintray[] = "-startintray";
char p_testmode[] = "-testmode";
char p_workdir[] = "-workdir";
int argIndex = 0;
args[argIndex++] = path;
if (crashreport) {
args[argIndex++] = crashreport;
} else {
args[argIndex++] = p_noupdate;
if (autostart) args[argIndex++] = p_autostart;
if (debug) args[argIndex++] = p_debug;
if (startintray) args[argIndex++] = p_startintray;
if (testmode) args[argIndex++] = p_testmode;
if (tosettings) args[argIndex++] = p_tosettings;
if (key) {
args[argIndex++] = p_key;
args[argIndex++] = key;
}
args[argIndex++] = p_noupdate;
if (autostart) args[argIndex++] = p_autostart;
if (debug) args[argIndex++] = p_debug;
if (startintray) args[argIndex++] = p_startintray;
if (testmode) args[argIndex++] = p_testmode;
if (tosettings) args[argIndex++] = p_tosettings;
if (key) {
args[argIndex++] = p_key;
args[argIndex++] = key;
}
if (customWorkingDir && workdir) {
args[argIndex++] = p_workdir;
args[argIndex++] = workdir;
}
pid_t pid = fork();
switch (pid) {

View File

@@ -23,7 +23,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
NSString *appName = @"Telegram.app";
NSString *appDir = nil;
NSString *workDir = nil;
NSString *crashReportArg = nil;
#ifdef _DEBUG
BOOL _debug = YES;
@@ -90,6 +89,7 @@ int main(int argc, const char * argv[]) {
openLog();
pid_t procId = 0;
BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO, testMode = NO;
BOOL customWorkingDir = NO;
NSString *key = nil;
for (int i = 0; i < argc; ++i) {
if ([@"-workpath" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
@@ -102,10 +102,6 @@ int main(int argc, const char * argv[]) {
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
procId = [[formatter numberFromString:[NSString stringWithUTF8String:argv[i]]] intValue];
}
} else if ([@"-crashreport" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
if (++i < argc) {
crashReportArg = [NSString stringWithUTF8String:argv[i]];
}
} else if ([@"-noupdate" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
update = NO;
} else if ([@"-tosettings" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
@@ -118,11 +114,16 @@ int main(int argc, const char * argv[]) {
startInTray = YES;
} else if ([@"-testmode" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
testMode = YES;
} else if ([@"-workdir_custom" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
customWorkingDir = YES;
} else if ([@"-key" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
if (++i < argc) key = [NSString stringWithUTF8String:argv[i]];
}
}
if (!workDir) workDir = appDir;
if (!workDir) {
workDir = appDir;
customWorkingDir = NO;
}
openLog();
NSMutableArray *argsArr = [[NSMutableArray alloc] initWithCapacity:argc];
for (int i = 0; i < argc; ++i) {
@@ -242,17 +243,19 @@ int main(int argc, const char * argv[]) {
}
NSString *appPath = [[NSArray arrayWithObjects:appDir, appRealName, nil] componentsJoinedByString:@""];
NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: crashReportArg ? crashReportArg : @"-noupdate", nil];
if (!crashReportArg) {
if (toSettings) [args addObject:@"-tosettings"];
if (_debug) [args addObject:@"-debug"];
if (startInTray) [args addObject:@"-startintray"];
if (testMode) [args addObject:@"-testmode"];
if (autoStart) [args addObject:@"-autostart"];
if (key) {
[args addObject:@"-key"];
[args addObject:key];
}
NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: @"-noupdate", nil];
if (toSettings) [args addObject:@"-tosettings"];
if (_debug) [args addObject:@"-debug"];
if (startInTray) [args addObject:@"-startintray"];
if (testMode) [args addObject:@"-testmode"];
if (autoStart) [args addObject:@"-autostart"];
if (key) {
[args addObject:@"-key"];
[args addObject:key];
}
if (customWorkingDir) {
[args addObject:@"-workdir"];
[args addObject:workDir];
}
writeLog([[NSArray arrayWithObjects:@"Running application '", appPath, @"' with args '", [args componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]);
NSError *error = nil;

View File

@@ -120,6 +120,7 @@ void ApiWrap::addLocalChangelogs(int oldAppVersion) {
addLocalAlphaChangelog(1001024, "\xE2\x80\x94 Radically improved navigation. New side panel on the right with quick access to shared media and group members.\n\xE2\x80\x94 Pinned Messages. If you are a channel admin, pin messages to focus your subscribers\xE2\x80\x99 attention on important announcements.\n\xE2\x80\x94 Also supported clearing history in supergroups and added a host of minor improvements.");
addLocalAlphaChangelog(1001026, "\xE2\x80\x94 Admin badges in supergroup messages.\n\xE2\x80\x94 Fix crashing on launch in OS X 10.6.\n\xE2\x80\x94 Bug fixes and other minor improvements.");
addLocalAlphaChangelog(1001027, "\xE2\x80\x94 Saved Messages. Bookmark messages by forwarding them to \xE2\x80\x9C""Saved Messages\xE2\x80\x9D. Access them from the Chats list or from the side menu.");
addLocalAlphaChangelog(1002002, "\xE2\x80\x94 Grouped photos and videos are displayed as albums.");
}
if (!addedSome) {
auto text = lng_new_version_wrap(lt_version, str_const_toString(AppVersionStr), lt_changes, lang(lng_new_version_minor), lt_link, qsl("https://desktop.telegram.org/changelog")).trimmed();
@@ -946,7 +947,7 @@ void ApiWrap::saveStickerSets(const Stickers::Order &localOrder, const Stickers:
request(base::take(_stickersClearRecentRequestId)).cancel();
auto writeInstalled = true, writeRecent = false, writeCloudRecent = false, writeFaved = false, writeArchived = false;
auto &recent = cGetRecentStickers();
auto &recent = Stickers::GetRecentPack();
auto &sets = Auth().data().stickerSetsRef();
_stickersOrder = localOrder;
@@ -1608,18 +1609,18 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
}
if (!v) return;
QMap<uint64, int32> msgsIds; // copied from feedMsgs
for (int32 i = 0, l = v->size(); i < l; ++i) {
const auto &msg(v->at(i));
switch (msg.type()) {
case mtpc_message: msgsIds.insert((uint64(uint32(msg.c_message().vid.v)) << 32) | uint64(i), i); break;
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i); break;
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i); break;
}
auto indices = base::flat_map<uint64, int>(); // copied from feedMsgs
for (auto i = 0, l = v->size(); i != l; ++i) {
const auto msgId = idFromMessage(v->at(i));
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
}
for_const (auto msgId, msgsIds) {
if (auto item = App::histories().addNewMessage(v->at(msgId), NewMessageExisting)) {
for (const auto [position, index] : indices) {
const auto item = App::histories().addNewMessage(
v->at(index),
NewMessageExisting);
if (item) {
item->setPendingInitDimensions();
}
}
@@ -2455,6 +2456,7 @@ void ApiWrap::forwardMessages(
}
auto forwardFrom = items.front()->history()->peer;
auto currentGroupId = items.front()->groupId();
auto ids = QVector<MTPint>();
auto randomIds = QVector<MTPlong>();
@@ -2462,8 +2464,12 @@ void ApiWrap::forwardMessages(
if (shared) {
++shared->requestsLeft;
}
const auto finalFlags = sendFlags
| (currentGroupId == MessageGroupId()
? MTPmessages_ForwardMessages::Flag(0)
: MTPmessages_ForwardMessages::Flag::f_grouped);
history->sendRequestId = request(MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
MTP_flags(finalFlags),
forwardFrom->input,
MTP_vector<MTPint>(ids),
MTP_vector<MTPlong>(randomIds),
@@ -2508,9 +2514,13 @@ void ApiWrap::forwardMessages(
App::historyRegRandom(randomId, newId);
}
}
if (forwardFrom != item->history()->peer) {
const auto newFrom = item->history()->peer;
const auto newGroupId = item->groupId();
if (forwardFrom != newFrom
|| currentGroupId != newGroupId) {
sendAccumulated();
forwardFrom = item->history()->peer;
forwardFrom = newFrom;
currentGroupId = newGroupId;
}
ids.push_back(MTP_int(item->id));
randomIds.push_back(MTP_long(randomId));

View File

@@ -46,6 +46,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "numbers.h"
#include "observer_peer.h"
#include "auth_session.h"
#include "core/crash_reports.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "window/themes/window_theme.h"
@@ -472,7 +473,7 @@ namespace {
QString pname = (showPhoneChanged || phoneChanged || nameChanged) ? ((showPhone && !phone.isEmpty()) ? formatPhone(phone) : QString()) : data->nameOrPhone;
if (!minimal && d.is_self() && uname != data->username) {
SignalHandlers::setCrashAnnotation("Username", uname);
CrashReports::SetAnnotation("Username", uname);
}
data->setName(fname, lname, pname, uname);
if (d.has_photo()) {
@@ -1104,29 +1105,23 @@ namespace {
}
void feedMsgs(const QVector<MTPMessage> &msgs, NewMessageType type) {
QMap<uint64, int32> msgsIds;
for (int32 i = 0, l = msgs.size(); i < l; ++i) {
const auto &msg(msgs.at(i));
switch (msg.type()) {
case mtpc_message: {
const auto &d(msg.c_message());
bool needToAdd = true;
auto indices = base::flat_map<uint64, int>();
for (int i = 0, l = msgs.size(); i != l; ++i) {
const auto &msg = msgs[i];
if (msg.type() == mtpc_message) {
const auto &data = msg.c_message();
if (type == NewMessageUnread) { // new message, index my forwarded messages to links overview
if (checkEntitiesAndViewsUpdate(d)) { // already in blocks
if (checkEntitiesAndViewsUpdate(data)) { // already in blocks
LOG(("Skipping message, because it is already in blocks!"));
needToAdd = false;
continue;
}
}
if (needToAdd) {
msgsIds.insert((uint64(uint32(d.vid.v)) << 32) | uint64(i), i);
}
} break;
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i); break;
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i); break;
}
const auto msgId = idFromMessage(msg);
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
}
for (QMap<uint64, int32>::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) {
histories().addNewMessage(msgs.at(i.value()), type);
for (const auto [position, index] : indices) {
histories().addNewMessage(msgs[index], type);
}
}
@@ -2670,7 +2665,9 @@ namespace {
p.setBrush(p.textPalette().selectOverlay);
p.drawEllipse(rect);
} else {
auto overlayCorners = (radius == ImageRoundRadius::Small) ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners;
auto overlayCorners = (radius == ImageRoundRadius::Small)
? SelectedOverlaySmallCorners
: SelectedOverlayLargeCorners;
auto overlayParts = RectPart::Full | RectPart::None;
if (radius == ImageRoundRadius::Large) {
complexAdjustRect(corners, rect, overlayParts);

View File

@@ -26,8 +26,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/localstorage.h"
#include "autoupdater.h"
#include "window/notifications_manager.h"
#include "core/crash_reports.h"
#include "messenger.h"
#include "base/timer.h"
#include "core/crash_report_window.h"
namespace {
@@ -71,8 +73,13 @@ QString _escapeFrom7bit(const QString &str) {
} // namespace
Application::Application(int &argc, char **argv) : QApplication(argc, argv) {
QByteArray d(QFile::encodeName(QDir(cWorkingDir()).absolutePath()));
Application::Application(
not_null<Core::Launcher*> launcher,
int &argc,
char **argv)
: QApplication(argc, argv)
, _launcher(launcher) {
const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath());
char h[33] = { 0 };
hashMd5Hex(d.constData(), d.size(), h);
#ifndef OS_MAC_STORE
@@ -206,12 +213,12 @@ void Application::singleInstanceChecked() {
if (!Logs::started() || (!cManyInstance() && !Logs::instanceChecked())) {
new NotStartedWindow();
} else {
SignalHandlers::Status status = SignalHandlers::start();
if (status == SignalHandlers::CantOpen) {
const auto status = CrashReports::Start();
if (status == CrashReports::CantOpen) {
new NotStartedWindow();
} else if (status == SignalHandlers::LastCrashed) {
} else if (status == CrashReports::LastCrashed) {
if (Sandbox::LastCrashDump().isEmpty()) { // don't handle bad closing for now
if (SignalHandlers::restart() == SignalHandlers::CantOpen) {
if (CrashReports::Restart() == CrashReports::CantOpen) {
new NotStartedWindow();
} else {
Sandbox::launch();
@@ -313,7 +320,7 @@ void Application::startApplication() {
void Application::createMessenger() {
Expects(!App::quitting());
_messengerInstance = std::make_unique<Messenger>();
_messengerInstance = std::make_unique<Messenger>(_launcher);
}
void Application::closeApplication() {

View File

@@ -21,11 +21,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
class UpdateChecker;
namespace Core {
class Launcher;
} // namespace Core
class Application : public QApplication {
Q_OBJECT
public:
Application(int &argc, char **argv);
Application(not_null<Core::Launcher*> launcher, int &argc, char **argv);
bool event(QEvent *e) override;
@@ -55,6 +60,7 @@ private:
typedef QPair<QLocalSocket*, QByteArray> LocalClient;
typedef QList<LocalClient> LocalClients;
not_null<Core::Launcher*> _launcher;
std::unique_ptr<Messenger> _messengerInstance;
QString _localServerName, _localSocketReadData;

View File

@@ -378,6 +378,13 @@ MessageIdsList AuthSessionData::itemsToIds(
}) | ranges::to_vector;
}
MessageIdsList AuthSessionData::groupToIds(
not_null<HistoryMessageGroup*> group) const {
auto result = itemsToIds(group->others);
result.push_back(group->leader->fullId());
return result;
}
AuthSession &Auth() {
auto result = Messenger::Instance().authSession();
Assert(result != nullptr);

View File

@@ -263,6 +263,7 @@ public:
HistoryItemsList idsToItems(const MessageIdsList &ids) const;
MessageIdsList itemsToIds(const HistoryItemsList &items) const;
MessageIdsList groupToIds(not_null<HistoryMessageGroup*> group) const;
private:
struct Variables {

View File

@@ -30,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/labels.h"
#include "styles/style_boxes.h"
#include "platform/platform_file_utilities.h"
#include "core/click_handler_types.h"
AboutBox::AboutBox(QWidget *parent)
: _version(this, lng_about_version(lt_version, QString::fromLatin1(AppVersionStr.c_str()) + (cAlphaVersion() ? " alpha" : "") + (cBetaVersion() ? qsl(" beta %1").arg(cBetaVersion()) : QString())), st::aboutVersionLink)
@@ -43,7 +44,18 @@ void AboutBox::prepare() {
addButton(langFactory(lng_close), [this] { closeBox(); });
const auto linkHook = [](const ClickHandlerPtr &link, auto button) {
if (const auto url = dynamic_cast<UrlClickHandler*>(link.data())) {
url->UrlClickHandler::onClick(button);
return false;
}
return true;
};
_text3->setRichText(lng_about_text_3(lt_faq_open, qsl("[a href=\"%1\"]").arg(telegramFaqLink()), lt_faq_close, qsl("[/a]")));
_text1->setClickHandlerHook(linkHook);
_text2->setClickHandlerHook(linkHook);
_text3->setClickHandlerHook(linkHook);
_version->setClickedCallback([this] { showVersionHistory(); });

View File

@@ -715,10 +715,10 @@ public:
}
void peerListSortRows(
base::lambda<bool(const PeerListRow &a, const PeerListRow &b)> compare) override {
_content->reorderRows([compare = std::move(compare)](
_content->reorderRows([&](
auto &&begin,
auto &&end) {
std::sort(begin, end, [&compare](auto &&a, auto &&b) {
std::sort(begin, end, [&](auto &&a, auto &&b) {
return compare(*a, *b);
});
});
@@ -726,10 +726,10 @@ public:
int peerListPartitionRows(
base::lambda<bool(const PeerListRow &a)> border) override {
auto result = 0;
_content->reorderRows([border = std::move(border), &result](
_content->reorderRows([&](
auto &&begin,
auto &&end) {
auto edge = std::stable_partition(begin, end, [&border](
auto edge = std::stable_partition(begin, end, [&](
auto &&current) {
return border(*current);
});

View File

@@ -480,7 +480,7 @@ EditCaptionBox::EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId) :
case MediaTypePhoto: {
_photo = true;
auto photo = static_cast<HistoryPhoto*>(media)->photo();
auto photo = static_cast<HistoryPhoto*>(media)->getPhoto();
dimensions = QSize(photo->full->width(), photo->full->height());
image = photo->full;
} break;
@@ -492,6 +492,17 @@ EditCaptionBox::EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId) :
image = doc->thumb;
} break;
case MediaTypeGrouped: {
if (const auto photo = media->getPhoto()) {
dimensions = QSize(photo->full->width(), photo->full->height());
image = photo->full;
} else if (const auto doc = media->getDocument()) {
dimensions = doc->dimensions;
image = doc->thumb;
_animated = true;
}
} break;
case MediaTypeFile:
case MediaTypeMusicFile:
case MediaTypeVoiceFile: {

View File

@@ -1551,13 +1551,13 @@ int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const {
auto customIt = Auth().data().stickerSets().constFind(Stickers::CustomSetId);
if (customIt != Auth().data().stickerSets().cend()) {
added = customIt->stickers.size();
for_const (auto &sticker, cGetRecentStickers()) {
for_const (auto &sticker, Stickers::GetRecentPack()) {
if (customIt->stickers.indexOf(sticker.first) < 0) {
++added;
}
}
} else {
added = cGetRecentStickers().size();
added = Stickers::GetRecentPack().size();
}
}
return result + added;

View File

@@ -347,7 +347,7 @@ void SetsReceived(const QVector<MTPStickerSet> &data, int32 hash) {
}
}
auto writeRecent = false;
auto &recent = cGetRecentStickers();
auto &recent = GetRecentPack();
for (auto it = sets.begin(), e = sets.end(); it != e;) {
bool installed = (it->flags & MTPDstickerSet::Flag::f_installed);
bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
@@ -449,7 +449,7 @@ void SpecialSetReceived(uint64 setId, const QString &setTitle, const QVector<MTP
}
auto writeRecent = false;
auto &recent = cGetRecentStickers();
auto &recent = GetRecentPack();
for (auto i = recent.begin(); i != recent.cend();) {
if (it->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
i = recent.erase(i);
@@ -748,7 +748,7 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data) {
}
auto writeRecent = false;
auto &recent = cGetRecentStickers();
auto &recent = GetRecentPack();
for (auto i = recent.begin(); i != recent.cend();) {
if (set->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
i = recent.erase(i);
@@ -816,4 +816,56 @@ QString GetSetTitle(const MTPDstickerSet &s) {
return title;
}
RecentStickerPack &GetRecentPack() {
if (cRecentStickers().isEmpty() && !cRecentStickersPreload().isEmpty()) {
const auto p = cRecentStickersPreload();
cSetRecentStickersPreload(RecentStickerPreload());
auto &recent = cRefRecentStickers();
recent.reserve(p.size());
for (const auto &preloaded : p) {
const auto document = App::document(preloaded.first);
if (!document || !document->sticker()) continue;
recent.push_back(qMakePair(document, preloaded.second));
}
}
return cRefRecentStickers();
}
void IncrementRecentHashtag(RecentHashtagPack &recent, const QString &tag) {
auto i = recent.begin(), e = recent.end();
for (; i != e; ++i) {
if (i->first == tag) {
++i->second;
if (qAbs(i->second) > 0x4000) {
for (auto j = recent.begin(); j != e; ++j) {
if (j->second > 1) {
j->second /= 2;
} else if (j->second > 0) {
j->second = 1;
}
}
}
for (; i != recent.begin(); --i) {
if (qAbs((i - 1)->second) > qAbs(i->second)) {
break;
}
qSwap(*i, *(i - 1));
}
break;
}
}
if (i == e) {
while (recent.size() >= 64) recent.pop_back();
recent.push_back(qMakePair(tag, 1));
for (i = recent.end() - 1; i != recent.begin(); --i) {
if ((i - 1)->second > i->second) {
break;
}
qSwap(*i, *(i - 1));
}
}
}
} // namespace Stickers

View File

@@ -85,4 +85,8 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data);
QString GetSetTitle(const MTPDstickerSet &s);
RecentStickerPack &GetRecentPack();
void IncrementRecentHashtag(RecentHashtagPack &recent, const QString &tag);
} // namespace Stickers

View File

@@ -1090,7 +1090,7 @@ void StickersListWidget::removeRecentSticker(int section, int index) {
clearSelection();
bool refresh = false;
auto sticker = _mySets[section].pack[index];
auto &recent = cGetRecentStickers();
auto &recent = Stickers::GetRecentPack();
for (int32 i = 0, l = recent.size(); i < l; ++i) {
if (recent.at(i).first == sticker) {
recent.removeAt(i);
@@ -1302,7 +1302,7 @@ void StickersListWidget::refreshRecentStickers(bool performResize) {
_custom.clear();
clearSelection();
auto &sets = Auth().data().stickerSets();
auto &recent = cGetRecentStickers();
auto &recent = Stickers::GetRecentPack();
auto customIt = sets.constFind(Stickers::CustomSetId);
auto cloudIt = sets.constFind(Stickers::CloudRecentSetId);
@@ -1773,7 +1773,7 @@ void StickersListWidget::removeSet(uint64 setId) {
request(MTPmessages_UninstallStickerSet(MTP_inputStickerSetShortName(MTP_string(it->shortName)))).send();
}
auto writeRecent = false;
auto &recent = cGetRecentStickers();
auto &recent = Stickers::GetRecentPack();
for (auto i = recent.begin(); i != recent.cend();) {
if (it->stickers.indexOf(i->first) >= 0) {
i = recent.erase(i);

View File

@@ -143,7 +143,7 @@ void HiddenUrlClickHandler::doOpen(QString url) {
Ui::show(Box<ConfirmBox>(lang(lng_open_this_link) + qsl("\n\n") + displayUrl, lang(lng_open_link), [urlText] {
Ui::hideLayer();
UrlClickHandler::doOpen(urlText);
}));
}), LayerOption::KeepOther);
} else {
UrlClickHandler::doOpen(urlText);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,226 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
class PreLaunchWindow : public QWidget {
public:
PreLaunchWindow(QString title = QString());
void activate();
int basicSize() const {
return _size;
}
~PreLaunchWindow();
static PreLaunchWindow *instance();
protected:
int _size;
};
class PreLaunchLabel : public QLabel {
public:
PreLaunchLabel(QWidget *parent);
void setText(const QString &text);
};
class PreLaunchInput : public QLineEdit {
public:
PreLaunchInput(QWidget *parent, bool password = false);
};
class PreLaunchLog : public QTextEdit {
public:
PreLaunchLog(QWidget *parent);
};
class PreLaunchButton : public QPushButton {
public:
PreLaunchButton(QWidget *parent, bool confirm = true);
void setText(const QString &text);
};
class PreLaunchCheckbox : public QCheckBox {
public:
PreLaunchCheckbox(QWidget *parent);
void setText(const QString &text);
};
class NotStartedWindow : public PreLaunchWindow {
public:
NotStartedWindow();
protected:
void closeEvent(QCloseEvent *e);
void resizeEvent(QResizeEvent *e);
private:
void updateControls();
PreLaunchLabel _label;
PreLaunchLog _log;
PreLaunchButton _close;
};
class LastCrashedWindow : public PreLaunchWindow {
Q_OBJECT
public:
LastCrashedWindow();
public slots:
void onViewReport();
void onSaveReport();
void onSendReport();
void onGetApp();
void onNetworkSettings();
void onNetworkSettingsSaved(QString host, quint32 port, QString username, QString password);
void onContinue();
void onCheckingFinished();
void onSendingError(QNetworkReply::NetworkError e);
void onSendingFinished();
void onSendingProgress(qint64 uploaded, qint64 total);
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
void onUpdateRetry();
void onUpdateSkip();
void onUpdateChecking();
void onUpdateLatest();
void onUpdateDownloading(qint64 ready, qint64 total);
void onUpdateReady();
void onUpdateFailed();
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
protected:
void closeEvent(QCloseEvent *e);
void resizeEvent(QResizeEvent *e);
private:
QString minidumpFileName();
void updateControls();
QString _host, _username, _password;
quint32 _port;
PreLaunchLabel _label, _pleaseSendReport, _yourReportName, _minidump;
PreLaunchLog _report;
PreLaunchButton _send, _sendSkip, _networkSettings, _continue, _showReport, _saveReport, _getApp;
PreLaunchCheckbox _includeUsername;
QString _minidumpName, _minidumpFull, _reportText;
QString _reportUsername, _reportTextNoUsername;
QByteArray getCrashReportRaw() const;
bool _reportShown, _reportSaved;
void excludeReportUsername();
enum SendingState {
SendingNoReport,
SendingUpdateCheck,
SendingNone,
SendingTooOld,
SendingTooMany,
SendingUnofficial,
SendingProgress,
SendingUploading,
SendingFail,
SendingDone,
};
SendingState _sendingState;
PreLaunchLabel _updating;
qint64 _sendingProgress, _sendingTotal;
QNetworkAccessManager _sendManager;
QNetworkReply *_checkReply, *_sendReply;
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
PreLaunchButton _updatingCheck, _updatingSkip;
enum UpdatingState {
UpdatingNone,
UpdatingCheck,
UpdatingLatest,
UpdatingDownload,
UpdatingFail,
UpdatingReady
};
UpdatingState _updatingState;
QString _newVersionDownload;
void setUpdatingState(UpdatingState state, bool force = false);
void setDownloadProgress(qint64 ready, qint64 total);
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
QString getReportField(const QLatin1String &name, const QLatin1String &prefix);
void addReportFieldPart(const QLatin1String &name, const QLatin1String &prefix, QHttpMultiPart *multipart);
};
class NetworkSettingsWindow : public PreLaunchWindow {
Q_OBJECT
public:
NetworkSettingsWindow(QWidget *parent, QString host, quint32 port, QString username, QString password);
signals:
void saved(QString host, quint32 port, QString username, QString password);
public slots:
void onSave();
protected:
void closeEvent(QCloseEvent *e);
void resizeEvent(QResizeEvent *e);
private:
void updateControls();
PreLaunchLabel _hostLabel, _portLabel, _usernameLabel, _passwordLabel;
PreLaunchInput _hostInput, _portInput, _usernameInput, _passwordInput;
PreLaunchButton _save, _cancel;
QWidget *_parent;
};
class ShowCrashReportWindow : public PreLaunchWindow {
public:
ShowCrashReportWindow(const QString &text);
protected:
void resizeEvent(QResizeEvent *e);
void closeEvent(QCloseEvent *e);
private:
PreLaunchLog _log;
};

View File

@@ -0,0 +1,580 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "core/crash_reports.h"
#include "platform/platform_specific.h"
#include <signal.h>
#include <new>
#include <mutex>
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
// see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/
#ifdef Q_OS_WIN
#pragma warning(push)
#pragma warning(disable:4091)
#include "client/windows/handler/exception_handler.h"
#pragma warning(pop)
#elif defined Q_OS_MAC // Q_OS_WIN
#include <execinfo.h>
#include <signal.h>
#include <sys/syscall.h>
#include <dlfcn.h>
#include <unistd.h>
#ifdef MAC_USE_BREAKPAD
#include "client/mac/handler/exception_handler.h"
#else // MAC_USE_BREAKPAD
#include "client/crashpad_client.h"
#endif // else for MAC_USE_BREAKPAD
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
#include <execinfo.h>
#include <signal.h>
#include <sys/syscall.h>
#include "client/linux/handler/exception_handler.h"
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
namespace CrashReports {
namespace {
using Annotations = std::map<std::string, std::string>;
using AnnotationRefs = std::map<std::string, const QString*>;
Annotations ProcessAnnotations;
AnnotationRefs ProcessAnnotationRefs;
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
QString ReportPath;
FILE *ReportFile = nullptr;
int ReportFileNo = 0;
char LaunchedDateTimeStr[32] = { 0 };
char LaunchedBinaryName[256] = { 0 };
void SafeWriteChar(char ch) {
fwrite(&ch, 1, 1, ReportFile);
}
template <bool Unsigned, typename Type>
struct writeNumberSignAndRemoveIt {
static void call(Type &number) {
if (number < 0) {
SafeWriteChar('-');
number = -number;
}
}
};
template <typename Type>
struct writeNumberSignAndRemoveIt<true, Type> {
static void call(Type &number) {
}
};
template <typename Type>
const dump &SafeWriteNumber(const dump &stream, Type number) {
if (!ReportFile) return stream;
writeNumberSignAndRemoveIt<(Type(-1) > Type(0)), Type>::call(number);
Type upper = 1, prev = number / 10;
while (prev >= upper) {
upper *= 10;
}
while (upper > 0) {
int digit = (number / upper);
SafeWriteChar('0' + digit);
number -= digit * upper;
upper /= 10;
}
return stream;
}
using ReservedMemoryChunk = std::array<gsl::byte, 1024 * 1024>;
std::unique_ptr<ReservedMemoryChunk> ReservedMemory;
void InstallOperatorNewHandler() {
ReservedMemory = std::make_unique<ReservedMemoryChunk>();
std::set_new_handler([] {
std::set_new_handler(nullptr);
ReservedMemory.reset();
Unexpected("Could not allocate!");
});
}
Qt::HANDLE ReportingThreadId = nullptr;
bool ReportingHeaderWritten = false;
QMutex ReportingMutex;
const char *BreakpadDumpPath = nullptr;
const wchar_t *BreakpadDumpPathW = nullptr;
#if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64
struct sigaction SIG_def[32];
void SignalHandler(int signum, siginfo_t *info, void *ucontext) {
if (signum > 0) {
sigaction(signum, &SIG_def[signum], 0);
}
#else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
void SignalHandler(int signum) {
#endif // else for Q_OS_MAC || Q_OS_LINUX || Q_OS_LINUX64
const char* name = 0;
switch (signum) {
case SIGABRT: name = "SIGABRT"; break;
case SIGSEGV: name = "SIGSEGV"; break;
case SIGILL: name = "SIGILL"; break;
case SIGFPE: name = "SIGFPE"; break;
#ifndef Q_OS_WIN
case SIGBUS: name = "SIGBUS"; break;
case SIGSYS: name = "SIGSYS"; break;
#endif // !Q_OS_WIN
}
Qt::HANDLE thread = QThread::currentThreadId();
if (thread == ReportingThreadId) return;
QMutexLocker lock(&ReportingMutex);
ReportingThreadId = thread;
if (!ReportingHeaderWritten) {
ReportingHeaderWritten = true;
auto dec2hex = [](int value) -> char {
if (value >= 0 && value < 10) {
return '0' + value;
} else if (value >= 10 && value < 16) {
return 'a' + (value - 10);
}
return '#';
};
for (const auto &i : ProcessAnnotationRefs) {
QByteArray utf8 = i.second->toUtf8();
std::string wrapped;
wrapped.reserve(4 * utf8.size());
for (auto ch : utf8) {
auto uch = static_cast<uchar>(ch);
wrapped.append("\\x", 2).append(1, dec2hex(uch >> 4)).append(1, dec2hex(uch & 0x0F));
}
ProcessAnnotations[i.first] = wrapped;
}
const Annotations c_ProcessAnnotations(ProcessAnnotations);
for (const auto &i : c_ProcessAnnotations) {
dump() << i.first.c_str() << ": " << i.second.c_str() << "\n";
}
psWriteDump();
dump() << "\n";
}
if (name) {
dump() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n";
} else if (signum == -1) {
dump() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n";
if (BreakpadDumpPath) {
dump() << "Minidump: " << BreakpadDumpPath << "\n";
} else if (BreakpadDumpPathW) {
dump() << "Minidump: " << BreakpadDumpPathW << "\n";
}
} else {
dump() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n";
}
// see https://github.com/benbjohnson/bandicoot
#if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64
ucontext_t *uc = (ucontext_t*)ucontext;
void *caller = 0;
if (uc) {
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
/* OSX < 10.6 */
#if defined(__x86_64__)
caller = (void*)uc->uc_mcontext->__ss.__rip;
#elif defined(__i386__)
caller = (void*)uc->uc_mcontext->__ss.__eip;
#else
caller = (void*)uc->uc_mcontext->__ss.__srr0;
#endif
#elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
/* OSX >= 10.6 */
#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
caller = (void*)uc->uc_mcontext->__ss.__rip;
#else
caller = (void*)uc->uc_mcontext->__ss.__eip;
#endif
#elif defined(__linux__)
/* Linux */
#if defined(__i386__)
caller = (void*)uc->uc_mcontext.gregs[14]; /* Linux 32 */
#elif defined(__X86_64__) || defined(__x86_64__)
caller = (void*)uc->uc_mcontext.gregs[16]; /* Linux 64 */
#elif defined(__ia64__) /* Linux IA64 */
caller = (void*)uc->uc_mcontext.sc_ip;
#endif
#endif
}
void *addresses[132] = { 0 };
size_t size = backtrace(addresses, 128);
/* overwrite sigaction with caller's address */
if (caller) {
for (int i = size; i > 1; --i) {
addresses[i + 3] = addresses[i];
}
addresses[2] = (void*)0x1;
addresses[3] = caller;
addresses[4] = (void*)0x1;
}
#ifdef Q_OS_MAC
dump() << "\nBase image addresses:\n";
for (size_t i = 0; i < size; ++i) {
Dl_info info;
dump() << i << " ";
if (dladdr(addresses[i], &info)) {
dump() << uint64(info.dli_fbase) << " (" << info.dli_fname << ")\n";
} else {
dump() << "_unknown_module_\n";
}
}
#endif // Q_OS_MAC
dump() << "\nBacktrace:\n";
backtrace_symbols_fd(addresses, size, ReportFileNo);
#else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
dump() << "\nBacktrace:\n";
psWriteStackTrace();
#endif // else for Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
dump() << "\n";
ReportingThreadId = nullptr;
}
bool SetSignalHandlers = true;
bool CrashLogged = false;
#if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
google_breakpad::ExceptionHandler* BreakpadExceptionHandler = 0;
#ifdef Q_OS_WIN
bool DumpCallback(const wchar_t* _dump_dir, const wchar_t* _minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool success)
#elif defined Q_OS_MAC // Q_OS_WIN
bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success)
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success)
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
{
if (CrashLogged) return success;
CrashLogged = true;
#ifdef Q_OS_WIN
BreakpadDumpPathW = _minidump_id;
SignalHandler(-1);
#else // Q_OS_WIN
#ifdef Q_OS_MAC
BreakpadDumpPath = _minidump_id;
#else // Q_OS_MAC
BreakpadDumpPath = md.path();
#endif // else for Q_OS_MAC
SignalHandler(-1, 0, 0);
#endif // else for Q_OS_WIN
return success;
}
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
} // namespace
void StartCatching() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
ProcessAnnotations["Binary"] = cExeName().toUtf8().constData();
ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData();
ProcessAnnotations["Version"] = (cBetaVersion() ? qsl("%1 beta").arg(cBetaVersion()) : (cAlphaVersion() ? qsl("%1 alpha") : qsl("%1")).arg(AppVersion)).toUtf8().constData();
ProcessAnnotations["Launched"] = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss").toUtf8().constData();
ProcessAnnotations["Platform"] = cPlatformString().toUtf8().constData();
ProcessAnnotations["UserTag"] = QString::number(Sandbox::UserTag(), 16).toUtf8().constData();
QString dumpspath = cWorkingDir() + qsl("tdata/dumps");
QDir().mkpath(dumpspath);
#ifdef Q_OS_WIN
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
dumpspath.toStdWString(),
google_breakpad::ExceptionHandler::FilterCallback(nullptr),
DumpCallback,
(void*)nullptr, // callback_context
google_breakpad::ExceptionHandler::HANDLER_ALL,
MINIDUMP_TYPE(MiniDumpNormal),
// MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithProcessThreadData | MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules | MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation),
(const wchar_t*)nullptr, // pipe_name
(const google_breakpad::CustomClientInfo*)nullptr
);
#elif defined Q_OS_MAC // Q_OS_WIN
#ifdef MAC_USE_BREAKPAD
#ifndef _DEBUG
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
QFile::encodeName(dumpspath).toStdString(),
/*FilterCallback*/ 0,
DumpCallback,
/*context*/ 0,
true,
0
);
#endif // !_DEBUG
SetSignalHandlers = false;
#else // MAC_USE_BREAKPAD
crashpad::CrashpadClient crashpad_client;
std::string handler = (cExeDir() + cExeName() + qsl("/Contents/Helpers/crashpad_handler")).toUtf8().constData();
std::string database = QFile::encodeName(dumpspath).constData();
if (crashpad_client.StartHandler(base::FilePath(handler),
base::FilePath(database),
std::string(),
ProcessAnnotations,
std::vector<std::string>(),
false)) {
crashpad_client.UseHandler();
}
#endif // else for MAC_USE_BREAKPAD
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
google_breakpad::MinidumpDescriptor(QFile::encodeName(dumpspath).toStdString()),
/*FilterCallback*/ 0,
DumpCallback,
/*context*/ 0,
true,
-1
);
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
}
void FinishCatching() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
#if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
delete base::take(BreakpadExceptionHandler);
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
}
Status Start() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
ReportPath = cWorkingDir() + qsl("tdata/working");
#ifdef Q_OS_WIN
FILE *f = nullptr;
if (_wfopen_s(&f, ReportPath.toStdWString().c_str(), L"rb") != 0) {
f = nullptr;
} else {
#else // !Q_OS_WIN
if (FILE *f = fopen(QFile::encodeName(ReportPath).constData(), "rb")) {
#endif // else for !Q_OS_WIN
QByteArray lastdump;
char buffer[256 * 1024] = { 0 };
int32 read = fread(buffer, 1, 256 * 1024, f);
if (read > 0) {
lastdump.append(buffer, read);
}
fclose(f);
Sandbox::SetLastCrashDump(lastdump);
LOG(("Opened '%1' for reading, the previous Telegram Desktop launch was not finished properly :( Crash log size: %2").arg(ReportPath).arg(lastdump.size()));
return LastCrashed;
}
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
return Restart();
}
Status Restart() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
if (ReportFile) {
return Started;
}
#ifdef Q_OS_WIN
if (_wfopen_s(&ReportFile, ReportPath.toStdWString().c_str(), L"wb") != 0) {
ReportFile = nullptr;
}
#else // Q_OS_WIN
ReportFile = fopen(QFile::encodeName(ReportPath).constData(), "wb");
#endif // else for Q_OS_WIN
if (ReportFile) {
#ifdef Q_OS_WIN
ReportFileNo = _fileno(ReportFile);
#else // Q_OS_WIN
ReportFileNo = fileno(ReportFile);
#endif // else for Q_OS_WIN
if (SetSignalHandlers) {
#ifndef Q_OS_WIN
struct sigaction sigact;
sigact.sa_sigaction = SignalHandler;
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
sigaction(SIGABRT, &sigact, &SIG_def[SIGABRT]);
sigaction(SIGSEGV, &sigact, &SIG_def[SIGSEGV]);
sigaction(SIGILL, &sigact, &SIG_def[SIGILL]);
sigaction(SIGFPE, &sigact, &SIG_def[SIGFPE]);
sigaction(SIGBUS, &sigact, &SIG_def[SIGBUS]);
sigaction(SIGSYS, &sigact, &SIG_def[SIGSYS]);
#else // !Q_OS_WIN
signal(SIGABRT, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGFPE, SignalHandler);
#endif // else for !Q_OS_WIN
}
InstallOperatorNewHandler();
return Started;
}
LOG(("FATAL: Could not open '%1' for writing!").arg(ReportPath));
return CantOpen;
#else // !TDESKTOP_DISABLE_CRASH_REPORTS
return Started;
#endif // else for !TDESKTOP_DISABLE_CRASH_REPORTS
}
void Finish() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
FinishCatching();
if (ReportFile) {
fclose(ReportFile);
ReportFile = nullptr;
#ifdef Q_OS_WIN
_wunlink(ReportPath.toStdWString().c_str());
#else // Q_OS_WIN
unlink(ReportPath.toUtf8().constData());
#endif // else for Q_OS_WIN
}
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
}
void SetAnnotation(const std::string &key, const QString &value) {
static QMutex mutex;
QMutexLocker lock(&mutex);
if (!value.trimmed().isEmpty()) {
ProcessAnnotations[key] = value.toUtf8().constData();
} else {
ProcessAnnotations.erase(key);
}
}
void SetAnnotationRef(const std::string &key, const QString *valuePtr) {
if (valuePtr) {
ProcessAnnotationRefs[key] = valuePtr;
} else {
ProcessAnnotationRefs.erase(key);
}
}
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
dump::~dump() {
if (ReportFile) {
fflush(ReportFile);
}
}
const dump &operator<<(const dump &stream, const char *str) {
if (!ReportFile) return stream;
fwrite(str, 1, strlen(str), ReportFile);
return stream;
}
const dump &operator<<(const dump &stream, const wchar_t *str) {
if (!ReportFile) return stream;
for (int i = 0, l = wcslen(str); i < l; ++i) {
if (str[i] >= 0 && str[i] < 128) {
SafeWriteChar(char(str[i]));
} else {
SafeWriteChar('?');
}
}
return stream;
}
const dump &operator<<(const dump &stream, int num) {
return SafeWriteNumber(stream, num);
}
const dump &operator<<(const dump &stream, unsigned int num) {
return SafeWriteNumber(stream, num);
}
const dump &operator<<(const dump &stream, unsigned long num) {
return SafeWriteNumber(stream, num);
}
const dump &operator<<(const dump &stream, unsigned long long num) {
return SafeWriteNumber(stream, num);
}
const dump &operator<<(const dump &stream, double num) {
if (num < 0) {
SafeWriteChar('-');
num = -num;
}
SafeWriteNumber(stream, uint64(floor(num)));
SafeWriteChar('.');
num -= floor(num);
for (int i = 0; i < 4; ++i) {
num *= 10;
int digit = int(floor(num));
SafeWriteChar('0' + digit);
num -= digit;
}
return stream;
}
#endif // TDESKTOP_DISABLE_CRASH_REPORTS
} // namespace CrashReports

View File

@@ -0,0 +1,85 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
namespace CrashReports {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
struct dump {
~dump();
};
const dump &operator<<(const dump &stream, const char *str);
const dump &operator<<(const dump &stream, const wchar_t *str);
const dump &operator<<(const dump &stream, int num);
const dump &operator<<(const dump &stream, unsigned int num);
const dump &operator<<(const dump &stream, unsigned long num);
const dump &operator<<(const dump &stream, unsigned long long num);
const dump &operator<<(const dump &stream, double num);
#endif // TDESKTOP_DISABLE_CRASH_REPORTS
enum Status {
CantOpen,
LastCrashed,
Started
};
Status Start();
Status Restart(); // can be only CantOpen or Started
void Finish();
void SetAnnotation(const std::string &key, const QString &value);
inline void ClearAnnotation(const std::string &key) {
SetAnnotation(key, QString());
}
// Remembers value pointer and tries to add the value to the crash report.
// Attention! You should call clearCrashAnnotationRef(key) before destroying value.
void SetAnnotationRef(const std::string &key, const QString *valuePtr);
inline void ClearAnnotationRef(const std::string &key) {
SetAnnotationRef(key, nullptr);
}
void StartCatching();
void FinishCatching();
} // namespace CrashReports
namespace base {
namespace assertion {
inline void log(const char *message, const char *file, int line) {
const auto info = QStringLiteral("%1 %2:%3"
).arg(message
).arg(file
).arg(line
);
const auto entry = QStringLiteral("Assertion Failed! ") + info;
#ifdef LOG
LOG((entry));
#endif // LOG
CrashReports::SetAnnotation("Assertion", info);
}
} // namespace assertion
} // namespace base

View File

@@ -0,0 +1,243 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "core/launcher.h"
#include "platform/platform_launcher.h"
#include "platform/platform_specific.h"
#include "core/crash_reports.h"
#include "application.h"
namespace Core {
std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
return std::make_unique<Platform::Launcher>(argc, argv);
}
Launcher::Launcher(int argc, char *argv[])
: _argc(argc)
, _argv(argv) {
}
void Launcher::init() {
_arguments = readArguments(_argc, _argv);
prepareSettings();
QCoreApplication::setApplicationName(qsl("TelegramDesktop"));
#ifndef OS_MAC_OLD
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
#endif // OS_MAC_OLD
initHook();
}
int Launcher::exec() {
init();
if (cLaunchMode() == LaunchModeFixPrevious) {
return psFixPrevious();
} else if (cLaunchMode() == LaunchModeCleanup) {
return psCleanup();
}
// both are finished in Application::closeApplication
Logs::start(this); // must be started before Platform is started
Platform::start(); // must be started before QApplication is created
auto result = 0;
{
Application app(this, _argc, _argv);
result = app.exec();
}
DEBUG_LOG(("Telegram finished, result: %1").arg(result));
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
if (cRestartingUpdate()) {
DEBUG_LOG(("Application Info: executing updater to install update..."));
if (!launchUpdater(UpdaterLaunch::PerformUpdate)) {
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
}
} else
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
if (cRestarting()) {
DEBUG_LOG(("Application Info: executing Telegram, because of restart..."));
launchUpdater(UpdaterLaunch::JustRelaunch);
}
CrashReports::Finish();
Platform::finish();
Logs::finish();
return result;
}
QStringList Launcher::readArguments(int argc, char *argv[]) const {
Expects(argc >= 0);
if (const auto native = readArgumentsHook(argc, argv)) {
return *native;
}
auto result = QStringList();
result.reserve(argc);
for (auto i = 0; i != argc; ++i) {
result.push_back(fromUtf8Safe(argv[i]));
}
return result;
}
QString Launcher::argumentsString() const {
return _arguments.join(' ');
}
void Launcher::prepareSettings() {
#ifdef Q_OS_MAC
#ifndef OS_MAC_OLD
if (QSysInfo::macVersion() >= QSysInfo::MV_10_11) {
gIsElCapitan = true;
}
#else // OS_MAC_OLD
if (QSysInfo::macVersion() < QSysInfo::MV_10_7) {
gIsSnowLeopard = true;
}
#endif // OS_MAC_OLD
#endif // Q_OS_MAC
switch (cPlatform()) {
case dbipWindows:
gUpdateURL = QUrl(qsl("http://tdesktop.com/win/tupdates/current"));
#ifndef OS_WIN_STORE
gPlatformString = qsl("Windows");
#else // OS_WIN_STORE
gPlatformString = qsl("WinStore");
#endif // OS_WIN_STORE
break;
case dbipMac:
gUpdateURL = QUrl(qsl("http://tdesktop.com/mac/tupdates/current"));
#ifndef OS_MAC_STORE
gPlatformString = qsl("MacOS");
#else // OS_MAC_STORE
gPlatformString = qsl("MacAppStore");
#endif // OS_MAC_STORE
break;
case dbipMacOld:
gUpdateURL = QUrl(qsl("http://tdesktop.com/mac32/tupdates/current"));
gPlatformString = qsl("MacOSold");
break;
case dbipLinux64:
gUpdateURL = QUrl(qsl("http://tdesktop.com/linux/tupdates/current"));
gPlatformString = qsl("Linux64bit");
break;
case dbipLinux32:
gUpdateURL = QUrl(qsl("http://tdesktop.com/linux32/tupdates/current"));
gPlatformString = qsl("Linux32bit");
break;
}
auto path = Platform::CurrentExecutablePath(_argc, _argv);
LOG(("Executable path before check: %1").arg(path));
if (!path.isEmpty()) {
auto info = QFileInfo(path);
if (info.isSymLink()) {
info = info.symLinkTarget();
}
if (info.exists()) {
gExeDir = info.absoluteDir().absolutePath() + '/';
gExeName = info.fileName();
}
}
if (cExeName().isEmpty()) {
LOG(("WARNING: Could not compute executable path, some features will be disabled."));
}
processArguments();
}
void Launcher::processArguments() {
enum class KeyFormat {
NoValues,
OneValue,
AllLeftValues,
};
auto parseMap = std::map<QByteArray, KeyFormat> {
{ "-testmode" , KeyFormat::NoValues },
{ "-debug" , KeyFormat::NoValues },
{ "-many" , KeyFormat::NoValues },
{ "-key" , KeyFormat::OneValue },
{ "-autostart" , KeyFormat::NoValues },
{ "-fixprevious", KeyFormat::NoValues },
{ "-cleanup" , KeyFormat::NoValues },
{ "-noupdate" , KeyFormat::NoValues },
{ "-tosettings" , KeyFormat::NoValues },
{ "-startintray", KeyFormat::NoValues },
{ "-sendpath" , KeyFormat::AllLeftValues },
{ "-workdir" , KeyFormat::OneValue },
{ "--" , KeyFormat::OneValue },
};
auto parseResult = QMap<QByteArray, QStringList>();
auto parsingKey = QByteArray();
auto parsingFormat = KeyFormat::NoValues;
for (const auto &argument : _arguments) {
switch (parsingFormat) {
case KeyFormat::OneValue: {
parseResult[parsingKey] = QStringList(argument.mid(0, 8192));
parsingFormat = KeyFormat::NoValues;
} break;
case KeyFormat::AllLeftValues: {
parseResult[parsingKey].push_back(argument.mid(0, 8192));
} break;
case KeyFormat::NoValues: {
parsingKey = argument.toLatin1();
auto it = parseMap.find(parsingKey);
if (it != parseMap.end()) {
parsingFormat = it->second;
parseResult[parsingKey] = QStringList();
}
} break;
}
}
gTestMode = parseResult.contains("-testmode");
gDebug = parseResult.contains("-debug");
gManyInstance = parseResult.contains("-many");
gKeyFile = parseResult.value("-key", QStringList()).join(QString());
gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart
: parseResult.contains("-fixprevious") ? LaunchModeFixPrevious
: parseResult.contains("-cleanup") ? LaunchModeCleanup
: LaunchModeNormal;
gNoStartUpdate = parseResult.contains("-noupdate");
gStartToSettings = parseResult.contains("-tosettings");
gStartInTray = parseResult.contains("-startintray");
gSendPaths = parseResult.value("-sendpath", QStringList());
gWorkingDir = parseResult.value("-workdir", QStringList()).join(QString());
if (!gWorkingDir.isEmpty()) {
if (QDir().exists(gWorkingDir)) {
_customWorkingDir = true;
} else {
gWorkingDir = QString();
}
}
gStartUrl = parseResult.value("--", QStringList()).join(QString());
}
} // namespace Core

View File

@@ -0,0 +1,70 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
namespace Core {
class Launcher {
public:
Launcher(int argc, char *argv[]);
static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
int exec();
QString argumentsString() const;
bool customWorkingDir() const {
return _customWorkingDir;
}
protected:
enum class UpdaterLaunch {
PerformUpdate,
JustRelaunch,
};
private:
void prepareSettings();
void processArguments();
QStringList readArguments(int argc, char *argv[]) const;
virtual base::optional<QStringList> readArgumentsHook(
int argc,
char *argv[]) const {
return base::none;
}
void init();
virtual void initHook() {
}
virtual bool launchUpdater(UpdaterLaunch action) = 0;
int _argc;
char **_argv;
QStringList _arguments;
bool _customWorkingDir = false;
};
} // namespace Core

View File

@@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#define BETA_VERSION_MACRO (0ULL)
constexpr int AppVersion = 1002000;
constexpr str_const AppVersionStr = "1.2";
constexpr bool AppAlphaVersion = false;
constexpr int AppVersion = 1002002;
constexpr str_const AppVersionStr = "1.2.2";
constexpr bool AppAlphaVersion = true;
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;

View File

@@ -224,7 +224,10 @@ QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = fals
return saveFileName(caption, filter, prefix, name, forceSavingAs, dir);
}
void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, ActionOnLoad action) {
void DocumentOpenClickHandler::doOpen(
not_null<DocumentData*> data,
HistoryItem *context,
ActionOnLoad action) {
if (!data->date) return;
auto msgId = context ? context->fullId() : FullMsgId();
@@ -329,9 +332,13 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
}
void DocumentOpenClickHandler::onClickImpl() const {
const auto item = App::hoveredLinkItem()
const auto item = context()
? App::histItemById(context())
: App::hoveredLinkItem()
? App::hoveredLinkItem()
: (App::contextItem() ? App::contextItem() : nullptr);
: App::contextItem()
? App::contextItem()
: nullptr;
const auto action = document()->isVoiceMessage()
? ActionOnLoadNone
: ActionOnLoadOpen;
@@ -339,13 +346,19 @@ void DocumentOpenClickHandler::onClickImpl() const {
}
void GifOpenClickHandler::onClickImpl() const {
const auto item = App::hoveredLinkItem()
const auto item = context()
? App::histItemById(context())
: App::hoveredLinkItem()
? App::hoveredLinkItem()
: (App::contextItem() ? App::contextItem() : nullptr);
: App::contextItem()
? App::contextItem()
: nullptr;
doOpen(document(), item, ActionOnLoadPlayInline);
}
void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) {
void DocumentSaveClickHandler::doSave(
not_null<DocumentData*> data,
bool forceSavingAs) {
if (!data->date) return;
auto filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs);

View File

@@ -311,15 +311,22 @@ QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);
class DocumentClickHandler : public LeftButtonClickHandler {
public:
DocumentClickHandler(DocumentData *document)
: _document(document) {
DocumentClickHandler(
not_null<DocumentData*> document,
FullMsgId context = FullMsgId())
: _document(document)
, _context(context) {
}
DocumentData *document() const {
not_null<DocumentData*> document() const {
return _document;
}
FullMsgId context() const {
return _context;
}
private:
DocumentData *_document;
not_null<DocumentData*> _document;
FullMsgId _context;
};
@@ -327,7 +334,7 @@ class DocumentSaveClickHandler : public DocumentClickHandler {
public:
using DocumentClickHandler::DocumentClickHandler;
static void doSave(
DocumentData *document,
not_null<DocumentData*> document,
bool forceSavingAs = false);
protected:
@@ -339,7 +346,7 @@ class DocumentOpenClickHandler : public DocumentClickHandler {
public:
using DocumentClickHandler::DocumentClickHandler;
static void doOpen(
DocumentData *document,
not_null<DocumentData*> document,
HistoryItem *context,
ActionOnLoad action = ActionOnLoadOpen);

View File

@@ -277,12 +277,7 @@ void PeerData::updateUserpic(
const auto size = kUserpicSize;
const auto loc = StorageImageLocation::FromMTP(size, size, location);
const auto photo = loc.isNull() ? ImagePtr() : ImagePtr(loc);
if (_userpicPhotoId != photoId
|| _userpic.v() != photo.v()
|| _userpicLocation != loc) {
setUserpic(photoId, loc, photo);
Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged);
}
setUserpicChecked(photoId, loc, photo);
}
void PeerData::clearUserpic() {
@@ -300,6 +295,19 @@ void PeerData::clearUserpic() {
}
return ImagePtr();
}();
setUserpicChecked(photoId, loc, photo);
}
void PeerData::setUserpicChecked(
PhotoId photoId,
const StorageImageLocation &location,
ImagePtr userpic) {
if (_userpicPhotoId != photoId
|| _userpic.v() != userpic.v()
|| _userpicLocation != location) {
setUserpic(photoId, location, userpic);
Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged);
}
}
void PeerData::fillNames() {

View File

@@ -238,6 +238,11 @@ private:
std::unique_ptr<Ui::EmptyUserpic> createEmptyUserpic() const;
void refreshEmptyUserpic() const;
void setUserpicChecked(
PhotoId photoId,
const StorageImageLocation &location,
ImagePtr userpic);
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
ImagePtr _userpic;

View File

@@ -121,7 +121,7 @@ ImagePtr PhotoData::makeReplyPreview() {
}
void PhotoOpenClickHandler::onClickImpl() const {
Messenger::Instance().showPhoto(this, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem());
Messenger::Instance().showPhoto(this);
}
void PhotoSaveClickHandler::onClickImpl() const {
@@ -136,13 +136,9 @@ void PhotoCancelClickHandler::onClickImpl() const {
if (!data->date) return;
if (data->uploading()) {
if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) {
if (auto media = item->getMedia()) {
if (media->type() == MediaTypePhoto && static_cast<HistoryPhoto*>(media)->photo() == data) {
App::contextItem(item);
App::main()->cancelUploadLayer();
}
}
if (const auto item = App::histItemById(context())) {
App::contextItem(item);
App::main()->cancelUploadLayer();
}
} else {
data->cancel();

View File

@@ -74,8 +74,11 @@ class PhotoClickHandler : public LeftButtonClickHandler {
public:
PhotoClickHandler(
not_null<PhotoData*> photo,
FullMsgId context = FullMsgId(),
PeerData *peer = nullptr)
: _photo(photo), _peer(peer) {
: _photo(photo)
, _context(context)
, _peer(peer) {
}
not_null<PhotoData*> photo() const {
return _photo;
@@ -83,10 +86,14 @@ public:
PeerData *peer() const {
return _peer;
}
FullMsgId context() const {
return _context;
}
private:
not_null<PhotoData*> _photo;
PeerData *_peer;
FullMsgId _context;
PeerData *_peer = nullptr;
};

View File

@@ -290,9 +290,7 @@ base::optional<bool> SharedMediaWithLastSlice::IsLastIsolated(
| [](FullMsgId msgId) { return App::histItemById(msgId); }
| [](HistoryItem *item) { return item ? item->getMedia() : nullptr; }
| [](HistoryMedia *media) {
return (media && media->type() == MediaTypePhoto)
? static_cast<HistoryPhoto*>(media)->photo()
: nullptr;
return media ? media->getPhoto() : nullptr;
}
| [](PhotoData *photo) { return photo ? photo->id : 0; }
| [&](PhotoId photoId) { return *lastPeerPhotoId != photoId; };

View File

@@ -36,6 +36,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "apiwrap.h"
#include "window/themes/window_theme.h"
#include "observer_peer.h"
#include "chat_helpers/stickers.h"
#include "auth_session.h"
#include "window/notifications_manager.h"
#include "window/window_controller.h"
@@ -2123,7 +2124,7 @@ void DialogsInner::saveRecentHashtags(const QString &text) {
recent = cRecentSearchHashtags();
}
found = true;
incrementRecentHashtag(recent, text.mid(i + 1, next - i - 1));
Stickers::IncrementRecentHashtag(recent, text.mid(i + 1, next - i - 1));
}
if (found) {
cSetRecentSearchHashtags(recent);

View File

@@ -38,6 +38,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "data/data_channel_admins.h"
#include "core/crash_reports.h"
namespace {
@@ -795,12 +796,7 @@ void Histories::checkSelfDestructItems() {
}
HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) {
auto msgId = MsgId(0);
switch (msg.type()) {
case mtpc_messageEmpty: msgId = msg.c_messageEmpty().vid.v; break;
case mtpc_message: msgId = msg.c_message().vid.v; break;
case mtpc_messageService: msgId = msg.c_messageService().vid.v; break;
}
const auto msgId = idFromMessage(msg);
if (!msgId) return nullptr;
auto result = App::histItemById(channelId(), msgId);
@@ -809,7 +805,10 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
result->detach();
}
if (msg.type() == mtpc_message) {
result->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0);
const auto media = msg.c_message().has_media()
? &msg.c_message().vmedia
: nullptr;
result->updateMedia(media);
if (applyServiceAction) {
App::checkSavedGif(result);
}
@@ -1093,33 +1092,37 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
return result;
}
HistoryItem *History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg) {
not_null<HistoryItem*> History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg) {
return HistoryMessage::create(this, id, flags, date, from, postAuthor, msg);
}
HistoryItem *History::createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, doc, caption, markup);
}
HistoryItem *History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, photo, caption, markup);
}
HistoryItem *History::createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, game, markup);
}
HistoryItem *History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
not_null<HistoryItem*> History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
auto message = HistoryService::PreparedText { text };
return addNewItem(HistoryService::create(this, msgId, date, message, flags), newMsg);
}
HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) {
if (isChannel()) return asChannelHistory()->addNewChannelMessage(msg, type);
if (isChannel()) {
return asChannelHistory()->addNewChannelMessage(msg, type);
}
if (type == NewMessageExisting) return addToHistory(msg);
if (type == NewMessageExisting) {
return addToHistory(msg);
}
if (!loadedAtBottom() || peer->migrateTo()) {
HistoryItem *item = addToHistory(msg);
const auto item = addToHistory(msg);
if (item) {
setLastMessage(item);
if (type == NewMessageUnread) {
@@ -1133,32 +1136,43 @@ HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type)
}
HistoryItem *History::addNewToLastBlock(const MTPMessage &msg, NewMessageType type) {
auto applyServiceAction = (type == NewMessageUnread);
auto detachExistingItem = (type != NewMessageLast);
auto item = createItem(msg, applyServiceAction, detachExistingItem);
const auto applyServiceAction = (type == NewMessageUnread);
const auto detachExistingItem = (type != NewMessageLast);
const auto item = createItem(msg, applyServiceAction, detachExistingItem);
if (!item || !item->detached()) {
return item;
}
return addNewItem(item, (type == NewMessageUnread));
const auto result = addNewItem(item, (type == NewMessageUnread));
if (type == NewMessageLast) {
// When we add just one last item, like we do while loading dialogs,
// we want to remove a single added grouped media, otherwise it will
// jump once we open the message history (first we show only that
// media, then we load the rest of the group and show the group).
//
// That way when we open the message history we show nothing until a
// whole history part is loaded, it certainly will contain the group.
removeOrphanMediaGroupPart();
}
return result;
}
HistoryItem *History::addToHistory(const MTPMessage &msg) {
return createItem(msg, false, false);
}
HistoryItem *History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item) {
not_null<HistoryItem*> History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item) {
return addNewItem(createItemForwarded(id, flags, date, from, postAuthor, item), true);
}
HistoryItem *History::addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
return addNewItem(createItemDocument(id, flags, viaBotId, replyTo, date, from, postAuthor, doc, caption, markup), true);
}
HistoryItem *History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
return addNewItem(createItemPhoto(id, flags, viaBotId, replyTo, date, from, postAuthor, photo, caption, markup), true);
}
HistoryItem *History::addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
not_null<HistoryItem*> History::addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
return addNewItem(createItemGame(id, flags, viaBotId, replyTo, date, from, postAuthor, game, markup), true);
}
@@ -1250,10 +1264,16 @@ void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) {
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged);
}
HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
not_null<HistoryItem*> History::addNewItem(not_null<HistoryItem*> adding, bool newMsg) {
Expects(!isBuildingFrontBlock());
addItemToBlock(adding);
const auto [groupFrom, groupTill] = recountGroupingFromTill(adding);
if (groupFrom != groupTill || groupFrom->groupId()) {
recountGrouping(groupFrom, groupTill);
}
setLastMessage(adding);
if (newMsg) {
newItemAdded(adding);
@@ -1433,8 +1453,7 @@ HistoryBlock *History::prepareBlockForAddingItem() {
return result;
};
void History::addItemToBlock(HistoryItem *item) {
Expects(item != nullptr);
void History::addItemToBlock(not_null<HistoryItem*> item) {
Expects(item->detached());
auto block = prepareBlockForAddingItem();
@@ -1499,7 +1518,7 @@ void History::addToSharedMedia(std::vector<MsgId> (&medias)[kSharedMediaTypeCoun
}
value.push_back(indices.join(","));
}
SignalHandlers::setCrashAnnotation("full", value.join(";"));
CrashReports::SetAnnotation("full", value.join(";"));
Assert(!"History desync caught!");
//// Logging
@@ -1527,6 +1546,9 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
return;
}
auto firstAdded = (HistoryItem*)nullptr;
auto lastAdded = (HistoryItem*)nullptr;
auto logged = QStringList();
logged.push_back(QString::number(minMsgId()));
logged.push_back(QString::number(maxMsgId()));
@@ -1538,9 +1560,12 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
--i;
auto adding = createItem(*i, false, true);
const auto adding = createItem(*i, false, true);
if (!adding) continue;
if (!firstAdded) firstAdded = adding;
lastAdded = adding;
if (minAdded < 0 || minAdded > adding->id) {
minAdded = adding->id;
}
@@ -1631,11 +1656,16 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
logged.push_back(QString::number(minAdded));
logged.push_back(QString::number(maxAdded));
SignalHandlers::setCrashAnnotation("old_minmaxwas_minmaxadd", logged.join(";"));
CrashReports::SetAnnotation("old_minmaxwas_minmaxadd", logged.join(";"));
addBlockToSharedMedia(block);
SignalHandlers::setCrashAnnotation("old_minmaxwas_minmaxadd", "");
CrashReports::ClearAnnotation("old_minmaxwas_minmaxadd");
if (lastAdded) {
const auto [from, till] = recountGroupingFromTill(lastAdded);
recountGrouping(firstAdded, till);
}
if (isChannel()) {
asChannelHistory()->checkJoinedMessage();
@@ -1654,6 +1684,9 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
}
}
auto firstAdded = (HistoryItem*)nullptr;
auto lastAdded = (HistoryItem*)nullptr;
Assert(!isBuildingFrontBlock());
if (!slice.isEmpty()) {
auto logged = QStringList();
@@ -1664,12 +1697,14 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
auto maxAdded = -1;
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
auto atLeastOneAdded = false;
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
--i;
auto adding = createItem(*i, false, true);
const auto adding = createItem(*i, false, true);
if (!adding) continue;
if (!firstAdded) firstAdded = adding;
lastAdded = adding;
if (minAdded < 0 || minAdded > adding->id) {
minAdded = adding->id;
}
@@ -1678,7 +1713,6 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
}
addItemToBlock(adding);
atLeastOneAdded = true;
if (auto types = adding->sharedMediaTypes()) {
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
auto type = static_cast<Storage::SharedMediaType>(i);
@@ -1693,21 +1727,26 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
}
logged.push_back(QString::number(minAdded));
logged.push_back(QString::number(maxAdded));
SignalHandlers::setCrashAnnotation("new_minmaxwas_minmaxadd", logged.join(";"));
CrashReports::SetAnnotation("new_minmaxwas_minmaxadd", logged.join(";"));
if (!atLeastOneAdded) {
if (!firstAdded) {
newLoaded = true;
setLastMessage(lastAvailableMessage());
}
addToSharedMedia(medias, wasLoadedAtBottom != loadedAtBottom());
SignalHandlers::setCrashAnnotation("new_minmaxwas_minmaxadd", "");
CrashReports::ClearAnnotation("new_minmaxwas_minmaxadd");
}
if (!wasLoadedAtBottom) {
checkAddAllToUnreadMentions();
}
if (firstAdded) {
const auto [from, till] = recountGroupingFromTill(firstAdded);
recountGrouping(from, lastAdded);
}
if (isChannel()) asChannelHistory()->checkJoinedMessage();
checkLastMsg();
}
@@ -2006,7 +2045,7 @@ void History::destroyUnreadBar() {
}
}
HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex) {
not_null<HistoryItem*> History::addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex) {
Expects(blockIndex >= 0);
Expects(blockIndex < blocks.size());
Expects(itemIndex >= 0);
@@ -2028,9 +2067,135 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex,
newItem->nextItemChanged();
}
const auto [groupFrom, groupTill] = recountGroupingFromTill(newItem);
if (groupFrom != groupTill || groupFrom->groupId()) {
recountGrouping(groupFrom, groupTill);
}
return newItem;
}
HistoryItem *History::findNextItem(not_null<HistoryItem*> item) const {
Expects(!item->detached());
const auto nextBlockIndex = item->block()->indexInHistory() + 1;
const auto nextItemIndex = item->indexInBlock() + 1;
if (nextItemIndex < int(item->block()->items.size())) {
return item->block()->items[nextItemIndex];
} else if (nextBlockIndex < int(blocks.size())) {
return blocks[nextBlockIndex]->items.front();
}
return nullptr;
}
HistoryItem *History::findPreviousItem(not_null<HistoryItem*> item) const {
Expects(!item->detached());
const auto blockIndex = item->block()->indexInHistory();
const auto itemIndex = item->indexInBlock();
if (itemIndex > 0) {
return item->block()->items[itemIndex - 1];
} else if (blockIndex > 0) {
return blocks[blockIndex - 1]->items.back();
}
return nullptr;
}
not_null<HistoryItem*> History::findGroupFirst(
not_null<HistoryItem*> item) const {
const auto group = item->Get<HistoryMessageGroup>();
Assert(group != nullptr);
Assert(group->leader != nullptr);
const auto leaderGroup = (group->leader == item)
? group
: group->leader->Get<HistoryMessageGroup>();
Assert(leaderGroup != nullptr);
return leaderGroup->others.empty()
? group->leader
: leaderGroup->others.front().get();
}
not_null<HistoryItem*> History::findGroupLast(
not_null<HistoryItem*> item) const {
const auto group = item->Get<HistoryMessageGroup>();
Assert(group != nullptr);
return group->leader;
}
void History::recountGroupingAround(not_null<HistoryItem*> item) {
Expects(item->history() == this);
if (!item->detached() && item->groupId()) {
const auto [groupFrom, groupTill] = recountGroupingFromTill(item);
recountGrouping(groupFrom, groupTill);
}
}
auto History::recountGroupingFromTill(not_null<HistoryItem*> item)
-> std::pair<not_null<HistoryItem*>, not_null<HistoryItem*>> {
const auto recountFromItem = [&] {
if (const auto prev = findPreviousItem(item)) {
if (prev->groupId()) {
return findGroupFirst(prev);
}
}
return item;
}();
if (recountFromItem == item && !item->groupId()) {
return { item, item };
}
const auto recountTillItem = [&] {
if (const auto next = findNextItem(item)) {
if (next->groupId()) {
return findGroupLast(next);
}
}
return item;
}();
return { recountFromItem, recountTillItem };
}
void History::recountGrouping(
not_null<HistoryItem*> from,
not_null<HistoryItem*> till) {
Expects(!from->detached());
Expects(!till->detached());
from->validateGroupId();
auto others = std::vector<not_null<HistoryItem*>>();
auto currentGroupId = from->groupId();
auto prev = from;
while (prev != till) {
auto item = findNextItem(prev);
item->validateGroupId();
const auto groupId = item->groupId();
if (currentGroupId) {
if (groupId == currentGroupId) {
others.push_back(prev);
} else {
for (const auto other : others) {
other->makeGroupMember(prev);
}
prev->makeGroupLeader(base::take(others));
currentGroupId = groupId;
}
} else if (groupId) {
currentGroupId = groupId;
}
prev = item;
}
if (currentGroupId) {
for (const auto other : others) {
other->makeGroupMember(prev);
}
till->makeGroupLeader(base::take(others));
}
}
void History::startBuildingFrontBlock(int expectedItemsCount) {
Assert(!isBuildingFrontBlock());
Assert(expectedItemsCount > 0);
@@ -2277,6 +2442,25 @@ bool History::isDisplayedEmpty() const {
return isEmpty() || ((blocks.size() == 1) && blocks.front()->items.size() == 1 && blocks.front()->items.front()->isEmpty());
}
bool History::hasOrphanMediaGroupPart() const {
if (loadedAtTop() || !loadedAtBottom()) {
return false;
} else if (blocks.size() != 1) {
return false;
} else if (blocks.front()->items.size() != 1) {
return false;
}
return blocks.front()->items.front()->groupId() != MessageGroupId();
}
bool History::removeOrphanMediaGroupPart() {
if (hasOrphanMediaGroupPart()) {
clear(true);
return true;
}
return false;
}
void History::clear(bool leaveItems) {
if (unreadBar) {
unreadBar = nullptr;
@@ -2470,7 +2654,7 @@ void History::setPinnedIndex(int pinnedIndex) {
void History::changeMsgId(MsgId oldId, MsgId newId) {
}
void History::removeBlock(HistoryBlock *block) {
void History::removeBlock(not_null<HistoryBlock*> block) {
Expects(block->items.empty());
if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
@@ -2521,9 +2705,21 @@ void HistoryBlock::clear(bool leaveItems) {
}
}
void HistoryBlock::removeItem(HistoryItem *item) {
void HistoryBlock::removeItem(not_null<HistoryItem*> item) {
Expects(item->block() == this);
auto [groupFrom, groupTill] = _history->recountGroupingFromTill(item);
const auto groupHistory = _history;
const auto needGroupRecount = (groupFrom != groupTill);
if (needGroupRecount) {
if (groupFrom == item) {
groupFrom = groupHistory->findNextItem(groupFrom);
}
if (groupTill == item) {
groupTill = groupHistory->findPreviousItem(groupTill);
}
}
auto blockIndex = indexInHistory();
auto itemIndex = item->indexInBlock();
if (_history->showFrom == item) {
@@ -2557,4 +2753,8 @@ void HistoryBlock::removeItem(HistoryItem *item) {
if (items.empty()) {
delete this;
}
if (needGroupRecount) {
groupHistory->recountGrouping(groupFrom, groupTill);
}
}

View File

@@ -136,6 +136,7 @@ enum HistoryMediaType {
MediaTypeVoiceFile,
MediaTypeGame,
MediaTypeInvoice,
MediaTypeGrouped,
MediaTypeCount
};
@@ -209,6 +210,8 @@ public:
return blocks.empty();
}
bool isDisplayedEmpty() const;
bool hasOrphanMediaGroupPart() const;
bool removeOrphanMediaGroupPart();
void clear(bool leaveItems = false);
void clearUpTill(MsgId availableMinId);
@@ -217,13 +220,13 @@ public:
virtual ~History();
HistoryItem *addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type);
HistoryItem *addToHistory(const MTPMessage &msg);
HistoryItem *addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
HistoryItem *addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
HistoryItem *addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
HistoryItem *addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
not_null<HistoryItem*> addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
not_null<HistoryItem*> addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
not_null<HistoryItem*> addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
not_null<HistoryItem*> addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
not_null<HistoryItem*> addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
// Used only internally and for channel admin log.
HistoryItem *createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem);
@@ -419,6 +422,7 @@ public:
}
HistoryItemsList validateForwardDraft();
void setForwardDraft(MessageIdsList &&items);
void recountGroupingAround(not_null<HistoryItem*> item);
// some fields below are a property of a currently displayed instance of this
// conversation history not a property of the conversation history itself
@@ -475,17 +479,17 @@ protected:
// this method just removes a block from the blocks list
// when the last item from this block was detached and
// calls the required previousItemChanged()
void removeBlock(HistoryBlock *block);
void removeBlock(not_null<HistoryBlock*> block);
void clearBlocks(bool leaveItems);
HistoryItem *createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
HistoryItem *createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
HistoryItem *createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
not_null<HistoryItem*> createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
not_null<HistoryItem*> createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
not_null<HistoryItem*> createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
not_null<HistoryItem*> createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
HistoryItem *addNewItem(HistoryItem *adding, bool newMsg);
HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex);
not_null<HistoryItem*> addNewItem(not_null<HistoryItem*> adding, bool newMsg);
not_null<HistoryItem*> addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex);
// All this methods add a new item to the first or last block
// depending on if we are in isBuildingFronBlock() state.
@@ -493,7 +497,7 @@ protected:
// Adds the item to the back or front block, depending on
// isBuildingFrontBlock(), creating the block if necessary.
void addItemToBlock(HistoryItem *item);
void addItemToBlock(not_null<HistoryItem*> item);
// Usually all new items are added to the last block.
// Only when we scroll up and add a new slice to the
@@ -517,6 +521,18 @@ private:
void clearSendAction(not_null<UserData*> from);
HistoryItem *findPreviousItem(not_null<HistoryItem*> item) const;
HistoryItem *findNextItem(not_null<HistoryItem*> item) const;
not_null<HistoryItem*> findGroupFirst(
not_null<HistoryItem*> item) const;
not_null<HistoryItem*> findGroupLast(
not_null<HistoryItem*> item) const;
auto recountGroupingFromTill(not_null<HistoryItem*> item)
-> std::pair<not_null<HistoryItem*>, not_null<HistoryItem*>>;
void recountGrouping(
not_null<HistoryItem*> from,
not_null<HistoryItem*> till);
enum class Flag {
f_has_pending_resized_items = (1 << 0),
f_pending_resize = (1 << 1),
@@ -624,7 +640,7 @@ public:
~HistoryBlock() {
clear();
}
void removeItem(HistoryItem *item);
void removeItem(not_null<HistoryItem*> item);
int resizeGetHeight(int newWidth, bool resizeAllItems);
int y() const {

View File

@@ -461,3 +461,9 @@ historyFastShareIcon: icon {{ "fast_share", msgServiceFg, point(4px, 3px)}};
historyGoToOriginalIcon: icon {{ "title_back-flip_horizontal", msgServiceFg, point(8px, 7px) }};
historySavedFont: font(semibold 14px);
historyGroupWidthMax: maxMediaSize;
historyGroupWidthMin: minPhotoSize;
historyGroupSkip: 4px;
historyGroupRadialSize: 44px;
historyGroupRadialLine: 3px;

View File

@@ -28,6 +28,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_media_types.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h"
#include "window/window_peer_menu.h"
#include "boxes/confirm_box.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/stickers.h"
#include "history/history_widget.h"
@@ -85,6 +87,43 @@ int BinarySearchBlocksOrItems(const T &list, int edge) {
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
class HistoryInner::BotAbout : public ClickHandlerHost {
public:
BotAbout(not_null<HistoryInner*> parent, not_null<BotInfo*> info);
// ClickHandlerHost interface
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
not_null<BotInfo*> info;
int width = 0;
int height = 0;
QRect rect;
private:
not_null<HistoryInner*> _parent;
};
HistoryInner::BotAbout::BotAbout(
not_null<HistoryInner*> parent,
not_null<BotInfo*> info)
: info(info)
, _parent(parent) {
}
void HistoryInner::BotAbout::clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) {
_parent->update(rect);
}
void HistoryInner::BotAbout::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
_parent->update(rect);
}
HistoryInner::HistoryInner(
not_null<HistoryWidget*> historyWidget,
not_null<Window::Controller*> controller,
@@ -363,6 +402,61 @@ void HistoryInner::enumerateDates(Method method) {
enumerateItems<EnumItemsDirection::BottomToTop>(dateCallback);
}
TextSelection HistoryInner::computeRenderSelection(
not_null<const SelectedItems*> selected,
not_null<HistoryItem*> item) const {
const auto itemSelection = [&](not_null<HistoryItem*> item) {
auto i = selected->find(item);
if (i != selected->end()) {
return i->second;
}
return TextSelection();
};
const auto group = item->Get<HistoryMessageGroup>();
if (group) {
if (group->leader != item) {
return TextSelection();
}
auto result = TextSelection();
auto allFullSelected = true;
const auto count = int(group->others.size());
for (auto i = 0; i != count; ++i) {
if (itemSelection(group->others[i]) == FullSelection) {
result = AddGroupItemSelection(result, i);
} else {
allFullSelected = false;
}
}
const auto leaderSelection = itemSelection(item);
if (leaderSelection == FullSelection) {
return allFullSelected
? FullSelection
: AddGroupItemSelection(result, count);
} else if (leaderSelection != TextSelection()) {
return leaderSelection;
}
return result;
}
return itemSelection(item);
}
TextSelection HistoryInner::itemRenderSelection(
not_null<HistoryItem*> item,
int selfromy,
int seltoy) const {
Expects(!item->detached());
const auto y = item->block()->y() + item->y();
if (y >= selfromy && y < seltoy) {
if (_dragSelecting && !item->serviceMsg() && item->id > 0) {
return FullSelection;
}
} else if (!_selected.empty()) {
return computeRenderSelection(&_selected, item);
}
return TextSelection();
}
void HistoryInner::paintEvent(QPaintEvent *e) {
if (Ui::skipPaintEvent(this, e)) {
return;
@@ -399,9 +493,6 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
adjustCurrent(clip.top());
auto selEnd = _selected.cend();
auto hasSel = !_selected.empty();
auto drawToY = clip.y() + clip.height();
auto selfromy = itemTop(_dragSelFrom);
@@ -425,18 +516,11 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
p.save();
p.translate(0, y);
if (clip.y() < y + item->height()) while (y < drawToY) {
TextSelection sel;
if (y >= selfromy && y < seltoy) {
if (_dragSelecting && !item->serviceMsg() && item->id > 0) {
sel = FullSelection;
}
} else if (hasSel) {
auto i = _selected.find(item);
if (i != selEnd) {
sel = i->second;
}
}
item->draw(p, clip.translated(0, -y), sel, ms);
const auto selection = itemRenderSelection(
item,
selfromy - mtop,
seltoy - mtop);
item->draw(p, clip.translated(0, -y), selection, ms);
if (item->hasViews()) {
App::main()->scheduleViewIncrement(item);
@@ -469,25 +553,18 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto iItem = (_curHistory == _history ? _curItem : 0);
auto item = block->items[iItem];
auto historyRect = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height()));
auto hclip = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height()));
auto y = htop + block->y() + item->y();
p.save();
p.translate(0, y);
while (y < drawToY) {
auto h = item->height();
if (historyRect.y() < y + h && hdrawtop < y + h) {
TextSelection sel;
if (y >= selfromy && y < seltoy) {
if (_dragSelecting && !item->serviceMsg() && item->id > 0) {
sel = FullSelection;
}
} else if (hasSel) {
auto i = _selected.find(item);
if (i != selEnd) {
sel = i->second;
}
}
item->draw(p, historyRect.translated(0, -y), sel, ms);
if (hclip.y() < y + h && hdrawtop < y + h) {
const auto selection = itemRenderSelection(
item,
selfromy - htop,
seltoy - htop);
item->draw(p, hclip.translated(0, -y), selection, ms);
if (item->hasViews()) {
App::main()->scheduleViewIncrement(item);
@@ -830,7 +907,9 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but
_mouseAction = MouseAction::PrepareDrag;
} else if (!_selected.empty()) {
if (_selected.cbegin()->second == FullSelection) {
if (_selected.find(_mouseActionItem) != _selected.cend() && App::hoveredItem()) {
if (_dragStateItem
&& _selected.find(_dragStateItem) != _selected.cend()
&& App::hoveredItem()) {
_mouseAction = MouseAction::PrepareDrag; // start items drag
} else if (!_pressWasInactive) {
_mouseAction = MouseAction::PrepareSelect; // start items select
@@ -915,6 +994,7 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but
void HistoryInner::mouseActionCancel() {
_mouseActionItem = nullptr;
_dragStateItem = nullptr;
_mouseAction = MouseAction::None;
_dragStartPosition = QPoint(0, 0);
_dragSelFrom = _dragSelTo = nullptr;
@@ -928,7 +1008,8 @@ void HistoryInner::performDrag() {
bool uponSelected = false;
if (_mouseActionItem) {
if (!_selected.empty() && _selected.cbegin()->second == FullSelection) {
uponSelected = (_selected.find(_mouseActionItem) != _selected.cend());
uponSelected = _dragStateItem
&& (_selected.find(_dragStateItem) != _selected.cend());
} else {
HistoryStateRequest request;
request.flags |= Text::StateRequest::Flag::LookupSymbol;
@@ -986,7 +1067,7 @@ void HistoryInner::performDrag() {
forwardMimeType = qsl("application/x-td-forward-pressed");
}
}
if (auto pressedLnkItem = App::pressedLinkItem()) {
if (const auto pressedLnkItem = _mouseActionItem) {
if ((pressedMedia = pressedLnkItem->getMedia())) {
if (forwardMimeType.isEmpty() && pressedMedia->dragItemByHandler(pressedHandler)) {
forwardMimeType = qsl("application/x-td-forward-pressed-link");
@@ -1029,6 +1110,9 @@ void HistoryInner::itemRemoved(not_null<const HistoryItem*> item) {
if (_mouseActionItem == item) {
mouseActionCancel();
}
if (_dragStateItem == item) {
_dragStateItem = nullptr;
}
if (_dragSelFrom == item || _dragSelTo == item) {
_dragSelFrom = 0;
@@ -1041,15 +1125,14 @@ void HistoryInner::itemRemoved(not_null<const HistoryItem*> item) {
void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button) {
mouseActionUpdate(screenPos);
auto pressedLinkItem = App::pressedLinkItem();
auto activated = ClickHandler::unpressed();
if (_mouseAction == MouseAction::Dragging) {
activated.clear();
} else if (auto pressed = pressedLinkItem) {
} else if (_mouseActionItem) {
// if we are in selecting items mode perhaps we want to
// toggle selection instead of activating the pressed link
if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && !_selected.empty() && _selected.cbegin()->second == FullSelection && button != Qt::RightButton) {
if (auto media = pressed->getMedia()) {
if (auto media = _mouseActionItem->getMedia()) {
if (media->toggleSelectionByHandlerClick(activated)) {
activated.clear();
}
@@ -1068,29 +1151,30 @@ void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton bu
App::activateClickHandler(activated, button);
return;
}
if (_mouseAction == MouseAction::PrepareSelect && !_pressWasInactive && !_selected.empty() && _selected.cbegin()->second == FullSelection) {
auto i = _selected.find(_mouseActionItem);
if (i == _selected.cend()) {
if (!_mouseActionItem->serviceMsg()
&& IsServerMsgId(_mouseActionItem->id)
&& _selected.size() < MaxSelectedItems) {
if (!_selected.empty() && _selected.cbegin()->second != FullSelection) {
_selected.clear();
}
_selected.emplace(_mouseActionItem, FullSelection);
}
} else {
_selected.erase(i);
}
if ((_mouseAction == MouseAction::PrepareSelect)
&& !_pressWasInactive
&& !_selected.empty()
&& (_selected.cbegin()->second == FullSelection)) {
changeSelectionAsGroup(
&_selected,
_mouseActionItem,
SelectAction::Invert);
repaintItem(_mouseActionItem);
} else if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && button != Qt::RightButton) {
auto i = _selected.find(_mouseActionItem);
} else if ((_mouseAction == MouseAction::PrepareDrag)
&& !_pressWasInactive
&& _dragStateItem
&& (button != Qt::RightButton)) {
auto i = _selected.find(_dragStateItem);
if (i != _selected.cend() && i->second == FullSelection) {
_selected.erase(i);
repaintItem(_mouseActionItem);
} else if (i == _selected.cend() && !_mouseActionItem->serviceMsg() && _mouseActionItem->id > 0 && !_selected.empty() && _selected.cbegin()->second == FullSelection) {
} else if ((i == _selected.cend())
&& !_dragStateItem->serviceMsg()
&& (_dragStateItem->id > 0)
&& !_selected.empty()
&& _selected.cbegin()->second == FullSelection) {
if (_selected.size() < MaxSelectedItems) {
_selected.emplace(_mouseActionItem, FullSelection);
_selected.emplace(_dragStateItem, FullSelection);
repaintItem(_mouseActionItem);
}
} else {
@@ -1117,7 +1201,8 @@ void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton bu
#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64
if (!_selected.empty() && _selected.cbegin()->second != FullSelection) {
setToClipboard(_selected.cbegin()->first->selectedText(_selected.cbegin()->second), QClipboard::Selection);
const auto [item, selection] = *_selected.cbegin();
setToClipboard(item->selectedText(selection), QClipboard::Selection);
}
#endif // Q_OS_LINUX32 || Q_OS_LINUX64
}
@@ -1180,7 +1265,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
isUponSelected = -1;
if (_selected.cbegin()->second == FullSelection) {
hasSelected = 2;
if (App::hoveredItem() && _selected.find(App::hoveredItem()) != _selected.cend()) {
if (_dragStateItem && _selected.find(_dragStateItem) != _selected.cend()) {
isUponSelected = 2;
} else {
isUponSelected = -2;
@@ -1206,13 +1291,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu = new Ui::PopupMenu(nullptr);
_contextMenuLink = ClickHandler::getActive();
HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
PhotoClickHandler *lnkPhoto = dynamic_cast<PhotoClickHandler*>(_contextMenuLink.data());
DocumentClickHandler *lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data());
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(_contextMenuLink.data());
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data());
auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false;
auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false;
auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false;
if (lnkPhoto || lnkDocument) {
const auto item = _dragStateItem;
if (isUponSelected > 0) {
_menu->addAction(lang((isUponSelected > 1) ? lng_context_copy_selected_items : lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
@@ -1227,6 +1312,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
auto isPinned = item->isPinned();
_menu->addAction(lang(isPinned ? lng_context_unpin_msg : lng_context_pin_msg), _widget, isPinned ? SLOT(onUnpinMessage()) : SLOT(onPinMessage()));
}
App::contextItem(item);
}
if (lnkPhoto) {
_menu->addAction(lang(lng_context_save_image), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, photo = lnkPhoto->photo()] {
@@ -1267,23 +1353,46 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}));
}
_menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) {
} else if (item) {
if (isUponSelected != -2) {
if (App::hoveredLinkItem()->canForward()) {
_menu->addAction(lang(lng_context_forward_msg), _widget, SLOT(forwardMessage()))->setEnabled(true);
if (item->canForward()) {
_menu->addAction(lang(lng_context_forward_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
forwardItem(item);
}
}))->setEnabled(true);
}
if (App::hoveredLinkItem()->canDelete()) {
if (item->canDelete()) {
_menu->addAction(lang(lng_context_delete_msg), base::lambda_guarded(this, [this] {
_widget->confirmDeleteContextItem();
if (const auto item = App::contextItem()) {
deleteItem(item);
}
}));
}
}
if (App::hoveredLinkItem()->id > 0 && !App::hoveredLinkItem()->serviceMsg()) {
_menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true);
if (item && item->id > 0 && !item->serviceMsg()) {
_menu->addAction(lang(lng_context_select_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
if (!item->detached()) {
changeSelection(&_selected, item, SelectAction::Select);
repaintItem(item);
_widget->updateTopBarSelection();
}
}
}))->setEnabled(true);
}
App::contextItem(App::hoveredLinkItem());
App::contextItem(item);
}
} else { // maybe cursor on some text history item?
auto item = App::hoveredItem()
? App::hoveredItem()
: App::hoveredLinkItem();
const auto group = item->getFullGroup();
if (group) {
item = group->others.empty()
? group->leader
: group->others.front().get();
}
bool canDelete = item && item->canDelete() && (item->id > 0 || !item->serviceMsg());
bool canForward = item && item->canForward();
@@ -1381,21 +1490,43 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
} else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->id > 0)) {
if (isUponSelected != -2) {
if (canForward) {
_menu->addAction(lang(lng_context_forward_msg), _widget, SLOT(forwardMessage()))->setEnabled(true);
_menu->addAction(lang(lng_context_forward_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
forwardAsGroup(item);
}
}))->setEnabled(true);
}
if (canDelete) {
_menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), base::lambda_guarded(this, [this] {
_widget->confirmDeleteContextItem();
if (const auto item = App::contextItem()) {
deleteAsGroup(item);
}
}));
}
}
if (item->id > 0 && !item->serviceMsg()) {
_menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true);
_menu->addAction(lang(lng_context_select_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
if (!item->detached()) {
changeSelectionAsGroup(&_selected, item, SelectAction::Select);
repaintItem(item);
_widget->updateTopBarSelection();
}
}
}))->setEnabled(true);
}
} else {
if (App::mousedItem() && !App::mousedItem()->serviceMsg() && App::mousedItem()->id > 0) {
_menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true);
_menu->addAction(lang(lng_context_select_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
if (!item->detached()) {
changeSelectionAsGroup(&_selected, item, SelectAction::Select);
repaintItem(item);
_widget->updateTopBarSelection();
}
}
}))->setEnabled(true);
item = App::mousedItem();
}
}
@@ -1513,12 +1644,13 @@ void HistoryInner::saveContextGif() {
}
void HistoryInner::copyContextText() {
auto item = App::contextItem();
const auto item = App::contextItem();
if (!item || (item->getMedia() && item->getMedia()->type() == MediaTypeSticker)) {
return;
}
setToClipboard(item->selectedText(FullSelection));
const auto group = item->getFullGroup();
const auto leader = group ? group->leader : item;
setToClipboard(leader->selectedText(FullSelection));
}
void HistoryInner::setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode) {
@@ -1532,46 +1664,79 @@ void HistoryInner::resizeEvent(QResizeEvent *e) {
}
TextWithEntities HistoryInner::getSelectedText() const {
SelectedItems sel = _selected;
auto selected = _selected;
if (_mouseAction == MouseAction::Selecting && _dragSelFrom && _dragSelTo) {
applyDragSelection(&sel);
applyDragSelection(&selected);
}
if (sel.empty()) {
if (selected.empty()) {
return TextWithEntities();
}
if (sel.cbegin()->second != FullSelection) {
return sel.cbegin()->first->selectedText(sel.cbegin()->second);
if (selected.cbegin()->second != FullSelection) {
const auto [item, selection] = *selected.cbegin();
return item->selectedText(selection);
}
int fullSize = 0;
QString timeFormat(qsl(", [dd.MM.yy hh:mm]\n"));
QMap<int, TextWithEntities> texts;
for (auto &selected : sel) {
auto item = selected.first;
if (item->detached()) continue;
const auto timeFormat = qsl(", [dd.MM.yy hh:mm]\n");
auto groupLeadersAdded = base::flat_set<not_null<HistoryItem*>>();
auto fullSize = 0;
auto texts = base::flat_map<std::pair<int, MsgId>, TextWithEntities>();
const auto addItem = [&](
not_null<HistoryItem*> item,
TextSelection selection) {
auto time = item->date.toString(timeFormat);
TextWithEntities part, unwrapped = item->selectedText(FullSelection);
int size = item->author()->name.size() + time.size() + unwrapped.text.size();
auto part = TextWithEntities();
auto unwrapped = item->selectedText(selection);
auto size = item->author()->name.size()
+ time.size()
+ unwrapped.text.size();
part.text.reserve(size);
int y = itemTop(item);
auto y = itemTop(item);
if (y >= 0) {
part.text.append(item->author()->name).append(time);
TextUtilities::Append(part, std::move(unwrapped));
texts.insert(y, part);
texts.emplace(std::make_pair(y, item->id), part);
fullSize += size;
}
};
for (const auto [item, selection] : selected) {
if (item->detached()) {
continue;
}
if (const auto group = item->Get<HistoryMessageGroup>()) {
if (groupLeadersAdded.contains(group->leader)) {
continue;
}
const auto leaderSelection = computeRenderSelection(
&selected,
group->leader);
if (leaderSelection == FullSelection) {
groupLeadersAdded.emplace(group->leader);
addItem(group->leader, FullSelection);
} else if (item == group->leader) {
const auto leaderFullSelection = AddGroupItemSelection(
TextSelection(),
int(group->others.size()));
addItem(item, leaderFullSelection);
} else {
addItem(item, FullSelection);
}
} else {
addItem(item, FullSelection);
}
}
TextWithEntities result;
auto result = TextWithEntities();
auto sep = qsl("\n\n");
result.text.reserve(fullSize + (texts.size() - 1) * sep.size());
for (auto i = texts.begin(), e = texts.end(); i != e; ++i) {
TextUtilities::Append(result, std::move(i.value()));
if (i + 1 != e) {
for (auto i = texts.begin(), e = texts.end(); i != e;) {
TextUtilities::Append(result, std::move(i->second));
if (++i != e) {
result.text.append(sep);
}
}
@@ -2007,10 +2172,11 @@ MessageIdsList HistoryInner::getSelectedItems() const {
return result;
}
void HistoryInner::selectItem(HistoryItem *item) {
void HistoryInner::selectItem(not_null<HistoryItem*> item) {
if (!_selected.empty() && _selected.cbegin()->second != FullSelection) {
_selected.clear();
} else if (_selected.size() == MaxSelectedItems && _selected.find(item) == _selected.cend()) {
} else if (_selected.size() == MaxSelectedItems
&& _selected.find(item) == _selected.cend()) {
return;
}
_selected.emplace(item, FullSelection);
@@ -2059,10 +2225,16 @@ void HistoryInner::onUpdateSelected() {
HistoryTextState dragState;
ClickHandlerHost *lnkhost = nullptr;
bool selectingText = (item == _mouseActionItem && item == App::hoveredItem() && !_selected.empty() && _selected.cbegin()->second != FullSelection);
auto selectingText = (item == _mouseActionItem)
&& (item == App::hoveredItem())
&& !_selected.empty()
&& (_selected.cbegin()->second != FullSelection);
if (point.y() < _historyPaddingTop) {
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
dragState = _botAbout->info->text.getState(point - _botAbout->rect.topLeft() - QPoint(st::msgPadding.left(), st::msgPadding.top() + st::botDescSkip + st::msgNameFont->height), _botAbout->width);
dragState = HistoryTextState(nullptr, _botAbout->info->text.getState(
point - _botAbout->rect.topLeft() - QPoint(st::msgPadding.left(), st::msgPadding.top() + st::botDescSkip + st::msgNameFont->height),
_botAbout->width));
_dragStateItem = App::histItemById(dragState.itemId);
lnkhost = _botAbout.get();
}
} else if (item) {
@@ -2077,7 +2249,7 @@ void HistoryInner::onUpdateSelected() {
auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
auto scrollDateOpacity = _scrollDateOpacity.current(_scrollDateShown ? 1. : 0.);
enumerateDates([this, &dragState, &lnkhost, &point, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](not_null<HistoryItem*> item, int itemtop, int dateTop) {
enumerateDates([&](not_null<HistoryItem*> item, int itemtop, int dateTop) {
// stop enumeration if the date is above our point
if (dateTop + dateHeight <= point.y()) {
return false;
@@ -2116,7 +2288,10 @@ void HistoryInner::onUpdateSelected() {
} else {
static_cast<DateClickHandler*>(_scrollDateLink.data())->setDate(item->date.date());
}
dragState.link = _scrollDateLink;
dragState = HistoryTextState(
nullptr,
_scrollDateLink);
_dragStateItem = App::histItemById(dragState.itemId);
lnkhost = item;
}
}
@@ -2132,11 +2307,12 @@ void HistoryInner::onUpdateSelected() {
selectingText = false;
}
dragState = item->getState(m, request);
_dragStateItem = App::histItemById(dragState.itemId);
lnkhost = item;
if (!dragState.link && m.x() >= st::historyPhotoLeft && m.x() < st::historyPhotoLeft + st::msgPhotoSize) {
if (auto msg = item->toHistoryMessage()) {
if (msg->hasFromPhoto()) {
enumerateUserpics([&dragState, &lnkhost, &point](not_null<HistoryMessage*> message, int userpicTop) -> bool {
enumerateUserpics([&](not_null<HistoryMessage*> message, int userpicTop) -> bool {
// stop enumeration if the userpic is below our point
if (userpicTop > point.y()) {
return false;
@@ -2144,7 +2320,10 @@ void HistoryInner::onUpdateSelected() {
// stop enumeration if we've found a userpic under the cursor
if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) {
dragState.link = message->displayFrom()->openLink();
dragState = HistoryTextState(
nullptr,
message->displayFrom()->openLink());
_dragStateItem = App::histItemById(dragState.itemId);
lnkhost = message;
return false;
}
@@ -2171,7 +2350,7 @@ void HistoryInner::onUpdateSelected() {
} else if (_mouseCursorState == HistoryInTextCursorState && (_selected.empty() || _selected.cbegin()->second != FullSelection)) {
cur = style::cur_text;
} else if (_mouseCursorState == HistoryInDateCursorState) {
// cur = style::cur_cross;
//cur = style::cur_cross;
}
} else if (item) {
if (_mouseAction == MouseAction::Selecting) {
@@ -2235,7 +2414,9 @@ void HistoryInner::onUpdateSelected() {
if (ClickHandler::getPressed()) {
cur = style::cur_pointer;
} else if (_mouseAction == MouseAction::Selecting && !_selected.empty() && _selected.cbegin()->second != FullSelection) {
} else if ((_mouseAction == MouseAction::Selecting)
&& !_selected.empty()
&& (_selected.cbegin()->second != FullSelection)) {
if (!_dragSelFrom || !_dragSelTo) {
cur = style::cur_text;
}
@@ -2243,7 +2424,7 @@ void HistoryInner::onUpdateSelected() {
}
// Voice message seek support.
if (auto pressedItem = App::pressedLinkItem()) {
if (const auto pressedItem = _dragStateItem) {
if (!pressedItem->detached()) {
if (pressedItem->history() == _history || pressedItem->history() == _migrated) {
auto adjustedPoint = mapPointToItem(point, pressedItem);
@@ -2282,14 +2463,6 @@ void HistoryInner::updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dr
update();
}
void HistoryInner::BotAbout::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
_parent->update(rect);
}
void HistoryInner::BotAbout::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
_parent->update(rect);
}
int HistoryInner::historyHeight() const {
int result = 0;
if (!_history || _history->isEmpty()) {
@@ -2337,13 +2510,16 @@ int HistoryInner::itemTop(const HistoryItem *item) const { // -1 if should not b
}
void HistoryInner::notifyIsBotChanged() {
BotInfo *newinfo = (_history && _history->peer->isUser()) ? _history->peer->asUser()->botInfo.get() : nullptr;
if ((!newinfo && !_botAbout) || (newinfo && _botAbout && _botAbout->info == newinfo)) {
const auto newinfo = (_history && _history->peer->isUser())
? _history->peer->asUser()->botInfo.get()
: nullptr;
if ((!newinfo && !_botAbout)
|| (newinfo && _botAbout && _botAbout->info == newinfo)) {
return;
}
if (newinfo) {
_botAbout.reset(new BotAbout(this, newinfo));
_botAbout = std::make_unique<BotAbout>(this, newinfo);
if (newinfo && !newinfo->inited) {
Auth().api().requestFullPeer(_peer);
}
@@ -2370,25 +2546,171 @@ void HistoryInner::applyDragSelection() {
applyDragSelection(&_selected);
}
void HistoryInner::addSelectionRange(SelectedItems *toItems, int32 fromblock, int32 fromitem, int32 toblock, int32 toitem, History *h) const {
bool HistoryInner::isSelected(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item);
return (i != toItems->cend()) && (i->second == FullSelection);
}
bool HistoryInner::isSelectedAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
if (const auto group = item->getFullGroup()) {
if (!isSelected(toItems, group->leader)) {
return false;
}
for (const auto other : group->others) {
if (!isSelected(toItems, other)) {
return false;
}
}
return true;
}
return isSelected(toItems, item);
}
bool HistoryInner::goodForSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
int &totalCount) const {
if (item->id <= 0 || item->serviceMsg()) {
return false;
}
if (toItems->find(item) == toItems->end()) {
++totalCount;
}
return true;
}
void HistoryInner::addToSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item);
if (i == toItems->cend()) {
toItems->emplace(item, FullSelection);
} else if (i->second != FullSelection) {
i->second = FullSelection;
}
}
void HistoryInner::removeFromSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item);
if (i != toItems->cend()) {
toItems->erase(i);
}
}
void HistoryInner::changeSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const {
if (action == SelectAction::Invert) {
action = isSelected(toItems, item)
? SelectAction::Deselect
: SelectAction::Select;
}
auto total = int(toItems->size());
const auto add = (action == SelectAction::Select);
if (add
&& goodForSelection(toItems, item, total)
&& total <= MaxSelectedItems) {
addToSelection(toItems, item);
} else {
removeFromSelection(toItems, item);
}
}
void HistoryInner::changeSelectionAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const {
const auto group = item->getFullGroup();
if (!group) {
return changeSelection(toItems, item, action);
}
if (action == SelectAction::Invert) {
action = isSelectedAsGroup(toItems, item)
? SelectAction::Deselect
: SelectAction::Select;
}
auto total = int(toItems->size());
const auto add = (action == SelectAction::Select);
const auto adding = [&] {
if (!add || !goodForSelection(toItems, group->leader, total)) {
return false;
}
for (const auto other : group->others) {
if (!goodForSelection(toItems, other, total)) {
return false;
}
}
return (total <= MaxSelectedItems);
}();
if (adding) {
addToSelection(toItems, group->leader);
for (const auto other : group->others) {
addToSelection(toItems, other);
}
} else {
removeFromSelection(toItems, group->leader);
for (const auto other : group->others) {
removeFromSelection(toItems, other);
}
}
}
void HistoryInner::forwardItem(not_null<HistoryItem*> item) {
Window::ShowForwardMessagesBox({ 1, item->fullId() });
}
void HistoryInner::forwardAsGroup(not_null<HistoryItem*> item) {
if (const auto group = item->getFullGroup()) {
auto items = Auth().data().itemsToIds(group->others);
items.push_back(group->leader->fullId());
Window::ShowForwardMessagesBox(std::move(items));
} else {
Window::ShowForwardMessagesBox({ 1, item->fullId() });
}
}
void HistoryInner::deleteItem(not_null<HistoryItem*> item) {
if (auto message = item->toHistoryMessage()) {
if (message->uploading()) {
App::main()->cancelUploadLayer();
return;
}
}
const auto suggestModerateActions = true;
Ui::show(Box<DeleteMessagesBox>(item, suggestModerateActions));
}
void HistoryInner::deleteAsGroup(not_null<HistoryItem*> item) {
const auto group = item->getFullGroup();
if (!group || group->others.empty()) {
return deleteItem(item);
}
auto items = Auth().data().itemsToIds(group->others);
items.push_back(group->leader->fullId());
Ui::show(Box<DeleteMessagesBox>(Auth().data().groupToIds(group)));
}
void HistoryInner::addSelectionRange(
not_null<SelectedItems*> toItems,
not_null<History*> history,
int fromblock,
int fromitem,
int toblock,
int toitem) const {
if (fromblock >= 0 && fromitem >= 0 && toblock >= 0 && toitem >= 0) {
for (; fromblock <= toblock; ++fromblock) {
auto block = h->blocks[fromblock];
for (int32 cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) {
auto block = history->blocks[fromblock];
for (int cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) {
auto item = block->items[fromitem];
auto i = toItems->find(item);
if (item->id > 0 && !item->serviceMsg()) {
if (i == toItems->cend()) {
if (toItems->size() >= MaxSelectedItems) break;
toItems->emplace(item, FullSelection);
} else if (i->second != FullSelection) {
i->second = FullSelection;
}
} else {
if (i != toItems->cend()) {
toItems->erase(i);
}
}
changeSelectionAsGroup(toItems, item, SelectAction::Select);
}
if (toItems->size() >= MaxSelectedItems) break;
fromitem = 0;
@@ -2396,27 +2718,33 @@ void HistoryInner::addSelectionRange(SelectedItems *toItems, int32 fromblock, in
}
}
void HistoryInner::applyDragSelection(SelectedItems *toItems) const {
int32 selfromy = itemTop(_dragSelFrom), seltoy = itemTop(_dragSelTo);
void HistoryInner::applyDragSelection(
not_null<SelectedItems*> toItems) const {
const auto selfromy = itemTop(_dragSelFrom);
const auto seltoy = [&] {
auto result = itemTop(_dragSelTo);
return (result < 0) ? result : (result + _dragSelTo->height());
}();
if (selfromy < 0 || seltoy < 0) {
return;
}
seltoy += _dragSelTo->height();
if (!toItems->empty() && toItems->cbegin()->second != FullSelection) {
toItems->clear();
}
if (_dragSelecting) {
int32 fromblock = _dragSelFrom->block()->indexInHistory(), fromitem = _dragSelFrom->indexInBlock();
int32 toblock = _dragSelTo->block()->indexInHistory(), toitem = _dragSelTo->indexInBlock();
auto fromblock = _dragSelFrom->block()->indexInHistory();
auto fromitem = _dragSelFrom->indexInBlock();
auto toblock = _dragSelTo->block()->indexInHistory();
auto toitem = _dragSelTo->indexInBlock();
if (_migrated) {
if (_dragSelFrom->history() == _migrated) {
if (_dragSelTo->history() == _migrated) {
addSelectionRange(toItems, fromblock, fromitem, toblock, toitem, _migrated);
addSelectionRange(toItems, _migrated, fromblock, fromitem, toblock, toitem);
toblock = -1;
toitem = -1;
} else {
addSelectionRange(toItems, fromblock, fromitem, _migrated->blocks.size() - 1, _migrated->blocks.back()->items.size() - 1, _migrated);
addSelectionRange(toItems, _migrated, fromblock, fromitem, _migrated->blocks.size() - 1, _migrated->blocks.back()->items.size() - 1);
}
fromblock = 0;
fromitem = 0;
@@ -2425,20 +2753,20 @@ void HistoryInner::applyDragSelection(SelectedItems *toItems) const {
toitem = -1;
}
}
addSelectionRange(toItems, fromblock, fromitem, toblock, toitem, _history);
addSelectionRange(toItems, _history, fromblock, fromitem, toblock, toitem);
} else {
for (auto i = toItems->begin(); i != toItems->cend();) {
auto toRemove = std::vector<not_null<HistoryItem*>>();
for (auto i = toItems->begin(); i != toItems->cend(); ++i) {
auto iy = itemTop(i->first);
if (iy < 0) {
if (iy < -1) i = toItems->erase(i);
continue;
}
if (iy >= selfromy && iy < seltoy) {
i = toItems->erase(i);
} else {
++i;
if (iy < -1) {
toRemove.push_back(i->first);
} else if (iy >= 0 && iy >= selfromy && iy < seltoy) {
toRemove.push_back(i->first);
}
}
for (const auto item : toRemove) {
changeSelectionAsGroup(toItems, item, SelectAction::Deselect);
}
}
}
@@ -2446,8 +2774,9 @@ QString HistoryInner::tooltipText() const {
if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
if (App::hoveredItem()) {
auto dateText = App::hoveredItem()->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
if (auto edited = App::hoveredItem()->Get<HistoryMessageEdited>()) {
dateText += '\n' + lng_edited_date(lt_date, edited->_editDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
auto editedDate = App::hoveredItem()->displayedEditDate();
if (!editedDate.isNull()) {
dateText += '\n' + lng_edited_date(lt_date, editedDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
}
if (auto forwarded = App::hoveredItem()->Get<HistoryMessageForwarded>()) {
dateText += '\n' + lng_forwarded_date(lt_date, forwarded->_originalDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));

View File

@@ -66,7 +66,7 @@ public:
HistoryTopBarWidget::SelectedState getSelectionState() const;
void clearSelectedItems(bool onlyTextSelection = false);
MessageIdsList getSelectedItems() const;
void selectItem(HistoryItem *item);
void selectItem(not_null<HistoryItem*> item);
void updateBotInfo(bool recount = true);
@@ -133,6 +133,9 @@ private slots:
void onScrollDateHideByTimer();
private:
class BotAbout;
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
enum class MouseAction {
None,
PrepareDrag,
@@ -140,6 +143,11 @@ private:
PrepareSelect,
Selecting,
};
enum class SelectAction {
Select,
Deselect,
Invert,
};
void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button);
void mouseActionUpdate(const QPoint &screenPos);
@@ -165,6 +173,13 @@ private:
HistoryItem *prevItem(HistoryItem *item);
HistoryItem *nextItem(HistoryItem *item);
void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting);
TextSelection itemRenderSelection(
not_null<HistoryItem*> item,
int selfromy,
int seltoy) const;
TextSelection computeRenderSelection(
not_null<const SelectedItems*> selected,
not_null<HistoryItem*> item) const;
void setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode = QClipboard::Clipboard);
@@ -186,23 +201,6 @@ private:
// or at least we don't need to display first _history date (just skip it by height)
int _historySkipHeight = 0;
class BotAbout : public ClickHandlerHost {
public:
BotAbout(HistoryInner *parent, BotInfo *info) : info(info), _parent(parent) {
}
BotInfo *info = nullptr;
int width = 0;
int height = 0;
QRect rect;
// ClickHandlerHost interface
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
private:
HistoryInner *_parent;
};
std::unique_ptr<BotAbout> _botAbout;
HistoryWidget *_widget = nullptr;
@@ -214,11 +212,45 @@ private:
bool _firstLoading = false;
style::cursor _cursor = style::cur_default;
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
SelectedItems _selected;
void applyDragSelection();
void applyDragSelection(SelectedItems *toItems) const;
void addSelectionRange(SelectedItems *toItems, int32 fromblock, int32 fromitem, int32 toblock, int32 toitem, History *h) const;
void applyDragSelection(not_null<SelectedItems*> toItems) const;
void addSelectionRange(
not_null<SelectedItems*> toItems,
not_null<History*> history,
int fromblock,
int fromitem,
int toblock,
int toitem) const;
bool isSelected(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
bool isSelectedAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
bool goodForSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
int &totalCount) const;
void addToSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
void removeFromSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
void changeSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const;
void changeSelectionAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const;
void forwardItem(not_null<HistoryItem*> item);
void forwardAsGroup(not_null<HistoryItem*> item);
void deleteItem(not_null<HistoryItem*> item);
void deleteAsGroup(not_null<HistoryItem*> item);
// Does any of the shown histories has this flag set.
bool hasPendingResizedItems() const {
@@ -230,6 +262,7 @@ private:
QPoint _dragStartPosition;
QPoint _mousePosition;
HistoryItem *_mouseActionItem = nullptr;
HistoryItem *_dragStateItem = nullptr;
HistoryCursorState _mouseCursorState = HistoryDefaultCursorState;
uint16 _mouseTextSymbol = 0;
bool _pressWasInactive = false;

View File

@@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "history/history_service_layout.h"
#include "history/history_media_types.h"
#include "history/history_media_grouped.h"
#include "history/history_message.h"
#include "media/media_clip_reader.h"
#include "styles/style_dialogs.h"
@@ -45,6 +46,29 @@ constexpr int kAttachMessageToPreviousSecondsDelta = 900;
} // namespace
HistoryTextState::HistoryTextState(not_null<const HistoryItem*> item)
: itemId(item->fullId()) {
}
HistoryTextState::HistoryTextState(
not_null<const HistoryItem*> item,
const Text::StateResult &state)
: itemId(item->fullId())
, cursor(state.uponSymbol
? HistoryInTextCursorState
: HistoryDefaultCursorState)
, link(state.link)
, afterSymbol(state.afterSymbol)
, symbol(state.symbol) {
}
HistoryTextState::HistoryTextState(
not_null<const HistoryItem*> item,
ClickHandlerPtr link)
: itemId(item->fullId())
, link(link) {
}
ReplyMarkupClickHandler::ReplyMarkupClickHandler(const HistoryItem *item, int row, int col)
: _itemId(item->fullId())
, _row(row)
@@ -563,7 +587,8 @@ HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default;
HistoryMediaPtr::HistoryMediaPtr() = default;
HistoryMediaPtr::HistoryMediaPtr(std::unique_ptr<HistoryMedia> pointer) : _pointer(std::move(pointer)) {
HistoryMediaPtr::HistoryMediaPtr(std::unique_ptr<HistoryMedia> pointer)
: _pointer(std::move(pointer)) {
if (_pointer) {
_pointer->attachToParent();
}
@@ -625,13 +650,15 @@ void HistoryItem::finishCreate() {
void HistoryItem::finishEdition(int oldKeyboardTop) {
setPendingInitDimensions();
if (App::main()) {
App::main()->dlgUpdated(history()->peer, id);
}
// invalidate cache for drawInDialog
if (history()->textCachedFor == this) {
history()->textCachedFor = nullptr;
invalidateChatsListEntry();
//if (groupId()) {
// history()->fixGroupAfterEdition(this);
//}
if (isHiddenByGroup()) {
// Perhaps caption was changed, we should refresh the group.
const auto group = Get<HistoryMessageGroup>();
group->leader->setPendingInitDimensions();
group->leader->invalidateChatsListEntry();
}
if (oldKeyboardTop >= 0) {
@@ -643,6 +670,17 @@ void HistoryItem::finishEdition(int oldKeyboardTop) {
App::historyUpdateDependent(this);
}
void HistoryItem::invalidateChatsListEntry() {
if (App::main()) {
App::main()->dlgUpdated(history()->peer, id);
}
// invalidate cache for drawInDialog
if (history()->textCachedFor == this) {
history()->textCachedFor = nullptr;
}
}
void HistoryItem::finishEditionToEmpty() {
recountDisplayDate();
finishEdition(-1);
@@ -768,6 +806,11 @@ void HistoryItem::detach() {
void HistoryItem::detachFast() {
_block = nullptr;
_indexInBlock = -1;
validateGroupId();
if (groupId()) {
makeGroupLeader({});
}
}
Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const {
@@ -1116,6 +1159,99 @@ void HistoryItem::setUnreadBarFreezed() {
}
}
bool HistoryItem::groupIdValidityChanged() {
if (Has<HistoryMessageGroup>()) {
if (_media && _media->canBeGrouped()) {
return false;
}
RemoveComponents(HistoryMessageGroup::Bit());
setPendingInitDimensions();
return true;
}
return false;
}
void HistoryItem::makeGroupMember(not_null<HistoryItem*> leader) {
Expects(leader != this);
const auto group = Get<HistoryMessageGroup>();
Assert(group != nullptr);
if (group->leader == this) {
if (auto single = _media ? _media->takeLastFromGroup() : nullptr) {
_media = std::move(single);
}
_flags |= MTPDmessage_ClientFlag::f_hidden_by_group;
setPendingInitDimensions();
group->leader = leader;
base::take(group->others);
} else if (group->leader != leader) {
group->leader = leader;
}
Ensures(isHiddenByGroup());
Ensures(group->others.empty());
}
void HistoryItem::makeGroupLeader(
std::vector<not_null<HistoryItem*>> &&others) {
const auto group = Get<HistoryMessageGroup>();
Assert(group != nullptr);
const auto leaderChanged = (group->leader != this);
if (leaderChanged) {
_flags &= ~MTPDmessage_ClientFlag::f_hidden_by_group;
setPendingInitDimensions();
}
group->others = std::move(others);
if (!_media || !_media->applyGroup(group->others)) {
resetGroupMedia(group->others);
invalidateChatsListEntry();
}
Ensures(!isHiddenByGroup());
}
HistoryMessageGroup *HistoryItem::getFullGroup() {
if (const auto group = Get<HistoryMessageGroup>()) {
if (group->leader == this) {
return group;
}
return group->leader->Get<HistoryMessageGroup>();
}
return nullptr;
}
void HistoryItem::resetGroupMedia(
const std::vector<not_null<HistoryItem*>> &others) {
if (!others.empty()) {
_media = std::make_unique<HistoryGroupedMedia>(this, others);
} else if (_media) {
_media = _media->takeLastFromGroup();
}
setPendingInitDimensions();
}
int HistoryItem::marginTop() const {
auto result = 0;
if (!isHiddenByGroup()) {
if (isAttachedToPrevious()) {
result += st::msgMarginTopAttached;
} else {
result += st::msgMargin.top();
}
}
result += displayedDateHeight();
if (const auto unreadbar = Get<HistoryMessageUnreadBar>()) {
result += unreadbar->height();
}
return result;
}
int HistoryItem::marginBottom() const {
return isHiddenByGroup() ? 0 : st::msgMargin.bottom();
}
void HistoryItem::clipCallback(Media::Clip::Notification notification) {
using namespace Media::Clip;

View File

@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "base/runtime_composer.h"
#include "base/flags.h"
#include "base/value_ordering.h"
namespace base {
template <typename Enum>
@@ -78,25 +79,33 @@ enum HistoryCursorState {
struct HistoryTextState {
HistoryTextState() = default;
HistoryTextState(const Text::StateResult &state)
: cursor(state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState)
, link(state.link)
, afterSymbol(state.afterSymbol)
, symbol(state.symbol) {
HistoryTextState(not_null<const HistoryItem*> item);
HistoryTextState(
not_null<const HistoryItem*> item,
const Text::StateResult &state);
HistoryTextState(
not_null<const HistoryItem*> item,
ClickHandlerPtr link);
HistoryTextState(
std::nullptr_t,
const Text::StateResult &state)
: cursor(state.uponSymbol
? HistoryInTextCursorState
: HistoryDefaultCursorState)
, link(state.link)
, afterSymbol(state.afterSymbol)
, symbol(state.symbol) {
}
HistoryTextState &operator=(const Text::StateResult &state) {
cursor = state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState;
link = state.link;
afterSymbol = state.afterSymbol;
symbol = state.symbol;
return *this;
}
HistoryTextState(ClickHandlerPtr link) : link(link) {
HistoryTextState(std::nullptr_t, ClickHandlerPtr link)
: link(link) {
}
FullMsgId itemId;
HistoryCursorState cursor = HistoryDefaultCursorState;
ClickHandlerPtr link;
bool afterSymbol = false;
uint16 symbol = 0;
};
struct HistoryStateRequest {
@@ -140,11 +149,11 @@ struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned> {
};
struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited> {
void create(const QDateTime &editDate, const QString &date);
void refresh(const QString &date, bool displayed);
int maxWidth() const;
QDateTime _editDate;
Text _edited;
QDateTime date;
Text text;
};
struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded> {
@@ -435,6 +444,34 @@ struct HistoryMessageUnreadBar : public RuntimeComponent<HistoryMessageUnreadBar
};
struct MessageGroupId {
using Underlying = uint64;
enum Type : Underlying {
None = 0,
} value;
MessageGroupId(Type value = None) : value(value) {
}
static MessageGroupId FromRaw(Underlying value) {
return static_cast<Type>(value);
}
explicit operator bool() const {
return value != None;
}
friend inline Type value_ordering_helper(MessageGroupId value) {
return value.value;
}
};
struct HistoryMessageGroup : public RuntimeComponent<HistoryMessageGroup> {
MessageGroupId groupId = MessageGroupId::None;
HistoryItem *leader = nullptr;
std::vector<not_null<HistoryItem*>> others;
};
class HistoryWebPage;
// Special type of Component for the channel actions log.
@@ -739,6 +776,15 @@ public:
}
virtual void setId(MsgId newId);
virtual bool displayEditedBadge() const {
return false;
}
virtual QDateTime displayedEditDate() const {
return QDateTime();
}
virtual void refreshEditedBadge() {
}
void drawInDialog(
Painter &p,
const QRect &r,
@@ -899,22 +945,8 @@ public:
}
return 0;
}
int marginTop() const {
int result = 0;
if (isAttachedToPrevious()) {
result += st::msgMarginTopAttached;
} else {
result += st::msgMargin.top();
}
result += displayedDateHeight();
if (auto unreadbar = Get<HistoryMessageUnreadBar>()) {
result += unreadbar->height();
}
return result;
}
int marginBottom() const {
return st::msgMargin.bottom();
}
int marginTop() const;
int marginBottom() const;
bool isAttachedToPrevious() const {
return _flags & MTPDmessage_ClientFlag::f_attach_to_previous;
}
@@ -932,6 +964,24 @@ public:
bool isEmpty() const {
return _text.isEmpty() && !_media && !Has<HistoryMessageLogEntryOriginal>();
}
bool isHiddenByGroup() const {
return _flags & MTPDmessage_ClientFlag::f_hidden_by_group;
}
MessageGroupId groupId() const {
if (const auto group = Get<HistoryMessageGroup>()) {
return group->groupId;
}
return MessageGroupId::None;
}
bool groupIdValidityChanged();
void validateGroupId() {
// Just ignore the result.
groupIdValidityChanged();
}
void makeGroupMember(not_null<HistoryItem*> leader);
void makeGroupLeader(std::vector<not_null<HistoryItem*>> &&others);
HistoryMessageGroup *getFullGroup();
int width() const {
return _width;
@@ -1053,6 +1103,7 @@ protected:
}
return nullptr;
}
void invalidateChatsListEntry();
[[nodiscard]] TextSelection skipTextSelection(
TextSelection selection) const {
@@ -1070,6 +1121,8 @@ protected:
HistoryMediaPtr _media;
private:
void resetGroupMedia(const std::vector<not_null<HistoryItem*>> &others);
int _y = 0;
int _width = 0;

View File

@@ -61,7 +61,9 @@ public:
}
virtual bool isDisplayed() const {
return true;
return !_parent->isHiddenByGroup();
}
virtual void updateNeedBubbleState() {
}
virtual bool isAboveMessage() const {
return false;
@@ -132,9 +134,14 @@ public:
virtual bool uploading() const {
return false;
}
virtual std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const = 0;
virtual std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const = 0;
virtual DocumentData *getDocument() {
virtual PhotoData *getPhoto() const {
return nullptr;
}
virtual DocumentData *getDocument() const {
return nullptr;
}
virtual Media::Clip::Reader *getClipReader() {
@@ -155,10 +162,40 @@ public:
virtual void attachToParent() {
}
virtual void detachFromParent() {
}
virtual bool canBeGrouped() const {
return false;
}
virtual QSize sizeForGrouping() const {
Unexpected("Grouping method call.");
}
virtual void drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
TimeMs ms,
const QRect &geometry,
RectParts corners,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const {
Unexpected("Grouping method call.");
}
virtual HistoryTextState getStateGrouped(
const QRect &geometry,
QPoint point,
HistoryStateRequest request) const {
Unexpected("Grouping method call.");
}
virtual std::unique_ptr<HistoryMedia> takeLastFromGroup() {
return nullptr;
}
virtual bool applyGroup(
const std::vector<not_null<HistoryItem*>> &others) {
return others.empty();
}
virtual void updateSentMedia(const MTPMessageMedia &media) {
}
@@ -190,6 +227,13 @@ public:
return false;
}
virtual bool overrideEditedDate() const {
return false;
}
virtual HistoryMessageEdited *displayedEditBadge() const {
Unexpected("displayedEditBadge() on non-grouped media.");
}
// An attach media in a web page can provide an
// additional text to be displayed below the attach.
// For example duration / progress for video messages.

View File

@@ -0,0 +1,454 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "history/history_media_grouped.h"
#include "history/history_media_types.h"
#include "history/history_message.h"
#include "storage/storage_shared_media.h"
#include "lang/lang_keys.h"
#include "ui/grouped_layout.h"
#include "styles/style_history.h"
namespace {
RectParts GetCornersFromSides(RectParts sides) {
const auto convert = [&](
RectPart side1,
RectPart side2,
RectPart corner) {
return ((sides & side1) && (sides & side2))
? corner
: RectPart::None;
};
return RectPart::None
| convert(RectPart::Top, RectPart::Left, RectPart::TopLeft)
| convert(RectPart::Top, RectPart::Right, RectPart::TopRight)
| convert(RectPart::Bottom, RectPart::Left, RectPart::BottomLeft)
| convert(RectPart::Bottom, RectPart::Right, RectPart::BottomRight);
}
} // namespace
HistoryGroupedMedia::Element::Element(not_null<HistoryItem*> item)
: item(item) {
}
HistoryGroupedMedia::HistoryGroupedMedia(
not_null<HistoryItem*> parent,
const std::vector<not_null<HistoryItem*>> &others)
: HistoryMedia(parent)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
const auto result = applyGroup(others);
Ensures(result);
}
void HistoryGroupedMedia::initDimensions() {
if (_caption.hasSkipBlock()) {
_caption.setSkipBlock(
_parent->skipBlockWidth(),
_parent->skipBlockHeight());
}
std::vector<QSize> sizes;
sizes.reserve(_elements.size());
for (const auto &element : _elements) {
const auto &media = element.content;
media->initDimensions();
sizes.push_back(media->sizeForGrouping());
}
const auto layout = Data::LayoutMediaGroup(
sizes,
st::historyGroupWidthMax,
st::historyGroupWidthMin,
st::historyGroupSkip);
Assert(layout.size() == _elements.size());
_maxw = _minh = 0;
for (auto i = 0, count = int(layout.size()); i != count; ++i) {
const auto &item = layout[i];
accumulate_max(_maxw, item.geometry.x() + item.geometry.width());
accumulate_max(_minh, item.geometry.y() + item.geometry.height());
_elements[i].initialGeometry = item.geometry;
_elements[i].sides = item.sides;
}
if (!_caption.isEmpty()) {
auto captionw = _maxw - st::msgPadding.left() - st::msgPadding.right();
_minh += st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) {
_minh += st::msgPadding.bottom();
}
}
}
int HistoryGroupedMedia::resizeGetHeight(int width) {
_width = width;
_height = 0;
if (_width < st::historyGroupWidthMin) {
return _height;
}
const auto initialSpacing = st::historyGroupSkip;
const auto factor = width / float64(_maxw);
const auto scale = [&](int value) {
return int(std::round(value * factor));
};
const auto spacing = scale(initialSpacing);
for (auto &element : _elements) {
const auto sides = element.sides;
const auto initialGeometry = element.initialGeometry;
const auto needRightSkip = !(sides & RectPart::Right);
const auto needBottomSkip = !(sides & RectPart::Bottom);
const auto initialLeft = initialGeometry.x();
const auto initialTop = initialGeometry.y();
const auto initialRight = initialLeft
+ initialGeometry.width()
+ (needRightSkip ? initialSpacing : 0);
const auto initialBottom = initialTop
+ initialGeometry.height()
+ (needBottomSkip ? initialSpacing : 0);
const auto left = scale(initialLeft);
const auto top = scale(initialTop);
const auto width = scale(initialRight)
- left
- (needRightSkip ? spacing : 0);
const auto height = scale(initialBottom)
- top
- (needBottomSkip ? spacing : 0);
element.geometry = QRect(left, top, width, height);
accumulate_max(_height, top + height);
}
if (!_caption.isEmpty()) {
const auto captionw = _width - st::msgPadding.left() - st::msgPadding.right();
_height += st::mediaPadding.bottom() + st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) {
_height += st::msgPadding.bottom();
}
}
return _height;
}
void HistoryGroupedMedia::draw(
Painter &p,
const QRect &clip,
TextSelection selection,
TimeMs ms) const {
for (auto i = 0, count = int(_elements.size()); i != count; ++i) {
const auto &element = _elements[i];
const auto elementSelection = (selection == FullSelection)
? FullSelection
: IsGroupItemSelection(selection, i)
? FullSelection
: TextSelection();
auto corners = GetCornersFromSides(element.sides);
if (!isBubbleTop()) {
corners &= ~(RectPart::TopLeft | RectPart::TopRight);
}
if (!isBubbleBottom() || !_caption.isEmpty()) {
corners &= ~(RectPart::BottomLeft | RectPart::BottomRight);
}
element.content->drawGrouped(
p,
clip,
elementSelection,
ms,
element.geometry,
corners,
&element.cacheKey,
&element.cache);
}
// date
const auto selected = (selection == FullSelection);
if (!_caption.isEmpty()) {
const auto captionw = _width - st::msgPadding.left() - st::msgPadding.right();
const auto outbg = _parent->hasOutLayout();
const auto captiony = _height
- (isBubbleBottom() ? st::msgPadding.bottom() : 0)
- _caption.countHeight(captionw);
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
_caption.draw(p, st::msgPadding.left(), captiony, captionw, style::al_left, 0, -1, selection);
} else if (_parent->getMedia() == this) {
auto fullRight = _width;
auto fullBottom = _height;
if (_parent->id < 0 || App::hoveredItem() == _parent) {
_parent->drawInfo(p, fullRight, fullBottom, _width, selected, InfoDisplayOverImage);
}
if (!_parent->hasBubble() && _parent->displayRightAction()) {
auto fastShareLeft = (fullRight + st::historyFastShareLeft);
auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
_parent->drawRightAction(p, fastShareLeft, fastShareTop, _width);
}
}
}
HistoryTextState HistoryGroupedMedia::getElementState(
QPoint point,
HistoryStateRequest request) const {
for (const auto &element : _elements) {
if (element.geometry.contains(point)) {
auto result = element.content->getStateGrouped(
element.geometry,
point,
request);
result.itemId = element.item->fullId();
return result;
}
}
return HistoryTextState(_parent);
}
HistoryTextState HistoryGroupedMedia::getState(
QPoint point,
HistoryStateRequest request) const {
auto result = getElementState(point, request);
if (!result.link && !_caption.isEmpty()) {
const auto captionw = _width - st::msgPadding.left() - st::msgPadding.right();
const auto captiony = _height
- (isBubbleBottom() ? st::msgPadding.bottom() : 0)
- _caption.countHeight(captionw);
if (QRect(st::msgPadding.left(), captiony, captionw, _height - captiony).contains(point)) {
return HistoryTextState(_parent, _caption.getState(
point - QPoint(st::msgPadding.left(), captiony),
captionw,
request.forText()));
}
} else if (_parent->getMedia() == this) {
auto fullRight = _width;
auto fullBottom = _height;
if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayOverImage)) {
result.cursor = HistoryInDateCursorState;
}
if (!_parent->hasBubble() && _parent->displayRightAction()) {
auto fastShareLeft = (fullRight + st::historyFastShareLeft);
auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
result.link = _parent->rightActionLink();
}
}
}
return result;
}
bool HistoryGroupedMedia::toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const {
for (const auto &element : _elements) {
if (element.content->toggleSelectionByHandlerClick(p)) {
return true;
}
}
return false;
}
bool HistoryGroupedMedia::dragItemByHandler(const ClickHandlerPtr &p) const {
for (const auto &element : _elements) {
if (element.content->dragItemByHandler(p)) {
return true;
}
}
return false;
}
TextSelection HistoryGroupedMedia::adjustSelection(
TextSelection selection,
TextSelectType type) const {
return _caption.adjustSelection(selection, type);
}
QString HistoryGroupedMedia::notificationText() const {
return WithCaptionNotificationText(lang(lng_in_dlg_photo), _caption);
}
QString HistoryGroupedMedia::inDialogsText() const {
return WithCaptionDialogsText(lang(lng_in_dlg_album), _caption);
}
TextWithEntities HistoryGroupedMedia::selectedText(
TextSelection selection) const {
if (!IsSubGroupSelection(selection)) {
return WithCaptionSelectedText(
lang(lng_in_dlg_album),
_caption,
selection);
} else if (IsGroupItemSelection(selection, int(_elements.size()) - 1)) {
return main()->selectedText(FullSelection);
}
return TextWithEntities();
}
void HistoryGroupedMedia::clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) {
for (const auto &element : _elements) {
element.content->clickHandlerActiveChanged(p, active);
}
}
void HistoryGroupedMedia::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
for (const auto &element : _elements) {
element.content->clickHandlerPressedChanged(p, pressed);
if (pressed && element.content->dragItemByHandler(p)) {
App::pressedLinkItem(element.item);
}
}
}
void HistoryGroupedMedia::attachToParent() {
for (const auto &element : _elements) {
element.content->attachToParent();
}
}
void HistoryGroupedMedia::detachFromParent() {
for (const auto &element : _elements) {
if (element.content) {
element.content->detachFromParent();
}
}
}
std::unique_ptr<HistoryMedia> HistoryGroupedMedia::takeLastFromGroup() {
return std::move(_elements.back().content);
}
bool HistoryGroupedMedia::applyGroup(
const std::vector<not_null<HistoryItem*>> &others) {
if (others.empty()) {
return false;
}
const auto pushElement = [&](not_null<HistoryItem*> item) {
const auto media = item->getMedia();
Assert(media != nullptr && media->canBeGrouped());
_elements.push_back(Element(item));
_elements.back().content = item->getMedia()->clone(_parent, item);
};
if (_elements.empty()) {
pushElement(_parent);
} else if (validateGroupElements(others)) {
return true;
}
// We're updating other elements, so we just need to preserve the main.
auto mainElement = std::move(_elements.back());
_elements.erase(_elements.begin(), _elements.end());
_elements.reserve(others.size() + 1);
for (const auto item : others) {
pushElement(item);
}
_elements.push_back(std::move(mainElement));
_parent->setPendingInitDimensions();
return true;
}
bool HistoryGroupedMedia::validateGroupElements(
const std::vector<not_null<HistoryItem*>> &others) const {
if (_elements.size() != others.size() + 1) {
return false;
}
for (auto i = 0, count = int(others.size()); i != count; ++i) {
if (_elements[i].item != others[i]) {
return false;
}
}
return true;
}
not_null<HistoryMedia*> HistoryGroupedMedia::main() const {
Expects(!_elements.empty());
return _elements.back().content.get();
}
bool HistoryGroupedMedia::hasReplyPreview() const {
return main()->hasReplyPreview();
}
ImagePtr HistoryGroupedMedia::replyPreview() {
return main()->replyPreview();
}
TextWithEntities HistoryGroupedMedia::getCaption() const {
return main()->getCaption();
}
Storage::SharedMediaTypesMask HistoryGroupedMedia::sharedMediaTypes() const {
return main()->sharedMediaTypes();
}
HistoryMessageEdited *HistoryGroupedMedia::displayedEditBadge() const {
if (!_caption.isEmpty()) {
return _elements.front().item->Get<HistoryMessageEdited>();
}
return nullptr;
}
void HistoryGroupedMedia::updateNeedBubbleState() {
const auto getItemCaption = [](const Element &element) {
if (const auto media = element.item->getMedia()) {
return media->getCaption();
}
return element.content->getCaption();
};
const auto captionText = [&] {
auto result = getItemCaption(_elements.front());
if (result.text.isEmpty()) {
return result;
}
for (auto i = 1, count = int(_elements.size()); i != count; ++i) {
if (!getItemCaption(_elements[i]).text.isEmpty()) {
return TextWithEntities();
}
}
return result;
}();
_caption.setText(
st::messageTextStyle,
captionText.text + _parent->skipBlock(),
itemTextNoMonoOptions(_parent));
_needBubble = computeNeedBubble();
}
bool HistoryGroupedMedia::needsBubble() const {
return _needBubble;
}
bool HistoryGroupedMedia::computeNeedBubble() const {
if (!_caption.isEmpty()) {
return true;
}
if (const auto message = _parent->toHistoryMessage()) {
if (message->viaBot()
|| message->Has<HistoryMessageReply>()
|| message->displayForwardedFrom()
|| message->displayFromName()) {
return true;
}
}
return false;
}

View File

@@ -0,0 +1,145 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "history/history_media.h"
#include "data/data_document.h"
#include "data/data_photo.h"
class HistoryGroupedMedia : public HistoryMedia {
public:
HistoryGroupedMedia(
not_null<HistoryItem*> parent,
const std::vector<not_null<HistoryItem*>> &others);
HistoryMediaType type() const override {
return MediaTypeGrouped;
}
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Unexpected("Clone HistoryGroupedMedia.");
}
void initDimensions() override;
int resizeGetHeight(int width) override;
void draw(
Painter &p,
const QRect &clip,
TextSelection selection,
TimeMs ms) const override;
HistoryTextState getState(
QPoint point,
HistoryStateRequest request) const override;
bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const override;
bool dragItemByHandler(const ClickHandlerPtr &p) const override;
[[nodiscard]] TextSelection adjustSelection(
TextSelection selection,
TextSelectType type) const override;
uint16 fullSelectionLength() const override {
return _caption.length();
}
bool hasTextForCopy() const override {
return !_caption.isEmpty();
}
PhotoData *getPhoto() const override {
return main()->getPhoto();
}
DocumentData *getDocument() const override {
return main()->getDocument();
}
QString notificationText() const override;
QString inDialogsText() const override;
TextWithEntities selectedText(TextSelection selection) const override;
void clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) override;
void clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) override;
void attachToParent() override;
void detachFromParent() override;
std::unique_ptr<HistoryMedia> takeLastFromGroup() override;
bool applyGroup(
const std::vector<not_null<HistoryItem*>> &others) override;
bool hasReplyPreview() const override;
ImagePtr replyPreview() override;
TextWithEntities getCaption() const override;
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
bool overrideEditedDate() const override {
return true;
}
HistoryMessageEdited *displayedEditBadge() const override;
bool canBeGrouped() const override {
return true;
}
bool skipBubbleTail() const override {
return isBubbleBottom() && _caption.isEmpty();
}
void updateNeedBubbleState() override;
bool needsBubble() const override;
bool customInfoLayout() const override {
return _caption.isEmpty();
}
bool allowsFastShare() const override {
return true;
}
private:
struct Element {
Element(not_null<HistoryItem*> item);
not_null<HistoryItem*> item;
std::unique_ptr<HistoryMedia> content;
RectParts sides = RectPart::None;
QRect initialGeometry;
QRect geometry;
mutable uint64 cacheKey = 0;
mutable QPixmap cache;
};
bool computeNeedBubble() const;
not_null<HistoryMedia*> main() const;
bool validateGroupElements(
const std::vector<not_null<HistoryItem*>> &others) const;
HistoryTextState getElementState(
QPoint point,
HistoryStateRequest request) const;
Text _caption;
std::vector<Element> _elements;
bool _needBubble = false;
};

File diff suppressed because it is too large Load Diff

View File

@@ -38,6 +38,16 @@ class EmptyUserpic;
} // namespace Ui
void HistoryInitMedia();
TextWithEntities WithCaptionSelectedText(
const QString &attachType,
const Text &caption,
TextSelection selection);
QString WithCaptionNotificationText(
const QString &attachType,
const Text &caption);
QString WithCaptionDialogsText(
const QString &attachType,
const Text &caption);
class HistoryFileMedia : public HistoryMedia {
public:
@@ -62,21 +72,28 @@ public:
protected:
ClickHandlerPtr _openl, _savel, _cancell;
void setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell);
void setDocumentLinks(DocumentData *document, bool inlinegif = false) {
void setDocumentLinks(
not_null<DocumentData*> document,
not_null<HistoryItem*> realParent,
bool inlinegif = false) {
ClickHandlerPtr open, save;
const auto context = realParent->fullId();
if (inlinegif) {
open = MakeShared<GifOpenClickHandler>(document);
open = MakeShared<GifOpenClickHandler>(document, context);
} else {
open = MakeShared<DocumentOpenClickHandler>(document);
open = MakeShared<DocumentOpenClickHandler>(document, context);
}
if (inlinegif) {
save = MakeShared<GifOpenClickHandler>(document);
save = MakeShared<GifOpenClickHandler>(document, context);
} else if (document->isVoiceMessage()) {
save = MakeShared<DocumentOpenClickHandler>(document);
save = MakeShared<DocumentOpenClickHandler>(document, context);
} else {
save = MakeShared<DocumentSaveClickHandler>(document);
save = MakeShared<DocumentSaveClickHandler>(document, context);
}
setLinks(std::move(open), std::move(save), MakeShared<DocumentCancelClickHandler>(document));
setLinks(
std::move(open),
std::move(save),
MakeShared<DocumentCancelClickHandler>(document, context));
}
// >= 0 will contain download / upload string, _statusSize = loaded bytes
@@ -129,23 +146,39 @@ protected:
class HistoryPhoto : public HistoryFileMedia {
public:
HistoryPhoto(not_null<HistoryItem*> parent, not_null<PhotoData*> photo, const QString &caption);
HistoryPhoto(not_null<HistoryItem*> parent, not_null<PeerData*> chat, not_null<PhotoData*> photo, int width);
HistoryPhoto(not_null<HistoryItem*> parent, not_null<PeerData*> chat, const MTPDphoto &photo, int width);
HistoryPhoto(not_null<HistoryItem*> parent, const HistoryPhoto &other);
HistoryPhoto(
not_null<HistoryItem*> parent,
not_null<PhotoData*> photo,
const QString &caption);
HistoryPhoto(
not_null<HistoryItem*> parent,
not_null<PeerData*> chat,
not_null<PhotoData*> photo,
int width);
HistoryPhoto(
not_null<HistoryItem*> parent,
not_null<PeerData*> chat,
const MTPDphoto &photo,
int width);
HistoryPhoto(
not_null<HistoryItem*> parent,
not_null<HistoryItem*> realParent,
const HistoryPhoto &other);
void init();
HistoryMediaType type() const override {
return MediaTypePhoto;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
return std::make_unique<HistoryPhoto>(newParent, *this);
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
return std::make_unique<HistoryPhoto>(newParent, realParent, *this);
}
void initDimensions() override;
int resizeGetHeight(int width) override;
void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
void draw(Painter &p, const QRect &clip, TextSelection selection, TimeMs ms) const override;
HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
[[nodiscard]] TextSelection adjustSelection(
@@ -166,10 +199,28 @@ public:
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
PhotoData *photo() const {
PhotoData *getPhoto() const override {
return _data;
}
bool canBeGrouped() const override {
return true;
}
QSize sizeForGrouping() const override;
void drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
TimeMs ms,
const QRect &geometry,
RectParts corners,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const override;
HistoryTextState getStateGrouped(
const QRect &geometry,
QPoint point,
HistoryStateRequest request) const override;
void updateSentMedia(const MTPMessageMedia &media) override;
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
@@ -210,6 +261,12 @@ protected:
}
private:
void validateGroupedCache(
const QRect &geometry,
RectParts corners,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const;
not_null<PhotoData*> _data;
int16 _pixw = 1;
int16 _pixh = 1;
@@ -219,13 +276,22 @@ private:
class HistoryVideo : public HistoryFileMedia {
public:
HistoryVideo(not_null<HistoryItem*> parent, DocumentData *document, const QString &caption);
HistoryVideo(not_null<HistoryItem*> parent, const HistoryVideo &other);
HistoryVideo(
not_null<HistoryItem*> parent,
not_null<DocumentData*> document,
const QString &caption);
HistoryVideo(
not_null<HistoryItem*> parent,
not_null<HistoryItem*> realParent,
const HistoryVideo &other);
HistoryMediaType type() const override {
return MediaTypeVideo;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
return std::make_unique<HistoryVideo>(newParent, *this);
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
return std::make_unique<HistoryVideo>(newParent, realParent, *this);
}
void initDimensions() override;
@@ -252,10 +318,28 @@ public:
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
DocumentData *getDocument() override {
DocumentData *getDocument() const override {
return _data;
}
bool canBeGrouped() const override {
return true;
}
QSize sizeForGrouping() const override;
void drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
TimeMs ms,
const QRect &geometry,
RectParts corners,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const override;
HistoryTextState getStateGrouped(
const QRect &geometry,
QPoint point,
HistoryStateRequest request) const override;
bool uploading() const override {
return _data->uploading();
}
@@ -297,13 +381,18 @@ protected:
}
private:
void validateGroupedCache(
const QRect &geometry,
RectParts corners,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const;
void setStatusSize(int32 newSize) const;
void updateStatusText() const;
not_null<DocumentData*> _data;
int32 _thumbw;
Text _caption;
void setStatusSize(int32 newSize) const;
void updateStatusText() const;
};
struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed> {
@@ -370,8 +459,14 @@ private:
class HistoryDocument : public HistoryFileMedia, public RuntimeComposer {
public:
HistoryDocument(not_null<HistoryItem*> parent, DocumentData *document, const QString &caption);
HistoryDocument(not_null<HistoryItem*> parent, const HistoryDocument &other);
HistoryDocument(
not_null<HistoryItem*> parent,
not_null<DocumentData*> document,
const QString &caption);
HistoryDocument(
not_null<HistoryItem*> parent,
const HistoryDocument &other);
HistoryMediaType type() const override {
return _data->isVoiceMessage()
? MediaTypeVoiceFile
@@ -379,7 +474,11 @@ public:
? MediaTypeMusicFile
: MediaTypeFile);
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryDocument>(newParent, *this);
}
@@ -418,7 +517,7 @@ public:
return _data->uploading();
}
DocumentData *getDocument() override {
DocumentData *getDocument() const override {
return _data;
}
@@ -488,12 +587,20 @@ private:
class HistoryGif : public HistoryFileMedia {
public:
HistoryGif(not_null<HistoryItem*> parent, DocumentData *document, const QString &caption);
HistoryGif(
not_null<HistoryItem*> parent,
not_null<DocumentData*> document,
const QString &caption);
HistoryGif(not_null<HistoryItem*> parent, const HistoryGif &other);
HistoryMediaType type() const override {
return MediaTypeGif;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryGif>(newParent, *this);
}
@@ -525,7 +632,7 @@ public:
return _data->uploading();
}
DocumentData *getDocument() override {
DocumentData *getDocument() const override {
return _data;
}
Media::Clip::Reader *getClipReader() override {
@@ -602,11 +709,18 @@ private:
class HistorySticker : public HistoryMedia {
public:
HistorySticker(not_null<HistoryItem*> parent, DocumentData *document);
HistorySticker(
not_null<HistoryItem*> parent,
not_null<DocumentData*> document);
HistoryMediaType type() const override {
return MediaTypeSticker;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistorySticker>(newParent, _data);
}
@@ -629,7 +743,7 @@ public:
QString notificationText() const override;
TextWithEntities selectedText(TextSelection selection) const override;
DocumentData *getDocument() override {
DocumentData *getDocument() const override {
return _data;
}
@@ -671,12 +785,27 @@ private:
class HistoryContact : public HistoryMedia {
public:
HistoryContact(not_null<HistoryItem*> parent, int32 userId, const QString &first, const QString &last, const QString &phone);
HistoryContact(
not_null<HistoryItem*> parent,
int32 userId,
const QString &first,
const QString &last,
const QString &phone);
HistoryMediaType type() const override {
return MediaTypeContact;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
return std::make_unique<HistoryContact>(newParent, _userId, _fname, _lname, _phone);
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryContact>(
newParent,
_userId,
_fname,
_lname,
_phone);
}
void initDimensions() override;
@@ -735,11 +864,16 @@ private:
class HistoryCall : public HistoryMedia {
public:
HistoryCall(not_null<HistoryItem*> parent, const MTPDmessageActionPhoneCall &call);
HistoryCall(
not_null<HistoryItem*> parent,
const MTPDmessageActionPhoneCall &call);
HistoryMediaType type() const override {
return MediaTypeCall;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Unexpected("Clone HistoryCall.");
}
@@ -790,12 +924,21 @@ private:
class HistoryWebPage : public HistoryMedia {
public:
HistoryWebPage(not_null<HistoryItem*> parent, not_null<WebPageData*> data);
HistoryWebPage(not_null<HistoryItem*> parent, const HistoryWebPage &other);
HistoryWebPage(
not_null<HistoryItem*> parent,
not_null<WebPageData*> data);
HistoryWebPage(
not_null<HistoryItem*> parent,
const HistoryWebPage &other);
HistoryMediaType type() const override {
return MediaTypeWebPage;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryWebPage>(newParent, *this);
}
@@ -830,7 +973,10 @@ public:
bool isDisplayed() const override {
return !_data->pendingTill && !_parent->Has<HistoryMessageLogEntryOriginal>();
}
DocumentData *getDocument() override {
PhotoData *getPhoto() const override {
return _attach ? _attach->getPhoto() : nullptr;
}
DocumentData *getDocument() const override {
return _attach ? _attach->getDocument() : nullptr;
}
Media::Clip::Reader *getClipReader() override {
@@ -898,12 +1044,17 @@ private:
class HistoryGame : public HistoryMedia {
public:
HistoryGame(not_null<HistoryItem*> parent, GameData *data);
HistoryGame(not_null<HistoryItem*> parent, not_null<GameData*> data);
HistoryGame(not_null<HistoryItem*> parent, const HistoryGame &other);
HistoryMediaType type() const override {
return MediaTypeGame;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryGame>(newParent, *this);
}
@@ -941,7 +1092,10 @@ public:
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
DocumentData *getDocument() override {
PhotoData *getPhoto() const override {
return _attach ? _attach->getPhoto() : nullptr;
}
DocumentData *getDocument() const override {
return _attach ? _attach->getDocument() : nullptr;
}
Media::Clip::Reader *getClipReader() override {
@@ -962,7 +1116,7 @@ public:
}
ImagePtr replyPreview() override;
GameData *game() {
not_null<GameData*> game() {
return _data;
}
@@ -993,7 +1147,7 @@ private:
QMargins inBubblePadding() const;
int bottomInfoPadding() const;
GameData *_data;
not_null<GameData*> _data;
ClickHandlerPtr _openl;
std::unique_ptr<HistoryMedia> _attach;
@@ -1007,12 +1161,21 @@ private:
class HistoryInvoice : public HistoryMedia {
public:
HistoryInvoice(not_null<HistoryItem*> parent, const MTPDmessageMediaInvoice &data);
HistoryInvoice(not_null<HistoryItem*> parent, const HistoryInvoice &other);
HistoryInvoice(
not_null<HistoryItem*> parent,
const MTPDmessageMediaInvoice &data);
HistoryInvoice(
not_null<HistoryItem*> parent,
const HistoryInvoice &other);
HistoryMediaType type() const override {
return MediaTypeInvoice;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryInvoice>(newParent, *this);
}
@@ -1103,12 +1266,23 @@ struct LocationData;
class HistoryLocation : public HistoryMedia {
public:
HistoryLocation(not_null<HistoryItem*> parent, const LocationCoords &coords, const QString &title = QString(), const QString &description = QString());
HistoryLocation(not_null<HistoryItem*> parent, const HistoryLocation &other);
HistoryLocation(
not_null<HistoryItem*> parent,
const LocationCoords &coords,
const QString &title = QString(),
const QString &description = QString());
HistoryLocation(
not_null<HistoryItem*> parent,
const HistoryLocation &other);
HistoryMediaType type() const override {
return MediaTypeLocation;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryLocation>(newParent, *this);
}

View File

@@ -125,6 +125,7 @@ bool HasMediaItems(const HistoryItemsList &items) {
switch (media->type()) {
case MediaTypePhoto:
case MediaTypeVideo:
case MediaTypeGrouped:
case MediaTypeFile:
case MediaTypeMusicFile:
case MediaTypeVoiceFile: return true;
@@ -181,27 +182,28 @@ bool HasInlineItems(const HistoryItemsList &items) {
void FastShareMessage(not_null<HistoryItem*> item) {
struct ShareData {
ShareData(const FullMsgId &msgId) : msgId(msgId) {
ShareData(not_null<PeerData*> peer, MessageIdsList &&ids)
: peer(peer)
, msgIds(std::move(ids)) {
}
FullMsgId msgId;
OrderedSet<mtpRequestId> requests;
not_null<PeerData*> peer;
MessageIdsList msgIds;
base::flat_set<mtpRequestId> requests;
};
auto data = MakeShared<ShareData>(item->fullId());
auto isGame = item->getMessageBot()
const auto data = MakeShared<ShareData>(item->history()->peer, [&] {
if (const auto group = item->getFullGroup()) {
return Auth().data().groupToIds(group);
}
return MessageIdsList(1, item->fullId());
}());
const auto isGame = item->getMessageBot()
&& item->getMedia()
&& (item->getMedia()->type() == MediaTypeGame);
const auto canCopyLink = item->hasDirectLink() || isGame;
auto canCopyLink = item->hasDirectLink();
if (!canCopyLink) {
if (auto bot = item->getMessageBot()) {
if (auto media = item->getMedia()) {
canCopyLink = (media->type() == MediaTypeGame);
}
}
}
auto copyCallback = [data]() {
if (auto main = App::main()) {
if (auto item = App::histItemById(data->msgId)) {
if (auto item = App::histItemById(data->msgIds[0])) {
if (item->hasDirectLink()) {
QApplication::clipboard()->setText(item->directLink());
@@ -224,12 +226,11 @@ void FastShareMessage(not_null<HistoryItem*> item) {
if (!data->requests.empty()) {
return; // Share clicked already.
}
auto item = App::histItemById(data->msgId);
if (!item || result.empty()) {
auto items = Auth().data().idsToItems(data->msgIds);
if (items.empty() || result.empty()) {
return;
}
auto items = HistoryItemsList(1, item);
auto restrictedSomewhere = false;
auto restrictedEverywhere = true;
auto firstError = QString();
@@ -262,16 +263,32 @@ void FastShareMessage(not_null<HistoryItem*> item) {
}
};
auto sendFlags = MTPmessages_ForwardMessages::Flag::f_with_my_score;
MTPVector<MTPint> msgIds = MTP_vector<MTPint>(1, MTP_int(data->msgId.msg));
auto sendFlags = MTPmessages_ForwardMessages::Flag::f_with_my_score
| MTPmessages_ForwardMessages::Flag::f_grouped;
auto msgIds = QVector<MTPint>();
msgIds.reserve(data->msgIds.size());
for (const auto fullId : data->msgIds) {
msgIds.push_back(MTP_int(fullId.msg));
}
auto generateRandom = [&] {
auto result = QVector<MTPlong>(data->msgIds.size());
for (auto &value : result) {
value = rand_value<MTPlong>();
}
return result;
};
if (auto main = App::main()) {
for (const auto peer : result) {
if (!GetErrorTextForForward(peer, items).isEmpty()) {
continue;
}
MTPVector<MTPlong> random = MTP_vector<MTPlong>(1, rand_value<MTPlong>());
auto request = MTPmessages_ForwardMessages(MTP_flags(sendFlags), item->history()->peer->input, msgIds, random, peer->input);
auto request = MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
data->peer->input,
MTP_vector<MTPint>(msgIds),
MTP_vector<MTPlong>(generateRandom()),
peer->input);
auto callback = doneCallback;
auto requestId = MTP::send(request, rpcDone(std::move(callback)));
data->requests.insert(requestId);
@@ -381,13 +398,13 @@ int HistoryMessageSigned::maxWidth() const {
return _signature.maxWidth();
}
void HistoryMessageEdited::create(const QDateTime &editDate, const QString &date) {
_editDate = editDate;
_edited.setText(st::msgDateTextStyle, lang(lng_edited) + ' ' + date, _textNameOptions);
void HistoryMessageEdited::refresh(const QString &date, bool displayed) {
const auto prefix = displayed ? (lang(lng_edited) + ' ') : QString();
text.setText(st::msgDateTextStyle, prefix + date, _textNameOptions);
}
int HistoryMessageEdited::maxWidth() const {
return _edited.maxWidth();
return text.maxWidth();
}
void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
@@ -630,7 +647,9 @@ int HistoryMessage::KeyboardStyle::minButtonWidth(HistoryMessageReplyMarkup::But
return result;
}
HistoryMessage::HistoryMessage(not_null<History*> history, const MTPDmessage &msg)
HistoryMessage::HistoryMessage(
not_null<History*> history,
const MTPDmessage &msg)
: HistoryItem(history, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) {
CreateConfig config;
@@ -655,6 +674,9 @@ HistoryMessage::HistoryMessage(not_null<History*> history, const MTPDmessage &ms
if (msg.has_reply_markup()) config.mtpMarkup = &msg.vreply_markup;
if (msg.has_edit_date()) config.editDate = ::date(msg.vedit_date);
if (msg.has_post_author()) config.author = qs(msg.vpost_author);
if (msg.has_grouped_id()) {
config.groupId = MessageGroupId::FromRaw(msg.vgrouped_id.v);
}
createComponents(config);
@@ -665,7 +687,9 @@ HistoryMessage::HistoryMessage(not_null<History*> history, const MTPDmessage &ms
setText({ text, entities });
}
HistoryMessage::HistoryMessage(not_null<History*> history, const MTPDmessageService &msg)
HistoryMessage::HistoryMessage(
not_null<History*> history,
const MTPDmessageService &msg)
: HistoryItem(history, msg.vid.v, mtpCastFlags(msg.vflags.v), ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) {
CreateConfig config;
@@ -750,27 +774,58 @@ HistoryMessage::HistoryMessage(
return (mediaType != MediaTypeCount);
};
if (cloneMedia()) {
_media = mediaOriginal->clone(this);
_media = mediaOriginal->clone(this, this);
}
setText(fwd->originalText());
}
HistoryMessage::HistoryMessage(not_null<History*> history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, const TextWithEntities &textWithEntities)
HistoryMessage::HistoryMessage(
not_null<History*> history,
MsgId id,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
const TextWithEntities &textWithEntities)
: HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, MTPnullMarkup);
setText(textWithEntities);
}
HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup)
HistoryMessage::HistoryMessage(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
not_null<DocumentData*> document,
const QString &caption,
const MTPReplyMarkup &markup)
: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
initMediaFromDocument(doc, caption);
initMediaFromDocument(document, caption);
setText(TextWithEntities());
}
HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup)
HistoryMessage::HistoryMessage(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
not_null<PhotoData*> photo,
const QString &caption,
const MTPReplyMarkup &markup)
: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
@@ -778,7 +833,17 @@ HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmess
setText(TextWithEntities());
}
HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup)
HistoryMessage::HistoryMessage(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
not_null<GameData*> game,
const MTPReplyMarkup &markup)
: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
@@ -786,7 +851,12 @@ HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmess
setText(TextWithEntities());
}
void HistoryMessage::createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup) {
void HistoryMessage::createComponentsHelper(
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
const QString &postAuthor,
const MTPReplyMarkup &markup) {
CreateConfig config;
if (flags & MTPDmessage::Flag::f_via_bot_id) config.viaBotId = viaBotId;
@@ -818,6 +888,7 @@ void HistoryMessage::updateMediaInBubbleState() {
return;
}
_media->updateNeedBubbleState();
if (!drawBubble()) {
_media->setInBubbleState(MediaInBubbleState::None);
return;
@@ -882,18 +953,43 @@ void HistoryMessage::applyGroupAdminChanges(
}
}
bool HistoryMessage::displayEditedBadge(bool hasViaBotOrInlineMarkup) const {
bool HistoryMessage::displayEditedBadge() const {
return !displayedEditDate().isNull();
}
QDateTime HistoryMessage::displayedEditDate() const {
auto hasViaBotId = Has<HistoryMessageVia>();
auto hasInlineMarkup = (inlineReplyMarkup() != nullptr);
return displayedEditDate(hasViaBotId || hasInlineMarkup);
}
QDateTime HistoryMessage::displayedEditDate(
bool hasViaBotOrInlineMarkup) const {
if (hasViaBotOrInlineMarkup) {
return false;
} else if (!(_flags & MTPDmessage::Flag::f_edit_date)) {
return false;
}
if (auto fromUser = from()->asUser()) {
return QDateTime();
} else if (const auto fromUser = from()->asUser()) {
if (fromUser->botInfo) {
return false;
return QDateTime();
}
}
return true;
if (const auto edited = displayedEditBadge()) {
return edited->date;
}
return QDateTime();
}
HistoryMessageEdited *HistoryMessage::displayedEditBadge() {
if (_media && _media->overrideEditedDate()) {
return _media->displayedEditBadge();
}
return Get<HistoryMessageEdited>();
}
const HistoryMessageEdited *HistoryMessage::displayedEditBadge() const {
if (_media && _media->overrideEditedDate()) {
return _media->displayedEditBadge();
}
return Get<HistoryMessageEdited>();
}
bool HistoryMessage::uploading() const {
@@ -945,7 +1041,7 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
}
return (config.inlineMarkup != nullptr);
};
if (displayEditedBadge(hasViaBot || hasInlineMarkup())) {
if (!config.editDate.isNull()) {
mask |= HistoryMessageEdited::Bit();
}
if (config.senderOriginal) {
@@ -960,10 +1056,13 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
} else if (config.inlineMarkup) {
mask |= HistoryMessageReplyMarkup::Bit();
}
if (config.groupId) {
mask |= HistoryMessageGroup::Bit();
}
UpdateComponents(mask);
if (auto reply = Get<HistoryMessageReply>()) {
if (const auto reply = Get<HistoryMessageReply>()) {
reply->replyToMsgId = config.replyTo;
if (!reply->updateData(this)) {
Auth().api().requestMessageData(
@@ -972,21 +1071,16 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
HistoryDependentItemCallback(fullId()));
}
}
if (auto via = Get<HistoryMessageVia>()) {
if (const auto via = Get<HistoryMessageVia>()) {
via->create(config.viaBotId);
}
if (auto views = Get<HistoryMessageViews>()) {
if (const auto views = Get<HistoryMessageViews>()) {
views->_views = config.viewsCount;
}
if (auto edited = Get<HistoryMessageEdited>()) {
edited->create(config.editDate, date.toString(cTimeFormat()));
if (auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->create(config.author, edited->_edited.originalText());
}
} else if (auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->create(config.author, date.toString(cTimeFormat()));
if (const auto edited = Get<HistoryMessageEdited>()) {
edited->date = config.editDate;
}
if (auto forwarded = Get<HistoryMessageForwarded>()) {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
forwarded->_originalDate = config.originalDate;
forwarded->_originalSender = App::peer(config.senderOriginal);
forwarded->_originalId = config.originalId;
@@ -994,7 +1088,7 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
forwarded->_savedFromPeer = App::peerLoaded(config.savedFromPeer);
forwarded->_savedFromMsgId = config.savedFromMsgId;
}
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
if (config.mtpMarkup) {
markup->create(*config.mtpMarkup);
} else if (config.inlineMarkup) {
@@ -1004,7 +1098,10 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
_flags |= MTPDmessage_ClientFlag::f_has_switch_inline_button;
}
}
initTime();
if (const auto group = Get<HistoryMessageGroup>()) {
group->groupId = config.groupId;
group->leader = this;
}
_fromNameVersion = displayFrom()->nameVersion;
}
@@ -1028,18 +1125,23 @@ QString formatViewsCount(int32 views) {
}
void HistoryMessage::initTime() {
if (auto msgsigned = Get<HistoryMessageSigned>()) {
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
_timeWidth = msgsigned->maxWidth();
} else if (auto edited = Get<HistoryMessageEdited>()) {
} else if (const auto edited = displayedEditBadge()) {
_timeWidth = edited->maxWidth();
} else {
_timeText = date.toString(cTimeFormat());
_timeWidth = st::msgDateFont->width(_timeText);
}
if (auto views = Get<HistoryMessageViews>()) {
if (const auto views = Get<HistoryMessageViews>()) {
views->_viewsText = (views->_views >= 0) ? formatViewsCount(views->_views) : QString();
views->_viewsWidth = views->_viewsText.isEmpty() ? 0 : st::msgDateFont->width(views->_viewsText);
}
if (_text.hasSkipBlock()) {
_text.setSkipBlock(skipBlockWidth(), skipBlockHeight());
_textWidth = -1;
_textHeight = 0;
}
}
void HistoryMessage::initMedia(const MTPMessageMedia *media) {
@@ -1144,6 +1246,7 @@ int32 HistoryMessage::plainMaxWidth() const {
void HistoryMessage::initDimensions() {
updateMediaInBubbleState();
refreshEditedBadge();
if (drawBubble()) {
auto forwarded = Get<HistoryMessageForwarded>();
auto reply = Get<HistoryMessageReply>();
@@ -1240,14 +1343,16 @@ void HistoryMessage::initDimensions() {
} else if (_media) {
_media->initDimensions();
_maxw = _media->maxWidth();
_minh = _media->minHeight();
_minh = _media->isDisplayed() ? _media->minHeight() : 0;
} else {
_maxw = st::msgMinWidth;
_minh = 0;
}
if (auto markup = inlineReplyMarkup()) {
if (const auto markup = inlineReplyMarkup()) {
if (!markup->inlineKeyboard) {
markup->inlineKeyboard = std::make_unique<ReplyKeyboard>(this, std::make_unique<KeyboardStyle>(st::msgBotKbButton));
markup->inlineKeyboard = std::make_unique<ReplyKeyboard>(
this,
std::make_unique<KeyboardStyle>(st::msgBotKbButton));
}
// if we have a text bubble we can resize it to fit the keyboard
@@ -1259,7 +1364,9 @@ void HistoryMessage::initDimensions() {
}
bool HistoryMessage::drawBubble() const {
if (Has<HistoryMessageLogEntryOriginal>()) {
if (isHiddenByGroup()) {
return false;
} else if (Has<HistoryMessageLogEntryOriginal>()) {
return true;
}
return _media ? (!emptyText() || _media->needsBubble()) : !isEmpty();
@@ -1334,24 +1441,11 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
if (message.has_edit_date()) {
_flags |= MTPDmessage::Flag::f_edit_date;
auto hasViaBotId = Has<HistoryMessageVia>();
auto hasInlineMarkup = (inlineReplyMarkup() != nullptr);
if (displayEditedBadge(hasViaBotId || hasInlineMarkup)) {
if (!Has<HistoryMessageEdited>()) {
AddComponents(HistoryMessageEdited::Bit());
}
auto edited = Get<HistoryMessageEdited>();
edited->create(::date(message.vedit_date), date.toString(cTimeFormat()));
if (auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->create(msgsigned->_author, edited->_edited.originalText());
}
} else if (Has<HistoryMessageEdited>()) {
RemoveComponents(HistoryMessageEdited::Bit());
if (auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->create(msgsigned->_author, date.toString(cTimeFormat()));
}
if (!Has<HistoryMessageEdited>()) {
AddComponents(HistoryMessageEdited::Bit());
}
initTime();
auto edited = Get<HistoryMessageEdited>();
edited->date = ::date(message.vedit_date);
}
TextWithEntities textWithEntities = { qs(message.vmessage), EntitiesInText() };
@@ -1381,6 +1475,22 @@ void HistoryMessage::applyEditionToEmpty() {
finishEditionToEmpty();
}
void HistoryMessage::refreshEditedBadge() {
const auto edited = displayedEditBadge();
const auto editDate = displayedEditDate();
const auto dateText = date.toString(cTimeFormat());
if (edited) {
edited->refresh(dateText, !editDate.isNull());
}
if (auto msgsigned = Get<HistoryMessageSigned>()) {
const auto text = (!edited || editDate.isNull())
? dateText
: edited->text.originalText();
msgsigned->create(msgsigned->_author, text);
}
initTime();
}
bool HistoryMessage::displayForwardedFrom() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
if (history()->peer->isSelf()) {
@@ -1397,7 +1507,9 @@ bool HistoryMessage::displayForwardedFrom() const {
void HistoryMessage::updateMedia(const MTPMessageMedia *media) {
auto setMediaAllowed = [](HistoryMediaType type) {
return (type == MediaTypeWebPage || type == MediaTypeGame || type == MediaTypeLocation);
return (type == MediaTypeWebPage)
|| (type == MediaTypeGame)
|| (type == MediaTypeLocation);
};
if (_flags & MTPDmessage_ClientFlag::f_from_inline_bot) {
bool needReSet = true;
@@ -1445,12 +1557,24 @@ Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const {
TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
TextWithEntities logEntryOriginalResult;
auto textResult = _text.originalTextWithEntities((selection == FullSelection) ? AllTextSelection : selection, ExpandLinksAll);
const auto textSelection = (selection == FullSelection)
? AllTextSelection
: IsSubGroupSelection(selection)
? TextSelection(0, 0)
: selection;
auto textResult = _text.originalTextWithEntities(
textSelection,
ExpandLinksAll);
auto skipped = skipTextSelection(selection);
auto mediaDisplayed = (_media && _media->isDisplayed());
auto mediaResult = mediaDisplayed ? _media->selectedText(skipped) : TextWithEntities();
auto mediaResult = (mediaDisplayed || isHiddenByGroup())
? _media->selectedText(skipped)
: TextWithEntities();
if (auto entry = Get<HistoryMessageLogEntryOriginal>()) {
logEntryOriginalResult = entry->_page->selectedText(mediaDisplayed ? _media->skipSelection(skipped) : skipped);
const auto originalSelection = mediaDisplayed
? _media->skipSelection(skipped)
: skipped;
logEntryOriginalResult = entry->_page->selectedText(originalSelection);
}
auto result = textResult;
if (result.text.isEmpty()) {
@@ -1512,6 +1636,7 @@ void HistoryMessage::setMedia(const MTPMessageMedia *media) {
_textWidth = -1;
_textHeight = 0;
}
_history->recountGroupingAround(this);
}
void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
@@ -1665,10 +1790,10 @@ void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, int32 width
}
dateX += HistoryMessage::timeLeft();
if (auto msgsigned = Get<HistoryMessageSigned>()) {
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->_signature.drawElided(p, dateX, dateY, _timeWidth);
} else if (auto edited = Get<HistoryMessageEdited>()) {
edited->_edited.drawElided(p, dateX, dateY, _timeWidth);
} else if (const auto edited = displayedEditBadge()) {
edited->text.drawElided(p, dateX, dateY, _timeWidth);
} else {
p.drawText(dateX, dateY + st::msgDateFont->ascent, _timeText);
}
@@ -1804,7 +1929,9 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
auto entry = Get<HistoryMessageLogEntryOriginal>();
auto mediaDisplayed = _media && _media->isDisplayed();
auto skipTail = isAttachedToNext() || (_media && _media->skipBubbleTail()) || (keyboard != nullptr);
auto skipTail = isAttachedToNext()
|| (_media && _media->skipBubbleTail())
|| (keyboard != nullptr);
auto displayTail = skipTail ? RectPart::None : (outbg && !Adaptive::ChatWide()) ? RectPart::Right : RectPart::Left;
HistoryLayout::paintBubble(p, g, width(), selected, outbg, displayTail);
@@ -1872,7 +1999,7 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize;
drawRightAction(p, fastShareLeft, fastShareTop, width());
}
} else if (_media) {
} else if (_media && _media->isDisplayed()) {
p.translate(g.topLeft());
_media->draw(p, clip.translated(-g.topLeft()), skipTextSelection(selection), ms);
p.translate(-g.topLeft());
@@ -1880,7 +2007,7 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
p.restoreTextPalette();
auto reply = Get<HistoryMessageReply>();
const auto reply = Get<HistoryMessageReply>();
if (reply && reply->isNameUpdated()) {
const_cast<HistoryMessage*>(this)->setPendingInitDimensions();
}
@@ -2006,16 +2133,16 @@ void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) {
}
int HistoryMessage::resizeContentGetHeight() {
int result = performResizeGetHeight();
const auto result = performResizeGetHeight();
auto keyboard = inlineReplyKeyboard();
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
int oldTop = markup->oldTop;
const auto keyboard = inlineReplyKeyboard();
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
const auto oldTop = markup->oldTop;
if (oldTop >= 0) {
markup->oldTop = -1;
if (keyboard) {
int h = st::msgBotKbButton.margin + keyboard->naturalHeight();
int keyboardTop = _height - h + st::msgBotKbButton.margin - marginBottom();
const auto height = st::msgBotKbButton.margin + keyboard->naturalHeight();
const auto keyboardTop = _height - height + st::msgBotKbButton.margin - marginBottom();
if (keyboardTop != oldTop) {
Notify::inlineKeyboardMoved(this, oldTop, keyboardTop);
}
@@ -2027,7 +2154,9 @@ int HistoryMessage::resizeContentGetHeight() {
}
int HistoryMessage::performResizeGetHeight() {
if (width() < st::msgMinWidth) return _height;
if (width() < st::msgMinWidth) {
return _height;
}
auto contentWidth = width() - (st::msgMargin.left() + st::msgMargin.right());
if (history()->peer->isSelf() && !hasOutLayout()) {
@@ -2111,14 +2240,14 @@ int HistoryMessage::performResizeGetHeight() {
reply->resize(countGeometry().width() - st::msgPadding.left() - st::msgPadding.right());
_height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
}
} else if (_media) {
} else if (_media && _media->isDisplayed()) {
_height = _media->resizeGetHeight(contentWidth);
} else {
_height = 0;
}
if (auto keyboard = inlineReplyKeyboard()) {
auto g = countGeometry();
auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
if (const auto keyboard = inlineReplyKeyboard()) {
const auto g = countGeometry();
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
_height += keyboardHeight;
keyboard->resize(g.width(), keyboardHeight - st::msgBotKbButton.margin);
}
@@ -2128,7 +2257,7 @@ int HistoryMessage::performResizeGetHeight() {
}
bool HistoryMessage::hasPoint(QPoint point) const {
auto g = countGeometry();
const auto g = countGeometry();
if (g.width() < 1) {
return false;
}
@@ -2161,7 +2290,7 @@ bool HistoryMessage::pointInTime(int right, int bottom, QPoint point, InfoDispla
}
HistoryTextState HistoryMessage::getState(QPoint point, HistoryStateRequest request) const {
HistoryTextState result;
auto result = HistoryTextState(this);
auto g = countGeometry();
if (g.width() < 1) {
@@ -2201,7 +2330,9 @@ HistoryTextState HistoryMessage::getState(QPoint point, HistoryStateRequest requ
auto entryLeft = g.left();
auto entryTop = trect.y() + trect.height();
if (point.y() >= entryTop && point.y() < entryTop + entryHeight) {
result = entry->_page->getState(point - QPoint(entryLeft, entryTop), request);
result = entry->_page->getState(
point - QPoint(entryLeft, entryTop),
request);
result.symbol += _text.length() + (mediaDisplayed ? _media->fullSelectionLength() : 0);
}
}
@@ -2248,7 +2379,7 @@ HistoryTextState HistoryMessage::getState(QPoint point, HistoryStateRequest requ
result.link = rightActionLink();
}
}
} else if (_media) {
} else if (_media && _media->isDisplayed()) {
result = _media->getState(point - g.topLeft(), request);
result.symbol += _text.length();
}
@@ -2278,7 +2409,7 @@ ClickHandlerPtr HistoryMessage::rightActionLink() const {
Window::SectionShow::Way::Forward,
savedFromMsgId);
} else {
FastShareMessage(item->toHistoryMessage());
FastShareMessage(item);
}
}
});
@@ -2337,7 +2468,10 @@ void HistoryMessage::updatePressed(QPoint point) {
}
}
bool HistoryMessage::getStateFromName(QPoint point, QRect &trect, HistoryTextState *outResult) const {
bool HistoryMessage::getStateFromName(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const {
if (displayFromName()) {
if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) {
auto user = displayFrom();
@@ -2357,7 +2491,11 @@ bool HistoryMessage::getStateFromName(QPoint point, QRect &trect, HistoryTextSta
return false;
}
bool HistoryMessage::getStateForwardedInfo(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const {
bool HistoryMessage::getStateForwardedInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult,
const HistoryStateRequest &request) const {
if (displayForwardedFrom()) {
auto forwarded = Get<HistoryMessageForwarded>();
auto fwdheight = ((forwarded->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
@@ -2367,7 +2505,10 @@ bool HistoryMessage::getStateForwardedInfo(QPoint point, QRect &trect, HistoryTe
if (breakEverywhere) {
textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
}
*outResult = forwarded->_text.getState(point - trect.topLeft(), trect.width(), textRequest);
*outResult = HistoryTextState(this, forwarded->_text.getState(
point - trect.topLeft(),
trect.width(),
textRequest));
outResult->symbol = 0;
outResult->afterSymbol = false;
if (breakEverywhere) {
@@ -2382,7 +2523,10 @@ bool HistoryMessage::getStateForwardedInfo(QPoint point, QRect &trect, HistoryTe
return false;
}
bool HistoryMessage::getStateReplyInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const {
bool HistoryMessage::getStateReplyInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const {
if (auto reply = Get<HistoryMessageReply>()) {
int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
if (point.y() >= trect.top() && point.y() < trect.top() + h) {
@@ -2396,7 +2540,10 @@ bool HistoryMessage::getStateReplyInfo(QPoint point, QRect &trect, HistoryTextSt
return false;
}
bool HistoryMessage::getStateViaBotIdInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const {
bool HistoryMessage::getStateViaBotIdInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const {
if (!displayFromName() && !Has<HistoryMessageForwarded>()) {
if (auto via = Get<HistoryMessageVia>()) {
if (QRect(trect.x(), trect.y(), via->_width, st::msgNameFont->height).contains(point)) {
@@ -2409,9 +2556,16 @@ bool HistoryMessage::getStateViaBotIdInfo(QPoint point, QRect &trect, HistoryTex
return false;
}
bool HistoryMessage::getStateText(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const {
bool HistoryMessage::getStateText(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult,
const HistoryStateRequest &request) const {
if (trect.contains(point)) {
*outResult = _text.getState(point - trect.topLeft(), trect.width(), request.forText());
*outResult = HistoryTextState(this, _text.getState(
point - trect.topLeft(),
trect.width(),
request.forText()));
return true;
}
return false;
@@ -2453,13 +2607,18 @@ TextSelection HistoryMessage::adjustSelection(TextSelection selection, TextSelec
}
void HistoryMessage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (_media) _media->clickHandlerActiveChanged(p, active);
HistoryItem::clickHandlerActiveChanged(p, active);
if (_media) {
_media->clickHandlerActiveChanged(p, active);
}
}
void HistoryMessage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
if (_media) _media->clickHandlerPressedChanged(p, pressed);
HistoryItem::clickHandlerPressedChanged(p, pressed);
if (_media) {
// HistoryGroupedMedia overrides HistoryItem App::pressedLinkItem().
_media->clickHandlerPressedChanged(p, pressed);
}
}
QString HistoryMessage::notificationHeader() const {

View File

@@ -30,26 +30,119 @@ void FastShareMessage(not_null<HistoryItem*> item);
class HistoryMessage : public HistoryItem, private HistoryItemInstantiated<HistoryMessage> {
public:
static not_null<HistoryMessage*> create(not_null<History*> history, const MTPDmessage &msg) {
static not_null<HistoryMessage*> create(
not_null<History*> history,
const MTPDmessage &msg) {
return _create(history, msg);
}
static not_null<HistoryMessage*> create(not_null<History*> history, const MTPDmessageService &msg) {
static not_null<HistoryMessage*> create(
not_null<History*> history,
const MTPDmessageService &msg) {
return _create(history, msg);
}
static not_null<HistoryMessage*> create(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, not_null<HistoryMessage*> fwd) {
static not_null<HistoryMessage*> create(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
QDateTime date,
UserId from,
const QString &postAuthor,
not_null<HistoryMessage*> fwd) {
return _create(history, msgId, flags, date, from, postAuthor, fwd);
}
static not_null<HistoryMessage*> create(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, const TextWithEntities &textWithEntities) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, postAuthor, textWithEntities);
static not_null<HistoryMessage*> create(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
const TextWithEntities &textWithEntities) {
return _create(
history,
msgId,
flags,
replyTo,
viaBotId,
date,
from,
postAuthor,
textWithEntities);
}
static not_null<HistoryMessage*> create(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, postAuthor, doc, caption, markup);
static not_null<HistoryMessage*> create(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
not_null<DocumentData*> document,
const QString &caption,
const MTPReplyMarkup &markup) {
return _create(
history,
msgId,
flags,
replyTo,
viaBotId,
date,
from,
postAuthor,
document,
caption,
markup);
}
static not_null<HistoryMessage*> create(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, postAuthor, photo, caption, markup);
static not_null<HistoryMessage*> create(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
not_null<PhotoData*> photo,
const QString &caption,
const MTPReplyMarkup &markup) {
return _create(
history,
msgId,
flags,
replyTo,
viaBotId,
date,
from,
postAuthor,
photo,
caption,
markup);
}
static not_null<HistoryMessage*> create(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, postAuthor, game, markup);
static not_null<HistoryMessage*> create(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
not_null<GameData*> game,
const MTPReplyMarkup &markup) {
return _create(
history,
msgId,
flags,
replyTo,
viaBotId,
date,
from,
postAuthor,
game,
markup);
}
void initTime();
@@ -70,7 +163,7 @@ public:
if (isAttachedToPrevious()) return false;
return true;
}
bool displayEditedBadge(bool hasViaBotOrInlineMarkup) const;
bool displayForwardedFrom() const;
bool uploading() const;
bool displayRightAction() const override;
@@ -116,6 +209,9 @@ public:
TextWithEntities originalText() const override;
bool textHasLinks() const override;
bool displayEditedBadge() const override;
QDateTime displayedEditDate() const override;
int infoWidth() const override;
int timeLeft() const override;
int timeWidth() const override {
@@ -155,14 +251,69 @@ public:
~HistoryMessage();
protected:
void refreshEditedBadge() override;
private:
HistoryMessage(not_null<History*> history, const MTPDmessage &msg);
HistoryMessage(not_null<History*> history, const MTPDmessageService &msg);
HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, not_null<HistoryMessage*> fwd); // local forwarded
HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, const TextWithEntities &textWithEntities); // local message
HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); // local document
HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); // local photo
HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup); // local game
HistoryMessage(
not_null<History*> history,
const MTPDmessage &msg);
HistoryMessage(
not_null<History*> history,
const MTPDmessageService &msg);
HistoryMessage(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
QDateTime date,
UserId from,
const QString &postAuthor,
not_null<HistoryMessage*> fwd); // local forwarded
HistoryMessage(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
const TextWithEntities &textWithEntities); // local message
HistoryMessage(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
not_null<DocumentData*> document,
const QString &caption,
const MTPReplyMarkup &markup); // local document
HistoryMessage(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
not_null<PhotoData*> photo,
const QString &caption,
const MTPReplyMarkup &markup); // local photo
HistoryMessage(
not_null<History*> history,
MsgId msgId,
MTPDmessage::Flags flags,
MsgId replyTo,
UserId viaBotId,
QDateTime date,
UserId from,
const QString &postAuthor,
not_null<GameData*> game,
const MTPReplyMarkup &markup); // local game
friend class HistoryItemInstantiated<HistoryMessage>;
void setEmptyText();
@@ -175,8 +326,10 @@ private:
int resizeContentGetHeight() override;
int performResizeGetHeight();
void applyEditionToEmpty();
QDateTime displayedEditDate(bool hasViaBotOrInlineMarkup) const;
const HistoryMessageEdited *displayedEditBadge() const;
HistoryMessageEdited *displayedEditBadge();
bool displayForwardedFrom() const;
void paintFromName(Painter &p, QRect &trect, bool selected) const;
void paintForwardedInfo(Painter &p, QRect &trect, bool selected) const;
void paintReplyInfo(Painter &p, QRect &trect, bool selected) const;
@@ -184,11 +337,28 @@ private:
void paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const;
void paintText(Painter &p, QRect &trect, TextSelection selection) const;
bool getStateFromName(QPoint point, QRect &trect, HistoryTextState *outResult) const;
bool getStateForwardedInfo(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const;
bool getStateReplyInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const;
bool getStateViaBotIdInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const;
bool getStateText(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const;
bool getStateFromName(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const;
bool getStateForwardedInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult,
const HistoryStateRequest &request) const;
bool getStateReplyInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const;
bool getStateViaBotIdInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const;
bool getStateText(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult,
const HistoryStateRequest &request) const;
void setMedia(const MTPMessageMedia *media);
void setReplyMarkup(const MTPReplyMarkup *markup);
@@ -214,6 +384,7 @@ private:
QString authorOriginal;
QDateTime originalDate;
QDateTime editDate;
MessageGroupId groupId = MessageGroupId::None;
// For messages created from MTP structs.
const MTPReplyMarkup *mtpMarkup = nullptr;

View File

@@ -286,6 +286,9 @@ HistoryService::PreparedText HistoryService::preparePinnedText() {
switch (media ? media->type() : MediaTypeCount) {
case MediaTypePhoto: return lang(lng_action_pinned_media_photo);
case MediaTypeVideo: return lang(lng_action_pinned_media_video);
case MediaTypeGrouped: return lang(media->getPhoto()
? lng_action_pinned_media_photo
: lng_action_pinned_media_video);
case MediaTypeContact: return lang(lng_action_pinned_media_contact);
case MediaTypeFile: return lang(lng_action_pinned_media_file);
case MediaTypeGif: {
@@ -594,7 +597,7 @@ bool HistoryService::hasPoint(QPoint point) const {
}
HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest request) const {
HistoryTextState result;
auto result = HistoryTextState(this);
auto g = countGeometry();
if (g.width() < 1) {
@@ -618,7 +621,10 @@ HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest requ
if (trect.contains(point)) {
auto textRequest = request.forText();
textRequest.align = style::al_center;
result = _text.getState(point - trect.topLeft(), trect.width(), textRequest);
result = HistoryTextState(this, _text.getState(
point - trect.topLeft(),
trect.width(),
textRequest));
if (auto gamescore = Get<HistoryServiceGameScore>()) {
if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(point)) {
result.link = gamescore->lnk;

View File

@@ -79,6 +79,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "window/window_peer_menu.h"
#include "inline_bots/inline_results_widget.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "core/crash_reports.h"
namespace {
@@ -781,6 +782,9 @@ void HistoryWidget::scrollToAnimationCallback(FullMsgId attachToId) {
}
void HistoryWidget::enqueueMessageHighlight(not_null<HistoryItem*> item) {
if (const auto group = item->getFullGroup()) {
item = group->leader;
}
auto enqueueMessageId = [this](MsgId universalId) {
if (_highlightQueue.empty() && !_highlightTimer.isActive()) {
highlightMessage(universalId);
@@ -883,6 +887,9 @@ void HistoryWidget::clearHighlightMessages() {
}
int HistoryWidget::itemTopForHighlight(not_null<HistoryItem*> item) const {
if (const auto group = item->getFullGroup()) {
item = group->leader;
}
auto itemTop = _list->itemTop(item);
Assert(itemTop >= 0);
@@ -2292,7 +2299,7 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages
if (_preloadRequest == requestId) {
auto to = toMigrated ? _migrated : _history;
if (cBetaVersion()) {
SignalHandlers::setCrashAnnotation("old_debugstr", QString(
CrashReports::SetAnnotation("old_debugstr", QString(
"%1_%2_%3_%4:%5_%6 (%7)"
).arg(PeerString(_debug_preloadDownPeer)
).arg(_debug_preloadOffsetId
@@ -2307,7 +2314,7 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages
addMessagesToFront(peer, *histList);
if (cBetaVersion()) {
SignalHandlers::setCrashAnnotation("old_debugstr", QString());
CrashReports::ClearAnnotation("old_debugstr");
}
_preloadRequest = 0;
@@ -2319,7 +2326,7 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages
} else if (_preloadDownRequest == requestId) {
auto to = toMigrated ? _migrated : _history;
if (cBetaVersion()) {
SignalHandlers::setCrashAnnotation("new_debugstr", QString(
CrashReports::SetAnnotation("new_debugstr", QString(
"%1_%2_%3_%4:%5_%6 (%7)"
).arg(PeerString(_debug_preloadDownPeer)
).arg(_debug_preloadDownOffsetId
@@ -2334,7 +2341,7 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages
addMessagesToBack(peer, *histList);
if (cBetaVersion()) {
SignalHandlers::setCrashAnnotation("new_debugstr", QString());
CrashReports::ClearAnnotation("new_debugstr");
}
_preloadDownRequest = 0;
@@ -3734,20 +3741,6 @@ void HistoryWidget::onCmdStart() {
setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, Ui::FlatTextarea::AddToUndoHistory);
}
void HistoryWidget::forwardMessage() {
auto item = App::contextItem();
if (!item || item->id < 0 || item->serviceMsg()) return;
Window::ShowForwardMessagesBox({ 1, item->fullId() });
}
void HistoryWidget::selectMessage() {
auto item = App::contextItem();
if (!item || item->id < 0 || item->serviceMsg()) return;
if (_list) _list->selectItem(item);
}
void HistoryWidget::setMembersShowAreaActive(bool active) {
if (!active) {
_membersDropdownShowTimer.stop();
@@ -4520,7 +4513,9 @@ void HistoryWidget::onThumbDocumentUploaded(
void HistoryWidget::onPhotoProgress(const FullMsgId &newId) {
if (const auto item = App::histItemById(newId)) {
const auto photo = (item->getMedia() && item->getMedia()->type() == MediaTypePhoto) ? static_cast<HistoryPhoto*>(item->getMedia())->photo() : nullptr;
const auto photo = item->getMedia()
? item->getMedia()->getPhoto()
: nullptr;
updateSendAction(item->history(), SendAction::Type::UploadPhoto, 0);
Auth().data().requestItemRepaint(item);
}
@@ -6195,19 +6190,6 @@ void HistoryWidget::onForwardSelected() {
});
}
void HistoryWidget::confirmDeleteContextItem() {
auto item = App::contextItem();
if (!item) return;
if (auto message = item->toHistoryMessage()) {
if (message->uploading()) {
App::main()->cancelUploadLayer();
return;
}
}
App::main()->deleteLayer();
}
void HistoryWidget::confirmDeleteSelectedItems() {
if (!_list) return;
@@ -6217,29 +6199,6 @@ void HistoryWidget::confirmDeleteSelectedItems() {
App::main()->deleteLayer(int(selected.size()));
}
void HistoryWidget::deleteContextItem(bool forEveryone) {
Ui::hideLayer();
auto item = App::contextItem();
if (!item) {
return;
}
auto toDelete = QVector<MTPint>(1, MTP_int(item->id));
auto history = item->history();
auto wasOnServer = (item->id > 0);
auto wasLast = (history->lastMsg == item);
item->destroy();
if (!wasOnServer && wasLast && !history->lastMsg) {
App::main()->checkPeerHistory(history->peer);
}
if (wasOnServer) {
App::main()->deleteMessages(history->peer, toDelete, forEveryone);
}
}
void HistoryWidget::deleteSelectedItems(bool forEveryone) {
Ui::hideLayer();
if (!_list) return;

View File

@@ -325,9 +325,7 @@ public:
bool isItemVisible(HistoryItem *item);
void confirmDeleteContextItem();
void confirmDeleteSelectedItems();
void deleteContextItem(bool forEveryone);
void deleteSelectedItems(bool forEveryone);
// Float player interface.
@@ -414,9 +412,6 @@ public slots:
void onWindowVisibleChanged();
void forwardMessage();
void selectMessage();
void onFieldFocused();
void onFieldResize();
void onCheckFieldAutocomplete();

View File

@@ -847,10 +847,8 @@ std::unique_ptr<BaseLayout> ListWidget::createLayout(
return nullptr;
}
auto getPhoto = [&]() -> PhotoData* {
if (auto media = item->getMedia()) {
if (media->type() == MediaTypePhoto) {
return static_cast<HistoryPhoto*>(media)->photo();
}
if (const auto media = item->getMedia()) {
return media->getPhoto();
}
return nullptr;
};

View File

@@ -260,10 +260,18 @@ Cover::Cover(
void Cover::setupChildGeometry() {
using namespace rpl::mappers;
//
// Visual Studio 2017 15.5.1 internal compiler error here.
// See https://developercommunity.visualstudio.com/content/problem/165155/ice-regression-in-1551-after-successfull-build-in.html
//
//rpl::combine(
// toggleShownValue(),
// widthValue(),
// _2)
rpl::combine(
toggleShownValue(),
widthValue(),
_2)
widthValue())
| rpl::map([](bool shown, int width) { return width; })
| rpl::start_with_next([this](int newWidth) {
_userpic->moveToLeft(
st::infoProfilePhotoLeft,
@@ -449,10 +457,18 @@ void SharedMediaCover::createLabel() {
Lang::Viewer(lng_profile_shared_media) | ToUpperValue(),
st::infoBlockHeaderLabel);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
//
// Visual Studio 2017 15.5.1 internal compiler error here.
// See https://developercommunity.visualstudio.com/content/problem/165155/ice-regression-in-1551-after-successfull-build-in.html
//
//rpl::combine(
// toggleShownValue(),
// widthValue(),
// _2)
rpl::combine(
toggleShownValue(),
widthValue(),
_2)
widthValue())
| rpl::map([](bool shown, int width) { return width; })
| rpl::start_with_next([this, weak = label.data()](int newWidth) {
auto availableWidth = newWidth
- st::infoBlockHeaderPosition.x()

View File

@@ -218,9 +218,9 @@ HistoryTextState Gif::getState(
HistoryStateRequest request) const {
if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
if (_delete && rtlpoint(point, _width).x() >= _width - st::stickerPanDeleteIconBg.width() && point.y() < st::stickerPanDeleteIconBg.height()) {
return _delete;
return { nullptr, _delete };
} else {
return _send;
return { nullptr, _send };
}
}
return {};
@@ -411,7 +411,7 @@ HistoryTextState Sticker::getState(
QPoint point,
HistoryStateRequest request) const {
if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
return _send;
return { nullptr, _send };
}
return {};
}
@@ -501,7 +501,7 @@ HistoryTextState Photo::getState(
QPoint point,
HistoryStateRequest request) const {
if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
return _send;
return { nullptr, _send };
}
return {};
}
@@ -646,10 +646,10 @@ HistoryTextState Video::getState(
QPoint point,
HistoryStateRequest request) const {
if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
return _link;
return { nullptr, _link };
}
if (QRect(st::inlineThumbSize + st::inlineThumbSkip, 0, _width - st::inlineThumbSize - st::inlineThumbSkip, _height).contains(point)) {
return _send;
return { nullptr, _send };
}
return {};
}
@@ -787,11 +787,11 @@ HistoryTextState File::getState(
QPoint point,
HistoryStateRequest request) const {
if (QRect(0, st::inlineRowMargin, st::msgFileSize, st::msgFileSize).contains(point)) {
return getShownDocument()->loading() ? _cancel : _open;
return { nullptr, getShownDocument()->loading() ? _cancel : _open };
} else {
auto left = st::msgFileSize + st::inlineThumbSkip;
if (QRect(left, 0, _width - left, _height).contains(point)) {
return _send;
return { nullptr, _send };
}
}
return {};
@@ -955,7 +955,7 @@ HistoryTextState Contact::getState(
if (!QRect(0, st::inlineRowMargin, st::msgFileSize, st::inlineThumbSize).contains(point)) {
auto left = (st::msgFileSize + st::inlineThumbSkip);
if (QRect(left, 0, _width - left, _height).contains(point)) {
return _send;
return { nullptr, _send };
}
}
return {};
@@ -1090,7 +1090,7 @@ HistoryTextState Article::getState(
QPoint point,
HistoryStateRequest request) const {
if (_withThumb && QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
return _link;
return { nullptr, _link };
}
auto left = _withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0;
if (QRect(left, 0, _width - left, _height).contains(point)) {
@@ -1100,10 +1100,10 @@ HistoryTextState Article::getState(
auto descriptionLines = 2;
auto descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines);
if (rtlrect(left, st::inlineRowMargin + titleHeight + descriptionHeight, _urlWidth, st::normalFont->height, _width).contains(point)) {
return _url;
return { nullptr, _url };
}
}
return _send;
return { nullptr, _send };
}
return {};
}
@@ -1275,23 +1275,23 @@ HistoryTextState Game::getState(
HistoryStateRequest request) const {
int left = st::inlineThumbSize + st::inlineThumbSkip;
if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
return _send;
return { nullptr, _send };
}
if (QRect(left, 0, _width - left, _height).contains(point)) {
return _send;
return { nullptr, _send };
}
return {};
}
void Game::prepareThumb(int width, int height) const {
auto thumb = ([this]() {
auto thumb = [this] {
if (auto photo = getResultPhoto()) {
return photo->medium;
} else if (auto document = getResultDocument()) {
return document->thumb;
}
return ImagePtr();
})();
}();
if (thumb->isNull()) {
return;
}

View File

@@ -24,6 +24,40 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
constexpr auto FullSelection = TextSelection { 0xFFFF, 0xFFFF };
inline bool IsSubGroupSelection(TextSelection selection) {
return (selection.from == 0xFFFF) && (selection.to != 0xFFFF);
}
inline bool IsGroupItemSelection(
TextSelection selection,
int index) {
Expects(index >= 0 && index < 0x0F);
return IsSubGroupSelection(selection) && (selection.to & (1 << index));
}
[[nodiscard]] inline TextSelection AddGroupItemSelection(
TextSelection selection,
int index) {
Expects(index >= 0 && index < 0x0F);
const auto bit = uint16(1U << index);
return TextSelection(
0xFFFF,
IsSubGroupSelection(selection) ? (selection.to | bit) : bit);
}
[[nodiscard]] inline TextSelection RemoveGroupItemSelection(
TextSelection selection,
int index) {
Expects(index >= 0 && index < 0x0F);
const auto bit = uint16(1U << index);
return IsSubGroupSelection(selection)
? TextSelection(0xFFFF, selection.to & ~bit)
: selection;
}
extern TextParseOptions _textNameOptions, _textDlgOptions;
extern TextParseOptions _historyTextOptions, _historyBotOptions, _historyTextNoMonoOptions, _historyBotNoMonoOptions;

File diff suppressed because it is too large Load Diff

View File

@@ -20,60 +20,61 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
class MTPlong;
namespace Core {
class Launcher;
} // namespace Core
namespace Logs {
void start();
bool started();
void finish();
void start(not_null<Core::Launcher*> launcher);
bool started();
void finish();
bool instanceChecked();
void multipleInstances();
bool instanceChecked();
void multipleInstances();
void closeMain();
void closeMain();
void writeMain(const QString &v);
void writeMain(const QString &v);
void writeDebug(const char *file, int32 line, const QString &v);
void writeTcp(const QString &v);
void writeMtp(int32 dc, const QString &v);
void writeDebug(const char *file, int32 line, const QString &v);
void writeTcp(const QString &v);
void writeMtp(int32 dc, const QString &v);
QString full();
inline const char *b(bool v) {
return v ? "[TRUE]" : "[FALSE]";
}
struct MemoryBuffer {
MemoryBuffer(const void *ptr, uint32 size) : p(ptr), s(size) {
}
QString str() const {
QString result;
const uchar *buf((const uchar*)p);
const char *hex = "0123456789ABCDEF";
result.reserve(s * 3);
for (uint32 i = 0; i < s; ++i) {
result += hex[(buf[i] >> 4)];
result += hex[buf[i] & 0x0F];
result += ' ';
}
result.chop(1);
return result;
}
const void *p;
uint32 s;
};
inline MemoryBuffer mb(const void *ptr, uint32 size) {
return MemoryBuffer(ptr, size);
}
QString vector(const QVector<MTPlong> &ids);
QString vector(const QVector<uint64> &ids);
QString full();
inline const char *b(bool v) {
return v ? "[TRUE]" : "[FALSE]";
}
struct MemoryBuffer {
MemoryBuffer(const void *ptr, uint32 size) : p(ptr), s(size) {
}
QString str() const {
QString result;
const uchar *buf((const uchar*)p);
const char *hex = "0123456789ABCDEF";
result.reserve(s * 3);
for (uint32 i = 0; i < s; ++i) {
result += hex[(buf[i] >> 4)];
result += hex[buf[i] & 0x0F];
result += ' ';
}
result.chop(1);
return result;
}
const void *p;
uint32 s;
};
inline MemoryBuffer mb(const void *ptr, uint32 size) {
return MemoryBuffer(ptr, size);
}
} // namespace Logs
#define LOG(msg) (Logs::writeMain(QString msg))
//usage LOG(("log: %1 %2").arg(1).arg(2))
@@ -85,47 +86,3 @@ namespace Logs {
#define MTP_LOG(dc, msg) { if (cDebug() || !Logs::started()) Logs::writeMtp(dc, QString msg); }
//usage MTP_LOG(dc, ("log: %1 %2").arg(1).arg(2))
namespace SignalHandlers {
struct dump {
~dump();
};
const dump &operator<<(const dump &stream, const char *str);
const dump &operator<<(const dump &stream, const wchar_t *str);
const dump &operator<<(const dump &stream, int num);
const dump &operator<<(const dump &stream, unsigned int num);
const dump &operator<<(const dump &stream, unsigned long num);
const dump &operator<<(const dump &stream, unsigned long long num);
const dump &operator<<(const dump &stream, double num);
enum Status {
CantOpen,
LastCrashed,
Started
};
Status start();
Status restart(); // can be only CantOpen or Started
void finish();
void setCrashAnnotation(const std::string &key, const QString &value);
// Remembers value pointer and tries to add the value to the crash report.
// Attention! You should call clearCrashAnnotationRef(key) before destroying value.
void setCrashAnnotationRef(const std::string &key, const QString *valuePtr);
inline void clearCrashAnnotationRef(const std::string &key) {
setCrashAnnotationRef(key, nullptr);
}
}
namespace base {
namespace assertion {
inline void log(const char *message, const char *file, int line) {
auto info = QStringLiteral("%1 %2:%3").arg(message).arg(file).arg(line);
LOG(("Assertion Failed! ") + info);
SignalHandlers::setCrashAnnotation("Assertion", info);
}
} // namespace assertion
} // namespace base

View File

@@ -18,49 +18,9 @@ to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "application.h"
#include "platform/platform_specific.h"
#include "storage/localstorage.h"
#include "core/launcher.h"
int main(int argc, char *argv[]) {
#ifndef Q_OS_MAC // Retina display support is working fine, others are not.
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
#endif // Q_OS_MAC
QCoreApplication::setApplicationName(qsl("TelegramDesktop"));
InitFromCommandLine(argc, argv);
if (cLaunchMode() == LaunchModeFixPrevious) {
return psFixPrevious();
} else if (cLaunchMode() == LaunchModeCleanup) {
return psCleanup();
}
// both are finished in Application::closeApplication
Logs::start(); // must be started before Platform is started
Platform::start(); // must be started before QApplication is created
int result = 0;
{
Application app(argc, argv);
result = app.exec();
}
DEBUG_LOG(("Telegram finished, result: %1").arg(result));
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
if (cRestartingUpdate()) {
DEBUG_LOG(("Application Info: executing updater to install update..."));
psExecUpdater();
} else
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
if (cRestarting()) {
DEBUG_LOG(("Application Info: executing Telegram, because of restart..."));
psExecTelegram();
}
SignalHandlers::finish();
Platform::finish();
Logs::finish();
return result;
const auto launcher = Core::Launcher::Create(argc, argv);
return launcher ? launcher->exec() : 1;
}

View File

@@ -610,6 +610,11 @@ bool MainWidget::setForwardDraft(PeerId peerId, ForwardWhatMessages what) {
item = App::contextItem();
} else if (what == ForwardPressedMessage) {
item = App::pressedItem();
if (const auto group = item ? item->getFullGroup() : nullptr) {
if (item->id > 0) {
return Auth().data().groupToIds(group);
}
}
} else if (what == ForwardPressedLinkMessage) {
item = App::pressedLinkItem();
}
@@ -965,9 +970,18 @@ void MainWidget::cancelUploadLayer() {
return;
}
Auth().uploader().pause(item->fullId());
Ui::show(Box<ConfirmBox>(lang(lng_selected_cancel_sure_this), lang(lng_selected_upload_stop), lang(lng_continue), base::lambda_guarded(this, [this] {
_history->deleteContextItem(false);
const auto itemId = item->fullId();
Auth().uploader().pause(itemId);
Ui::show(Box<ConfirmBox>(lang(lng_selected_cancel_sure_this), lang(lng_selected_upload_stop), lang(lng_continue), base::lambda_guarded(this, [=] {
Ui::hideLayer();
if (const auto item = App::histItemById(itemId)) {
const auto history = item->history();
const auto wasLast = (history->lastMsg == item);
item->destroy();
if (wasLast && !history->lastMsg) {
checkPeerHistory(history->peer);
}
}
Auth().uploader().unpause();
}), base::lambda_guarded(this, [] {
Auth().uploader().unpause();
@@ -1574,7 +1588,7 @@ void MainWidget::saveRecentHashtags(const QString &text) {
recent = cRecentWriteHashtags();
}
found = true;
incrementRecentHashtag(recent, text.mid(i + 1, next - i - 1));
Stickers::IncrementRecentHashtag(recent, text.mid(i + 1, next - i - 1));
}
if (found) {
cSetRecentWriteHashtags(recent);
@@ -3680,31 +3694,26 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha
// feed messages and groups, copy from App::feedMsgs
auto h = App::history(channel->id);
auto &vmsgs = d.vnew_messages.v;
QMap<uint64, int> msgsIds;
for (int i = 0, l = vmsgs.size(); i < l; ++i) {
auto &msg = vmsgs[i];
switch (msg.type()) {
case mtpc_message: {
const auto &d(msg.c_message());
if (App::checkEntitiesAndViewsUpdate(d)) { // new message, index my forwarded messages to links _overview, already in blocks
auto indices = base::flat_map<uint64, int>();
for (auto i = 0, l = vmsgs.size(); i != l; ++i) {
const auto &msg = vmsgs[i];
if (msg.type() == mtpc_message) {
const auto &data = msg.c_message();
if (App::checkEntitiesAndViewsUpdate(data)) { // new message, index my forwarded messages to links _overview, already in blocks
LOG(("Skipping message, because it is already in blocks!"));
} else {
msgsIds.insert((uint64(uint32(d.vid.v)) << 32) | uint64(i), i + 1);
continue;
}
} break;
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i + 1); break;
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i + 1); break;
}
const auto msgId = idFromMessage(msg);
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
}
for_const (auto msgIndex, msgsIds) {
if (msgIndex > 0) { // add message
auto &msg = vmsgs.at(msgIndex - 1);
if (channel->id != peerFromMessage(msg)) {
LOG(("API Error: message with invalid peer returned in channelDifference, channelId: %1, peer: %2").arg(peerToChannel(channel->id)).arg(peerFromMessage(msg)));
continue; // wtf
}
h->addNewMessage(msg, NewMessageUnread);
for (const auto [position, index] : indices) {
const auto &msg = vmsgs[index];
if (channel->id != peerFromMessage(msg)) {
LOG(("API Error: message with invalid peer returned in channelDifference, channelId: %1, peer: %2").arg(peerToChannel(channel->id)).arg(peerFromMessage(msg)));
continue; // wtf
}
h->addNewMessage(msg, NewMessageUnread);
}
feedUpdateVector(d.vother_updates, true);
@@ -4389,7 +4398,7 @@ void MainWidget::incrementSticker(DocumentData *sticker) {
// Remove that sticker from old recent, now it is in cloud recent stickers.
bool writeOldRecent = false;
auto &recent = cGetRecentStickers();
auto &recent = Stickers::GetRecentPack();
for (auto i = recent.begin(), e = recent.end(); i != e; ++i) {
if (i->first == sticker) {
writeOldRecent = true;

File diff suppressed because it is too large Load Diff

View File

@@ -224,219 +224,3 @@ private:
Local::ClearManager *_clearManager = nullptr;
};
class PreLaunchWindow : public TWidget {
public:
PreLaunchWindow(QString title = QString());
void activate();
int basicSize() const {
return _size;
}
~PreLaunchWindow();
static PreLaunchWindow *instance();
protected:
int _size;
};
class PreLaunchLabel : public QLabel {
public:
PreLaunchLabel(QWidget *parent);
void setText(const QString &text);
};
class PreLaunchInput : public QLineEdit {
public:
PreLaunchInput(QWidget *parent, bool password = false);
};
class PreLaunchLog : public QTextEdit {
public:
PreLaunchLog(QWidget *parent);
};
class PreLaunchButton : public QPushButton {
public:
PreLaunchButton(QWidget *parent, bool confirm = true);
void setText(const QString &text);
};
class PreLaunchCheckbox : public QCheckBox {
public:
PreLaunchCheckbox(QWidget *parent);
void setText(const QString &text);
};
class NotStartedWindow : public PreLaunchWindow {
public:
NotStartedWindow();
protected:
void closeEvent(QCloseEvent *e);
void resizeEvent(QResizeEvent *e);
private:
void updateControls();
PreLaunchLabel _label;
PreLaunchLog _log;
PreLaunchButton _close;
};
class LastCrashedWindow : public PreLaunchWindow {
Q_OBJECT
public:
LastCrashedWindow();
public slots:
void onViewReport();
void onSaveReport();
void onSendReport();
void onGetApp();
void onNetworkSettings();
void onNetworkSettingsSaved(QString host, quint32 port, QString username, QString password);
void onContinue();
void onCheckingFinished();
void onSendingError(QNetworkReply::NetworkError e);
void onSendingFinished();
void onSendingProgress(qint64 uploaded, qint64 total);
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
void onUpdateRetry();
void onUpdateSkip();
void onUpdateChecking();
void onUpdateLatest();
void onUpdateDownloading(qint64 ready, qint64 total);
void onUpdateReady();
void onUpdateFailed();
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
protected:
void closeEvent(QCloseEvent *e);
void resizeEvent(QResizeEvent *e);
private:
QString minidumpFileName();
void updateControls();
QString _host, _username, _password;
quint32 _port;
PreLaunchLabel _label, _pleaseSendReport, _yourReportName, _minidump;
PreLaunchLog _report;
PreLaunchButton _send, _sendSkip, _networkSettings, _continue, _showReport, _saveReport, _getApp;
PreLaunchCheckbox _includeUsername;
QString _minidumpName, _minidumpFull, _reportText;
QString _reportUsername, _reportTextNoUsername;
QByteArray getCrashReportRaw() const;
bool _reportShown, _reportSaved;
void excludeReportUsername();
enum SendingState {
SendingNoReport,
SendingUpdateCheck,
SendingNone,
SendingTooOld,
SendingTooMany,
SendingUnofficial,
SendingProgress,
SendingUploading,
SendingFail,
SendingDone,
};
SendingState _sendingState;
PreLaunchLabel _updating;
qint64 _sendingProgress, _sendingTotal;
QNetworkAccessManager _sendManager;
QNetworkReply *_checkReply, *_sendReply;
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
PreLaunchButton _updatingCheck, _updatingSkip;
enum UpdatingState {
UpdatingNone,
UpdatingCheck,
UpdatingLatest,
UpdatingDownload,
UpdatingFail,
UpdatingReady
};
UpdatingState _updatingState;
QString _newVersionDownload;
void setUpdatingState(UpdatingState state, bool force = false);
void setDownloadProgress(qint64 ready, qint64 total);
#endif // !TDESKTOP_DISABLE_AUTOUPDATE
QString getReportField(const QLatin1String &name, const QLatin1String &prefix);
void addReportFieldPart(const QLatin1String &name, const QLatin1String &prefix, QHttpMultiPart *multipart);
};
class NetworkSettingsWindow : public PreLaunchWindow {
Q_OBJECT
public:
NetworkSettingsWindow(QWidget *parent, QString host, quint32 port, QString username, QString password);
signals:
void saved(QString host, quint32 port, QString username, QString password);
public slots:
void onSave();
protected:
void closeEvent(QCloseEvent *e);
void resizeEvent(QResizeEvent *e);
private:
void updateControls();
PreLaunchLabel _hostLabel, _portLabel, _usernameLabel, _passwordLabel;
PreLaunchInput _hostInput, _portInput, _usernameInput, _passwordInput;
PreLaunchButton _save, _cancel;
QWidget *_parent;
};
class ShowCrashReportWindow : public PreLaunchWindow {
public:
ShowCrashReportWindow(const QString &text);
protected:
void resizeEvent(QResizeEvent *e);
void closeEvent(QCloseEvent *e);
private:
PreLaunchLog _log;
};

View File

@@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "media/media_audio_ffmpeg_loader.h"
#include "core/crash_reports.h"
namespace {
constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16;
@@ -423,7 +425,7 @@ AudioPlayerLoader::ReadResult FFMpegLoader::readFromReadyFrame(QByteArray &resul
).arg(ptrdiff_t(frame->extended_data[0])
).arg(ptrdiff_t(frame->data[1])
);
SignalHandlers::setCrashAnnotation(key, value);
CrashReports::SetAnnotation(key, value);
}
}
@@ -435,7 +437,7 @@ AudioPlayerLoader::ReadResult FFMpegLoader::readFromReadyFrame(QByteArray &resul
if (frame->extended_data[1] == nullptr) {
const auto key = "ffmpeg_" + std::to_string(ptrdiff_t(this));
SignalHandlers::setCrashAnnotation(key, QString());
CrashReports::ClearAnnotation(key);
}
int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1);

View File

@@ -20,10 +20,25 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "media/media_child_ffmpeg_loader.h"
#include "core/crash_reports.h"
namespace {
constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16;
constexpr int64_t AudioToChannelLayout = AV_CH_LAYOUT_STEREO;
constexpr int32 AudioToChannels = 2;
bool IsPlanarFormat(int format) {
return (format == AV_SAMPLE_FMT_U8P)
|| (format == AV_SAMPLE_FMT_S16P)
|| (format == AV_SAMPLE_FMT_S32P)
|| (format == AV_SAMPLE_FMT_FLTP)
|| (format == AV_SAMPLE_FMT_DBLP)
|| (format == AV_SAMPLE_FMT_S64P);
}
} // namespace
VideoSoundData::~VideoSoundData() {
if (context) {
avcodec_close(context);
@@ -179,11 +194,48 @@ AudioPlayerLoader::ReadResult ChildFFMpegLoader::readFromReadyFrame(QByteArray &
return ReadResult::Error;
}
}
// See the same check in media_audio_ffmpeg_loader.cpp.
if (_frame->extended_data[1] == nullptr) {
const auto params = _parentData->context;
if (IsPlanarFormat(params->sample_fmt) && params->channels > 1) {
LOG(("Audio Error: Inconsistent frame layout/channels in file, codec: (%1;%2;%3), frame: (%4;%5;%6)."
).arg(params->channel_layout
).arg(params->channels
).arg(params->sample_fmt
).arg(_frame->channel_layout
).arg(_frame->channels
).arg(_frame->format
));
return ReadResult::Error;
} else {
const auto key = "ffmpeg_" + std::to_string(ptrdiff_t(this));
const auto value = QString("codec: (%1;%2;%3), frame: (%4;%5;%6), ptrs: (%7;%8;%9)"
).arg(params->channel_layout
).arg(params->channels
).arg(params->sample_fmt
).arg(_frame->channel_layout
).arg(_frame->channels
).arg(_frame->format
).arg(ptrdiff_t(_frame->data[0])
).arg(ptrdiff_t(_frame->extended_data[0])
).arg(ptrdiff_t(_frame->data[1])
);
CrashReports::SetAnnotation(key, value);
}
}
if ((res = swr_convert(_swrContext, _dstSamplesData, dstSamples, (const uint8_t**)_frame->extended_data, _frame->nb_samples)) < 0) {
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(_file.name()).arg(_data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
return ReadResult::Error;
}
if (_frame->extended_data[1] == nullptr) {
const auto key = "ffmpeg_" + std::to_string(ptrdiff_t(this));
CrashReports::ClearAnnotation(key);
}
int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1);
result.append((const char*)_dstSamplesData[0], resultLen);
samplesAdded += resultLen / _sampleSize;

View File

@@ -1266,8 +1266,9 @@ void MediaView::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
_zoom = 0;
_caption = Text();
if (auto itemMsg = item ? item->toHistoryMessage() : nullptr) {
if (auto photoMsg = dynamic_cast<HistoryPhoto*>(itemMsg->getMedia())) {
if (const auto media = item ? item->getMedia() : nullptr) {
const auto caption = media->getCaption();
if (!caption.text.isEmpty()) {
auto asBot = (item->author()->isUser()
&& item->author()->asUser()->botInfo);
auto skipw = qMax(_dateNav.left() + _dateNav.width(), _headerNav.left() + _headerNav.width());
@@ -1275,7 +1276,7 @@ void MediaView::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
_caption = Text(maxw);
_caption.setMarkedText(
st::mediaviewCaptionStyle,
photoMsg->getCaption(),
caption,
itemTextOptions(item));
}
}
@@ -2254,21 +2255,16 @@ MediaView::Entity MediaView::entityForSharedMedia(int index) const {
return { base::none, nullptr };
}
auto value = (*_sharedMediaData)[index];
if (auto photo = base::get_if<not_null<PhotoData*>>(&value)) {
if (const auto photo = base::get_if<not_null<PhotoData*>>(&value)) {
// Last peer photo.
return { *photo, nullptr };
} else if (auto itemId = base::get_if<FullMsgId>(&value)) {
if (auto item = App::histItemById(*itemId)) {
if (auto media = item->getMedia()) {
switch (media->type()) {
case MediaTypePhoto: return {
static_cast<HistoryPhoto*>(item->getMedia())->photo(),
item
};
case MediaTypeFile:
case MediaTypeVideo:
case MediaTypeGif:
case MediaTypeSticker: return { media->getDocument(), item };
} else if (const auto itemId = base::get_if<FullMsgId>(&value)) {
if (const auto item = App::histItemById(*itemId)) {
if (const auto media = item->getMedia()) {
if (const auto photo = media->getPhoto()) {
return { photo, item };
} else if (const auto document = media->getDocument()) {
return { document, item };
}
}
return { base::none, item };

View File

@@ -75,7 +75,9 @@ struct Messenger::Private {
base::Timer quitTimer;
};
Messenger::Messenger() : QObject()
Messenger::Messenger(not_null<Core::Launcher*> launcher)
: QObject()
, _launcher(launcher)
, _private(std::make_unique<Private>())
, _langpack(std::make_unique<Lang::Instance>())
, _audio(std::make_unique<Media::Audio::Instance>())
@@ -199,9 +201,11 @@ bool Messenger::hideMediaView() {
return false;
}
void Messenger::showPhoto(not_null<const PhotoOpenClickHandler*> link, HistoryItem *item) {
return (!item && link->peer())
? showPhoto(link->photo(), link->peer())
void Messenger::showPhoto(not_null<const PhotoOpenClickHandler*> link) {
const auto item = App::histItemById(link->context());
const auto peer = link->peer();
return (!item && peer)
? showPhoto(link->photo(), peer)
: showPhoto(link->photo(), item);
}
@@ -212,7 +216,9 @@ void Messenger::showPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
_mediaView->setFocus();
}
void Messenger::showPhoto(not_null<PhotoData*> photo, PeerData *peer) {
void Messenger::showPhoto(
not_null<PhotoData*> photo,
not_null<PeerData*> peer) {
if (_mediaView->isHidden()) Ui::hideLayer(anim::type::instant);
_mediaView->showPhoto(photo, peer);
_mediaView->activateWindow();

View File

@@ -24,6 +24,17 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mtproto/auth_key.h"
#include "base/timer.h"
class AuthSession;
class AuthSessionData;
class MainWidget;
class FileUploader;
class Translator;
class MediaView;
namespace Core {
class Launcher;
} // namespace Core
namespace App {
void quit();
} // namespace App
@@ -36,13 +47,6 @@ using AuthKeyPtr = std::shared_ptr<AuthKey>;
using AuthKeysList = std::vector<AuthKeyPtr>;
} // namespace MTP
class AuthSession;
class AuthSessionData;
class MainWidget;
class FileUploader;
class Translator;
class MediaView;
namespace Media {
namespace Audio {
class Instance;
@@ -59,13 +63,17 @@ class Messenger final : public QObject, public RPCSender, private base::Subscrib
Q_OBJECT
public:
Messenger();
Messenger(not_null<Core::Launcher*> launcher);
Messenger(const Messenger &other) = delete;
Messenger &operator=(const Messenger &other) = delete;
~Messenger();
not_null<Core::Launcher*> launcher() const {
return _launcher;
}
// Windows interface.
MainWindow *getActiveWindow() const;
bool closeActiveWindow();
@@ -78,9 +86,9 @@ public:
// MediaView interface.
void checkMediaViewActivation();
bool hideMediaView();
void showPhoto(not_null<const PhotoOpenClickHandler*> link, HistoryItem *item = nullptr);
void showPhoto(not_null<const PhotoOpenClickHandler*> link);
void showPhoto(not_null<PhotoData*> photo, HistoryItem *item);
void showPhoto(not_null<PhotoData*> photo, PeerData *item);
void showPhoto(not_null<PhotoData*> photo, not_null<PeerData*> item);
void showDocument(not_null<DocumentData*> document, HistoryItem *item);
PeerData *ui_getPeerForMouseAction();
@@ -220,6 +228,8 @@ private:
void loggedOut();
not_null<Core::Launcher*> _launcher;
QMap<FullMsgId, PeerId> photoUpdates;
QMap<MTP::DcId, TimeMs> killDownloadSessionTimes;

View File

@@ -45,6 +45,15 @@ constexpr auto kMaxModExpSize = 256;
// Don't try to handle messages larger than this size.
constexpr auto kMaxMessageLength = 16 * 1024 * 1024;
QString LogIdsVector(const QVector<MTPlong> &ids) {
if (!ids.size()) return "[]";
auto idsStr = QString("[%1").arg(ids.cbegin()->v);
for (const auto &id : ids) {
idsStr += QString(", %2").arg(id.v);
}
return idsStr + "]";
}
bool IsGoodModExpFirst(const openssl::BigNum &modexp, const openssl::BigNum &prime) {
auto diff = prime - modexp;
if (modexp.failed() || prime.failed() || diff.failed()) {
@@ -1432,7 +1441,7 @@ void ConnectionPrivate::handleReceived() {
// send acks
uint32 toAckSize = ackRequestData.size();
if (toAckSize) {
DEBUG_LOG(("MTP Info: will send %1 acks, ids: %2").arg(toAckSize).arg(Logs::vector(ackRequestData)));
DEBUG_LOG(("MTP Info: will send %1 acks, ids: %2").arg(toAckSize).arg(LogIdsVector(ackRequestData)));
emit sendAnythingAsync(MTPAckSendWaiting);
}
@@ -1546,7 +1555,7 @@ ConnectionPrivate::HandleResult ConnectionPrivate::handleOneReceived(const mtpPr
auto &ids = msg.c_msgs_ack().vmsg_ids.v;
uint32 idsCount = ids.size();
DEBUG_LOG(("Message Info: acks received, ids: %1").arg(Logs::vector(ids)));
DEBUG_LOG(("Message Info: acks received, ids: %1").arg(LogIdsVector(ids)));
if (!idsCount) return (badTime ? HandleResult::Ignored : HandleResult::Success);
if (badTime) {
@@ -1676,7 +1685,7 @@ ConnectionPrivate::HandleResult ConnectionPrivate::handleOneReceived(const mtpPr
msg.read(from, end);
auto &ids = msg.c_msgs_state_req().vmsg_ids.v;
auto idsCount = ids.size();
DEBUG_LOG(("Message Info: msgs_state_req received, ids: %1").arg(Logs::vector(ids)));
DEBUG_LOG(("Message Info: msgs_state_req received, ids: %1").arg(LogIdsVector(ids)));
if (!idsCount) return HandleResult::Success;
QByteArray info(idsCount, Qt::Uninitialized);
@@ -1787,7 +1796,7 @@ ConnectionPrivate::HandleResult ConnectionPrivate::handleOneReceived(const mtpPr
QVector<MTPlong> toAck;
DEBUG_LOG(("Message Info: msgs all info received, msgId %1, reqMsgIds: %2, states %3").arg(msgId).arg(Logs::vector(ids)).arg(Logs::mb(states.data(), states.length()).str()));
DEBUG_LOG(("Message Info: msgs all info received, msgId %1, reqMsgIds: %2, states %3").arg(msgId).arg(LogIdsVector(ids)).arg(Logs::mb(states.data(), states.length()).str()));
handleMsgsStates(ids, states, toAck);
requestsAcked(toAck);
@@ -1856,7 +1865,7 @@ ConnectionPrivate::HandleResult ConnectionPrivate::handleOneReceived(const mtpPr
auto &ids = msg.c_msg_resend_req().vmsg_ids.v;
auto idsCount = ids.size();
DEBUG_LOG(("Message Info: resend of msgs requested, ids: %1").arg(Logs::vector(ids)));
DEBUG_LOG(("Message Info: resend of msgs requested, ids: %1").arg(LogIdsVector(ids)));
if (!idsCount) return (badTime ? HandleResult::Ignored : HandleResult::Success);
QVector<quint64> toResend(ids.size());
@@ -2087,7 +2096,7 @@ bool ConnectionPrivate::requestsFixTimeSalt(const QVector<MTPlong> &ids, int32 s
void ConnectionPrivate::requestsAcked(const QVector<MTPlong> &ids, bool byResponse) {
uint32 idsCount = ids.size();
DEBUG_LOG(("Message Info: requests acked, ids %1").arg(Logs::vector(ids)));
DEBUG_LOG(("Message Info: requests acked, ids %1").arg(LogIdsVector(ids)));
RPCCallbackClears clearedAcked;
QVector<MTPlong> toAckMore;

View File

@@ -246,7 +246,7 @@ mtpRequestId Session::send(const TRequest &request, RPCResponseHandler callbacks
sendPrepared(reqSerialized, msCanWait);
} catch (Exception &e) {
requestId = 0;
rpcErrorOccured(requestId, callbacks.onFail, rpcClientError("NO_REQUEST_ID", QString("send() failed to queue request, exception: %1").arg(e.what())));
requestPrepareFailed(callbacks.onFail, e);
}
if (requestId) registerRequest(requestId, toMainDC ? -getDcWithShift() : getDcWithShift());
return requestId;

View File

@@ -23,9 +23,22 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mtproto/connection.h"
#include "mtproto/dcenter.h"
#include "mtproto/auth_key.h"
#include "core/crash_reports.h"
namespace MTP {
namespace internal {
namespace {
QString LogIds(const QVector<uint64> &ids) {
if (!ids.size()) return "[]";
auto idsStr = QString("[%1").arg(*ids.cbegin());
for (const auto id : ids) {
idsStr += QString(", %2").arg(id);
}
return idsStr + "]";
}
} // namespace
void SessionData::setKey(const AuthKeyPtr &key) {
if (_authKey != key) {
@@ -140,6 +153,23 @@ bool Session::rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &o
return _instance->rpcErrorOccured(requestId, onFail, err);
}
void Session::requestPrepareFailed(
const RPCFailHandlerPtr &onFail,
Exception &e) {
CrashReports::SetAnnotation("RequestException", QString::fromLatin1(e.what()));
Unexpected("Exception in Session::send()");
const auto requestId = 0;
const auto error = rpcClientError(
"NO_REQUEST_ID",
QString(
"send() failed to queue request, exception: %1"
).arg(
e.what()
));
rpcErrorOccured(requestId, onFail, error);
}
void Session::restart() {
if (_killed) {
DEBUG_LOG(("Session Error: can't restart a killed session"));
@@ -271,7 +301,7 @@ void Session::checkRequestsByTimer() {
}
if (stateRequestIds.size()) {
DEBUG_LOG(("MTP Info: requesting state of msgs: %1").arg(Logs::vector(stateRequestIds)));
DEBUG_LOG(("MTP Info: requesting state of msgs: %1").arg(LogIds(stateRequestIds)));
{
QWriteLocker locker(data.stateRequestMutex());
for (uint32 i = 0, l = stateRequestIds.size(); i < l; ++i) {

View File

@@ -356,6 +356,7 @@ private:
mtpRequestId storeRequest(mtpRequest &request, const RPCResponseHandler &parser);
mtpRequest getRequest(mtpRequestId requestId);
bool rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err);
void requestPrepareFailed(const RPCFailHandlerPtr &onFail, Exception &e);
not_null<Instance*> _instance;
std::unique_ptr<Connection> _connection;

View File

@@ -81,8 +81,11 @@ enum class MTPDmessage_ClientFlag : uint32 {
// message has an admin badge in supergroup
f_has_admin_badge = (1U << 20),
// message is not displayed because it is part of a group
f_hidden_by_group = (1U << 19),
// update this when adding new client side flags
MIN_FIELD = (1U << 20),
MIN_FIELD = (1U << 19),
};
DEFINE_MTP_CLIENT_FLAGS(MTPDmessage)

View File

@@ -283,7 +283,7 @@ Photo::Photo(
not_null<PhotoData*> photo)
: ItemBase(parent)
, _data(photo)
, _link(MakeShared<PhotoOpenClickHandler>(photo)) {
, _link(MakeShared<PhotoOpenClickHandler>(photo, parent->fullId())) {
}
void Photo::initDimensions() {
@@ -352,7 +352,7 @@ HistoryTextState Photo::getState(
QPoint point,
HistoryStateRequest request) const {
if (hasPoint(point)) {
return _link;
return { parent(), _link };
}
return {};
}
@@ -508,7 +508,12 @@ HistoryTextState Video::getState(
bool loaded = _data->loaded();
if (hasPoint(point)) {
return loaded ? _openl : (_data->loading() ? _cancell : _savel);
const auto link = loaded
? _openl
: _data->loading()
? _cancell
: _savel;
return { parent(), link };
}
return {};
}
@@ -687,13 +692,14 @@ HistoryTextState Voice::getState(
_st.songThumbSize,
_width);
if (inner.contains(point)) {
return loaded
const auto link = loaded
? _openl
: ((_data->loading() || _data->status == FileUploading)
? _cancell
: _openl);
: (_data->loading() || _data->status == FileUploading)
? _cancell
: _openl;
return { parent(), link };
}
auto result = HistoryTextState();
auto result = HistoryTextState(parent());
const auto statusmaxwidth = _width - nameleft - nameright;
const auto statusrect = rtlrect(
nameleft,
@@ -718,7 +724,7 @@ HistoryTextState Voice::getState(
st::normalFont->height,
_width);
if (namerect.contains(point) && !result.link && !_data->loading()) {
return _namel;
return { parent(), _namel };
}
return result;
}
@@ -1014,11 +1020,12 @@ HistoryTextState Document::getState(
_st.songThumbSize,
_width);
if (inner.contains(point)) {
return loaded
const auto link = loaded
? _openl
: ((_data->loading() || _data->status == FileUploading)
? _cancell
: _openl);
: (_data->loading() || _data->status == FileUploading)
? _cancell
: _openl;
return { parent(), link };
}
const auto namerect = rtlrect(
nameleft,
@@ -1027,7 +1034,7 @@ HistoryTextState Document::getState(
st::semiboldFont->height,
_width);
if (namerect.contains(point) && !_data->loading()) {
return _namel;
return { parent(), _namel };
}
} else {
const auto nameleft = _st.fileThumbSize + _st.filePadding.right();
@@ -1047,11 +1054,12 @@ HistoryTextState Document::getState(
_width);
if (rthumb.contains(point)) {
return loaded
const auto link = loaded
? _openl
: ((_data->loading() || _data->status == FileUploading)
? _cancell
: _savel);
: (_data->loading() || _data->status == FileUploading)
? _cancell
: _savel;
return { parent(), link };
}
if (_data->status != FileUploadFailed) {
@@ -1062,7 +1070,7 @@ HistoryTextState Document::getState(
st::normalFont->height,
_width);
if (daterect.contains(point)) {
return _msgl;
return { parent(), _msgl };
}
}
if (!_data->loading() && _data->isValid()) {
@@ -1073,7 +1081,7 @@ HistoryTextState Document::getState(
_height - st::linksBorder,
_width);
if (loaded && leftofnamerect.contains(point)) {
return _namel;
return { parent(), _namel };
}
const auto namerect = rtlrect(
nameleft,
@@ -1082,7 +1090,7 @@ HistoryTextState Document::getState(
st::semiboldFont->height,
_width);
if (namerect.contains(point)) {
return _namel;
return { parent(), _namel };
}
}
}
@@ -1201,7 +1209,9 @@ Link::Link(
}
}
_page = (media && media->type() == MediaTypeWebPage) ? static_cast<HistoryWebPage*>(media)->webpage().get() : nullptr;
_page = (media && media->type() == MediaTypeWebPage)
? static_cast<HistoryWebPage*>(media)->webpage().get()
: nullptr;
if (_page) {
mainUrl = _page->url;
if (_page->document) {
@@ -1210,7 +1220,9 @@ Link::Link(
if (_page->type == WebPageProfile || _page->type == WebPageVideo) {
_photol = MakeShared<UrlClickHandler>(_page->url);
} else if (_page->type == WebPagePhoto || _page->siteName == qstr("Twitter") || _page->siteName == qstr("Facebook")) {
_photol = MakeShared<PhotoOpenClickHandler>(_page->photo);
_photol = MakeShared<PhotoOpenClickHandler>(
_page->photo,
parent->fullId());
} else {
_photol = MakeShared<UrlClickHandler>(_page->url);
}
@@ -1413,7 +1425,7 @@ HistoryTextState Link::getState(
HistoryStateRequest request) const {
int32 left = st::linksPhotoSize + st::linksPhotoPadding, top = st::linksMargin.top() + st::linksBorder, w = _width - left;
if (rtlrect(0, top, st::linksPhotoSize, st::linksPhotoSize, _width).contains(point)) {
return _photol;
return { parent(), _photol };
}
if (!_title.isEmpty() && _text.isEmpty() && _links.size() == 1) {
@@ -1421,7 +1433,7 @@ HistoryTextState Link::getState(
}
if (!_title.isEmpty()) {
if (rtlrect(left, top, qMin(w, _titlew), st::semiboldFont->height, _width).contains(point)) {
return _photol;
return { parent(), _photol };
}
top += st::webPageTitleFont->height;
}
@@ -1430,7 +1442,7 @@ HistoryTextState Link::getState(
}
for (int32 i = 0, l = _links.size(); i < l; ++i) {
if (rtlrect(left, top, qMin(w, _links.at(i).width), st::normalFont->height, _width).contains(point)) {
return ClickHandlerPtr(_links[i].lnk);
return { parent(), ClickHandlerPtr(_links[i].lnk) };
}
top += st::normalFont->height;
}

View File

@@ -0,0 +1,117 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "platform/linux/launcher_linux.h"
#include "core/crash_reports.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <cstdlib>
#include <unistd.h>
#include <dirent.h>
#include <pwd.h>
namespace Platform {
namespace {
class Arguments {
public:
void push(QByteArray argument) {
argument.append(char(0));
_argumentValues.push_back(argument);
_arguments.push_back(_argumentValues.back().data());
}
char **result() {
_arguments.push_back(nullptr);
return _arguments.data();
}
private:
std::vector<QByteArray> _argumentValues;
std::vector<char*> _arguments;
};
} // namespace
bool Launcher::launchUpdater(UpdaterLaunch action) {
if (cExeName().isEmpty()) {
return false;
}
const auto binaryName = (action == UpdaterLaunch::JustRelaunch)
? cExeName()
: QStringLiteral("Updater");
auto argumentsList = Arguments();
argumentsList.push(QFile::encodeName(cExeDir() + binaryName));
if (cLaunchMode() == LaunchModeAutoStart) {
argumentsList.push("-autostart");
}
if (cDebug()) {
argumentsList.push("-debug");
}
if (cStartInTray()) {
argumentsList.push("-startintray");
}
if (cTestMode()) {
argumentsList.push("-testmode");
}
if (cDataFile() != qsl("data")) {
argumentsList.push("-key");
argumentsList.push(QFile::encodeName(cDataFile()));
}
if (action == UpdaterLaunch::JustRelaunch) {
argumentsList.push("-noupdate");
argumentsList.push("-tosettings");
if (customWorkingDir()) {
argumentsList.push("-workdir");
argumentsList.push(QFile::encodeName(cWorkingDir()));
}
} else {
argumentsList.push("-workpath");
argumentsList.push(QFile::encodeName(cWorkingDir()));
argumentsList.push("-exename");
argumentsList.push(QFile::encodeName(cExeName()));
argumentsList.push("-exepath");
argumentsList.push(QFile::encodeName(cExeDir()));
if (customWorkingDir()) {
argumentsList.push("-workdir_custom");
}
}
Logs::closeMain();
CrashReports::Finish();
const auto args = argumentsList.result();
pid_t pid = fork();
switch (pid) {
case -1: return false;
case 0: execv(args[0], args); return false;
}
return true;
}
} // namespace

View File

@@ -0,0 +1,36 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/launcher.h"
namespace Platform {
class Launcher : public Core::Launcher {
public:
using Core::Launcher::Launcher;
private:
bool launchUpdater(UpdaterLaunch action) override;
};
} // namespace Platform

View File

@@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "platform/linux/file_utilities_linux.h"
#include "platform/platform_notifications_manager.h"
#include "storage/localstorage.h"
#include "core/crash_reports.h"
#include <sys/stat.h>
#include <sys/types.h>
@@ -525,98 +526,6 @@ void psNewVersion() {
psRegisterCustomScheme();
}
bool _execUpdater(bool update = true, const QString &crashreport = QString()) {
if (cExeName().isEmpty()) {
return false;
}
static const int MaxLen = 65536, MaxArgsCount = 128;
char path[MaxLen] = {0};
QByteArray data(QFile::encodeName(cExeDir() + (update ? "Updater" : cExeName())));
memcpy(path, data.constData(), data.size());
char *args[MaxArgsCount] = { 0 };
char p_noupdate[] = "-noupdate";
char p_autostart[] = "-autostart";
char p_debug[] = "-debug";
char p_tosettings[] = "-tosettings";
char p_key[] = "-key";
char p_datafile[MaxLen] = { 0 };
char p_path[] = "-workpath";
char p_pathbuf[MaxLen] = { 0 };
char p_startintray[] = "-startintray";
char p_testmode[] = "-testmode";
char p_crashreport[] = "-crashreport";
char p_crashreportbuf[MaxLen] = { 0 };
char p_exe[] = "-exename";
char p_exebuf[MaxLen] = { 0 };
char p_exepath[] = "-exepath";
char p_exepathbuf[MaxLen] = { 0 };
int argIndex = 0;
args[argIndex++] = path;
if (!update) {
args[argIndex++] = p_noupdate;
args[argIndex++] = p_tosettings;
}
if (cLaunchMode() == LaunchModeAutoStart) args[argIndex++] = p_autostart;
if (cDebug()) args[argIndex++] = p_debug;
if (cStartInTray()) args[argIndex++] = p_startintray;
if (cTestMode()) args[argIndex++] = p_testmode;
if (cDataFile() != qsl("data")) {
QByteArray dataf = QFile::encodeName(cDataFile());
if (dataf.size() < MaxLen) {
memcpy(p_datafile, dataf.constData(), dataf.size());
args[argIndex++] = p_key;
args[argIndex++] = p_datafile;
}
}
QByteArray pathf = QFile::encodeName(cWorkingDir());
if (pathf.size() < MaxLen) {
memcpy(p_pathbuf, pathf.constData(), pathf.size());
args[argIndex++] = p_path;
args[argIndex++] = p_pathbuf;
}
if (!crashreport.isEmpty()) {
QByteArray crashreportf = QFile::encodeName(crashreport);
if (crashreportf.size() < MaxLen) {
memcpy(p_crashreportbuf, crashreportf.constData(), crashreportf.size());
args[argIndex++] = p_crashreport;
args[argIndex++] = p_crashreportbuf;
}
}
QByteArray exef = QFile::encodeName(cExeName());
if (exef.size() > 0 && exef.size() < MaxLen) {
memcpy(p_exebuf, exef.constData(), exef.size());
args[argIndex++] = p_exe;
args[argIndex++] = p_exebuf;
}
QByteArray exepathf = QFile::encodeName(cExeDir());
if (exepathf.size() > 0 && exepathf.size() < MaxLen) {
memcpy(p_exepathbuf, exepathf.constData(), exepathf.size());
args[argIndex++] = p_exepath;
args[argIndex++] = p_exepathbuf;
}
Logs::closeMain();
SignalHandlers::finish();
pid_t pid = fork();
switch (pid) {
case -1: return false;
case 0: execv(path, args); return false;
}
return true;
}
void psExecUpdater() {
if (!_execUpdater()) {
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
}
}
void psExecTelegram(const QString &crashreport) {
_execUpdater(false, crashreport);
}
bool psShowOpenWithMenu(int x, int y, const QString &file) {
return false;
}

View File

@@ -77,9 +77,6 @@ void psBringToBack(QWidget *w);
int psCleanup();
int psFixPrevious();
void psExecUpdater();
void psExecTelegram(const QString &arg = QString());
QAbstractNativeEventFilter *psNativeEventFilter();
void psNewVersion();

View File

@@ -0,0 +1,38 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/launcher.h"
namespace Platform {
class Launcher : public Core::Launcher {
public:
using Core::Launcher::Launcher;
private:
void initHook() override;
bool launchUpdater(UpdaterLaunch action) override;
};
} // namespace Platform

View File

@@ -0,0 +1,98 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "platform/mac/launcher_mac.h"
#include "core/crash_reports.h"
#include "platform/mac/mac_utilities.h"
#include <Cocoa/Cocoa.h>
#include <CoreFoundation/CFURL.h>
namespace Platform {
void Launcher::initHook() {
#ifndef OS_MAC_OLD
// macOS Retina display support is working fine, others are not.
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling, false);
#endif // OS_MAC_OLD
}
bool Launcher::launchUpdater(UpdaterLaunch action) {
if (cExeName().isEmpty()) {
return false;
}
@autoreleasepool {
#ifdef OS_MAC_STORE
// In AppStore version we don't have Updater.
// We just relaunch our app.
if (action == UpdaterLaunch::JustRelaunch) {
NSDictionary *conf = [NSDictionary dictionaryWithObject:[NSArray array] forKey:NSWorkspaceLaunchConfigurationArguments];
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL fileURLWithPath:Q2NSString(cExeDir() + cExeName())] options:NSWorkspaceLaunchAsync | NSWorkspaceLaunchNewInstance configuration:conf error:0];
return true;
}
#endif // OS_MAC_STORE
NSString *path = @"", *args = @"";
@try {
path = [[NSBundle mainBundle] bundlePath];
if (!path) {
LOG(("Could not get bundle path!!"));
return false;
}
path = [path stringByAppendingString:@"/Contents/Frameworks/Updater"];
NSMutableArray *args = [[NSMutableArray alloc] initWithObjects:@"-workpath", Q2NSString(cWorkingDir()), @"-procid", nil];
[args addObject:[NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]]];
if (cRestartingToSettings()) [args addObject:@"-tosettings"];
if (action == UpdaterLaunch::JustRelaunch) [args addObject:@"-noupdate"];
if (cLaunchMode() == LaunchModeAutoStart) [args addObject:@"-autostart"];
if (cDebug()) [args addObject:@"-debug"];
if (cStartInTray()) [args addObject:@"-startintray"];
if (cTestMode()) [args addObject:@"-testmode"];
if (cDataFile() != qsl("data")) {
[args addObject:@"-key"];
[args addObject:Q2NSString(cDataFile())];
}
if (customWorkingDir()) {
[args addObject:@"-workdir_custom"];
}
DEBUG_LOG(("Application Info: executing %1 %2").arg(NS2QString(path)).arg(NS2QString([args componentsJoinedByString:@" "])));
Logs::closeMain();
CrashReports::Finish();
if (![NSTask launchedTaskWithLaunchPath:path arguments:args]) {
DEBUG_LOG(("Task not launched while executing %1 %2").arg(NS2QString(path)).arg(NS2QString([args componentsJoinedByString:@" "])));
return false;
}
}
@catch (NSException *exception) {
LOG(("Exception caught while executing %1 %2").arg(NS2QString(path)).arg(NS2QString(args)));
return false;
}
@finally {
}
}
return true;
}
} // namespace Platform

View File

@@ -38,6 +38,20 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include <IOKit/hidsystem/ev_keymap.h>
#include <SPMediaKeyTap.h>
@interface MainWindowObserver : NSObject {
}
- (id) init:(MainWindow::Private*)window;
- (void) activeSpaceDidChange:(NSNotification *)aNotification;
- (void) darkModeChanged:(NSNotification *)aNotification;
- (void) screenIsLocked:(NSNotification *)aNotification;
- (void) screenIsUnlocked:(NSNotification *)aNotification;
- (void) windowWillEnterFullScreen:(NSNotification *)aNotification;
- (void) windowWillExitFullScreen:(NSNotification *)aNotification;
@end // @interface MainWindowObserver
namespace Platform {
namespace {
// When we close a window that is fullscreen we first leave the fullscreen
@@ -56,23 +70,34 @@ id FindClassInSubviews(NSView *parent, NSString *className) {
return nil;
}
#ifndef OS_MAC_OLD
class LayerCreationChecker : public QObject {
public:
LayerCreationChecker(NSView * __weak view, base::lambda<void()> callback)
: _weakView(view)
, _callback(std::move(callback)) {
QCoreApplication::instance()->installEventFilter(this);
}
protected:
bool eventFilter(QObject *object, QEvent *event) override {
if (!_weakView || [_weakView layer] != nullptr) {
_callback();
}
return QObject::eventFilter(object, event);
}
private:
NSView * __weak _weakView = nil;
base::lambda<void()> _callback;
};
#endif // OS_MAC_OLD
} // namespace
@interface MainWindowObserver : NSObject {
}
- (id) init:(MainWindow::Private*)window;
- (void) activeSpaceDidChange:(NSNotification *)aNotification;
- (void) darkModeChanged:(NSNotification *)aNotification;
- (void) screenIsLocked:(NSNotification *)aNotification;
- (void) screenIsUnlocked:(NSNotification *)aNotification;
- (void) windowWillEnterFullScreen:(NSNotification *)aNotification;
- (void) windowWillExitFullScreen:(NSNotification *)aNotification;
@end // @interface MainWindowObserver
namespace Platform {
class MainWindow::Private {
public:
Private(MainWindow *window);
@@ -108,6 +133,7 @@ private:
NSView * __weak _nativeView = nil;
id __weak _nativeTitleWrapWeak = nil;
id __weak _nativeTitleWeak = nil;
std::unique_ptr<LayerCreationChecker> _layerCreationChecker;
#endif // !OS_MAC_OLD
bool _useNativeTitle = false;
bool _inFullScreen = false;
@@ -220,6 +246,22 @@ void MainWindow::Private::initCustomTitle() {
auto full = [_nativeView frame];
_public->_customTitleHeight = qMax(qRound(full.size.height - inner.size.height), 0);
// Qt still has some bug with layer-backed widgets containing QOpenGLWidgets.
// See https://github.com/telegramdesktop/tdesktop/issues/4150
// Tried to workaround it by catching the first moment we have CALayer created
// and explicitly setting contentsScale to window->backingScaleFactor there.
_layerCreationChecker = std::make_unique<LayerCreationChecker>(_nativeView, [=] {
if (_nativeView && _nativeWindow) {
if (CALayer *layer = [_nativeView layer]) {
LOG(("Window Info: Setting layer scale factor to: %1").arg([_nativeWindow backingScaleFactor]));
[layer setContentsScale: [_nativeWindow backingScaleFactor]];
_layerCreationChecker = nullptr;
}
} else {
_layerCreationChecker = nullptr;
}
});
// Disabled for now.
//_useNativeTitle = true;
//setWindowTitle(qsl("Telegram"));

View File

@@ -78,9 +78,6 @@ void psBringToBack(QWidget *w);
int psCleanup();
int psFixPrevious();
void psExecUpdater();
void psExecTelegram(const QString &crashreport = QString());
bool psShowOpenWithMenu(int x, int y, const QString &file);
QAbstractNativeEventFilter *psNativeEventFilter();

View File

@@ -17,19 +17,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "platform/mac/specific_mac.h"
#include <cstdlib>
#include "lang/lang_keys.h"
#include "application.h"
#include "mainwidget.h"
#include "history/history_widget.h"
#include "core/crash_reports.h"
#include "storage/localstorage.h"
#include "passcodewidget.h"
#include "mainwindow.h"
#include "history/history_location_manager.h"
#include "platform/mac/mac_utilities.h"
#include <cstdlib>
#include <execinfo.h>
#include <Cocoa/Cocoa.h>
@@ -91,8 +90,10 @@ QAbstractNativeEventFilter *psNativeEventFilter() {
}
void psWriteDump() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
double v = objc_appkitVersion();
SignalHandlers::dump() << "OS-Version: " << v;
CrashReports::dump() << "OS-Version: " << v;
#endif // TDESKTOP_DISABLE_CRASH_REPORTS
}
QString demanglestr(const QString &mangled) {
@@ -413,16 +414,6 @@ void psNewVersion() {
objc_registerCustomScheme();
}
void psExecUpdater() {
if (!objc_execUpdater()) {
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
}
}
void psExecTelegram(const QString &crashreport) {
objc_execTelegram(crashreport);
}
void psAutoStart(bool start, bool silent) {
}

View File

@@ -32,8 +32,6 @@ bool objc_idleTime(TimeMs &idleTime);
void objc_start();
void objc_ignoreApplicationActivationRightNow();
void objc_finish();
bool objc_execUpdater();
void objc_execTelegram(const QString &crashreport);
void objc_registerCustomScheme();

View File

@@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_window.h"
#include "lang/lang_keys.h"
#include "base/timer.h"
#include "core/crash_reports.h"
#include <Cocoa/Cocoa.h>
#include <CoreFoundation/CFURL.h>
@@ -410,74 +411,6 @@ void objc_registerCustomScheme() {
#endif // !TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME
}
BOOL _execUpdater(BOOL update = YES, const QString &crashreport = QString()) {
@autoreleasepool {
NSString *path = @"", *args = @"";
@try {
path = [[NSBundle mainBundle] bundlePath];
if (!path) {
LOG(("Could not get bundle path!!"));
return NO;
}
path = [path stringByAppendingString:@"/Contents/Frameworks/Updater"];
NSMutableArray *args = [[NSMutableArray alloc] initWithObjects:@"-workpath", Q2NSString(cWorkingDir()), @"-procid", nil];
[args addObject:[NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]]];
if (cRestartingToSettings()) [args addObject:@"-tosettings"];
if (!update) [args addObject:@"-noupdate"];
if (cLaunchMode() == LaunchModeAutoStart) [args addObject:@"-autostart"];
if (cDebug()) [args addObject:@"-debug"];
if (cStartInTray()) [args addObject:@"-startintray"];
if (cTestMode()) [args addObject:@"-testmode"];
if (cDataFile() != qsl("data")) {
[args addObject:@"-key"];
[args addObject:Q2NSString(cDataFile())];
}
if (!crashreport.isEmpty()) {
[args addObject:@"-crashreport"];
[args addObject:Q2NSString(crashreport)];
}
DEBUG_LOG(("Application Info: executing %1 %2").arg(NS2QString(path)).arg(NS2QString([args componentsJoinedByString:@" "])));
Logs::closeMain();
SignalHandlers::finish();
if (![NSTask launchedTaskWithLaunchPath:path arguments:args]) {
DEBUG_LOG(("Task not launched while executing %1 %2").arg(NS2QString(path)).arg(NS2QString([args componentsJoinedByString:@" "])));
return NO;
}
}
@catch (NSException *exception) {
LOG(("Exception caught while executing %1 %2").arg(NS2QString(path)).arg(NS2QString(args)));
return NO;
}
@finally {
}
}
return YES;
}
bool objc_execUpdater() {
return !!_execUpdater();
}
void objc_execTelegram(const QString &crashreport) {
if (cExeName().isEmpty()) {
return;
}
#ifndef OS_MAC_STORE
_execUpdater(NO, crashreport);
#else // OS_MAC_STORE
@autoreleasepool {
NSDictionary *conf = [NSDictionary dictionaryWithObject:[NSArray array] forKey:NSWorkspaceLaunchConfigurationArguments];
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL fileURLWithPath:Q2NSString(cExeDir() + cExeName())] options:NSWorkspaceLaunchAsync | NSWorkspaceLaunchNewInstance configuration:conf error:0];
}
#endif // OS_MAC_STORE
}
void objc_activateProgram(WId winId) {
[NSApp activateIgnoringOtherApps:YES];
if (winId) {

View File

@@ -0,0 +1,43 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
namespace Platform {
//class Launcher : public Core::Launcher {
//public:
// using Core::Launcher::Launcher;
//
// ...
//
//};
} // namespace Platform
// Platform dependent implementations.
#ifdef Q_OS_MAC
#include "platform/mac/launcher_mac.h"
#elif defined Q_OS_LINUX // Q_OS_MAC
#include "platform/linux/launcher_linux.h"
#elif defined Q_OS_WINRT || defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX
#include "platform/win/launcher_win.h"
#endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT || Q_OS_WIN

View File

@@ -0,0 +1,145 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "platform/win/launcher_win.h"
#include "core/crash_reports.h"
#include "platform/platform_specific.h"
#include <windows.h>
#include <shellapi.h>
namespace Platform {
base::optional<QStringList> Launcher::readArgumentsHook(
int argc,
char *argv[]) const {
auto count = 0;
if (const auto list = CommandLineToArgvW(GetCommandLine(), &count)) {
const auto guard = gsl::finally([&] { LocalFree(list); });
if (count > 0) {
auto result = QStringList();
result.reserve(count);
for (auto i = 0; i != count; ++i) {
result.push_back(QString::fromWCharArray(list[i]));
}
return result;
}
}
return base::none;
}
bool Launcher::launchUpdater(UpdaterLaunch action) {
if (cExeName().isEmpty()) {
return false;
}
const auto operation = (action == UpdaterLaunch::JustRelaunch)
? QString()
: (cWriteProtected()
? qsl("runas")
: QString());
const auto binaryPath = (action == UpdaterLaunch::JustRelaunch)
? (cExeDir() + cExeName())
: (cWriteProtected()
? (cWorkingDir() + qsl("tupdates/temp/Updater.exe"))
: (cExeDir() + qsl("Updater.exe")));
auto argumentsList = QStringList();
const auto pushArgument = [&](const QString &argument) {
argumentsList.push_back(argument.trimmed());
};
if (cLaunchMode() == LaunchModeAutoStart) {
pushArgument(qsl("-autostart"));
}
if (cDebug()) {
pushArgument(qsl("-debug"));
}
if (cStartInTray()) {
pushArgument(qsl("-startintray"));
}
if (cTestMode()) {
pushArgument(qsl("-testmode"));
}
if (customWorkingDir()) {
pushArgument(qsl("-workdir"));
pushArgument('"' + cWorkingDir() + '"');
}
if (cDataFile() != qsl("data")) {
pushArgument(qsl("-key"));
pushArgument('"' + cDataFile() + '"');
}
if (action == UpdaterLaunch::JustRelaunch) {
pushArgument(qsl("-noupdate"));
if (cRestartingToSettings()) {
pushArgument(qsl("-tosettings"));
}
} else {
pushArgument(qsl("-update"));
pushArgument(qsl("-exename"));
pushArgument('"' + cExeName() + '"');
if (cWriteProtected()) {
pushArgument(qsl("-writeprotected"));
pushArgument('"' + cExeDir() + '"');
}
}
return launch(operation, binaryPath, argumentsList);
}
bool Launcher::launch(
const QString &operation,
const QString &binaryPath,
const QStringList &argumentsList) {
const auto convertPath = [](const QString &path) {
return QDir::toNativeSeparators(path).toStdWString();
};
const auto nativeBinaryPath = convertPath(binaryPath);
const auto nativeWorkingDir = convertPath(cWorkingDir());
const auto arguments = argumentsList.join(' ');
DEBUG_LOG(("Application Info: executing %1 %2"
).arg(binaryPath
).arg(arguments
));
Logs::closeMain();
CrashReports::Finish();
const auto hwnd = HWND(0);
const auto result = ShellExecute(
hwnd,
operation.isEmpty() ? nullptr : operation.toStdWString().c_str(),
nativeBinaryPath.c_str(),
arguments.toStdWString().c_str(),
nativeWorkingDir.empty() ? nullptr : nativeWorkingDir.c_str(),
SW_SHOWNORMAL);
if (long(result) < 32) {
DEBUG_LOG(("Application Error: failed to execute %1, working directory: '%2', result: %3"
).arg(binaryPath
).arg(cWorkingDir()
).arg(long(result)
));
return false;
}
return true;
}
} // namespace

View File

@@ -0,0 +1,45 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/launcher.h"
namespace Platform {
class Launcher : public Core::Launcher {
public:
using Core::Launcher::Launcher;
private:
base::optional<QStringList> readArgumentsHook(
int argc,
char *argv[]) const override;
bool launchUpdater(UpdaterLaunch action) override;
bool launch(
const QString &operation,
const QString &binaryPath,
const QStringList &argumentsList);
};
} // namespace Platform

View File

@@ -32,6 +32,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/localstorage.h"
#include "passcodewidget.h"
#include "base/task_queue.h"
#include "core/crash_reports.h"
#include <Shobjidl.h>
#include <shellapi.h>
@@ -647,53 +648,6 @@ void psNewVersion() {
}
}
void psExecUpdater() {
if (cExeName().isEmpty()) {
return;
}
QString targs = qsl("-update -exename \"") + cExeName() + '"';
if (cLaunchMode() == LaunchModeAutoStart) targs += qsl(" -autostart");
if (cDebug()) targs += qsl(" -debug");
if (cStartInTray()) targs += qsl(" -startintray");
if (cWriteProtected()) targs += qsl(" -writeprotected \"") + cExeDir() + '"';
QString updaterPath = cWriteProtected() ? (cWorkingDir() + qsl("tupdates/temp/Updater.exe")) : (cExeDir() + qsl("Updater.exe"));
QString updater(QDir::toNativeSeparators(updaterPath)), wdir(QDir::toNativeSeparators(cWorkingDir()));
DEBUG_LOG(("Application Info: executing %1 %2").arg(cExeDir() + "Updater.exe").arg(targs));
HINSTANCE r = ShellExecute(0, cWriteProtected() ? L"runas" : 0, updater.toStdWString().c_str(), targs.toStdWString().c_str(), wdir.isEmpty() ? 0 : wdir.toStdWString().c_str(), SW_SHOWNORMAL);
if (long(r) < 32) {
DEBUG_LOG(("Application Error: failed to execute %1, working directory: '%2', result: %3").arg(updater).arg(wdir).arg(long(r)));
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
}
}
void psExecTelegram(const QString &crashreport) {
if (cExeName().isEmpty()) {
return;
}
QString targs = crashreport.isEmpty() ? qsl("-noupdate") : ('"' + crashreport + '"');
if (crashreport.isEmpty()) {
if (cRestartingToSettings()) targs += qsl(" -tosettings");
if (cLaunchMode() == LaunchModeAutoStart) targs += qsl(" -autostart");
if (cDebug()) targs += qsl(" -debug");
if (cStartInTray()) targs += qsl(" -startintray");
if (cTestMode()) targs += qsl(" -testmode");
if (cDataFile() != qsl("data")) targs += qsl(" -key \"") + cDataFile() + '"';
}
QString telegram(QDir::toNativeSeparators(cExeDir() + cExeName())), wdir(QDir::toNativeSeparators(cWorkingDir()));
DEBUG_LOG(("Application Info: executing %1 %2").arg(cExeDir() + cExeName()).arg(targs));
Logs::closeMain();
SignalHandlers::finish();
HINSTANCE r = ShellExecute(0, 0, telegram.toStdWString().c_str(), targs.toStdWString().c_str(), wdir.isEmpty() ? 0 : wdir.toStdWString().c_str(), SW_SHOWNORMAL);
if (long(r) < 32) {
DEBUG_LOG(("Application Error: failed to execute %1, working directory: '%2', result: %3").arg(telegram).arg(wdir).arg(long(r)));
}
}
void _manageAppLnk(bool create, bool silent, int path_csidl, const wchar_t *args, const wchar_t *description) {
if (cExeName().isEmpty()) {
return;
@@ -1291,7 +1245,7 @@ QString psPrepareCrashDump(const QByteArray &crashdump, QString dumpfile) {
void psWriteStackTrace() {
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
if (!LoadDbgHelp()) {
SignalHandlers::dump() << "ERROR: Could not load dbghelp.dll!\n";
CrashReports::dump() << "ERROR: Could not load dbghelp.dll!\n";
return;
}
@@ -1348,17 +1302,17 @@ void psWriteStackTrace() {
// deeper frame could not be found.
// CONTEXT need not to be suplied if imageTyp is IMAGE_FILE_MACHINE_I386!
if (!stackWalk64(imageType, hProcess, hThread, &s, &c, ReadProcessMemoryRoutine64, symFunctionTableAccess64, symGetModuleBase64, NULL)) {
SignalHandlers::dump() << "ERROR: Call to StackWalk64() failed!\n";
CrashReports::dump() << "ERROR: Call to StackWalk64() failed!\n";
return;
}
if (s.AddrPC.Offset == s.AddrReturn.Offset) {
SignalHandlers::dump() << s.AddrPC.Offset << "\n";
SignalHandlers::dump() << "ERROR: StackWalk64() endless callstack!";
CrashReports::dump() << s.AddrPC.Offset << "\n";
CrashReports::dump() << "ERROR: StackWalk64() endless callstack!";
return;
}
if (s.AddrPC.Offset != 0) { // we seem to have a valid PC
SignalHandlers::dump() << s.AddrPC.Offset << "\n";
CrashReports::dump() << s.AddrPC.Offset << "\n";
}
if (s.AddrReturn.Offset == 0) {

View File

@@ -90,9 +90,6 @@ void psBringToBack(QWidget *w);
int psCleanup();
int psFixPrevious();
void psExecUpdater();
void psExecTelegram(const QString &arg = QString());
QAbstractNativeEventFilter *psNativeEventFilter();
void psNewVersion();

View File

@@ -20,14 +20,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "settings.h"
#include "platform/platform_specific.h"
#include "data/data_document.h"
bool gRtl = false;
Qt::LayoutDirection gLangDir = gRtl ? Qt::RightToLeft : Qt::LeftToRight;
QString gArguments;
bool gAlphaVersion = AppAlphaVersion;
uint64 gBetaVersion = AppBetaVersion;
uint64 gRealBetaVersion = AppBetaVersion;
@@ -66,7 +61,9 @@ uint32 gConnectionsInSession = 1;
QString gLoggedPhoneNumber;
QByteArray gLocalSalt;
DBIScale gRealScale = dbisAuto, gScreenScale = dbisOne, gConfigScale = dbisAuto;
DBIScale gRealScale = dbisAuto;
DBIScale gScreenScale = dbisOne;
DBIScale gConfigScale = dbisAuto;
bool gCompressPastedImage = true;
QString gTimeFormat = qsl("hh:mm");
@@ -92,6 +89,8 @@ int32 gIntRetinaFactor = 1;
#ifdef Q_OS_WIN
DBIPlatform gPlatform = dbipWindows;
#elif defined OS_MAC_OLD
DBIPlatform gPlatform = dbipMacOld;
#elif defined Q_OS_MAC
DBIPlatform gPlatform = dbipMac;
#elif defined Q_OS_LINUX64
@@ -117,155 +116,3 @@ int32 gAutoDownloadPhoto = 0; // all auto download
int32 gAutoDownloadAudio = 0;
int32 gAutoDownloadGif = 0;
bool gAutoPlayGif = true;
void ParseCommandLineArguments(const QStringList &arguments) {
enum class KeyFormat {
NoValues,
OneValue,
AllLeftValues,
};
auto parseMap = std::map<QByteArray, KeyFormat> {
{ "-testmode" , KeyFormat::NoValues },
{ "-debug" , KeyFormat::NoValues },
{ "-many" , KeyFormat::NoValues },
{ "-key" , KeyFormat::OneValue },
{ "-autostart" , KeyFormat::NoValues },
{ "-fixprevious", KeyFormat::NoValues },
{ "-cleanup" , KeyFormat::NoValues },
{ "-noupdate" , KeyFormat::NoValues },
{ "-tosettings" , KeyFormat::NoValues },
{ "-startintray", KeyFormat::NoValues },
{ "-sendpath" , KeyFormat::AllLeftValues },
{ "-workdir" , KeyFormat::OneValue },
{ "--" , KeyFormat::OneValue },
};
auto parseResult = QMap<QByteArray, QStringList>();
auto parsingKey = QByteArray();
auto parsingFormat = KeyFormat::NoValues;
for (auto &argument : arguments) {
switch (parsingFormat) {
case KeyFormat::OneValue: {
parseResult[parsingKey] = QStringList(argument.mid(0, 8192));
parsingFormat = KeyFormat::NoValues;
} break;
case KeyFormat::AllLeftValues: {
parseResult[parsingKey].push_back(argument.mid(0, 8192));
} break;
case KeyFormat::NoValues: {
parsingKey = argument.toLatin1();
auto it = parseMap.find(parsingKey);
if (it != parseMap.end()) {
parsingFormat = it->second;
parseResult[parsingKey] = QStringList();
}
} break;
}
}
gTestMode = parseResult.contains("-testmode");
gDebug = parseResult.contains("-debug");
gManyInstance = parseResult.contains("-many");
gKeyFile = parseResult.value("-key", QStringList()).join(QString());
gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart
: parseResult.contains("-fixprevious") ? LaunchModeFixPrevious
: parseResult.contains("-cleanup") ? LaunchModeCleanup : LaunchModeNormal;
gNoStartUpdate = parseResult.contains("-noupdate");
gStartToSettings = parseResult.contains("-tosettings");
gStartInTray = parseResult.contains("-startintray");
gSendPaths = parseResult.value("-sendpath", QStringList());
gWorkingDir = parseResult.value("-workdir", QStringList()).join(QString());
if (!gWorkingDir.isEmpty() && !QDir().exists(gWorkingDir)) {
gWorkingDir = QString();
}
gStartUrl = parseResult.value("--", QStringList()).join(QString());
}
void InitFromCommandLine(int argc, char *argv[]) {
Expects(argc >= 0);
auto arguments = QStringList();
arguments.reserve(argc);
for (auto i = 0; i != argc; ++i) {
arguments.push_back(fromUtf8Safe(argv[i]));
}
gArguments = arguments.join(' ');
#ifdef Q_OS_MAC
#ifndef OS_MAC_OLD
if (QSysInfo::macVersion() >= QSysInfo::MV_10_11) {
gIsElCapitan = true;
}
#else // OS_MAC_OLD
if (QSysInfo::macVersion() < QSysInfo::MV_10_7) {
gIsSnowLeopard = true;
}
gPlatform = dbipMacOld;
#endif // OS_MAC_OLD
#endif // Q_OS_MAC
switch (cPlatform()) {
case dbipWindows:
gUpdateURL = QUrl(qsl("http://tdesktop.com/win/tupdates/current"));
#ifndef OS_WIN_STORE
gPlatformString = qsl("Windows");
#else // OS_WIN_STORE
gPlatformString = qsl("WinStore");
#endif // OS_WIN_STORE
break;
case dbipMac:
gUpdateURL = QUrl(qsl("http://tdesktop.com/mac/tupdates/current"));
#ifndef OS_MAC_STORE
gPlatformString = qsl("MacOS");
#else // OS_MAC_STORE
gPlatformString = qsl("MacAppStore");
#endif // OS_MAC_STORE
break;
case dbipMacOld:
gUpdateURL = QUrl(qsl("http://tdesktop.com/mac32/tupdates/current"));
gPlatformString = qsl("MacOSold");
break;
case dbipLinux64:
gUpdateURL = QUrl(qsl("http://tdesktop.com/linux/tupdates/current"));
gPlatformString = qsl("Linux64bit");
break;
case dbipLinux32:
gUpdateURL = QUrl(qsl("http://tdesktop.com/linux32/tupdates/current"));
gPlatformString = qsl("Linux32bit");
break;
}
auto path = Platform::CurrentExecutablePath(argc, argv);
LOG(("Executable path before check: %1").arg(path));
if (!path.isEmpty()) {
auto info = QFileInfo(path);
if (info.isSymLink()) {
info = info.symLinkTarget();
}
if (info.exists()) {
gExeDir = info.absoluteDir().absolutePath() + '/';
gExeName = info.fileName();
}
}
if (cExeName().isEmpty()) {
LOG(("WARNING: Could not compute executable path, some features will be disabled."));
}
ParseCommandLineArguments(arguments);
}
RecentStickerPack &cGetRecentStickers() {
if (cRecentStickers().isEmpty() && !cRecentStickersPreload().isEmpty()) {
RecentStickerPreload p(cRecentStickersPreload());
cSetRecentStickersPreload(RecentStickerPreload());
RecentStickerPack &recent(cRefRecentStickers());
recent.reserve(p.size());
for (RecentStickerPreload::const_iterator i = p.cbegin(), e = p.cend(); i != e; ++i) {
DocumentData *doc = App::document(i->first);
if (!doc || !doc->sticker()) continue;
recent.push_back(qMakePair(doc, i->second));
}
}
return cRefRecentStickers();
}

View File

@@ -20,8 +20,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
void InitFromCommandLine(int argc, char *argv[]);
extern bool gDebug;
inline bool cDebug() {
#if defined _DEBUG
@@ -55,8 +53,6 @@ inline bool rtl() {
return cRtl();
}
DeclareReadSetting(QString, Arguments);
DeclareSetting(bool, AlphaVersion);
DeclareSetting(uint64, BetaVersion);
DeclareSetting(uint64, RealBetaVersion);
@@ -169,15 +165,13 @@ DeclareRefSetting(EmojiColorVariants, EmojiVariants);
class DocumentData;
typedef QList<QPair<DocumentData*, int16> > RecentStickerPackOld;
typedef QVector<QPair<uint64, ushort> > RecentStickerPreload;
typedef QVector<QPair<DocumentData*, ushort> > RecentStickerPack;
typedef QList<QPair<DocumentData*, int16>> RecentStickerPackOld;
typedef QVector<QPair<uint64, ushort>> RecentStickerPreload;
typedef QVector<QPair<DocumentData*, ushort>> RecentStickerPack;
DeclareSetting(RecentStickerPreload, RecentStickersPreload);
DeclareRefSetting(RecentStickerPack, RecentStickers);
RecentStickerPack &cGetRecentStickers();
typedef QList<QPair<QString, ushort> > RecentHashtagPack;
typedef QList<QPair<QString, ushort>> RecentHashtagPack;
DeclareRefSetting(RecentHashtagPack, RecentWriteHashtags);
DeclareSetting(RecentHashtagPack, RecentSearchHashtags);
@@ -203,41 +197,6 @@ inline bool passcodeCanTry() {
return dt >= 30000;
}
inline void incrementRecentHashtag(RecentHashtagPack &recent, const QString &tag) {
RecentHashtagPack::iterator i = recent.begin(), e = recent.end();
for (; i != e; ++i) {
if (i->first == tag) {
++i->second;
if (qAbs(i->second) > 0x4000) {
for (RecentHashtagPack::iterator j = recent.begin(); j != e; ++j) {
if (j->second > 1) {
j->second /= 2;
} else if (j->second > 0) {
j->second = 1;
}
}
}
for (; i != recent.begin(); --i) {
if (qAbs((i - 1)->second) > qAbs(i->second)) {
break;
}
qSwap(*i, *(i - 1));
}
break;
}
}
if (i == e) {
while (recent.size() >= 64) recent.pop_back();
recent.push_back(qMakePair(tag, 1));
for (i = recent.end() - 1; i != recent.begin(); --i) {
if ((i - 1)->second > i->second) {
break;
}
qSwap(*i, *(i - 1));
}
}
}
DeclareSetting(QStringList, SendPaths);
DeclareSetting(QString, StartUrl);
@@ -271,5 +230,3 @@ DeclareSetting(int32, AutoDownloadPhoto);
DeclareSetting(int32, AutoDownloadAudio);
DeclareSetting(int32, AutoDownloadGif);
DeclareSetting(bool, AutoPlayGif);
void settingsParseArgs(int argc, char *argv[]);

View File

@@ -94,6 +94,8 @@ CoverWidget::CoverWidget(QWidget *parent, UserData *self)
}
PhotoData *CoverWidget::validatePhoto() const {
Expects(_self != nullptr);
const auto photo = _self->userpicPhotoId()
? App::photo(_self->userpicPhotoId())
: nullptr;
@@ -106,7 +108,7 @@ PhotoData *CoverWidget::validatePhoto() const {
}
void CoverWidget::showPhoto() {
if (auto photo = validatePhoto()) {
if (const auto photo = validatePhoto()) {
Messenger::Instance().showPhoto(photo, _self);
}
}

View File

@@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/localstorage.h"
#include "platform/platform_file_utilities.h"
#include "auth_session.h"
#include "core/crash_reports.h"
namespace Storage {
@@ -722,11 +723,11 @@ bool mtpFileLoader::feedPart(int offset, base::const_byte_span bytes) {
// Debugging weird out of memory crashes.
auto info = QString("offset: %1, size: %2, cancelled: %3, finished: %4, filename: '%5', tocache: %6, fromcloud: %7, data: %8, fullsize: %9").arg(offset).arg(bytes.size()).arg(Logs::b(_cancelled)).arg(Logs::b(_finished)).arg(_filename).arg(int(_toCache)).arg(int(_fromCloud)).arg(_data.size()).arg(_size);
info += QString(", locationtype: %1, inqueue: %2, localstatus: %3").arg(int(_locationType)).arg(Logs::b(_inQueue)).arg(int(_localStatus));
SignalHandlers::setCrashAnnotation("DebugInfo", info);
CrashReports::SetAnnotation("DebugInfo", info);
}
_data.reserve(offset + bytes.size());
if (offset > 100 * 1024 * 1024) {
SignalHandlers::setCrashAnnotation("DebugInfo", QString());
CrashReports::ClearAnnotation("DebugInfo");
}
if (offset > _data.size()) {

View File

@@ -251,7 +251,7 @@ bool FileLoadTask::CheckForSong(const QString &filepath, const QByteArray &conte
if (!CheckMimeOrExtensions(filepath, result->filemime, mimes, extensions)) {
return false;
}
return false;
auto media = Media::Player::PrepareForSending(filepath, content);
if (media.duration < 0) {
return false;

View File

@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/serialize_document.h"
#include "storage/serialize_common.h"
#include "chat_helpers/stickers.h"
#include "data/data_drafts.h"
#include "window/themes/window_theme.h"
#include "observer_peer.h"
@@ -1778,7 +1779,7 @@ void _writeUserSettings() {
}
size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64));
size += sizeof(quint32) + sizeof(qint32) + (cRecentStickersPreload().isEmpty() ? cGetRecentStickers().size() : cRecentStickersPreload().size()) * (sizeof(uint64) + sizeof(ushort));
size += sizeof(quint32) + sizeof(qint32) + (Stickers::GetRecentPack().isEmpty() ? Stickers::GetRecentPack().size() : cRecentStickersPreload().size()) * (sizeof(uint64) + sizeof(ushort));
size += sizeof(quint32) + Serialize::stringSize(cDialogLastPath());
size += sizeof(quint32) + 3 * sizeof(qint32);
size += sizeof(quint32) + 2 * sizeof(qint32);
@@ -1822,11 +1823,11 @@ void _writeUserSettings() {
}
data.stream << quint32(dbiEmojiVariants) << cEmojiVariants();
{
RecentStickerPreload v(cRecentStickersPreload());
auto v = cRecentStickersPreload();
if (v.isEmpty()) {
v.reserve(cGetRecentStickers().size());
for (RecentStickerPack::const_iterator i = cGetRecentStickers().cbegin(), e = cGetRecentStickers().cend(); i != e; ++i) {
v.push_back(qMakePair(i->first->id, i->second));
v.reserve(Stickers::GetRecentPack().size());
for_const (auto &pair, Stickers::GetRecentPack()) {
v.push_back(qMakePair(pair.first->id, pair.second));
}
}
data.stream << quint32(dbiRecentStickers) << v;

View File

@@ -0,0 +1,589 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "ui/grouped_layout.h"
namespace Data {
namespace {
int Round(float64 value) {
return int(std::round(value));
}
class Layouter {
public:
Layouter(
const std::vector<QSize> &sizes,
int maxWidth,
int minWidth,
int spacing);
std::vector<GroupMediaLayout> layout() const;
private:
static std::vector<float64> CountRatios(const std::vector<QSize> &sizes);
static std::string CountProportions(const std::vector<float64> &ratios);
std::vector<GroupMediaLayout> layoutTwo() const;
std::vector<GroupMediaLayout> layoutThree() const;
std::vector<GroupMediaLayout> layoutFour() const;
std::vector<GroupMediaLayout> layoutOne() const;
std::vector<GroupMediaLayout> layoutTwoTopBottom() const;
std::vector<GroupMediaLayout> layoutTwoLeftRightEqual() const;
std::vector<GroupMediaLayout> layoutTwoLeftRight() const;
std::vector<GroupMediaLayout> layoutThreeLeftAndOther() const;
std::vector<GroupMediaLayout> layoutThreeTopAndOther() const;
std::vector<GroupMediaLayout> layoutFourLeftAndOther() const;
std::vector<GroupMediaLayout> layoutFourTopAndOther() const;
const std::vector<QSize> &_sizes;
const std::vector<float64> _ratios;
const std::string _proportions;
const int _count = 0;
const int _maxWidth = 0;
const int _maxHeight = 0;
const int _minWidth = 0;
const int _spacing = 0;
const float64 _averageRatio = 1.;
const float64 _maxSizeRatio = 1.;
};
class ComplexLayouter {
public:
ComplexLayouter(
const std::vector<float64> &ratios,
float64 averageRatio,
int maxWidth,
int minWidth,
int spacing);
std::vector<GroupMediaLayout> layout() const;
private:
struct Attempt {
std::vector<int> lineCounts;
std::vector<float64> heights;
};
static std::vector<float64> CropRatios(
const std::vector<float64> &ratios,
float64 averageRatio);
const std::vector<float64> _ratios;
const int _count = 0;
const int _maxWidth = 0;
const int _maxHeight = 0;
const int _minWidth = 0;
const int _spacing = 0;
const float64 _averageRatio = 1.;
};
Layouter::Layouter(
const std::vector<QSize> &sizes,
int maxWidth,
int minWidth,
int spacing)
: _sizes(sizes)
, _ratios(CountRatios(_sizes))
, _proportions(CountProportions(_ratios))
, _count(int(_ratios.size()))
// All apps currently use square max size first.
// In complex case they use maxWidth * 4 / 3 as maxHeight.
, _maxWidth(maxWidth)
, _maxHeight(maxWidth)
, _minWidth(minWidth)
, _spacing(spacing)
, _averageRatio(ranges::accumulate(_ratios, 0.) / _count)
, _maxSizeRatio(_maxWidth / float64(_maxHeight)) {
}
std::vector<float64> Layouter::CountRatios(const std::vector<QSize> &sizes) {
return ranges::view::all(
sizes
) | ranges::view::transform([](const QSize &size) {
return size.width() / float64(size.height());
}) | ranges::to_vector;
}
std::string Layouter::CountProportions(const std::vector<float64> &ratios) {
return ranges::view::all(
ratios
) | ranges::view::transform([](float64 ratio) {
return (ratio > 1.2) ? 'w' : (ratio < 0.8) ? 'n' : 'q';
}) | ranges::to_<std::string>();
}
std::vector<GroupMediaLayout> Layouter::layout() const {
if (!_count) {
return {};
} else if (_count == 1) {
return layoutOne();
}
using namespace rpl::mappers;
if (_count >= 5 || ranges::find_if(_ratios, _1 > 2) != _ratios.end()) {
return ComplexLayouter(
_ratios,
_averageRatio,
_maxWidth,
_minWidth,
_spacing).layout();
}
if (_count == 2) {
return layoutTwo();
} else if (_count == 3) {
return layoutThree();
}
return layoutFour();
}
std::vector<GroupMediaLayout> Layouter::layoutTwo() const {
Expects(_count == 2);
if ((_proportions == "ww")
&& (_averageRatio > 1.4 * _maxSizeRatio)
&& (_ratios[1] - _ratios[0] < 0.2)) {
return layoutTwoTopBottom();
} else if (_proportions == "ww" || _proportions == "qq") {
return layoutTwoLeftRightEqual();
}
return layoutTwoLeftRight();
}
std::vector<GroupMediaLayout> Layouter::layoutThree() const {
Expects(_count == 3);
auto result = std::vector<GroupMediaLayout>(_count);
if (_proportions[0] == 'n') {
return layoutThreeLeftAndOther();
}
return layoutThreeTopAndOther();
}
std::vector<GroupMediaLayout> Layouter::layoutFour() const {
Expects(_count == 4);
auto result = std::vector<GroupMediaLayout>(_count);
if (_proportions[0] == 'w') {
return layoutFourTopAndOther();
}
return layoutFourLeftAndOther();
}
std::vector<GroupMediaLayout> Layouter::layoutOne() const {
Expects(_count == 1);
const auto width = _maxWidth;
const auto height = (_sizes[0].height() * width) / _sizes[0].width();
return {
{
QRect(0, 0, width, height),
RectPart::Left | RectPart::Top | RectPart::Right | RectPart::Bottom
},
};
}
std::vector<GroupMediaLayout> Layouter::layoutTwoTopBottom() const {
Expects(_count == 2);
const auto width = _maxWidth;
const auto height = Round(std::min(
width / _ratios[0],
std::min(
width / _ratios[1],
(_maxHeight - _spacing) / 2.)));
return {
{
QRect(0, 0, width, height),
RectPart::Left | RectPart::Top | RectPart::Right
},
{
QRect(0, height + _spacing, width, height),
RectPart::Left | RectPart::Bottom | RectPart::Right
},
};
}
std::vector<GroupMediaLayout> Layouter::layoutTwoLeftRightEqual() const {
Expects(_count == 2);
const auto width = (_maxWidth - _spacing) / 2;
const auto height = Round(std::min(
width / _ratios[0],
std::min(width / _ratios[1], _maxHeight * 1.)));
return {
{
QRect(0, 0, width, height),
RectPart::Top | RectPart::Left | RectPart::Bottom
},
{
QRect(width + _spacing, 0, width, height),
RectPart::Top | RectPart::Right | RectPart::Bottom
},
};
}
std::vector<GroupMediaLayout> Layouter::layoutTwoLeftRight() const {
Expects(_count == 2);
const auto minimalWidth = Round(_minWidth * 1.5);
const auto secondWidth = std::min(
Round(std::max(
0.4 * (_maxWidth - _spacing),
(_maxWidth - _spacing) / _ratios[0]
/ (1. / _ratios[0] + 1. / _ratios[1]))),
_maxWidth - _spacing - minimalWidth);
const auto firstWidth = _maxWidth
- secondWidth
- _spacing;
const auto height = std::min(
_maxHeight,
Round(std::min(
firstWidth / _ratios[0],
secondWidth / _ratios[1])));
return {
{
QRect(0, 0, firstWidth, height),
RectPart::Top | RectPart::Left | RectPart::Bottom
},
{
QRect(firstWidth + _spacing, 0, secondWidth, height),
RectPart::Top | RectPart::Right | RectPart::Bottom
},
};
}
std::vector<GroupMediaLayout> Layouter::layoutThreeLeftAndOther() const {
Expects(_count == 3);
const auto firstHeight = _maxHeight;
const auto thirdHeight = Round(std::min(
(_maxHeight - _spacing) / 2.,
(_ratios[1] * (_maxWidth - _spacing)
/ (_ratios[2] + _ratios[1]))));
const auto secondHeight = firstHeight
- thirdHeight
- _spacing;
const auto rightWidth = std::max(
_minWidth,
Round(std::min(
(_maxWidth - _spacing) / 2.,
std::min(
thirdHeight * _ratios[2],
secondHeight * _ratios[1]))));
const auto leftWidth = std::min(
Round(firstHeight * _ratios[0]),
_maxWidth - _spacing - rightWidth);
return {
{
QRect(0, 0, leftWidth, firstHeight),
RectPart::Top | RectPart::Left | RectPart::Bottom
},
{
QRect(leftWidth + _spacing, 0, rightWidth, secondHeight),
RectPart::Top | RectPart::Right
},
{
QRect(leftWidth + _spacing, secondHeight + _spacing, rightWidth, thirdHeight),
RectPart::Bottom | RectPart::Right
},
};
}
std::vector<GroupMediaLayout> Layouter::layoutThreeTopAndOther() const {
Expects(_count == 3);
const auto firstWidth = _maxWidth;
const auto firstHeight = Round(std::min(
firstWidth / _ratios[0],
(_maxHeight - _spacing) * 0.66));
const auto secondWidth = (_maxWidth - _spacing) / 2;
const auto secondHeight = std::min(
_maxHeight - firstHeight - _spacing,
Round(std::min(
secondWidth / _ratios[1],
secondWidth / _ratios[2])));
const auto thirdWidth = firstWidth - secondWidth - _spacing;
return {
{
QRect(0, 0, firstWidth, firstHeight),
RectPart::Left | RectPart::Top | RectPart::Right
},
{
QRect(0, firstHeight + _spacing, secondWidth, secondHeight),
RectPart::Bottom | RectPart::Left
},
{
QRect(secondWidth + _spacing, firstHeight + _spacing, thirdWidth, secondHeight),
RectPart::Bottom | RectPart::Right
},
};
}
std::vector<GroupMediaLayout> Layouter::layoutFourTopAndOther() const {
Expects(_count == 4);
const auto w = _maxWidth;
const auto h0 = Round(std::min(
w / _ratios[0],
(_maxHeight - _spacing) * 0.66));
const auto h = Round(
(_maxWidth - 2 * _spacing)
/ (_ratios[1] + _ratios[2] + _ratios[3]));
const auto w0 = std::max(
_minWidth,
Round(std::min(
(_maxWidth - 2 * _spacing) * 0.4,
h * _ratios[1])));
const auto w2 = Round(std::max(
std::max(
_minWidth * 1.,
(_maxWidth - 2 * _spacing) * 0.33),
h * _ratios[3]));
const auto w1 = w - w0 - w2 - 2 * _spacing;
const auto h1 = std::min(
_maxHeight - h0 - _spacing,
h);
return {
{
QRect(0, 0, w, h0),
RectPart::Left | RectPart::Top | RectPart::Right
},
{
QRect(0, h0 + _spacing, w0, h1),
RectPart::Bottom | RectPart::Left
},
{
QRect(w0 + _spacing, h0 + _spacing, w1, h1),
RectPart::Bottom,
},
{
QRect(w0 + _spacing + w1 + _spacing, h0 + _spacing, w2, h1),
RectPart::Right | RectPart::BottomLeft
},
};
}
std::vector<GroupMediaLayout> Layouter::layoutFourLeftAndOther() const {
Expects(_count == 4);
const auto h = _maxHeight;
const auto w0 = Round(std::min(
h * _ratios[0],
(_maxWidth - _spacing) * 0.6));
const auto w = Round(
(_maxHeight - 2 * _spacing)
/ (1. / _ratios[1] + 1. / _ratios[2] + 1. / _ratios[3])
);
const auto h0 = Round(w / _ratios[1]);
const auto h1 = Round(w / _ratios[2]);
const auto h2 = h - h0 - h1 - 2 * _spacing;
const auto w1 = std::max(
_minWidth,
std::min(_maxWidth - w0 - _spacing, w));
return {
{
QRect(0, 0, w0, h),
RectPart::Top | RectPart::Left | RectPart::Bottom
},
{
QRect(w0 + _spacing, 0, w1, h0),
RectPart::Top | RectPart::Right
},
{
QRect(w0 + _spacing, h0 + _spacing, w1, h1),
RectPart::Right
},
{
QRect(w0 + _spacing, h0 + h1 + 2 * _spacing, w1, h2),
RectPart::Bottom | RectPart::Right
},
};
}
ComplexLayouter::ComplexLayouter(
const std::vector<float64> &ratios,
float64 averageRatio,
int maxWidth,
int minWidth,
int spacing)
: _ratios(CropRatios(ratios, averageRatio))
, _count(int(_ratios.size()))
// All apps currently use square max size first.
// In complex case they use maxWidth * 4 / 3 as maxHeight.
, _maxWidth(maxWidth)
, _maxHeight(maxWidth * 4 / 3)
, _minWidth(minWidth)
, _spacing(spacing)
, _averageRatio(averageRatio) {
}
std::vector<float64> ComplexLayouter::CropRatios(
const std::vector<float64> &ratios,
float64 averageRatio) {
return ranges::view::all(
ratios
) | ranges::view::transform([&](float64 ratio) {
return (averageRatio > 1.1)
? snap(ratio, 1., 1.7)
: snap(ratio, 0.66667, 1.);
}) | ranges::to_vector;
}
std::vector<GroupMediaLayout> ComplexLayouter::layout() const {
Expects(_count > 1);
auto result = std::vector<GroupMediaLayout>(_count);
auto attempts = std::vector<Attempt>();
const auto multiHeight = [&](int offset, int count) {
const auto ratios = gsl::make_span(_ratios).subspan(offset, count);
const auto sum = ranges::accumulate(ratios, 0.);
return (_maxWidth - (count - 1) * _spacing) / sum;
};
const auto pushAttempt = [&](std::vector<int> lineCounts) {
auto heights = std::vector<float64>();
heights.reserve(lineCounts.size());
auto offset = 0;
for (auto count : lineCounts) {
heights.push_back(multiHeight(offset, count));
offset += count;
}
attempts.push_back({ std::move(lineCounts), std::move(heights) });
};
for (auto first = 1; first != _count; ++first) {
const auto second = _count - first;
if (first > 3 || second > 3) {
continue;
}
pushAttempt({ first, second });
}
for (auto first = 1; first != _count - 1; ++first) {
for (auto second = 1; second != _count - first; ++second) {
const auto third = _count - first - second;
if ((first > 3)
|| (second > ((_averageRatio < 0.85) ? 4 : 3))
|| (third > 3)) {
continue;
}
pushAttempt({ first, second, third });
}
}
for (auto first = 1; first != _count - 1; ++first) {
for (auto second = 1; second != _count - first; ++second) {
for (auto third = 1; third != _count - first - second; ++third) {
const auto fourth = _count - first - second - third;
if (first > 3 || second > 3 || third > 3 || fourth > 3) {
continue;
}
pushAttempt({ first, second, third, fourth });
}
}
}
auto optimalAttempt = (const Attempt*)nullptr;
auto optimalDiff = 0.;
for (const auto &attempt : attempts) {
const auto &heights = attempt.heights;
const auto &counts = attempt.lineCounts;
const auto lineCount = int(counts.size());
const auto totalHeight = ranges::accumulate(heights, 0.)
+ _spacing * (lineCount - 1);
const auto minLineHeight = ranges::min(heights);
const auto maxLineHeight = ranges::max(heights);
const auto bad1 = (minLineHeight < _minWidth) ? 1.5 : 1.;
const auto bad2 = [&] {
for (auto line = 1; line != lineCount; ++line) {
if (counts[line - 1] > counts[line]) {
return 1.5;
}
}
return 1.;
}();
const auto diff = std::abs(totalHeight - _maxHeight) * bad1 * bad2;
if (!optimalAttempt || diff < optimalDiff) {
optimalAttempt = &attempt;
optimalDiff = diff;
}
}
Assert(optimalAttempt != nullptr);
const auto &optimalCounts = optimalAttempt->lineCounts;
const auto &optimalHeights = optimalAttempt->heights;
const auto rowCount = int(optimalCounts.size());
auto index = 0;
auto y = 0.;
for (auto row = 0; row != rowCount; ++row) {
const auto colCount = optimalCounts[row];
const auto lineHeight = optimalHeights[row];
const auto height = Round(lineHeight);
auto x = 0;
for (auto col = 0; col != colCount; ++col) {
const auto sides = RectPart::None
| (row == 0 ? RectPart::Top : RectPart::None)
| (row == rowCount - 1 ? RectPart::Bottom : RectPart::None)
| (col == 0 ? RectPart::Left : RectPart::None)
| (col == colCount - 1 ? RectPart::Right : RectPart::None);
const auto ratio = _ratios[index];
const auto width = (col == colCount - 1)
? (_maxWidth - x)
: Round(ratio * lineHeight);
result[index] = {
QRect(x, y, width, height),
sides
};
x += width + _spacing;
++index;
}
y += height + _spacing;
}
return result;
}
} // namespace
std::vector<GroupMediaLayout> LayoutMediaGroup(
const std::vector<QSize> &sizes,
int maxWidth,
int minWidth,
int spacing) {
return Layouter(sizes, maxWidth, minWidth, spacing).layout();
}
} // namespace Data

View File

@@ -0,0 +1,36 @@
/*
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.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
namespace Data {
struct GroupMediaLayout {
QRect geometry;
RectParts sides = RectPart::None;
};
std::vector<GroupMediaLayout> LayoutMediaGroup(
const std::vector<QSize> &sizes,
int maxWidth,
int minWidth,
int spacing);
} // namespace Data

View File

@@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "ui/text/text_block.h"
#include "core/crash_reports.h"
// COPIED FROM qtextlayout.cpp AND MODIFIED
namespace {
@@ -327,7 +329,7 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi
QString part = str.mid(_from, length);
// Attempt to catch a crash in text processing
SignalHandlers::setCrashAnnotationRef("CrashString", &part);
CrashReports::SetAnnotationRef("CrashString", &part);
QStackTextEngine engine(part, blockFont->f);
QTextLayout layout(&engine);
@@ -338,7 +340,7 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi
layout.endLayout();
SignalHandlers::clearCrashAnnotationRef("CrashString");
CrashReports::ClearAnnotationRef("CrashString");
}
}

View File

@@ -130,7 +130,7 @@ Controller::ColumnLayout Controller::computeColumnLayout() const {
int Controller::countDialogsWidthFromRatio(int bodyWidth) const {
auto result = qRound(bodyWidth * Auth().data().dialogsWidthRatio());
accumulate_max(result, st::columnMinimalWidthLeft);
accumulate_min(result, st::columnMaximalWidthLeft);
// accumulate_min(result, st::columnMaximalWidthLeft);
return result;
}

Some files were not shown because too many files have changed in this diff Show More