diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index b9274671bc..bc126eb6cc 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -28,7 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "lang.h" #include "boxes/confirmbox.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "ui/widgets/tooltip.h" #include "langloaderplain.h" #include "localstorage.h" diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp index d11d58c9aa..87c581ca5f 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.cpp +++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp @@ -28,7 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/contactsbox.h" #include "boxes/confirmbox.h" #include "boxes/photocropbox.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" diff --git a/Telegram/SourceFiles/boxes/addcontactbox.h b/Telegram/SourceFiles/boxes/addcontactbox.h index df12901c21..d9d9a805ba 100644 --- a/Telegram/SourceFiles/boxes/addcontactbox.h +++ b/Telegram/SourceFiles/boxes/addcontactbox.h @@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "boxes/abstractbox.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" class ConfirmBox; diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 1643b7e4b0..a5122a4a15 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -33,7 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "messenger.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "ui/widgets/multi_select.h" #include "ui/widgets/scroll_area.h" #include "ui/effects/widget_slide_wrap.h" diff --git a/Telegram/SourceFiles/boxes/downloadpathbox.cpp b/Telegram/SourceFiles/boxes/downloadpathbox.cpp index a83f95a498..1054dc1dc9 100644 --- a/Telegram/SourceFiles/boxes/downloadpathbox.cpp +++ b/Telegram/SourceFiles/boxes/downloadpathbox.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "lang.h" #include "localstorage.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "pspecific.h" @@ -100,19 +100,19 @@ void DownloadPathBox::onChange() { } void DownloadPathBox::onEditPath() { - filedialogInit(); - QString path, lastPath = cDialogLastPath(); - if (!Global::DownloadPath().isEmpty() && Global::DownloadPath() != qstr("tmp")) { - cSetDialogLastPath(Global::DownloadPath().left(Global::DownloadPath().size() - (Global::DownloadPath().endsWith('/') ? 1 : 0))); - } - if (filedialogGetDir(path, lang(lng_download_path_choose))) { - if (!path.isEmpty()) { - _path = path + '/'; + auto initialPath = [] { + if (!Global::DownloadPath().isEmpty() && Global::DownloadPath() != qstr("tmp")) { + return Global::DownloadPath().left(Global::DownloadPath().size() - (Global::DownloadPath().endsWith('/') ? 1 : 0)); + } + return QString(); + }; + FileDialog::GetFolder(lang(lng_download_path_choose), initialPath(), base::lambda_guarded(this, [this](const QString &result) { + if (!result.isEmpty()) { + _path = result + '/'; _pathBookmark = psDownloadPathBookmark(_path); setPathText(QDir::toNativeSeparators(_path)); } - } - cSetDialogLastPath(lastPath); + })); } void DownloadPathBox::save() { diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index f3254930aa..7ac3b6813f 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -25,7 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "mainwidget.h" #include "history/history_media_types.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 816f7ccc77..443f5908e2 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -28,6 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "core/qthelp_url.h" #include "localstorage.h" #include "ui/widgets/tooltip.h" +#include "core/file_utilities.h" QString UrlClickHandler::copyToClipboardContextItemText() const { return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link); @@ -68,10 +69,7 @@ void UrlClickHandler::doOpen(QString url) { Ui::Tooltip::Hide(); if (isEmail(url)) { - QUrl u(qstr("mailto:") + url); - if (!QDesktopServices::openUrl(u)) { - psOpenFile(u.toString(QUrl::FullyEncoded), true); - } + File::OpenEmailLink(url); return; } diff --git a/Telegram/SourceFiles/ui/filedialog.cpp b/Telegram/SourceFiles/core/file_utilities.cpp similarity index 62% rename from Telegram/SourceFiles/ui/filedialog.cpp rename to Telegram/SourceFiles/core/file_utilities.cpp index f86eafc1b8..498b636386 100644 --- a/Telegram/SourceFiles/ui/filedialog.cpp +++ b/Telegram/SourceFiles/core/file_utilities.cpp @@ -19,70 +19,25 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "mainwindow.h" #include "localstorage.h" #include "platform/platform_file_utilities.h" - #include "core/task_queue.h" -void filedialogInit() { - if (cDialogLastPath().isEmpty()) { -#ifdef Q_OS_WIN - // hack to restore previous dir without hurting performance - QSettings settings(QSettings::UserScope, qstr("QtProject")); - settings.beginGroup(qstr("Qt")); - QByteArray sd = settings.value(qstr("filedialog")).toByteArray(); - QDataStream stream(&sd, QIODevice::ReadOnly); - if (!stream.atEnd()) { - int version = 3, _QFileDialogMagic = 190; - QByteArray splitterState; - QByteArray headerData; - QList bookmarks; - QStringList history; - QString currentDirectory; - qint32 marker; - qint32 v; - qint32 viewMode; - stream >> marker; - stream >> v; - if (marker == _QFileDialogMagic && v == version) { - stream >> splitterState - >> bookmarks - >> history - >> currentDirectory - >> headerData - >> viewMode; - cSetDialogLastPath(currentDirectory); - } - } - if (cDialogHelperPath().isEmpty()) { - QDir temppath(cWorkingDir() + "tdata/tdummy/"); - if (!temppath.exists()) { - temppath.mkpath(temppath.absolutePath()); - } - if (temppath.exists()) { - cSetDialogHelperPath(temppath.absolutePath()); - } - } -#else // Q_OS_WIN - cSetDialogLastPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); -#endif // Q_OS_WIN - } -} - namespace FileDialog { namespace internal { -bool getFiles(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, FileDialog::internal::Type type, QString startFile = QString()) { - filedialogInit(); +void InitLastPathDefault() { + cSetDialogLastPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); +} - if (Platform::FileDialog::Supported()) { - return Platform::FileDialog::Get(files, remoteContent, caption, filter, type, startFile); +bool GetDefault(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, FileDialog::internal::Type type, QString startFile = QString()) { + if (cDialogLastPath().isEmpty()) { + Platform::FileDialog::InitLastPath(); } -#if defined Q_OS_LINUX || defined Q_OS_MAC // use native remoteContent = QByteArray(); if (startFile.isEmpty() || startFile.at(0) != '/') { startFile = cDialogLastPath() + '/' + startFile; @@ -107,122 +62,56 @@ bool getFiles(QStringList &files, QByteArray &remoteContent, const QString &capt files = QStringList(); return false; } - QString path = QFileInfo(file).absoluteDir().absolutePath(); - if (!path.isEmpty() && path != cDialogLastPath()) { - cSetDialogLastPath(path); - Local::writeUserSettings(); + if (type != Type::ReadFolder) { + // Save last used directory for all queries except directory choosing. + auto path = QFileInfo(file).absoluteDir().absolutePath(); + if (!path.isEmpty() && path != cDialogLastPath()) { + cSetDialogLastPath(path); + Local::writeUserSettings(); + } } files = QStringList(file); return true; -#else // Q_OS_LINUX || Q_OS_MAC - - // A hack for fast dialog create. There was some huge performance problem - // if we open a file dialog in some folder with a large amount of files. - // Some internal Qt watcher iterated over all of them, querying some information - // that forced file icon and maybe other properties being resolved and this was - // a blocking operation. - auto helperPath = cDialogHelperPathFinal(); - QFileDialog dialog(App::wnd() ? App::wnd()->filedialogParent() : 0, caption, helperPath, filter); - - dialog.setModal(true); - if (type == Type::ReadFile || type == Type::ReadFiles) { - dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile); - dialog.setAcceptMode(QFileDialog::AcceptOpen); - } else if (type == Type::ReadFolder) { // save dir - dialog.setAcceptMode(QFileDialog::AcceptOpen); - - // We use "obsolete" value ::DirectoryOnly instead of ::Directory + ::ShowDirsOnly - // because in Windows XP native dialog this one works, while the "preferred" one - // shows a native file choose dialog where you can't choose a directory, only open one. - dialog.setFileMode(QFileDialog::DirectoryOnly); - dialog.setOption(QFileDialog::ShowDirsOnly); - } else { // save file - dialog.setFileMode(QFileDialog::AnyFile); - dialog.setAcceptMode(QFileDialog::AcceptSave); - } - dialog.show(); - - auto realLastPath = cDialogLastPath(); - if (realLastPath.isEmpty() || realLastPath.endsWith(qstr("/tdummy"))) { - realLastPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); - } - dialog.setDirectory(realLastPath); - - if (type == Type::WriteFile) { - QString toSelect(startFile); -#ifdef Q_OS_WIN - int32 lastSlash = toSelect.lastIndexOf('/'); - if (lastSlash >= 0) { - toSelect = toSelect.mid(lastSlash + 1); - } - int32 lastBackSlash = toSelect.lastIndexOf('\\'); - if (lastBackSlash >= 0) { - toSelect = toSelect.mid(lastBackSlash + 1); - } -#endif - dialog.selectFile(toSelect); - } - - int res = dialog.exec(); - - QString path = dialog.directory().absolutePath(); - if (path != cDialogLastPath()) { - cSetDialogLastPath(path); - Local::writeUserSettings(); - } - - if (res == QDialog::Accepted) { - if (type == Type::ReadFiles) { - files = dialog.selectedFiles(); - } else { - files = dialog.selectedFiles().mid(0, 1); - } - if (type == Type::ReadFile || type == Type::ReadFiles) { -#ifdef Q_OS_WIN - remoteContent = dialog.selectedRemoteContent(); -#endif // Q_OS_WIN - } - return true; - } - - files = QStringList(); - remoteContent = QByteArray(); - return false; -#endif // Q_OS_WIN } } // namespace internal } // namespace FileDialog bool filedialogGetOpenFiles(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter) { - return FileDialog::internal::getFiles(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFiles); + return Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFiles); } bool filedialogGetOpenFile(QString &file, QByteArray &remoteContent, const QString &caption, const QString &filter) { QStringList files; - bool result = FileDialog::internal::getFiles(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFile); + bool result = Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::ReadFile); file = files.isEmpty() ? QString() : files.at(0); return result; } -bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &startName) { +bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &initialPath) { QStringList files; QByteArray remoteContent; - bool result = FileDialog::internal::getFiles(files, remoteContent, caption, filter, FileDialog::internal::Type::WriteFile, startName); + bool result = Platform::FileDialog::Get(files, remoteContent, caption, filter, FileDialog::internal::Type::WriteFile, initialPath); file = files.isEmpty() ? QString() : files.at(0); return result; } -bool filedialogGetDir(QString &dir, const QString &caption) { +bool filedialogGetDir(QString &dir, const QString &caption, const QString &initialPath) { QStringList files; QByteArray remoteContent; - bool result = FileDialog::internal::getFiles(files, remoteContent, caption, QString(), FileDialog::internal::Type::ReadFolder); + bool result = Platform::FileDialog::Get(files, remoteContent, caption, QString(), FileDialog::internal::Type::ReadFolder, initialPath); dir = files.isEmpty() ? QString() : files.at(0); return result; } QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path, bool skipExistance) { - filedialogInit(); + auto directoryPath = path; + if (directoryPath.isEmpty()) { + if (cDialogLastPath().isEmpty()) { + Platform::FileDialog::InitLastPath(); + } + directoryPath = cDialogLastPath(); + } time_t t = time(NULL); struct tm tm; @@ -235,7 +124,7 @@ QString filedialogDefaultName(const QString &prefix, const QString &extension, c if (skipExistance) { name = base + extension; } else { - QDir dir(path.isEmpty() ? cDialogLastPath() : path); + QDir dir(directoryPath); QString nameBase = dir.absolutePath() + '/' + base; name = nameBase + extension; for (int i = 0; QFileInfo(name).exists(); ++i) { @@ -268,6 +157,50 @@ QString filedialogAllFilesFilter() { #endif // Q_OS_WIN } +namespace File { + +void OpenEmailLink(const QString &email) { + base::TaskQueue::Main().Put([email] { + Platform::File::UnsafeOpenEmailLink(email); + }); +} + +void OpenWith(const QString &filepath, QPoint menuPosition) { + base::TaskQueue::Main().Put([filepath, menuPosition] { + if (!Platform::File::UnsafeShowOpenWithDropdown(filepath, menuPosition)) { + if (!Platform::File::UnsafeShowOpenWith(filepath)) { + Platform::File::UnsafeLaunch(filepath); + } + } + }); +} + +void Launch(const QString &filepath) { + base::TaskQueue::Main().Put([filepath] { + Platform::File::UnsafeLaunch(filepath); + }); +} + +void ShowInFolder(const QString &filepath) { + base::TaskQueue::Main().Put([filepath] { + Platform::File::UnsafeShowInFolder(filepath); + }); +} + +namespace internal { + +void UnsafeOpenEmailLinkDefault(const QString &email) { + auto url = QUrl(qstr("mailto:") + email); + QDesktopServices::openUrl(url); +} + +void UnsafeLaunchDefault(const QString &filepath) { + QDesktopServices::openUrl(QUrl::fromLocalFile(filepath)); +} + +} // namespace internal +} // namespace File + namespace FileDialog { namespace { @@ -375,7 +308,7 @@ bool processQuery() { case Query::Type::ReadFolder: { QString folder; - if (filedialogGetDir(folder, query.caption)) { + if (filedialogGetDir(folder, query.caption, query.filePath)) { if (!folder.isEmpty()) { update.filePaths.push_back(folder); } @@ -394,7 +327,7 @@ base::Observable &QueryDone() { return QueryDoneObservable; } -void askOpenPath(const QString &caption, const QString &filter, base::lambda callback, base::lambda failed) { +void GetOpenPath(const QString &caption, const QString &filter, base::lambda callback, base::lambda failed) { base::TaskQueue::Main().Put([caption, filter, callback = std::move(callback), failed = std::move(failed)] { auto file = QString(); auto remoteContent = QByteArray(); @@ -413,7 +346,7 @@ void askOpenPath(const QString &caption, const QString &filter, base::lambda callback, base::lambda failed) { +void GetOpenPaths(const QString &caption, const QString &filter, base::lambda callback, base::lambda failed) { base::TaskQueue::Main().Put([caption, filter, callback = std::move(callback), failed = std::move(failed)] { auto files = QStringList(); auto remoteContent = QByteArray(); @@ -431,7 +364,7 @@ void askOpenPaths(const QString &caption, const QString &filter, base::lambda callback, base::lambda failed) { +void GetWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda callback, base::lambda failed) { base::TaskQueue::Main().Put([caption, filter, initialPath, callback = std::move(callback), failed = std::move(failed)] { auto file = QString(); if (filedialogGetSaveFile(file, caption, filter, initialPath)) { @@ -444,10 +377,10 @@ void askWritePath(const QString &caption, const QString &filter, const QString & }); } -void askFolder(const QString &caption, base::lambda callback, base::lambda failed) { - base::TaskQueue::Main().Put([caption, callback = std::move(callback), failed = std::move(failed)] { +void GetFolder(const QString &caption, const QString &initialPath, base::lambda callback, base::lambda failed) { + base::TaskQueue::Main().Put([caption, initialPath, callback = std::move(callback), failed = std::move(failed)] { auto folder = QString(); - if (filedialogGetDir(folder, caption) && !folder.isEmpty()) { + if (filedialogGetDir(folder, caption, initialPath) && !folder.isEmpty()) { if (callback) { callback(folder); } diff --git a/Telegram/SourceFiles/ui/filedialog.h b/Telegram/SourceFiles/core/file_utilities.h similarity index 68% rename from Telegram/SourceFiles/ui/filedialog.h rename to Telegram/SourceFiles/core/file_utilities.h index b315c74b0a..80ebf32870 100644 --- a/Telegram/SourceFiles/ui/filedialog.h +++ b/Telegram/SourceFiles/core/file_utilities.h @@ -22,17 +22,37 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "core/observer.h" -void filedialogInit(); +// legacy bool filedialogGetOpenFiles(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter); bool filedialogGetOpenFile(QString &file, QByteArray &remoteContent, const QString &caption, const QString &filter); -bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &startName); -bool filedialogGetDir(QString &dir, const QString &caption); +bool filedialogGetSaveFile(QString &file, const QString &caption, const QString &filter, const QString &initialPath); +bool filedialogGetDir(QString &dir, const QString &caption, const QString &initialPath); QString filedialogDefaultName(const QString &prefix, const QString &extension, const QString &path = QString(), bool skipExistance = false); QString filedialogNextFilename(const QString &name, const QString &cur, const QString &path = QString()); QString filedialogAllFilesFilter(); +namespace File { + +// Those functions are async wrappers to Platform::File::Unsafe* calls. +void OpenEmailLink(const QString &email); +void OpenWith(const QString &filepath, QPoint menuPosition); +void Launch(const QString &filepath); +void ShowInFolder(const QString &filepath); + +namespace internal { + +inline QString UrlToLocalDefault(const QUrl &url) { + return url.toLocalFile(); +} + +void UnsafeOpenEmailLinkDefault(const QString &email); +void UnsafeLaunchDefault(const QString &filepath); + +} // namespace internal +} // namespace File + namespace FileDialog { namespace internal { @@ -43,6 +63,10 @@ enum class Type { WriteFile, }; +void InitLastPathDefault(); + +bool GetDefault(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile); + } // namespace internal using QueryId = uint64; @@ -69,9 +93,9 @@ struct OpenResult { QStringList paths; QByteArray remoteContent; }; -void askOpenPath(const QString &caption, const QString &filter, base::lambda callback, base::lambda failed = base::lambda()); -void askOpenPaths(const QString &caption, const QString &filter, base::lambda callback, base::lambda failed = base::lambda()); -void askWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda callback, base::lambda failed = base::lambda()); -void askFolder(const QString &caption, base::lambda callback, base::lambda failed = base::lambda()); +void GetOpenPath(const QString &caption, const QString &filter, base::lambda callback, base::lambda failed = base::lambda()); +void GetOpenPaths(const QString &caption, const QString &filter, base::lambda callback, base::lambda failed = base::lambda()); +void GetWritePath(const QString &caption, const QString &filter, const QString &initialPath, base::lambda callback, base::lambda failed = base::lambda()); +void GetFolder(const QString &caption, const QString &initialPath, base::lambda callback, base::lambda failed = base::lambda()); } // namespace FileDialog diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h index b893bef39b..075c00370c 100644 --- a/Telegram/SourceFiles/core/utils.h +++ b/Telegram/SourceFiles/core/utils.h @@ -489,10 +489,11 @@ enum DBIPeerReportSpamStatus { dbiprsRequesting = 5, // requesting the cloud setting right now }; -inline QString strMakeFromLetters(const uint32 *letters, int32 len) { +template +inline QString strMakeFromLetters(const uint32 (&letters)[Size]) { QString result; - result.reserve(len); - for (int32 i = 0; i < len; ++i) { + result.reserve(Size); + for (int32 i = 0; i < Size; ++i) { result.push_back(QChar((((letters[i] >> 16) & 0xFF) << 8) | (letters[i] & 0xFF))); } return result; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 459a8db5bf..3403f8a360 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -29,7 +29,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/confirmbox.h" #include "boxes/send_files_box.h" #include "boxes/sharebox.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "ui/toast/toast.h" #include "ui/buttons/history_down_button.h" #include "ui/widgets/buttons.h" @@ -1532,7 +1532,7 @@ void HistoryInner::showContextInFolder() { } } if (!filepath.isEmpty()) { - psShowInFolder(filepath); + File::ShowInFolder(filepath); } } diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index f2d9196514..ac71b8c92c 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "localimageloader.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "ui/widgets/tooltip.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/scroll_area.h" diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp index 70d54a9be1..fca4ce1fea 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp @@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "inline_bots/inline_bot_layout_item.h" #include "inline_bots/inline_bot_send_data.h" #include "mtproto/file_download.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "mainwidget.h" namespace InlineBots { diff --git a/Telegram/SourceFiles/intro/intropwdcheck.cpp b/Telegram/SourceFiles/intro/intropwdcheck.cpp index d83d6fc7fb..4de7b4ceea 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.cpp +++ b/Telegram/SourceFiles/intro/intropwdcheck.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_intro.h" #include "styles/style_boxes.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "boxes/confirmbox.h" #include "lang.h" #include "application.h" diff --git a/Telegram/SourceFiles/intro/introsignup.cpp b/Telegram/SourceFiles/intro/introsignup.cpp index f573ec2c08..8a23aa8848 100644 --- a/Telegram/SourceFiles/intro/introsignup.cpp +++ b/Telegram/SourceFiles/intro/introsignup.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_intro.h" #include "styles/style_boxes.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "boxes/photocropbox.h" #include "boxes/confirmbox.h" #include "lang.h" diff --git a/Telegram/SourceFiles/intro/introsignup.h b/Telegram/SourceFiles/intro/introsignup.h index d5db7aca7d..14747f064e 100644 --- a/Telegram/SourceFiles/intro/introsignup.h +++ b/Telegram/SourceFiles/intro/introsignup.h @@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "intro/introwidget.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" namespace Ui { class RoundButton; diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp index cc21384187..9887e4858b 100644 --- a/Telegram/SourceFiles/layerwidget.cpp +++ b/Telegram/SourceFiles/layerwidget.cpp @@ -27,7 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "application.h" #include "mainwindow.h" #include "mainwidget.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "styles/style_boxes.h" #include "styles/style_widgets.h" #include "styles/style_stickers.h" diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index cc7ed129ed..e337ff469c 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -26,7 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "application.h" #include "fileuploader.h" #include "mainwindow.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" #include "media/media_audio.h" diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index d5857511b4..20ad3b2145 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -20,9 +20,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "localimageloader.h" -#include "ui/filedialog.h" -#include "media/media_audio.h" +#include "core/file_utilities.h" +#include "media/media_audio.h" #include "boxes/send_files_box.h" #include "media/media_clip_reader.h" #include "mainwidget.h" diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 4cc1a11f53..f1e52e738e 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1578,7 +1578,7 @@ void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) { auto filepath = document->filepath(DocumentData::FilePathResolveSaveFromData); if (!filepath.isEmpty()) { if (documentIsValidMediaFile(filepath)) { - psOpenFile(filepath); + File::Launch(filepath); } } } diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 53e443d8bb..3c44041967 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -25,7 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "mainwindow.h" #include "application.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/buttons.h" #include "media/media_clip_reader.h" @@ -631,7 +631,7 @@ void MediaView::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool presse } void MediaView::showSaveMsgFile() { - psShowInFolder(_saveMsgFilename); + File::ShowInFolder(_saveMsgFilename); } void MediaView::close() { @@ -908,9 +908,9 @@ void MediaView::onSaveCancel() { void MediaView::onShowInFolder() { if (!_doc) return; - QString filepath = _doc->filepath(DocumentData::FilePathResolveChecked); + auto filepath = _doc->filepath(DocumentData::FilePathResolveChecked); if (!filepath.isEmpty()) { - psShowInFolder(filepath); + File::ShowInFolder(filepath); } } diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp index 73d46999dd..a1003079ff 100644 --- a/Telegram/SourceFiles/messenger.cpp +++ b/Telegram/SourceFiles/messenger.cpp @@ -38,7 +38,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "window/themes/window_theme.h" #include "history/history_location_manager.h" #include "ui/widgets/tooltip.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "serialize/serialize_common.h" namespace { diff --git a/Telegram/SourceFiles/mtproto/file_download.cpp b/Telegram/SourceFiles/mtproto/file_download.cpp index c23b6791ae..996f919fa0 100644 --- a/Telegram/SourceFiles/mtproto/file_download.cpp +++ b/Telegram/SourceFiles/mtproto/file_download.cpp @@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "messenger.h" #include "localstorage.h" +#include "platform/platform_file_utilities.h" namespace { @@ -203,7 +204,7 @@ void FileLoader::localLoaded(const StorageImageSaved &result, const QByteArray & if (_fileIsOpen) { _file.close(); _fileIsOpen = false; - psPostprocessFile(QFileInfo(_file).absoluteFilePath()); + Platform::File::PostprocessDownloaded(QFileInfo(_file).absoluteFilePath()); } FileDownload::ImageLoaded().notify(); @@ -531,7 +532,7 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe if (_fileIsOpen) { _file.close(); _fileIsOpen = false; - psPostprocessFile(QFileInfo(_file).absoluteFilePath()); + Platform::File::PostprocessDownloaded(QFileInfo(_file).absoluteFilePath()); } removeFromQueue(); @@ -693,7 +694,7 @@ void webFileLoader::onFinished(const QByteArray &data) { if (_fileIsOpen) { _file.close(); _fileIsOpen = false; - psPostprocessFile(QFileInfo(_file).absoluteFilePath()); + Platform::File::PostprocessDownloaded(QFileInfo(_file).absoluteFilePath()); } removeFromQueue(); diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 93678e3925..c5392c008e 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_overview.h" #include "styles/style_history.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" #include "lang.h" diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 3ee7afd85e..59423b06fb 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -27,7 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" #include "boxes/photocropbox.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/tooltip.h" #include "ui/widgets/buttons.h" @@ -1440,7 +1440,7 @@ void OverviewInner::showContextInFolder() { if (auto lnkDocument = dynamic_cast(_contextMenuLnk.data())) { auto filepath = lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked); if (!filepath.isEmpty()) { - psShowInFolder(filepath); + File::ShowInFolder(filepath); } } } diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index 5b8232772c..c1f536e4b9 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -30,6 +30,44 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org QStringList qt_make_filter_list(const QString &filter); namespace Platform { +namespace File { +namespace internal { + +QByteArray EscapeShell(const QByteArray &content) { + auto result = QByteArray(); + + auto b = content.constData(), e = content.constEnd(); + for (auto ch = b; ch != e; ++ch) { + if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') { + if (result.isEmpty()) { + result.reserve(content.size() * 2); + } + if (ch > b) { + result.append(b, ch - b); + } + result.append('\\'); + b = ch; + } + } + if (result.isEmpty()) { + return content; + } + + if (e > b) { + result.append(b, e - b); + } + return result; +} + +} // namespace internal + +void UnsafeShowInFolder(const QString &filepath) { + Ui::hideLayer(true); + system(("xdg-open " + internal::EscapeShell(QFile::encodeName(QFileInfo(filepath).absoluteDir().absolutePath()))).constData()); +} + +} // namespace File + namespace FileDialog { namespace { @@ -43,11 +81,9 @@ namespace { constexpr auto kPreviewWidth = 256; constexpr auto kPreviewHeight = 512; -} // namespace - using Type = ::FileDialog::internal::Type; -bool Supported() { +bool NativeSupported() { return Platform::internal::GdkHelperLoaded() && (Libs::gtk_widget_hide_on_delete != nullptr) && (Libs::gtk_clipboard_store != nullptr) @@ -84,11 +120,11 @@ bool Supported() { } bool PreviewSupported() { - return Supported() + return NativeSupported() && (Libs::gdk_pixbuf_new_from_file_at_size != nullptr); } -bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, Type type, QString startFile) { +bool GetNative(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, Type type, QString startFile) { auto parent = App::wnd() ? App::wnd()->filedialogParent() : nullptr; internal::GtkFileDialog dialog(parent, caption, QString(), filter); @@ -131,6 +167,15 @@ bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, return false; } +} // namespace + +bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, Type type, QString startFile) { + if (NativeSupported()) { + return GetNative(files, remoteContent, caption, filter, type, startFile); + } + return ::FileDialog::internal::GetDefault(files, remoteContent, caption, filter, type, startFile); +} + namespace internal { QGtkDialog::QGtkDialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) { diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.h b/Telegram/SourceFiles/platform/linux/file_utilities_linux.h index 37931f176c..422c40cb9b 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.h +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.h @@ -31,18 +31,42 @@ extern "C" { namespace Platform { namespace File { +namespace internal { + +QByteArray EscapeShell(const QByteArray &content); + +} // namespace internal inline QString UrlToLocal(const QUrl &url) { - return url.toLocalFile(); + return ::File::internal::UrlToLocalDefault(url); +} + +inline void UnsafeOpenEmailLink(const QString &email) { + return ::File::internal::UnsafeOpenEmailLinkDefault(email); +} + +inline bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition) { + return false; +} + +inline bool UnsafeShowOpenWith(const QString &filepath) { + return false; +} + +inline void UnsafeLaunch(const QString &filepath) { + return ::File::internal::UnsafeLaunchDefault(filepath); +} + +inline void PostprocessDownloaded(const QString &filepath) { } } // namespace File namespace FileDialog { -bool Supported(); - -bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile); +inline void InitLastPath() { + ::FileDialog::internal::InitLastPathDefault(); +} namespace internal { diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 5b2e9fca1a..a787b0667e 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -374,7 +374,7 @@ void MainWindow::updateIconCounters() { } bool MainWindow::psHasNativeNotifications() { - return Notifications::supported(); + return Notifications::Supported(); } void MainWindow::LibsLoaded() { diff --git a/Telegram/SourceFiles/platform/mac/file_utilities_mac.h b/Telegram/SourceFiles/platform/mac/file_utilities_mac.h index 78d753e930..5ffd2f917d 100644 --- a/Telegram/SourceFiles/platform/mac/file_utilities_mac.h +++ b/Telegram/SourceFiles/platform/mac/file_utilities_mac.h @@ -23,14 +23,25 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "platform/platform_file_utilities.h" namespace Platform { +namespace File { + +inline void UnsafeOpenEmailLink(const QString &email) { + return ::File::internal::UnsafeOpenEmailLinkDefault(email); +} + +inline void PostprocessDownloaded(const QString &filepath) { +} + +} // namespace File + namespace FileDialog { -inline bool Supported() { - return false; +inline void InitLastPath() { + ::FileDialog::internal::InitLastPathDefault(); } inline bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile) { - return false; + return ::FileDialog::internal::GetDefault(files, remoteContent, caption, filter, type, startFile); } } // namespace FileDialog diff --git a/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm b/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm index 402edf0fc9..9ef43b95ce 100644 --- a/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm +++ b/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm @@ -22,10 +22,366 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "platform/mac/file_utilities_mac.h" #include "platform/mac/mac_utilities.h" +#include "styles/style_window.h" #include #include +namespace { + +using namespace Platform; + +QString strNeedToReload() { + const uint32 letters[] = { 0x82007746, 0xBB00C649, 0x7E00235F, 0x9A00FE54, 0x4C004542, 0x91001772, 0x8A00D76F, 0xC700B977, 0x7F005F73, 0x34003665, 0x2300D572, 0x72002E54, 0x18001461, 0x14004A62, 0x5100CC6C, 0x83002365, 0x5A002C56, 0xA5004369, 0x26004265, 0x0D006577 }; + return strMakeFromLetters(letters); +} + +QString strNeedToRefresh1() { + const uint32 letters[] = { 0xEF006746, 0xF500CE49, 0x1500715F, 0x95001254, 0x3A00CB4C, 0x17009469, 0xB400DA73, 0xDE00C574, 0x9200EC56, 0x3C00A669, 0xFD00D865, 0x59000977 }; + return strMakeFromLetters(letters); +} + +QString strNeedToRefresh2() { + const uint32 letters[] = { 0x8F001546, 0xAF007A49, 0xB8002B5F, 0x1A000B54, 0x0D003E49, 0xE0003663, 0x4900796F, 0x0500836E, 0x9A00D156, 0x5E00FF69, 0x5900C765, 0x3D00D177 }; + return strMakeFromLetters(letters); +} + +} // namespace + +@interface OpenWithApp : NSObject { + NSString *fullname; + NSURL *app; + NSImage *icon; +} +@property (nonatomic, retain) NSString *fullname; +@property (nonatomic, retain) NSURL *app; +@property (nonatomic, retain) NSImage *icon; +@end + +@implementation OpenWithApp +@synthesize fullname, app, icon; + +- (void) dealloc { + [fullname release]; + [app release]; + [icon release]; + [super dealloc]; +} + +@end + +@interface OpenFileWithInterface : NSObject { +} + +- (id) init:(NSString *)file; +- (BOOL) popupAtX:(int)x andY:(int)y; +- (void) itemChosen:(id)sender; +- (void) dealloc; + +@end + +@implementation OpenFileWithInterface { + NSString *toOpen; + + NSURL *defUrl; + NSString *defBundle, *defName, *defVersion; + NSImage *defIcon; + + NSMutableArray *apps; + + NSMenu *menu; +} + +- (void) fillAppByUrl:(NSURL*)url bundle:(NSString**)bundle name:(NSString**)name version:(NSString**)version icon:(NSImage**)icon { + NSBundle *b = [NSBundle bundleWithURL:url]; + if (b) { + NSString *path = [url path]; + *name = [[NSFileManager defaultManager] displayNameAtPath: path]; + if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleName"]; + if (*name) { + *bundle = [b bundleIdentifier]; + if (bundle) { + *version = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + *icon = [[NSWorkspace sharedWorkspace] iconForFile: path]; + if (*icon && [*icon isValid]) [*icon setSize: CGSizeMake(16., 16.)]; + return; + } + } + } + *bundle = *name = *version = nil; + *icon = nil; +} + +- (id) init:(NSString*)file { + toOpen = [file retain]; + if (self = [super init]) { + NSURL *url = [NSURL fileURLWithPath:file]; + defUrl = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:url]; + if (defUrl) { + [self fillAppByUrl:defUrl bundle:&defBundle name:&defName version:&defVersion icon:&defIcon]; + if (!defBundle || !defName) { + defUrl = nil; + } + } + NSArray *appsList = (NSArray*)LSCopyApplicationURLsForURL(CFURLRef(url), kLSRolesAll); + NSMutableDictionary *data = [NSMutableDictionary dictionaryWithCapacity:16]; + int fullcount = 0; + for (id app in appsList) { + if (fullcount > 15) break; + + NSString *bundle = nil, *name = nil, *version = nil; + NSImage *icon = nil; + [self fillAppByUrl:(NSURL*)app bundle:&bundle name:&name version:&version icon:&icon]; + if (bundle && name) { + if ([bundle isEqualToString:defBundle] && [version isEqualToString:defVersion]) continue; + NSString *key = [[NSArray arrayWithObjects:bundle, name, nil] componentsJoinedByString:@"|"]; + if (!version) version = @""; + + NSMutableDictionary *versions = (NSMutableDictionary*)[data objectForKey:key]; + if (!versions) { + versions = [NSMutableDictionary dictionaryWithCapacity:2]; + [data setValue:versions forKey:key]; + } + if (![versions objectForKey:version]) { + [versions setValue:[NSArray arrayWithObjects:name, icon, app, nil] forKey:version]; + ++fullcount; + } + } + } + if (fullcount || defUrl) { + apps = [NSMutableArray arrayWithCapacity:fullcount]; + for (id key in data) { + NSMutableDictionary *val = (NSMutableDictionary*)[data objectForKey:key]; + for (id ver in val) { + NSArray *app = (NSArray*)[val objectForKey:ver]; + OpenWithApp *a = [[OpenWithApp alloc] init]; + NSString *fullname = (NSString*)[app objectAtIndex:0], *version = (NSString*)ver; + BOOL showVersion = ([val count] > 1); + if (!showVersion) { + NSError *error = NULL; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^\\d+\\.\\d+\\.\\d+(\\.\\d+)?$" options:NSRegularExpressionCaseInsensitive error:&error]; + showVersion = ![regex numberOfMatchesInString:version options:NSMatchingWithoutAnchoringBounds range:{0,[version length]}]; + } + if (showVersion) fullname = [[NSArray arrayWithObjects:fullname, @" (", version, @")", nil] componentsJoinedByString:@""]; + [a setFullname:fullname]; + [a setIcon:(NSImage*)[app objectAtIndex:1]]; + [a setApp:(NSURL*)[app objectAtIndex:2]]; + [apps addObject:a]; + [a release]; + } + } + } + [apps sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"fullname" ascending:YES]]]; + [appsList release]; + menu = nil; + } + return self; +} + +- (BOOL) popupAtX:(int)x andY:(int)y { + if (![apps count] && !defName) return NO; + menu = [[NSMenu alloc] initWithTitle:@"Open With"]; + + int index = 0; + if (defName) { + NSMenuItem *item = [menu insertItemWithTitle:[[NSArray arrayWithObjects:defName, @" (default)", nil] componentsJoinedByString:@""] action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++]; + if (defIcon) [item setImage:defIcon]; + [item setTarget:self]; + [menu insertItem:[NSMenuItem separatorItem] atIndex:index++]; + } + if ([apps count]) { + for (id a in apps) { + OpenWithApp *app = (OpenWithApp*)a; + NSMenuItem *item = [menu insertItemWithTitle:[a fullname] action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++]; + if ([app icon]) [item setImage:[app icon]]; + [item setTarget:self]; + } + [menu insertItem:[NSMenuItem separatorItem] atIndex:index++]; + } + NSMenuItem *item = [menu insertItemWithTitle:NSlang(lng_mac_choose_program_menu) action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++]; + [item setTarget:self]; + + [menu popUpMenuPositioningItem:nil atLocation:CGPointMake(x, y) inView:nil]; + + return YES; +} + +- (void) itemChosen:(id)sender { + NSArray *items = [menu itemArray]; + NSURL *url = nil; + for (int i = 0, l = [items count]; i < l; ++i) { + if ([items objectAtIndex:i] == sender) { + if (defName) i -= 2; + if (i < 0) { + url = defUrl; + } else if (i < int([apps count])) { + url = [(OpenWithApp*)[apps objectAtIndex:i] app]; + } + break; + } + } + if (url) { + [[NSWorkspace sharedWorkspace] openFile:toOpen withApplication:[url path]]; + } else if (!Platform::File::UnsafeShowOpenWith(NS2QString(toOpen))) { + Platform::File::UnsafeLaunch(NS2QString(toOpen)); + } +} + +- (void) dealloc { + [toOpen release]; + if (menu) [menu release]; + [super dealloc]; +} + +@end + +@interface NSURL(CompareUrls) + +- (BOOL) isEquivalent:(NSURL *)aURL; + +@end + +@implementation NSURL(CompareUrls) + +- (BOOL) isEquivalent:(NSURL *)aURL { + if ([self isEqual:aURL]) return YES; + if ([[self scheme] caseInsensitiveCompare:[aURL scheme]] != NSOrderedSame) return NO; + if ([[self host] caseInsensitiveCompare:[aURL host]] != NSOrderedSame) return NO; + if ([[self path] compare:[aURL path]] != NSOrderedSame) return NO; + if ([[self port] compare:[aURL port]] != NSOrderedSame) return NO; + if ([[self query] compare:[aURL query]] != NSOrderedSame) return NO; + return YES; +} + +@end + +@interface ChooseApplicationDelegate : NSObject { +} + +- (id) init:(NSArray *)recommendedApps withPanel:(NSOpenPanel *)creator withSelector:(NSPopUpButton *)menu withGood:(NSTextField *)goodLabel withBad:(NSTextField *)badLabel withIcon:(NSImageView *)badIcon withAccessory:(NSView *)acc; +- (BOOL) panel:(id)sender shouldEnableURL:(NSURL *)url; +- (void) panelSelectionDidChange:(id)sender; +- (void) menuDidClose; +- (void) dealloc; + +@end + +@implementation ChooseApplicationDelegate { + BOOL onlyRecommended; + NSArray *apps; + NSOpenPanel *panel; + NSPopUpButton *selector; + NSTextField *good, *bad; + NSImageView *icon; + NSString *recom; + NSView *accessory; +} + +- (id) init:(NSArray *)recommendedApps withPanel:(NSOpenPanel *)creator withSelector:(NSPopUpButton *)menu withGood:(NSTextField *)goodLabel withBad:(NSTextField *)badLabel withIcon:(NSImageView *)badIcon withAccessory:(NSView *)acc { + if (self = [super init]) { + onlyRecommended = YES; + recom = [NSlang(lng_mac_recommended_apps) copy]; + apps = recommendedApps; + panel = creator; + selector = menu; + good = goodLabel; + bad = badLabel; + icon = badIcon; + accessory = acc; + [selector setAction:@selector(menuDidClose)]; + } + return self; +} + +- (BOOL) isRecommended:(NSURL *)url { + if (apps) { + for (id app in apps) { + if ([(NSURL*)app isEquivalent:url]) { + return YES; + } + } + } + return NO; +} + +- (BOOL) panel:(id)sender shouldEnableURL:(NSURL *)url { + NSNumber *isDirectory; + if ([url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil] && isDirectory != nil && [isDirectory boolValue]) { + if (onlyRecommended) { + CFStringRef ext = CFURLCopyPathExtension((CFURLRef)url); + NSNumber *isPackage; + if ([url getResourceValue:&isPackage forKey:NSURLIsPackageKey error:nil] && isPackage != nil && [isPackage boolValue]) { + return [self isRecommended:url]; + } + } + return YES; + } + return NO; +} + +- (void) panelSelectionDidChange:(id)sender { + NSArray *urls = [panel URLs]; + if ([urls count]) { + if ([self isRecommended:[urls firstObject]]) { + [bad removeFromSuperview]; + [icon removeFromSuperview]; + [accessory addSubview:good]; + } else { + [good removeFromSuperview]; + [accessory addSubview:bad]; + [accessory addSubview:icon]; + } + } else { + [good removeFromSuperview]; + [bad removeFromSuperview]; + [icon removeFromSuperview]; + } +} + +- (void) menuDidClose { + onlyRecommended = [[[selector selectedItem] title] isEqualToString:recom]; + [self refreshPanelTable]; +} + +- (BOOL) refreshDataInViews: (NSArray*)subviews { + for (id view in subviews) { + NSString *cls = [view className]; + if ([cls isEqualToString:Q2NSString(strNeedToReload())]) { + [view reloadData]; + } else if ([cls isEqualToString:Q2NSString(strNeedToRefresh1())] || [cls isEqualToString:Q2NSString(strNeedToRefresh2())]) { + [view reloadData]; + return YES; + } else { + NSArray *next = [view subviews]; + if ([next count] && [self refreshDataInViews:next]) { + return YES; + } + } + } + return NO; +} + + +- (void) refreshPanelTable { + @autoreleasepool { + + [self refreshDataInViews:[[panel contentView] subviews]]; + [panel validateVisibleColumns]; + + } +} + +- (void) dealloc { + if (apps) { + [apps release]; + [recom release]; + } + [super dealloc]; +} + +@end + namespace Platform { namespace File { @@ -40,5 +396,195 @@ QString UrlToLocal(const QUrl &url) { return result; } +bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition) { + @autoreleasepool { + + NSString *file = Q2NSString(filepath); + @try { + OpenFileWithInterface *menu = [[[OpenFileWithInterface alloc] init:file] autorelease]; + auto r = QApplication::desktop()->screenGeometry(menuPosition); + auto x = menuPosition.x(); + auto y = r.y() + r.height() - menuPosition.y(); + return !![menu popupAtX:x andY:y]; + } + @catch (NSException *exception) { + } + @finally { + } + + } + return false; +} + +bool UnsafeShowOpenWith(const QString &filepath) { + @autoreleasepool { + + NSString *file = Q2NSString(filepath); + @try { + NSURL *url = [NSURL fileURLWithPath:file]; + NSString *ext = [url pathExtension]; + NSArray *names = [url pathComponents]; + NSString *name = [names count] ? [names lastObject] : @""; + NSArray *apps = (NSArray*)LSCopyApplicationURLsForURL(CFURLRef(url), kLSRolesAll); + + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + + NSRect fullRect = { { 0., 0. }, { st::macAccessoryWidth, st::macAccessoryHeight } }; + NSView *accessory = [[NSView alloc] initWithFrame:fullRect]; + + [accessory setAutoresizesSubviews:YES]; + + NSPopUpButton *selector = [[NSPopUpButton alloc] init]; + [accessory addSubview:selector]; + [selector addItemWithTitle:NSlang(lng_mac_recommended_apps)]; + [selector addItemWithTitle:NSlang(lng_mac_all_apps)]; + [selector sizeToFit]; + + NSTextField *enableLabel = [[NSTextField alloc] init]; + [accessory addSubview:enableLabel]; + [enableLabel setStringValue:NSlang(lng_mac_enable_filter)]; + [enableLabel setFont:[selector font]]; + [enableLabel setBezeled:NO]; + [enableLabel setDrawsBackground:NO]; + [enableLabel setEditable:NO]; + [enableLabel setSelectable:NO]; + [enableLabel sizeToFit]; + + NSRect selectorFrame = [selector frame], enableFrame = [enableLabel frame]; + enableFrame.size.width += st::macEnableFilterAdd; + enableFrame.origin.x = (fullRect.size.width - selectorFrame.size.width - enableFrame.size.width) / 2.; + selectorFrame.origin.x = (fullRect.size.width - selectorFrame.size.width + enableFrame.size.width) / 2.; + enableFrame.origin.y = fullRect.size.height - selectorFrame.size.height - st::macEnableFilterTop + (selectorFrame.size.height - enableFrame.size.height) / 2.; + selectorFrame.origin.y = fullRect.size.height - selectorFrame.size.height - st::macSelectorTop; + [enableLabel setFrame:enableFrame]; + [enableLabel setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; + [selector setFrame:selectorFrame]; + [selector setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; + + NSButton *button = [[NSButton alloc] init]; + [accessory addSubview:button]; + [button setButtonType:NSSwitchButton]; + [button setFont:[selector font]]; + [button setTitle:NSlang(lng_mac_always_open_with)]; + [button sizeToFit]; + NSRect alwaysRect = [button frame]; + alwaysRect.origin.x = (fullRect.size.width - alwaysRect.size.width) / 2; + alwaysRect.origin.y = selectorFrame.origin.y - alwaysRect.size.height - st::macAlwaysThisAppTop; + [button setFrame:alwaysRect]; + [button setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; +#ifdef OS_MAC_STORE + [button setHidden:YES]; +#endif // OS_MAC_STORE + NSTextField *goodLabel = [[NSTextField alloc] init]; + [goodLabel setStringValue:Q2NSString(lng_mac_this_app_can_open(lt_file, NS2QString(name)))]; + [goodLabel setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + [goodLabel setBezeled:NO]; + [goodLabel setDrawsBackground:NO]; + [goodLabel setEditable:NO]; + [goodLabel setSelectable:NO]; + [goodLabel sizeToFit]; + NSRect goodFrame = [goodLabel frame]; + goodFrame.origin.x = (fullRect.size.width - goodFrame.size.width) / 2.; + goodFrame.origin.y = alwaysRect.origin.y - goodFrame.size.height - st::macAppHintTop; + [goodLabel setFrame:goodFrame]; + + NSTextField *badLabel = [[NSTextField alloc] init]; + [badLabel setStringValue:Q2NSString(lng_mac_not_known_app(lt_file, NS2QString(name)))]; + [badLabel setFont:[goodLabel font]]; + [badLabel setBezeled:NO]; + [badLabel setDrawsBackground:NO]; + [badLabel setEditable:NO]; + [badLabel setSelectable:NO]; + [badLabel sizeToFit]; + NSImageView *badIcon = [[NSImageView alloc] init]; + NSImage *badImage = [NSImage imageNamed:NSImageNameCaution]; + [badIcon setImage:badImage]; + [badIcon setFrame:NSMakeRect(0, 0, st::macCautionIconSize, st::macCautionIconSize)]; + + NSRect badFrame = [badLabel frame], badIconFrame = [badIcon frame]; + badFrame.origin.x = (fullRect.size.width - badFrame.size.width + badIconFrame.size.width) / 2.; + badIconFrame.origin.x = (fullRect.size.width - badFrame.size.width - badIconFrame.size.width) / 2.; + badFrame.origin.y = alwaysRect.origin.y - badFrame.size.height - st::macAppHintTop; + badIconFrame.origin.y = badFrame.origin.y; + [badLabel setFrame:badFrame]; + [badIcon setFrame:badIconFrame]; + + [openPanel setAccessoryView:accessory]; + + ChooseApplicationDelegate *delegate = [[ChooseApplicationDelegate alloc] init:apps withPanel:openPanel withSelector:selector withGood:goodLabel withBad:badLabel withIcon:badIcon withAccessory:accessory]; + [openPanel setDelegate:delegate]; + + [openPanel setCanChooseDirectories:NO]; + [openPanel setCanChooseFiles:YES]; + [openPanel setAllowsMultipleSelection:NO]; + [openPanel setResolvesAliases:YES]; + [openPanel setTitle:NSlang(lng_mac_choose_app)]; + [openPanel setMessage:Q2NSString(lng_mac_choose_text(lt_file, NS2QString(name)))]; + + NSArray *appsPaths = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationDirectory inDomains:NSLocalDomainMask]; + if ([appsPaths count]) [openPanel setDirectoryURL:[appsPaths firstObject]]; + [openPanel beginWithCompletionHandler:^(NSInteger result){ + if (result == NSFileHandlingPanelOKButton) { + if ([[openPanel URLs] count] > 0) { + NSURL *app = [[openPanel URLs] objectAtIndex:0]; + NSString *path = [app path]; + if ([button state] == NSOnState) { + NSArray *UTIs = (NSArray *)UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, + (CFStringRef)ext, + nil); + for (NSString *UTI in UTIs) { + OSStatus result = LSSetDefaultRoleHandlerForContentType((CFStringRef)UTI, + kLSRolesAll, + (CFStringRef)[[NSBundle bundleWithPath:path] bundleIdentifier]); + DEBUG_LOG(("App Info: set default handler for '%1' UTI result: %2").arg(NS2QString(UTI)).arg(result)); + } + + [UTIs release]; + } + [[NSWorkspace sharedWorkspace] openFile:file withApplication:[app path]]; + } + } + [selector release]; + [button release]; + [enableLabel release]; + [goodLabel release]; + [badLabel release]; + [badIcon release]; + [accessory release]; + [delegate release]; + }]; + } + @catch (NSException *exception) { + [[NSWorkspace sharedWorkspace] openFile:file]; + } + @finally { + } + + } + + return YES; +} + +void UnsafeLaunch(const QString &filepath) { + @autoreleasepool { + + NSString *file = Q2NSString(filepath); + if ([[NSWorkspace sharedWorkspace] openFile:file] == NO) { + UnsafeShowOpenWith(filepath); + } + + } +} + +void UnsafeShowInFolder(const QString &filepath) { + auto folder = QFileInfo(filepath).absolutePath(); + + @autoreleasepool { + + [[NSWorkspace sharedWorkspace] selectFile:Q2NSString(filepath) inFileViewerRootedAtPath:Q2NSString(folder)]; + + } +} + } // namespace File } // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index fa9c80a061..7c8b545f81 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -293,7 +293,7 @@ void MainWindow::psUpdateWorkmode() { trayIcon = nullptr; } } - if (auto manager = Platform::Notifications::ManagerNative()) { + if (auto manager = Platform::Notifications::GetNativeManager()) { manager->updateDelegate(); } } diff --git a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h index 262ae4d183..a0466e4feb 100644 --- a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h +++ b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h @@ -36,10 +36,6 @@ inline bool SkipToast() { return false; } -inline Window::Notifications::Manager *GetManager() { - return GetNativeManager(); -} - class Manager : public Window::Notifications::NativeManager { public: Manager(); @@ -59,5 +55,9 @@ private: }; +inline Window::Notifications::Manager *GetManager() { + return GetNativeManager(); +} + } // namespace Notifications } // namespace Platform diff --git a/Telegram/SourceFiles/platform/platform_file_utilities.h b/Telegram/SourceFiles/platform/platform_file_utilities.h index 6767cd7105..593ab286da 100644 --- a/Telegram/SourceFiles/platform/platform_file_utilities.h +++ b/Telegram/SourceFiles/platform/platform_file_utilities.h @@ -20,20 +20,29 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once -#include "ui/filedialog.h" +#include "core/file_utilities.h" namespace Platform { namespace File { QString UrlToLocal(const QUrl &url); +// All these functions may enter a nested event loop. Use with caution. +void UnsafeOpenEmailLink(const QString &email); +bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition); +bool UnsafeShowOpenWith(const QString &filepath); +void UnsafeLaunch(const QString &filepath); +void UnsafeShowInFolder(const QString &filepath); + +void PostprocessDownloaded(const QString &filepath); + } // namespace File namespace FileDialog { -bool Supported(); +void InitLastPath(); -bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile); +bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile = QString()); } // namespace FileDialog } // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/audio_win.cpp b/Telegram/SourceFiles/platform/win/audio_win.cpp index 5b1edb6c35..f388ea0f27 100644 --- a/Telegram/SourceFiles/platform/win/audio_win.cpp +++ b/Telegram/SourceFiles/platform/win/audio_win.cpp @@ -180,7 +180,6 @@ void DeInit() { if (WasCoInitialized) { CoUninitialize(); } - AUDCLNT_E_NOT_INITIALIZED; } } // namespace Audio diff --git a/Telegram/SourceFiles/platform/win/file_utilities_win.cpp b/Telegram/SourceFiles/platform/win/file_utilities_win.cpp index 516963352b..a3a716c86a 100644 --- a/Telegram/SourceFiles/platform/win/file_utilities_win.cpp +++ b/Telegram/SourceFiles/platform/win/file_utilities_win.cpp @@ -21,3 +21,401 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "platform/win/file_utilities_win.h" +#include "mainwindow.h" +#include "localstorage.h" +#include "platform/win/windows_dlls.h" +#include "lang.h" + +#include +#include + +HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &, int hbitmapFormat); + +namespace Platform { +namespace File { +namespace { + +class OpenWithApp { +public: + OpenWithApp(const QString &name, IAssocHandler *handler, HBITMAP icon = nullptr) + : _name(name) + , _handler(handler) + , _icon(icon) { + } + OpenWithApp(OpenWithApp &&other) + : _name(base::take(other._name)) + , _handler(base::take(other._handler)) + , _icon(base::take(other._icon)) { + } + OpenWithApp &operator=(OpenWithApp &&other) { + _name = base::take(other._name); + _icon = base::take(other._icon); + _handler = base::take(other._handler); + return (*this); + } + + OpenWithApp(const OpenWithApp &other) = delete; + OpenWithApp &operator=(const OpenWithApp &other) = delete; + + ~OpenWithApp() { + if (_icon) { + DeleteBitmap(_icon); + } + if (_handler) { + _handler->Release(); + } + } + + const QString &name() const { + return _name; + } + HBITMAP icon() const { + return _icon; + } + IAssocHandler *handler() const { + return _handler; + } + +private: + QString _name; + IAssocHandler *_handler = nullptr; + HBITMAP _icon = nullptr; + +}; + +HBITMAP IconToBitmap(LPWSTR icon, int iconindex) { + if (!icon) return 0; + WCHAR tmpIcon[4096]; + if (icon[0] == L'@' && SUCCEEDED(SHLoadIndirectString(icon, tmpIcon, 4096, 0))) { + icon = tmpIcon; + } + int32 w = GetSystemMetrics(SM_CXSMICON), h = GetSystemMetrics(SM_CYSMICON); + + HICON ico = ExtractIcon(0, icon, iconindex); + if (!ico) { + if (!iconindex) { // try to read image + QImage img(QString::fromWCharArray(icon)); + if (!img.isNull()) { + return qt_pixmapToWinHBITMAP(App::pixmapFromImageInPlace(img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)), /* HBitmapAlpha */ 2); + } + } + return 0; + } + + HDC screenDC = GetDC(0), hdc = CreateCompatibleDC(screenDC); + HBITMAP result = CreateCompatibleBitmap(screenDC, w, h); + HGDIOBJ was = SelectObject(hdc, result); + DrawIconEx(hdc, 0, 0, ico, w, h, 0, NULL, DI_NORMAL); + SelectObject(hdc, was); + DeleteDC(hdc); + ReleaseDC(0, screenDC); + + DestroyIcon(ico); + + return (HBITMAP)CopyImage(result, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_CREATEDIBSECTION); +} + +} // namespace + +void UnsafeOpenEmailLink(const QString &email) { + auto url = QUrl(qstr("mailto:") + email); + if (!QDesktopServices::openUrl(url)) { + auto wstringUrl = url.toString(QUrl::FullyEncoded).toStdWString(); + if (Dlls::SHOpenWithDialog) { + OPENASINFO info; + info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC | OAIF_FILE_IS_URI | OAIF_URL_PROTOCOL; + info.pcszClass = NULL; + info.pcszFile = wstringUrl.c_str(); + Dlls::SHOpenWithDialog(0, &info); + } else if (Dlls::OpenAs_RunDLL) { + Dlls::OpenAs_RunDLL(0, 0, wstringUrl.c_str(), SW_SHOWNORMAL); + } else { + ShellExecute(0, L"open", wstringUrl.c_str(), 0, 0, SW_SHOWNORMAL); + } + } +} + +bool UnsafeShowOpenWithDropdown(const QString &filepath, QPoint menuPosition) { + auto window = App::wnd(); + if (!window) return false; + + auto parentHWND = window->psHwnd(); + auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString(); + + auto result = false; + std::vector handlers; + IShellItem* pItem = nullptr; + if (SUCCEEDED(Dlls::SHCreateItemFromParsingName(wstringPath.c_str(), nullptr, IID_PPV_ARGS(&pItem)))) { + IEnumAssocHandlers *assocHandlers = nullptr; + if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_EnumAssocHandlers, IID_PPV_ARGS(&assocHandlers)))) { + HRESULT hr = S_FALSE; + do { + IAssocHandler *handler = nullptr; + ULONG ulFetched = 0; + hr = assocHandlers->Next(1, &handler, &ulFetched); + if (FAILED(hr) || hr == S_FALSE || !ulFetched) break; + + LPWSTR name = 0; + if (SUCCEEDED(handler->GetUIName(&name))) { + LPWSTR icon = 0; + int iconindex = 0; + if (SUCCEEDED(handler->GetIconLocation(&icon, &iconindex)) && icon) { + handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler, IconToBitmap(icon, iconindex))); + CoTaskMemFree(icon); + } else { + handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler)); + } + CoTaskMemFree(name); + } else { + handler->Release(); + } + } while (hr != S_FALSE); + assocHandlers->Release(); + } + + if (!handlers.empty()) { + HMENU menu = CreatePopupMenu(); + std::sort(handlers.begin(), handlers.end(), [](const OpenWithApp &a, const OpenWithApp &b) { + return a.name() < b.name(); + }); + for (int32 i = 0, l = handlers.size(); i < l; ++i) { + MENUITEMINFO menuInfo = { 0 }; + menuInfo.cbSize = sizeof(menuInfo); + menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID; + menuInfo.fType = MFT_STRING; + menuInfo.wID = i + 1; + if (auto icon = handlers[i].icon()) { + menuInfo.fMask |= MIIM_BITMAP; + menuInfo.hbmpItem = icon; + } + + auto name = handlers[i].name(); + if (name.size() > 512) name = name.mid(0, 512); + WCHAR nameArr[1024]; + name.toWCharArray(nameArr); + nameArr[name.size()] = 0; + menuInfo.dwTypeData = nameArr; + InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo); + } + MENUITEMINFO sepInfo = { 0 }; + sepInfo.cbSize = sizeof(sepInfo); + sepInfo.fMask = MIIM_STRING | MIIM_DATA; + sepInfo.fType = MFT_SEPARATOR; + InsertMenuItem(menu, GetMenuItemCount(menu), true, &sepInfo); + + MENUITEMINFO menuInfo = { 0 }; + menuInfo.cbSize = sizeof(menuInfo); + menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID; + menuInfo.fType = MFT_STRING; + menuInfo.wID = handlers.size() + 1; + + QString name = lang(lng_wnd_choose_program_menu); + if (name.size() > 512) name = name.mid(0, 512); + WCHAR nameArr[1024]; + name.toWCharArray(nameArr); + nameArr[name.size()] = 0; + menuInfo.dwTypeData = nameArr; + InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo); + + int sel = TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, menuPosition.x(), menuPosition.y(), 0, parentHWND, 0); + DestroyMenu(menu); + + if (sel > 0) { + if (sel <= handlers.size()) { + IDataObject *dataObj = 0; + if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_DataObject, IID_PPV_ARGS(&dataObj))) && dataObj) { + handlers[sel - 1].handler()->Invoke(dataObj); + dataObj->Release(); + result = true; + } + } + } else { + result = true; + } + } + + pItem->Release(); + } + return result; +} + +bool UnsafeShowOpenWith(const QString &filepath) { + auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString(); + if (Dlls::SHOpenWithDialog) { + OPENASINFO info; + info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC; + info.pcszClass = NULL; + info.pcszFile = wstringPath.c_str(); + Dlls::SHOpenWithDialog(0, &info); + return true; + } else if (Dlls::OpenAs_RunDLL) { + Dlls::OpenAs_RunDLL(0, 0, wstringPath.c_str(), SW_SHOWNORMAL); + return true; + } + return false; +} + +void UnsafeLaunch(const QString &filepath) { + auto wstringPath = QDir::toNativeSeparators(filepath).toStdWString(); + ShellExecute(0, L"open", wstringPath.c_str(), 0, 0, SW_SHOWNORMAL); +} + +void UnsafeShowInFolder(const QString &filepath) { + auto pathEscaped = QDir::toNativeSeparators(filepath).replace('"', qsl("\"\"")); + auto wstringParam = (qstr("/select,") + pathEscaped).toStdWString(); + ShellExecute(0, 0, L"explorer", wstringParam.c_str(), 0, SW_SHOWNORMAL); +} + +void PostprocessDownloaded(const QString &filepath) { + auto wstringZoneFile = QDir::toNativeSeparators(filepath).toStdWString() + L":Zone.Identifier"; + auto f = CreateFile(wstringZoneFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (f == INVALID_HANDLE_VALUE) { // :( + return; + } + + const char data[] = "[ZoneTransfer]\r\nZoneId=3\r\n"; + + DWORD written = 0; + BOOL result = WriteFile(f, data, sizeof(data), &written, NULL); + CloseHandle(f); + + if (!result || written != sizeof(data)) { // :( + return; + } +} + +} // namespace File + +namespace FileDialog { +namespace { + +using Type = ::FileDialog::internal::Type; + +} // namespace + +void InitLastPath() { + // hack to restore previous dir without hurting performance + QSettings settings(QSettings::UserScope, qstr("QtProject")); + settings.beginGroup(qstr("Qt")); + QByteArray sd = settings.value(qstr("filedialog")).toByteArray(); + QDataStream stream(&sd, QIODevice::ReadOnly); + if (!stream.atEnd()) { + int version = 3, _QFileDialogMagic = 190; + QByteArray splitterState; + QByteArray headerData; + QList bookmarks; + QStringList history; + QString currentDirectory; + qint32 marker; + qint32 v; + qint32 viewMode; + stream >> marker; + stream >> v; + if (marker == _QFileDialogMagic && v == version) { + stream >> splitterState + >> bookmarks + >> history + >> currentDirectory + >> headerData + >> viewMode; + cSetDialogLastPath(currentDirectory); + } + } + + if (cDialogHelperPath().isEmpty()) { + QDir temppath(cWorkingDir() + "tdata/tdummy/"); + if (!temppath.exists()) { + temppath.mkpath(temppath.absolutePath()); + } + if (temppath.exists()) { + cSetDialogHelperPath(temppath.absolutePath()); + } + } +} + +bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile) { + if (cDialogLastPath().isEmpty()) { + Platform::FileDialog::InitLastPath(); + } + + // A hack for fast dialog create. There was some huge performance problem + // if we open a file dialog in some folder with a large amount of files. + // Some internal Qt watcher iterated over all of them, querying some information + // that forced file icon and maybe other properties being resolved and this was + // a blocking operation. + auto helperPath = cDialogHelperPathFinal(); + QFileDialog dialog(App::wnd() ? App::wnd()->filedialogParent() : 0, caption, helperPath, filter); + + dialog.setModal(true); + if (type == Type::ReadFile || type == Type::ReadFiles) { + dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + } else if (type == Type::ReadFolder) { // save dir + dialog.setAcceptMode(QFileDialog::AcceptOpen); + + // We use "obsolete" value ::DirectoryOnly instead of ::Directory + ::ShowDirsOnly + // because in Windows XP native dialog this one works, while the "preferred" one + // shows a native file choose dialog where you can't choose a directory, only open one. + dialog.setFileMode(QFileDialog::DirectoryOnly); + dialog.setOption(QFileDialog::ShowDirsOnly); + } else { // save file + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setAcceptMode(QFileDialog::AcceptSave); + } + dialog.show(); + + auto realLastPath = ([startFile] { + // If we're given some non empty path containing a folder - use it. + if (!startFile.isEmpty() && (startFile.indexOf('/') >= 0 || startFile.indexOf('\\') >= 0)) { + return QFileInfo(startFile).dir().absolutePath(); + } + return cDialogLastPath(); + })(); + if (realLastPath.isEmpty() || realLastPath.endsWith(qstr("/tdummy"))) { + realLastPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + } + dialog.setDirectory(realLastPath); + + if (type == Type::WriteFile) { + auto toSelect = startFile; + auto lastSlash = toSelect.lastIndexOf('/'); + if (lastSlash >= 0) { + toSelect = toSelect.mid(lastSlash + 1); + } + auto lastBackSlash = toSelect.lastIndexOf('\\'); + if (lastBackSlash >= 0) { + toSelect = toSelect.mid(lastBackSlash + 1); + } + dialog.selectFile(toSelect); + } + + int res = dialog.exec(); + + if (type != Type::ReadFolder) { + // Save last used directory for all queries except directory choosing. + auto path = dialog.directory().absolutePath(); + if (path != cDialogLastPath()) { + cSetDialogLastPath(path); + Local::writeUserSettings(); + } + } + + if (res == QDialog::Accepted) { + if (type == Type::ReadFiles) { + files = dialog.selectedFiles(); + } else { + files = dialog.selectedFiles().mid(0, 1); + } + if (type == Type::ReadFile || type == Type::ReadFiles) { + remoteContent = dialog.selectedRemoteContent(); + } + return true; + } + + files = QStringList(); + remoteContent = QByteArray(); + return false; +} + +} // namespace FileDialog +} // namespace Platform \ No newline at end of file diff --git a/Telegram/SourceFiles/platform/win/file_utilities_win.h b/Telegram/SourceFiles/platform/win/file_utilities_win.h index 8010873049..9fe3d7ef51 100644 --- a/Telegram/SourceFiles/platform/win/file_utilities_win.h +++ b/Telegram/SourceFiles/platform/win/file_utilities_win.h @@ -30,16 +30,4 @@ inline QString UrlToLocal(const QUrl &url) { } } // namespace File - -namespace FileDialog { - -inline bool Supported() { - return false; -} - -inline bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, ::FileDialog::internal::Type type, QString startFile) { - return false; -} - -} // namespace FileDialog } // namespace Platform diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index f9f09b7ced..60139f9940 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -26,7 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "profile/profile_cover_drop_area.h" #include "profile/profile_userpic_button.h" #include "ui/widgets/buttons.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "ui/widgets/labels.h" #include "observer_peer.h" #include "boxes/confirmbox.h" diff --git a/Telegram/SourceFiles/profile/profile_cover.h b/Telegram/SourceFiles/profile/profile_cover.h index 03db54de21..8223afc93a 100644 --- a/Telegram/SourceFiles/profile/profile_cover.h +++ b/Telegram/SourceFiles/profile/profile_cover.h @@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "core/observer.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" namespace style { struct RoundButton; diff --git a/Telegram/SourceFiles/pspecific_linux.cpp b/Telegram/SourceFiles/pspecific_linux.cpp index ba903664b3..09401a00bb 100644 --- a/Telegram/SourceFiles/pspecific_linux.cpp +++ b/Telegram/SourceFiles/pspecific_linux.cpp @@ -23,7 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "application.h" #include "mainwidget.h" #include "mainwindow.h" - +#include "platform/linux/file_utilities_linux.h" #include "localstorage.h" #include @@ -38,32 +38,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include using namespace Platform; +using Platform::File::internal::EscapeShell; namespace { -QByteArray escapeShell(const QByteArray &str) { - QByteArray result; - const char *b = str.constData(), *e = str.constEnd(); - for (const char *ch = b; ch != e; ++ch) { - if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') { - if (result.isEmpty()) { - result.reserve(str.size() * 2); - } - if (ch > b) { - result.append(b, ch - b); - } - result.append('\\'); - b = ch; - } - } - if (result.isEmpty()) return str; - - if (e > b) { - result.append(b, e - b); - } - return result; -} - class _PsEventFilter : public QAbstractNativeEventFilter { public: bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) { @@ -129,7 +107,7 @@ QStringList addr2linestr(uint64 *addresses, int count) { if (!count) return result; result.reserve(count); - QByteArray cmd = "addr2line -e " + escapeShell(QFile::encodeName(cExeDir() + cExeName())); + QByteArray cmd = "addr2line -e " + EscapeShell(QFile::encodeName(cExeDir() + cExeName())); for (int i = 0; i < count; ++i) { if (addresses[i]) { cmd += qsl(" 0x%1").arg(addresses[i], 0, 16).toUtf8(); @@ -387,18 +365,6 @@ int psFixPrevious() { return 0; } -void psPostprocessFile(const QString &name) { -} - -void psOpenFile(const QString &name, bool openWith) { - QDesktopServices::openUrl(QUrl::fromLocalFile(name)); -} - -void psShowInFolder(const QString &name) { - Ui::hideLayer(true); - system(("xdg-open " + escapeShell(QFile::encodeName(QFileInfo(name).absoluteDir().absolutePath()))).constData()); -} - namespace Platform { void start() { @@ -503,7 +469,7 @@ void psRegisterCustomScheme() { s << "Version=1.0\n"; s << "Name=Telegram Desktop\n"; s << "Comment=Official desktop version of Telegram messaging app\n"; - s << "Exec=" << escapeShell(QFile::encodeName(cExeDir() + cExeName())) << " -- %u\n"; + s << "Exec=" << EscapeShell(QFile::encodeName(cExeDir() + cExeName())) << " -- %u\n"; s << "Icon=telegram\n"; s << "Terminal=false\n"; s << "StartupWMClass=Telegram\n"; @@ -512,11 +478,11 @@ void psRegisterCustomScheme() { s << "MimeType=x-scheme-handler/tg;\n"; f.close(); - if (_psRunCommand("desktop-file-install --dir=" + escapeShell(QFile::encodeName(home + qsl(".local/share/applications"))) + " --delete-original " + escapeShell(QFile::encodeName(file)))) { + if (_psRunCommand("desktop-file-install --dir=" + EscapeShell(QFile::encodeName(home + qsl(".local/share/applications"))) + " --delete-original " + EscapeShell(QFile::encodeName(file)))) { DEBUG_LOG(("App Info: removing old .desktop file")); QFile(qsl("%1.local/share/applications/telegram.desktop").arg(home)).remove(); - _psRunCommand("update-desktop-database " + escapeShell(QFile::encodeName(home + qsl(".local/share/applications")))); + _psRunCommand("update-desktop-database " + EscapeShell(QFile::encodeName(home + qsl(".local/share/applications")))); _psRunCommand("xdg-mime default telegramdesktop.desktop x-scheme-handler/tg"); } } else { @@ -526,7 +492,7 @@ void psRegisterCustomScheme() { #endif // !TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION DEBUG_LOG(("App Info: registerting for Gnome")); - if (_psRunCommand("gconftool-2 -t string -s /desktop/gnome/url-handlers/tg/command " + escapeShell(escapeShell(QFile::encodeName(cExeDir() + cExeName())) + " -- %s"))) { + if (_psRunCommand("gconftool-2 -t string -s /desktop/gnome/url-handlers/tg/command " + EscapeShell(EscapeShell(QFile::encodeName(cExeDir() + cExeName())) + " -- %s"))) { _psRunCommand("gconftool-2 -t bool -s /desktop/gnome/url-handlers/tg/needs_terminal false"); _psRunCommand("gconftool-2 -t bool -s /desktop/gnome/url-handlers/tg/enabled true"); } @@ -547,7 +513,7 @@ void psRegisterCustomScheme() { QTextStream s(&f); s.setCodec("UTF-8"); s << "[Protocol]\n"; - s << "exec=" << QFile::decodeName(escapeShell(QFile::encodeName(cExeDir() + cExeName()))) << " -- %u\n"; + s << "exec=" << QFile::decodeName(EscapeShell(QFile::encodeName(cExeDir() + cExeName()))) << " -- %u\n"; s << "protocol=tg\n"; s << "input=none\n"; s << "output=none\n"; diff --git a/Telegram/SourceFiles/pspecific_linux.h b/Telegram/SourceFiles/pspecific_linux.h index 6c0168d12d..0117f4a07f 100644 --- a/Telegram/SourceFiles/pspecific_linux.h +++ b/Telegram/SourceFiles/pspecific_linux.h @@ -63,12 +63,6 @@ int psFixPrevious(); void psExecUpdater(); void psExecTelegram(const QString &arg = QString()); -bool psShowOpenWithMenu(int x, int y, const QString &file); - -void psPostprocessFile(const QString &name); -void psOpenFile(const QString &name, bool openWith = false); -void psShowInFolder(const QString &name); - QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); diff --git a/Telegram/SourceFiles/pspecific_mac.cpp b/Telegram/SourceFiles/pspecific_mac.cpp index b2e9568422..67356aff9e 100644 --- a/Telegram/SourceFiles/pspecific_mac.cpp +++ b/Telegram/SourceFiles/pspecific_mac.cpp @@ -384,21 +384,6 @@ int psFixPrevious() { return 0; } -bool psShowOpenWithMenu(int x, int y, const QString &file) { - return objc_showOpenWithMenu(x, y, file); -} - -void psPostprocessFile(const QString &name) { -} - -void psOpenFile(const QString &name, bool openWith) { - objc_openFile(name, openWith); -} - -void psShowInFolder(const QString &name) { - objc_showInFinder(name, QFileInfo(name).absolutePath()); -} - namespace Platform { void start() { @@ -413,7 +398,7 @@ void finish() { } bool TransparentWindowsSupported(QPoint globalPosition) { - return true; + return true; } namespace ThirdParty { @@ -468,35 +453,20 @@ bool psLaunchMaps(const LocationCoords &coords) { QString strNotificationAboutThemeChange() { const uint32 letters[] = { 0xE9005541, 0x5600DC70, 0x88001570, 0xF500D86C, 0x8100E165, 0xEE005949, 0x2900526E, 0xAE00FB74, 0x96000865, 0x7000CD72, 0x3B001566, 0x5F007361, 0xAE00B663, 0x74009A65, 0x29003054, 0xC6002668, 0x98003865, 0xFA00336D, 0xA3007A65, 0x93001443, 0xBB007868, 0xE100E561, 0x3500366E, 0xC0007A67, 0x0200CA65, 0xBE00DF64, 0xE300BB4E, 0x2900D26F, 0xD500D374, 0xE900E269, 0x86008F66, 0xC4006669, 0x1C00A863, 0xE600A761, 0x8E00EE74, 0xB300B169, 0xCF00B36F, 0xE600D36E }; - return strMakeFromLetters(letters, sizeof(letters) / sizeof(letters[0])); + return strMakeFromLetters(letters); } QString strNotificationAboutScreenLocked() { const uint32 letters[] = { 0x22008263, 0x0800DB6F, 0x45004F6D, 0xCC00972E, 0x0E00A861, 0x9700D970, 0xA100D570, 0x8900686C, 0xB300B365, 0xFE00DE2E, 0x76009B73, 0xFA00BF63, 0xE000A772, 0x9C009F65, 0x4E006065, 0xD900426E, 0xB7007849, 0x64006473, 0x6700824C, 0xE300706F, 0x7C00A063, 0x8F00D76B, 0x04001C65, 0x1C00A664 }; - return strMakeFromLetters(letters, base::array_size(letters)); + return strMakeFromLetters(letters); } QString strNotificationAboutScreenUnlocked() { const uint32 letters[] = { 0x9200D763, 0xC8003C6F, 0xD2003F6D, 0x6000012E, 0x36004061, 0x4400E570, 0xA500BF70, 0x2E00796C, 0x4A009E65, 0x2E00612E, 0xC8001D73, 0x57002263, 0xF0005872, 0x49000765, 0xE5008D65, 0xE600D76E, 0xE8007049, 0x19005C73, 0x34009455, 0xB800B36E, 0xF300CA6C, 0x4C00806F, 0x5300A763, 0xD1003B6B, 0x63003565, 0xF800F264 }; - return strMakeFromLetters(letters, base::array_size(letters)); + return strMakeFromLetters(letters); } QString strStyleOfInterface() { const uint32 letters[] = { 0xEF004041, 0x4C007F70, 0x1F007A70, 0x9E00A76C, 0x8500D165, 0x2E003749, 0x7B00526E, 0x3400E774, 0x3C00FA65, 0x6200B172, 0xF7001D66, 0x0B002961, 0x71008C63, 0x86005465, 0xA3006F53, 0x11006174, 0xCD001779, 0x8200556C, 0x6C009B65 }; - return strMakeFromLetters(letters, sizeof(letters) / sizeof(letters[0])); -} - -QString strNeedToReload() { - const uint32 letters[] = { 0x82007746, 0xBB00C649, 0x7E00235F, 0x9A00FE54, 0x4C004542, 0x91001772, 0x8A00D76F, 0xC700B977, 0x7F005F73, 0x34003665, 0x2300D572, 0x72002E54, 0x18001461, 0x14004A62, 0x5100CC6C, 0x83002365, 0x5A002C56, 0xA5004369, 0x26004265, 0x0D006577 }; - return strMakeFromLetters(letters, sizeof(letters) / sizeof(letters[0])); -} - -QString strNeedToRefresh1() { - const uint32 letters[] = { 0xEF006746, 0xF500CE49, 0x1500715F, 0x95001254, 0x3A00CB4C, 0x17009469, 0xB400DA73, 0xDE00C574, 0x9200EC56, 0x3C00A669, 0xFD00D865, 0x59000977 }; - return strMakeFromLetters(letters, sizeof(letters) / sizeof(letters[0])); -} - -QString strNeedToRefresh2() { - const uint32 letters[] = { 0x8F001546, 0xAF007A49, 0xB8002B5F, 0x1A000B54, 0x0D003E49, 0xE0003663, 0x4900796F, 0x0500836E, 0x9A00D156, 0x5E00FF69, 0x5900C765, 0x3D00D177 }; - return strMakeFromLetters(letters, sizeof(letters) / sizeof(letters[0])); + return strMakeFromLetters(letters); } diff --git a/Telegram/SourceFiles/pspecific_mac.h b/Telegram/SourceFiles/pspecific_mac.h index f5f2498106..79a051ac7d 100644 --- a/Telegram/SourceFiles/pspecific_mac.h +++ b/Telegram/SourceFiles/pspecific_mac.h @@ -68,10 +68,6 @@ void psExecTelegram(const QString &crashreport = QString()); bool psShowOpenWithMenu(int x, int y, const QString &file); -void psPostprocessFile(const QString &name); -void psOpenFile(const QString &name, bool openWith = false); -void psShowInFolder(const QString &name); - QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); @@ -111,8 +107,5 @@ QString strNotificationAboutThemeChange(); QString strNotificationAboutScreenLocked(); QString strNotificationAboutScreenUnlocked(); QString strStyleOfInterface(); -QString strNeedToReload(); -QString strNeedToRefresh1(); -QString strNeedToRefresh2(); bool psLaunchMaps(const LocationCoords &coords); diff --git a/Telegram/SourceFiles/pspecific_mac_p.h b/Telegram/SourceFiles/pspecific_mac_p.h index 0735122043..9691b7e90c 100644 --- a/Telegram/SourceFiles/pspecific_mac_p.h +++ b/Telegram/SourceFiles/pspecific_mac_p.h @@ -31,10 +31,6 @@ void objc_outputDebugString(const QString &str); bool objc_idleSupported(); bool objc_idleTime(TimeMs &idleTime); -bool objc_showOpenWithMenu(int x, int y, const QString &file); - -void objc_showInFinder(const QString &file, const QString &path); -void objc_openFile(const QString &file, bool openwith); void objc_start(); void objc_finish(); bool objc_execUpdater(); diff --git a/Telegram/SourceFiles/pspecific_mac_p.mm b/Telegram/SourceFiles/pspecific_mac_p.mm index ba6de4b13e..478e7d9670 100644 --- a/Telegram/SourceFiles/pspecific_mac_p.mm +++ b/Telegram/SourceFiles/pspecific_mac_p.mm @@ -325,516 +325,6 @@ bool objc_idleTime(TimeMs &idleTime) { // taken from https://github.com/trueinte return true; } -@interface OpenWithApp : NSObject { - NSString *fullname; - NSURL *app; - NSImage *icon; -} -@property (nonatomic, retain) NSString *fullname; -@property (nonatomic, retain) NSURL *app; -@property (nonatomic, retain) NSImage *icon; -@end - -@implementation OpenWithApp -@synthesize fullname, app, icon; - -- (void) dealloc { - [fullname release]; - [app release]; - [icon release]; - [super dealloc]; -} - -@end - -@interface OpenFileWithInterface : NSObject { -} - -- (id) init:(NSString *)file; -- (BOOL) popupAtX:(int)x andY:(int)y; -- (void) itemChosen:(id)sender; -- (void) dealloc; - -@end - -@implementation OpenFileWithInterface { - NSString *toOpen; - - NSURL *defUrl; - NSString *defBundle, *defName, *defVersion; - NSImage *defIcon; - - NSMutableArray *apps; - - NSMenu *menu; -} - -- (void) fillAppByUrl:(NSURL*)url bundle:(NSString**)bundle name:(NSString**)name version:(NSString**)version icon:(NSImage**)icon { - NSBundle *b = [NSBundle bundleWithURL:url]; - if (b) { - NSString *path = [url path]; - *name = [[NSFileManager defaultManager] displayNameAtPath: path]; - if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleDisplayName"]; - if (!*name) *name = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleName"]; - if (*name) { - *bundle = [b bundleIdentifier]; - if (bundle) { - *version = (NSString*)[b objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - *icon = [[NSWorkspace sharedWorkspace] iconForFile: path]; - if (*icon && [*icon isValid]) [*icon setSize: CGSizeMake(16., 16.)]; - return; - } - } - } - *bundle = *name = *version = nil; - *icon = nil; -} - -- (id) init:(NSString*)file { - toOpen = [file retain]; - if (self = [super init]) { - NSURL *url = [NSURL fileURLWithPath:file]; - defUrl = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:url]; - if (defUrl) { - [self fillAppByUrl:defUrl bundle:&defBundle name:&defName version:&defVersion icon:&defIcon]; - if (!defBundle || !defName) { - defUrl = nil; - } - } - NSArray *appsList = (NSArray*)LSCopyApplicationURLsForURL(CFURLRef(url), kLSRolesAll); - NSMutableDictionary *data = [NSMutableDictionary dictionaryWithCapacity:16]; - int fullcount = 0; - for (id app in appsList) { - if (fullcount > 15) break; - - NSString *bundle = nil, *name = nil, *version = nil; - NSImage *icon = nil; - [self fillAppByUrl:(NSURL*)app bundle:&bundle name:&name version:&version icon:&icon]; - if (bundle && name) { - if ([bundle isEqualToString:defBundle] && [version isEqualToString:defVersion]) continue; - NSString *key = [[NSArray arrayWithObjects:bundle, name, nil] componentsJoinedByString:@"|"]; - if (!version) version = @""; - - NSMutableDictionary *versions = (NSMutableDictionary*)[data objectForKey:key]; - if (!versions) { - versions = [NSMutableDictionary dictionaryWithCapacity:2]; - [data setValue:versions forKey:key]; - } - if (![versions objectForKey:version]) { - [versions setValue:[NSArray arrayWithObjects:name, icon, app, nil] forKey:version]; - ++fullcount; - } - } - } - if (fullcount || defUrl) { - apps = [NSMutableArray arrayWithCapacity:fullcount]; - for (id key in data) { - NSMutableDictionary *val = (NSMutableDictionary*)[data objectForKey:key]; - for (id ver in val) { - NSArray *app = (NSArray*)[val objectForKey:ver]; - OpenWithApp *a = [[OpenWithApp alloc] init]; - NSString *fullname = (NSString*)[app objectAtIndex:0], *version = (NSString*)ver; - BOOL showVersion = ([val count] > 1); - if (!showVersion) { - NSError *error = NULL; - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^\\d+\\.\\d+\\.\\d+(\\.\\d+)?$" options:NSRegularExpressionCaseInsensitive error:&error]; - showVersion = ![regex numberOfMatchesInString:version options:NSMatchingWithoutAnchoringBounds range:{0,[version length]}]; - } - if (showVersion) fullname = [[NSArray arrayWithObjects:fullname, @" (", version, @")", nil] componentsJoinedByString:@""]; - [a setFullname:fullname]; - [a setIcon:(NSImage*)[app objectAtIndex:1]]; - [a setApp:(NSURL*)[app objectAtIndex:2]]; - [apps addObject:a]; - [a release]; - } - } - } - [apps sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"fullname" ascending:YES]]]; - [appsList release]; - menu = nil; - } - return self; -} - -- (BOOL) popupAtX:(int)x andY:(int)y { - if (![apps count] && !defName) return NO; - menu = [[NSMenu alloc] initWithTitle:@"Open With"]; - - int index = 0; - if (defName) { - NSMenuItem *item = [menu insertItemWithTitle:[[NSArray arrayWithObjects:defName, @" (default)", nil] componentsJoinedByString:@""] action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++]; - if (defIcon) [item setImage:defIcon]; - [item setTarget:self]; - [menu insertItem:[NSMenuItem separatorItem] atIndex:index++]; - } - if ([apps count]) { - for (id a in apps) { - OpenWithApp *app = (OpenWithApp*)a; - NSMenuItem *item = [menu insertItemWithTitle:[a fullname] action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++]; - if ([app icon]) [item setImage:[app icon]]; - [item setTarget:self]; - } - [menu insertItem:[NSMenuItem separatorItem] atIndex:index++]; - } - NSMenuItem *item = [menu insertItemWithTitle:NSlang(lng_mac_choose_program_menu) action:@selector(itemChosen:) keyEquivalent:@"" atIndex:index++]; - [item setTarget:self]; - - [menu popUpMenuPositioningItem:nil atLocation:CGPointMake(x, y) inView:nil]; - - return YES; -} - -- (void) itemChosen:(id)sender { - NSArray *items = [menu itemArray]; - NSURL *url = nil; - for (int i = 0, l = [items count]; i < l; ++i) { - if ([items objectAtIndex:i] == sender) { - if (defName) i -= 2; - if (i < 0) { - url = defUrl; - } else if (i < int([apps count])) { - url = [(OpenWithApp*)[apps objectAtIndex:i] app]; - } - break; - } - } - if (url) { - [[NSWorkspace sharedWorkspace] openFile:toOpen withApplication:[url path]]; - } else { - objc_openFile(NS2QString(toOpen), true); - } -} - -- (void) dealloc { - [toOpen release]; - if (menu) [menu release]; - [super dealloc]; -} - -@end - -bool objc_showOpenWithMenu(int x, int y, const QString &f) { - @autoreleasepool { - - NSString *file = Q2NSString(f); - @try { - OpenFileWithInterface *menu = [[[OpenFileWithInterface alloc] init:file] autorelease]; - QRect r = QApplication::desktop()->screenGeometry(QPoint(x, y)); - y = r.y() + r.height() - y; - return !![menu popupAtX:x andY:y]; - } - @catch (NSException *exception) { - } - @finally { - } - - } - return false; -} - -void objc_showInFinder(const QString &file, const QString &path) { - @autoreleasepool { - - [[NSWorkspace sharedWorkspace] selectFile:Q2NSString(file) inFileViewerRootedAtPath:Q2NSString(path)]; - - } -} - -@interface NSURL(CompareUrls) - -- (BOOL) isEquivalent:(NSURL *)aURL; - -@end - -@implementation NSURL(CompareUrls) - -- (BOOL) isEquivalent:(NSURL *)aURL { - if ([self isEqual:aURL]) return YES; - if ([[self scheme] caseInsensitiveCompare:[aURL scheme]] != NSOrderedSame) return NO; - if ([[self host] caseInsensitiveCompare:[aURL host]] != NSOrderedSame) return NO; - if ([[self path] compare:[aURL path]] != NSOrderedSame) return NO; - if ([[self port] compare:[aURL port]] != NSOrderedSame) return NO; - if ([[self query] compare:[aURL query]] != NSOrderedSame) return NO; - return YES; -} - -@end - -@interface ChooseApplicationDelegate : NSObject { -} - -- (id) init:(NSArray *)recommendedApps withPanel:(NSOpenPanel *)creator withSelector:(NSPopUpButton *)menu withGood:(NSTextField *)goodLabel withBad:(NSTextField *)badLabel withIcon:(NSImageView *)badIcon withAccessory:(NSView *)acc; -- (BOOL) panel:(id)sender shouldEnableURL:(NSURL *)url; -- (void) panelSelectionDidChange:(id)sender; -- (void) menuDidClose; -- (void) dealloc; - -@end - -@implementation ChooseApplicationDelegate { - BOOL onlyRecommended; - NSArray *apps; - NSOpenPanel *panel; - NSPopUpButton *selector; - NSTextField *good, *bad; - NSImageView *icon; - NSString *recom; - NSView *accessory; -} - -- (id) init:(NSArray *)recommendedApps withPanel:(NSOpenPanel *)creator withSelector:(NSPopUpButton *)menu withGood:(NSTextField *)goodLabel withBad:(NSTextField *)badLabel withIcon:(NSImageView *)badIcon withAccessory:(NSView *)acc { - if (self = [super init]) { - onlyRecommended = YES; - recom = [NSlang(lng_mac_recommended_apps) copy]; - apps = recommendedApps; - panel = creator; - selector = menu; - good = goodLabel; - bad = badLabel; - icon = badIcon; - accessory = acc; - [selector setAction:@selector(menuDidClose)]; - } - return self; -} - -- (BOOL) isRecommended:(NSURL *)url { - if (apps) { - for (id app in apps) { - if ([(NSURL*)app isEquivalent:url]) { - return YES; - } - } - } - return NO; -} - -- (BOOL) panel:(id)sender shouldEnableURL:(NSURL *)url { - NSNumber *isDirectory; - if ([url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil] && isDirectory != nil && [isDirectory boolValue]) { - if (onlyRecommended) { - CFStringRef ext = CFURLCopyPathExtension((CFURLRef)url); - NSNumber *isPackage; - if ([url getResourceValue:&isPackage forKey:NSURLIsPackageKey error:nil] && isPackage != nil && [isPackage boolValue]) { - return [self isRecommended:url]; - } - } - return YES; - } - return NO; -} - -- (void) panelSelectionDidChange:(id)sender { - NSArray *urls = [panel URLs]; - if ([urls count]) { - if ([self isRecommended:[urls firstObject]]) { - [bad removeFromSuperview]; - [icon removeFromSuperview]; - [accessory addSubview:good]; - } else { - [good removeFromSuperview]; - [accessory addSubview:bad]; - [accessory addSubview:icon]; - } - } else { - [good removeFromSuperview]; - [bad removeFromSuperview]; - [icon removeFromSuperview]; - } -} - -- (void) menuDidClose { - onlyRecommended = [[[selector selectedItem] title] isEqualToString:recom]; - [self refreshPanelTable]; -} - -- (BOOL) refreshDataInViews: (NSArray*)subviews { - for (id view in subviews) { - NSString *cls = [view className]; - if ([cls isEqualToString:Q2NSString(strNeedToReload())]) { - [view reloadData]; - } else if ([cls isEqualToString:Q2NSString(strNeedToRefresh1())] || [cls isEqualToString:Q2NSString(strNeedToRefresh2())]) { - [view reloadData]; - return YES; - } else { - NSArray *next = [view subviews]; - if ([next count] && [self refreshDataInViews:next]) { - return YES; - } - } - } - return NO; -} - - -- (void) refreshPanelTable { - @autoreleasepool { - - [self refreshDataInViews:[[panel contentView] subviews]]; - [panel validateVisibleColumns]; - - } -} - -- (void) dealloc { - if (apps) { - [apps release]; - [recom release]; - } - [super dealloc]; -} - -@end - -void objc_openFile(const QString &f, bool openwith) { - @autoreleasepool { - - NSString *file = Q2NSString(f); - if (openwith || [[NSWorkspace sharedWorkspace] openFile:file] == NO) { - @try { - NSURL *url = [NSURL fileURLWithPath:file]; - NSString *ext = [url pathExtension]; - NSArray *names = [url pathComponents]; - NSString *name = [names count] ? [names lastObject] : @""; - NSArray *apps = (NSArray*)LSCopyApplicationURLsForURL(CFURLRef(url), kLSRolesAll); - - NSOpenPanel *openPanel = [NSOpenPanel openPanel]; - - NSRect fullRect = { { 0., 0. }, { st::macAccessoryWidth, st::macAccessoryHeight } }; - NSView *accessory = [[NSView alloc] initWithFrame:fullRect]; - - [accessory setAutoresizesSubviews:YES]; - - NSPopUpButton *selector = [[NSPopUpButton alloc] init]; - [accessory addSubview:selector]; - [selector addItemWithTitle:NSlang(lng_mac_recommended_apps)]; - [selector addItemWithTitle:NSlang(lng_mac_all_apps)]; - [selector sizeToFit]; - - NSTextField *enableLabel = [[NSTextField alloc] init]; - [accessory addSubview:enableLabel]; - [enableLabel setStringValue:NSlang(lng_mac_enable_filter)]; - [enableLabel setFont:[selector font]]; - [enableLabel setBezeled:NO]; - [enableLabel setDrawsBackground:NO]; - [enableLabel setEditable:NO]; - [enableLabel setSelectable:NO]; - [enableLabel sizeToFit]; - - NSRect selectorFrame = [selector frame], enableFrame = [enableLabel frame]; - enableFrame.size.width += st::macEnableFilterAdd; - enableFrame.origin.x = (fullRect.size.width - selectorFrame.size.width - enableFrame.size.width) / 2.; - selectorFrame.origin.x = (fullRect.size.width - selectorFrame.size.width + enableFrame.size.width) / 2.; - enableFrame.origin.y = fullRect.size.height - selectorFrame.size.height - st::macEnableFilterTop + (selectorFrame.size.height - enableFrame.size.height) / 2.; - selectorFrame.origin.y = fullRect.size.height - selectorFrame.size.height - st::macSelectorTop; - [enableLabel setFrame:enableFrame]; - [enableLabel setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; - [selector setFrame:selectorFrame]; - [selector setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; - - NSButton *button = [[NSButton alloc] init]; - [accessory addSubview:button]; - [button setButtonType:NSSwitchButton]; - [button setFont:[selector font]]; - [button setTitle:NSlang(lng_mac_always_open_with)]; - [button sizeToFit]; - NSRect alwaysRect = [button frame]; - alwaysRect.origin.x = (fullRect.size.width - alwaysRect.size.width) / 2; - alwaysRect.origin.y = selectorFrame.origin.y - alwaysRect.size.height - st::macAlwaysThisAppTop; - [button setFrame:alwaysRect]; - [button setAutoresizingMask:NSViewMinXMargin|NSViewMaxXMargin]; -#ifdef OS_MAC_STORE - [button setHidden:YES]; -#endif // OS_MAC_STORE - NSTextField *goodLabel = [[NSTextField alloc] init]; - [goodLabel setStringValue:Q2NSString(lng_mac_this_app_can_open(lt_file, NS2QString(name)))]; - [goodLabel setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - [goodLabel setBezeled:NO]; - [goodLabel setDrawsBackground:NO]; - [goodLabel setEditable:NO]; - [goodLabel setSelectable:NO]; - [goodLabel sizeToFit]; - NSRect goodFrame = [goodLabel frame]; - goodFrame.origin.x = (fullRect.size.width - goodFrame.size.width) / 2.; - goodFrame.origin.y = alwaysRect.origin.y - goodFrame.size.height - st::macAppHintTop; - [goodLabel setFrame:goodFrame]; - - NSTextField *badLabel = [[NSTextField alloc] init]; - [badLabel setStringValue:Q2NSString(lng_mac_not_known_app(lt_file, NS2QString(name)))]; - [badLabel setFont:[goodLabel font]]; - [badLabel setBezeled:NO]; - [badLabel setDrawsBackground:NO]; - [badLabel setEditable:NO]; - [badLabel setSelectable:NO]; - [badLabel sizeToFit]; - NSImageView *badIcon = [[NSImageView alloc] init]; - NSImage *badImage = [NSImage imageNamed:NSImageNameCaution]; - [badIcon setImage:badImage]; - [badIcon setFrame:NSMakeRect(0, 0, st::macCautionIconSize, st::macCautionIconSize)]; - - NSRect badFrame = [badLabel frame], badIconFrame = [badIcon frame]; - badFrame.origin.x = (fullRect.size.width - badFrame.size.width + badIconFrame.size.width) / 2.; - badIconFrame.origin.x = (fullRect.size.width - badFrame.size.width - badIconFrame.size.width) / 2.; - badFrame.origin.y = alwaysRect.origin.y - badFrame.size.height - st::macAppHintTop; - badIconFrame.origin.y = badFrame.origin.y; - [badLabel setFrame:badFrame]; - [badIcon setFrame:badIconFrame]; - - [openPanel setAccessoryView:accessory]; - - ChooseApplicationDelegate *delegate = [[ChooseApplicationDelegate alloc] init:apps withPanel:openPanel withSelector:selector withGood:goodLabel withBad:badLabel withIcon:badIcon withAccessory:accessory]; - [openPanel setDelegate:delegate]; - - [openPanel setCanChooseDirectories:NO]; - [openPanel setCanChooseFiles:YES]; - [openPanel setAllowsMultipleSelection:NO]; - [openPanel setResolvesAliases:YES]; - [openPanel setTitle:NSlang(lng_mac_choose_app)]; - [openPanel setMessage:Q2NSString(lng_mac_choose_text(lt_file, NS2QString(name)))]; - - NSArray *appsPaths = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationDirectory inDomains:NSLocalDomainMask]; - if ([appsPaths count]) [openPanel setDirectoryURL:[appsPaths firstObject]]; - [openPanel beginWithCompletionHandler:^(NSInteger result){ - if (result == NSFileHandlingPanelOKButton) { - if ([[openPanel URLs] count] > 0) { - NSURL *app = [[openPanel URLs] objectAtIndex:0]; - NSString *path = [app path]; - if ([button state] == NSOnState) { - NSArray *UTIs = (NSArray *)UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, - (CFStringRef)ext, - nil); - for (NSString *UTI in UTIs) { - OSStatus result = LSSetDefaultRoleHandlerForContentType((CFStringRef)UTI, - kLSRolesAll, - (CFStringRef)[[NSBundle bundleWithPath:path] bundleIdentifier]); - DEBUG_LOG(("App Info: set default handler for '%1' UTI result: %2").arg(NS2QString(UTI)).arg(result)); - } - - [UTIs release]; - } - [[NSWorkspace sharedWorkspace] openFile:file withApplication:[app path]]; - } - } - [selector release]; - [button release]; - [enableLabel release]; - [goodLabel release]; - [badLabel release]; - [badIcon release]; - [accessory release]; - [delegate release]; - }]; - } - @catch (NSException *exception) { - [[NSWorkspace sharedWorkspace] openFile:file]; - } - @finally { - } - } - - } -} - void objc_start() { _sharedDelegate = [[ApplicationDelegate alloc] init]; [[NSApplication sharedApplication] setDelegate:_sharedDelegate]; diff --git a/Telegram/SourceFiles/pspecific_win.cpp b/Telegram/SourceFiles/pspecific_win.cpp index 06066403c7..1e4b5b6dd5 100644 --- a/Telegram/SourceFiles/pspecific_win.cpp +++ b/Telegram/SourceFiles/pspecific_win.cpp @@ -525,211 +525,6 @@ int psFixPrevious() { return 0; } -void psPostprocessFile(const QString &name) { - std::wstring zoneFile = QDir::toNativeSeparators(name).toStdWString() + L":Zone.Identifier"; - HANDLE f = CreateFile(zoneFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); - if (f == INVALID_HANDLE_VALUE) { // :( - return; - } - - const char data[] = "[ZoneTransfer]\r\nZoneId=3\r\n"; - - DWORD written = 0; - BOOL result = WriteFile(f, data, sizeof(data), &written, NULL); - CloseHandle(f); - - if (!result || written != sizeof(data)) { // :( - return; - } -} - -HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &, int hbitmapFormat); - -namespace { - struct OpenWithApp { - OpenWithApp(const QString &name, HBITMAP icon, IAssocHandler *handler) : name(name), icon(icon), handler(handler) { - } - OpenWithApp(const QString &name, IAssocHandler *handler) : name(name), icon(0), handler(handler) { - } - void destroy() { - if (icon) DeleteBitmap(icon); - if (handler) handler->Release(); - } - QString name; - HBITMAP icon; - IAssocHandler *handler; - }; - - bool OpenWithAppLess(const OpenWithApp &a, const OpenWithApp &b) { - return a.name < b.name; - } - - HBITMAP _iconToBitmap(LPWSTR icon, int iconindex) { - if (!icon) return 0; - WCHAR tmpIcon[4096]; - if (icon[0] == L'@' && SUCCEEDED(SHLoadIndirectString(icon, tmpIcon, 4096, 0))) { - icon = tmpIcon; - } - int32 w = GetSystemMetrics(SM_CXSMICON), h = GetSystemMetrics(SM_CYSMICON); - - HICON ico = ExtractIcon(0, icon, iconindex); - if (!ico) { - if (!iconindex) { // try to read image - QImage img(QString::fromWCharArray(icon)); - if (!img.isNull()) { - return qt_pixmapToWinHBITMAP(App::pixmapFromImageInPlace(img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)), /* HBitmapAlpha */ 2); - } - } - return 0; - } - - HDC screenDC = GetDC(0), hdc = CreateCompatibleDC(screenDC); - HBITMAP result = CreateCompatibleBitmap(screenDC, w, h); - HGDIOBJ was = SelectObject(hdc, result); - DrawIconEx(hdc, 0, 0, ico, w, h, 0, NULL, DI_NORMAL); - SelectObject(hdc, was); - DeleteDC(hdc); - ReleaseDC(0, screenDC); - - DestroyIcon(ico); - - return (HBITMAP)CopyImage(result, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_CREATEDIBSECTION); -// return result; - } -} - -bool psShowOpenWithMenu(int x, int y, const QString &file) { - if (!useOpenWith || !App::wnd()) return false; - - bool result = false; - QList handlers; - IShellItem* pItem = nullptr; - if (SUCCEEDED(Dlls::SHCreateItemFromParsingName(QDir::toNativeSeparators(file).toStdWString().c_str(), nullptr, IID_PPV_ARGS(&pItem)))) { - IEnumAssocHandlers *assocHandlers = 0; - if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_EnumAssocHandlers, IID_PPV_ARGS(&assocHandlers)))) { - HRESULT hr = S_FALSE; - do - { - IAssocHandler *handler = 0; - ULONG ulFetched = 0; - hr = assocHandlers->Next(1, &handler, &ulFetched); - if (FAILED(hr) || hr == S_FALSE || !ulFetched) break; - - LPWSTR name = 0; - if (SUCCEEDED(handler->GetUIName(&name))) { - LPWSTR icon = 0; - int iconindex = 0; - if (SUCCEEDED(handler->GetIconLocation(&icon, &iconindex)) && icon) { - handlers.push_back(OpenWithApp(QString::fromWCharArray(name), _iconToBitmap(icon, iconindex), handler)); - CoTaskMemFree(icon); - } else { - handlers.push_back(OpenWithApp(QString::fromWCharArray(name), handler)); - } - CoTaskMemFree(name); - } else { - handler->Release(); - } - } while (hr != S_FALSE); - assocHandlers->Release(); - } - - if (!handlers.isEmpty()) { - HMENU menu = CreatePopupMenu(); - std::sort(handlers.begin(), handlers.end(), OpenWithAppLess); - for (int32 i = 0, l = handlers.size(); i < l; ++i) { - MENUITEMINFO menuInfo = { 0 }; - menuInfo.cbSize = sizeof(menuInfo); - menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID; - menuInfo.fType = MFT_STRING; - menuInfo.wID = i + 1; - if (handlers.at(i).icon) { - menuInfo.fMask |= MIIM_BITMAP; - menuInfo.hbmpItem = handlers.at(i).icon; - } - - QString name = handlers.at(i).name; - if (name.size() > 512) name = name.mid(0, 512); - WCHAR nameArr[1024]; - name.toWCharArray(nameArr); - nameArr[name.size()] = 0; - menuInfo.dwTypeData = nameArr; - InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo); - } - MENUITEMINFO sepInfo = { 0 }; - sepInfo.cbSize = sizeof(sepInfo); - sepInfo.fMask = MIIM_STRING | MIIM_DATA; - sepInfo.fType = MFT_SEPARATOR; - InsertMenuItem(menu, GetMenuItemCount(menu), true, &sepInfo); - - MENUITEMINFO menuInfo = { 0 }; - menuInfo.cbSize = sizeof(menuInfo); - menuInfo.fMask = MIIM_STRING | MIIM_DATA | MIIM_ID; - menuInfo.fType = MFT_STRING; - menuInfo.wID = handlers.size() + 1; - - QString name = lang(lng_wnd_choose_program_menu); - if (name.size() > 512) name = name.mid(0, 512); - WCHAR nameArr[1024]; - name.toWCharArray(nameArr); - nameArr[name.size()] = 0; - menuInfo.dwTypeData = nameArr; - InsertMenuItem(menu, GetMenuItemCount(menu), TRUE, &menuInfo); - - int sel = TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, x, y, 0, App::wnd()->psHwnd(), 0); - DestroyMenu(menu); - - if (sel > 0) { - if (sel <= handlers.size()) { - IDataObject *dataObj = 0; - if (SUCCEEDED(pItem->BindToHandler(nullptr, BHID_DataObject, IID_PPV_ARGS(&dataObj))) && dataObj) { - handlers.at(sel - 1).handler->Invoke(dataObj); - dataObj->Release(); - result = true; - } - } - } else { - result = true; - } - for (int i = 0, l = handlers.size(); i < l; ++i) { - handlers[i].destroy(); - } - } - - pItem->Release(); - } - return result; -} - -void psOpenFile(const QString &name, bool openWith) { - base::TaskQueue::Main().Put([name, openWith] { - bool mailtoScheme = name.startsWith(qstr("mailto:")); - std::wstring wname = mailtoScheme ? name.toStdWString() : QDir::toNativeSeparators(name).toStdWString(); - - if (openWith && useOpenAs) { - if (Dlls::SHOpenWithDialog) { - OPENASINFO info; - info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_REGISTER_EXT | OAIF_EXEC; - if (mailtoScheme) info.oaifInFlags |= OAIF_FILE_IS_URI | OAIF_URL_PROTOCOL; - info.pcszClass = NULL; - info.pcszFile = wname.c_str(); - Dlls::SHOpenWithDialog(0, &info); - } else { - Dlls::OpenAs_RunDLL(0, 0, wname.c_str(), SW_SHOWNORMAL); - } - } else { - ShellExecute(0, L"open", wname.c_str(), 0, 0, SW_SHOWNORMAL); - } - }); -} - -void psShowInFolder(const QString &name) { - base::TaskQueue::Main().Put([name] { - auto nameEscaped = QDir::toNativeSeparators(name).replace('"', qsl("\"\"")); - ShellExecute(0, 0, qsl("explorer").toStdWString().c_str(), (qsl("/select,") + nameEscaped).toStdWString().c_str(), 0, SW_SHOWNORMAL); - }); -} - - namespace Platform { void start() { diff --git a/Telegram/SourceFiles/pspecific_win.h b/Telegram/SourceFiles/pspecific_win.h index 909d06f0d2..2d1f0a22d0 100644 --- a/Telegram/SourceFiles/pspecific_win.h +++ b/Telegram/SourceFiles/pspecific_win.h @@ -63,12 +63,6 @@ int psFixPrevious(); void psExecUpdater(); void psExecTelegram(const QString &arg = QString()); -bool psShowOpenWithMenu(int x, int y, const QString &file); - -void psPostprocessFile(const QString &name); -void psOpenFile(const QString &name, bool openWith = false); -void psShowInFolder(const QString &name); - QAbstractNativeEventFilter *psNativeEventFilter(); void psNewVersion(); diff --git a/Telegram/SourceFiles/settings/settings_background_widget.h b/Telegram/SourceFiles/settings/settings_background_widget.h index 4f3954eb27..c5a6fa600b 100644 --- a/Telegram/SourceFiles/settings/settings_background_widget.h +++ b/Telegram/SourceFiles/settings/settings_background_widget.h @@ -22,7 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "settings/settings_block_widget.h" #include "ui/effects/radial_animation.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" namespace Settings { diff --git a/Telegram/SourceFiles/settings/settings_cover.h b/Telegram/SourceFiles/settings/settings_cover.h index 1138d69286..178960e4f6 100644 --- a/Telegram/SourceFiles/settings/settings_cover.h +++ b/Telegram/SourceFiles/settings/settings_cover.h @@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "core/observer.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "settings/settings_block_widget.h" diff --git a/Telegram/SourceFiles/settings/settings_general_widget.cpp b/Telegram/SourceFiles/settings/settings_general_widget.cpp index f947910b14..a07e8fbc02 100644 --- a/Telegram/SourceFiles/settings/settings_general_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_general_widget.cpp @@ -33,7 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/languagebox.h" #include "boxes/confirmbox.h" #include "boxes/aboutbox.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "langloaderplain.h" #include "autoupdater.h" diff --git a/Telegram/SourceFiles/settings/settings_general_widget.h b/Telegram/SourceFiles/settings/settings_general_widget.h index 833b3857c9..5e606987aa 100644 --- a/Telegram/SourceFiles/settings/settings_general_widget.h +++ b/Telegram/SourceFiles/settings/settings_general_widget.h @@ -21,7 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "settings/settings_block_widget.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" namespace Settings { diff --git a/Telegram/SourceFiles/settings/settings_layer.cpp b/Telegram/SourceFiles/settings/settings_layer.cpp index 3ee2ed1dd4..7230aa3dc8 100644 --- a/Telegram/SourceFiles/settings/settings_layer.cpp +++ b/Telegram/SourceFiles/settings/settings_layer.cpp @@ -34,7 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "boxes/confirmbox.h" #include "application.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "window/themes/window_theme.h" namespace Settings { diff --git a/Telegram/SourceFiles/settings/settings_notifications_widget.cpp b/Telegram/SourceFiles/settings/settings_notifications_widget.cpp index 7e7647b06f..03bc160ffe 100644 --- a/Telegram/SourceFiles/settings/settings_notifications_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_notifications_widget.cpp @@ -30,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "window/notifications_manager.h" #include "boxes/notifications_box.h" +#include "platform/platform_notifications_manager.h" namespace Settings { @@ -72,16 +73,14 @@ void NotificationsWidget::createNotificationsControls() { style::margins margin(0, 0, 0, st::settingsSkip); style::margins slidedPadding(0, margin.bottom() / 2, 0, margin.bottom() - (margin.bottom() / 2)); - QString nativeNotificationsLabel; + auto nativeNotificationsLabel = QString(); + if (Platform::Notifications::Supported()) { #ifdef Q_OS_WIN - if (App::wnd()->psHasNativeNotifications()) { nativeNotificationsLabel = lang(lng_settings_use_windows); - } -#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 - if (App::wnd()->psHasNativeNotifications()) { +#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_WIN nativeNotificationsLabel = lang(lng_settings_use_native_notifications); +#endif // Q_OS_WIN || Q_OS_LINUX64 || Q_OS_LINUX32 } -#endif // Q_OS_WIN if (!nativeNotificationsLabel.isEmpty()) { addChildRow(_nativeNotifications, margin, nativeNotificationsLabel, SLOT(onNativeNotifications()), Global::NativeNotifications()); } diff --git a/Telegram/SourceFiles/settings/settings_widget.cpp b/Telegram/SourceFiles/settings/settings_widget.cpp index 361624193c..d91450aa40 100644 --- a/Telegram/SourceFiles/settings/settings_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_widget.cpp @@ -35,7 +35,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "boxes/confirmbox.h" #include "lang.h" #include "messenger.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "window/themes/window_theme.h" #include "window/themes/window_theme_editor.h" @@ -93,7 +93,7 @@ void fillCodes() { } }); Codes.insert(qsl("loadcolors"), []() { - FileDialog::askOpenPath("Open palette file", "Palette (*.tdesktop-palette)", [](const FileDialog::OpenResult &result) { + FileDialog::GetOpenPath("Open palette file", "Palette (*.tdesktop-palette)", [](const FileDialog::OpenResult &result) { if (!result.paths.isEmpty()) { Window::Theme::Apply(result.paths.front()); } diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 9e65e2ac85..1a91037b3c 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -28,7 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "application.h" #include "fileuploader.h" #include "mainwindow.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "apiwrap.h" #include "boxes/confirmbox.h" #include "media/media_audio.h" @@ -1191,16 +1191,14 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, } else { auto filepath = location.name(); if (documentIsValidMediaFile(filepath)) { - psOpenFile(filepath); - } else { - psShowInFolder(filepath); + File::Launch(filepath); } } if (App::main()) App::main()->mediaMarkRead(data); } else if (data->voice() || data->song() || data->isVideo()) { auto filepath = location.name(); if (documentIsValidMediaFile(filepath)) { - psOpenFile(filepath); + File::Launch(filepath); } if (App::main()) App::main()->mediaMarkRead(data); } else if (data->size < App::kImageSizeLimit) { @@ -1218,14 +1216,14 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, App::wnd()->showDocument(data, context); } } else { - psOpenFile(location.name()); + File::Launch(location.name()); } location.accessDisable(); } else { - psOpenFile(location.name()); + File::Launch(location.name()); } } else { - psOpenFile(location.name()); + File::Launch(location.name()); } return; } @@ -1256,10 +1254,7 @@ void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) { auto filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs); if (!filepath.isEmpty() && !forceSavingAs) { - auto pos = QCursor::pos(); - if (!psShowOpenWithMenu(pos.x(), pos.y(), filepath)) { - psOpenFile(filepath, true); - } + File::OpenWith(filepath, QCursor::pos()); } else { auto fileinfo = QFileInfo(filepath); auto filedir = filepath.isEmpty() ? QDir() : fileinfo.dir(); @@ -1499,14 +1494,11 @@ void DocumentData::performActionOnLoad() { if (already.isEmpty()) return; if (_actionOnLoad == ActionOnLoadOpenWith) { - QPoint pos(QCursor::pos()); - if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { - psOpenFile(already, true); - } + File::OpenWith(already, QCursor::pos()); } else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) { if (voice() || song() || isVideo()) { if (documentIsValidMediaFile(already)) { - psOpenFile(already); + File::Launch(already); } if (App::main()) App::main()->mediaMarkRead(this); } else if (loc.accessEnable()) { @@ -1517,11 +1509,11 @@ void DocumentData::performActionOnLoad() { App::wnd()->showDocument(this, item); } } else { - psOpenFile(already); + File::Launch(already); } loc.accessDisable(); } else { - psOpenFile(already); + File::Launch(already); } } } diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp index ecacd4167b..ef1770d385 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp @@ -39,7 +39,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "core/task_queue.h" #include "core/zlib_help.h" #include "ui/toast/toast.h" -#include "ui/filedialog.h" +#include "core/file_utilities.h" #include "boxes/editcolorbox.h" #include "lang.h" @@ -635,7 +635,7 @@ void ThemeExportBox::updateThumbnail() { } void ThemeExportBox::chooseBackgroundFromFile() { - FileDialog::askOpenPath(lang(lng_theme_editor_choose_image), "Image files (*.jpeg *.jpg *.png)", base::lambda_guarded(this, [this](const FileDialog::OpenResult &result) { + FileDialog::GetOpenPath(lang(lng_theme_editor_choose_image), "Image files (*.jpeg *.jpg *.png)", base::lambda_guarded(this, [this](const FileDialog::OpenResult &result) { auto content = result.remoteContent; if (!result.paths.isEmpty()) { QFile f(result.paths.front()); @@ -664,7 +664,7 @@ void ThemeExportBox::exportTheme() { auto caption = lang(lng_theme_editor_choose_name); auto filter = "Themes (*.tdesktop-theme)"; auto name = "awesome.tdesktop-theme"; - FileDialog::askWritePath(caption, filter, name, base::lambda_guarded(this, [this](const QString &path) { + FileDialog::GetWritePath(caption, filter, name, base::lambda_guarded(this, [this](const QString &path) { zlib::FileToWrite zip; zip_fileinfo zfi = { { 0, 0, 0, 0, 0, 0 }, 0, 0, 0 }; @@ -798,7 +798,7 @@ void Editor::paintEvent(QPaintEvent *e) { void Editor::Start() { auto palettePath = Local::themePaletteAbsolutePath(); if (palettePath.isEmpty()) { - FileDialog::askWritePath(lang(lng_theme_editor_save_palette), "Palette (*.tdesktop-palette)", "colors.tdesktop-palette", [](const QString &path) { + FileDialog::GetWritePath(lang(lng_theme_editor_save_palette), "Palette (*.tdesktop-palette)", "colors.tdesktop-palette", [](const QString &path) { if (!Local::copyThemeColorsToPalette(path)) { writeDefaultPalette(path); } diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index d117c10980..fc52ff52ce 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -213,6 +213,8 @@ '<(src_loc)/core/click_handler.h', '<(src_loc)/core/click_handler_types.cpp', '<(src_loc)/core/click_handler_types.h', + '<(src_loc)/core/file_utilities.cpp', + '<(src_loc)/core/file_utilities.h', '<(src_loc)/core/lambda.h', '<(src_loc)/core/observer.cpp', '<(src_loc)/core/observer.h', @@ -563,8 +565,6 @@ '<(src_loc)/ui/countryinput.h', '<(src_loc)/ui/emoji_config.cpp', '<(src_loc)/ui/emoji_config.h', - '<(src_loc)/ui/filedialog.cpp', - '<(src_loc)/ui/filedialog.h', '<(src_loc)/ui/images.cpp', '<(src_loc)/ui/images.h', '<(src_loc)/ui/twidget.cpp',