mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-05-02 20:34:07 +02:00
Work on abstracting CEF conventions and logic into a separate library
This commit is contained in:
parent
0a9c84feec
commit
3aace0b399
lib/TweetLib.Browser.CEF
Component
Data
Interfaces
Logic
windows/TweetDuck
@ -1,12 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using TweetLib.Browser.Base;
|
using TweetLib.Browser.Base;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
using TweetLib.Browser.CEF.Interfaces;
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
using TweetLib.Browser.Events;
|
using TweetLib.Browser.Events;
|
||||||
using TweetLib.Browser.Interfaces;
|
using TweetLib.Browser.Interfaces;
|
||||||
using TweetLib.Utils.Static;
|
using TweetLib.Utils.Static;
|
||||||
|
|
||||||
namespace TweetLib.Browser.CEF.Component {
|
namespace TweetLib.Browser.CEF.Component {
|
||||||
public abstract class BrowserComponent<TFrame> : IBrowserComponent where TFrame : IDisposable {
|
public abstract class BrowserComponent<TFrame, TRequest> : IBrowserComponent where TFrame : IDisposable {
|
||||||
public bool Ready { get; private set; }
|
public bool Ready { get; private set; }
|
||||||
|
|
||||||
public string Url => browser.Url;
|
public string Url => browser.Url;
|
||||||
@ -16,22 +18,25 @@ public abstract class BrowserComponent<TFrame> : IBrowserComponent where TFrame
|
|||||||
public event EventHandler<PageLoadEventArgs>? PageLoadStart;
|
public event EventHandler<PageLoadEventArgs>? PageLoadStart;
|
||||||
public event EventHandler<PageLoadEventArgs>? PageLoadEnd;
|
public event EventHandler<PageLoadEventArgs>? PageLoadEnd;
|
||||||
|
|
||||||
private readonly IBrowserWrapper<TFrame> browser;
|
private readonly IBrowserWrapper<TFrame, TRequest> browser;
|
||||||
|
private readonly ICefAdapter cefAdapter;
|
||||||
private readonly IFrameAdapter<TFrame> frameAdapter;
|
private readonly IFrameAdapter<TFrame> frameAdapter;
|
||||||
|
private readonly IRequestAdapter<TRequest> requestAdapter;
|
||||||
|
|
||||||
protected BrowserComponent(IBrowserWrapper<TFrame> browser, IFrameAdapter<TFrame> frameAdapter) {
|
protected BrowserComponent(IBrowserWrapper<TFrame, TRequest> browser, ICefAdapter cefAdapter, IFrameAdapter<TFrame> frameAdapter, IRequestAdapter<TRequest> requestAdapter) {
|
||||||
this.browser = browser;
|
this.browser = browser;
|
||||||
|
this.cefAdapter = cefAdapter;
|
||||||
this.frameAdapter = frameAdapter;
|
this.frameAdapter = frameAdapter;
|
||||||
|
this.requestAdapter = requestAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void Setup(BrowserSetup setup);
|
public abstract void Setup(BrowserSetup setup);
|
||||||
public abstract void AttachBridgeObject(string name, object bridge);
|
public abstract void AttachBridgeObject(string name, object bridge);
|
||||||
public abstract void DownloadFile(string url, string path, Action? onSuccess, Action<Exception>? onError);
|
|
||||||
|
|
||||||
private sealed class BrowserLoadedEventArgsImpl : BrowserLoadedEventArgs {
|
private sealed class BrowserLoadedEventArgsImpl : BrowserLoadedEventArgs {
|
||||||
private readonly IBrowserWrapper<TFrame> browser;
|
private readonly IBrowserWrapper<TFrame, TRequest> browser;
|
||||||
|
|
||||||
public BrowserLoadedEventArgsImpl(IBrowserWrapper<TFrame> browser) {
|
public BrowserLoadedEventArgsImpl(IBrowserWrapper<TFrame, TRequest> browser) {
|
||||||
this.browser = browser;
|
this.browser = browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,5 +87,25 @@ public void RunScript(string identifier, string script) {
|
|||||||
using TFrame frame = browser.MainFrame;
|
using TFrame frame = browser.MainFrame;
|
||||||
frameAdapter.ExecuteJavaScriptAsync(frame, script, identifier, 1);
|
frameAdapter.ExecuteJavaScriptAsync(frame, script, identifier, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DownloadFile(string url, string path, Action? onSuccess, Action<Exception>? onError) {
|
||||||
|
cefAdapter.RunOnUiThread(() => {
|
||||||
|
var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var request = browser.CreateGetRequest();
|
||||||
|
requestAdapter.SetUrl(request, url);
|
||||||
|
requestAdapter.SetMethod(request, "GET");
|
||||||
|
requestAdapter.SetReferrer(request, Url);
|
||||||
|
requestAdapter.SetAllowStoredCredentials(request);
|
||||||
|
|
||||||
|
using TFrame frame = browser.MainFrame;
|
||||||
|
browser.RequestDownload(frame, request, new DownloadCallbacks(fileStream, onSuccess, onError));
|
||||||
|
} catch (Exception e) {
|
||||||
|
fileStream.Dispose();
|
||||||
|
onError?.Invoke(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
lib/TweetLib.Browser.CEF/Data/DownloadCallbacks.cs
Normal file
33
lib/TweetLib.Browser.CEF/Data/DownloadCallbacks.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Data {
|
||||||
|
public sealed class DownloadCallbacks {
|
||||||
|
internal bool HasData { get; private set; }
|
||||||
|
|
||||||
|
private readonly FileStream fileStream;
|
||||||
|
private readonly Action? onSuccess;
|
||||||
|
private readonly Action<Exception>? onError;
|
||||||
|
|
||||||
|
internal DownloadCallbacks(FileStream fileStream, Action? onSuccess, Action<Exception>? onError) {
|
||||||
|
this.fileStream = fileStream;
|
||||||
|
this.onSuccess = onSuccess;
|
||||||
|
this.onError = onError;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnData(Stream data) {
|
||||||
|
data.CopyTo(fileStream);
|
||||||
|
HasData |= fileStream.Position > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnSuccess() {
|
||||||
|
fileStream.Dispose();
|
||||||
|
onSuccess?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnError(Exception e) {
|
||||||
|
fileStream.Dispose();
|
||||||
|
onError?.Invoke(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
|
||||||
namespace TweetLib.Browser.CEF.Interfaces {
|
namespace TweetLib.Browser.CEF.Interfaces {
|
||||||
public interface IBrowserWrapper<TFrame> where TFrame : IDisposable {
|
public interface IBrowserWrapper<TFrame, TRequest> where TFrame : IDisposable {
|
||||||
string Url { get; }
|
string Url { get; }
|
||||||
TFrame MainFrame { get; }
|
TFrame MainFrame { get; }
|
||||||
|
|
||||||
void AddWordToDictionary(string word);
|
void AddWordToDictionary(string word);
|
||||||
|
TRequest CreateGetRequest();
|
||||||
|
void RequestDownload(TFrame frame, TRequest request, DownloadCallbacks callbacks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
lib/TweetLib.Browser.CEF/Interfaces/ICefAdapter.cs
Normal file
7
lib/TweetLib.Browser.CEF/Interfaces/ICefAdapter.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Interfaces {
|
||||||
|
public interface ICefAdapter {
|
||||||
|
void RunOnUiThread(Action action);
|
||||||
|
}
|
||||||
|
}
|
16
lib/TweetLib.Browser.CEF/Interfaces/IMenuModelAdapter.cs
Normal file
16
lib/TweetLib.Browser.CEF/Interfaces/IMenuModelAdapter.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
namespace TweetLib.Browser.CEF.Interfaces {
|
||||||
|
public interface IMenuModelAdapter<T> {
|
||||||
|
int GetItemCount(T model);
|
||||||
|
|
||||||
|
void AddCommand(T model, int command, string name);
|
||||||
|
int GetCommandAt(T model, int index);
|
||||||
|
|
||||||
|
void AddCheckCommand(T model, int command, string name);
|
||||||
|
void SetChecked(T model, int command, bool isChecked);
|
||||||
|
|
||||||
|
void AddSeparator(T model);
|
||||||
|
bool IsSeparatorAt(T model, int index);
|
||||||
|
|
||||||
|
void RemoveAt(T model, int index);
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,8 @@ public interface IRequestAdapter<T> {
|
|||||||
|
|
||||||
void SetUrl(T request, string url);
|
void SetUrl(T request, string url);
|
||||||
|
|
||||||
|
void SetMethod(T request, string method);
|
||||||
|
|
||||||
bool IsTransitionForwardBack(T request);
|
bool IsTransitionForwardBack(T request);
|
||||||
|
|
||||||
bool IsCspReport(T request);
|
bool IsCspReport(T request);
|
||||||
@ -15,5 +17,9 @@ public interface IRequestAdapter<T> {
|
|||||||
ResourceType GetResourceType(T request);
|
ResourceType GetResourceType(T request);
|
||||||
|
|
||||||
void SetHeader(T request, string header, string value);
|
void SetHeader(T request, string header, string value);
|
||||||
|
|
||||||
|
void SetReferrer(T request, string referrer);
|
||||||
|
|
||||||
|
void SetAllowStoredCredentials(T request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,12 @@
|
|||||||
using TweetLib.Browser.CEF.Interfaces;
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
namespace TweetLib.Browser.CEF.Logic {
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
public abstract class ByteArrayResourceHandlerLogic {
|
||||||
public sealed class ByteArrayResourceHandlerLogic<TResponse> {
|
|
||||||
public delegate void WriteToOut<T>(T dataOut, byte[] dataIn, int position, int length);
|
public delegate void WriteToOut<T>(T dataOut, byte[] dataIn, int position, int length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||||
|
public sealed class ByteArrayResourceHandlerLogic<TResponse> : ByteArrayResourceHandlerLogic {
|
||||||
private readonly ByteArrayResource resource;
|
private readonly ByteArrayResource resource;
|
||||||
private readonly IResponseAdapter<TResponse> responseAdapter;
|
private readonly IResponseAdapter<TResponse> responseAdapter;
|
||||||
|
|
||||||
|
125
lib/TweetLib.Browser.CEF/Logic/ContextMenuLogic.cs
Normal file
125
lib/TweetLib.Browser.CEF/Logic/ContextMenuLogic.cs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
using TweetLib.Browser.Contexts;
|
||||||
|
using TweetLib.Browser.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
|
public abstract class ContextMenuLogic {
|
||||||
|
protected const int CommandCustomFirst = 220;
|
||||||
|
protected const int CommandCustomLast = 250;
|
||||||
|
|
||||||
|
protected static readonly HashSet<int> AllowedCommands = new () {
|
||||||
|
-1, // NotFound
|
||||||
|
110, // Undo
|
||||||
|
111, // Redo
|
||||||
|
112, // Cut
|
||||||
|
113, // Copy
|
||||||
|
114, // Paste
|
||||||
|
115, // Delete
|
||||||
|
116, // SelectAll
|
||||||
|
200, // SpellCheckSuggestion0
|
||||||
|
201, // SpellCheckSuggestion1
|
||||||
|
202, // SpellCheckSuggestion2
|
||||||
|
203, // SpellCheckSuggestion3
|
||||||
|
204, // SpellCheckSuggestion4
|
||||||
|
205, // SpellCheckNoSuggestions
|
||||||
|
206 // AddToDictionary
|
||||||
|
};
|
||||||
|
|
||||||
|
protected sealed class ContextMenuActionRegistry : ContextMenuActionRegistry<int> {
|
||||||
|
private const int CommandUserFirst = 26500;
|
||||||
|
|
||||||
|
protected override int NextId(int n) {
|
||||||
|
return CommandUserFirst + 500 + n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ContextMenuLogic<TModel> : ContextMenuLogic {
|
||||||
|
private readonly IContextMenuHandler? handler;
|
||||||
|
private readonly IMenuModelAdapter<TModel> modelAdapter;
|
||||||
|
private readonly ContextMenuActionRegistry actionRegistry;
|
||||||
|
|
||||||
|
public ContextMenuLogic(IContextMenuHandler? handler, IMenuModelAdapter<TModel> modelAdapter) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.modelAdapter = modelAdapter;
|
||||||
|
this.actionRegistry = new ContextMenuActionRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ContextMenuBuilder : IContextMenuBuilder {
|
||||||
|
private readonly IMenuModelAdapter<TModel> modelAdapter;
|
||||||
|
private readonly ContextMenuActionRegistry actionRegistry;
|
||||||
|
private readonly TModel model;
|
||||||
|
|
||||||
|
public ContextMenuBuilder(IMenuModelAdapter<TModel> modelAdapter, ContextMenuActionRegistry actionRegistry, TModel model) {
|
||||||
|
this.model = model;
|
||||||
|
this.actionRegistry = actionRegistry;
|
||||||
|
this.modelAdapter = modelAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAction(string name, Action action) {
|
||||||
|
var id = actionRegistry.AddAction(action);
|
||||||
|
modelAdapter.AddCommand(model, id, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddActionWithCheck(string name, bool isChecked, Action action) {
|
||||||
|
var id = actionRegistry.AddAction(action);
|
||||||
|
modelAdapter.AddCheckCommand(model, id, name);
|
||||||
|
modelAdapter.SetChecked(model, id, isChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSeparator() {
|
||||||
|
int count = modelAdapter.GetItemCount(model);
|
||||||
|
if (count > 0 && !modelAdapter.IsSeparatorAt(model, count - 1)) { // do not add separators if there is nothing to separate
|
||||||
|
modelAdapter.AddSeparator(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveSeparatorIfLast(TModel model) {
|
||||||
|
int count = modelAdapter.GetItemCount(model);
|
||||||
|
if (count > 0 && modelAdapter.IsSeparatorAt(model, count - 1)) {
|
||||||
|
modelAdapter.RemoveAt(model, count - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnBeforeContextMenu(TModel model, Context context) {
|
||||||
|
for (int i = modelAdapter.GetItemCount(model) - 1; i >= 0; i--) {
|
||||||
|
int command = modelAdapter.GetCommandAt(model, i);
|
||||||
|
|
||||||
|
if (!(AllowedCommands.Contains(command) || command is >= CommandCustomFirst and <= CommandCustomLast)) {
|
||||||
|
modelAdapter.RemoveAt(model, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = modelAdapter.GetItemCount(model) - 2; i >= 0; i--) {
|
||||||
|
if (modelAdapter.IsSeparatorAt(model, i) && modelAdapter.IsSeparatorAt(model, i + 1)) {
|
||||||
|
modelAdapter.RemoveAt(model, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelAdapter.GetItemCount(model) > 0 && modelAdapter.IsSeparatorAt(model, 0)) {
|
||||||
|
modelAdapter.RemoveAt(model, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new ContextMenuBuilder(modelAdapter, actionRegistry, model);
|
||||||
|
builder.AddSeparator();
|
||||||
|
handler?.Show(builder, context);
|
||||||
|
builder.RemoveSeparatorIfLast(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnContextMenuCommand(int commandId) {
|
||||||
|
return actionRegistry.Execute(commandId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnContextMenuDismissed() {
|
||||||
|
actionRegistry.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RunContextMenu() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
|
|
||||||
namespace TweetLib.Browser.CEF.Logic {
|
namespace TweetLib.Browser.CEF.Logic {
|
||||||
public sealed class DownloadRequestClientLogic {
|
public sealed class DownloadRequestClientLogic {
|
||||||
@ -10,24 +11,18 @@ public enum RequestStatus {
|
|||||||
Failed
|
Failed
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly FileStream fileStream;
|
private readonly DownloadCallbacks callbacks;
|
||||||
private readonly Action? onSuccess;
|
|
||||||
private readonly Action<Exception>? onError;
|
|
||||||
|
|
||||||
private bool hasFailed;
|
private bool hasFailed;
|
||||||
|
|
||||||
public DownloadRequestClientLogic(FileStream fileStream, Action? onSuccess, Action<Exception>? onError) {
|
public DownloadRequestClientLogic(DownloadCallbacks callbacks) {
|
||||||
this.fileStream = fileStream;
|
this.callbacks = callbacks;
|
||||||
this.onSuccess = onSuccess;
|
|
||||||
this.onError = onError;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetAuthCredentials(IDisposable callback) {
|
public bool GetAuthCredentials(IDisposable callback) {
|
||||||
callback.Dispose();
|
callback.Dispose();
|
||||||
|
|
||||||
hasFailed = true;
|
hasFailed = true;
|
||||||
fileStream.Dispose();
|
callbacks.OnError(new Exception("This URL requires authentication."));
|
||||||
onError?.Invoke(new Exception("This URL requires authentication."));
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -38,10 +33,9 @@ public void OnDownloadData(Stream data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
data.CopyTo(fileStream);
|
callbacks.OnData(data);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
fileStream.Dispose();
|
callbacks.OnError(e);
|
||||||
onError?.Invoke(e);
|
|
||||||
hasFailed = true;
|
hasFailed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,20 +46,17 @@ public void OnRequestComplete(RequestStatus status) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isEmpty = fileStream.Position == 0;
|
|
||||||
fileStream.Dispose();
|
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case RequestStatus.Failed:
|
case RequestStatus.Success when callbacks.HasData:
|
||||||
onError?.Invoke(new Exception("Unknown error."));
|
callbacks.OnSuccess();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RequestStatus.Success when isEmpty:
|
|
||||||
onError?.Invoke(new Exception("File is empty."));
|
|
||||||
return;
|
|
||||||
|
|
||||||
case RequestStatus.Success:
|
case RequestStatus.Success:
|
||||||
onSuccess?.Invoke();
|
callbacks.OnError(new Exception("File is empty."));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
callbacks.OnError(new Exception("Unknown error."));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
windows/TweetDuck/Browser/Base/CefAdapter.cs
Normal file
15
windows/TweetDuck/Browser/Base/CefAdapter.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefAdapter : ICefAdapter {
|
||||||
|
public static CefAdapter Instance { get; } = new CefAdapter();
|
||||||
|
|
||||||
|
private CefAdapter() {}
|
||||||
|
|
||||||
|
public void RunOnUiThread(Action action) {
|
||||||
|
Cef.UIThreadTaskFactory.StartNew(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
using TweetLib.Browser.CEF.Interfaces;
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Base {
|
namespace TweetDuck.Browser.Base {
|
||||||
sealed class CefBrowserAdapter : IBrowserWrapper<IFrame> {
|
sealed class CefBrowserAdapter : IBrowserWrapper<IFrame, IRequest> {
|
||||||
public string Url => browser.Address;
|
public string Url => browser.Address;
|
||||||
public IFrame MainFrame => browser.GetMainFrame();
|
public IFrame MainFrame => browser.GetMainFrame();
|
||||||
|
|
||||||
@ -16,5 +17,14 @@ public CefBrowserAdapter(ChromiumWebBrowser browser) {
|
|||||||
public void AddWordToDictionary(string word) {
|
public void AddWordToDictionary(string word) {
|
||||||
browser.AddWordToDictionary(word);
|
browser.AddWordToDictionary(word);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IRequest CreateGetRequest() {
|
||||||
|
using var frame = MainFrame;
|
||||||
|
return frame.CreateRequest(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestDownload(IFrame frame, IRequest request, DownloadCallbacks callbacks) {
|
||||||
|
frame.CreateUrlRequest(request, new CefDownloadRequestClient(callbacks));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using CefSharp.WinForms;
|
using CefSharp.WinForms;
|
||||||
using TweetDuck.Browser.Handling;
|
using TweetDuck.Browser.Handling;
|
||||||
@ -11,7 +9,7 @@
|
|||||||
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Base {
|
namespace TweetDuck.Browser.Base {
|
||||||
sealed class CefBrowserComponent : BrowserComponent<IFrame> {
|
sealed class CefBrowserComponent : BrowserComponent<IFrame, IRequest> {
|
||||||
public delegate ContextMenuBase CreateContextMenu(IContextMenuHandler handler);
|
public delegate ContextMenuBase CreateContextMenu(IContextMenuHandler handler);
|
||||||
|
|
||||||
private static readonly CreateContextMenu DefaultContextMenuFactory = handler => new ContextMenuBase(handler);
|
private static readonly CreateContextMenu DefaultContextMenuFactory = handler => new ContextMenuBase(handler);
|
||||||
@ -25,7 +23,7 @@ sealed class CefBrowserComponent : BrowserComponent<IFrame> {
|
|||||||
|
|
||||||
private CreateContextMenu createContextMenu;
|
private CreateContextMenu createContextMenu;
|
||||||
|
|
||||||
public CefBrowserComponent(ChromiumWebBrowser browser, CreateContextMenu createContextMenu = null, bool autoReload = true) : base(new CefBrowserAdapter(browser), CefFrameAdapter.Instance) {
|
public CefBrowserComponent(ChromiumWebBrowser browser, CreateContextMenu createContextMenu = null, bool autoReload = true) : base(new CefBrowserAdapter(browser), CefAdapter.Instance, CefFrameAdapter.Instance, CefRequestAdapter.Instance) {
|
||||||
this.browser = browser;
|
this.browser = browser;
|
||||||
this.browser.LoadingStateChanged += OnLoadingStateChanged;
|
this.browser.LoadingStateChanged += OnLoadingStateChanged;
|
||||||
this.browser.LoadError += OnLoadError;
|
this.browser.LoadError += OnLoadError;
|
||||||
@ -70,26 +68,5 @@ private void OnFrameLoadStart(object sender, FrameLoadStartEventArgs e) {
|
|||||||
private void OnFrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
|
private void OnFrameLoadEnd(object sender, FrameLoadEndEventArgs e) {
|
||||||
base.OnFrameLoadEnd(e.Url, e.Frame);
|
base.OnFrameLoadEnd(e.Url, e.Frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DownloadFile(string url, string path, Action onSuccess, Action<Exception> onError) {
|
|
||||||
Cef.UIThreadTaskFactory.StartNew(() => {
|
|
||||||
var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
|
||||||
|
|
||||||
try {
|
|
||||||
using IFrame frame = browser.GetMainFrame();
|
|
||||||
|
|
||||||
var request = frame.CreateRequest(false);
|
|
||||||
request.Method = "GET";
|
|
||||||
request.Url = url;
|
|
||||||
request.Flags = UrlRequestFlags.AllowStoredCredentials;
|
|
||||||
request.SetReferrer(Url, ReferrerPolicy.Default);
|
|
||||||
|
|
||||||
frame.CreateUrlRequest(request, new CefDownloadRequestClient(fileStream, onSuccess, onError));
|
|
||||||
} catch (Exception e) {
|
|
||||||
fileStream.Dispose();
|
|
||||||
onError?.Invoke(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
namespace TweetDuck.Browser.Base {
|
namespace TweetDuck.Browser.Base {
|
||||||
sealed class CefByteArrayResourceHandler : IResourceHandler {
|
sealed class CefByteArrayResourceHandler : IResourceHandler {
|
||||||
private static readonly ByteArrayResourceHandlerLogic<IResponse>.WriteToOut<Stream> WriteToOut = delegate (Stream dataOut, byte[] dataIn, int position, int length) {
|
private static readonly ByteArrayResourceHandlerLogic.WriteToOut<Stream> WriteToOut = delegate (Stream dataOut, byte[] dataIn, int position, int length) {
|
||||||
dataOut.Write(dataIn, position, length);
|
dataOut.Write(dataIn, position, length);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
31
windows/TweetDuck/Browser/Base/CefContextMenuHandler.cs
Normal file
31
windows/TweetDuck/Browser/Base/CefContextMenuHandler.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Logic;
|
||||||
|
using TweetLib.Browser.Contexts;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
abstract class CefContextMenuHandler : IContextMenuHandler {
|
||||||
|
private readonly ContextMenuLogic<IMenuModel> logic;
|
||||||
|
|
||||||
|
protected CefContextMenuHandler(TweetLib.Browser.Interfaces.IContextMenuHandler handler) {
|
||||||
|
this.logic = new ContextMenuLogic<IMenuModel>(handler, CefMenuModelAdapter.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Context CreateContext(IContextMenuParams parameters);
|
||||||
|
|
||||||
|
public virtual void OnBeforeContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||||
|
logic.OnBeforeContextMenu(model, CreateContext(parameters));
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool OnContextMenuCommand(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
||||||
|
return logic.OnContextMenuCommand((int) commandId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnContextMenuDismissed(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame) {
|
||||||
|
logic.OnContextMenuDismissed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RunContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback) {
|
||||||
|
return logic.RunContextMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,81 +0,0 @@
|
|||||||
using System;
|
|
||||||
using CefSharp;
|
|
||||||
using TweetLib.Browser.CEF.Data;
|
|
||||||
using TweetLib.Browser.Contexts;
|
|
||||||
using TweetLib.Browser.Interfaces;
|
|
||||||
using TweetLib.Core.Features.TweetDeck;
|
|
||||||
using TweetLib.Core.Features.Twitter;
|
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Base {
|
|
||||||
sealed class CefContextMenuModel : IContextMenuBuilder {
|
|
||||||
private readonly IMenuModel model;
|
|
||||||
private readonly ContextMenuActionRegistry<CefMenuCommand> actionRegistry;
|
|
||||||
|
|
||||||
public CefContextMenuModel(IMenuModel model, ContextMenuActionRegistry<CefMenuCommand> actionRegistry) {
|
|
||||||
this.model = model;
|
|
||||||
this.actionRegistry = actionRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddAction(string name, Action action) {
|
|
||||||
var id = actionRegistry.AddAction(action);
|
|
||||||
model.AddItem(id, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddActionWithCheck(string name, bool isChecked, Action action) {
|
|
||||||
var id = actionRegistry.AddAction(action);
|
|
||||||
model.AddCheckItem(id, name);
|
|
||||||
model.SetChecked(id, isChecked);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddSeparator() {
|
|
||||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
|
|
||||||
model.AddSeparator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Context CreateContext(IContextMenuParams parameters, TweetDeckExtraContext extraContext, ImageQuality imageQuality) {
|
|
||||||
var context = new Context();
|
|
||||||
var flags = parameters.TypeFlags;
|
|
||||||
|
|
||||||
var tweet = extraContext?.Tweet;
|
|
||||||
if (tweet != null && !flags.HasFlag(ContextMenuType.Editable)) {
|
|
||||||
context.Tweet = tweet;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Link = GetLink(parameters, extraContext);
|
|
||||||
context.Media = GetMedia(parameters, extraContext, imageQuality);
|
|
||||||
|
|
||||||
if (flags.HasFlag(ContextMenuType.Selection)) {
|
|
||||||
context.Selection = new Selection(parameters.SelectionText, flags.HasFlag(ContextMenuType.Editable));
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Link? GetLink(IContextMenuParams parameters, TweetDeckExtraContext extraContext) {
|
|
||||||
var link = extraContext?.Link;
|
|
||||||
if (link != null) {
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && extraContext?.Media == null) {
|
|
||||||
return new Link(parameters.LinkUrl, parameters.UnfilteredLinkUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Media? GetMedia(IContextMenuParams parameters, TweetDeckExtraContext extraContext, ImageQuality imageQuality) {
|
|
||||||
var media = extraContext?.Media;
|
|
||||||
if (media != null) {
|
|
||||||
return media;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) {
|
|
||||||
return new Media(Media.Type.Image, TwitterUrls.GetMediaLink(parameters.SourceUrl, imageQuality));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Data;
|
||||||
using TweetLib.Browser.CEF.Logic;
|
using TweetLib.Browser.CEF.Logic;
|
||||||
using static TweetLib.Browser.CEF.Logic.DownloadRequestClientLogic.RequestStatus;
|
using static TweetLib.Browser.CEF.Logic.DownloadRequestClientLogic.RequestStatus;
|
||||||
|
|
||||||
@ -8,8 +8,8 @@ namespace TweetDuck.Browser.Base {
|
|||||||
sealed class CefDownloadRequestClient : UrlRequestClient {
|
sealed class CefDownloadRequestClient : UrlRequestClient {
|
||||||
private readonly DownloadRequestClientLogic logic;
|
private readonly DownloadRequestClientLogic logic;
|
||||||
|
|
||||||
public CefDownloadRequestClient(FileStream fileStream, Action onSuccess, Action<Exception> onError) {
|
public CefDownloadRequestClient(DownloadCallbacks callbacks) {
|
||||||
this.logic = new DownloadRequestClientLogic(fileStream, onSuccess, onError);
|
this.logic = new DownloadRequestClientLogic(callbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool GetAuthCredentials(bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) {
|
protected override bool GetAuthCredentials(bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) {
|
||||||
|
42
windows/TweetDuck/Browser/Base/CefMenuModelAdapter.cs
Normal file
42
windows/TweetDuck/Browser/Base/CefMenuModelAdapter.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using CefSharp;
|
||||||
|
using TweetLib.Browser.CEF.Interfaces;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Base {
|
||||||
|
sealed class CefMenuModelAdapter : IMenuModelAdapter<IMenuModel> {
|
||||||
|
public static CefMenuModelAdapter Instance { get; } = new CefMenuModelAdapter();
|
||||||
|
|
||||||
|
private CefMenuModelAdapter() {}
|
||||||
|
|
||||||
|
public int GetItemCount(IMenuModel model) {
|
||||||
|
return model.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddCommand(IMenuModel model, int command, string name) {
|
||||||
|
model.AddItem((CefMenuCommand) command, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetCommandAt(IMenuModel model, int index) {
|
||||||
|
return (int) model.GetCommandIdAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddCheckCommand(IMenuModel model, int command, string name) {
|
||||||
|
model.AddCheckItem((CefMenuCommand) command, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetChecked(IMenuModel model, int command, bool isChecked) {
|
||||||
|
model.SetChecked((CefMenuCommand) command, isChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSeparator(IMenuModel model) {
|
||||||
|
model.AddSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSeparatorAt(IMenuModel model, int index) {
|
||||||
|
return model.GetTypeAt(index) == MenuItemType.Separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(IMenuModel model, int index) {
|
||||||
|
model.RemoveAt(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,10 @@ public void SetUrl(IRequest request, string url) {
|
|||||||
request.Url = url;
|
request.Url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetMethod(IRequest request, string method) {
|
||||||
|
request.Method = method;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsTransitionForwardBack(IRequest request) {
|
public bool IsTransitionForwardBack(IRequest request) {
|
||||||
return request.TransitionType.HasFlag(TransitionType.ForwardBack);
|
return request.TransitionType.HasFlag(TransitionType.ForwardBack);
|
||||||
}
|
}
|
||||||
@ -42,5 +46,13 @@ public ResourceType GetResourceType(IRequest request) {
|
|||||||
public void SetHeader(IRequest request, string header, string value) {
|
public void SetHeader(IRequest request, string header, string value) {
|
||||||
request.SetHeaderByName(header, value, overwrite: true);
|
request.SetHeaderByName(header, value, overwrite: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetReferrer(IRequest request, string referrer) {
|
||||||
|
request.SetReferrer(referrer, ReferrerPolicy.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAllowStoredCredentials(IRequest request) {
|
||||||
|
request.Flags |= UrlRequestFlags.AllowStoredCredentials;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,7 @@ protected override IResourceHandler GetResourceHandler(IWebBrowser browserContro
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) {
|
protected override IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) {
|
||||||
var filter = logic.GetResourceResponseFilter(request, response);
|
return CefResponseFilter.Create(logic.GetResourceResponseFilter(request, response));
|
||||||
return filter == null ? null : new CefResponseFilter(filter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnResourceLoadComplete(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength) {
|
protected override void OnResourceLoadComplete(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength) {
|
||||||
|
@ -5,9 +5,13 @@
|
|||||||
|
|
||||||
namespace TweetDuck.Browser.Base {
|
namespace TweetDuck.Browser.Base {
|
||||||
sealed class CefResponseFilter : IResponseFilter {
|
sealed class CefResponseFilter : IResponseFilter {
|
||||||
|
public static CefResponseFilter Create(ResponseFilterLogic logic) {
|
||||||
|
return logic == null ? null : new CefResponseFilter(logic);
|
||||||
|
}
|
||||||
|
|
||||||
private readonly ResponseFilterLogic logic;
|
private readonly ResponseFilterLogic logic;
|
||||||
|
|
||||||
public CefResponseFilter(ResponseFilterLogic logic) {
|
private CefResponseFilter(ResponseFilterLogic logic) {
|
||||||
this.logic = logic;
|
this.logic = logic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,76 +1,27 @@
|
|||||||
using System.Collections.Generic;
|
using System.Drawing;
|
||||||
using System.Drawing;
|
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Browser.Base;
|
using TweetDuck.Browser.Base;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Utils;
|
using TweetDuck.Utils;
|
||||||
using TweetLib.Browser.CEF.Data;
|
|
||||||
using TweetLib.Browser.Contexts;
|
using TweetLib.Browser.Contexts;
|
||||||
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
|
using TweetLib.Core.Features.Twitter;
|
||||||
|
using IContextMenuHandler = TweetLib.Browser.Interfaces.IContextMenuHandler;
|
||||||
|
|
||||||
namespace TweetDuck.Browser.Handling {
|
namespace TweetDuck.Browser.Handling {
|
||||||
class ContextMenuBase : IContextMenuHandler {
|
class ContextMenuBase : CefContextMenuHandler {
|
||||||
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand) 26500;
|
private const CefMenuCommand MenuOpenDevTools = (CefMenuCommand) 26500;
|
||||||
|
|
||||||
private static readonly HashSet<CefMenuCommand> AllowedCefCommands = new HashSet<CefMenuCommand> {
|
|
||||||
CefMenuCommand.NotFound,
|
|
||||||
CefMenuCommand.Undo,
|
|
||||||
CefMenuCommand.Redo,
|
|
||||||
CefMenuCommand.Cut,
|
|
||||||
CefMenuCommand.Copy,
|
|
||||||
CefMenuCommand.Paste,
|
|
||||||
CefMenuCommand.Delete,
|
|
||||||
CefMenuCommand.SelectAll,
|
|
||||||
CefMenuCommand.SpellCheckSuggestion0,
|
|
||||||
CefMenuCommand.SpellCheckSuggestion1,
|
|
||||||
CefMenuCommand.SpellCheckSuggestion2,
|
|
||||||
CefMenuCommand.SpellCheckSuggestion3,
|
|
||||||
CefMenuCommand.SpellCheckSuggestion4,
|
|
||||||
CefMenuCommand.SpellCheckNoSuggestions,
|
|
||||||
CefMenuCommand.AddToDictionary
|
|
||||||
};
|
|
||||||
|
|
||||||
protected static UserConfig Config => Program.Config.User;
|
protected static UserConfig Config => Program.Config.User;
|
||||||
|
|
||||||
private readonly TweetLib.Browser.Interfaces.IContextMenuHandler handler;
|
public ContextMenuBase(IContextMenuHandler handler) : base(handler) {}
|
||||||
private readonly ContextMenuActionRegistry actionRegistry;
|
|
||||||
|
|
||||||
public ContextMenuBase(TweetLib.Browser.Interfaces.IContextMenuHandler handler) {
|
protected override Context CreateContext(IContextMenuParams parameters) {
|
||||||
this.handler = handler;
|
return CreateContext(parameters, null, Config.TwitterImageQuality);
|
||||||
this.actionRegistry = new ContextMenuActionRegistry();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class ContextMenuActionRegistry : ContextMenuActionRegistry<CefMenuCommand> {
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||||
protected override CefMenuCommand NextId(int n) {
|
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||||
return CefMenuCommand.UserFirst + 500 + n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual Context CreateContext(IContextMenuParams parameters) {
|
|
||||||
return CefContextMenuModel.CreateContext(parameters, null, Config.TwitterImageQuality);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
|
||||||
for (int i = model.Count - 1; i >= 0; i--) {
|
|
||||||
CefMenuCommand command = model.GetCommandIdAt(i);
|
|
||||||
|
|
||||||
if (!AllowedCefCommands.Contains(command) && !(command >= CefMenuCommand.CustomFirst && command <= CefMenuCommand.CustomLast)) {
|
|
||||||
model.RemoveAt(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = model.Count - 2; i >= 0; i--) {
|
|
||||||
if (model.GetTypeAt(i) == MenuItemType.Separator && model.GetTypeAt(i + 1) == MenuItemType.Separator) {
|
|
||||||
model.RemoveAt(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model.Count > 0 && model.GetTypeAt(0) == MenuItemType.Separator) {
|
|
||||||
model.RemoveAt(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddSeparator(model);
|
|
||||||
handler?.Show(new CefContextMenuModel(model, actionRegistry), CreateContext(parameters));
|
|
||||||
RemoveSeparatorIfLast(model);
|
|
||||||
AddLastContextMenuItems(model);
|
AddLastContextMenuItems(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,8 +29,8 @@ protected virtual void AddLastContextMenuItems(IMenuModel model) {
|
|||||||
AddDebugMenuItems(model);
|
AddDebugMenuItems(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags) {
|
||||||
if (actionRegistry.Execute(commandId)) {
|
if (base.OnContextMenuCommand(browserControl, browser, frame, parameters, commandId, eventFlags)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,14 +42,6 @@ public virtual bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser br
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) {
|
|
||||||
actionRegistry.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void AddDebugMenuItems(IMenuModel model) {
|
protected static void AddDebugMenuItems(IMenuModel model) {
|
||||||
if (Config.DevToolsInContextMenu) {
|
if (Config.DevToolsInContextMenu) {
|
||||||
AddSeparator(model);
|
AddSeparator(model);
|
||||||
@ -107,15 +50,54 @@ protected static void AddDebugMenuItems(IMenuModel model) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static void AddSeparator(IMenuModel model) {
|
protected static void AddSeparator(IMenuModel model) {
|
||||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) { // do not add separators if there is nothing to separate
|
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) != MenuItemType.Separator) {
|
||||||
model.AddSeparator();
|
model.AddSeparator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RemoveSeparatorIfLast(IMenuModel model) {
|
protected static Context CreateContext(IContextMenuParams parameters, TweetDeckExtraContext extraContext, ImageQuality imageQuality) {
|
||||||
if (model.Count > 0 && model.GetTypeAt(model.Count - 1) == MenuItemType.Separator) {
|
var context = new Context();
|
||||||
model.RemoveAt(model.Count - 1);
|
var flags = parameters.TypeFlags;
|
||||||
|
|
||||||
|
var tweet = extraContext?.Tweet;
|
||||||
|
if (tweet != null && !flags.HasFlag(ContextMenuType.Editable)) {
|
||||||
|
context.Tweet = tweet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.Link = GetLink(parameters, extraContext);
|
||||||
|
context.Media = GetMedia(parameters, extraContext, imageQuality);
|
||||||
|
|
||||||
|
if (flags.HasFlag(ContextMenuType.Selection)) {
|
||||||
|
context.Selection = new Selection(parameters.SelectionText, flags.HasFlag(ContextMenuType.Editable));
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Link? GetLink(IContextMenuParams parameters, TweetDeckExtraContext extraContext) {
|
||||||
|
var link = extraContext?.Link;
|
||||||
|
if (link != null) {
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && extraContext?.Media == null) {
|
||||||
|
return new Link(parameters.LinkUrl, parameters.UnfilteredLinkUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Media? GetMedia(IContextMenuParams parameters, TweetDeckExtraContext extraContext, ImageQuality imageQuality) {
|
||||||
|
var media = extraContext?.Media;
|
||||||
|
if (media != null) {
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents) {
|
||||||
|
return new Media(Media.Type.Image, TwitterUrls.GetMediaLink(parameters.SourceUrl, imageQuality));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDuck.Browser.Base;
|
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetLib.Browser.Contexts;
|
using TweetLib.Browser.Contexts;
|
||||||
using TweetLib.Core.Features.TweetDeck;
|
using TweetLib.Core.Features.TweetDeck;
|
||||||
@ -31,7 +30,7 @@ public ContextMenuBrowser(FormBrowser form, IContextMenuHandler handler, TweetDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override Context CreateContext(IContextMenuParams parameters) {
|
protected override Context CreateContext(IContextMenuParams parameters) {
|
||||||
return CefContextMenuModel.CreateContext(parameters, extraContext, Config.TwitterImageQuality);
|
return CreateContext(parameters, extraContext, Config.TwitterImageQuality);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model) {
|
||||||
|
@ -95,15 +95,17 @@
|
|||||||
<Compile Include="Application\FileDialogs.cs" />
|
<Compile Include="Application\FileDialogs.cs" />
|
||||||
<Compile Include="Application\MessageDialogs.cs" />
|
<Compile Include="Application\MessageDialogs.cs" />
|
||||||
<Compile Include="Application\SystemHandler.cs" />
|
<Compile Include="Application\SystemHandler.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefAdapter.cs" />
|
||||||
<Compile Include="Browser\Base\CefBrowserAdapter.cs" />
|
<Compile Include="Browser\Base\CefBrowserAdapter.cs" />
|
||||||
<Compile Include="Browser\Base\CefBrowserComponent.cs" />
|
<Compile Include="Browser\Base\CefBrowserComponent.cs" />
|
||||||
<Compile Include="Browser\Base\CefContextMenuModel.cs" />
|
<Compile Include="Browser\Base\CefContextMenuHandler.cs" />
|
||||||
<Compile Include="Browser\Base\CefDownloadRequestClient.cs" />
|
<Compile Include="Browser\Base\CefDownloadRequestClient.cs" />
|
||||||
<Compile Include="Browser\Base\CefDragDataAdapter.cs" />
|
<Compile Include="Browser\Base\CefDragDataAdapter.cs" />
|
||||||
<Compile Include="Browser\Base\CefDragHandler.cs" />
|
<Compile Include="Browser\Base\CefDragHandler.cs" />
|
||||||
<Compile Include="Browser\Base\CefErrorCodeAdapter.cs" />
|
<Compile Include="Browser\Base\CefErrorCodeAdapter.cs" />
|
||||||
<Compile Include="Browser\Base\CefFrameAdapter.cs" />
|
<Compile Include="Browser\Base\CefFrameAdapter.cs" />
|
||||||
<Compile Include="Browser\Base\CefLifeSpanHandler.cs" />
|
<Compile Include="Browser\Base\CefLifeSpanHandler.cs" />
|
||||||
|
<Compile Include="Browser\Base\CefMenuModelAdapter.cs" />
|
||||||
<Compile Include="Browser\Base\CefRequestAdapter.cs" />
|
<Compile Include="Browser\Base\CefRequestAdapter.cs" />
|
||||||
<Compile Include="Browser\Base\CefRequestHandler.cs" />
|
<Compile Include="Browser\Base\CefRequestHandler.cs" />
|
||||||
<Compile Include="Browser\Base\CefResourceHandlerFactory.cs" />
|
<Compile Include="Browser\Base\CefResourceHandlerFactory.cs" />
|
||||||
|
Loading…
Reference in New Issue
Block a user