794 lines
31 KiB
C#
794 lines
31 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using System.Xml;
|
|
using System.Threading;
|
|
using System.IO;
|
|
using System.Resources;
|
|
|
|
namespace RadioDJViewer
|
|
{
|
|
public partial class Main : Form
|
|
{
|
|
private string _currentTrackKey = string.Empty;
|
|
private string selectedFolderPath = string.Empty;
|
|
private string outputFolderPath = string.Empty;
|
|
private bool isConnected = false;
|
|
private string currentProfile = "Default";
|
|
private string currentSongImagePath = string.Empty; // Path to the current song image
|
|
private string defaultImagePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "default.jpg");
|
|
private string mainImagesFolderPath = string.Empty; // New field for main images folder path
|
|
private Profile loadedProfile = null; // Field to store the loaded profile
|
|
private WidgetServer widgetServer = null; // Local widget HTTP server
|
|
private bool widgetRunning = false;
|
|
private CancellationTokenSource statusCts = null;
|
|
|
|
// Timer to poll REST API
|
|
private System.Windows.Forms.Timer apiTimer;
|
|
private string lastApiXml = null;
|
|
|
|
private System.Windows.Forms.Timer marqueeTimerTitle;
|
|
private System.Windows.Forms.Timer marqueeTimerArtist;
|
|
private System.Windows.Forms.Timer marqueeTimerAlbum;
|
|
private int marqueeOffsetTitle = 0;
|
|
private int marqueeOffsetArtist = 0;
|
|
private int marqueeOffsetAlbum = 0;
|
|
private string marqueeTextTitle = "";
|
|
private string marqueeTextArtist = "";
|
|
private string marqueeTextAlbum = "";
|
|
|
|
private System.Windows.Forms.Timer pauseTimerTitle;
|
|
private System.Windows.Forms.Timer pauseTimerArtist;
|
|
private System.Windows.Forms.Timer pauseTimerAlbum;
|
|
|
|
private int marqueeScrollSpeed = 100; // Default to Medium
|
|
private int marqueePauseTime = 6000; // Default to 6 seconds
|
|
private string marqueeSeparator = " | "; // Default separator
|
|
private bool loggingEnabled = false;
|
|
private string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RadioDJViewer.log");
|
|
|
|
private bool isDarkMode = false; // New field for dark mode
|
|
|
|
public Main()
|
|
{
|
|
InitializeComponent();
|
|
UpdateStatusBar();
|
|
// Set up timer for polling API every 3 seconds
|
|
apiTimer = new System.Windows.Forms.Timer();
|
|
apiTimer.Interval = 3000; // 3 seconds
|
|
apiTimer.Tick += ApiTimer_Tick;
|
|
// Marquee timers for each label
|
|
marqueeTimerTitle = new System.Windows.Forms.Timer();
|
|
marqueeTimerTitle.Interval = marqueeScrollSpeed;
|
|
marqueeTimerTitle.Tick += MarqueeTimerTitle_Tick;
|
|
marqueeTimerArtist = new System.Windows.Forms.Timer();
|
|
marqueeTimerArtist.Interval = marqueeScrollSpeed;
|
|
marqueeTimerArtist.Tick += MarqueeTimerArtist_Tick;
|
|
marqueeTimerAlbum = new System.Windows.Forms.Timer();
|
|
marqueeTimerAlbum.Interval = marqueeScrollSpeed;
|
|
marqueeTimerAlbum.Tick += MarqueeTimerAlbum_Tick;
|
|
// Wire up dark mode button
|
|
this.uicolor.Click += Uicolor_Click;
|
|
this.load_profile_button.Click += load_profile_button_Click;
|
|
PopulateProfileDropBox();
|
|
UpdateTheme();
|
|
// Auto-load profiles if profiles.json exists
|
|
AutoLoadProfiles();
|
|
}
|
|
|
|
private void EnsureDefaultImageExists()
|
|
{
|
|
if (!File.Exists(defaultImagePath))
|
|
{
|
|
// Try to extract from resources
|
|
var fallbackImg = Properties.Resources.fallback;
|
|
if (fallbackImg != null)
|
|
{
|
|
fallbackImg.Save(defaultImagePath, System.Drawing.Imaging.ImageFormat.Jpeg);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetMarqueeScrollSpeed(string speed)
|
|
{
|
|
switch (speed)
|
|
{
|
|
case "Very Slow": marqueeScrollSpeed = 300; break;
|
|
case "Slow": marqueeScrollSpeed = 200; break;
|
|
case "Medium": marqueeScrollSpeed = 100; break;
|
|
case "Fast": marqueeScrollSpeed = 60; break;
|
|
case "Very Fast": marqueeScrollSpeed = 30; break;
|
|
default: marqueeScrollSpeed = 100; break;
|
|
}
|
|
marqueeTimerTitle.Interval = marqueeScrollSpeed;
|
|
marqueeTimerArtist.Interval = marqueeScrollSpeed;
|
|
marqueeTimerAlbum.Interval = marqueeScrollSpeed;
|
|
}
|
|
|
|
public void SetMarqueePauseTime(int seconds)
|
|
{
|
|
marqueePauseTime = seconds * 1000;
|
|
}
|
|
|
|
public void SetMarqueeSeparator(string separator)
|
|
{
|
|
marqueeSeparator = separator;
|
|
}
|
|
|
|
private void LogMessage(string message)
|
|
{
|
|
if (!loggingEnabled) return;
|
|
try
|
|
{
|
|
File.AppendAllText(logFilePath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}\r\n");
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private int GetVisibleChars(Label label)
|
|
{
|
|
// Use the label's actual width and font to estimate visible characters
|
|
using (Graphics g = label.CreateGraphics())
|
|
{
|
|
// Use the full label width for measurement
|
|
SizeF size = g.MeasureString("W", label.Font);
|
|
int chars = (int)(label.Width / size.Width);
|
|
// If the label is single-line, ensure we use the full width
|
|
return Math.Max(chars, 1);
|
|
}
|
|
}
|
|
|
|
private void SetLabelTextFull(Label label, string text)
|
|
{
|
|
// Always set the full text, and let marquee logic handle scrolling
|
|
label.Text = text;
|
|
}
|
|
|
|
private void MarqueeTimerTitle_Tick(object sender, EventArgs e)
|
|
{
|
|
int visibleChars = GetVisibleChars(label4);
|
|
if (marqueeTextTitle.Length > visibleChars)
|
|
{
|
|
string separator = string.IsNullOrEmpty(marqueeSeparator) ? "" : marqueeSeparator;
|
|
string scrollText = marqueeTextTitle + separator;
|
|
string repeatedScrollText = scrollText + scrollText;
|
|
marqueeOffsetTitle = (marqueeOffsetTitle + 1) % scrollText.Length;
|
|
label4.Text = repeatedScrollText.Substring(marqueeOffsetTitle, visibleChars);
|
|
LogMessage($"MarqueeTitle: separator='{separator}', scrollText='{scrollText}', offset={marqueeOffsetTitle}");
|
|
if (marqueeOffsetTitle == 0)
|
|
{
|
|
marqueeTimerTitle.Stop();
|
|
if (pauseTimerTitle == null)
|
|
{
|
|
pauseTimerTitle = new System.Windows.Forms.Timer();
|
|
pauseTimerTitle.Tick += (s, args) => {
|
|
pauseTimerTitle.Stop();
|
|
marqueeTimerTitle.Start();
|
|
};
|
|
}
|
|
pauseTimerTitle.Interval = marqueePauseTime;
|
|
pauseTimerTitle.Start();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetLabelTextFull(label4, marqueeTextTitle);
|
|
}
|
|
}
|
|
|
|
private void MarqueeTimerArtist_Tick(object sender, EventArgs e)
|
|
{
|
|
int visibleChars = GetVisibleChars(label5);
|
|
if (marqueeTextArtist.Length > visibleChars)
|
|
{
|
|
string separator = string.IsNullOrEmpty(marqueeSeparator) ? "" : marqueeSeparator;
|
|
string scrollText = marqueeTextArtist + separator;
|
|
string repeatedScrollText = scrollText + scrollText;
|
|
marqueeOffsetArtist = (marqueeOffsetArtist + 1) % scrollText.Length;
|
|
label5.Text = repeatedScrollText.Substring(marqueeOffsetArtist, visibleChars);
|
|
LogMessage($"MarqueeArtist: separator='{separator}', scrollText='{scrollText}', offset={marqueeOffsetArtist}");
|
|
if (marqueeOffsetArtist == 0)
|
|
{
|
|
marqueeTimerArtist.Stop();
|
|
if (pauseTimerArtist == null)
|
|
{
|
|
pauseTimerArtist = new System.Windows.Forms.Timer();
|
|
pauseTimerArtist.Tick += (s, args) => {
|
|
pauseTimerArtist.Stop();
|
|
marqueeTimerArtist.Start();
|
|
};
|
|
}
|
|
pauseTimerArtist.Interval = marqueePauseTime;
|
|
pauseTimerArtist.Start();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetLabelTextFull(label5, marqueeTextArtist);
|
|
}
|
|
}
|
|
|
|
private void MarqueeTimerAlbum_Tick(object sender, EventArgs e)
|
|
{
|
|
int visibleChars = GetVisibleChars(label6);
|
|
if (marqueeTextAlbum.Length > visibleChars)
|
|
{
|
|
string separator = string.IsNullOrEmpty(marqueeSeparator) ? "" : marqueeSeparator;
|
|
string scrollText = marqueeTextAlbum + separator;
|
|
string repeatedScrollText = scrollText + scrollText;
|
|
marqueeOffsetAlbum = (marqueeOffsetAlbum + 1) % scrollText.Length;
|
|
label6.Text = repeatedScrollText.Substring(marqueeOffsetAlbum, visibleChars);
|
|
LogMessage($"MarqueeAlbum: separator='{separator}', scrollText='{scrollText}', offset={marqueeOffsetAlbum}");
|
|
if (marqueeOffsetAlbum == 0)
|
|
{
|
|
marqueeTimerAlbum.Stop();
|
|
if (pauseTimerAlbum == null)
|
|
{
|
|
pauseTimerAlbum = new System.Windows.Forms.Timer();
|
|
pauseTimerAlbum.Tick += (s, args) => {
|
|
pauseTimerAlbum.Stop();
|
|
marqueeTimerAlbum.Start();
|
|
};
|
|
}
|
|
pauseTimerAlbum.Interval = marqueePauseTime;
|
|
pauseTimerAlbum.Start();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetLabelTextFull(label6, marqueeTextAlbum);
|
|
}
|
|
}
|
|
|
|
private void ApiTimer_Tick(object sender, EventArgs e)
|
|
{
|
|
if (isConnected)
|
|
{
|
|
_ = PollRestApi();
|
|
}
|
|
}
|
|
|
|
private async Task PollRestApi()
|
|
{
|
|
if (loadedProfile == null) return;
|
|
string url = loadedProfile.UrlFormat;
|
|
if (!string.IsNullOrWhiteSpace(url))
|
|
{
|
|
url = url.Replace("{ip}", loadedProfile.IP ?? "")
|
|
.Replace("{port}", loadedProfile.Port ?? "")
|
|
.Replace("{password}", loadedProfile.Password ?? "");
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
var client = new System.Net.Http.HttpClient();
|
|
var response = await client.GetAsync(url);
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var xml = await response.Content.ReadAsStringAsync();
|
|
if (xml != lastApiXml)
|
|
{
|
|
lastApiXml = xml;
|
|
ParseAndDisplaySongInfo(xml);
|
|
// Build a key from Artist and Title and only show change if different
|
|
try
|
|
{
|
|
var newKey = $"{marqueeTextArtist}|{marqueeTextTitle}";
|
|
if (!string.Equals(newKey, _currentTrackKey, StringComparison.Ordinal))
|
|
{
|
|
_currentTrackKey = newKey;
|
|
ShowTemporaryStatus("Song Change Detected", 2000);
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private void AutoLoadProfiles()
|
|
{
|
|
string profilesPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "profiles.json");
|
|
if (File.Exists(profilesPath))
|
|
{
|
|
var profileNames = ProfileStorage.GetProfileNames();
|
|
if (profileNames.Count > 0)
|
|
{
|
|
// Optionally, load the first profile by default
|
|
var profile = ProfileStorage.LoadProfile(profileNames[0]);
|
|
if (profile != null)
|
|
{
|
|
LoadProfile(profile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private async void button1_Click(object sender, EventArgs e)
|
|
{
|
|
// Show connecting message
|
|
ShowTemporaryStatus("Connecting to API...", 0);
|
|
|
|
// Connect to REST API (ConnectToRestApi will start web server if needed)
|
|
await ConnectToRestApi();
|
|
if (isConnected)
|
|
{
|
|
apiTimer.Start();
|
|
}
|
|
}
|
|
|
|
private void button2_Click(object sender, EventArgs e)
|
|
{
|
|
// Disconnect
|
|
isConnected = false;
|
|
apiTimer.Stop();
|
|
// Stop widget server when disconnecting
|
|
try
|
|
{
|
|
widgetServer?.Stop();
|
|
widgetServer = null;
|
|
widgetRunning = false;
|
|
}
|
|
catch { }
|
|
ClearStatusMessage();
|
|
UpdateStatusBar();
|
|
}
|
|
|
|
private async Task ConnectToRestApi()
|
|
{
|
|
// Use loaded profile values
|
|
if (loadedProfile == null)
|
|
{
|
|
MessageBox.Show("No profile loaded. Please load a profile first.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
isConnected = false;
|
|
UpdateStatusBar();
|
|
return;
|
|
}
|
|
string url = loadedProfile.UrlFormat;
|
|
if (!string.IsNullOrWhiteSpace(url))
|
|
{
|
|
url = url.Replace("{ip}", loadedProfile.IP ?? "")
|
|
.Replace("{port}", loadedProfile.Port ?? "")
|
|
.Replace("{password}", loadedProfile.Password ?? "");
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("Profile URL format is missing.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
isConnected = false;
|
|
UpdateStatusBar();
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
var client = new System.Net.Http.HttpClient();
|
|
var response = await client.GetAsync(url);
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var xml = await response.Content.ReadAsStringAsync();
|
|
ParseAndDisplaySongInfo(xml);
|
|
try
|
|
{
|
|
_currentTrackKey = $"{marqueeTextArtist}|{marqueeTextTitle}";
|
|
}
|
|
catch { }
|
|
isConnected = true;
|
|
// Clear the 'Connecting...' persistent message and show connected
|
|
ClearStatusMessage();
|
|
UpdateStatusBar();
|
|
// Start web server immediately after API connection if profile requests it
|
|
StartWidgetServerIfEnabled();
|
|
}
|
|
else
|
|
{
|
|
isConnected = false;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show($"Error connecting to REST API: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
isConnected = false;
|
|
}
|
|
UpdateStatusBar();
|
|
}
|
|
|
|
// Call this method after parsing song info, assuming image path is available from profile or API
|
|
private void UpdateCurrentSongImage(string imageName)
|
|
{
|
|
string imagePath = string.Empty;
|
|
string outputImageName = loadedProfile?.OutputImageName;
|
|
bool useFallback = false;
|
|
if (!string.IsNullOrEmpty(mainImagesFolderPath) && !string.IsNullOrEmpty(imageName))
|
|
{
|
|
imagePath = Path.Combine(mainImagesFolderPath, imageName);
|
|
if (!File.Exists(imagePath))
|
|
{
|
|
useFallback = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
useFallback = true;
|
|
}
|
|
if (useFallback)
|
|
{
|
|
// Use fallback PNG from resources
|
|
var fallbackImg = Properties.Resources.fallback;
|
|
if (fallbackImg != null)
|
|
{
|
|
var resized = new Bitmap(fallbackImg, pictureBox1.Size);
|
|
pictureBox1.Image = resized;
|
|
// Save fallback image to output folder as PNG
|
|
if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName))
|
|
{
|
|
string destPath = Path.Combine(outputFolderPath, Path.ChangeExtension(outputImageName, ".png"));
|
|
resized.Save(destPath, System.Drawing.Imaging.ImageFormat.Png);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pictureBox1.Image = null;
|
|
}
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
using (var img = Image.FromFile(imagePath))
|
|
{
|
|
var resized = new Bitmap(img, pictureBox1.Size);
|
|
pictureBox1.Image = resized;
|
|
// Save to output folder as PNG
|
|
if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName))
|
|
{
|
|
string destPath = Path.Combine(outputFolderPath, Path.ChangeExtension(outputImageName, ".png"));
|
|
resized.Save(destPath, System.Drawing.Imaging.ImageFormat.Png);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// If loading image fails, fallback
|
|
var fallbackImg = Properties.Resources.fallback;
|
|
if (fallbackImg != null)
|
|
{
|
|
var resized = new Bitmap(fallbackImg, pictureBox1.Size);
|
|
pictureBox1.Image = resized;
|
|
if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName))
|
|
{
|
|
string destPath = Path.Combine(outputFolderPath, Path.ChangeExtension(outputImageName, ".png"));
|
|
resized.Save(destPath, System.Drawing.Imaging.ImageFormat.Png);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pictureBox1.Image = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void StartMarqueeTimers()
|
|
{
|
|
marqueeOffsetTitle = 0;
|
|
marqueeOffsetArtist = 0;
|
|
marqueeOffsetAlbum = 0;
|
|
marqueeTimerTitle.Start();
|
|
marqueeTimerArtist.Start();
|
|
marqueeTimerAlbum.Start();
|
|
}
|
|
|
|
private void StopMarqueeTimers()
|
|
{
|
|
marqueeTimerTitle.Stop();
|
|
marqueeTimerArtist.Stop();
|
|
marqueeTimerAlbum.Stop();
|
|
}
|
|
|
|
private void ParseAndDisplaySongInfo(string xml)
|
|
{
|
|
try
|
|
{
|
|
var doc = new XmlDocument();
|
|
doc.LoadXml(xml);
|
|
// Parse title, artist, album from XML (case-insensitive)
|
|
string title = doc.SelectSingleNode("//*[translate(local-name(), 'TITLE', 'title')='title']")?.InnerText;
|
|
string artist = doc.SelectSingleNode("//*[translate(local-name(), 'ARTIST', 'artist')='artist']")?.InnerText;
|
|
string album = doc.SelectSingleNode("//*[translate(local-name(), 'ALBUM', 'album')='album']")?.InnerText;
|
|
string albumArt = doc.SelectSingleNode("//*[translate(local-name(), 'ALBUMART', 'albumart')='albumart']")?.InnerText;
|
|
|
|
marqueeTextTitle = !string.IsNullOrWhiteSpace(title) ? title : "No title";
|
|
marqueeTextArtist = !string.IsNullOrWhiteSpace(artist) ? artist : "No artist";
|
|
marqueeTextAlbum = !string.IsNullOrWhiteSpace(album) ? album : "No album";
|
|
StartMarqueeTimers();
|
|
|
|
// Write to output files
|
|
if (!string.IsNullOrEmpty(outputFolderPath))
|
|
{
|
|
File.WriteAllText(Path.Combine(outputFolderPath, "title.txt"), marqueeTextTitle);
|
|
File.WriteAllText(Path.Combine(outputFolderPath, "artist.txt"), marqueeTextArtist);
|
|
File.WriteAllText(Path.Combine(outputFolderPath, "album.txt"), marqueeTextAlbum);
|
|
}
|
|
|
|
// Handle album art image
|
|
if (!string.IsNullOrWhiteSpace(albumArt))
|
|
{
|
|
UpdateCurrentSongImage(albumArt);
|
|
}
|
|
else
|
|
{
|
|
UpdateCurrentSongImage(null);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
marqueeTextTitle = "No title";
|
|
marqueeTextArtist = "No artist";
|
|
marqueeTextAlbum = "No album";
|
|
StartMarqueeTimers();
|
|
pictureBox1.Image = null;
|
|
}
|
|
}
|
|
|
|
private void UpdateStatusBar()
|
|
{
|
|
// Update color box based on connection states
|
|
try
|
|
{
|
|
if (!isConnected)
|
|
{
|
|
toolStripStatusLabel2.BackColor = Color.Red; // disconnected/error
|
|
}
|
|
else
|
|
{
|
|
if (isConnected && widgetRunning)
|
|
toolStripStatusLabel2.BackColor = Color.Blue; // both running
|
|
else
|
|
toolStripStatusLabel2.BackColor = Color.Green; // API ok, web off
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
// Profile
|
|
toolStripStatusLabel4.Text = $"Profile: {currentProfile}";
|
|
// If there's no active temporary message, ensure label3 shows default
|
|
if (statusCts == null)
|
|
{
|
|
toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected";
|
|
}
|
|
}
|
|
|
|
private void ShowTemporaryStatus(string message, int milliseconds)
|
|
{
|
|
// Cancel any previous temporary status
|
|
try
|
|
{
|
|
statusCts?.Cancel();
|
|
}
|
|
catch { }
|
|
statusCts = null;
|
|
|
|
if (milliseconds <= 0)
|
|
{
|
|
// Persistent message until changed - keep a non-null token so UpdateStatusBar doesn't overwrite
|
|
var persistent = new CancellationTokenSource();
|
|
statusCts = persistent;
|
|
toolStripStatusLabel3.Text = message;
|
|
return;
|
|
}
|
|
|
|
var cts = new CancellationTokenSource();
|
|
statusCts = cts;
|
|
toolStripStatusLabel3.Text = message;
|
|
|
|
Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
await Task.Delay(milliseconds, cts.Token);
|
|
if (!cts.IsCancellationRequested)
|
|
{
|
|
// clear temporary message
|
|
statusCts = null;
|
|
this.BeginInvoke((Action)(() => {
|
|
toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected";
|
|
}));
|
|
}
|
|
}
|
|
catch { }
|
|
});
|
|
}
|
|
|
|
private void ClearStatusMessage()
|
|
{
|
|
try { statusCts?.Cancel(); } catch { }
|
|
statusCts = null;
|
|
try { toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected"; } catch { }
|
|
}
|
|
|
|
private void StartWidgetServerIfEnabled()
|
|
{
|
|
// Called after API connection is confirmed
|
|
if (loadedProfile == null || !loadedProfile.WebServerEnabled)
|
|
return;
|
|
|
|
try
|
|
{
|
|
if (widgetServer == null)
|
|
{
|
|
int port = loadedProfile.WebServerPort > 0 ? loadedProfile.WebServerPort : 8080;
|
|
widgetServer = new WidgetServer(port, () => marqueeTextArtist, () => marqueeTextTitle, () => this.currentSongImagePath ?? this.defaultImagePath);
|
|
}
|
|
widgetServer.Start();
|
|
widgetRunning = true;
|
|
UpdateStatusBar();
|
|
ShowTemporaryStatus($"Web: Running on {loadedProfile.WebServerPort}", 5000);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
widgetRunning = false;
|
|
UpdateStatusBar();
|
|
// Show error message
|
|
ShowTemporaryStatus($"Web server error: {ex.Message}", 5000);
|
|
}
|
|
}
|
|
|
|
private void restAPISettingToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
// Open settings/profile dialog
|
|
var settingsForm = new Form1();
|
|
settingsForm.ApplyTheme(isDarkMode); // Pass current color mode
|
|
settingsForm.ShowDialog();
|
|
}
|
|
|
|
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
|
var versionAttr = assembly.GetCustomAttributes(typeof(System.Reflection.AssemblyFileVersionAttribute), false)
|
|
.OfType<System.Reflection.AssemblyFileVersionAttribute>().FirstOrDefault();
|
|
string version = versionAttr != null ? versionAttr.Version : assembly.GetName().Version.ToString();
|
|
string message = $"RadioDJViewer\nVersion: {version}\nGitHub: https://git.smartcraft.me/minster586/RadioDJViewer";
|
|
MessageBox.Show(message, "About RadioDJViewer", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
}
|
|
|
|
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
Application.Exit();
|
|
}
|
|
|
|
private void toolStripStatusLabel4_Click(object sender, EventArgs e)
|
|
{
|
|
// Profile click handler (to be implemented)
|
|
}
|
|
|
|
public void LoadProfile(Profile profile)
|
|
{
|
|
// Update main form fields from profile
|
|
currentProfile = profile.Name;
|
|
selectedFolderPath = profile.OutputFolder;
|
|
outputFolderPath = profile.OutputFolder;
|
|
mainImagesFolderPath = profile.MainImagesFolder; // Update new field
|
|
loadedProfile = profile; // Store loaded profile for REST API connection
|
|
// Update polling rate when profile is loaded
|
|
int pollingRate = profile.PollingRateSeconds > 0 ? profile.PollingRateSeconds : 3;
|
|
apiTimer.Interval = pollingRate * 1000;
|
|
SetMarqueeScrollSpeed(profile.MarqueeScrollSpeed ?? "Medium");
|
|
SetMarqueePauseTime(profile.MarqueePauseTime > 0 ? profile.MarqueePauseTime : 6);
|
|
SetMarqueeSeparator(profile.MarqueeSeparator ?? " | ");
|
|
// Immediately apply marquee settings to timers
|
|
if (pauseTimerTitle != null) pauseTimerTitle.Interval = marqueePauseTime;
|
|
if (pauseTimerArtist != null) pauseTimerArtist.Interval = marqueePauseTime;
|
|
if (pauseTimerAlbum != null) pauseTimerAlbum.Interval = marqueePauseTime;
|
|
marqueeTimerTitle.Interval = marqueeScrollSpeed;
|
|
marqueeTimerArtist.Interval = marqueeScrollSpeed;
|
|
marqueeTimerAlbum.Interval = marqueeScrollSpeed;
|
|
UpdateStatusBar();
|
|
// You can add more logic to update other fields if needed
|
|
}
|
|
|
|
private void Uicolor_Click(object sender, EventArgs e)
|
|
{
|
|
isDarkMode = !isDarkMode;
|
|
UpdateTheme();
|
|
}
|
|
|
|
private Bitmap InvertIcon(Bitmap original)
|
|
{
|
|
Bitmap inverted = new Bitmap(original.Width, original.Height);
|
|
for (int y = 0; y < original.Height; y++)
|
|
{
|
|
for (int x = 0; x < original.Width; x++)
|
|
{
|
|
Color pixel = original.GetPixel(x, y);
|
|
Color inv = Color.FromArgb(pixel.A, 255 - pixel.R, 255 - pixel.G, 255 - pixel.B);
|
|
inverted.SetPixel(x, y, inv);
|
|
}
|
|
}
|
|
return inverted;
|
|
}
|
|
|
|
private void UpdateTheme()
|
|
{
|
|
// Set icon
|
|
Bitmap icon = isDarkMode ? Properties.Resources.MdiWeatherSunny : Properties.Resources.MdiWeatherNight;
|
|
// Invert sun icon for visibility in dark mode
|
|
if (isDarkMode && icon != null)
|
|
{
|
|
icon = InvertIcon(icon);
|
|
}
|
|
// Resize icon to fit button and center it
|
|
if (icon != null)
|
|
{
|
|
int btnW = uicolor.Width;
|
|
int btnH = uicolor.Height;
|
|
var resizedIcon = new Bitmap(icon, btnW - 8, btnH - 8); // Padding for centering
|
|
uicolor.Image = resizedIcon;
|
|
uicolor.ImageAlign = ContentAlignment.MiddleCenter;
|
|
}
|
|
// Set colors
|
|
Color backColor = isDarkMode ? Color.FromArgb(32, 32, 32) : SystemColors.Control;
|
|
Color foreColor = isDarkMode ? Color.White : SystemColors.ControlText;
|
|
this.BackColor = backColor;
|
|
this.ForeColor = foreColor;
|
|
foreach (Control c in this.Controls)
|
|
{
|
|
ApplyThemeRecursive(c, backColor, foreColor);
|
|
}
|
|
}
|
|
|
|
private void ApplyThemeRecursive(Control control, Color backColor, Color foreColor)
|
|
{
|
|
control.BackColor = backColor;
|
|
control.ForeColor = foreColor;
|
|
foreach (Control child in control.Controls)
|
|
{
|
|
ApplyThemeRecursive(child, backColor, foreColor);
|
|
}
|
|
}
|
|
|
|
private void label7_Click(object sender, EventArgs e)
|
|
{
|
|
|
|
}
|
|
|
|
public string GetCurrentProfileName()
|
|
{
|
|
return currentProfile;
|
|
}
|
|
|
|
private void PopulateProfileDropBox()
|
|
{
|
|
profile_drop_box.Items.Clear();
|
|
var profileNames = ProfileStorage.GetProfileNames();
|
|
profile_drop_box.Items.AddRange(profileNames.ToArray());
|
|
// Set selected item to current profile if available
|
|
string currentProfile = GetCurrentProfileName();
|
|
if (!string.IsNullOrEmpty(currentProfile) && profileNames.Contains(currentProfile))
|
|
profile_drop_box.SelectedItem = currentProfile;
|
|
else if (profile_drop_box.Items.Count > 0)
|
|
profile_drop_box.SelectedIndex = 0;
|
|
}
|
|
|
|
private void load_profile_button_Click(object sender, EventArgs e)
|
|
{
|
|
if (profile_drop_box.SelectedItem != null)
|
|
{
|
|
var profile = ProfileStorage.LoadProfile(profile_drop_box.SelectedItem.ToString());
|
|
if (profile != null)
|
|
{
|
|
LoadProfile(profile);
|
|
UpdateStatusBar();
|
|
MessageBox.Show($"Profile '{profile.Name}' loaded.", "Profile Loaded", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|