diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 6a687c2619..ec950fa17f 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -491,7 +491,10 @@ void VideoSaveLink::doSave(bool forceSavingAs) const { QString already = data->already(true); if (!already.isEmpty() && !forceSavingAs) { - psOpenFile(already, true); + QPoint pos(QCursor::pos()); + if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } } else { QDir alreadyDir(already.isEmpty() ? QDir() : QFileInfo(already).dir()); QString name = already.isEmpty() ? QString(".mov") : already; @@ -577,7 +580,10 @@ void AudioSaveLink::doSave(bool forceSavingAs) const { QString already = data->already(true); if (!already.isEmpty() && !forceSavingAs) { - psOpenFile(already, true); + QPoint pos(QCursor::pos()); + if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } } else { QDir alreadyDir(already.isEmpty() ? QDir() : QFileInfo(already).dir()); QString name = already.isEmpty() ? QString(".ogg") : already; @@ -678,7 +684,10 @@ void DocumentSaveLink::doSave(bool forceSavingAs) const { QString already = data->already(true); if (!already.isEmpty() && !forceSavingAs) { - psOpenFile(already, true); + QPoint pos(QCursor::pos()); + if (!psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } } else { QDir alreadyDir(already.isEmpty() ? QDir() : QFileInfo(already).dir()); QString name = already.isEmpty() ? data->name : already, filter; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 998e62be5d..38e73dbd6a 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1050,7 +1050,12 @@ void MainWidget::videoLoadProgress(mtpFileLoader *loader) { video->finish(); QString already = video->already(); if (!already.isEmpty() && video->openOnSave) { - psOpenFile(already, video->openOnSave < 0); + QPoint pos(QCursor::pos()); + if (video->openOnSave < 0 && !psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } else { + psOpenFile(already, video->openOnSave < 0); + } } } } @@ -1105,7 +1110,12 @@ void MainWidget::audioLoadProgress(mtpFileLoader *loader) { audioVoice()->play(audio); } } else { - psOpenFile(already, audio->openOnSave < 0); + QPoint pos(QCursor::pos()); + if (audio->openOnSave < 0 && !psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } else { + psOpenFile(already, audio->openOnSave < 0); + } } } } @@ -1164,7 +1174,12 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) { psOpenFile(already); } } else { - psOpenFile(already, document->openOnSave < 0); + QPoint pos(QCursor::pos()); + if (document->openOnSave < 0 && !psShowOpenWithMenu(pos.x(), pos.y(), already)) { + psOpenFile(already, true); + } else { + psOpenFile(already, document->openOnSave < 0); + } } } } diff --git a/Telegram/SourceFiles/pspecific_linux.cpp b/Telegram/SourceFiles/pspecific_linux.cpp index e6866b777e..ce787c4a42 100644 --- a/Telegram/SourceFiles/pspecific_linux.cpp +++ b/Telegram/SourceFiles/pspecific_linux.cpp @@ -1654,6 +1654,10 @@ void psExecTelegram() { _execUpdater(false); } +bool psShowOpenWithMenu(int x, int y, const QString &file) { + return false; +} + void psAutoStart(bool start, bool silent) { } diff --git a/Telegram/SourceFiles/pspecific_linux.h b/Telegram/SourceFiles/pspecific_linux.h index 37326e5a6c..ae2945c949 100644 --- a/Telegram/SourceFiles/pspecific_linux.h +++ b/Telegram/SourceFiles/pspecific_linux.h @@ -197,6 +197,8 @@ bool psCheckReadyUpdate(); void psExecUpdater(); void psExecTelegram(); +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); diff --git a/Telegram/SourceFiles/pspecific_mac.cpp b/Telegram/SourceFiles/pspecific_mac.cpp index e381e73747..becb9b2213 100644 --- a/Telegram/SourceFiles/pspecific_mac.cpp +++ b/Telegram/SourceFiles/pspecific_mac.cpp @@ -1110,6 +1110,10 @@ bool psCheckReadyUpdate() { return true; } +bool psShowOpenWithMenu(int x, int y, const QString &file) { + return objc_showOpenWithMenu(x, y, file); +} + void psPostprocessFile(const QString &name) { } diff --git a/Telegram/SourceFiles/pspecific_mac.h b/Telegram/SourceFiles/pspecific_mac.h index bdc402fc7b..3a92ce63a3 100644 --- a/Telegram/SourceFiles/pspecific_mac.h +++ b/Telegram/SourceFiles/pspecific_mac.h @@ -221,6 +221,8 @@ bool psCheckReadyUpdate(); void psExecUpdater(); void psExecTelegram(); +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); diff --git a/Telegram/SourceFiles/pspecific_mac_p.h b/Telegram/SourceFiles/pspecific_mac_p.h index 04761c59f5..cb95f02a64 100644 --- a/Telegram/SourceFiles/pspecific_mac_p.h +++ b/Telegram/SourceFiles/pspecific_mac_p.h @@ -59,6 +59,8 @@ void objc_debugShowAlert(const QString &str); void objc_outputDebugString(const QString &str); int64 objc_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(); diff --git a/Telegram/SourceFiles/pspecific_mac_p.mm b/Telegram/SourceFiles/pspecific_mac_p.mm index db182d90e2..e23a96c4de 100644 --- a/Telegram/SourceFiles/pspecific_mac_p.mm +++ b/Telegram/SourceFiles/pspecific_mac_p.mm @@ -369,6 +369,209 @@ int64 objc_idleTime() { // taken from https://github.com/trueinteractions/tint/i return (result == err) ? -1 : int64(result); } +@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; + 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:@"Other..." 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(objcString(toOpen), true); + } +} + +- (void) dealloc { + if (apps) [apps release]; + [super dealloc]; + if (menu) [menu release]; +} + +@end + +bool objc_showOpenWithMenu(int x, int y, const QString &f) { + NSString *file = QNSString(f).s(); + @try { + OpenFileWithInterface *menu = [[OpenFileWithInterface alloc] init:file]; + 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) { [[NSWorkspace sharedWorkspace] selectFile:QNSString(file).s() inFileViewerRootedAtPath:QNSString(path).s()]; } diff --git a/Telegram/SourceFiles/pspecific_wnd.cpp b/Telegram/SourceFiles/pspecific_wnd.cpp index cc0f6b4ff1..2630bc8433 100644 --- a/Telegram/SourceFiles/pspecific_wnd.cpp +++ b/Telegram/SourceFiles/pspecific_wnd.cpp @@ -2295,6 +2295,10 @@ void psExecTelegram() { } } +bool psShowOpenWithMenu(int x, int y, const QString &file) { + return false; +} + void _manageAppLnk(bool create, bool silent, int path_csidl, const wchar_t *args, const wchar_t *description) { WCHAR startupFolder[MAX_PATH]; HRESULT hres = SHGetFolderPath(0, path_csidl, 0, SHGFP_TYPE_CURRENT, startupFolder); diff --git a/Telegram/SourceFiles/pspecific_wnd.h b/Telegram/SourceFiles/pspecific_wnd.h index 4ec4d0269f..39cbe2ef90 100644 --- a/Telegram/SourceFiles/pspecific_wnd.h +++ b/Telegram/SourceFiles/pspecific_wnd.h @@ -199,6 +199,8 @@ bool psCheckReadyUpdate(); void psExecUpdater(); void psExecTelegram(); +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); diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index a61ae53b44..e147daa7f1 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -1813,7 +1813,7 @@ CODE_SIGN_IDENTITY = ""; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 0.7.8; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_COMPATIBILITY_VERSION = 0.7; DYLIB_CURRENT_VERSION = 0.7.8; ENABLE_STRICT_OBJC_MSGSEND = YES;