Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de16a66a4a | ||
|
|
b0f191515a | ||
|
|
89ccaccb88 | ||
|
|
1f070da202 | ||
|
|
963e969d2a | ||
|
|
4734700ac5 | ||
|
|
d9da2edd7c | ||
|
|
6d48ca850e | ||
|
|
3e7ac7eb26 | ||
|
|
520a644150 | ||
|
|
3a56b7cabd | ||
|
|
efa72578cd | ||
|
|
b6087ce7ce | ||
|
|
537400d8b2 | ||
|
|
4c9931ab02 | ||
|
|
0a4038d061 | ||
|
|
2d5188b968 | ||
|
|
4bab7583ba | ||
|
|
b2f29b674d | ||
|
|
574f4a73cb | ||
|
|
05e3ddce0c | ||
|
|
3c101b0a50 | ||
|
|
e998bd0b3f | ||
|
|
251176df47 | ||
|
|
97c15865a5 | ||
|
|
9d4558de2b | ||
|
|
38f7f48c17 | ||
|
|
9534121676 | ||
|
|
10b76d921b |
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
|
||||
@@ -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 &¤t) {
|
||||
return border(*current);
|
||||
});
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
1080
Telegram/SourceFiles/core/crash_report_window.cpp
Normal file
1080
Telegram/SourceFiles/core/crash_report_window.cpp
Normal file
File diff suppressed because it is too large
Load Diff
226
Telegram/SourceFiles/core/crash_report_window.h
Normal file
226
Telegram/SourceFiles/core/crash_report_window.h
Normal 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;
|
||||
|
||||
};
|
||||
580
Telegram/SourceFiles/core/crash_reports.cpp
Normal file
580
Telegram/SourceFiles/core/crash_reports.cpp
Normal 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
|
||||
85
Telegram/SourceFiles/core/crash_reports.h
Normal file
85
Telegram/SourceFiles/core/crash_reports.h
Normal 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
|
||||
243
Telegram/SourceFiles/core/launcher.cpp
Normal file
243
Telegram/SourceFiles/core/launcher.cpp
Normal 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
|
||||
70
Telegram/SourceFiles/core/launcher.h
Normal file
70
Telegram/SourceFiles/core/launcher.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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; };
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
454
Telegram/SourceFiles/history/history_media_grouped.cpp
Normal file
454
Telegram/SourceFiles/history/history_media_grouped.cpp
Normal 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;
|
||||
}
|
||||
145
Telegram/SourceFiles/history/history_media_grouped.h
Normal file
145
Telegram/SourceFiles/history/history_media_grouped.h
Normal 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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
117
Telegram/SourceFiles/platform/linux/launcher_linux.cpp
Normal file
117
Telegram/SourceFiles/platform/linux/launcher_linux.cpp
Normal 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
|
||||
36
Telegram/SourceFiles/platform/linux/launcher_linux.h
Normal file
36
Telegram/SourceFiles/platform/linux/launcher_linux.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -77,9 +77,6 @@ void psBringToBack(QWidget *w);
|
||||
int psCleanup();
|
||||
int psFixPrevious();
|
||||
|
||||
void psExecUpdater();
|
||||
void psExecTelegram(const QString &arg = QString());
|
||||
|
||||
QAbstractNativeEventFilter *psNativeEventFilter();
|
||||
|
||||
void psNewVersion();
|
||||
|
||||
38
Telegram/SourceFiles/platform/mac/launcher_mac.h
Normal file
38
Telegram/SourceFiles/platform/mac/launcher_mac.h
Normal 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
|
||||
98
Telegram/SourceFiles/platform/mac/launcher_mac.mm
Normal file
98
Telegram/SourceFiles/platform/mac/launcher_mac.mm
Normal 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
|
||||
@@ -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"));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
43
Telegram/SourceFiles/platform/platform_launcher.h
Normal file
43
Telegram/SourceFiles/platform/platform_launcher.h
Normal 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
|
||||
145
Telegram/SourceFiles/platform/win/launcher_win.cpp
Normal file
145
Telegram/SourceFiles/platform/win/launcher_win.cpp
Normal 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
|
||||
45
Telegram/SourceFiles/platform/win/launcher_win.h
Normal file
45
Telegram/SourceFiles/platform/win/launcher_win.h
Normal 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
|
||||
@@ -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) {
|
||||
|
||||
@@ -90,9 +90,6 @@ void psBringToBack(QWidget *w);
|
||||
int psCleanup();
|
||||
int psFixPrevious();
|
||||
|
||||
void psExecUpdater();
|
||||
void psExecTelegram(const QString &arg = QString());
|
||||
|
||||
QAbstractNativeEventFilter *psNativeEventFilter();
|
||||
|
||||
void psNewVersion();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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[]);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
589
Telegram/SourceFiles/ui/grouped_layout.cpp
Normal file
589
Telegram/SourceFiles/ui/grouped_layout.cpp
Normal 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
|
||||
36
Telegram/SourceFiles/ui/grouped_layout.h
Normal file
36
Telegram/SourceFiles/ui/grouped_layout.h
Normal 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
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user