using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Collections.Generic;
public static class ClipboardFusionHelper
{
// --- Localization ---
private static string _currentLanguage = "en"; // Default language
private static readonly Dictionary<string, Dictionary<string, string>> _translations = new Dictionary<string, Dictionary<string, string>>
{
{
"ru", new Dictionary<string, string>
{
{ "FormTitle", "Мульти-вставка из истории (Двойной клик для отметки)" },
{ "ReverseOrderCheckBox", "Объединить в обратном порядке" },
{ "SeparatorLabel", "Разделитель:" },
{ "OkButton", "OK" },
{ "PasteButton", "Вставить" },
{ "CancelButton", "Отмена" },
{ "SelectAllMenuItem", "Выбрать все" },
{ "DeselectAllMenuItem", "Снять выделение" },
{ "ErrorLoadingHistoryPrefix", "Ошибка загрузки истории:" },
{ "HistoryIsEmpty", "История буфера обмена пуста." },
{ "ErrorLoadingHistoryMessage", "Ошибка загрузки истории: {0} См. лог для деталей." },
{ "ErrorProcessingSelectionMessage", "Ошибка при обработке выбора: {0}" },
{ "SetClipboardLogMessage", "MultiPastUtil: Попытка установить текст в буфер обмена (успех: {0}). Результат: {1}" }, // {0} bool, {1} text
{ "ErrorSavingSettingsLog", "Ошибка сохранения настроек MultiPastUtil: {0}" }
}
},
{
"en", new Dictionary<string, string>
{
{ "FormTitle", "Multi-Paste from History (Double-click to check)" },
{ "ReverseOrderCheckBox", "Combine in reverse order" },
{ "SeparatorLabel", "Separator:" },
{ "OkButton", "OK" },
{ "PasteButton", "Paste" },
{ "CancelButton", "Cancel" },
{ "SelectAllMenuItem", "Select All" },
{ "DeselectAllMenuItem", "Deselect All" },
{ "ErrorLoadingHistoryPrefix", "Error loading history:" },
{ "HistoryIsEmpty", "Clipboard history is empty." },
{ "ErrorLoadingHistoryMessage", "Error loading history: {0} See log for details." },
{ "ErrorProcessingSelectionMessage", "Error processing selection: {0}" },
{ "SetClipboardLogMessage", "MultiPastUtil: Attempted to set clipboard text (success: {0}). Result: {1}" },
{ "ErrorSavingSettingsLog", "Error saving MultiPastUtil settings: {0}" }
}
}
// You can add other languages here
};
// Method to get localized string
private static string GetLocalizedString(string key, params object[] args)
{
string format = key; // By default, return the key if nothing is found
// Try to find translation for current language
if (_translations.TryGetValue(_currentLanguage, out var langDict) && langDict.TryGetValue(key, out var translation))
{
format = translation;
}
// If there is no translation for current language, try to find for English (fallback)
else if (_currentLanguage != "en" && _translations.TryGetValue("en", out var enLangDict) && enLangDict.TryGetValue(key, out var enTranslation))
{
format = enTranslation;
}
// Format string if arguments are passed
try
{
return args.Length > 0 ? string.Format(format, args) : format;
}
catch (FormatException ex)
{
BFS.General.LogText($"Localization format error for key '{key}': {ex.Message}");
return format; // Return unformatted string in case of error
}
}
// Method to set language
public static void SetLanguage(string languageCode)
{
if (_translations.ContainsKey(languageCode))
{
_currentLanguage = languageCode;
BFS.General.LogText($"MultiPastUtil language set to: {languageCode}");
// Save language selection in settings for future runs
try
{
BFS.ScriptSettings.WriteValue(SettingLanguage, languageCode);
}
catch (Exception ex)
{
BFS.General.LogText($"Не удалось сохранить настройку языка: {ex.Message}");
}
}
else
{
BFS.General.LogText($"MultiPastUtil language '{languageCode}' not found.");
}
}
// --- End of Localization ---
// This variable will store the user's choice
private static string _resultText = null;
// Define constants
private const int DefaultFormWidth = 800;
private const int DefaultFormHeight = 450;
private const int DefaultListBoxWidth = 200;
private const int HistoryItemsToLoad = 100;
private const int ButtonPanelHeight = 30;
private const int TopPanelHeight = 30;
private const int ButtonWidth = 75;
private const int SplitterWidth = 5;
private const int SplitterMinSize = 100;
private const int SplitterMinExtra = 150;
private const string DefaultSeparator = "\r\n";
// Keys for settings
private const string SettingFormWidth = "MultiPastUtil_FormWidth";
private const string SettingFormHeight = "MultiPastUtil_FormHeight";
private const string SettingListBoxWidth = "MultiPastUtil_ListBoxWidth";
private const string SettingLanguage = "MultiPastUtil_Language"; // Add key for language
// Helper function to read settings
private static int ReadIntSetting(string key, int defaultValue)
{
try
{
// Try to read value
string stringValue = BFS.ScriptSettings.ReadValue(key);
// Try to convert to int
if (int.TryParse(stringValue, out int value) && value > 50) // Add minimum check
{
return value;
}
}
catch (Exception ex)
{
// Log error if needed
BFS.General.LogText($"Ошибка чтения настройки '{key}': {ex.Message}");
}
// Return default value if something went wrong
return defaultValue;
}
public static string ProcessText(string text)
{
_resultText = text;
// Read language from settings, if it exists
try
{
string savedLanguage = BFS.ScriptSettings.ReadValue(SettingLanguage);
if (!string.IsNullOrEmpty(savedLanguage) && _translations.ContainsKey(savedLanguage))
{
_currentLanguage = savedLanguage; // Set saved language
}
}
catch (Exception ex)
{
BFS.General.LogText($"Не удалось прочитать настройку языка: {ex.Message}");
}
// Read saved sizes
int formWidth = ReadIntSetting(SettingFormWidth, DefaultFormWidth);
int formHeight = ReadIntSetting(SettingFormHeight, DefaultFormHeight);
int listBoxWidth = ReadIntSetting(SettingListBoxWidth, DefaultListBoxWidth);
// Wrap form in using
using (Form form = new Form())
{
form.Text = GetLocalizedString("FormTitle"); // Use localization
form.ClientSize = new Size(formWidth, formHeight);
form.StartPosition = FormStartPosition.CenterScreen;
// Create main table to place all elements
TableLayoutPanel mainLayout = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 1,
RowCount = 4, // Menu, top panel, main area, bottom button panel
};
// Configure rows: menu and top panel fixed height, main area stretches, bottom button panel fixed height
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Menu
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Top panel
mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); // Main area (stretched)
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Bottom button panel
// Clear all controls
form.Controls.Clear();
// Add only main table to form
form.Controls.Add(mainLayout);
// --- Initialize main panels and controls in advance ---
FlowLayoutPanel topPanel = new FlowLayoutPanel
{
Height = TopPanelHeight,
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.LeftToRight,
Padding = new Padding(5, 5, 5, 0),
AutoSize = true
};
Panel buttonPanel = new Panel { Height = ButtonPanelHeight, Dock = DockStyle.Bottom };
CheckedListBox checkedListBox = new CheckedListBox
{
Dock = DockStyle.Fill,
CheckOnClick = false,
HorizontalScrollbar = true,
IntegralHeight = false
};
// --- End of preliminary initialization ---
// Create menu for form (again using MenuStrip)
MenuStrip menuStrip = new MenuStrip();
menuStrip.Dock = DockStyle.Top;
ToolStripMenuItem languageMenu = new ToolStripMenuItem("Language / Язык");
ToolStripMenuItem ruMenuItem = new ToolStripMenuItem("Русский") { Name = "ruMenuItem" };
ToolStripMenuItem enMenuItem = new ToolStripMenuItem("English") { Name = "enMenuItem" };
// Set marks for current language
ruMenuItem.Checked = _currentLanguage == "ru";
enMenuItem.Checked = _currentLanguage == "en";
// Then set event handlers for both menu items
ruMenuItem.Click += (sender, e) =>
{
if (_currentLanguage != "ru")
{
SetLanguage("ru");
UpdateFormLanguage(form);
}
};
enMenuItem.Click += (sender, e) =>
{
if (_currentLanguage != "en")
{
SetLanguage("en");
UpdateFormLanguage(form);
}
};
// Add menu items
languageMenu.DropDownItems.Add(ruMenuItem);
languageMenu.DropDownItems.Add(enMenuItem);
menuStrip.Items.Add(languageMenu);
// Add MenuStrip to first table cell
mainLayout.Controls.Add(menuStrip, 0, 0);
// --- Fill and add top panel (already created) ---
Label separatorLabel = new Label
{
Name = "separatorLabel",
Text = GetLocalizedString("SeparatorLabel"),
AutoSize = true,
TextAlign = ContentAlignment.MiddleLeft,
Margin = new Padding(10, 5, 0, 0)
};
TextBox separatorTextBox = new TextBox
{
Width = 100,
Margin = new Padding(5, 3, 0, 0)
};
// Empty label for spacing
Label spacerLabel = new Label
{
Width = 20,
Text = "",
AutoSize = false
};
CheckBox reverseOrderCheckBox = new CheckBox {
Name = "reverseOrderCheckBox",
Text = GetLocalizedString("ReverseOrderCheckBox"),
AutoSize = true,
Margin = new Padding(0, 3, 0, 0)
};
// Add elements to top panel in left-to-right order
topPanel.Controls.Add(separatorLabel);
topPanel.Controls.Add(separatorTextBox);
topPanel.Controls.Add(spacerLabel);
topPanel.Controls.Add(reverseOrderCheckBox);
// Add top panel to second table cell
mainLayout.Controls.Add(topPanel, 0, 1);
// --- Fill and add button panel (already created) ---
Button okButton = new Button { Text = GetLocalizedString("OkButton"), DialogResult = DialogResult.OK, Dock = DockStyle.Right, Width = ButtonWidth };
Button cancelButton = new Button { Text = GetLocalizedString("CancelButton"), DialogResult = DialogResult.Cancel, Dock = DockStyle.Right, Width = ButtonWidth };
buttonPanel.Controls.AddRange(new Control[] { okButton, cancelButton });
// Add button panel to last table cell
mainLayout.Controls.Add(buttonPanel, 0, 3);
// Create SplitContainer for main area (list on left, text on right)
SplitContainer splitContainer = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Vertical,
SplitterWidth = SplitterWidth,
};
// Add SplitContainer to third (main) table cell
mainLayout.Controls.Add(splitContainer, 0, 2);
// Add CheckedListBox to left panel of SplitContainer
splitContainer.Panel1.Controls.Add(checkedListBox);
// RichTextBox
RichTextBox richTextBox = new RichTextBox
{
Dock = DockStyle.Fill,
ReadOnly = true,
BorderStyle = BorderStyle.FixedSingle,
WordWrap = true
};
// Add RichTextBox to right panel of SplitContainer
splitContainer.Panel2.Controls.Add(richTextBox);
// Context menu
ContextMenuStrip contextMenu = new ContextMenuStrip();
ToolStripMenuItem selectAllMenuItem = new ToolStripMenuItem(GetLocalizedString("SelectAllMenuItem")) { Name = "selectAllMenuItem" };
ToolStripMenuItem deselectAllMenuItem = new ToolStripMenuItem(GetLocalizedString("DeselectAllMenuItem")) { Name = "deselectAllMenuItem" };
contextMenu.Items.AddRange(new ToolStripItem[] { selectAllMenuItem, deselectAllMenuItem });
checkedListBox.ContextMenuStrip = contextMenu;
// "Select All" handler
selectAllMenuItem.Click += (sender, e) =>
{
for (int i = 0; i < checkedListBox.Items.Count; i++)
{
string itemText = checkedListBox.Items[i].ToString();
// Skip service messages (use key for check)
if (!itemText.StartsWith(GetLocalizedString("ErrorLoadingHistoryPrefix")) && itemText != GetLocalizedString("HistoryIsEmpty"))
{
checkedListBox.SetItemChecked(i, true);
}
}
};
// "Deselect All" handler
deselectAllMenuItem.Click += (sender, e) =>
{
for (int i = 0; i < checkedListBox.Items.Count; i++)
{
checkedListBox.SetItemChecked(i, false);
}
};
// Load and fill list
List<string> historyItemsForList = new List<string>();
try
{
int totalItems = BFS.ClipboardFusion.GetHistoryItemCount();
if (totalItems > 0)
{
// Determine how many items to load (not more than available, and not more than limit)
int countToLoad = Math.Min(totalItems, HistoryItemsToLoad);
// Load the latest N items (from index 0 to countToLoad - 1)
for (int i = 0; i < countToLoad; i++)
{
// Get each item by index (0 = latest)
string historyItem = BFS.ClipboardFusion.GetHistoryText(i);
historyItemsForList.Add(historyItem ?? string.Empty);
}
}
checkedListBox.Items.Clear(); // Clear before adding
if (historyItemsForList.Count > 0)
{
// Add all items at once
checkedListBox.Items.AddRange(historyItemsForList.ToArray());
// Pre-select first item (latest) (UX)
checkedListBox.SelectedIndex = 0;
}
else
{
checkedListBox.Items.Add(GetLocalizedString("HistoryIsEmpty")); // Localization
}
}
catch (Exception ex)
{
checkedListBox.Items.Clear();
string errorMsg = GetLocalizedString("ErrorLoadingHistoryPrefix") + $" {ex.Message}"; // Localization + details
checkedListBox.Items.Add(errorMsg);
BFS.Dialog.ShowMessageError(GetLocalizedString("ErrorLoadingHistoryMessage", ex.Message)); // Localization with formatting
BFS.General.LogText($"Ошибка в макросе MultiPastUtil при получении истории: {ex.ToString()}");
}
// Event handlers
checkedListBox.SelectedIndexChanged += (sender, e) =>
{
if (checkedListBox.SelectedItem != null)
{
string selectedText = checkedListBox.SelectedItem.ToString();
// Use keys for check
if (!selectedText.StartsWith(GetLocalizedString("ErrorLoadingHistoryPrefix")) && selectedText != GetLocalizedString("HistoryIsEmpty"))
{
richTextBox.Text = selectedText;
}
else
{
richTextBox.Text = string.Empty;
}
}
else
{
richTextBox.Text = string.Empty;
}
};
// Double click handler
// Only handles toggling the checkbox
checkedListBox.DoubleClick += (sender, e) =>
{
// Get index of element under cursor at moment of double click
// Use MouseEventArgs if available, otherwise rely on SelectedIndex
int index = -1;
if (e is MouseEventArgs me) // Check if there is mouse information
{
index = checkedListBox.IndexFromPoint(me.Location);
}
else // Backup variant (less accurate if click was not exactly on an item)
{
index = checkedListBox.SelectedIndex;
}
// Check if clicked on an existing item
if (index != -1 && index < checkedListBox.Items.Count)
{
string itemText = checkedListBox.Items[index].ToString();
// Prevent marking service messages (use keys)
if (!itemText.StartsWith(GetLocalizedString("ErrorLoadingHistoryPrefix")) && itemText != GetLocalizedString("HistoryIsEmpty"))
{
// Invert checkbox state for this item
checkedListBox.SetItemChecked(index, !checkedListBox.GetItemChecked(index));
}
}
};
// Button handlers
SetupButtonHandlers(form, okButton, cancelButton, checkedListBox, reverseOrderCheckBox, separatorTextBox, text); // Pass original text
// Form closing handler to save settings
form.FormClosing += (sender, e) =>
{
try
{
// Save current sizes only if window is not minimized
if (form.WindowState == FormWindowState.Normal)
{
BFS.ScriptSettings.WriteValue(SettingFormWidth, form.ClientSize.Width.ToString());
BFS.ScriptSettings.WriteValue(SettingFormHeight, form.ClientSize.Height.ToString());
// Take list width directly from control, since splitter changes it
BFS.ScriptSettings.WriteValue(SettingListBoxWidth, checkedListBox.Width.ToString());
}
}
catch (Exception ex)
{
BFS.General.LogText(GetLocalizedString("ErrorSavingSettingsLog", ex.ToString())); // Localization
// Don't interrupt closing form due to saving error
}
};
// Show form
form.ShowDialog();
} // form will be disposed here automatically
return _resultText;
}
// Method for centralized UI update when language changes
private static void UpdateFormLanguage(Form form)
{
if (form == null) return;
// Update form title
form.Text = GetLocalizedString("FormTitle");
// --- Find and update controls ---
// Find controls in main layout (TableLayoutPanel)
TableLayoutPanel mainLayout = form.Controls.OfType<TableLayoutPanel>().FirstOrDefault();
if (mainLayout == null) return; // Unable to find main layout
// 1. Top panel (FlowLayoutPanel)
FlowLayoutPanel topPanel = mainLayout.Controls.OfType<FlowLayoutPanel>().FirstOrDefault();
if (topPanel != null)
{
var separatorLabel = topPanel.Controls.OfType<Label>().FirstOrDefault(lbl => lbl.Name == "separatorLabel");
if (separatorLabel != null) separatorLabel.Text = GetLocalizedString("SeparatorLabel");
var reverseCheckBox = topPanel.Controls.OfType<CheckBox>().FirstOrDefault(chk => chk.Name == "reverseOrderCheckBox");
if (reverseCheckBox != null) reverseCheckBox.Text = GetLocalizedString("ReverseOrderCheckBox");
}
// 2. Button panel (Panel with DockStyle.Bottom)
Panel buttonPanel = mainLayout.Controls.OfType<Panel>().FirstOrDefault(p => p.Dock == DockStyle.Bottom);
if (buttonPanel != null)
{
var okButton = buttonPanel.Controls.OfType<Button>().FirstOrDefault(btn => btn.DialogResult == DialogResult.OK);
if (okButton != null) okButton.Text = GetLocalizedString("OkButton");
var cancelButton = buttonPanel.Controls.OfType<Button>().FirstOrDefault(btn => btn.DialogResult == DialogResult.Cancel);
if (cancelButton != null) cancelButton.Text = GetLocalizedString("CancelButton");
}
// 3. Context menu (in CheckedListBox inside SplitContainer)
SplitContainer splitContainer = mainLayout.Controls.OfType<SplitContainer>().FirstOrDefault();
CheckedListBox checkedListBox = splitContainer?.Panel1.Controls.OfType<CheckedListBox>().FirstOrDefault();
if (checkedListBox?.ContextMenuStrip != null)
{
var selectAll = checkedListBox.ContextMenuStrip.Items.OfType<ToolStripMenuItem>().FirstOrDefault(mi => mi.Name == "selectAllMenuItem");
if (selectAll != null) selectAll.Text = GetLocalizedString("SelectAllMenuItem");
var deselectAll = checkedListBox.ContextMenuStrip.Items.OfType<ToolStripMenuItem>().FirstOrDefault(mi => mi.Name == "deselectAllMenuItem");
if (deselectAll != null) deselectAll.Text = GetLocalizedString("DeselectAllMenuItem");
}
// 4. Main menu (MenuStrip)
MenuStrip menuStrip = mainLayout.Controls.OfType<MenuStrip>().FirstOrDefault();
if (menuStrip != null)
{
// Find menu items by name
var ruMenuItem = menuStrip.Items.OfType<ToolStripMenuItem>()
.SelectMany(item => item.DropDownItems.OfType<ToolStripMenuItem>())
.FirstOrDefault(mi => mi.Name == "ruMenuItem");
var enMenuItem = menuStrip.Items.OfType<ToolStripMenuItem>()
.SelectMany(item => item.DropDownItems.OfType<ToolStripMenuItem>())
.FirstOrDefault(mi => mi.Name == "enMenuItem");
// Update their state Checked
if (ruMenuItem != null) ruMenuItem.Checked = (_currentLanguage == "ru");
if (enMenuItem != null) enMenuItem.Checked = (_currentLanguage == "en");
}
}
// Helper method to update individual control
private static void UpdateControlLanguage(Control control)
{
if (control is CheckBox checkBox)
{
// Update text of checkboxes according to their content
if (checkBox.Text.Contains("Объединить в обратном порядке") || checkBox.Text.Contains("Combine in reverse order"))
{
checkBox.Text = GetLocalizedString("ReverseOrderCheckBox");
}
}
else if (control is Label label)
{
// Update text of labels
if (label.Text.Contains("Разделитель") || label.Text.Contains("Separator"))
{
label.Text = GetLocalizedString("SeparatorLabel");
}
}
else if (control is Button button)
{
// Update buttons
if (button.Text == "OK" || button.Text == "ОК")
{
button.Text = GetLocalizedString("OkButton");
}
else if (button.Text.Contains("Отмена") || button.Text.Contains("Cancel"))
{
button.Text = GetLocalizedString("CancelButton");
}
}
}
// Code of helper methods
private static void SetupContextMenuHandlers(CheckedListBox checkedListBox) { /* ... */ }
private static void LoadHistory(CheckedListBox checkedListBox) { /* ... */ }
private static void SetupButtonHandlers(Form form, Button okButton, Button cancelButton, CheckedListBox checkedListBox, CheckBox reverseOrderCheckBox, TextBox separatorTextBox, string originalText)
{
// Button handler for OK button
okButton.Click += (sender, e) =>
{
try
{
// Get only real selected items (use keys for filtering)
var selectedItems = checkedListBox.CheckedItems.OfType<string>()
.Where(item => !item.StartsWith(GetLocalizedString("ErrorLoadingHistoryPrefix")) && item != GetLocalizedString("HistoryIsEmpty"))
.ToList();
string finalResult = string.Empty;
if (selectedItems.Count > 0)
{
// Check reverse order checkbox
if (reverseOrderCheckBox.Checked)
{
selectedItems.Reverse(); // Reverse the list
}
// Определяем разделитель
string separator = string.IsNullOrEmpty(separatorTextBox.Text)
? DefaultSeparator // Use constant "\r\n"
: "\r\n" + separatorTextBox.Text + "\r\n"; // Use user text
// Join with selected separator
finalResult = string.Join(separator, selectedItems);
}
else
{
// If nothing is selected, you can either return an empty string, or the original text
// Now return an empty string
finalResult = string.Empty; // or finalResult = originalText;
}
// Assign the result to the static variable
_resultText = finalResult;
// Log the result (optional)
BFS.General.LogText(GetLocalizedString("SetClipboardLogMessage", true, finalResult));
// The form will close automatically, since DialogResult = OK
}
catch (Exception ex)
{
BFS.Dialog.ShowMessageError(GetLocalizedString("ErrorProcessingSelectionMessage", ex.Message));
BFS.General.LogText($"Ошибка в MultiPastUtil okButton.Click: {ex.ToString()}");
_resultText = originalText; // Return original text in case of error
// Do not close the form at error, allow user to fix
form.DialogResult = DialogResult.None; // Reset DialogResult, so the form does not close automatically
}
};
// Button handler for Cancel button (already works through DialogResult = Cancel)
// Additional actions are not required, simply closes the form.
// You can add logic to reset _resultText at cancellation, if needed:
cancelButton.Click += (sender, e) =>
{
_resultText = originalText; // Reset the result to the original at cancellation
};
}
}