Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ After editing `.po` files, always run `compile-translations.ps1` to regenerate t

**`src/Sic/SettingsDialog.cs` + `SettingsDialog.Designer.cs`** — Settings form. A `TabControl` (filling the form, OK/Cancel beneath) with two tabs, each its own flat `TableLayoutPanel`:
- **General** — language dropdown, confirm-exit checkbox, "check for updates on startup" checkbox, background update-frequency dropdown.
- **Images** — output folder (textbox + browse + reset), a "detect data in clipboard" checkbox (issue #36), and a target-formats checklist that selects which formats appear in the main window's dropdown (issue #47). Built as individual `CheckBox` controls stacked in a `TableLayoutPanel` (`formatsPanel`, filled at runtime in `PopulateFormats()`) inside a `GroupBox` (`formatsGroupBox`) — *not* a `CheckedListBox`, which doesn't reliably announce toggle state changes to screen readers. The group box caption (rather than a separate label) supplies the accessible group name; each checkbox is its own tab stop. OK requires at least one format ticked; when all are ticked it saves `EnabledFormats` as empty so formats added in future versions show automatically.
- **Images** — output folder (textbox + browse + reset), a "save converted images in the same folder as the original" checkbox (issue #33), a "detect data in clipboard" checkbox (issue #36), and a target-formats checklist that selects which formats appear in the main window's dropdown (issue #47). Built as individual `CheckBox` controls stacked in a `TableLayoutPanel` (`formatsPanel`, filled at runtime in `PopulateFormats()`) inside a `GroupBox` (`formatsGroupBox`) — *not* a `CheckedListBox`, which doesn't reliably announce toggle state changes to screen readers. The group box caption (rather than a separate label) supplies the accessible group name; each checkbox is its own tab stop. OK requires at least one format ticked; when all are ticked it saves `EnabledFormats` as empty so formats added in future versions show automatically.

Tab page `Text` doesn't honor `&` mnemonics — navigate tabs with Ctrl+Tab / Ctrl+PgUp/Dn. Exposes `UpdatePeriodicCheckChanged` so `MainWindow` can re-arm the background update loop live (without a restart) when the frequency changes.

### Utilities (`src/Sic/Utils/`)

- `Config.cs` — Static config manager using SharpConfig. Reads/writes `%APPDATA%/Oire/Sic/Sic.cfg` with a `[General]` section (Language, OutputFolder, LastInputFolder, ConfirmExitWithQueue, CheckForUpdatesOnStartup, UpdateCheckInterval, DetectClipboardData, EnabledFormats). `EnabledFormats` is a comma-separated list of SIC! format keys shown in the target dropdown (empty = all); parse it via `GetEnabledFormatKeys()` and filter with `ImageConverter.GetEnabledFormats(...)`. Accepts `isGui` parameter to route errors to MessageBox (GUI) or stderr (CLI).
- `Config.cs` — Static config manager using SharpConfig. Reads/writes `%APPDATA%/Oire/Sic/Sic.cfg` with a `[General]` section (Language, OutputFolder, SaveToSourceFolder, LastInputFolder, ConfirmExitWithQueue, CheckForUpdatesOnStartup, UpdateCheckInterval, DetectClipboardData, EnabledFormats). `SaveToSourceFolder` (issue #33), when on, writes converted files next to each original instead of into `OutputFolder`; clipboard/URL items have no source folder and fall back to `OutputFolder`. `EnabledFormats` is a comma-separated list of SIC! format keys shown in the target dropdown (empty = all); parse it via `GetEnabledFormatKeys()` and filter with `ImageConverter.GetEnabledFormats(...)`. Accepts `isGui` parameter to route errors to MessageBox (GUI) or stderr (CLI).
- `FileHelper.cs` — Cloud placeholder detection (OneDrive/SharePoint recall attributes) and image file enumeration with glob patterns.
- `UrlHelper.cs` — Single source of truth for link validation: `IsValidHttpUrl(text, out url)` trims input and checks for an absolute http(s) URL. Used by the "Add by link" dialog, Ctrl+V paste, and clipboard auto-detection so all three validate links identically.
- `Localization.cs` — Wraps GetText.NET with convenience methods: `_()`, `_n()`, `_p()`, `_pn()` for translations. Loads `.mo` files from the `locale/` folder relative to the executable. Falls back through language parents to `en-US`.
Expand Down Expand Up @@ -135,6 +135,7 @@ SIC! is an accessible image format converter primarily aimed at blind and low-co
### Settings (via SharpConfig, stored in `%APPDATA%/Oire/Sic/Sic.cfg`)

- Output folder (default: `Converted` subfolder in the data directory)
- Save converted images in the same folder as the original (`SaveToSourceFolder`, default: off) — when on, each converted file is written next to its source file instead of into the output folder (issue #33). Clipboard captures and downloaded links have no source folder, so they still go to the output folder.
- Language
- Confirm exit when images are in the queue
- Check for updates on startup (default: enabled) — a single silent check shortly after launch
Expand Down
4 changes: 2 additions & 2 deletions src/Sic/MainWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ await Task.Run(() => {
continue;
}

var outputPath = ImageConverter.GenerateOutputPath(item, targetFormat, outputFolder);
var outputPath = ImageConverter.GenerateOutputPath(item, targetFormat, outputFolder, Config.General.SaveToSourceFolder);

if (File.Exists(outputPath)) {
var resolution = ResolveFileConflict(outputPath);
Expand Down Expand Up @@ -594,7 +594,7 @@ private async void CreateMultiSizeIcoMenuItem_Click(object? sender, EventArgs e)
var index = imageListView.SelectedIndices[0];
var item = _imageItems[index];
var outputFolder = ValidateOutputFolder();
var outputPath = ImageConverter.GenerateOutputPath(item, "ICO", outputFolder);
var outputPath = ImageConverter.GenerateOutputPath(item, "ICO", outputFolder, Config.General.SaveToSourceFolder);

if (File.Exists(outputPath)) {
var resolution = ResolveFileConflict(outputPath);
Expand Down
18 changes: 14 additions & 4 deletions src/Sic/Services/ImageConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,14 +316,24 @@ public static string GetFileExtension(string format) {
};
}

public static string GenerateOutputPath(ImageItem item, string targetFormat, string outputFolder) {
public static string GenerateOutputPath(ImageItem item, string targetFormat, string outputFolder, bool saveToSourceFolder = false) {
var baseName = Path.GetFileNameWithoutExtension(item.FileName);
var extension = GetFileExtension(targetFormat);

// Write next to the original when requested (issue #33), but only for items that actually
// came from a file on disk. Clipboard captures and downloaded links have no source folder,
// so they fall through to the configured output folder below.
if (saveToSourceFolder && !string.IsNullOrWhiteSpace(item.FilePath)) {
var sourceDir = Path.GetDirectoryName(item.FilePath);
if (!string.IsNullOrWhiteSpace(sourceDir)) {
return Path.Combine(sourceDir, baseName + extension);
}
}

if (string.IsNullOrWhiteSpace(outputFolder)) {
outputFolder = App.DefaultOutputFolder;
}

var baseName = Path.GetFileNameWithoutExtension(item.FileName);
var extension = GetFileExtension(targetFormat);

if (item.BasePath != null && item.FilePath != null) {
var relativePath = Path.GetRelativePath(item.BasePath, item.FilePath);
var relativeDir = Path.GetDirectoryName(relativePath) ?? "";
Expand Down
37 changes: 27 additions & 10 deletions src/Sic/SettingsDialog.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/Sic/SettingsDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ private void TabControl_Selected(object? sender, TabControlEventArgs e) {

private void LoadSettings() {
outputFolderTextBox.Text = Config.General.OutputFolder;
saveToSourceFolderCheckBox.Checked = Config.General.SaveToSourceFolder;
confirmExitCheckBox.Checked = Config.General.ConfirmExitWithQueue;
checkUpdatesOnStartupCheckBox.Checked = Config.General.CheckForUpdatesOnStartup;
detectClipboardCheckBox.Checked = Config.General.DetectClipboardData;
Expand Down Expand Up @@ -219,6 +220,7 @@ private void OkButton_Click(object? sender, EventArgs e) {
var oldUpdateInterval = Config.General.UpdateCheckInterval;

Config.General.OutputFolder = folder;
Config.General.SaveToSourceFolder = saveToSourceFolderCheckBox.Checked;
Config.General.ConfirmExitWithQueue = confirmExitCheckBox.Checked;
Config.General.CheckForUpdatesOnStartup = checkUpdatesOnStartupCheckBox.Checked;
Config.General.DetectClipboardData = detectClipboardCheckBox.Checked;
Expand Down
7 changes: 7 additions & 0 deletions src/Sic/Utils/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ public class Config {
public class SectionGeneral {
public string Language { get; set; } = "System";
public string OutputFolder { get; set; } = App.DefaultOutputFolder;

/// <summary>When <c>true</c>, converted files are written next to their source file
/// instead of into <see cref="OutputFolder"/> (issue #33). Items that have no source
/// folder — clipboard captures and downloaded links — still fall back to
/// <see cref="OutputFolder"/>. Opt-in; off by default.</summary>
public bool SaveToSourceFolder { get; set; }

public string LastInputFolder { get; set; } = "";
public bool ConfirmExitWithQueue { get; set; } = true;

Expand Down
Loading