diff --git a/Application/App.xaml.cs b/Application/App.xaml.cs
index 2e10bc0..8ac0960 100644
--- a/Application/App.xaml.cs
+++ b/Application/App.xaml.cs
@@ -85,8 +85,9 @@ namespace FeedCenter
var splashWindow = new SplashWindow();
splashWindow.ShowDialog();
- // Set whether we should auto-start
- Current.SetStartWithWindows(Settings.Default.StartWithWindows);
+ // Set whether we should auto-start (if not debugging)
+ if (!IsDebugBuild)
+ Current.SetStartWithWindows(Settings.Default.StartWithWindows);
// Initialize the window
mainWindow.Initialize();
diff --git a/Application/FeedCenter.csproj b/Application/FeedCenter.csproj
index 13d466b..4e1e368 100644
--- a/Application/FeedCenter.csproj
+++ b/Application/FeedCenter.csproj
@@ -144,9 +144,6 @@
MinimumRecommendedRules.ruleset
-
- ..\..\Common.Wpf.MarkupExtensions\bin\Release\Common.Wpf.MarkupExtensions.dll
-
False
..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.dll
@@ -161,6 +158,10 @@
..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll
True
+
+ False
+ ..\..\..\Public\namebasedgrid\bin\Debug\NameBasedGrid.dll
+
@@ -190,7 +191,18 @@
FeedChooserWindow.xaml
+
+
+
+
+
+
+
+
+
+
+
MSBuild:Compile
Designer
@@ -277,7 +289,7 @@
Designer
MSBuild:Compile
-
+
MSBuild:Compile
Designer
@@ -293,7 +305,7 @@
-
+
MainWindow.xaml
Code
diff --git a/Application/FeedCenter.csproj.DotSettings b/Application/FeedCenter.csproj.DotSettings
index 2a8adce..688c02c 100644
--- a/Application/FeedCenter.csproj.DotSettings
+++ b/Application/FeedCenter.csproj.DotSettings
@@ -1,2 +1,3 @@
- True
\ No newline at end of file
+ True
+ True
\ No newline at end of file
diff --git a/Application/MainWindow.xaml.cs b/Application/MainWindow.xaml.cs
deleted file mode 100644
index ef21f2c..0000000
--- a/Application/MainWindow.xaml.cs
+++ /dev/null
@@ -1,1418 +0,0 @@
-using Common.Debug;
-using Common.Helpers;
-using Common.IO;
-using Common.Update;
-using Common.Wpf.Extensions;
-using FeedCenter.Options;
-using FeedCenter.Properties;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Threading;
-using System.Web.UI;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-using System.Windows.Interop;
-using System.Windows.Media;
-using Common.Internet;
-
-namespace FeedCenter
-{
- public partial class MainWindow
- {
- private readonly string[] _chromeExtensions = { "chrome-extension://ehojfdcmnajoklleckniaifaijfnkpbi/subscribe.html?", "chrome-extension://nlbjncdgjeocebhnmkbbbdekmmmcbfjd/subscribe.html?" };
-
- #region Member variables
-
- private int _feedIndex;
- private Feed _currentFeed;
- private DateTime _lastFeedDisplay;
- private DateTime _lastFeedRead;
- private System.Windows.Forms.Timer _mainTimer;
- private FeedCenterEntities _database;
-
- private InterprocessMessageListener _commandLineListener;
- private BackgroundWorker _feedReadWorker;
-
- #endregion
-
- #region Constructor
-
- public MainWindow()
- {
- InitializeComponent();
- }
-
- private bool _activated;
- protected override void OnActivated(EventArgs e)
- {
- base.OnActivated(e);
-
- if (_activated)
- return;
-
- _activated = true;
-
- // Load the lock state
- HandleWindowLockState();
-
- // Watch for size and location changes
- SizeChanged += HandleWindowSizeChanged;
- LocationChanged += HandleWindowLocationChanged;
-
- // Watch for setting changes
- Settings.Default.PropertyChanged += HandlePropertyChanged;
- }
-
- public void Initialize()
- {
- // Setup the update message properties and callbacks
- UpdateCheck.ApplicationName = Properties.Resources.ApplicationDisplayName;
- UpdateCheck.UpdateServer = Settings.Default.VersionLocation;
- UpdateCheck.UpdateFile = Settings.Default.VersionFile;
- UpdateCheck.ApplicationShutdown = ApplicationShutdown;
- UpdateCheck.ApplicationCurrentMessage = ApplicationCurrentMessage;
- UpdateCheck.ApplicationUpdateMessage = ApplicationUpdateMessage;
-
- // Show the notification icon
- NotificationIcon.Initialize(this);
-
- // Load window settings
- LoadWindowSettings();
-
- // Set the foreground color to something that can be seen
- LinkTextList.Foreground = (System.Drawing.SystemColors.Desktop.GetBrightness() < 0.5) ? Brushes.White : Brushes.Black;
- HeaderLabel.Foreground = LinkTextList.Foreground;
-
- // Create the background worker that does the actual reading
- _feedReadWorker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
- _feedReadWorker.DoWork += HandleFeedReadWorkerStart;
- _feedReadWorker.ProgressChanged += HandleFeedReadWorkerProgressChanged;
- _feedReadWorker.RunWorkerCompleted += HandleFeedReadWorkerCompleted;
-
- // Setup the database
- _database = new FeedCenterEntities();
-
- // Initialize the command line listener
- _commandLineListener = new InterprocessMessageListener(Properties.Resources.ApplicationName);
- _commandLineListener.MessageReceived += HandleCommandLine;
-
- // Handle any command line we were started with
- HandleCommandLine(null, new InterprocessMessageListener.InterprocessMessageEventArgs(Environment.CommandLine));
-
- // Create a timer to keep track of things we need to do
- InitializeTimer();
-
- // Initialize the feed display
- InitializeFeed();
-
- // Check for update
- if (Settings.Default.CheckVersionAtStartup)
- UpdateCheck.CheckForUpdate();
-
- // Show the link if updates are available
- if (UpdateCheck.UpdateAvailable)
- NewVersionLink.Visibility = Visibility.Visible;
-
- Tracer.WriteLine("MainForm creation finished");
- }
-
- private static bool ApplicationUpdateMessage(string title, string message)
- {
- return MessageBox.Show(message, title, MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes;
- }
-
- private static void ApplicationCurrentMessage(string title, string message)
- {
- MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Information);
- }
-
- private static void ApplicationShutdown()
- {
- Application.Current.Shutdown();
- }
-
- #endregion
-
- #region Window overrides
-
- protected override void OnClosing(CancelEventArgs e)
- {
- base.OnClosing(e);
-
- // Ditch the worker
- if (_feedReadWorker != null)
- {
- _feedReadWorker.CancelAsync();
- _feedReadWorker.Dispose();
- }
-
- // Get rid of the timer
- TerminateTimer();
-
- // Save current window settings
- SaveWindowSettings();
-
- // Save settings
- Settings.Default.Save();
-
- // Save options
- _database.SaveChanges();
-
- // Get rid of the notification icon
- NotificationIcon.Dispose();
- }
-
- #endregion
-
- #region Setting events
-
- private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- // Make sure we're on the right thread
- if (!Dispatcher.CheckAccess())
- {
- Dispatcher.Invoke(new EventHandler(HandlePropertyChanged), sender, e);
- return;
- }
-
- if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.MultipleLineDisplay))
- {
- // Update the current feed
- DisplayFeed();
- }
- else if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.WindowLocked))
- {
- // Update the window for the new window lock value
- HandleWindowLockState();
- }
- else if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.ToolbarLocation))
- {
- // Update the window for the toolbar location
- switch (Settings.Default.ToolbarLocation)
- {
- case Dock.Top:
- Grid.SetRow(NavigationToolbarTray, MainGrid.GetRowIndex(TopToolbarRow));
-
- break;
- case Dock.Bottom:
- Grid.SetRow(NavigationToolbarTray, MainGrid.GetRowIndex(BottomToolbarRow));
- break;
- }
- }
- }
-
- #endregion
-
- #region Window methods
-
- private void LoadWindowSettings()
- {
- // Get the last window location
- var windowLocation = Settings.Default.WindowLocation;
-
- // Set the window into position
- Left = windowLocation.X;
- Top = windowLocation.Y;
-
- // Get the last window size
- var windowSize = Settings.Default.WindowSize;
-
- // Set the window into the previous size if it is valid
- if (!windowSize.Width.Equals(0) && !windowSize.Height.Equals(0))
- {
- Width = windowSize.Width;
- Height = windowSize.Height;
- }
-
- // Set the location of the navigation tray
- switch (Settings.Default.ToolbarLocation)
- {
- case Dock.Top:
- Grid.SetRow(NavigationToolbarTray, MainGrid.GetRowIndex(TopToolbarRow));
- break;
- case Dock.Bottom:
- Grid.SetRow(NavigationToolbarTray, MainGrid.GetRowIndex(BottomToolbarRow));
- break;
- }
-
- // Load the lock state
- HandleWindowLockState();
- }
-
- private void SaveWindowSettings()
- {
- // Set the last window location
- Settings.Default.WindowLocation = new Point(Left, Top);
-
- // Set the last window size
- Settings.Default.WindowSize = new Size(Width, Height);
-
- // Save the dock on the navigation tray
- Settings.Default.ToolbarLocation = Grid.GetRow(NavigationToolbarTray) == MainGrid.GetRowIndex(TopToolbarRow) ? Dock.Top : Dock.Bottom;
-
- // Save settings
- Settings.Default.Save();
- }
-
- private void HandleWindowLockState()
- {
- // Set the resize mode for the window
- ResizeMode = Settings.Default.WindowLocked ? ResizeMode.NoResize : ResizeMode.CanResize;
-
- // Show or hide the border
- WindowBorder.BorderBrush = Settings.Default.WindowLocked ? SystemColors.ActiveBorderBrush : Brushes.Transparent;
-
- // Update the borders
- UpdateBorder();
- }
-
- #endregion
-
- #region Header events
-
- private void HandleHeaderLabelMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
- {
- // Ignore if the window is locked
- if (Settings.Default.WindowLocked)
- return;
-
- // Start dragging
- DragMove();
- }
-
- private void HandleCloseButtonClick(object sender, RoutedEventArgs e)
- {
- // Close the window
- Close();
- }
-
- #endregion
-
- #region Timer handling
-
- private void InitializeTimer()
- {
- _mainTimer = new System.Windows.Forms.Timer { Interval = 1000 };
- _mainTimer.Tick += HandleMainTimerTick;
- }
-
- private void TerminateTimer()
- {
- StopTimer();
-
- _mainTimer.Dispose();
- }
-
- private void StartTimer()
- {
- _mainTimer.Start();
- }
-
- private void StopTimer()
- {
- _mainTimer.Stop();
- }
-
- private void HandleMainTimerTick(object sender, EventArgs e)
- {
- // If the background worker is busy then don't do anything
- if (_feedReadWorker.IsBusy)
- return;
-
- // Stop the timer for now
- StopTimer();
-
- // Move to the next feed if the scroll interval has expired and the mouse isn't hovering
- if (LinkTextList.IsMouseOver)
- _lastFeedDisplay = DateTime.Now;
- else if (DateTime.Now - _lastFeedDisplay >= Settings.Default.FeedScrollInterval)
- NextFeed();
-
- // Check to see if we should try to read the feeds
- if (DateTime.Now - _lastFeedRead >= Settings.Default.FeedCheckInterval)
- ReadFeeds();
-
- // Get the timer going again
- StartTimer();
- }
-
- #endregion
-
- #region Feed display
-
- private void UpdateToolbarButtonState()
- {
- // Cache the feed count to save (a little) time
- var feedCount = _database.Feeds.Count();
-
- // Set button states
- PreviousToolbarButton.IsEnabled = (feedCount > 1);
- NextToolbarButton.IsEnabled = (feedCount > 1);
- RefreshToolbarButton.IsEnabled = (feedCount > 0);
- FeedButton.IsEnabled = (feedCount > 0);
- OpenAllToolbarButton.IsEnabled = (feedCount > 0);
- MarkReadToolbarButton.IsEnabled = (feedCount > 0);
- FeedLabel.Visibility = (feedCount == 0 ? Visibility.Hidden : Visibility.Visible);
- FeedButton.Visibility = (feedCount > 1 ? Visibility.Hidden : Visibility.Visible);
- }
-
- private void InitializeFeed()
- {
- UpdateToolbarButtonState();
-
- // Cache the feed count to save (a little) time
- var feedCount = _database.Feeds.Count();
-
- // Clear the link list
- LinkTextList.Items.Clear();
-
- // Reset the feed index
- _feedIndex = -1;
-
- // Start the timer
- StartTimer();
-
- // Don't go further if we have no feeds
- if (feedCount == 0)
- return;
-
- // Get the first feed
- NextFeed();
- }
-
- private void NextFeed()
- {
- var feedCount = _database.Feeds.Count();
-
- if (feedCount == 0)
- return;
-
- if (Settings.Default.DisplayEmptyFeeds)
- {
- // Increment the index and adjust if we've gone around the end
- _feedIndex = (_feedIndex + 1) % feedCount;
-
- // Get the feed
- _currentFeed = _database.Feeds.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
- }
- else
- {
- // Keep track if we found something
- var found = false;
-
- // Remember our starting position
- var startIndex = (_feedIndex == -1 ? 0 : _feedIndex);
-
- // Increment the index and adjust if we've gone around the end
- _feedIndex = (_feedIndex + 1) % feedCount;
-
- // Loop until we come back to the start index
- do
- {
- // Get the feed
- _currentFeed = _database.Feeds.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
-
- // If the current feed has unread items then we can display it
- if (_currentFeed.Items.Count(item => !item.BeenRead) > 0)
- {
- found = true;
- break;
- }
-
- // Increment the index and adjust if we've gone around the end
- _feedIndex = (_feedIndex + 1) % feedCount;
- }
- while (_feedIndex != startIndex);
-
- // If nothing was found then clear the current feed
- if (!found)
- {
- _feedIndex = -1;
- _currentFeed = null;
- }
- }
-
- // Update the feed timestamp
- _lastFeedDisplay = DateTime.Now;
-
- // Update the display
- DisplayFeed();
- }
-
- private void PreviousFeed()
- {
- var feedCount = _database.Feeds.Count();
-
- if (feedCount == 0)
- return;
-
- if (Settings.Default.DisplayEmptyFeeds)
- {
- // Decrement the feed index
- _feedIndex--;
-
- // If we've gone below the start of the list then reset to the end
- if (_feedIndex < 0)
- _feedIndex = feedCount - 1;
-
- // Get the feed
- _currentFeed = _database.Feeds.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
- }
- else
- {
- // Keep track if we found something
- var found = false;
-
- // Remember our starting position
- var startIndex = (_feedIndex == -1 ? 0 : _feedIndex);
-
- // Decrement the feed index
- _feedIndex--;
-
- // If we've gone below the start of the list then reset to the end
- if (_feedIndex < 0)
- _feedIndex = feedCount - 1;
-
- // Loop until we come back to the start index
- do
- {
- // Get the feed
- _currentFeed = _database.Feeds.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
-
- // If the current feed has unread items then we can display it
- if (_currentFeed.Items.Count(item => !item.BeenRead) > 0)
- {
- found = true;
- break;
- }
-
- // Decrement the feed index
- _feedIndex--;
-
- // If we've gone below the start of the list then reset to the end
- if (_feedIndex < 0)
- _feedIndex = feedCount - 1;
- }
- while (_feedIndex != startIndex);
-
- // If nothing was found then clear the current feed
- if (!found)
- {
- _feedIndex = -1;
- _currentFeed = null;
- }
- }
-
- // Update the feed timestamp
- _lastFeedDisplay = DateTime.Now;
-
- // Update the display
- DisplayFeed();
- }
-
- private void UpdateOpenAllButton()
- {
- var multipleOpenAction = _currentFeed.MultipleOpenAction;
-
- switch (multipleOpenAction)
- {
- case MultipleOpenAction.IndividualPages:
- OpenAllToolbarButton.ToolTip = Properties.Resources.openAllMultipleToolbarButton;
- break;
- case MultipleOpenAction.SinglePage:
- OpenAllToolbarButton.ToolTip = Properties.Resources.openAllSingleToolbarButton;
- break;
- }
- }
-
- private void DisplayFeed()
- {
- // Just clear the display if we have no feed
- if (_currentFeed == null)
- {
- FeedLabel.Text = string.Empty;
- FeedButton.Visibility = Visibility.Hidden;
- LinkTextList.Items.Clear();
-
- return;
- }
-
- // Set the header to the feed title
- FeedLabel.Text = (_currentFeed.Name.Length > 0 ? _currentFeed.Name : _currentFeed.Title);
- FeedButton.Visibility = _database.Feeds.Count() > 1 ? Visibility.Visible : Visibility.Hidden;
-
- // Clear the current list
- LinkTextList.Items.Clear();
-
- // Sort the items by sequence
- var sortedItems = _currentFeed.Items.Where(item => !item.BeenRead).OrderBy(item => item.Sequence);
-
- // Loop over all items in the current feed
- foreach (var feedItem in sortedItems)
- {
- // Add the list item
- LinkTextList.Items.Add(feedItem);
- }
-
- UpdateOpenAllButton();
- }
-
- private void MarkAllItemsAsRead()
- {
- // Loop over all items and mark them as read
- foreach (FeedItem feedItem in LinkTextList.Items)
- feedItem.BeenRead = true;
-
- // Save the changes
- _database.SaveChanges();
-
- // Clear the list
- LinkTextList.Items.Clear();
- }
-
- #endregion
-
- #region Feed reading
-
- private class FeedReadWorkerInput
- {
- public bool ForceRead;
- public Feed Feed;
- }
-
- private void SetProgressMode(bool value, int feedCount)
- {
- // Reset the progress bar if we need it
- if (value)
- {
- FeedReadProgress.Value = 0;
- FeedReadProgress.Maximum = feedCount + 2;
- FeedReadProgress.Visibility = Visibility.Visible;
- }
- else
- {
- FeedReadProgress.Visibility = Visibility.Collapsed;
- }
- }
-
- private void ReadCurrentFeed(bool forceRead = false)
- {
- // Don't read if we're already working
- if (_feedReadWorker.IsBusy)
- return;
-
- // Don't read if there is nothing to read
- if (!_database.Feeds.Any())
- return;
-
- // Switch to progress mode
- SetProgressMode(true, 1);
-
- // Create the input class
- var workerInput = new FeedReadWorkerInput { ForceRead = forceRead, Feed = _currentFeed };
-
- // Start the worker
- _feedReadWorker.RunWorkerAsync(workerInput);
- }
-
- private void ReadFeeds(bool forceRead = false)
- {
- // Don't read if we're already working
- if (_feedReadWorker.IsBusy)
- return;
-
- // Don't read if there is nothing to read
- if (!_database.Feeds.Any())
- return;
-
- // Switch to progress mode
- SetProgressMode(true, _database.Feeds.Count());
-
- // Create the input class
- var workerInput = new FeedReadWorkerInput { ForceRead = forceRead, Feed = null };
-
- // Start the worker
- _feedReadWorker.RunWorkerAsync(workerInput);
- }
-
- private void HandleFeedReadWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
- {
- // Set progress
- FeedReadProgress.Value = e.ProgressPercentage;
- }
-
- private void HandleFeedReadWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
- {
- // Reset the database to current settings
- ResetDatabase();
-
- // Save settings
- Settings.Default.Save();
-
- // Set the read timestamp
- _lastFeedRead = DateTime.Now;
-
- // Update the current feed
- DisplayFeed();
-
- // Switch to normal mode
- SetProgressMode(false, 0);
-
- // Check for update
- if (UpdateCheck.UpdateAvailable)
- NewVersionLink.Visibility = Visibility.Visible;
-
- UpdateErrorLink();
- }
-
- private void UpdateErrorLink()
- {
- var feedErrorCount = _database.Feeds.Count(f => f.LastReadResult != FeedReadResult.Success);
-
- // Set the visibility of the error link
- FeedErrorsLink.Visibility = feedErrorCount == 0 ? Visibility.Collapsed : Visibility.Visible;
-
- // Set the text to match the number of errors
- FeedErrorsLink.Text = feedErrorCount == 1
- ? Properties.Resources.FeedErrorLink
- : string.Format(Properties.Resources.FeedErrorsLink, feedErrorCount);
- }
-
- private static void HandleFeedReadWorkerStart(object sender, DoWorkEventArgs e)
- {
- // Create a new database instance for just this thread
- var database = new FeedCenterEntities();
-
- // Get the worker
- var worker = (BackgroundWorker) sender;
-
- // Get the input information
- var workerInput = (FeedReadWorkerInput) e.Argument;
-
- // Setup for progress
- var currentProgress = 0;
-
- // Create the list of feeds to read
- var feedsToRead = new List();
-
- // If we have a single feed then add it to the list - otherwise add them all
- if (workerInput.Feed != null)
- feedsToRead.Add(database.Feeds.First(feed => feed.ID == workerInput.Feed.ID));
- else
- feedsToRead.AddRange(database.Feeds);
-
- // Loop over each feed and read it
- foreach (var feed in feedsToRead)
- {
- // Read the feed
- feed.Read(database, workerInput.ForceRead);
-
- // Increment progress
- currentProgress += 1;
-
- // Report progress
- worker.ReportProgress(currentProgress);
- }
-
- // Save the changes
- database.SaveChanges();
-
- // Increment progress
- currentProgress += 1;
-
- // Report progress
- worker.ReportProgress(currentProgress);
-
- // See if we're due for a version check
- if (DateTime.Now - Settings.Default.LastVersionCheck >= Settings.Default.VersionCheckInterval)
- {
- // Get the update information
- UpdateCheck.CheckForUpdate();
-
- // Update the last check time
- Settings.Default.LastVersionCheck = DateTime.Now;
- }
-
- // Increment progress
- currentProgress += 1;
-
- // Report progress
- worker.ReportProgress(currentProgress);
-
- // Sleep for a little bit so the user can see the update
- Thread.Sleep(Settings.Default.ProgressSleepInterval * 3);
- }
-
- #endregion
-
- #region Remote command line handling
-
- private void HandleCommandLine(object sender, InterprocessMessageListener.InterprocessMessageEventArgs e)
- {
- // If the command line is blank then ignore it
- if (e.Message.Length == 0)
- return;
-
- // Pad the command line with a trailing space just to be lazy in parsing
- var commandLine = e.Message + " ";
-
- // Look for the feed URL in the command line
- var startPosition = commandLine.IndexOf("feed://", StringComparison.Ordinal);
-
- // If we found one then we should extract and process it
- if (startPosition > 0)
- {
- // Advance past the protocol
- startPosition += 7;
-
- // Starting at the URL position look for the next space
- var endPosition = commandLine.IndexOf(" ", startPosition, StringComparison.Ordinal);
-
- // Extract the feed URL
- var feedUrl = commandLine.Substring(startPosition, endPosition - startPosition);
-
- // Add the HTTP protocol by default
- feedUrl = "http://" + feedUrl;
-
- // Create a new feed using the URL
- HandleNewFeed(feedUrl);
- }
- }
-
- private delegate void NewFeedDelegate(string feedUrl);
- private void HandleNewFeed(string feedUrl)
- {
- // Create and configure the new feed
- var feed = Feed.Create(_database);
- feed.Source = feedUrl;
- feed.Category = _database.DefaultCategory;
-
- // Try to detect the feed type
- var feedTypeResult = feed.DetectFeedType();
-
- // If we can't figure it out it could be an HTML page
- if (feedTypeResult.Item1 == FeedType.Unknown)
- {
- // Only check if the feed was able to be read - otherwise fall through and show the dialog
- if (feedTypeResult.Item2.Length > 0)
- {
- // Create and load an HTML document with the text
- var htmlDocument = new HtmlAgilityPack.HtmlDocument();
- htmlDocument.LoadHtml(feedTypeResult.Item2);
-
- // Look for all RSS or atom links in the document
- var rssLinks = htmlDocument.DocumentNode.Descendants("link")
- .Where(n => n.Attributes["type"] != null && (n.Attributes["type"].Value == "application/rss+xml" || n.Attributes["type"].Value == "application/atom+xml"))
- .Select(n => new Tuple(UrlHelper.GetAbsoluteUrlString(feed.Source, n.Attributes["href"].Value), WebUtility.HtmlDecode(n.Attributes["title"]?.Value ?? string.Empty)))
- .ToList();
-
- // If there was only one link found then switch to feed to it
- if (rssLinks.Count == 1)
- {
- feed.Source = rssLinks[0].Item1;
- }
- else
- {
- var feedChooserWindow = new FeedChooserWindow();
- var feedLink = feedChooserWindow.Display(this, rssLinks);
-
- if (string.IsNullOrEmpty(feedLink))
- return;
-
- feed.Source = feedLink;
- }
- }
- }
-
- // Read the feed for the first time
- var feedReadResult = feed.Read(_database);
-
- // See if we read the feed okay
- if (feedReadResult == FeedReadResult.Success)
- {
- // Update the feed name to be the title
- feed.Name = feed.Title;
-
- // Add the feed to the feed table
- _database.Feeds.Add(feed);
-
- // Save the changes
- _database.SaveChanges();
-
- // Show a tip
- NotificationIcon.ShowBalloonTip(string.Format(Properties.Resources.FeedAddedNotification, feed.Name), System.Windows.Forms.ToolTipIcon.Info);
-
- // Re-initialize the feed display
- DisplayFeed();
- }
- else
- {
- // Feed read failed - ceate a new feed window
- var feedForm = new FeedWindow();
-
- var dialogResult = feedForm.Display(_database, feed, this);
-
- // Display the new feed form
- if (dialogResult.HasValue && dialogResult.Value)
- {
- // Add the feed to the feed table
- _database.Feeds.Add(feed);
-
- // Save the changes
- _database.SaveChanges();
-
- // Re-initialize the feed display
- DisplayFeed();
- }
- }
- }
-
- #endregion
-
- #region Database helpers
-
- private void ResetDatabase()
- {
- // Get the ID of the current feed
- var currentId = _currentFeed?.ID ?? Guid.Empty;
-
- // Create a new database object
- _database = new FeedCenterEntities();
-
- UpdateToolbarButtonState();
-
- // Get a list of feeds ordered by name
- var feedList = _database.Feeds.OrderBy(f => f.Name).ToList();
-
- // First try to find the current feed by ID to see if it is still there
- var newIndex = feedList.FindIndex(f => f.ID == currentId);
-
- if (newIndex == -1)
- {
- // The current feed isn't there anymore so see if we can find a feed at the old index
- if (feedList.ElementAtOrDefault(_feedIndex) != null)
- newIndex = _feedIndex;
-
- // If there is no feed at the old location then give up and go back to the start
- if (newIndex == -1 && feedList.Count > 0)
- newIndex = 0;
- }
-
- // Set the current index to the new index
- _feedIndex = newIndex;
-
- // Re-get the current feed
- _currentFeed = (_feedIndex == -1 ? null : _database.Feeds.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex));
- }
-
- #endregion
-
- #region Drag and drop
-
- private void HandleDragOver(object sender, DragEventArgs e)
- {
- // Default to not allowed
- e.Effects = DragDropEffects.None;
- e.Handled = true;
-
- // If there isn't any text in the data then it is not allowed
- if (!e.Data.GetDataPresent(DataFormats.Text))
- return;
-
- // Get the data as a string
- var data = (string) e.Data.GetData(DataFormats.Text);
-
- // If the data doesn't look like a URI then it is not allowed
- if (!Uri.IsWellFormedUriString(data, UriKind.Absolute))
- return;
-
- // Allowed
- e.Effects = DragDropEffects.Copy;
- }
-
- private void HandleDragDrop(object sender, DragEventArgs e)
- {
- // Get the data as a string
- var data = (string) e.Data.GetData(DataFormats.Text);
-
- // Check to see if the data starts with any known Chrome extension
- var chromeExtension = _chromeExtensions.FirstOrDefault(c => data.StartsWith(c));
-
- // Remove the Chrome extension URL and decode the URL
- if (chromeExtension != null)
- {
- data = data.Substring(chromeExtension.Length);
- data = WebUtility.UrlDecode(data);
- }
-
- // Handle the new feed but allow the drag/drop to complete
- Dispatcher.BeginInvoke(new NewFeedDelegate(HandleNewFeed), data);
- }
-
- #endregion
-
- #region Link list events
-
- private void HandleLinkTextListMouseUp(object sender, MouseButtonEventArgs e)
- {
- switch (e.ChangedButton)
- {
- case MouseButton.XButton1:
-
- PreviousFeed();
- break;
-
- case MouseButton.XButton2:
-
- NextFeed();
- break;
- }
- }
-
- private void HandleItemMouseUp(object sender, MouseButtonEventArgs e)
- {
- // Only handle the middle button
- if (e.ChangedButton != MouseButton.Middle)
- return;
-
- // Get the feed item
- var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
-
- // The feed item has been read and is no longer new
- feedItem.BeenRead = true;
- feedItem.New = false;
-
- // Remove the item from the list
- LinkTextList.Items.Remove(feedItem);
-
- // Save the changes
- _database.SaveChanges();
-
- }
-
- private void HandleItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
- {
- // Get the feed item
- var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
-
- // Open the item link
- if (BrowserCommon.OpenLink(feedItem.Link))
- {
- // The feed item has been read and is no longer new
- feedItem.BeenRead = true;
- feedItem.New = false;
-
- // Remove the item from the list
- LinkTextList.Items.Remove(feedItem);
-
- // Save the changes
- _database.SaveChanges();
- }
- }
-
- #endregion
-
- #region Feed list menu
-
- private void HandleFeedButtonClick(object sender, RoutedEventArgs e)
- {
- // Create a new context menu
- var contextMenu = new ContextMenu();
-
- // Loop over each feed
- foreach (var feed in _database.Feeds.OrderBy(feed => feed.Name))
- {
- // Build a string to display the feed name and the unread count
- var display = $"{feed.Name} ({feed.Items.Count(item => !item.BeenRead):d})";
-
- // Create a menu item
- var menuItem = new MenuItem
- {
- Header = display,
- Tag = feed,
-
- // Set the current item to bold
- FontWeight = feed == _currentFeed ? FontWeights.Bold : FontWeights.Normal
- };
-
-
- // Handle the click
- menuItem.Click += HandleFeedMenuItemClick;
-
- // Add the item to the list
- contextMenu.Items.Add(menuItem);
- }
-
- // Set the context menu placement to this button
- contextMenu.PlacementTarget = this;
-
- // Open the context menu
- contextMenu.IsOpen = true;
- }
-
- private void HandleFeedMenuItemClick(object sender, RoutedEventArgs e)
- {
- // Get the menu item clicked
- var menuItem = (MenuItem) sender;
-
- // Get the feed from the menu item tab
- var feed = (Feed) menuItem.Tag;
-
- // Loop over all feeds and look for the index of the new one
- var feedIndex = 0;
- foreach (var loopFeed in _database.Feeds.OrderBy(loopFeed => loopFeed.Name))
- {
- if (loopFeed == feed)
- {
- _feedIndex = feedIndex;
- break;
- }
-
- feedIndex++;
- }
-
- // Set the current feed
- _currentFeed = feed;
-
- // Update the feed timestamp
- _lastFeedDisplay = DateTime.Now;
-
- // Update the display
- DisplayFeed();
- }
-
- #endregion
-
- #region Window border
-
- private void UpdateBorder()
- {
- var windowInteropHelper = new WindowInteropHelper(this);
-
- var screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
-
- var rectangle = new System.Drawing.Rectangle
- {
- X = (int) Left,
- Y = (int) Top,
- Width = (int) Width,
- Height = (int) Height
- };
-
- var borderThickness = new Thickness();
-
- if (rectangle.Right != screen.WorkingArea.Right)
- borderThickness.Right = 1;
-
- if (rectangle.Left != screen.WorkingArea.Left)
- borderThickness.Left = 1;
-
- if (rectangle.Top != screen.WorkingArea.Top)
- borderThickness.Top = 1;
-
- if (rectangle.Bottom != screen.WorkingArea.Bottom)
- borderThickness.Bottom = 1;
-
- WindowBorder.BorderThickness = borderThickness;
- }
-
- private DelayedMethod _windowStateDelay;
-
- private void HandleWindowSizeChanged(object sender, SizeChangedEventArgs e)
- {
- if (_windowStateDelay == null)
- _windowStateDelay = new DelayedMethod(500, UpdateWindowSettings);
-
- _windowStateDelay.Reset();
- }
-
- private void HandleWindowLocationChanged(object sender, EventArgs e)
- {
- if (_windowStateDelay == null)
- _windowStateDelay = new DelayedMethod(500, UpdateWindowSettings);
-
- _windowStateDelay.Reset();
- }
-
- private void UpdateWindowSettings()
- {
- // Save current window settings
- SaveWindowSettings();
-
- // Update the border
- UpdateBorder();
- }
-
- #endregion
-
- #region Feed header
-
- private void HandleFeedLabelMouseDown(object sender, MouseButtonEventArgs e)
- {
- // Open the link for the current feed on a left double click
- if (e.ClickCount == 2 && e.ChangedButton == MouseButton.Left)
- BrowserCommon.OpenLink(_currentFeed.Link);
- }
-
- #endregion
-
- #region Navigation toolbar
-
- private void HandlePreviousToolbarButtonClick(object sender, RoutedEventArgs e)
- {
- PreviousFeed();
- }
-
- private void HandleNextToolbarButtonClick(object sender, RoutedEventArgs e)
- {
- NextFeed();
- }
-
- private void OpenAllFeedItemsIndividually()
- {
- // Create a new list of feed items
- var feedItems = (from FeedItem feedItem in LinkTextList.Items select feedItem).ToList();
-
- // Get the browser
- var browser = BrowserCommon.FindBrowser(Settings.Default.Browser);
-
- // Cache the settings object
- var settings = Settings.Default;
-
- // Start with a longer sleep interval to give time for the browser to come up
- var sleepInterval = settings.OpenAllSleepIntervalFirst;
-
- // Loop over all items
- foreach (var feedItem in feedItems)
- {
- // Try to open the link
- if (BrowserCommon.OpenLink(browser, feedItem.Link))
- {
- // Mark the feed as read
- feedItem.BeenRead = true;
-
- // Remove the item
- LinkTextList.Items.Remove(feedItem);
- }
-
- // Wait a little bit
- Thread.Sleep(sleepInterval);
-
- // Switch to the normal sleep interval
- sleepInterval = settings.OpenAllSleepInterval;
- }
-
- // Save the changes
- _database.SaveChanges();
- }
-
- private void HandleOptionsToolbarButtonClick(object sender, RoutedEventArgs e)
- {
- // Create the options form
- var optionsWindow = new OptionsWindow { Owner = this };
-
- // Show the options form and get the result
- var result = optionsWindow.ShowDialog();
-
- // If okay was selected
- if (result.HasValue && result.Value)
- {
- // Reset the database to current settings
- ResetDatabase();
-
- // Re-initialize the feed display
- DisplayFeed();
- }
- }
-
- private void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e)
- {
- MarkAllItemsAsRead();
- }
-
- private void HandleShowErrorsButtonClick(object sender, RoutedEventArgs e)
- {
- // Create the feed error window
- var feedErrorWindow = new FeedErrorWindow();
-
- // Display the window
- var result = feedErrorWindow.Display(this);
-
- // If okay was selected
- if (result.GetValueOrDefault())
- {
- // Reset the database to current settings
- ResetDatabase();
-
- // Re-initialize the feed display
- DisplayFeed();
-
- UpdateErrorLink();
- }
- }
-
- private void HandleRefreshMenuItemClick(object sender, RoutedEventArgs e)
- {
- var menuItem = (MenuItem) e.Source;
-
- if (Equals(menuItem, MenuRefresh))
- ReadCurrentFeed(true);
- else if (Equals(menuItem, MenuRefreshAll))
- ReadFeeds(true);
- }
-
- private void HandleRefreshToolbarButtonClick(object sender, RoutedEventArgs e)
- {
- ReadFeeds(true);
- }
-
- private void HandleOpenAllMenuItemClick(object sender, RoutedEventArgs e)
- {
- var menuItem = (MenuItem) e.Source;
-
- if (Equals(menuItem, MenuOpenAllSinglePage))
- OpenAllFeedItemsOnSinglePage();
- else if (Equals(menuItem, MenuOpenAllMultiplePages))
- OpenAllFeedItemsIndividually();
- }
-
- private void HandleOpenAllToolbarButtonClick(object sender, RoutedEventArgs e)
- {
- var multipleOpenAction = _currentFeed.MultipleOpenAction;
-
- switch (multipleOpenAction)
- {
- case MultipleOpenAction.IndividualPages:
- OpenAllFeedItemsIndividually();
- break;
- case MultipleOpenAction.SinglePage:
- OpenAllFeedItemsOnSinglePage();
- break;
- }
- }
-
- private void HandleEditCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
- {
- // Create a new feed window
- var feedWindow = new FeedWindow();
-
- // Display the feed window and get the result
- var result = feedWindow.Display(_database, _currentFeed, this);
-
- // If OK was clicked...
- if (result.HasValue && result.Value)
- {
- // Save
- _database.SaveChanges();
-
- // Update feed
- DisplayFeed();
- }
- }
-
- private void HandleDeleteCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
- {
- // Confirm this delete since it is for real
- if (MessageBox.Show(this, Properties.Resources.ConfirmDelete, string.Empty, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
- return;
-
- // Get the current feed
- var feedToDelete = _currentFeed;
-
- // Move to the next feed
- NextFeed();
-
- // Delete all items
- foreach (var item in feedToDelete.Items.ToList())
- _database.FeedItems.Remove(item);
-
- // Delete the feed
- _database.Feeds.Remove(feedToDelete);
-
- // Save
- _database.SaveChanges();
- }
-
- #endregion
-
- #region Single page reading
-
- private void OpenAllFeedItemsOnSinglePage()
- {
- var fileName = Path.GetTempFileName() + ".html";
- TextWriter textWriter = new StreamWriter(fileName);
-
- using (var htmlTextWriter = new HtmlTextWriter(textWriter))
- {
- htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Html);
-
- htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Head);
-
- htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Title);
- htmlTextWriter.Write(_currentFeed.Title);
- htmlTextWriter.RenderEndTag();
-
- htmlTextWriter.AddAttribute("http-equiv", "Content-Type");
- htmlTextWriter.AddAttribute("content", "text/html; charset=utf-8");
- htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Meta);
- htmlTextWriter.RenderEndTag();
-
- htmlTextWriter.RenderEndTag();
-
- htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Body);
-
- var sortedItems = from item in _currentFeed.Items where !item.BeenRead orderby item.Sequence ascending select item;
-
- var firstItem = true;
-
- foreach (var item in sortedItems)
- {
- if (!firstItem)
- {
- htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Hr);
- htmlTextWriter.RenderEndTag();
- }
-
- htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Div);
-
- htmlTextWriter.AddAttribute(HtmlTextWriterAttribute.Href, item.Link);
- htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.A);
- htmlTextWriter.Write(item.Title.Length == 0 ? item.Link : item.Title);
- htmlTextWriter.RenderEndTag();
-
- htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Br);
- htmlTextWriter.RenderEndTag();
-
- htmlTextWriter.Write(item.Description);
-
- htmlTextWriter.RenderEndTag();
-
- firstItem = false;
- }
-
- htmlTextWriter.RenderEndTag();
- htmlTextWriter.RenderEndTag();
- }
-
- textWriter.Flush();
- textWriter.Close();
-
- BrowserCommon.OpenLink(fileName);
-
- MarkAllItemsAsRead();
- }
-
- #endregion
-
- private void HandleNewVersionLinkClick(object sender, RoutedEventArgs e)
- {
- // Display update information
- UpdateCheck.DisplayUpdateInformation(true);
- }
- }
-}
diff --git a/Application/MainWindow/CategoryList.cs b/Application/MainWindow/CategoryList.cs
new file mode 100644
index 0000000..31c1416
--- /dev/null
+++ b/Application/MainWindow/CategoryList.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private void DisplayCategory()
+ {
+ CategoryLabel.Text = string.Format(Properties.Resources.CategoryFilterHeader, _currentCategory == null ? Properties.Resources.AllCategory : _currentCategory.Name);
+ }
+
+ private void HandleCategoryButtonClick(object sender, RoutedEventArgs e)
+ {
+ // Create a new context menu
+ var contextMenu = new ContextMenu();
+
+ // Create the "all" menu item
+ var menuItem = new MenuItem
+ {
+ Header = Properties.Resources.AllCategory,
+ Tag = null,
+
+ // Set the current item to bold
+ FontWeight = _currentCategory == null ? FontWeights.Bold : FontWeights.Normal
+ };
+
+ // Handle the click
+ menuItem.Click += HandleCategoryMenuItemClick;
+
+ // Add the item to the list
+ contextMenu.Items.Add(menuItem);
+
+ // Loop over each feed
+ foreach (var category in _database.Categories.OrderBy(category => category.Name))
+ {
+ // Create a menu item
+ menuItem = new MenuItem
+ {
+ Header = category.Name,
+ Tag = category,
+
+ // Set the current item to bold
+ FontWeight = category.ID == _currentCategory?.ID ? FontWeights.Bold : FontWeights.Normal
+ };
+
+ // Handle the click
+ menuItem.Click += HandleCategoryMenuItemClick;
+
+ // Add the item to the list
+ contextMenu.Items.Add(menuItem);
+ }
+
+ // Set the context menu placement to this button
+ contextMenu.PlacementTarget = this;
+
+ // Open the context menu
+ contextMenu.IsOpen = true;
+ }
+
+ private void HandleCategoryMenuItemClick(object sender, RoutedEventArgs e)
+ {
+ // Get the menu item clicked
+ var menuItem = (MenuItem) sender;
+
+ // Get the category from the menu item tab
+ var category = (Category) menuItem.Tag;
+
+ // If the category changed then reset the current feed to the first in the category
+ if (_currentCategory?.ID != category?.ID)
+ {
+ _currentFeed = category == null ? _database.Feeds.FirstOrDefault() : category.Feeds.FirstOrDefault();
+ }
+
+ // Set the current category
+ _currentCategory = category;
+
+ // Get the current feed list to match the category
+ _feedList = _currentCategory?.Feeds.ToList() ?? _database.Feeds.ToList();
+
+ // Reset the feed index
+ _feedIndex = -1;
+
+ // Get the first feed
+ NextFeed();
+
+ // Update the feed timestamp
+ _lastFeedDisplay = DateTime.Now;
+
+ // Update the display
+ DisplayCategory();
+ DisplayFeed();
+ }
+ }
+}
diff --git a/Application/MainWindow/CommandLine.cs b/Application/MainWindow/CommandLine.cs
new file mode 100644
index 0000000..6756e1b
--- /dev/null
+++ b/Application/MainWindow/CommandLine.cs
@@ -0,0 +1,42 @@
+using Common.IO;
+using System;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private InterprocessMessageListener _commandLineListener;
+
+ private void HandleCommandLine(object sender, InterprocessMessageListener.InterprocessMessageEventArgs e)
+ {
+ // If the command line is blank then ignore it
+ if (e.Message.Length == 0)
+ return;
+
+ // Pad the command line with a trailing space just to be lazy in parsing
+ var commandLine = e.Message + " ";
+
+ // Look for the feed URL in the command line
+ var startPosition = commandLine.IndexOf("feed://", StringComparison.Ordinal);
+
+ // If we found one then we should extract and process it
+ if (startPosition > 0)
+ {
+ // Advance past the protocol
+ startPosition += 7;
+
+ // Starting at the URL position look for the next space
+ var endPosition = commandLine.IndexOf(" ", startPosition, StringComparison.Ordinal);
+
+ // Extract the feed URL
+ var feedUrl = commandLine.Substring(startPosition, endPosition - startPosition);
+
+ // Add the HTTP protocol by default
+ feedUrl = "http://" + feedUrl;
+
+ // Create a new feed using the URL
+ HandleNewFeed(feedUrl);
+ }
+ }
+ }
+}
diff --git a/Application/MainWindow/DragDrop.cs b/Application/MainWindow/DragDrop.cs
new file mode 100644
index 0000000..e628440
--- /dev/null
+++ b/Application/MainWindow/DragDrop.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Windows;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private readonly string[] _chromeExtensions = { "chrome-extension://ehojfdcmnajoklleckniaifaijfnkpbi/subscribe.html?", "chrome-extension://nlbjncdgjeocebhnmkbbbdekmmmcbfjd/subscribe.html?" };
+
+ private void HandleDragOver(object sender, DragEventArgs e)
+ {
+ // Default to not allowed
+ e.Effects = DragDropEffects.None;
+ e.Handled = true;
+
+ // If there isn't any text in the data then it is not allowed
+ if (!e.Data.GetDataPresent(DataFormats.Text))
+ return;
+
+ // Get the data as a string
+ var data = (string) e.Data.GetData(DataFormats.Text);
+
+ // If the data doesn't look like a URI then it is not allowed
+ if (!Uri.IsWellFormedUriString(data, UriKind.Absolute))
+ return;
+
+ // Allowed
+ e.Effects = DragDropEffects.Copy;
+ }
+
+ private void HandleDragDrop(object sender, DragEventArgs e)
+ {
+ // Get the data as a string
+ var data = (string) e.Data.GetData(DataFormats.Text);
+
+ // Check to see if the data starts with any known Chrome extension
+ var chromeExtension = _chromeExtensions.FirstOrDefault(c => data.StartsWith(c));
+
+ // Remove the Chrome extension URL and decode the URL
+ if (chromeExtension != null)
+ {
+ data = data.Substring(chromeExtension.Length);
+ data = WebUtility.UrlDecode(data);
+ }
+
+ // Handle the new feed but allow the drag/drop to complete
+ Dispatcher.BeginInvoke(new NewFeedDelegate(HandleNewFeed), data);
+ }
+ }
+}
diff --git a/Application/MainWindow/FeedCreation.cs b/Application/MainWindow/FeedCreation.cs
new file mode 100644
index 0000000..cb151a1
--- /dev/null
+++ b/Application/MainWindow/FeedCreation.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Linq;
+using System.Net;
+using Common.Internet;
+using FeedCenter.Options;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private delegate void NewFeedDelegate(string feedUrl);
+ private void HandleNewFeed(string feedUrl)
+ {
+ // Create and configure the new feed
+ var feed = Feed.Create(_database);
+ feed.Source = feedUrl;
+ feed.Category = _database.DefaultCategory;
+
+ // Try to detect the feed type
+ var feedTypeResult = feed.DetectFeedType();
+
+ // If we can't figure it out it could be an HTML page
+ if (feedTypeResult.Item1 == FeedType.Unknown)
+ {
+ // Only check if the feed was able to be read - otherwise fall through and show the dialog
+ if (feedTypeResult.Item2.Length > 0)
+ {
+ // Create and load an HTML document with the text
+ var htmlDocument = new HtmlAgilityPack.HtmlDocument();
+ htmlDocument.LoadHtml(feedTypeResult.Item2);
+
+ // Look for all RSS or atom links in the document
+ var rssLinks = htmlDocument.DocumentNode.Descendants("link")
+ .Where(n => n.Attributes["type"] != null && (n.Attributes["type"].Value == "application/rss+xml" || n.Attributes["type"].Value == "application/atom+xml"))
+ .Select(n => new Tuple(UrlHelper.GetAbsoluteUrlString(feed.Source, n.Attributes["href"].Value), WebUtility.HtmlDecode(n.Attributes["title"]?.Value ?? string.Empty)))
+ .ToList();
+
+ // If there was only one link found then switch to feed to it
+ if (rssLinks.Count == 1)
+ {
+ feed.Source = rssLinks[0].Item1;
+ }
+ else
+ {
+ var feedChooserWindow = new FeedChooserWindow();
+ var feedLink = feedChooserWindow.Display(this, rssLinks);
+
+ if (string.IsNullOrEmpty(feedLink))
+ return;
+
+ feed.Source = feedLink;
+ }
+ }
+ }
+
+ // Read the feed for the first time
+ var feedReadResult = feed.Read(_database);
+
+ // See if we read the feed okay
+ if (feedReadResult == FeedReadResult.Success)
+ {
+ // Update the feed name to be the title
+ feed.Name = feed.Title;
+
+ // Add the feed to the feed table
+ _database.Feeds.Add(feed);
+
+ // Save the changes
+ _database.SaveChanges();
+
+ // Show a tip
+ NotificationIcon.ShowBalloonTip(string.Format(Properties.Resources.FeedAddedNotification, feed.Name), System.Windows.Forms.ToolTipIcon.Info);
+
+ // Re-initialize the feed display
+ DisplayFeed();
+ }
+ else
+ {
+ // Feed read failed - ceate a new feed window
+ var feedForm = new FeedWindow();
+
+ var dialogResult = feedForm.Display(_database, feed, this);
+
+ // Display the new feed form
+ if (dialogResult.HasValue && dialogResult.Value)
+ {
+ // Add the feed to the feed table
+ _database.Feeds.Add(feed);
+
+ // Save the changes
+ _database.SaveChanges();
+
+ // Re-initialize the feed display
+ DisplayFeed();
+ }
+ }
+ }
+ }
+}
diff --git a/Application/MainWindow/FeedList.cs b/Application/MainWindow/FeedList.cs
new file mode 100644
index 0000000..11e906f
--- /dev/null
+++ b/Application/MainWindow/FeedList.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private void HandleLinkTextListMouseUp(object sender, MouseButtonEventArgs e)
+ {
+ switch (e.ChangedButton)
+ {
+ case MouseButton.XButton1:
+
+ PreviousFeed();
+ break;
+
+ case MouseButton.XButton2:
+
+ NextFeed();
+ break;
+ }
+ }
+
+ private void HandleItemMouseUp(object sender, MouseButtonEventArgs e)
+ {
+ // Only handle the middle button
+ if (e.ChangedButton != MouseButton.Middle)
+ return;
+
+ // Get the feed item
+ var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
+
+ // The feed item has been read and is no longer new
+ feedItem.BeenRead = true;
+ feedItem.New = false;
+
+ // Remove the item from the list
+ LinkTextList.Items.Remove(feedItem);
+
+ // Save the changes
+ _database.SaveChanges();
+
+ }
+
+ private void HandleItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
+ {
+ // Get the feed item
+ var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
+
+ // Open the item link
+ if (BrowserCommon.OpenLink(feedItem.Link))
+ {
+ // The feed item has been read and is no longer new
+ feedItem.BeenRead = true;
+ feedItem.New = false;
+
+ // Remove the item from the list
+ LinkTextList.Items.Remove(feedItem);
+
+ // Save the changes
+ _database.SaveChanges();
+ }
+ }
+
+ private void HandleFeedButtonClick(object sender, RoutedEventArgs e)
+ {
+ // Create a new context menu
+ var contextMenu = new ContextMenu();
+
+ // Loop over each feed
+ foreach (var feed in _feedList.OrderBy(feed => feed.Name))
+ {
+ // Build a string to display the feed name and the unread count
+ var display = $"{feed.Name} ({feed.Items.Count(item => !item.BeenRead):d})";
+
+ // Create a menu item
+ var menuItem = new MenuItem
+ {
+ Header = display,
+ Tag = feed,
+
+ // Set the current item to bold
+ FontWeight = feed == _currentFeed ? FontWeights.Bold : FontWeights.Normal
+ };
+
+
+ // Handle the click
+ menuItem.Click += HandleFeedMenuItemClick;
+
+ // Add the item to the list
+ contextMenu.Items.Add(menuItem);
+ }
+
+ // Set the context menu placement to this button
+ contextMenu.PlacementTarget = this;
+
+ // Open the context menu
+ contextMenu.IsOpen = true;
+ }
+
+ private void HandleFeedMenuItemClick(object sender, RoutedEventArgs e)
+ {
+ // Get the menu item clicked
+ var menuItem = (MenuItem) sender;
+
+ // Get the feed from the menu item tab
+ var feed = (Feed) menuItem.Tag;
+
+ // Loop over all feeds and look for the index of the new one
+ var feedIndex = 0;
+ foreach (var loopFeed in _feedList.OrderBy(loopFeed => loopFeed.Name))
+ {
+ if (loopFeed == feed)
+ {
+ _feedIndex = feedIndex;
+ break;
+ }
+
+ feedIndex++;
+ }
+
+ // Set the current feed
+ _currentFeed = feed;
+
+ // Update the feed timestamp
+ _lastFeedDisplay = DateTime.Now;
+
+ // Update the display
+ DisplayFeed();
+ }
+ }
+}
diff --git a/Application/MainWindow/FeedReading.cs b/Application/MainWindow/FeedReading.cs
new file mode 100644
index 0000000..e47c7aa
--- /dev/null
+++ b/Application/MainWindow/FeedReading.cs
@@ -0,0 +1,185 @@
+using Common.Update;
+using FeedCenter.Properties;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading;
+using System.Windows;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private BackgroundWorker _feedReadWorker;
+
+ private class FeedReadWorkerInput
+ {
+ public bool ForceRead;
+ public Feed Feed;
+ }
+
+ private void SetProgressMode(bool value, int feedCount)
+ {
+ // Reset the progress bar if we need it
+ if (value)
+ {
+ FeedReadProgress.Value = 0;
+ FeedReadProgress.Maximum = feedCount + 2;
+ FeedReadProgress.Visibility = Visibility.Visible;
+ }
+ else
+ {
+ FeedReadProgress.Visibility = Visibility.Collapsed;
+ }
+ }
+
+ private void ReadCurrentFeed(bool forceRead = false)
+ {
+ // Don't read if we're already working
+ if (_feedReadWorker.IsBusy)
+ return;
+
+ // Don't read if there is nothing to read
+ if (!_database.Feeds.Any())
+ return;
+
+ // Switch to progress mode
+ SetProgressMode(true, 1);
+
+ // Create the input class
+ var workerInput = new FeedReadWorkerInput { ForceRead = forceRead, Feed = _currentFeed };
+
+ // Start the worker
+ _feedReadWorker.RunWorkerAsync(workerInput);
+ }
+
+ private void ReadFeeds(bool forceRead = false)
+ {
+ // Don't read if we're already working
+ if (_feedReadWorker.IsBusy)
+ return;
+
+ // Don't read if there is nothing to read
+ if (!_database.Feeds.Any())
+ return;
+
+ // Switch to progress mode
+ SetProgressMode(true, _database.Feeds.Count());
+
+ // Create the input class
+ var workerInput = new FeedReadWorkerInput { ForceRead = forceRead, Feed = null };
+
+ // Start the worker
+ _feedReadWorker.RunWorkerAsync(workerInput);
+ }
+
+ private void HandleFeedReadWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
+ {
+ // Set progress
+ FeedReadProgress.Value = e.ProgressPercentage;
+ }
+
+ private void HandleFeedReadWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
+ {
+ // Reset the database to current settings
+ ResetDatabase();
+
+ // Save settings
+ Settings.Default.Save();
+
+ // Set the read timestamp
+ _lastFeedRead = DateTime.Now;
+
+ // Update the current feed
+ DisplayFeed();
+
+ // Switch to normal mode
+ SetProgressMode(false, 0);
+
+ // Check for update
+ if (UpdateCheck.UpdateAvailable)
+ NewVersionLink.Visibility = Visibility.Visible;
+
+ UpdateErrorLink();
+ }
+
+ private void UpdateErrorLink()
+ {
+ var feedErrorCount = _database.Feeds.Count(f => f.LastReadResult != FeedReadResult.Success);
+
+ // Set the visibility of the error link
+ FeedErrorsLink.Visibility = feedErrorCount == 0 ? Visibility.Collapsed : Visibility.Visible;
+
+ // Set the text to match the number of errors
+ FeedErrorsLink.Text = feedErrorCount == 1
+ ? Properties.Resources.FeedErrorLink
+ : string.Format(Properties.Resources.FeedErrorsLink, feedErrorCount);
+ }
+
+ private static void HandleFeedReadWorkerStart(object sender, DoWorkEventArgs e)
+ {
+ // Create a new database instance for just this thread
+ var database = new FeedCenterEntities();
+
+ // Get the worker
+ var worker = (BackgroundWorker) sender;
+
+ // Get the input information
+ var workerInput = (FeedReadWorkerInput) e.Argument;
+
+ // Setup for progress
+ var currentProgress = 0;
+
+ // Create the list of feeds to read
+ var feedsToRead = new List();
+
+ // If we have a single feed then add it to the list - otherwise add them all
+ if (workerInput.Feed != null)
+ feedsToRead.Add(database.Feeds.First(feed => feed.ID == workerInput.Feed.ID));
+ else
+ feedsToRead.AddRange(database.Feeds);
+
+ // Loop over each feed and read it
+ foreach (var feed in feedsToRead)
+ {
+ // Read the feed
+ feed.Read(database, workerInput.ForceRead);
+
+ // Increment progress
+ currentProgress += 1;
+
+ // Report progress
+ worker.ReportProgress(currentProgress);
+ }
+
+ // Save the changes
+ database.SaveChanges();
+
+ // Increment progress
+ currentProgress += 1;
+
+ // Report progress
+ worker.ReportProgress(currentProgress);
+
+ // See if we're due for a version check
+ if (DateTime.Now - Settings.Default.LastVersionCheck >= Settings.Default.VersionCheckInterval)
+ {
+ // Get the update information
+ UpdateCheck.CheckForUpdate();
+
+ // Update the last check time
+ Settings.Default.LastVersionCheck = DateTime.Now;
+ }
+
+ // Increment progress
+ currentProgress += 1;
+
+ // Report progress
+ worker.ReportProgress(currentProgress);
+
+ // Sleep for a little bit so the user can see the update
+ Thread.Sleep(Settings.Default.ProgressSleepInterval * 3);
+ }
+ }
+}
diff --git a/Application/MainWindow/Header.cs b/Application/MainWindow/Header.cs
new file mode 100644
index 0000000..9ea5af5
--- /dev/null
+++ b/Application/MainWindow/Header.cs
@@ -0,0 +1,32 @@
+using FeedCenter.Properties;
+using System.Windows;
+using System.Windows.Input;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private void HandleHeaderLabelMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ // Ignore if the window is locked
+ if (Settings.Default.WindowLocked)
+ return;
+
+ // Start dragging
+ DragMove();
+ }
+
+ private void HandleCloseButtonClick(object sender, RoutedEventArgs e)
+ {
+ // Close the window
+ Close();
+ }
+
+ private void HandleFeedLabelMouseDown(object sender, MouseButtonEventArgs e)
+ {
+ // Open the link for the current feed on a left double click
+ if (e.ClickCount == 2 && e.ChangedButton == MouseButton.Left)
+ BrowserCommon.OpenLink(_currentFeed.Link);
+ }
+ }
+}
diff --git a/Application/MainWindow.xaml b/Application/MainWindow/MainWindow.xaml
similarity index 81%
rename from Application/MainWindow.xaml
rename to Application/MainWindow/MainWindow.xaml
index e495b80..b091efb 100644
--- a/Application/MainWindow.xaml
+++ b/Application/MainWindow/MainWindow.xaml
@@ -5,10 +5,10 @@
xmlns:windows="clr-namespace:Common.Wpf.Windows;assembly=Common.Wpf"
xmlns:toolbar="clr-namespace:Common.Wpf.Toolbar;assembly=Common.Wpf"
xmlns:splitButton="clr-namespace:Common.Wpf.Toolbar.SplitButton;assembly=Common.Wpf"
- xmlns:markup="clr-namespace:Common.Wpf.MarkupExtensions;assembly=Common.Wpf.MarkupExtensions"
xmlns:linkControl="clr-namespace:Common.Wpf.LinkControl;assembly=Common.Wpf"
xmlns:htmlTextBlock="clr-namespace:Common.Wpf.HtmlTextBlock;assembly=Common.Wpf"
xmlns:system="clr-namespace:System;assembly=mscorlib"
+ xmlns:nameBasedGrid="clr-namespace:NameBasedGrid;assembly=NameBasedGrid"
Title="MainWindow"
Height="360"
Width="252"
@@ -37,29 +37,31 @@
Name="WindowBorder"
Padding="0"
Background="{x:Static SystemColors.DesktopBrush}">
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
+ nameBasedGrid:NameBasedGrid.Row="HeaderRow">
@@ -82,7 +84,7 @@
+
+
+
+
+
+
+
+
+ nameBasedGrid:NameBasedGrid.Row="FeedRow">
@@ -122,7 +150,7 @@
Background="{x:Static SystemColors.DesktopBrush}"
MouseUp="HandleLinkTextListMouseUp"
Foreground="White"
- Grid.Row="{markup:GridRow RowName=FeedListRow}"
+ nameBasedGrid:NameBasedGrid.Row="FeedListRow"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
@@ -185,11 +213,11 @@
+ nameBasedGrid:NameBasedGrid.Row="ProgressRow" />
+ nameBasedGrid:NameBasedGrid.Row="TopToolbarRow">
@@ -198,15 +226,15 @@
Name="PreviousToolbarButton"
Click="HandlePreviousToolbarButtonClick"
ToolTip="{x:Static properties:Resources.previousToolbarButton}"
- ImageSource="Resources/Left.ico" />
+ ImageSource="../Resources/Left.ico" />
+ ImageSource="../Resources/Right.ico" />
+ ImageSource="../Resources/Comments-edit.ico" />
+ Image="../Resources/Compile.ico">
+
diff --git a/Application/MainWindow/MainWindow.xaml.cs b/Application/MainWindow/MainWindow.xaml.cs
new file mode 100644
index 0000000..5ef1304
--- /dev/null
+++ b/Application/MainWindow/MainWindow.xaml.cs
@@ -0,0 +1,395 @@
+using Common.Debug;
+using Common.Helpers;
+using Common.IO;
+using Common.Update;
+using FeedCenter.Properties;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private FeedCenterEntities _database;
+ private int _feedIndex;
+
+ private Category _currentCategory;
+ private ICollection _feedList;
+ private Feed _currentFeed;
+
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+
+ public void Initialize()
+ {
+ // Setup the update handler
+ InitializeUpdate();
+
+ // Show the notification icon
+ NotificationIcon.Initialize(this);
+
+ // Load window settings
+ LoadWindowSettings();
+
+ // Set the foreground color to something that can be seen
+ LinkTextList.Foreground = (System.Drawing.SystemColors.Desktop.GetBrightness() < 0.5) ? Brushes.White : Brushes.Black;
+ HeaderLabel.Foreground = LinkTextList.Foreground;
+
+ // Create the background worker that does the actual reading
+ _feedReadWorker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
+ _feedReadWorker.DoWork += HandleFeedReadWorkerStart;
+ _feedReadWorker.ProgressChanged += HandleFeedReadWorkerProgressChanged;
+ _feedReadWorker.RunWorkerCompleted += HandleFeedReadWorkerCompleted;
+
+ // Setup the database
+ _database = new FeedCenterEntities();
+
+ // Initialize the command line listener
+ _commandLineListener = new InterprocessMessageListener(Properties.Resources.ApplicationName);
+ _commandLineListener.MessageReceived += HandleCommandLine;
+
+ // Handle any command line we were started with
+ HandleCommandLine(null, new InterprocessMessageListener.InterprocessMessageEventArgs(Environment.CommandLine));
+
+ // Create a timer to keep track of things we need to do
+ InitializeTimer();
+
+ // Initialize the feed display
+ InitializeDisplay();
+
+ // Check for update
+ if (Settings.Default.CheckVersionAtStartup)
+ UpdateCheck.CheckForUpdate();
+
+ // Show the link if updates are available
+ if (UpdateCheck.UpdateAvailable)
+ NewVersionLink.Visibility = Visibility.Visible;
+
+ Tracer.WriteLine("MainForm creation finished");
+ }
+
+ #region Setting events
+
+ private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ // Make sure we're on the right thread
+ if (!Dispatcher.CheckAccess())
+ {
+ Dispatcher.Invoke(new EventHandler(HandlePropertyChanged), sender, e);
+ return;
+ }
+
+ if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.MultipleLineDisplay))
+ {
+ // Update the current feed
+ DisplayFeed();
+ }
+ else if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.WindowLocked))
+ {
+ // Update the window for the new window lock value
+ HandleWindowLockState();
+ }
+ else if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.ToolbarLocation))
+ {
+ // Update the window for the toolbar location
+ switch (Settings.Default.ToolbarLocation)
+ {
+ case Dock.Top:
+ NameBasedGrid.NameBasedGrid.SetRow(NavigationToolbarTray, "TopToolbarRow");
+
+ break;
+ case Dock.Bottom:
+ NameBasedGrid.NameBasedGrid.SetRow(NavigationToolbarTray, "BottomToolbarRow");
+
+ break;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Feed display
+
+ private void UpdateToolbarButtonState()
+ {
+ // Cache the feed count to save (a little) time
+ var feedCount = _feedList?.Count ?? 0;
+
+ // Set button states
+ PreviousToolbarButton.IsEnabled = (feedCount > 1);
+ NextToolbarButton.IsEnabled = (feedCount > 1);
+ RefreshToolbarButton.IsEnabled = (feedCount > 0);
+ FeedButton.IsEnabled = (feedCount > 0);
+ OpenAllToolbarButton.IsEnabled = (feedCount > 0);
+ MarkReadToolbarButton.IsEnabled = (feedCount > 0);
+ FeedLabel.Visibility = (feedCount == 0 ? Visibility.Hidden : Visibility.Visible);
+ FeedButton.Visibility = (feedCount > 1 ? Visibility.Hidden : Visibility.Visible);
+ CategoryGrid.Visibility = (_database.Categories.Count() > 1 ? Visibility.Visible : Visibility.Collapsed);
+ }
+
+ private void InitializeDisplay()
+ {
+ UpdateToolbarButtonState();
+
+ // Get the last category (defaulting to none)
+ _currentCategory = _database.Categories.FirstOrDefault(category => category.ID.ToString() == Settings.Default.LastCategoryID);
+ DisplayCategory();
+
+ // Get the current feed list to match the category
+ _feedList = _currentCategory?.Feeds.ToList() ?? _database.Feeds.ToList();
+
+ // Clear the link list
+ LinkTextList.Items.Clear();
+
+ // Reset the feed index
+ _feedIndex = -1;
+
+ // Start the timer
+ StartTimer();
+
+ // Don't go further if we have no feeds
+ if (_feedList.Count == 0)
+ return;
+
+ // Get the first feed
+ NextFeed();
+ }
+
+ private void NextFeed()
+ {
+ var feedCount = _feedList.Count();
+
+ if (feedCount == 0)
+ return;
+
+ if (Settings.Default.DisplayEmptyFeeds)
+ {
+ // Increment the index and adjust if we've gone around the end
+ _feedIndex = (_feedIndex + 1) % feedCount;
+
+ // Get the feed
+ _currentFeed = _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
+ }
+ else
+ {
+ // Keep track if we found something
+ var found = false;
+
+ // Remember our starting position
+ var startIndex = (_feedIndex == -1 ? 0 : _feedIndex);
+
+ // Increment the index and adjust if we've gone around the end
+ _feedIndex = (_feedIndex + 1) % feedCount;
+
+ // Loop until we come back to the start index
+ do
+ {
+ // Get the feed
+ _currentFeed = _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
+
+ // If the current feed has unread items then we can display it
+ if (_currentFeed.Items.Count(item => !item.BeenRead) > 0)
+ {
+ found = true;
+ break;
+ }
+
+ // Increment the index and adjust if we've gone around the end
+ _feedIndex = (_feedIndex + 1) % feedCount;
+ }
+ while (_feedIndex != startIndex);
+
+ // If nothing was found then clear the current feed
+ if (!found)
+ {
+ _feedIndex = -1;
+ _currentFeed = null;
+ }
+ }
+
+ // Update the feed timestamp
+ _lastFeedDisplay = DateTime.Now;
+
+ // Update the display
+ DisplayFeed();
+ }
+
+ private void PreviousFeed()
+ {
+ var feedCount = _feedList.Count();
+
+ if (feedCount == 0)
+ return;
+
+ if (Settings.Default.DisplayEmptyFeeds)
+ {
+ // Decrement the feed index
+ _feedIndex--;
+
+ // If we've gone below the start of the list then reset to the end
+ if (_feedIndex < 0)
+ _feedIndex = feedCount - 1;
+
+ // Get the feed
+ _currentFeed = _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
+ }
+ else
+ {
+ // Keep track if we found something
+ var found = false;
+
+ // Remember our starting position
+ var startIndex = (_feedIndex == -1 ? 0 : _feedIndex);
+
+ // Decrement the feed index
+ _feedIndex--;
+
+ // If we've gone below the start of the list then reset to the end
+ if (_feedIndex < 0)
+ _feedIndex = feedCount - 1;
+
+ // Loop until we come back to the start index
+ do
+ {
+ // Get the feed
+ _currentFeed = _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
+
+ // If the current feed has unread items then we can display it
+ if (_currentFeed.Items.Count(item => !item.BeenRead) > 0)
+ {
+ found = true;
+ break;
+ }
+
+ // Decrement the feed index
+ _feedIndex--;
+
+ // If we've gone below the start of the list then reset to the end
+ if (_feedIndex < 0)
+ _feedIndex = feedCount - 1;
+ }
+ while (_feedIndex != startIndex);
+
+ // If nothing was found then clear the current feed
+ if (!found)
+ {
+ _feedIndex = -1;
+ _currentFeed = null;
+ }
+ }
+
+ // Update the feed timestamp
+ _lastFeedDisplay = DateTime.Now;
+
+ // Update the display
+ DisplayFeed();
+ }
+
+ private void UpdateOpenAllButton()
+ {
+ var multipleOpenAction = _currentFeed.MultipleOpenAction;
+
+ switch (multipleOpenAction)
+ {
+ case MultipleOpenAction.IndividualPages:
+ OpenAllToolbarButton.ToolTip = Properties.Resources.openAllMultipleToolbarButton;
+ break;
+ case MultipleOpenAction.SinglePage:
+ OpenAllToolbarButton.ToolTip = Properties.Resources.openAllSingleToolbarButton;
+ break;
+ }
+ }
+
+ private void DisplayFeed()
+ {
+ // Just clear the display if we have no feed
+ if (_currentFeed == null)
+ {
+ FeedLabel.Text = string.Empty;
+ FeedButton.Visibility = Visibility.Hidden;
+ LinkTextList.Items.Clear();
+
+ return;
+ }
+
+ // Set the header to the feed title
+ FeedLabel.Text = (_currentFeed.Name.Length > 0 ? _currentFeed.Name : _currentFeed.Title);
+ FeedButton.Visibility = _feedList.Count() > 1 ? Visibility.Visible : Visibility.Hidden;
+
+ // Clear the current list
+ LinkTextList.Items.Clear();
+
+ // Sort the items by sequence
+ var sortedItems = _currentFeed.Items.Where(item => !item.BeenRead).OrderBy(item => item.Sequence);
+
+ // Loop over all items in the current feed
+ foreach (var feedItem in sortedItems)
+ {
+ // Add the list item
+ LinkTextList.Items.Add(feedItem);
+ }
+
+ UpdateOpenAllButton();
+ }
+
+ private void MarkAllItemsAsRead()
+ {
+ // Loop over all items and mark them as read
+ foreach (FeedItem feedItem in LinkTextList.Items)
+ feedItem.BeenRead = true;
+
+ // Save the changes
+ _database.SaveChanges();
+
+ // Clear the list
+ LinkTextList.Items.Clear();
+ }
+
+ #endregion
+
+ #region Database helpers
+
+ private void ResetDatabase()
+ {
+ // Get the ID of the current feed
+ var currentId = _currentFeed?.ID ?? Guid.Empty;
+
+ // Create a new database object
+ _database = new FeedCenterEntities();
+
+ UpdateToolbarButtonState();
+
+ // Get a list of feeds ordered by name
+ var feedList = _feedList.OrderBy(f => f.Name).ToList();
+
+ // First try to find the current feed by ID to see if it is still there
+ var newIndex = feedList.FindIndex(f => f.ID == currentId);
+
+ if (newIndex == -1)
+ {
+ // The current feed isn't there anymore so see if we can find a feed at the old index
+ if (feedList.ElementAtOrDefault(_feedIndex) != null)
+ newIndex = _feedIndex;
+
+ // If there is no feed at the old location then give up and go back to the start
+ if (newIndex == -1 && feedList.Count > 0)
+ newIndex = 0;
+ }
+
+ // Set the current index to the new index
+ _feedIndex = newIndex;
+
+ // Re-get the current feed
+ _currentFeed = (_feedIndex == -1 ? null : _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex));
+ }
+
+ #endregion
+ }
+}
diff --git a/Application/MainWindow/Timer.cs b/Application/MainWindow/Timer.cs
new file mode 100644
index 0000000..65de37d
--- /dev/null
+++ b/Application/MainWindow/Timer.cs
@@ -0,0 +1,59 @@
+using FeedCenter.Properties;
+using System;
+using System.Windows.Forms;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private Timer _mainTimer;
+ private DateTime _lastFeedRead;
+ private DateTime _lastFeedDisplay;
+
+ private void InitializeTimer()
+ {
+ _mainTimer = new Timer { Interval = 1000 };
+ _mainTimer.Tick += HandleMainTimerTick;
+ }
+
+ private void TerminateTimer()
+ {
+ StopTimer();
+
+ _mainTimer.Dispose();
+ }
+
+ private void StartTimer()
+ {
+ _mainTimer.Start();
+ }
+
+ private void StopTimer()
+ {
+ _mainTimer.Stop();
+ }
+
+ private void HandleMainTimerTick(object sender, EventArgs e)
+ {
+ // If the background worker is busy then don't do anything
+ if (_feedReadWorker.IsBusy)
+ return;
+
+ // Stop the timer for now
+ StopTimer();
+
+ // Move to the next feed if the scroll interval has expired and the mouse isn't hovering
+ if (LinkTextList.IsMouseOver)
+ _lastFeedDisplay = DateTime.Now;
+ else if (DateTime.Now - _lastFeedDisplay >= Settings.Default.FeedScrollInterval)
+ NextFeed();
+
+ // Check to see if we should try to read the feeds
+ if (DateTime.Now - _lastFeedRead >= Settings.Default.FeedCheckInterval)
+ ReadFeeds();
+
+ // Get the timer going again
+ StartTimer();
+ }
+ }
+}
diff --git a/Application/MainWindow/Toolbar.cs b/Application/MainWindow/Toolbar.cs
new file mode 100644
index 0000000..b862b84
--- /dev/null
+++ b/Application/MainWindow/Toolbar.cs
@@ -0,0 +1,254 @@
+using FeedCenter.Options;
+using FeedCenter.Properties;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Web.UI;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private void HandlePreviousToolbarButtonClick(object sender, RoutedEventArgs e)
+ {
+ PreviousFeed();
+ }
+
+ private void HandleNextToolbarButtonClick(object sender, RoutedEventArgs e)
+ {
+ NextFeed();
+ }
+
+ private void OpenAllFeedItemsIndividually()
+ {
+ // Create a new list of feed items
+ var feedItems = (from FeedItem feedItem in LinkTextList.Items select feedItem).ToList();
+
+ // Get the browser
+ var browser = BrowserCommon.FindBrowser(Settings.Default.Browser);
+
+ // Cache the settings object
+ var settings = Settings.Default;
+
+ // Start with a longer sleep interval to give time for the browser to come up
+ var sleepInterval = settings.OpenAllSleepIntervalFirst;
+
+ // Loop over all items
+ foreach (var feedItem in feedItems)
+ {
+ // Try to open the link
+ if (BrowserCommon.OpenLink(browser, feedItem.Link))
+ {
+ // Mark the feed as read
+ feedItem.BeenRead = true;
+
+ // Remove the item
+ LinkTextList.Items.Remove(feedItem);
+ }
+
+ // Wait a little bit
+ Thread.Sleep(sleepInterval);
+
+ // Switch to the normal sleep interval
+ sleepInterval = settings.OpenAllSleepInterval;
+ }
+
+ // Save the changes
+ _database.SaveChanges();
+ }
+
+ private void HandleOptionsToolbarButtonClick(object sender, RoutedEventArgs e)
+ {
+ // Create the options form
+ var optionsWindow = new OptionsWindow { Owner = this };
+
+ // Show the options form and get the result
+ var result = optionsWindow.ShowDialog();
+
+ // If okay was selected
+ if (result.HasValue && result.Value)
+ {
+ // Reset the database to current settings
+ ResetDatabase();
+
+ // Re-initialize the feed display
+ DisplayFeed();
+ }
+ }
+
+ private void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e)
+ {
+ MarkAllItemsAsRead();
+ }
+
+ private void HandleShowErrorsButtonClick(object sender, RoutedEventArgs e)
+ {
+ // Create the feed error window
+ var feedErrorWindow = new FeedErrorWindow();
+
+ // Display the window
+ var result = feedErrorWindow.Display(this);
+
+ // If okay was selected
+ if (result.GetValueOrDefault())
+ {
+ // Reset the database to current settings
+ ResetDatabase();
+
+ // Re-initialize the feed display
+ DisplayFeed();
+
+ UpdateErrorLink();
+ }
+ }
+
+ private void HandleRefreshMenuItemClick(object sender, RoutedEventArgs e)
+ {
+ var menuItem = (MenuItem) e.Source;
+
+ if (Equals(menuItem, MenuRefresh))
+ ReadCurrentFeed(true);
+ else if (Equals(menuItem, MenuRefreshAll))
+ ReadFeeds(true);
+ }
+
+ private void HandleRefreshToolbarButtonClick(object sender, RoutedEventArgs e)
+ {
+ ReadFeeds(true);
+ }
+
+ private void HandleOpenAllMenuItemClick(object sender, RoutedEventArgs e)
+ {
+ var menuItem = (MenuItem) e.Source;
+
+ if (Equals(menuItem, MenuOpenAllSinglePage))
+ OpenAllFeedItemsOnSinglePage();
+ else if (Equals(menuItem, MenuOpenAllMultiplePages))
+ OpenAllFeedItemsIndividually();
+ }
+
+ private void HandleOpenAllToolbarButtonClick(object sender, RoutedEventArgs e)
+ {
+ var multipleOpenAction = _currentFeed.MultipleOpenAction;
+
+ switch (multipleOpenAction)
+ {
+ case MultipleOpenAction.IndividualPages:
+ OpenAllFeedItemsIndividually();
+ break;
+ case MultipleOpenAction.SinglePage:
+ OpenAllFeedItemsOnSinglePage();
+ break;
+ }
+ }
+
+ private void HandleEditCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
+ {
+ // Create a new feed window
+ var feedWindow = new FeedWindow();
+
+ // Display the feed window and get the result
+ var result = feedWindow.Display(_database, _currentFeed, this);
+
+ // If OK was clicked...
+ if (result.HasValue && result.Value)
+ {
+ // Save
+ _database.SaveChanges();
+
+ // Update feed
+ DisplayFeed();
+ }
+ }
+
+ private void HandleDeleteCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
+ {
+ // Confirm this delete since it is for real
+ if (MessageBox.Show(this, Properties.Resources.ConfirmDelete, string.Empty, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
+ return;
+
+ // Get the current feed
+ var feedToDelete = _currentFeed;
+
+ // Move to the next feed
+ NextFeed();
+
+ // Delete all items
+ foreach (var item in feedToDelete.Items.ToList())
+ _database.FeedItems.Remove(item);
+
+ // Delete the feed
+ _database.Feeds.Remove(feedToDelete);
+
+ // Save
+ _database.SaveChanges();
+ }
+
+ private void OpenAllFeedItemsOnSinglePage()
+ {
+ var fileName = Path.GetTempFileName() + ".html";
+ TextWriter textWriter = new StreamWriter(fileName);
+
+ using (var htmlTextWriter = new HtmlTextWriter(textWriter))
+ {
+ htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Html);
+
+ htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Head);
+
+ htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Title);
+ htmlTextWriter.Write(_currentFeed.Title);
+ htmlTextWriter.RenderEndTag();
+
+ htmlTextWriter.AddAttribute("http-equiv", "Content-Type");
+ htmlTextWriter.AddAttribute("content", "text/html; charset=utf-8");
+ htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Meta);
+ htmlTextWriter.RenderEndTag();
+
+ htmlTextWriter.RenderEndTag();
+
+ htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Body);
+
+ var sortedItems = from item in _currentFeed.Items where !item.BeenRead orderby item.Sequence ascending select item;
+
+ var firstItem = true;
+
+ foreach (var item in sortedItems)
+ {
+ if (!firstItem)
+ {
+ htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Hr);
+ htmlTextWriter.RenderEndTag();
+ }
+
+ htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Div);
+
+ htmlTextWriter.AddAttribute(HtmlTextWriterAttribute.Href, item.Link);
+ htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.A);
+ htmlTextWriter.Write(item.Title.Length == 0 ? item.Link : item.Title);
+ htmlTextWriter.RenderEndTag();
+
+ htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Br);
+ htmlTextWriter.RenderEndTag();
+
+ htmlTextWriter.Write(item.Description);
+
+ htmlTextWriter.RenderEndTag();
+
+ firstItem = false;
+ }
+
+ htmlTextWriter.RenderEndTag();
+ htmlTextWriter.RenderEndTag();
+ }
+
+ textWriter.Flush();
+ textWriter.Close();
+
+ BrowserCommon.OpenLink(fileName);
+
+ MarkAllItemsAsRead();
+ }
+ }
+}
diff --git a/Application/MainWindow/UpdateHandler.cs b/Application/MainWindow/UpdateHandler.cs
new file mode 100644
index 0000000..d51813b
--- /dev/null
+++ b/Application/MainWindow/UpdateHandler.cs
@@ -0,0 +1,40 @@
+using Common.Update;
+using FeedCenter.Properties;
+using System.Windows;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private static void InitializeUpdate()
+ {
+ UpdateCheck.ApplicationName = Properties.Resources.ApplicationDisplayName;
+ UpdateCheck.UpdateServer = Settings.Default.VersionLocation;
+ UpdateCheck.UpdateFile = Settings.Default.VersionFile;
+ UpdateCheck.ApplicationShutdown = ApplicationShutdown;
+ UpdateCheck.ApplicationCurrentMessage = ApplicationCurrentMessage;
+ UpdateCheck.ApplicationUpdateMessage = ApplicationUpdateMessage;
+ }
+
+ private static bool ApplicationUpdateMessage(string title, string message)
+ {
+ return MessageBox.Show(message, title, MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes;
+ }
+
+ private static void ApplicationCurrentMessage(string title, string message)
+ {
+ MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+
+ private static void ApplicationShutdown()
+ {
+ Application.Current.Shutdown();
+ }
+
+ private void HandleNewVersionLinkClick(object sender, RoutedEventArgs e)
+ {
+ // Display update information
+ UpdateCheck.DisplayUpdateInformation(true);
+ }
+ }
+}
diff --git a/Application/MainWindow/WindowHandler.cs b/Application/MainWindow/WindowHandler.cs
new file mode 100644
index 0000000..5e6dedc
--- /dev/null
+++ b/Application/MainWindow/WindowHandler.cs
@@ -0,0 +1,180 @@
+using FeedCenter.Properties;
+using System;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Interop;
+using System.Windows.Media;
+using Common.Helpers;
+
+namespace FeedCenter
+{
+ public partial class MainWindow
+ {
+ private void LoadWindowSettings()
+ {
+ // Get the last window location
+ var windowLocation = Settings.Default.WindowLocation;
+
+ // Set the window into position
+ Left = windowLocation.X;
+ Top = windowLocation.Y;
+
+ // Get the last window size
+ var windowSize = Settings.Default.WindowSize;
+
+ // Set the window into the previous size if it is valid
+ if (!windowSize.Width.Equals(0) && !windowSize.Height.Equals(0))
+ {
+ Width = windowSize.Width;
+ Height = windowSize.Height;
+ }
+
+ // Set the location of the navigation tray
+ switch (Settings.Default.ToolbarLocation)
+ {
+ case Dock.Top:
+ NameBasedGrid.NameBasedGrid.SetRow(NavigationToolbarTray, "TopToolbarRow");
+ break;
+ case Dock.Bottom:
+ NameBasedGrid.NameBasedGrid.SetRow(NavigationToolbarTray, "BottomToolbarRow");
+ break;
+ }
+
+ // Load the lock state
+ HandleWindowLockState();
+ }
+
+ private void SaveWindowSettings()
+ {
+ // Set the last window location
+ Settings.Default.WindowLocation = new Point(Left, Top);
+
+ // Set the last window size
+ Settings.Default.WindowSize = new Size(Width, Height);
+
+ // Save the dock on the navigation tray
+ Settings.Default.ToolbarLocation = NameBasedGrid.NameBasedGrid.GetRow(NavigationToolbarTray) == "TopToolbarRow" ? Dock.Top : Dock.Bottom;
+
+ // Save settings
+ Settings.Default.Save();
+ }
+
+ private void HandleWindowLockState()
+ {
+ // Set the resize mode for the window
+ ResizeMode = Settings.Default.WindowLocked ? ResizeMode.NoResize : ResizeMode.CanResize;
+
+ // Show or hide the border
+ WindowBorder.BorderBrush = Settings.Default.WindowLocked ? SystemColors.ActiveBorderBrush : Brushes.Transparent;
+
+ // Update the borders
+ UpdateBorder();
+ }
+
+ protected override void OnClosing(CancelEventArgs e)
+ {
+ base.OnClosing(e);
+
+ // Ditch the worker
+ if (_feedReadWorker != null)
+ {
+ _feedReadWorker.CancelAsync();
+ _feedReadWorker.Dispose();
+ }
+
+ // Get rid of the timer
+ TerminateTimer();
+
+ // Save current window settings
+ SaveWindowSettings();
+
+ // Save settings
+ Settings.Default.Save();
+
+ // Save options
+ _database.SaveChanges();
+
+ // Get rid of the notification icon
+ NotificationIcon.Dispose();
+ }
+
+ private DelayedMethod _windowStateDelay;
+ private void HandleWindowSizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ if (_windowStateDelay == null)
+ _windowStateDelay = new DelayedMethod(500, UpdateWindowSettings);
+
+ _windowStateDelay.Reset();
+ }
+
+ private void HandleWindowLocationChanged(object sender, EventArgs e)
+ {
+ if (_windowStateDelay == null)
+ _windowStateDelay = new DelayedMethod(500, UpdateWindowSettings);
+
+ _windowStateDelay.Reset();
+ }
+
+ private void UpdateBorder()
+ {
+ var windowInteropHelper = new WindowInteropHelper(this);
+
+ var screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
+
+ var rectangle = new System.Drawing.Rectangle
+ {
+ X = (int) Left,
+ Y = (int) Top,
+ Width = (int) Width,
+ Height = (int) Height
+ };
+
+ var borderThickness = new Thickness();
+
+ if (rectangle.Right != screen.WorkingArea.Right)
+ borderThickness.Right = 1;
+
+ if (rectangle.Left != screen.WorkingArea.Left)
+ borderThickness.Left = 1;
+
+ if (rectangle.Top != screen.WorkingArea.Top)
+ borderThickness.Top = 1;
+
+ if (rectangle.Bottom != screen.WorkingArea.Bottom)
+ borderThickness.Bottom = 1;
+
+ WindowBorder.BorderThickness = borderThickness;
+ }
+
+ private void UpdateWindowSettings()
+ {
+ // Save current window settings
+ SaveWindowSettings();
+
+ // Update the border
+ UpdateBorder();
+ }
+
+ private bool _activated;
+ protected override void OnActivated(EventArgs e)
+ {
+ base.OnActivated(e);
+
+ if (_activated)
+ return;
+
+ _activated = true;
+
+ // Load the lock state
+ HandleWindowLockState();
+
+ // Watch for size and location changes
+ SizeChanged += HandleWindowSizeChanged;
+ LocationChanged += HandleWindowLocationChanged;
+
+ // Watch for setting changes
+ Settings.Default.PropertyChanged += HandlePropertyChanged;
+ }
+ }
+}
diff --git a/Application/Options/FeedsOptionsPanel.xaml.cs b/Application/Options/FeedsOptionsPanel.xaml.cs
index f3efe91..1dd0f65 100644
--- a/Application/Options/FeedsOptionsPanel.xaml.cs
+++ b/Application/Options/FeedsOptionsPanel.xaml.cs
@@ -394,7 +394,9 @@ namespace FeedCenter.Options
_collectionViewSource.View.Refresh();
- //textBlock.TextDecorations = null;
+ var dataGridRow = (DataGridRow) sender;
+
+ dataGridRow.FontWeight = FontWeights.Normal;
}
private void HandleListBoxItemPreviewMouseMove(object sender, MouseEventArgs e)
diff --git a/Application/Properties/Resources.Designer.cs b/Application/Properties/Resources.Designer.cs
index 8d53d04..a7b3d7d 100644
--- a/Application/Properties/Resources.Designer.cs
+++ b/Application/Properties/Resources.Designer.cs
@@ -96,6 +96,15 @@ namespace FeedCenter.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to < all >.
+ ///
+ public static string AllCategory {
+ get {
+ return ResourceManager.GetString("AllCategory", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
///
@@ -196,6 +205,15 @@ namespace FeedCenter.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Category: {0}.
+ ///
+ public static string CategoryFilterHeader {
+ get {
+ return ResourceManager.GetString("CategoryFilterHeader", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Category.
///
diff --git a/Application/Properties/Resources.resx b/Application/Properties/Resources.resx
index 4b7f4b4..477699f 100644
--- a/Application/Properties/Resources.resx
+++ b/Application/Properties/Resources.resx
@@ -517,4 +517,10 @@
Choose Feed to Add
+
+ < all >
+
+
+ Category: {0}
+
\ No newline at end of file
diff --git a/Application/Properties/Settings.Designer.cs b/Application/Properties/Settings.Designer.cs
index 890bdb8..cb39435 100644
--- a/Application/Properties/Settings.Designer.cs
+++ b/Application/Properties/Settings.Designer.cs
@@ -277,5 +277,17 @@ namespace FeedCenter.Properties {
return ((string)(this["VersionLocation"]));
}
}
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string LastCategoryID {
+ get {
+ return ((string)(this["LastCategoryID"]));
+ }
+ set {
+ this["LastCategoryID"] = value;
+ }
+ }
}
}
diff --git a/Application/Properties/Settings.settings b/Application/Properties/Settings.settings
index 79f4c09..89418d9 100644
--- a/Application/Properties/Settings.settings
+++ b/Application/Properties/Settings.settings
@@ -68,5 +68,8 @@
http://server/FeedCenter/
+
+
+
\ No newline at end of file
diff --git a/Application/app.config b/Application/app.config
index 230b03b..850a605 100644
--- a/Application/app.config
+++ b/Application/app.config
@@ -57,6 +57,9 @@
Normal
+
+
+