using System.Drawing; using System.Windows.Forms; using CefSharp.WinForms; using TweetDuck.Browser.Data; using TweetDuck.Browser.Handling; using TweetDuck.Browser.Handling.General; using TweetDuck.Configuration; using TweetDuck.Controls; using TweetDuck.Management.Analytics; using TweetDuck.Utils; using TweetLib.Core.Features.Notifications; using TweetLib.Core.Features.Twitter; namespace TweetDuck.Browser.Notification { abstract partial class FormNotificationBase : Form, AnalyticsFile.IProvider { public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandlers.ForBytes(Properties.Resources.avatar, "image/png")); protected const string BlankURL = TwitterUrls.TweetDeck + "/?blank"; public static string FontSize = null; public static string HeadLayout = null; protected static UserConfig Config => Program.Config.User; protected static int FontSizeLevel { get => FontSize switch { "largest" => 4, "large" => 3, "small" => 1, "smallest" => 0, _ => 2 }; } 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; } } } public AnalyticsFile AnalyticsFile => owner.AnalyticsFile; protected override bool ShowWithoutActivation => true; protected float DpiScale { get; } protected double SizeScale => DpiScale * Config.ZoomLevel / 100.0; private readonly FormBrowser owner; #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; public bool CanViewDetail => currentNotification != null && !string.IsNullOrEmpty(currentNotification.ColumnId) && !string.IsNullOrEmpty(currentNotification.ChirpId); protected bool IsPaused => pauseCounter > 0; protected bool IsCursorOverBrowser => browser.Bounds.Contains(PointToClient(Cursor.Position)); public bool FreezeTimer { get; set; } public bool ContextMenuOpen { get; set; } protected FormNotificationBase(FormBrowser owner, bool enableContextMenu) { InitializeComponent(); this.owner = owner; this.owner.FormClosed += owner_FormClosed; var resourceRequestHandler = new ResourceRequestHandlerBase(); var resourceHandlers = resourceRequestHandler.ResourceHandlers; resourceHandlers.Register(BlankURL, ResourceHandlers.ForString(string.Empty)); resourceHandlers.Register(TwitterUrls.TweetDeck, () => this.resourceHandler); resourceHandlers.Register(AppLogo); this.browser = new ChromiumWebBrowser(BlankURL) { MenuHandler = new ContextMenuNotification(this, enableContextMenu), JsDialogHandler = new JavaScriptDialogHandler(), LifeSpanHandler = new CustomLifeSpanHandler(), RequestHandler = new RequestHandlerBase(false), ResourceRequestHandlerFactory = resourceRequestHandler.SelfFactory }; this.browser.Dock = DockStyle.None; this.browser.ClientSize = ClientSize; this.browser.SetupZoomEvents(); Controls.Add(browser); Disposed += (sender, args) => { this.owner.FormClosed -= owner_FormClosed; this.browser.Dispose(); }; DpiScale = this.GetDPIScale(); // ReSharper disable once VirtualMemberCallInContructor UpdateTitle(); } 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(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 abstract string GetTweetHTML(DesktopNotification tweet); protected virtual void LoadTweet(DesktopNotification tweet) { currentNotification = tweet; resourceHandler.SetHTML(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); } } 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); } } } }