Added tray icon support
This commit is contained in:
@@ -10,6 +10,9 @@ Archive/
|
|||||||
Error/
|
Error/
|
||||||
Output/
|
Output/
|
||||||
|
|
||||||
|
# Rust Project (reference implementation)
|
||||||
|
RUST/
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
|
|||||||
Vendored
+114
-4
@@ -150,7 +150,8 @@
|
|||||||
"label": "build-all-x86",
|
"label": "build-all-x86",
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"build-service-release-x86",
|
"build-service-release-x86",
|
||||||
"build-cli-release-x86"
|
"build-cli-release-x86",
|
||||||
|
"build-tray-release-x86"
|
||||||
],
|
],
|
||||||
"dependsOrder": "parallel",
|
"dependsOrder": "parallel",
|
||||||
"group": {
|
"group": {
|
||||||
@@ -163,7 +164,8 @@
|
|||||||
"label": "build-all-x64",
|
"label": "build-all-x64",
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"build-service-release-x64",
|
"build-service-release-x64",
|
||||||
"build-cli-release-x64"
|
"build-cli-release-x64",
|
||||||
|
"build-tray-release-x64"
|
||||||
],
|
],
|
||||||
"dependsOrder": "parallel",
|
"dependsOrder": "parallel",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
@@ -270,7 +272,8 @@
|
|||||||
"label": "publish-all-x86",
|
"label": "publish-all-x86",
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"publish-service-x86-self-contained",
|
"publish-service-x86-self-contained",
|
||||||
"publish-cli-x86-self-contained"
|
"publish-cli-x86-self-contained",
|
||||||
|
"publish-tray-x86-self-contained"
|
||||||
],
|
],
|
||||||
"dependsOrder": "parallel",
|
"dependsOrder": "parallel",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
@@ -280,7 +283,8 @@
|
|||||||
"label": "publish-all-x64",
|
"label": "publish-all-x64",
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"publish-service-x64-self-contained",
|
"publish-service-x64-self-contained",
|
||||||
"publish-cli-x64-self-contained"
|
"publish-cli-x64-self-contained",
|
||||||
|
"publish-tray-x64-self-contained"
|
||||||
],
|
],
|
||||||
"dependsOrder": "parallel",
|
"dependsOrder": "parallel",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
@@ -354,6 +358,112 @@
|
|||||||
"group": "build"
|
"group": "build"
|
||||||
},
|
},
|
||||||
// ============================================
|
// ============================================
|
||||||
|
// TRAY APP TASKS
|
||||||
|
// ============================================
|
||||||
|
{
|
||||||
|
"label": "build-tray-debug",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/PrintServiceTray/PrintServiceTray.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary",
|
||||||
|
"-c",
|
||||||
|
"Debug"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile",
|
||||||
|
"group": "build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "build-tray-release-x86",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/PrintServiceTray/PrintServiceTray.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary",
|
||||||
|
"-c",
|
||||||
|
"Release",
|
||||||
|
"-r",
|
||||||
|
"win-x86"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile",
|
||||||
|
"group": "build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "build-tray-release-x64",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/PrintServiceTray/PrintServiceTray.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary",
|
||||||
|
"-c",
|
||||||
|
"Release",
|
||||||
|
"-r",
|
||||||
|
"win-x64"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile",
|
||||||
|
"group": "build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "run-tray",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"--project",
|
||||||
|
"${workspaceFolder}/PrintServiceTray/PrintServiceTray.csproj"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile",
|
||||||
|
"group": "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish-tray-x86-self-contained",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/PrintServiceTray/PrintServiceTray.csproj",
|
||||||
|
"-c",
|
||||||
|
"Release",
|
||||||
|
"-r",
|
||||||
|
"win-x86",
|
||||||
|
"--self-contained",
|
||||||
|
"true",
|
||||||
|
"/p:PublishSingleFile=true",
|
||||||
|
"/p:PublishTrimmed=false",
|
||||||
|
"-o",
|
||||||
|
"${workspaceFolder}/publish/x86"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile",
|
||||||
|
"group": "build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish-tray-x64-self-contained",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/PrintServiceTray/PrintServiceTray.csproj",
|
||||||
|
"-c",
|
||||||
|
"Release",
|
||||||
|
"-r",
|
||||||
|
"win-x64",
|
||||||
|
"--self-contained",
|
||||||
|
"true",
|
||||||
|
"/p:PublishSingleFile=true",
|
||||||
|
"/p:PublishTrimmed=false",
|
||||||
|
"-o",
|
||||||
|
"${workspaceFolder}/publish/x64"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile",
|
||||||
|
"group": "build"
|
||||||
|
},
|
||||||
|
// ============================================
|
||||||
// CLEAN TASKS
|
// CLEAN TASKS
|
||||||
// ============================================
|
// ============================================
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrintService", "PrintServic
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrintServiceCLI", "PrintServiceCLI\PrintServiceCLI.csproj", "{799E32EC-0A62-4CA1-92A6-F7E53A2A7F73}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrintServiceCLI", "PrintServiceCLI\PrintServiceCLI.csproj", "{799E32EC-0A62-4CA1-92A6-F7E53A2A7F73}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrintServiceTray", "PrintServiceTray\PrintServiceTray.csproj", "{42CF28FF-90E4-4912-B427-EA5F675F641E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -24,5 +26,9 @@ Global
|
|||||||
{799E32EC-0A62-4CA1-92A6-F7E53A2A7F73}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{799E32EC-0A62-4CA1-92A6-F7E53A2A7F73}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{799E32EC-0A62-4CA1-92A6-F7E53A2A7F73}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{799E32EC-0A62-4CA1-92A6-F7E53A2A7F73}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{799E32EC-0A62-4CA1-92A6-F7E53A2A7F73}.Release|Any CPU.Build.0 = Release|Any CPU
|
{799E32EC-0A62-4CA1-92A6-F7E53A2A7F73}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{42CF28FF-90E4-4912-B427-EA5F675F641E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{42CF28FF-90E4-4912-B427-EA5F675F641E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{42CF28FF-90E4-4912-B427-EA5F675F641E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{42CF28FF-90E4-4912-B427-EA5F675F641E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -122,6 +122,17 @@ public class IpcService : IDisposable
|
|||||||
_logger.LogDebug("Received IPC message: {Message}", message);
|
_logger.LogDebug("Received IPC message: {Message}", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to determine command type
|
||||||
|
var commandType = DetermineCommandType(message);
|
||||||
|
|
||||||
|
if (commandType == "GetQueueStatus")
|
||||||
|
{
|
||||||
|
var status = GetQueueStatus();
|
||||||
|
await SendQueueStatusResponse(pipeServer, status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle print command
|
||||||
var command = JsonSerializer.Deserialize<PrintCommand>(message);
|
var command = JsonSerializer.Deserialize<PrintCommand>(message);
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
@@ -163,6 +174,70 @@ public class IpcService : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine the type of command from JSON message
|
||||||
|
/// </summary>
|
||||||
|
private string? DetermineCommandType(string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var doc = JsonDocument.Parse(message);
|
||||||
|
if (doc.RootElement.TryGetProperty("Command", out var cmdProp))
|
||||||
|
{
|
||||||
|
return cmdProp.GetString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get current queue status
|
||||||
|
/// </summary>
|
||||||
|
private QueueStatusResponse GetQueueStatus()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jobs = _queueService.GetAllJobs();
|
||||||
|
var jobStatuses = jobs.Select(j => new JobStatus
|
||||||
|
{
|
||||||
|
Id = j.Id.ToString(),
|
||||||
|
FileName = j.OriginalFilename,
|
||||||
|
DocumentType = j.DocumentType,
|
||||||
|
Status = j.Status.ToString(),
|
||||||
|
PrinterName = null, // Will be set when processing starts
|
||||||
|
CurrentPage = null,
|
||||||
|
TotalPages = null
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return new QueueStatusResponse
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Jobs = jobStatuses
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to get queue status");
|
||||||
|
return new QueueStatusResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = $"Error: {ex.Message}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send queue status response
|
||||||
|
/// </summary>
|
||||||
|
private async Task SendQueueStatusResponse(NamedPipeServerStream pipeServer, QueueStatusResponse response)
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.Serialize(response);
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(json);
|
||||||
|
await pipeServer.WriteAsync(bytes, 0, bytes.Length);
|
||||||
|
await pipeServer.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Process a print command
|
/// Process a print command
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -239,3 +314,27 @@ public class PrintResponse
|
|||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
public string Message { get; set; } = string.Empty;
|
public string Message { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue status response for tray app
|
||||||
|
/// </summary>
|
||||||
|
public class QueueStatusResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public List<JobStatus> Jobs { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Status of a single job
|
||||||
|
/// </summary>
|
||||||
|
public class JobStatus
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
public string FileName { get; set; } = string.Empty;
|
||||||
|
public string DocumentType { get; set; } = string.Empty;
|
||||||
|
public string Status { get; set; } = string.Empty;
|
||||||
|
public string? PrinterName { get; set; }
|
||||||
|
public int? CurrentPage { get; set; }
|
||||||
|
public int? TotalPages { get; set; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,6 +59,14 @@ public class PrintQueueService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int Count => _queue.Count;
|
public int Count => _queue.Count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all jobs in the queue (snapshot)
|
||||||
|
/// </summary>
|
||||||
|
public PrintJob[] GetAllJobs()
|
||||||
|
{
|
||||||
|
return _queue.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save queue state to disk
|
/// Save queue state to disk
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Generated
+38
@@ -0,0 +1,38 @@
|
|||||||
|
namespace PrintServiceTray;
|
||||||
|
|
||||||
|
partial class Form1
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Required designer variable.
|
||||||
|
/// </summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resources being used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && (components != null))
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required method for Designer support - do not modify
|
||||||
|
/// the contents of this method with the code editor.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
this.components = new System.ComponentModel.Container();
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(800, 450);
|
||||||
|
this.Text = "Form1";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace PrintServiceTray;
|
||||||
|
|
||||||
|
public partial class Form1 : Form
|
||||||
|
{
|
||||||
|
public Form1()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace PrintServiceTray.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for all document types
|
||||||
|
/// </summary>
|
||||||
|
public class PrinterConfiguration
|
||||||
|
{
|
||||||
|
public Dictionary<string, DocumentTypeConfig> DocumentTypes { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for a single document type
|
||||||
|
/// </summary>
|
||||||
|
public class DocumentTypeConfig
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public List<PageConfig> Pages { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration for a single page
|
||||||
|
/// </summary>
|
||||||
|
public class PageConfig
|
||||||
|
{
|
||||||
|
public int PageNumber { get; set; }
|
||||||
|
public string PrinterName { get; set; } = string.Empty;
|
||||||
|
public int TrayNumber { get; set; }
|
||||||
|
public string? TrayLabel { get; set; } // Optional: "Pink Paper", "Green Paper", etc.
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace PrintServiceTray.Models;
|
||||||
|
|
||||||
|
public class QueueStatusRequest
|
||||||
|
{
|
||||||
|
public string Command { get; set; } = "GetQueueStatus";
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace PrintServiceTray.Models;
|
||||||
|
|
||||||
|
public class QueueStatusResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public List<JobStatus> Jobs { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JobStatus
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
public string FileName { get; set; } = string.Empty;
|
||||||
|
public string DocumentType { get; set; } = string.Empty;
|
||||||
|
public string Status { get; set; } = string.Empty;
|
||||||
|
public string? PrinterName { get; set; }
|
||||||
|
public int? CurrentPage { get; set; }
|
||||||
|
public int? TotalPages { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<RuntimeHostConfigurationOption Include="System.Drawing.EnableWindowsSupport" Value="true" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
namespace PrintServiceTray;
|
||||||
|
|
||||||
|
static class Program
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The main entry point for the application.
|
||||||
|
/// </summary>
|
||||||
|
[STAThread]
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Prevent multiple instances
|
||||||
|
bool createdNew;
|
||||||
|
using var mutex = new Mutex(true, "LAAPC_PrintServiceTray_Mutex", out createdNew);
|
||||||
|
|
||||||
|
if (!createdNew)
|
||||||
|
{
|
||||||
|
MessageBox.Show("LAAPC Print Service Tray is already running.", "Already Running",
|
||||||
|
MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationConfiguration.Initialize();
|
||||||
|
Application.Run(new TrayApplicationContext());
|
||||||
|
|
||||||
|
GC.KeepAlive(mutex);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Fatal error: {ex.Message}\n\n{ex.StackTrace}", "Error",
|
||||||
|
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using PrintServiceTray.Models;
|
||||||
|
|
||||||
|
namespace PrintServiceTray.Services;
|
||||||
|
|
||||||
|
public class ConfigurationService
|
||||||
|
{
|
||||||
|
private readonly string _globalConfigPath;
|
||||||
|
private readonly string _localConfigPath;
|
||||||
|
|
||||||
|
public ConfigurationService()
|
||||||
|
{
|
||||||
|
// Global config: next to CLI executable (C:\LAAPC or wherever installed)
|
||||||
|
var cliPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||||
|
"LAAPC"
|
||||||
|
);
|
||||||
|
_globalConfigPath = Path.Combine(cliPath, "printer-config.json");
|
||||||
|
|
||||||
|
// Local config: user's AppData
|
||||||
|
var appDataPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"LAAPC"
|
||||||
|
);
|
||||||
|
Directory.CreateDirectory(appDataPath);
|
||||||
|
_localConfigPath = Path.Combine(appDataPath, "printer-config.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load configuration (local overrides global)
|
||||||
|
/// </summary>
|
||||||
|
public PrinterConfiguration Load()
|
||||||
|
{
|
||||||
|
var config = new PrinterConfiguration();
|
||||||
|
|
||||||
|
// Load global first
|
||||||
|
if (File.Exists(_globalConfigPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(_globalConfigPath);
|
||||||
|
var globalConfig = JsonSerializer.Deserialize<PrinterConfiguration>(json);
|
||||||
|
if (globalConfig != null)
|
||||||
|
{
|
||||||
|
config = globalConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load local and merge/override
|
||||||
|
if (File.Exists(_localConfigPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(_localConfigPath);
|
||||||
|
var localConfig = JsonSerializer.Deserialize<PrinterConfiguration>(json);
|
||||||
|
if (localConfig != null)
|
||||||
|
{
|
||||||
|
// Merge: local overrides global for matching document types
|
||||||
|
foreach (var kvp in localConfig.DocumentTypes)
|
||||||
|
{
|
||||||
|
config.DocumentTypes[kvp.Key] = kvp.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save configuration to global or local
|
||||||
|
/// </summary>
|
||||||
|
public void Save(PrinterConfiguration config, bool saveGlobal)
|
||||||
|
{
|
||||||
|
var path = saveGlobal ? _globalConfigPath : _localConfigPath;
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
var directory = Path.GetDirectoryName(path);
|
||||||
|
if (!string.IsNullOrEmpty(directory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
};
|
||||||
|
var json = JsonSerializer.Serialize(config, options);
|
||||||
|
File.WriteAllText(path, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all installed printers
|
||||||
|
/// </summary>
|
||||||
|
public List<string> GetInstalledPrinters()
|
||||||
|
{
|
||||||
|
var printers = new List<string>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (string printerName in System.Drawing.Printing.PrinterSettings.InstalledPrinters)
|
||||||
|
{
|
||||||
|
printers.Add(printerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return printers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get available trays for a printer
|
||||||
|
/// </summary>
|
||||||
|
public List<(int Number, string Name)> GetPrinterTrays(string printerName)
|
||||||
|
{
|
||||||
|
var trays = new List<(int, string)>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var printerSettings = new System.Drawing.Printing.PrinterSettings
|
||||||
|
{
|
||||||
|
PrinterName = printerName
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (System.Drawing.Printing.PaperSource source in printerSettings.PaperSources)
|
||||||
|
{
|
||||||
|
trays.Add((source.RawKind, source.SourceName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return trays;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GlobalConfigPath => _globalConfigPath;
|
||||||
|
public string LocalConfigPath => _localConfigPath;
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using PrintServiceTray.Models;
|
||||||
|
|
||||||
|
namespace PrintServiceTray.Services;
|
||||||
|
|
||||||
|
public class ServiceClient
|
||||||
|
{
|
||||||
|
private readonly string _pipeName;
|
||||||
|
private readonly int _timeout;
|
||||||
|
|
||||||
|
public ServiceClient(string pipeName = "PrintServicePipe", int timeoutMs = 5000)
|
||||||
|
{
|
||||||
|
_pipeName = pipeName;
|
||||||
|
_timeout = timeoutMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<QueueStatusResponse> GetQueueStatusAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var pipe = new NamedPipeClientStream(".", _pipeName, PipeDirection.InOut);
|
||||||
|
await pipe.ConnectAsync(_timeout);
|
||||||
|
|
||||||
|
var request = new QueueStatusRequest();
|
||||||
|
var requestJson = JsonSerializer.Serialize(request);
|
||||||
|
var requestBytes = Encoding.UTF8.GetBytes(requestJson);
|
||||||
|
|
||||||
|
await pipe.WriteAsync(requestBytes);
|
||||||
|
await pipe.FlushAsync();
|
||||||
|
|
||||||
|
var buffer = new byte[4096];
|
||||||
|
var bytesRead = await pipe.ReadAsync(buffer);
|
||||||
|
var responseJson = Encoding.UTF8.GetString(buffer, 0, bytesRead);
|
||||||
|
|
||||||
|
var response = JsonSerializer.Deserialize<QueueStatusResponse>(responseJson);
|
||||||
|
return response ?? new QueueStatusResponse { Success = false, Message = "Invalid response" };
|
||||||
|
}
|
||||||
|
catch (TimeoutException)
|
||||||
|
{
|
||||||
|
return new QueueStatusResponse { Success = false, Message = "Service not responding" };
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new QueueStatusResponse { Success = false, Message = $"Error: {ex.Message}" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsServiceRunningAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await GetQueueStatusAsync();
|
||||||
|
return response.Success;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using PrintServiceTray.Services;
|
||||||
|
using PrintServiceTray.Models;
|
||||||
|
|
||||||
|
namespace PrintServiceTray;
|
||||||
|
|
||||||
|
public class TrayApplicationContext : ApplicationContext
|
||||||
|
{
|
||||||
|
private readonly NotifyIcon _trayIcon;
|
||||||
|
private readonly ServiceClient _serviceClient;
|
||||||
|
private readonly System.Windows.Forms.Timer _refreshTimer;
|
||||||
|
private readonly ToolStripMenuItem _queueStatusItem;
|
||||||
|
private readonly ToolStripMenuItem _serviceStatusItem;
|
||||||
|
|
||||||
|
public TrayApplicationContext()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_serviceClient = new ServiceClient();
|
||||||
|
|
||||||
|
// Create context menu
|
||||||
|
var contextMenu = new ContextMenuStrip();
|
||||||
|
|
||||||
|
// Service status (at top)
|
||||||
|
_serviceStatusItem = new ToolStripMenuItem("● Service Running")
|
||||||
|
{
|
||||||
|
Enabled = false,
|
||||||
|
Font = new Font(contextMenu.Font, FontStyle.Bold)
|
||||||
|
};
|
||||||
|
contextMenu.Items.Add(_serviceStatusItem);
|
||||||
|
contextMenu.Items.Add(new ToolStripSeparator());
|
||||||
|
|
||||||
|
// Queue status section
|
||||||
|
_queueStatusItem = new ToolStripMenuItem("Queue Status")
|
||||||
|
{
|
||||||
|
Enabled = false
|
||||||
|
};
|
||||||
|
contextMenu.Items.Add(_queueStatusItem);
|
||||||
|
|
||||||
|
var viewQueueItem = new ToolStripMenuItem("View Full Queue...");
|
||||||
|
viewQueueItem.Click += ViewQueue_Click;
|
||||||
|
contextMenu.Items.Add(viewQueueItem);
|
||||||
|
|
||||||
|
contextMenu.Items.Add(new ToolStripSeparator());
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
var configureItem = new ToolStripMenuItem("Configure Printers...");
|
||||||
|
configureItem.Click += Configure_Click;
|
||||||
|
contextMenu.Items.Add(configureItem);
|
||||||
|
|
||||||
|
contextMenu.Items.Add(new ToolStripSeparator());
|
||||||
|
|
||||||
|
// Exit
|
||||||
|
var exitItem = new ToolStripMenuItem("Exit");
|
||||||
|
exitItem.Click += Exit_Click;
|
||||||
|
contextMenu.Items.Add(exitItem);
|
||||||
|
|
||||||
|
// Create tray icon
|
||||||
|
_trayIcon = new NotifyIcon
|
||||||
|
{
|
||||||
|
Icon = SystemIcons.Information,
|
||||||
|
ContextMenuStrip = contextMenu,
|
||||||
|
Visible = true,
|
||||||
|
Text = "LAAPC Print Service"
|
||||||
|
};
|
||||||
|
|
||||||
|
_trayIcon.DoubleClick += TrayIcon_DoubleClick;
|
||||||
|
|
||||||
|
// Show startup notification
|
||||||
|
_trayIcon.ShowBalloonTip(2000, "LAAPC Print Service", "Tray app started", ToolTipIcon.Info);
|
||||||
|
|
||||||
|
// Set up refresh timer (every 2 seconds)
|
||||||
|
_refreshTimer = new System.Windows.Forms.Timer
|
||||||
|
{
|
||||||
|
Interval = 2000
|
||||||
|
};
|
||||||
|
_refreshTimer.Tick += RefreshTimer_Tick;
|
||||||
|
_refreshTimer.Start();
|
||||||
|
|
||||||
|
// Initial refresh
|
||||||
|
_ = RefreshStatusAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Failed to initialize tray app: {ex.Message}\n\n{ex.StackTrace}",
|
||||||
|
"Initialization Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void RefreshTimer_Tick(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
await RefreshStatusAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshStatusAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var status = await _serviceClient.GetQueueStatusAsync();
|
||||||
|
|
||||||
|
if (status.Success)
|
||||||
|
{
|
||||||
|
_serviceStatusItem.Text = "● Service Running";
|
||||||
|
_serviceStatusItem.ForeColor = Color.Green;
|
||||||
|
|
||||||
|
// Update queue status
|
||||||
|
if (status.Jobs.Count == 0)
|
||||||
|
{
|
||||||
|
_queueStatusItem.Text = "Queue Status: Empty";
|
||||||
|
_queueStatusItem.DropDownItems.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_queueStatusItem.Text = $"Queue Status ({status.Jobs.Count} {(status.Jobs.Count == 1 ? "job" : "jobs")})";
|
||||||
|
_queueStatusItem.Enabled = true;
|
||||||
|
|
||||||
|
// Update dropdown items with job list
|
||||||
|
_queueStatusItem.DropDownItems.Clear();
|
||||||
|
|
||||||
|
foreach (var job in status.Jobs.Take(10)) // Show max 10 in menu
|
||||||
|
{
|
||||||
|
var statusIcon = job.Status == "Processing" ? "●" : "○";
|
||||||
|
var jobText = $"{statusIcon} {job.Status}: {job.FileName}";
|
||||||
|
|
||||||
|
if (job.PrinterName != null)
|
||||||
|
{
|
||||||
|
jobText += $" ({job.PrinterName})";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.CurrentPage.HasValue && job.TotalPages.HasValue)
|
||||||
|
{
|
||||||
|
jobText += $" - Page {job.CurrentPage}/{job.TotalPages}";
|
||||||
|
}
|
||||||
|
|
||||||
|
var jobItem = new ToolStripMenuItem(jobText) { Enabled = false };
|
||||||
|
_queueStatusItem.DropDownItems.Add(jobItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.Jobs.Count > 10)
|
||||||
|
{
|
||||||
|
_queueStatusItem.DropDownItems.Add(new ToolStripSeparator());
|
||||||
|
var moreItem = new ToolStripMenuItem($"... and {status.Jobs.Count - 10} more")
|
||||||
|
{
|
||||||
|
Enabled = false
|
||||||
|
};
|
||||||
|
_queueStatusItem.DropDownItems.Add(moreItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tooltip
|
||||||
|
_trayIcon.Text = status.Jobs.Count == 0
|
||||||
|
? "LAAPC Print Service - Queue empty"
|
||||||
|
: $"LAAPC Print Service - {status.Jobs.Count} job(s) in queue";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_serviceStatusItem.Text = "○ Service Not Running";
|
||||||
|
_serviceStatusItem.ForeColor = Color.Red;
|
||||||
|
_queueStatusItem.Text = "Queue Status: Unavailable";
|
||||||
|
_queueStatusItem.DropDownItems.Clear();
|
||||||
|
_trayIcon.Text = "LAAPC Print Service - Not running";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Error refreshing status: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TrayIcon_DoubleClick(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Double-click opens full queue view
|
||||||
|
ViewQueue_Click(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ViewQueue_Click(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// TODO: Open queue view window
|
||||||
|
MessageBox.Show("Queue view window coming soon!", "LAAPC Print Service", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Configure_Click(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var configForm = new Forms.ConfigurationForm();
|
||||||
|
configForm.ShowDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Exit_Click(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_refreshTimer.Stop();
|
||||||
|
_trayIcon.Visible = false;
|
||||||
|
Application.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_refreshTimer?.Stop();
|
||||||
|
_refreshTimer?.Dispose();
|
||||||
|
_trayIcon?.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
# LAAPC Print Service - Remaining Work
|
||||||
|
|
||||||
|
## ✅ Completed
|
||||||
|
- [x] Core service architecture (queue, IPC, file monitoring)
|
||||||
|
- [x] CLI with self-installation capability
|
||||||
|
- [x] Basic printing with tray control
|
||||||
|
- [x] PDF output for testing
|
||||||
|
- [x] System tray application with status display
|
||||||
|
- [x] Configuration dialog for printer/tray assignments
|
||||||
|
- [x] Fixed System.Drawing.Common platform compatibility
|
||||||
|
|
||||||
|
## 🅿️ Parking Lot - Configuration Dialog Issues
|
||||||
|
|
||||||
|
### Known Issues (Non-Critical)
|
||||||
|
- [ ] **Printer resets on add page:** When adding a new page to a document type, the printer dropdown resets to the first printer in the list if the configuration hasn't been saved yet
|
||||||
|
- Expected: Should remember previous page's printer selection or default intelligently
|
||||||
|
- Workaround: Save after configuring each page
|
||||||
|
|
||||||
|
- [ ] **Save closes dialog:** Clicking Save closes the configuration dialog, forcing users to reopen it to continue editing
|
||||||
|
- Expected: Save button should persist changes but keep dialog open
|
||||||
|
- Suggested: Add "Save & Close" and "Save" as separate buttons, or just make Save not close the dialog
|
||||||
|
|
||||||
|
## 🔲 Immediate Testing
|
||||||
|
|
||||||
|
### Test Configuration Dialog
|
||||||
|
- [ ] Right-click tray icon → "Configure Printers..."
|
||||||
|
- [ ] Create new document type
|
||||||
|
- [ ] Add pages with printer/tray assignments
|
||||||
|
- [ ] Save configuration (test both local and global)
|
||||||
|
- [ ] Verify printer-config.json file created correctly
|
||||||
|
- [ ] Test loading existing configuration
|
||||||
|
|
||||||
|
## 🔨 High Priority - Service Integration
|
||||||
|
|
||||||
|
### 1. Integrate printer-config.json into PrintService
|
||||||
|
**Goal:** Service should read printer-config.json instead of appsettings.json
|
||||||
|
|
||||||
|
**Files to modify:**
|
||||||
|
- `PrintService/Services/ConfigurationService.cs` (CREATE NEW)
|
||||||
|
- Load printer-config.json from C:\ProgramData\LAAPC\ (global) and %LOCALAPPDATA%\LAAPC\ (local)
|
||||||
|
- Merge local over global (same logic as tray app)
|
||||||
|
- Return `PrinterConfiguration` object
|
||||||
|
|
||||||
|
- `PrintService/Models/PrinterConfig.cs` (COPY from PrintServiceTray)
|
||||||
|
- Copy the new config models into PrintService project
|
||||||
|
|
||||||
|
- `PrintService/Worker.cs` (MODIFY)
|
||||||
|
- Replace DocumentConfig loading with PrinterConfiguration loading
|
||||||
|
- Pass PageConfig[] to DocumentProcessor
|
||||||
|
|
||||||
|
- `PrintService/Services/DocumentProcessor.cs` (MODIFY - or DELETE if not needed)
|
||||||
|
- Update to work with new PageConfig format
|
||||||
|
- May need to refactor logic
|
||||||
|
|
||||||
|
- `PrintService/Services/PrinterService.cs` (MODIFY)
|
||||||
|
- Update Print() method signature to accept PageConfig[]
|
||||||
|
- Print each page to correct printer/tray based on PageConfig
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Keep appsettings.json for service-level settings (queue path, retry counts, etc.)
|
||||||
|
- printer-config.json ONLY for document type → printer/tray mappings
|
||||||
|
- Support missing config gracefully (log warning, skip job)
|
||||||
|
|
||||||
|
## 🔨 High Priority - Page Duplication Pattern
|
||||||
|
|
||||||
|
### 2. Implement Multi-Tray Page Duplication
|
||||||
|
**User Requirement:** Print EACH original page to ALL trays in sequence
|
||||||
|
|
||||||
|
**Current Behavior:**
|
||||||
|
```
|
||||||
|
TraySequence: [3,4,1,2]
|
||||||
|
Original document: 4 pages
|
||||||
|
Output: 4 pages (page 1→tray3, page 2→tray4, page 3→tray1, page 4→tray2)
|
||||||
|
```
|
||||||
|
|
||||||
|
**New Behavior:**
|
||||||
|
```
|
||||||
|
TraySequence: [3,4,1,2]
|
||||||
|
Original document: 4 pages
|
||||||
|
Output: 16 pages
|
||||||
|
Page 1 → Tray 3
|
||||||
|
Page 1 → Tray 4
|
||||||
|
Page 1 → Tray 1
|
||||||
|
Page 1 → Tray 2
|
||||||
|
Page 2 → Tray 3
|
||||||
|
Page 2 → Tray 4
|
||||||
|
Page 2 → Tray 1
|
||||||
|
Page 2 → Tray 2
|
||||||
|
... (and so on)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Files to modify:**
|
||||||
|
- `PrintService/Services/PrinterService.cs`
|
||||||
|
- Modify PrintPage event handler
|
||||||
|
- Add page duplication logic
|
||||||
|
- Track: originalPageIndex, trayIndex, currentOutputPage
|
||||||
|
- For each original page, cycle through all trays before moving to next page
|
||||||
|
|
||||||
|
**Algorithm:**
|
||||||
|
```csharp
|
||||||
|
int originalPageIndex = 0;
|
||||||
|
int trayIndex = 0;
|
||||||
|
int totalOriginalPages = CalculateTotalPages(lines);
|
||||||
|
bool hasMorePages = true;
|
||||||
|
|
||||||
|
PrintPage event:
|
||||||
|
1. Render lines for originalPageIndex
|
||||||
|
2. Set tray to pageConfigs[originalPageIndex].TrayNumber
|
||||||
|
3. Increment trayIndex
|
||||||
|
4. If trayIndex >= trays.Length:
|
||||||
|
trayIndex = 0
|
||||||
|
originalPageIndex++
|
||||||
|
5. hasMorePages = (originalPageIndex < totalOriginalPages)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔨 Medium Priority - Content Modifications
|
||||||
|
|
||||||
|
### 3. Port Rust Content Transformation Logic
|
||||||
|
**Reference:** `RUST/cgwprint/src/background.rs` and `RUST/cgwprint/data/printers.json`
|
||||||
|
|
||||||
|
**DO NOT START UNTIL:** Tray config and page duplication are complete and tested
|
||||||
|
|
||||||
|
**New config fields needed in PageConfig:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"PageNumber": 1,
|
||||||
|
"PrinterName": "Brother HL-L6415DW",
|
||||||
|
"TrayNumber": 3,
|
||||||
|
"TrayLabel": "Pink",
|
||||||
|
|
||||||
|
// NEW FIELDS:
|
||||||
|
"RemoveOrderNumber": false,
|
||||||
|
"RemoveOrderNumberLabel": false,
|
||||||
|
"IndentOrderNumber": 0,
|
||||||
|
"RowShift": {
|
||||||
|
"5": -2, // Move line 5 up by 2 positions
|
||||||
|
"12": 1 // Move line 12 down by 1 position
|
||||||
|
},
|
||||||
|
"RowTrim": {
|
||||||
|
"3": { "Start": 0, "End": 40 }, // Keep only first 40 chars of line 3
|
||||||
|
"7": { "Start": 10, "End": 50 } // Keep chars 10-50 of line 7
|
||||||
|
},
|
||||||
|
"BoldFont": "Courier New Bold",
|
||||||
|
"NormalFont": "Courier New"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Processing steps:**
|
||||||
|
1. Parse hex escapes (\xHH → bytes)
|
||||||
|
2. Split on form feed (\x0C) into pages
|
||||||
|
3. Font replacement (ESC+w+1 → bold, ESC+w+0 → normal)
|
||||||
|
4. Order number manipulation (remove/indent based on config)
|
||||||
|
5. Apply row_shift (vertical position adjustments)
|
||||||
|
6. Apply row_trim (substring extraction)
|
||||||
|
7. Add PCL/PJL header/footer wrapping
|
||||||
|
|
||||||
|
**Files to modify:**
|
||||||
|
- `PrintService/Models/PageConfig.cs` - Add new properties
|
||||||
|
- `PrintService/Services/DocumentProcessor.cs` - Add content transformation
|
||||||
|
- `PrintService/Services/ContentTransformer.cs` (CREATE NEW)
|
||||||
|
- ParseHexEscapes()
|
||||||
|
- SplitPages()
|
||||||
|
- ProcessFontCodes()
|
||||||
|
- ManipulateOrderNumber()
|
||||||
|
- ApplyRowShift()
|
||||||
|
- ApplyRowTrim()
|
||||||
|
- WrapWithPclHeaders()
|
||||||
|
|
||||||
|
**Location-specific variations:**
|
||||||
|
- "there are 3 locations where this app is used and each has a slight different layout"
|
||||||
|
- Use document type naming to differentiate: "Invoice_Location1", "Invoice_Location2", etc.
|
||||||
|
- Or add "Location" field to config and filter by it
|
||||||
|
|
||||||
|
## 📋 Testing Checklist
|
||||||
|
|
||||||
|
### Before Production Deployment
|
||||||
|
- [ ] Test service installation on clean 32-bit Windows 10 machine
|
||||||
|
- [ ] Test CLI from Harbor/Clipper integration
|
||||||
|
- [ ] Test rapid concurrent print requests (race condition prevention)
|
||||||
|
- [ ] Test multi-tray printing on Brother HL-L6415DW
|
||||||
|
- [ ] Test configuration changes without service restart
|
||||||
|
- [ ] Test queue persistence (stop service mid-job, restart, verify resume)
|
||||||
|
- [ ] Test error handling (printer offline, invalid tray, etc.)
|
||||||
|
- [ ] Test tray app startup on Windows boot (add to Startup folder?)
|
||||||
|
- [ ] Verify file cleanup after successful print
|
||||||
|
- [ ] Verify error folder gets populated on failures
|
||||||
|
|
||||||
|
## 🎯 Future Enhancements (Low Priority)
|
||||||
|
- [ ] Add logging to file for tray app (currently only service logs)
|
||||||
|
- [ ] Add "Start/Stop Service" option in tray menu
|
||||||
|
- [ ] Add "View Logs" option in tray menu
|
||||||
|
- [ ] Add notification sound for completed jobs
|
||||||
|
- [ ] Add job history view (last 50 jobs)
|
||||||
|
- [ ] Support per-page margins configuration
|
||||||
|
- [ ] Support custom font sizes per document type
|
||||||
|
- [ ] Add web UI for remote monitoring
|
||||||
|
|
||||||
|
## 📝 Important Notes
|
||||||
|
|
||||||
|
### Platform Compatibility
|
||||||
|
- **System.Drawing.Common:** Requires RuntimeHostConfigurationOption in .csproj
|
||||||
|
- **Remove explicit package reference** to avoid startup errors
|
||||||
|
- WinForms apps automatically include System.Drawing on Windows
|
||||||
|
|
||||||
|
### Configuration Locations
|
||||||
|
- **Global:** `C:\ProgramData\LAAPC\printer-config.json` (shared, requires admin)
|
||||||
|
- **Local:** `%LOCALAPPDATA%\LAAPC\printer-config.json` (per-user, no admin)
|
||||||
|
- **Merge strategy:** Local settings override global settings by document type
|
||||||
|
|
||||||
|
### Print Service Config
|
||||||
|
- Service currently uses: `appsettings.json` (old format with DocumentConfig)
|
||||||
|
- Service needs to migrate to: `printer-config.json` (new format with PageConfig[])
|
||||||
|
- Keep `appsettings.json` for non-printer settings (QueuePath, MaxRetries, etc.)
|
||||||
|
|
||||||
|
### Page Duplication Logic
|
||||||
|
- Current: One page per tray (simple mapping)
|
||||||
|
- Required: All pages to all trays (page duplication with tray cycling)
|
||||||
|
- Example: 4-page invoice × 5 trays = 20 physical pages printed
|
||||||
|
- Reason: Simulates carbon copy paper (each page needs copy in each color)
|
||||||
|
|
||||||
|
### Content Modifications
|
||||||
|
- Do LAST (most complex, least critical for initial deployment)
|
||||||
|
- Three locations have slightly different requirements
|
||||||
|
- May need per-location configuration or document type naming convention
|
||||||
|
- PCL/PJL wrapping may only apply to specific printers
|
||||||
|
|
||||||
|
### Git Status
|
||||||
|
- Last commit: cce9248
|
||||||
|
- `RUST/` folder excluded from version control
|
||||||
|
- Remember to commit after each major milestone
|
||||||
|
|
||||||
|
## 🚀 Next Steps (In Order)
|
||||||
|
1. **RIGHT NOW:** Test configuration dialog in tray app
|
||||||
|
2. Integrate printer-config.json into service
|
||||||
|
3. Implement page duplication pattern
|
||||||
|
4. Test on production hardware (Brother HL-L6415DW)
|
||||||
|
5. Deploy to first location
|
||||||
|
6. Gather feedback
|
||||||
|
7. Port content modifications from Rust
|
||||||
|
8. Deploy to remaining two locations
|
||||||
Reference in New Issue
Block a user