diff --git a/Projector.sln b/Projector.sln new file mode 100644 index 0000000..0a9b12a --- /dev/null +++ b/Projector.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36202.13 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Projector", "Projector\Projector.csproj", "{B61419F5-CBDA-4DEF-B7A9-30DD5BCC467F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B61419F5-CBDA-4DEF-B7A9-30DD5BCC467F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B61419F5-CBDA-4DEF-B7A9-30DD5BCC467F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B61419F5-CBDA-4DEF-B7A9-30DD5BCC467F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B61419F5-CBDA-4DEF-B7A9-30DD5BCC467F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {61C9AAF3-56EB-47A0-AB37-C40A3F4851E9} + EndGlobalSection +EndGlobal diff --git a/Projector/App.config b/Projector/App.config new file mode 100644 index 0000000..ac55d9e --- /dev/null +++ b/Projector/App.config @@ -0,0 +1,15 @@ + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/Projector/FourChannelToStereoSampleProvider.cs b/Projector/FourChannelToStereoSampleProvider.cs new file mode 100644 index 0000000..6b87360 --- /dev/null +++ b/Projector/FourChannelToStereoSampleProvider.cs @@ -0,0 +1,84 @@ +using NAudio.Wave; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Projector +{ + // Custom ISampleProvider to mix 4 channels to stereo + public class FourChannelToStereoSampleProvider : ISampleProvider + { + private readonly ISampleProvider _source; + private float offTransition = 1f; + private float _voiceVolume = 0f; + private bool goingOff = false; + + public WaveFormat WaveFormat { get; } + + public void SetOff() + { + goingOff = true; + } + + public bool IsOff => goingOff && offTransition == 0f; + + public float VoiceVolume + { + get + { + return _voiceVolume; + } + set + { + _voiceVolume = Math.Clamp(value, 0f, 1f); + } + } + + public FourChannelToStereoSampleProvider(ISampleProvider source) + { + if (source.WaveFormat.Channels != 4) + throw new ArgumentException("Source must have 4 channels"); + if (source.WaveFormat.Encoding != WaveFormatEncoding.IeeeFloat) + throw new ArgumentException("Source must be IEEE float format"); + + _source = source; + WaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(source.WaveFormat.SampleRate, 2); + } + + public int Read(float[] buffer, int offset, int count) + { + int samplesNeeded = (count / 2) * 4; + float[] sourceBuffer = new float[samplesNeeded]; + int samplesRead = _source.Read(sourceBuffer, 0, samplesNeeded); + + int stereoSamples = samplesRead / 4 * 2; + int outIndex = offset; + + if (goingOff && offTransition > 0f) + { + offTransition -= 0.2f; + if (offTransition <= 0f) + { + offTransition = 0f; + } + } + + float musicVolume = 1f - VoiceVolume; + float voiceVolume = VoiceVolume; + + for (int i = 0; i < samplesRead; i += 4) + { + // Mix channel 0 and 2 to left, 1 and 3 to right + float left = (sourceBuffer[i] * musicVolume) + (sourceBuffer[i + 2] * voiceVolume); + float right = (sourceBuffer[i + 1] * musicVolume) + (sourceBuffer[i + 3] * voiceVolume); + buffer[outIndex++] = left * offTransition; + buffer[outIndex++] = right * offTransition; + } + + return stereoSamples; + } + } + +} diff --git a/Projector/FrmMain.Designer.cs b/Projector/FrmMain.Designer.cs new file mode 100644 index 0000000..39ad0c5 --- /dev/null +++ b/Projector/FrmMain.Designer.cs @@ -0,0 +1,109 @@ +namespace Projector +{ + partial class FrmMain + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + DgvHymns = new DataGridView(); + CnHymn = new DataGridViewTextBoxColumn(); + CnPlay = new DataGridViewButtonColumn(); + TbFilter = new TextBox(); + ((System.ComponentModel.ISupportInitialize)DgvHymns).BeginInit(); + SuspendLayout(); + // + // DgvHymns + // + DgvHymns.AllowUserToAddRows = false; + DgvHymns.AllowUserToDeleteRows = false; + DgvHymns.AllowUserToResizeColumns = false; + DgvHymns.AllowUserToResizeRows = false; + DgvHymns.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; + DgvHymns.Columns.AddRange(new DataGridViewColumn[] { CnHymn, CnPlay }); + DgvHymns.Dock = DockStyle.Fill; + DgvHymns.Location = new Point(0, 38); + DgvHymns.Margin = new Padding(5); + DgvHymns.MultiSelect = false; + DgvHymns.Name = "DgvHymns"; + DgvHymns.ReadOnly = true; + DgvHymns.RowHeadersVisible = false; + DgvHymns.RowHeadersWidth = 51; + DgvHymns.SelectionMode = DataGridViewSelectionMode.FullRowSelect; + DgvHymns.Size = new Size(665, 547); + DgvHymns.TabIndex = 7; + DgvHymns.CellPainting += DgvHymns_CellPainting; + // + // CnHymn + // + CnHymn.HeaderText = "Hymn"; + CnHymn.MinimumWidth = 6; + CnHymn.Name = "CnHymn"; + CnHymn.ReadOnly = true; + CnHymn.Width = 600; + // + // CnPlay + // + CnPlay.HeaderText = "P"; + CnPlay.MinimumWidth = 6; + CnPlay.Name = "CnPlay"; + CnPlay.ReadOnly = true; + CnPlay.Width = 32; + // + // TbFilter + // + TbFilter.BorderStyle = BorderStyle.FixedSingle; + TbFilter.Dock = DockStyle.Top; + TbFilter.Location = new Point(0, 0); + TbFilter.Margin = new Padding(5); + TbFilter.Name = "TbFilter"; + TbFilter.Size = new Size(665, 38); + TbFilter.TabIndex = 6; + TbFilter.TextChanged += TbFilter_TextChanged; + // + // FrmMain + // + AutoScaleDimensions = new SizeF(13F, 31F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(665, 585); + Controls.Add(DgvHymns); + Controls.Add(TbFilter); + Font = new Font("Segoe UI", 13.8F, FontStyle.Regular, GraphicsUnit.Point, 0); + Margin = new Padding(5); + Name = "FrmMain"; + Text = "SDA Church Projector"; + Load += FrmMain_Load; + ((System.ComponentModel.ISupportInitialize)DgvHymns).EndInit(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + private TextBox TbFilter; + private DataGridView DgvHymns; + private DataGridViewTextBoxColumn CnHymn; + private DataGridViewButtonColumn CnPlay; + } +} diff --git a/Projector/FrmMain.cs b/Projector/FrmMain.cs new file mode 100644 index 0000000..e180578 --- /dev/null +++ b/Projector/FrmMain.cs @@ -0,0 +1,288 @@ +using NAudio.Wave; +using System; +using System.Windows.Forms; +using YoutubeExplode; +using YoutubeExplode.Videos; +using YoutubeExplode.Videos.Streams; + +namespace Projector +{ + public partial class FrmMain : Form + { + private Video? _video = null; + private StreamManifest? _streamManifest = null; + FourChannelToStereoSampleProvider? mixer = null; + string musicPath = "C:\\Hymnal\\Music"; + string editsPath = "C:\\Hymnal\\Edits"; + string LyricsPath = "C:\\Hymnal\\Lyrics"; + FrmPreview frmPreview = new FrmPreview(); + + public FrmMain() + { + InitializeComponent(); + + DgvHymns.CellContentClick += DgvHymns_CellContentClick; + } + + private void FrmMain_Load(object sender, EventArgs e) + { + FrmMain_Resize(null, null); + + if (!System.IO.Directory.Exists(musicPath)) + { + MessageBox.Show("Hymns directory does not exist: " + musicPath); + return; + } + + var hymns = System.IO.Directory.GetFiles(musicPath, "*.m4a"); + foreach (var hymn in hymns) + { + var fileName = System.IO.Path.GetFileNameWithoutExtension(hymn); + DgvHymns.Rows.Add(fileName, ""); + } + } + + private void DgvHymns_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) + { + if (DgvHymns.Columns[e.ColumnIndex].Name == "CnPlay" && e.RowIndex >= 0) + { + e.Paint(e.CellBounds, DataGridViewPaintParts.All); + var icon = Properties.Resources.Play; // Assuming you have a play icon in resources + var iconRect = new Rectangle(e.CellBounds.X + 2, e.CellBounds.Y + 2, e.CellBounds.Height - 4, e.CellBounds.Height - 4); + e.Graphics?.DrawImage(icon, iconRect); + e.Handled = true; // Prevent default painting + } + } + + // 3. Handle the Play button click: + private void DgvHymns_CellContentClick(object sender, DataGridViewCellEventArgs e) + { + // Check if PlayButton column and not header row + if (e.RowIndex >= 0 && DgvHymns.Columns[e.ColumnIndex].Name == "CnPlay") + { + var hymnName = DgvHymns.Rows[e.RowIndex].Cells["CnHymn"].Value?.ToString(); + if (!string.IsNullOrEmpty(hymnName)) + { + PlayHymn(hymnName); + } + } + } + + private void PlayHymn(string name) + { + string audioFilePath = Path.Combine(editsPath, name + ".m4a"); + if (!File.Exists(audioFilePath)) + { + audioFilePath = Path.Combine(musicPath, name + ".m4a"); + } + + string number = name.Substring(0, 3).Trim(); // Extract the first 3 characters + + try + { + // Open the audio file + var reader = new MediaFoundationReader(audioFilePath); + + // Check for 4 channels + if (reader.WaveFormat.Channels != 4) + { + MessageBox.Show("Audio file does not have 4 channels."); + reader.Dispose(); + return; + } + + // Create a custom sample provider to mix channels 0+2 to left, 1+3 to right + mixer = new FourChannelToStereoSampleProvider(reader.ToSampleProvider()); + + // Search the folder in LyricsPath that starts with the hymn number + string lyricsFilePath = Directory.GetDirectories(LyricsPath).ToList().ConvertAll(d => Path.GetFileName(d)) + .FirstOrDefault(dir => dir.StartsWith(number, StringComparison.OrdinalIgnoreCase)); + + // Get all the jpg files in the lyrics folder + if (lyricsFilePath != null) + { + string lyricsFolderPath = Path.Combine(LyricsPath, lyricsFilePath); + var jpgFiles = Directory.GetFiles(lyricsFolderPath, "*.jpg"); + + // Order the jpg files by name + Array.Sort(jpgFiles, StringComparer.OrdinalIgnoreCase); + + // Display the first jpg file in a PictureBox or similar control + if (jpgFiles.Length > 0) + { + // Assuming you have a PictureBox named pictureBoxLyrics + frmPreview.SetImages(jpgFiles, mixer); + } + } + + var waveOut = new WaveOutEvent(); + waveOut.Init(mixer); + waveOut.Play(); + frmPreview.ShowDialog(this); + TbFilter.SelectAll(); + TbFilter.Focus(); + + // Crate a task to handle the transition + Task.Run(() => + { + mixer.SetOff(); + while (!mixer.IsOff) + { + Thread.Sleep(100); // Check every 100ms + } + waveOut.Stop(); + }); + + // Optionally, store waveOut and reader as fields to stop/dispose later + } + catch (Exception ex) + { + MessageBox.Show("Error playing audio: " + ex.Message); + } + } + + private void TbFilter_TextChanged(object sender, EventArgs e) + { + // Filter the DataGridView based on the text in the filter TextBox + string filterText = TbFilter.Text.ToLower(); + foreach (DataGridViewRow row in DgvHymns.Rows) + { + if (row.Cells["CnHymn"].Value != null) + { + row.Visible = row.Cells["CnHymn"].Value.ToString().ToLower().Contains(filterText); + } + } + + var visibleRows = DgvHymns.Rows + .Cast() + .Where(row => row.Visible) + .ToList(); + if (visibleRows.Count > 0) + { + int firstVisibleRowIndex = visibleRows[0].Index; + DgvHymns.Rows[firstVisibleRowIndex].Selected = true; + DgvHymns.FirstDisplayedScrollingRowIndex = firstVisibleRowIndex; + } + } + + private void FrmMain_Resize(object sender, EventArgs e) + { + DgvHymns.Columns["CnHymn"].Width = Width - 80; + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + if (keyData == Keys.Down) + { + var visibleRows = DgvHymns.Rows + .Cast() + .Where(row => row.Visible) + .ToList(); + + if (visibleRows.Count > 0) + { + int selectedRowIndex = visibleRows.FindIndex(row => row.Selected); + selectedRowIndex++; + if (selectedRowIndex < visibleRows.Count) + { + visibleRows[selectedRowIndex].Selected = true; + + int firstDisplayed = DgvHymns.FirstDisplayedScrollingRowIndex; + int displayedCount = DgvHymns.DisplayedRowCount(false); // false = only fully visible rows + int lastDisplayed = firstDisplayed + displayedCount - 1; + if (selectedRowIndex >= lastDisplayed) + { + DgvHymns.FirstDisplayedScrollingRowIndex = visibleRows[selectedRowIndex - displayedCount + 1].Index; + } + } + } + + return true; + } + else if (keyData == Keys.Up) + { + var visibleRows = DgvHymns.Rows + .Cast() + .Where(row => row.Visible) + .ToList(); + + if (visibleRows.Count > 0) + { + int selectedRowIndex = visibleRows.FindIndex(row => row.Selected); + selectedRowIndex--; + if (selectedRowIndex >= 0) + { + visibleRows[selectedRowIndex].Selected = true; + + int firstDisplayed = DgvHymns.FirstDisplayedScrollingRowIndex; + int displayedCount = DgvHymns.DisplayedRowCount(false); // false = only fully visible rows + + if (selectedRowIndex < firstDisplayed) + { + DgvHymns.FirstDisplayedScrollingRowIndex = visibleRows[selectedRowIndex].Index; + } + } + } + + return true; + } + else if (keyData == Keys.Enter) + { + // Handle Enter key to simulate Play button click + var selectedRow = DgvHymns.SelectedRows.Cast().FirstOrDefault(); + if (selectedRow != null && selectedRow.Cells["CnPlay"].Value != null) + { + DgvHymns_CellContentClick(sender: null, e: new DataGridViewCellEventArgs(1, selectedRow.Index)); + return true; // Indicate that the key press has been handled + } + } + else if (keyData == Keys.Home) + { + var visibleRows = DgvHymns.Rows + .Cast() + .Where(row => row.Visible) + .ToList(); + + if (visibleRows.Count > 0) + { + visibleRows.First().Selected = true; + int selectedRowIndex = visibleRows.First().Index; + + int firstDisplayed = DgvHymns.FirstDisplayedScrollingRowIndex; + int displayedCount = DgvHymns.DisplayedRowCount(false); // false = only fully visible rows + + if (selectedRowIndex < firstDisplayed) + { + DgvHymns.FirstDisplayedScrollingRowIndex = visibleRows[selectedRowIndex].Index; + } + } + return true; // Indicate that the key press has been handled + } + else if (keyData == Keys.End) + { + var visibleRows = DgvHymns.Rows + .Cast() + .Where(row => row.Visible) + .ToList(); + + if (visibleRows.Count > 0) + { + visibleRows.Last().Selected = true; + int selectedRowIndex = visibleRows.Last().Index; + + int firstDisplayed = DgvHymns.FirstDisplayedScrollingRowIndex; + int displayedCount = DgvHymns.DisplayedRowCount(false); // false = only fully visible rows + int lastDisplayed = firstDisplayed + displayedCount - 1; + if (selectedRowIndex >= lastDisplayed) + { + DgvHymns.FirstDisplayedScrollingRowIndex = visibleRows[selectedRowIndex - displayedCount + 1].Index; + } + } + + return true; // Indicate that the key press has been handled + } + + return base.ProcessCmdKey(ref msg, keyData); // Allow default processing if not an arrow key + } + } +} diff --git a/Projector/FrmMain.resx b/Projector/FrmMain.resx new file mode 100644 index 0000000..87a0343 --- /dev/null +++ b/Projector/FrmMain.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + True + + + True + + \ No newline at end of file diff --git a/Projector/FrmPreview.Designer.cs b/Projector/FrmPreview.Designer.cs new file mode 100644 index 0000000..646071c --- /dev/null +++ b/Projector/FrmPreview.Designer.cs @@ -0,0 +1,133 @@ +namespace Projector +{ + partial class FrmPreview + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmPreview)); + PbShow = new PictureBox(); + toolStrip1 = new ToolStrip(); + TsbPrevious = new ToolStripButton(); + TsbNext = new ToolStripButton(); + TkVoice = new TrackBar(); + ((System.ComponentModel.ISupportInitialize)PbShow).BeginInit(); + toolStrip1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)TkVoice).BeginInit(); + SuspendLayout(); + // + // PbShow + // + PbShow.BackColor = Color.Black; + PbShow.BorderStyle = BorderStyle.FixedSingle; + PbShow.Dock = DockStyle.Fill; + PbShow.Location = new Point(0, 0); + PbShow.Name = "PbShow"; + PbShow.Size = new Size(550, 345); + PbShow.SizeMode = PictureBoxSizeMode.StretchImage; + PbShow.TabIndex = 0; + PbShow.TabStop = false; + // + // toolStrip1 + // + toolStrip1.AutoSize = false; + toolStrip1.BackColor = Color.Black; + toolStrip1.Dock = DockStyle.Bottom; + toolStrip1.GripStyle = ToolStripGripStyle.Hidden; + toolStrip1.ImageScalingSize = new Size(220, 120); + toolStrip1.Items.AddRange(new ToolStripItem[] { TsbPrevious, TsbNext }); + toolStrip1.Location = new Point(0, 345); + toolStrip1.Name = "toolStrip1"; + toolStrip1.Size = new Size(599, 134); + toolStrip1.TabIndex = 1; + toolStrip1.Text = "toolStrip1"; + // + // TsbPrevious + // + TsbPrevious.AutoSize = false; + TsbPrevious.DisplayStyle = ToolStripItemDisplayStyle.Image; + TsbPrevious.Image = (Image)resources.GetObject("TsbPrevious.Image"); + TsbPrevious.ImageTransparentColor = Color.Magenta; + TsbPrevious.Name = "TsbPrevious"; + TsbPrevious.Size = new Size(236, 128); + TsbPrevious.Text = "Previous"; + TsbPrevious.Click += TsbPrevious_Click; + // + // TsbNext + // + TsbNext.Alignment = ToolStripItemAlignment.Right; + TsbNext.AutoSize = false; + TsbNext.DisplayStyle = ToolStripItemDisplayStyle.Image; + TsbNext.Image = (Image)resources.GetObject("TsbNext.Image"); + TsbNext.ImageTransparentColor = Color.Magenta; + TsbNext.Name = "TsbNext"; + TsbNext.Size = new Size(236, 128); + TsbNext.Text = "Next"; + TsbNext.Click += TsbNext_Click; + // + // TkVoice + // + TkVoice.AutoSize = false; + TkVoice.BackColor = Color.Black; + TkVoice.Dock = DockStyle.Right; + TkVoice.Location = new Point(550, 0); + TkVoice.Name = "TkVoice"; + TkVoice.Orientation = Orientation.Vertical; + TkVoice.Size = new Size(49, 345); + TkVoice.TabIndex = 2; + TkVoice.TickStyle = TickStyle.Both; + TkVoice.Scroll += TkVoice_Scroll; + // + // FrmPreview + // + AutoScaleDimensions = new SizeF(8F, 20F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(599, 479); + Controls.Add(PbShow); + Controls.Add(TkVoice); + Controls.Add(toolStrip1); + FormBorderStyle = FormBorderStyle.SizableToolWindow; + Name = "FrmPreview"; + StartPosition = FormStartPosition.CenterParent; + Text = "Preview"; + FormClosed += FrmPreview_FormClosed; + Load += FrmPreview_Load; + ((System.ComponentModel.ISupportInitialize)PbShow).EndInit(); + toolStrip1.ResumeLayout(false); + toolStrip1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)TkVoice).EndInit(); + ResumeLayout(false); + } + + #endregion + + private PictureBox PbShow; + private ToolStrip toolStrip1; + private ToolStripButton TsbPrevious; + private ToolStripButton TsbNext; + private TrackBar TkVoice; + } +} \ No newline at end of file diff --git a/Projector/FrmPreview.cs b/Projector/FrmPreview.cs new file mode 100644 index 0000000..2170cdb --- /dev/null +++ b/Projector/FrmPreview.cs @@ -0,0 +1,156 @@ +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; + +namespace Projector +{ + public partial class FrmPreview : Form + { + private List imagesList = new List(); + private int index = 0; + private FourChannelToStereoSampleProvider? _mixer = null; + private FrmProjector frmProjector = new FrmProjector(); + private bool secondScreenEnabled = false; + + public FrmPreview() + { + InitializeComponent(); + + Screen[] screens = Screen.AllScreens; + if (screens.Length > 1) + { + secondScreenEnabled = true; + Screen targetScreen = screens[1]; + frmProjector.StartPosition = FormStartPosition.Manual; + frmProjector.Location = targetScreen.Bounds.Location; + } + } + + public void SetImages(string[] images, FourChannelToStereoSampleProvider mixer) + { + if (secondScreenEnabled && !frmProjector.Visible) + { + frmProjector.Show(); + } + + _mixer = mixer; + mixer.VoiceVolume = TkVoice.Value / 10f; + imagesList.Clear(); + imagesList.AddRange(images); + index = 0; + ShowImage(index); + } + + public void Clear() + { + imagesList.Clear(); + PbShow.ImageLocation = null; + } + + private void ShowImage(int index) + { + if (index >= 0 && index < imagesList.Count) + { + frmProjector.SetImage(imagesList[index]); + PbShow.ImageLocation = imagesList[index]; + this.Text = $"Preview - {System.IO.Path.GetFileName(imagesList[index])}"; + + if (index + 1 < imagesList.Count) + { + TsbNext.Image = Image.FromFile(imagesList[index + 1]); + } + else + { + TsbNext.Image = null; // No next image + } + + if (index > 0) + { + TsbPrevious.Image = Image.FromFile(imagesList[index - 1]); + } + else + { + TsbPrevious.Image = null; // No previous image + } + } + else if (index == imagesList.Count) + { + frmProjector.SetImage(null); + PbShow.ImageLocation = null; + this.Text = $"Preview - End"; + TsbNext.Image = null; + TsbPrevious.Image = Image.FromFile(imagesList[index - 1]); + } + else + { + frmProjector.SetImage(null); + PbShow.ImageLocation = null; + TsbNext.Image = null; + TsbPrevious.Image = null; + } + } + + private void TsbPrevious_Click(object sender, EventArgs e) + { + if (index > 0) + { + index--; + ShowImage(index); + } + } + + private void TsbNext_Click(object sender, EventArgs e) + { + if (index < imagesList.Count) + { + index++; + ShowImage(index); + } + } + + private void FrmPreview_Load(object sender, EventArgs e) + { + + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + if (keyData == Keys.Left) + { + // Handle Left arrow key press + TsbPrevious_Click(sender: null, e: null); // Simulate click on Previous button + return true; // Indicate that the key press has been handled and prevent default behavior + } + else if (keyData == Keys.Right) + { + // Handle Right arrow key press + TsbNext_Click(sender: null, e: null); // Simulate click on Next button + return true; + } + else if (keyData == Keys.Escape) + { + // Close the form when Escape is pressed + this.Close(); + } + + return base.ProcessCmdKey(ref msg, keyData); // Allow default processing if not an arrow key + } + + private void TkVoice_Scroll(object sender, EventArgs e) + { + _mixer.VoiceVolume = TkVoice.Value / 10f; + } + + private void FrmPreview_FormClosed(object sender, FormClosedEventArgs e) + { + frmProjector.SetImage(null); + //frmProjector.Close(); + } + } +} diff --git a/Projector/FrmPreview.resx b/Projector/FrmPreview.resx new file mode 100644 index 0000000..b65107f --- /dev/null +++ b/Projector/FrmPreview.resx @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEESURBVEhL3ZKvDoJQFId5Dk0238GGr2DXbqBa3Cw6uy+g + RZozaTM4gxvZQHA6kc05/2CAetzP7bIrF0Hk3iLbx91O+L5xLloQBKQSDS/TNMkwDKnAGQYw0HVdKnAK + Ae/+kIK0wPV0JHvWI3veo4M1kR+wBjot24UQFpEW4OUAX6I2MJMc2K2GoRzrwp18HXDOHjUXp9cZFaeR + GoC0OnWpODq8zqyRxMDGvYdyRtbIx8B6f6Py2HmT/xKJDVRqjY/ytAhm/FwI1FtdKg23gjCOaITdFz8X + An3rIoiSYDL+Z2BzrDl3gMmiPwPAmrHu3IEksG6sXVkAwPlHAZVovu93VKKpfp4ISreGcqlKAwAAAABJ + RU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEESURBVEhL3ZKvDoJQFId5Dk0238GGr2DXbqBa3Cw6uy+g + RZozaTM4gxvZQHA6kc05/2CAetzP7bIrF0Hk3iLbx91O+L5xLloQBKQSDS/TNMkwDKnAGQYw0HVdKnAK + Ae/+kIK0wPV0JHvWI3veo4M1kR+wBjot24UQFpEW4OUAX6I2MJMc2K2GoRzrwp18HXDOHjUXp9cZFaeR + GoC0OnWpODq8zqyRxMDGvYdyRtbIx8B6f6Py2HmT/xKJDVRqjY/ytAhm/FwI1FtdKg23gjCOaITdFz8X + An3rIoiSYDL+Z2BzrDl3gMmiPwPAmrHu3IEksG6sXVkAwPlHAZVovu93VKKpfp4ISreGcqlKAwAAAABJ + RU5ErkJggg== + + + \ No newline at end of file diff --git a/Projector/FrmProjector.Designer.cs b/Projector/FrmProjector.Designer.cs new file mode 100644 index 0000000..6655fab --- /dev/null +++ b/Projector/FrmProjector.Designer.cs @@ -0,0 +1,64 @@ +namespace Projector +{ + partial class FrmProjector + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + PbShow = new PictureBox(); + ((System.ComponentModel.ISupportInitialize)PbShow).BeginInit(); + SuspendLayout(); + // + // PbShow + // + PbShow.BackColor = Color.Black; + PbShow.Dock = DockStyle.Fill; + PbShow.Location = new Point(0, 0); + PbShow.Name = "PbShow"; + PbShow.Size = new Size(800, 450); + PbShow.SizeMode = PictureBoxSizeMode.StretchImage; + PbShow.TabIndex = 0; + PbShow.TabStop = false; + // + // FrmProjector + // + AutoScaleDimensions = new SizeF(8F, 20F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(800, 450); + Controls.Add(PbShow); + FormBorderStyle = FormBorderStyle.None; + Name = "FrmProjector"; + Text = "FrmProjector"; + WindowState = FormWindowState.Maximized; + ((System.ComponentModel.ISupportInitialize)PbShow).EndInit(); + ResumeLayout(false); + } + + #endregion + + private PictureBox PbShow; + } +} \ No newline at end of file diff --git a/Projector/FrmProjector.cs b/Projector/FrmProjector.cs new file mode 100644 index 0000000..839b798 --- /dev/null +++ b/Projector/FrmProjector.cs @@ -0,0 +1,25 @@ +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; + +namespace Projector +{ + public partial class FrmProjector : Form + { + public FrmProjector() + { + InitializeComponent(); + } + + public void SetImage(string? imagePath) + { + PbShow.ImageLocation = imagePath; + } + } +} diff --git a/Projector/FrmProjector.resx b/Projector/FrmProjector.resx new file mode 100644 index 0000000..461e118 --- /dev/null +++ b/Projector/FrmProjector.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Projector/Program.cs b/Projector/Program.cs new file mode 100644 index 0000000..b3222ba --- /dev/null +++ b/Projector/Program.cs @@ -0,0 +1,17 @@ +namespace Projector +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.Run(new FrmMain()); + } + } +} \ No newline at end of file diff --git a/Projector/Projector.csproj b/Projector/Projector.csproj new file mode 100644 index 0000000..55c39bf --- /dev/null +++ b/Projector/Projector.csproj @@ -0,0 +1,44 @@ + + + + WinExe + net9.0-windows + enable + true + enable + + + + + + + + + + + True + True + Hymns.settings + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + SettingsSingleFileGenerator + Hymns.Designer.cs + + + + \ No newline at end of file diff --git a/Projector/Properties/Hymns.Designer.cs b/Projector/Properties/Hymns.Designer.cs new file mode 100644 index 0000000..f0c09bf --- /dev/null +++ b/Projector/Properties/Hymns.Designer.cs @@ -0,0 +1,35 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Projector.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")] + internal sealed partial class Hymns : global::System.Configuration.ApplicationSettingsBase { + + private static Hymns defaultInstance = ((Hymns)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Hymns()))); + + public static Hymns Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string Lyrics { + get { + return ((string)(this["Lyrics"])); + } + } + } +} diff --git a/Projector/Properties/Hymns.settings b/Projector/Properties/Hymns.settings new file mode 100644 index 0000000..d701b9f --- /dev/null +++ b/Projector/Properties/Hymns.settings @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Projector/Properties/Resources.Designer.cs b/Projector/Properties/Resources.Designer.cs new file mode 100644 index 0000000..efddde3 --- /dev/null +++ b/Projector/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Projector.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Projector.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Play { + get { + object obj = ResourceManager.GetObject("Play", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/Projector/Properties/Resources.resx b/Projector/Properties/Resources.resx new file mode 100644 index 0000000..4bbe180 --- /dev/null +++ b/Projector/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\Play.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/Projector/Resources/Play.png b/Projector/Resources/Play.png new file mode 100644 index 0000000..27579e0 Binary files /dev/null and b/Projector/Resources/Play.png differ