feat: Add Print Service Tray application with configuration and queue management
- Implemented PrintQueueService with job counting and snapshot retrieval. - Created Form1 as the main entry point for the tray application. - Developed ConfigurationForm for managing printer configurations and document types. - Added models for printer configuration, queue status requests, and responses. - Established ConfigurationService for loading and saving printer configurations. - Introduced ServiceClient for IPC communication with the print service. - Built TrayApplicationContext for managing the system tray icon and status updates. - Added TODO.md for tracking remaining work and future enhancements.
This commit is contained in:
@@ -0,0 +1,515 @@
|
||||
using PrintServiceTray.Models;
|
||||
using PrintServiceTray.Services;
|
||||
|
||||
namespace PrintServiceTray.Forms;
|
||||
|
||||
public partial class ConfigurationForm : Form
|
||||
{
|
||||
private readonly ConfigurationService _configService;
|
||||
private PrinterConfiguration _config;
|
||||
private string? _selectedDocType;
|
||||
private List<string> _installedPrinters;
|
||||
|
||||
// UI Controls
|
||||
private ListBox _docTypeListBox = null!;
|
||||
private Button _newDocTypeButton = null!;
|
||||
private Button _deleteDocTypeButton = null!;
|
||||
private Panel _pageConfigPanel = null!;
|
||||
private RadioButton _saveGlobalRadio = null!;
|
||||
private RadioButton _saveLocalRadio = null!;
|
||||
private Button _saveButton = null!;
|
||||
private Button _cancelButton = null!;
|
||||
private Button _addPageButton = null!;
|
||||
private Button _removePageButton = null!;
|
||||
private Label _configLabel = null!;
|
||||
|
||||
private List<PageConfigControl> _pageControls = new();
|
||||
|
||||
public ConfigurationForm()
|
||||
{
|
||||
_configService = new ConfigurationService();
|
||||
_config = _configService.Load();
|
||||
_installedPrinters = _configService.GetInstalledPrinters();
|
||||
|
||||
InitializeComponent();
|
||||
LoadDocumentTypes();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
Text = "LAAPC Printer Configuration";
|
||||
Size = new Size(900, 600);
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
MaximizeBox = false;
|
||||
|
||||
// Left panel - Document Types
|
||||
var leftPanel = new Panel
|
||||
{
|
||||
Location = new Point(10, 10),
|
||||
Size = new Size(250, 540),
|
||||
BorderStyle = BorderStyle.FixedSingle
|
||||
};
|
||||
|
||||
var docTypeLabel = new Label
|
||||
{
|
||||
Text = "Document Types:",
|
||||
Location = new Point(10, 10),
|
||||
Size = new Size(230, 20),
|
||||
Font = new Font(Font, FontStyle.Bold)
|
||||
};
|
||||
|
||||
_docTypeListBox = new ListBox
|
||||
{
|
||||
Location = new Point(10, 35),
|
||||
Size = new Size(230, 430)
|
||||
};
|
||||
_docTypeListBox.SelectedIndexChanged += DocTypeListBox_SelectedIndexChanged;
|
||||
|
||||
_newDocTypeButton = new Button
|
||||
{
|
||||
Text = "New",
|
||||
Location = new Point(10, 475),
|
||||
Size = new Size(110, 30)
|
||||
};
|
||||
_newDocTypeButton.Click += NewDocType_Click;
|
||||
|
||||
_deleteDocTypeButton = new Button
|
||||
{
|
||||
Text = "Delete",
|
||||
Location = new Point(130, 475),
|
||||
Size = new Size(110, 30)
|
||||
};
|
||||
_deleteDocTypeButton.Click += DeleteDocType_Click;
|
||||
|
||||
leftPanel.Controls.AddRange(new Control[] {
|
||||
docTypeLabel, _docTypeListBox, _newDocTypeButton, _deleteDocTypeButton
|
||||
});
|
||||
|
||||
// Right panel - Page Configuration
|
||||
var rightPanel = new Panel
|
||||
{
|
||||
Location = new Point(270, 10),
|
||||
Size = new Size(610, 540),
|
||||
BorderStyle = BorderStyle.FixedSingle
|
||||
};
|
||||
|
||||
_configLabel = new Label
|
||||
{
|
||||
Text = "Configuration for: (Select a document type)",
|
||||
Location = new Point(10, 10),
|
||||
Size = new Size(590, 20),
|
||||
Font = new Font(Font, FontStyle.Bold)
|
||||
};
|
||||
|
||||
var pageConfigLabel = new Label
|
||||
{
|
||||
Text = "Page Configuration:",
|
||||
Location = new Point(10, 40),
|
||||
Size = new Size(590, 20)
|
||||
};
|
||||
|
||||
_pageConfigPanel = new Panel
|
||||
{
|
||||
Location = new Point(10, 65),
|
||||
Size = new Size(590, 350),
|
||||
AutoScroll = true,
|
||||
BorderStyle = BorderStyle.FixedSingle
|
||||
};
|
||||
|
||||
_addPageButton = new Button
|
||||
{
|
||||
Text = "+ Add Page",
|
||||
Location = new Point(10, 425),
|
||||
Size = new Size(120, 30)
|
||||
};
|
||||
_addPageButton.Click += AddPage_Click;
|
||||
|
||||
_removePageButton = new Button
|
||||
{
|
||||
Text = "- Remove Last",
|
||||
Location = new Point(140, 425),
|
||||
Size = new Size(120, 30)
|
||||
};
|
||||
_removePageButton.Click += RemovePage_Click;
|
||||
|
||||
// Save options
|
||||
var saveToLabel = new Label
|
||||
{
|
||||
Text = "Save to:",
|
||||
Location = new Point(10, 465),
|
||||
Size = new Size(60, 20)
|
||||
};
|
||||
|
||||
_saveGlobalRadio = new RadioButton
|
||||
{
|
||||
Text = "Global (All Users)",
|
||||
Location = new Point(80, 465),
|
||||
Size = new Size(150, 20)
|
||||
};
|
||||
|
||||
_saveLocalRadio = new RadioButton
|
||||
{
|
||||
Text = "Local (My Computer)",
|
||||
Location = new Point(240, 465),
|
||||
Size = new Size(170, 20),
|
||||
Checked = true
|
||||
};
|
||||
|
||||
_saveButton = new Button
|
||||
{
|
||||
Text = "Save",
|
||||
Location = new Point(430, 495),
|
||||
Size = new Size(80, 35)
|
||||
};
|
||||
_saveButton.Click += Save_Click;
|
||||
|
||||
_cancelButton = new Button
|
||||
{
|
||||
Text = "Cancel",
|
||||
Location = new Point(520, 495),
|
||||
Size = new Size(80, 35)
|
||||
};
|
||||
_cancelButton.Click += (s, e) => Close();
|
||||
|
||||
rightPanel.Controls.AddRange(new Control[] {
|
||||
_configLabel, pageConfigLabel, _pageConfigPanel,
|
||||
_addPageButton, _removePageButton,
|
||||
saveToLabel, _saveGlobalRadio, _saveLocalRadio,
|
||||
_saveButton, _cancelButton
|
||||
});
|
||||
|
||||
Controls.AddRange(new Control[] { leftPanel, rightPanel });
|
||||
}
|
||||
|
||||
private void LoadDocumentTypes()
|
||||
{
|
||||
_docTypeListBox.Items.Clear();
|
||||
foreach (var docType in _config.DocumentTypes.Keys.OrderBy(k => k))
|
||||
{
|
||||
_docTypeListBox.Items.Add(docType);
|
||||
}
|
||||
}
|
||||
|
||||
private void DocTypeListBox_SelectedIndexChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (_docTypeListBox.SelectedItem is string docType)
|
||||
{
|
||||
_selectedDocType = docType;
|
||||
LoadPageConfiguration(docType);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPageConfiguration(string docType)
|
||||
{
|
||||
_configLabel.Text = $"Configuration for: {docType}";
|
||||
_pageConfigPanel.Controls.Clear();
|
||||
_pageControls.Clear();
|
||||
|
||||
if (!_config.DocumentTypes.TryGetValue(docType, out var config))
|
||||
{
|
||||
config = new DocumentTypeConfig { Name = docType };
|
||||
_config.DocumentTypes[docType] = config;
|
||||
}
|
||||
|
||||
int yPos = 10;
|
||||
foreach (var page in config.Pages.OrderBy(p => p.PageNumber))
|
||||
{
|
||||
var pageControl = new PageConfigControl(page.PageNumber, _installedPrinters, _configService);
|
||||
pageControl.Location = new Point(10, yPos);
|
||||
pageControl.LoadPage(page);
|
||||
_pageConfigPanel.Controls.Add(pageControl);
|
||||
_pageControls.Add(pageControl);
|
||||
yPos += pageControl.Height + 10;
|
||||
}
|
||||
|
||||
if (config.Pages.Count == 0)
|
||||
{
|
||||
// Add first page by default
|
||||
AddPage_Click(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPage_Click(object? sender, EventArgs e)
|
||||
{
|
||||
if (_selectedDocType == null) return;
|
||||
|
||||
var config = _config.DocumentTypes[_selectedDocType];
|
||||
int nextPageNum = config.Pages.Count > 0 ? config.Pages.Max(p => p.PageNumber) + 1 : 1;
|
||||
|
||||
var newPage = new PageConfig
|
||||
{
|
||||
PageNumber = nextPageNum,
|
||||
PrinterName = _installedPrinters.FirstOrDefault() ?? "",
|
||||
TrayNumber = 1
|
||||
};
|
||||
config.Pages.Add(newPage);
|
||||
|
||||
LoadPageConfiguration(_selectedDocType);
|
||||
}
|
||||
|
||||
private void RemovePage_Click(object? sender, EventArgs e)
|
||||
{
|
||||
if (_selectedDocType == null) return;
|
||||
|
||||
var config = _config.DocumentTypes[_selectedDocType];
|
||||
if (config.Pages.Count > 0)
|
||||
{
|
||||
var lastPage = config.Pages.OrderByDescending(p => p.PageNumber).First();
|
||||
config.Pages.Remove(lastPage);
|
||||
LoadPageConfiguration(_selectedDocType);
|
||||
}
|
||||
}
|
||||
|
||||
private void NewDocType_Click(object? sender, EventArgs e)
|
||||
{
|
||||
var dialog = new TextInputDialog("New Document Type", "Enter document type name:");
|
||||
if (dialog.ShowDialog() == DialogResult.OK && !string.IsNullOrWhiteSpace(dialog.InputText))
|
||||
{
|
||||
var docType = dialog.InputText.Trim();
|
||||
if (!_config.DocumentTypes.ContainsKey(docType))
|
||||
{
|
||||
_config.DocumentTypes[docType] = new DocumentTypeConfig { Name = docType };
|
||||
LoadDocumentTypes();
|
||||
_docTypeListBox.SelectedItem = docType;
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show($"Document type '{docType}' already exists.", "Duplicate", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteDocType_Click(object? sender, EventArgs e)
|
||||
{
|
||||
if (_selectedDocType == null) return;
|
||||
|
||||
var result = MessageBox.Show(
|
||||
$"Are you sure you want to delete '{_selectedDocType}'?",
|
||||
"Confirm Delete",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question);
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
_config.DocumentTypes.Remove(_selectedDocType);
|
||||
_selectedDocType = null;
|
||||
LoadDocumentTypes();
|
||||
_pageConfigPanel.Controls.Clear();
|
||||
_configLabel.Text = "Configuration for: (Select a document type)";
|
||||
}
|
||||
}
|
||||
|
||||
private void Save_Click(object? sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Save current page configurations
|
||||
if (_selectedDocType != null && _config.DocumentTypes.TryGetValue(_selectedDocType, out var config))
|
||||
{
|
||||
config.Pages.Clear();
|
||||
foreach (var pageControl in _pageControls)
|
||||
{
|
||||
config.Pages.Add(pageControl.GetPageConfig());
|
||||
}
|
||||
}
|
||||
|
||||
bool saveGlobal = _saveGlobalRadio.Checked;
|
||||
_configService.Save(_config, saveGlobal);
|
||||
|
||||
MessageBox.Show(
|
||||
$"Configuration saved to {(saveGlobal ? "global" : "local")} file successfully!",
|
||||
"Success",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Information);
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(
|
||||
$"Failed to save configuration: {ex.Message}",
|
||||
"Error",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom control for each page configuration
|
||||
public class PageConfigControl : UserControl
|
||||
{
|
||||
private readonly int _pageNumber;
|
||||
private readonly List<string> _printers;
|
||||
private readonly ConfigurationService _configService;
|
||||
|
||||
private ComboBox _printerCombo = null!;
|
||||
private ComboBox _trayCombo = null!;
|
||||
|
||||
public PageConfigControl(int pageNumber, List<string> printers, ConfigurationService configService)
|
||||
{
|
||||
_pageNumber = pageNumber;
|
||||
_printers = printers;
|
||||
_configService = configService;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
Size = new Size(560, 60);
|
||||
BorderStyle = BorderStyle.FixedSingle;
|
||||
|
||||
var pageLabel = new Label
|
||||
{
|
||||
Text = $"Page {_pageNumber}:",
|
||||
Location = new Point(10, 10),
|
||||
Size = new Size(70, 20),
|
||||
Font = new Font(Font, FontStyle.Bold)
|
||||
};
|
||||
|
||||
var printerLabel = new Label
|
||||
{
|
||||
Text = "Printer:",
|
||||
Location = new Point(10, 32),
|
||||
Size = new Size(60, 20)
|
||||
};
|
||||
|
||||
_printerCombo = new ComboBox
|
||||
{
|
||||
Location = new Point(75, 30),
|
||||
Size = new Size(250, 25),
|
||||
DropDownStyle = ComboBoxStyle.DropDownList
|
||||
};
|
||||
_printerCombo.Items.AddRange(_printers.ToArray());
|
||||
_printerCombo.SelectedIndexChanged += PrinterCombo_SelectedIndexChanged;
|
||||
|
||||
var trayLabel = new Label
|
||||
{
|
||||
Text = "Tray:",
|
||||
Location = new Point(340, 32),
|
||||
Size = new Size(40, 20)
|
||||
};
|
||||
|
||||
_trayCombo = new ComboBox
|
||||
{
|
||||
Location = new Point(385, 30),
|
||||
Size = new Size(165, 25),
|
||||
DropDownStyle = ComboBoxStyle.DropDownList
|
||||
};
|
||||
|
||||
Controls.AddRange(new Control[] {
|
||||
pageLabel, printerLabel, _printerCombo, trayLabel, _trayCombo
|
||||
});
|
||||
}
|
||||
|
||||
public void LoadPage(PageConfig page)
|
||||
{
|
||||
_printerCombo.SelectedItem = page.PrinterName;
|
||||
LoadTrays(page.PrinterName);
|
||||
|
||||
// Try to find and select the tray
|
||||
for (int i = 0; i < _trayCombo.Items.Count; i++)
|
||||
{
|
||||
if (_trayCombo.Items[i] is TrayItem item && item.TrayNumber == page.TrayNumber)
|
||||
{
|
||||
_trayCombo.SelectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PrinterCombo_SelectedIndexChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (_printerCombo.SelectedItem is string printer)
|
||||
{
|
||||
LoadTrays(printer);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadTrays(string printerName)
|
||||
{
|
||||
_trayCombo.Items.Clear();
|
||||
var trays = _configService.GetPrinterTrays(printerName);
|
||||
foreach (var (number, name) in trays)
|
||||
{
|
||||
_trayCombo.Items.Add(new TrayItem { TrayNumber = number, TrayName = name });
|
||||
}
|
||||
if (_trayCombo.Items.Count > 0)
|
||||
{
|
||||
_trayCombo.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public PageConfig GetPageConfig()
|
||||
{
|
||||
var selectedTray = _trayCombo.SelectedItem as TrayItem;
|
||||
return new PageConfig
|
||||
{
|
||||
PageNumber = _pageNumber,
|
||||
PrinterName = _printerCombo.SelectedItem?.ToString() ?? "",
|
||||
TrayNumber = selectedTray?.TrayNumber ?? 1,
|
||||
TrayLabel = selectedTray?.TrayName
|
||||
};
|
||||
}
|
||||
|
||||
private class TrayItem
|
||||
{
|
||||
public int TrayNumber { get; set; }
|
||||
public string TrayName { get; set; } = "";
|
||||
|
||||
public override string ToString() => $"Tray {TrayNumber} - {TrayName}";
|
||||
}
|
||||
}
|
||||
|
||||
// Simple text input dialog
|
||||
public class TextInputDialog : Form
|
||||
{
|
||||
private TextBox _textBox = null!;
|
||||
public string InputText => _textBox.Text;
|
||||
|
||||
public TextInputDialog(string title, string prompt)
|
||||
{
|
||||
Text = title;
|
||||
Size = new Size(400, 150);
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
|
||||
var promptLabel = new Label
|
||||
{
|
||||
Text = prompt,
|
||||
Location = new Point(20, 20),
|
||||
Size = new Size(360, 20)
|
||||
};
|
||||
|
||||
_textBox = new TextBox
|
||||
{
|
||||
Location = new Point(20, 50),
|
||||
Size = new Size(360, 25)
|
||||
};
|
||||
|
||||
var okButton = new Button
|
||||
{
|
||||
Text = "OK",
|
||||
DialogResult = DialogResult.OK,
|
||||
Location = new Point(220, 85),
|
||||
Size = new Size(75, 30)
|
||||
};
|
||||
|
||||
var cancelButton = new Button
|
||||
{
|
||||
Text = "Cancel",
|
||||
DialogResult = DialogResult.Cancel,
|
||||
Location = new Point(305, 85),
|
||||
Size = new Size(75, 30)
|
||||
};
|
||||
|
||||
AcceptButton = okButton;
|
||||
CancelButton = cancelButton;
|
||||
|
||||
Controls.AddRange(new Control[] { promptLabel, _textBox, okButton, cancelButton });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user