using System.Drawing; using System.Windows.Forms; using CefSharp.WinForms; using TweetDuck.Browser.Adapters; using TweetDuck.Browser.Handling; using TweetDuck.Configuration; using TweetDuck.Controls; using TweetDuck.Utils; using TweetLib.Browser.Interfaces; using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Twitter; using TweetLib.Core.Systems.Configuration; namespace TweetDuck.Browser.Notification { abstract partial class FormNotificationBase : Form { protected static UserConfig Config => Program.Config.User; protected delegate NotificationBrowser CreateBrowserImplFunc(FormNotificationBase form, IBrowserComponent browserComponent); protected virtual Point PrimaryLocation { get { Screen screen; if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length) { screen = Screen.AllScreens[Config.NotificationDisplay - 1]; } else { screen = Screen.FromControl(owner); } int edgeDist = Config.NotificationEdgeDistance; switch (Config.NotificationPosition) { case DesktopNotification.Position.TopLeft: return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + edgeDist); case DesktopNotification.Position.TopRight: return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist); case DesktopNotification.Position.BottomLeft: return new Point(screen.WorkingArea.X + edgeDist, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height); case DesktopNotification.Position.BottomRight: return new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + screen.WorkingArea.Height - edgeDist - Height); case DesktopNotification.Position.Custom: if (!Config.IsCustomNotificationPositionSet) { Config.CustomNotificationPosition = new Point(screen.WorkingArea.X + screen.WorkingArea.Width - edgeDist - Width, screen.WorkingArea.Y + edgeDist); Config.Save(); } return Config.CustomNotificationPosition; } return Location; } } protected bool IsNotificationVisible => Location != ControlExtensions.InvisibleLocation; protected virtual bool CanDragWindow => true; public new Point Location { get { return base.Location; } set { Visible = (base.Location = value) != ControlExtensions.InvisibleLocation; FormBorderStyle = NotificationBorderStyle; } } protected virtual FormBorderStyle NotificationBorderStyle { get { if (WindowsUtils.ShouldAvoidToolWindow && Visible) { // Visible = workaround for alt+tab return FormBorderStyle.FixedSingle; } else { return FormBorderStyle.FixedToolWindow; } } } protected override bool ShowWithoutActivation => true; protected float DpiScale { get; } protected double SizeScale => DpiScale * Config.ZoomLevel / 100.0; private readonly FormBrowser owner; protected readonly IBrowserComponent browserComponent; private readonly NotificationBrowser browserImpl; #pragma warning disable IDE0069 // Disposable fields should be disposed protected readonly ChromiumWebBrowser browser; #pragma warning restore IDE0069 // Disposable fields should be disposed private readonly ResourceHandlerNotification resourceHandler = new ResourceHandlerNotification(); private DesktopNotification currentNotification; private int pauseCounter; public string CurrentTweetUrl => currentNotification?.TweetUrl; public string CurrentQuoteUrl => currentNotification?.QuoteUrl; protected bool IsPaused => pauseCounter > 0; protected internal bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position)); public bool FreezeTimer { get; set; } public bool ContextMenuOpen { get; set; } protected FormNotificationBase(FormBrowser owner, CreateBrowserImplFunc createBrowserImpl) { InitializeComponent(); this.owner = owner; this.owner.FormClosed += owner_FormClosed; this.browser = new ChromiumWebBrowser(NotificationBrowser.BlankURL) { RequestHandler = new RequestHandlerBase(false) }; this.browserComponent = new ComponentImpl(browser, this); this.browserImpl = createBrowserImpl(this, browserComponent); this.browser.Dock = DockStyle.None; this.browser.ClientSize = ClientSize; Controls.Add(browser); Disposed += (sender, args) => { this.owner.FormClosed -= owner_FormClosed; this.browserImpl.Dispose(); this.browser.Dispose(); }; DpiScale = this.GetDPIScale(); // ReSharper disable once VirtualMemberCallInContructor UpdateTitle(); } protected sealed class ComponentImpl : CefBrowserComponent { private readonly FormNotificationBase owner; public ComponentImpl(ChromiumWebBrowser browser, FormNotificationBase owner) : base(browser) { this.owner = owner; } protected override ContextMenuBase SetupContextMenu(IContextMenuHandler handler) { return new ContextMenuNotification(owner, handler); } protected override CefResourceHandlerFactory SetupResourceHandlerFactory(IResourceRequestHandler handler) { var registry = new CefResourceHandlerRegistry(); registry.RegisterStatic(NotificationBrowser.BlankURL, string.Empty); registry.RegisterDynamic(TwitterUrls.TweetDeck, owner.resourceHandler); return new CefResourceHandlerFactory(handler, registry); } } protected override void Dispose(bool disposing) { if (disposing) { components?.Dispose(); resourceHandler.Dispose(); } base.Dispose(disposing); } protected override void WndProc(ref Message m) { if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanDragWindow) { // WM_SYSCOMMAND, SC_MOVE return; } base.WndProc(ref m); } // event handlers private void owner_FormClosed(object sender, FormClosedEventArgs e) { Close(); } // notification methods public virtual void HideNotification() { browser.Load(NotificationBrowser.BlankURL); DisplayTooltip(null); Location = ControlExtensions.InvisibleLocation; currentNotification = null; } public virtual void FinishCurrentNotification() {} public virtual void PauseNotification() { if (pauseCounter++ == 0 && IsNotificationVisible) { Location = ControlExtensions.InvisibleLocation; } } public virtual void ResumeNotification() { if (pauseCounter > 0) { --pauseCounter; } } protected virtual void LoadTweet(DesktopNotification tweet) { currentNotification = tweet; resourceHandler.SetHTML(browserImpl.GetTweetHTML(tweet)); browser.Load(TwitterUrls.TweetDeck); DisplayTooltip(null); } protected virtual void SetNotificationSize(int width, int height) { browser.ClientSize = ClientSize = new Size(BrowserUtils.Scale(width, SizeScale), BrowserUtils.Scale(height, SizeScale)); } protected virtual void UpdateTitle() { string title = currentNotification?.ColumnTitle; Text = string.IsNullOrEmpty(title) || !Config.DisplayNotificationColumn ? Program.BrandName : $"{Program.BrandName} - {title}"; } public void ShowTweetDetail() { if (currentNotification != null && owner.ShowTweetDetail(currentNotification.ColumnId, currentNotification.ChirpId, currentNotification.TweetUrl)) { FinishCurrentNotification(); } } public void MoveToVisibleLocation() { bool needsReactivating = Location == ControlExtensions.InvisibleLocation; Location = PrimaryLocation; if (needsReactivating) { NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE); } } public void DisplayTooltip(string text) { if (string.IsNullOrEmpty(text)) { toolTip.Hide(this); } else { Point position = PointToClient(Cursor.Position); position.Offset(20, 5); toolTip.Show(text, this, position); } } } }