windowcapture
исходный код / UI/WordSidePanel.cs

WordSidePanel.cs

1959 строк · 79,789 байт · модуль UI
   1using System;
   2using System.Collections.Generic;
   3using System.Drawing;
   4using System.Drawing.Drawing2D;
   5using System.Threading;
   6using System.Windows.Forms;
   7using WindowCapture.Integration;
   8using WindowCapture.Models;
   9
  10namespace WindowCapture.UI
  11{
  12    /// <summary>
  13    /// Side panel for Word integration - shows document preview and export controls
  14    /// </summary>
  15    public class WordSidePanel : Panel
  16    {
  17        private static readonly Color DarkBg = Color.FromArgb(30, 30, 34);
  18        private static readonly Color DarkPanel = Color.FromArgb(40, 40, 45);
  19        private static readonly Color DarkField = Color.FromArgb(50, 50, 55);
  20        private static readonly Color AccentColor = Color.FromArgb(86, 156, 214);
  21        private static readonly Color TextColor = Color.FromArgb(230, 230, 230);
  22        private static readonly Color DimTextColor = Color.FromArgb(140, 140, 140);
  23        private static readonly Color BorderColor = Color.FromArgb(60, 60, 65);
  24        private static readonly Color SuccessColor = Color.FromArgb(78, 154, 78);
  25
  26        private PictureBox previewBox;
  27        private Label lblDocName;
  28        private Label lblStatus;
  29        private ComboBox cmbDocuments;
  30        private TextBox txtDescription;
  31        private TextBox txtCaption;
  32        private Button btnInsert;
  33        private Button btnRefresh;
  34        private Button btnClose;
  35        private System.Windows.Forms.Timer refreshTimer;
  36
  37        // Title page section
  38        private GlassScrollPanel glassScroll;
  39        private Panel scrollPanel;
  40        private Panel titlePageSection;
  41        private Button btnTitlePageToggle;
  42        private bool titlePageExpanded = false;
  43        private TextBox txtTemplatePath;
  44        private TextBox txtWorkNumber;
  45        private ComboBox cmbDiscipline;
  46        private ComboBox cmbTopic;
  47        private ComboBox cmbStudent;
  48        private ComboBox cmbGroup;
  49        private ComboBox cmbTeacher;
  50        private Button btnInsertTitlePage;
  51        private Button btnGenerateDescription;
  52        private TextBox txtGeminiApiKey;
  53
  54        // Formatting section
  55        private Panel formattingSection;
  56        private Button btnFormattingToggle;
  57        private bool formattingExpanded = false;
  58        private Button btnAddPageNumbering;
  59        private Button btnOriginalize;
  60
  61        // Gemini section
  62        private Panel geminiSection;
  63        private Button btnGeminiToggle;
  64        private bool geminiExpanded = false;
  65        private ListBox lstGeminiModels;
  66        private Button btnResetModelStatuses;
  67        private TextBox txtGeminiThinkingBudget;
  68        private ComboBox cmbGeminiMediaResolution;
  69        private TextBox txtGeminiInstruction;
  70
  71        private Bitmap imageToInsert;
  72        private EditorForm parentEditor;
  73        private bool isAnimating;
  74        private int targetWidth;
  75        private const int PanelWidth = 320;
  76        private const int AnimationStep = 40;
  77        private bool ignoreSpaceUntilRelease = true; // Ignore space key until released
  78        private bool pendingFocus = false; // Set focus after space is released
  79
  80        public event EventHandler InsertSuccess;
  81        public event EventHandler PanelClosed;
  82
  83        public WordSidePanel(EditorForm editor)
  84        {
  85            parentEditor = editor;
  86            InitializePanel();
  87            SetupRefreshTimer();
  88        }
  89
  90        private void InitializePanel()
  91        {
  92            Width = 0; // Start collapsed
  93            Dock = DockStyle.Right;
  94            BackColor = Color.Transparent;
  95
  96            // Handle Ctrl+Space at panel level
  97            this.KeyDown += Panel_KeyDown;
  98
  99            int margin = 12;
 100            int fieldWidth = PanelWidth - margin * 2;
 101
 102            // Scrollable container — glass scroll panel (same style as settings)
 103            glassScroll = new GlassScrollPanel();
 104            glassScroll.Dock = DockStyle.Fill;
 105            glassScroll.BackColor = Color.Transparent;
 106            Controls.Add(glassScroll);
 107            scrollPanel = glassScroll.Content; // All controls go into inner panel
 108
 109            int y = 10;
 110
 111            // Header
 112            var lblHeader = new Label();
 113            lblHeader.Text = "Word Export";
 114            lblHeader.Font = new Font("Segoe UI Semibold", 11f);
 115            lblHeader.ForeColor = TextColor;
 116            lblHeader.Location = new Point(margin, y);
 117            lblHeader.AutoSize = true;
 118            scrollPanel.Controls.Add(lblHeader);
 119
 120            // Close button
 121            btnClose = new Button();
 122            btnClose.Text = "x";
 123            btnClose.Size = new Size(28, 28);
 124            btnClose.Location = new Point(PanelWidth - margin - 28 - 20, y - 2); // -20 for scrollbar
 125            btnClose.FlatStyle = FlatStyle.Flat;
 126            btnClose.FlatAppearance.BorderSize = 0;
 127            btnClose.BackColor = Color.Transparent;
 128            btnClose.ForeColor = DimTextColor;
 129            btnClose.Font = new Font("Segoe UI", 10f);
 130            btnClose.Cursor = Cursors.Hand;
 131            btnClose.TabStop = false; // Don't receive focus via Tab
 132            btnClose.Click += (s, e) => Hide();
 133            btnClose.MouseEnter += (s, e) => { btnClose.ForeColor = TextColor; };
 134            btnClose.MouseLeave += (s, e) => { btnClose.ForeColor = DimTextColor; };
 135            // Ignore space key on close button (prevents accidental close)
 136            btnClose.KeyDown += (s, e) =>
 137            {
 138                if (e.KeyCode == Keys.Space)
 139                {
 140                    e.SuppressKeyPress = true;
 141                    e.Handled = true;
 142                }
 143            };
 144            scrollPanel.Controls.Add(btnClose);
 145            y += 35;
 146
 147            // Document selector
 148            AddLabelToScroll("Документ:", margin, y);
 149            y += 22;
 150
 151            cmbDocuments = new ComboBox();
 152            cmbDocuments.Location = new Point(margin, y);
 153            cmbDocuments.Size = new Size(fieldWidth - 40, 28);
 154            cmbDocuments.DropDownStyle = ComboBoxStyle.DropDownList;
 155            cmbDocuments.BackColor = DarkField;
 156            cmbDocuments.ForeColor = TextColor;
 157            cmbDocuments.FlatStyle = FlatStyle.Flat;
 158            cmbDocuments.Font = new Font("Segoe UI", 9f);
 159            cmbDocuments.SelectedIndexChanged += CmbDocuments_SelectedIndexChanged;
 160            scrollPanel.Controls.Add(cmbDocuments);
 161
 162            btnRefresh = new Button();
 163            btnRefresh.Text = "R";
 164            btnRefresh.Size = new Size(32, 28);
 165            btnRefresh.Location = new Point(cmbDocuments.Right + 4, y);
 166            btnRefresh.FlatStyle = FlatStyle.Flat;
 167            btnRefresh.FlatAppearance.BorderColor = BorderColor;
 168            btnRefresh.BackColor = DarkField;
 169            btnRefresh.ForeColor = TextColor;
 170            btnRefresh.Font = new Font("Segoe UI", 9f);
 171            btnRefresh.Cursor = Cursors.Hand;
 172            btnRefresh.TabStop = false;
 173            btnRefresh.Click += (s, e) => LoadDocuments();
 174            scrollPanel.Controls.Add(btnRefresh);
 175            y += 38;
 176
 177            // Document preview
 178            AddLabelToScroll("Превью документа:", margin, y);
 179            y += 22;
 180
 181            var previewPanel = new Panel();
 182            previewPanel.Location = new Point(margin, y);
 183            previewPanel.Size = new Size(fieldWidth, 140);
 184            previewPanel.BackColor = DarkField;
 185            previewPanel.Paint += (s, e) =>
 186            {
 187                using (var pen = new Pen(BorderColor))
 188                    e.Graphics.DrawRectangle(pen, 0, 0, previewPanel.Width - 1, previewPanel.Height - 1);
 189            };
 190            scrollPanel.Controls.Add(previewPanel);
 191
 192            previewBox = new PictureBox();
 193            previewBox.Dock = DockStyle.Fill;
 194            previewBox.SizeMode = PictureBoxSizeMode.Zoom;
 195            previewBox.BackColor = Color.Transparent;
 196            previewPanel.Controls.Add(previewBox);
 197
 198            lblDocName = new Label();
 199            lblDocName.Text = "";
 200            lblDocName.ForeColor = DimTextColor;
 201            lblDocName.Font = new Font("Segoe UI", 8f);
 202            lblDocName.Location = new Point(margin, y + 142);
 203            lblDocName.Size = new Size(fieldWidth, 16);
 204            scrollPanel.Controls.Add(lblDocName);
 205            y += 165;
 206
 207            // Description
 208            AddLabelToScroll("Описание:", margin, y);
 209            y += 22;
 210
 211            txtDescription = new TextBox();
 212            txtDescription.Location = new Point(margin, y);
 213            txtDescription.Size = new Size(fieldWidth, 60);
 214            txtDescription.Multiline = true;
 215            txtDescription.BackColor = DarkField;
 216            txtDescription.ForeColor = TextColor;
 217            txtDescription.BorderStyle = BorderStyle.FixedSingle;
 218            txtDescription.Font = new Font("Segoe UI", 9f);
 219            txtDescription.ScrollBars = ScrollBars.Vertical;
 220            scrollPanel.Controls.Add(txtDescription);
 221            y += 70;
 222
 223            // Caption
 224            AddLabelToScroll("Название рисунка:", margin, y);
 225            y += 22;
 226
 227            txtCaption = new TextBox();
 228            txtCaption.Location = new Point(margin, y);
 229            txtCaption.Size = new Size(fieldWidth, 26);
 230            txtCaption.BackColor = DarkField;
 231            txtCaption.ForeColor = TextColor;
 232            txtCaption.BorderStyle = BorderStyle.FixedSingle;
 233            txtCaption.Font = new Font("Segoe UI", 9f);
 234            scrollPanel.Controls.Add(txtCaption);
 235            y += 34;
 236
 237            // Generate description button (AI)
 238            btnGenerateDescription = new Button();
 239            btnGenerateDescription.Text = "Сгенерировать описание (AI)";
 240            btnGenerateDescription.Location = new Point(margin, y);
 241            btnGenerateDescription.Size = new Size(fieldWidth, 28);
 242            btnGenerateDescription.FlatStyle = FlatStyle.Flat;
 243            btnGenerateDescription.FlatAppearance.BorderColor = BorderColor;
 244            btnGenerateDescription.BackColor = DarkField;
 245            btnGenerateDescription.ForeColor = TextColor;
 246            btnGenerateDescription.Font = new Font("Segoe UI", 9f);
 247            btnGenerateDescription.Cursor = Cursors.Hand;
 248            btnGenerateDescription.TabStop = false;
 249            btnGenerateDescription.Click += BtnGenerateDescription_Click;
 250            scrollPanel.Controls.Add(btnGenerateDescription);
 251            y += 38;
 252
 253            // Status
 254            lblStatus = new Label();
 255            lblStatus.Location = new Point(margin, y);
 256            lblStatus.Size = new Size(fieldWidth, 20);
 257            lblStatus.ForeColor = DimTextColor;
 258            lblStatus.Font = new Font("Segoe UI", 8f);
 259            scrollPanel.Controls.Add(lblStatus);
 260            y += 25;
 261
 262            // Insert button
 263            btnInsert = new Button();
 264            btnInsert.Text = "Вставить (Enter)";
 265            btnInsert.Location = new Point(margin, y);
 266            btnInsert.Size = new Size(fieldWidth, 34);
 267            btnInsert.FlatStyle = FlatStyle.Flat;
 268            btnInsert.FlatAppearance.BorderColor = AccentColor;
 269            btnInsert.BackColor = AccentColor;
 270            btnInsert.ForeColor = TextColor;
 271            btnInsert.Font = new Font("Segoe UI", 10f);
 272            btnInsert.Cursor = Cursors.Hand;
 273            btnInsert.TabStop = false;
 274            btnInsert.Click += BtnInsert_Click;
 275            scrollPanel.Controls.Add(btnInsert);
 276            y += 48;
 277
 278            // Title page section toggle
 279            btnTitlePageToggle = new Button();
 280            btnTitlePageToggle.Text = "Титульный лист ▶";
 281            btnTitlePageToggle.Location = new Point(margin, y);
 282            btnTitlePageToggle.Size = new Size(fieldWidth, 30);
 283            btnTitlePageToggle.FlatStyle = FlatStyle.Flat;
 284            btnTitlePageToggle.FlatAppearance.BorderColor = BorderColor;
 285            btnTitlePageToggle.BackColor = Color.FromArgb(25, 255, 255, 255);
 286            btnTitlePageToggle.ForeColor = TextColor;
 287            btnTitlePageToggle.Font = new Font("Segoe UI", 9f);
 288            btnTitlePageToggle.Cursor = Cursors.Hand;
 289            btnTitlePageToggle.TabStop = false;
 290            btnTitlePageToggle.TextAlign = ContentAlignment.MiddleLeft;
 291            btnTitlePageToggle.Click += BtnTitlePageToggle_Click;
 292            scrollPanel.Controls.Add(btnTitlePageToggle);
 293            y += 35;
 294
 295            // Title page collapsible section
 296            titlePageSection = new Panel();
 297            titlePageSection.Location = new Point(margin, y);
 298            titlePageSection.Size = new Size(fieldWidth, 0); // Start collapsed
 299            titlePageSection.BackColor = Color.FromArgb(20, 255, 255, 255);
 300            titlePageSection.Visible = false;
 301            scrollPanel.Controls.Add(titlePageSection);
 302
 303            InitializeTitlePageSection(fieldWidth, margin);
 304
 305            // Formatting section toggle (positioned after title page section)
 306            btnFormattingToggle = new Button();
 307            btnFormattingToggle.Text = "Форматирование ▶";
 308            btnFormattingToggle.Size = new Size(fieldWidth, 30);
 309            btnFormattingToggle.FlatStyle = FlatStyle.Flat;
 310            btnFormattingToggle.FlatAppearance.BorderColor = BorderColor;
 311            btnFormattingToggle.BackColor = Color.FromArgb(25, 255, 255, 255);
 312            btnFormattingToggle.ForeColor = TextColor;
 313            btnFormattingToggle.Font = new Font("Segoe UI", 9f);
 314            btnFormattingToggle.Cursor = Cursors.Hand;
 315            btnFormattingToggle.TabStop = false;
 316            btnFormattingToggle.TextAlign = ContentAlignment.MiddleLeft;
 317            btnFormattingToggle.Click += BtnFormattingToggle_Click;
 318            scrollPanel.Controls.Add(btnFormattingToggle);
 319
 320            // Formatting collapsible section
 321            formattingSection = new Panel();
 322            formattingSection.Size = new Size(fieldWidth, 0);
 323            formattingSection.BackColor = Color.FromArgb(20, 255, 255, 255);
 324            formattingSection.Visible = false;
 325            scrollPanel.Controls.Add(formattingSection);
 326
 327            InitializeFormattingSection(fieldWidth, margin);
 328
 329            // Gemini section toggle
 330            btnGeminiToggle = new Button();
 331            btnGeminiToggle.Text = "Gemini AI ▶";
 332            btnGeminiToggle.Size = new Size(fieldWidth, 30);
 333            btnGeminiToggle.FlatStyle = FlatStyle.Flat;
 334            btnGeminiToggle.FlatAppearance.BorderColor = BorderColor;
 335            btnGeminiToggle.BackColor = Color.FromArgb(25, 255, 255, 255);
 336            btnGeminiToggle.ForeColor = TextColor;
 337            btnGeminiToggle.Font = new Font("Segoe UI", 9f);
 338            btnGeminiToggle.Cursor = Cursors.Hand;
 339            btnGeminiToggle.TabStop = false;
 340            btnGeminiToggle.TextAlign = ContentAlignment.MiddleLeft;
 341            btnGeminiToggle.Click += BtnGeminiToggle_Click;
 342            scrollPanel.Controls.Add(btnGeminiToggle);
 343
 344            // Gemini collapsible section
 345            geminiSection = new Panel();
 346            geminiSection.Size = new Size(fieldWidth, 0);
 347            geminiSection.BackColor = Color.FromArgb(20, 255, 255, 255);
 348            geminiSection.Visible = false;
 349            scrollPanel.Controls.Add(geminiSection);
 350
 351            InitializeGeminiSection(fieldWidth, margin);
 352
 353            // Update positions based on title page expansion
 354            UpdateSectionPositions(margin);
 355
 356            // Hook mouse wheel from child controls to glass scroll
 357            glassScroll.HookChildWheelEvents(scrollPanel);
 358
 359            // Paint border
 360            Paint += (s, e) =>
 361            {
 362                using (var pen = new Pen(BorderColor))
 363                    e.Graphics.DrawLine(pen, 0, 0, 0, Height);
 364            };
 365
 366            // Handle keys - ignore space until released after panel opens
 367            txtCaption.KeyDown += TextBox_KeyDown;
 368            txtCaption.KeyUp += TextBox_KeyUp;
 369            txtCaption.PreviewKeyDown += TextBox_PreviewKeyDown;
 370
 371            txtDescription.KeyDown += TextBox_KeyDown;
 372            txtDescription.KeyUp += TextBox_KeyUp;
 373            txtDescription.PreviewKeyDown += TextBox_PreviewKeyDown;
 374        }
 375
 376        private void InitializeTitlePageSection(int fieldWidth, int margin)
 377        {
 378            int innerMargin = 8;
 379            int innerWidth = fieldWidth - innerMargin * 2;
 380            int yy = 10;
 381
 382            // Template path
 383            AddLabelToPanel(titlePageSection, "Шаблон:", innerMargin, yy);
 384            yy += 20;
 385
 386            txtTemplatePath = new TextBox();
 387            txtTemplatePath.Location = new Point(innerMargin, yy);
 388            txtTemplatePath.Size = new Size(innerWidth - 35, 24);
 389            txtTemplatePath.BackColor = DarkField;
 390            txtTemplatePath.ForeColor = TextColor;
 391            txtTemplatePath.BorderStyle = BorderStyle.FixedSingle;
 392            txtTemplatePath.Font = new Font("Segoe UI", 8f);
 393            txtTemplatePath.ReadOnly = true;
 394            titlePageSection.Controls.Add(txtTemplatePath);
 395
 396            var btnBrowse = new Button();
 397            btnBrowse.Text = "...";
 398            btnBrowse.Location = new Point(txtTemplatePath.Right + 3, yy);
 399            btnBrowse.Size = new Size(30, 24);
 400            btnBrowse.FlatStyle = FlatStyle.Flat;
 401            btnBrowse.FlatAppearance.BorderColor = BorderColor;
 402            btnBrowse.BackColor = DarkField;
 403            btnBrowse.ForeColor = TextColor;
 404            btnBrowse.TabStop = false;
 405            btnBrowse.Click += BtnBrowseTemplate_Click;
 406            titlePageSection.Controls.Add(btnBrowse);
 407            yy += 32;
 408
 409            // Work number
 410            AddLabelToPanel(titlePageSection, "Номер работы:", innerMargin, yy);
 411            yy += 20;
 412
 413            txtWorkNumber = new TextBox();
 414            txtWorkNumber.Location = new Point(innerMargin, yy);
 415            txtWorkNumber.Size = new Size(60, 24);
 416            txtWorkNumber.BackColor = DarkField;
 417            txtWorkNumber.ForeColor = TextColor;
 418            txtWorkNumber.BorderStyle = BorderStyle.FixedSingle;
 419            txtWorkNumber.Font = new Font("Segoe UI", 9f);
 420            txtWorkNumber.Text = "1";
 421            titlePageSection.Controls.Add(txtWorkNumber);
 422            yy += 32;
 423
 424            // Discipline
 425            yy = AddComboBoxWithAdd(titlePageSection, "Дисциплина:", innerMargin, yy, innerWidth,
 426                out cmbDiscipline, "discipline");
 427
 428            // Topic
 429            yy = AddComboBoxWithAdd(titlePageSection, "Тема:", innerMargin, yy, innerWidth,
 430                out cmbTopic, "topic");
 431
 432            // Student
 433            yy = AddComboBoxWithAdd(titlePageSection, "Студент:", innerMargin, yy, innerWidth,
 434                out cmbStudent, "student");
 435
 436            // Group
 437            yy = AddComboBoxWithAdd(titlePageSection, "Группа:", innerMargin, yy, innerWidth,
 438                out cmbGroup, "group");
 439
 440            // Teacher
 441            yy = AddComboBoxWithAdd(titlePageSection, "Преподаватель:", innerMargin, yy, innerWidth,
 442                out cmbTeacher, "teacher");
 443
 444            // Insert title page button
 445            btnInsertTitlePage = new Button();
 446            btnInsertTitlePage.Text = "Вставить титульный лист";
 447            btnInsertTitlePage.Location = new Point(innerMargin, yy);
 448            btnInsertTitlePage.Size = new Size(innerWidth, 30);
 449            btnInsertTitlePage.FlatStyle = FlatStyle.Flat;
 450            btnInsertTitlePage.FlatAppearance.BorderColor = SuccessColor;
 451            btnInsertTitlePage.BackColor = SuccessColor;
 452            btnInsertTitlePage.ForeColor = TextColor;
 453            btnInsertTitlePage.Font = new Font("Segoe UI", 9f);
 454            btnInsertTitlePage.Cursor = Cursors.Hand;
 455            btnInsertTitlePage.TabStop = false;
 456            btnInsertTitlePage.Click += BtnInsertTitlePage_Click;
 457            titlePageSection.Controls.Add(btnInsertTitlePage);
 458            yy += 40;
 459
 460            // Store total height
 461            titlePageSection.Tag = yy; // Store expanded height
 462        }
 463
 464        private ListBox lstApiKeys;
 465        private Button btnAddApiKey;
 466        private Button btnRemoveApiKey;
 467
 468        private void InitializeGeminiSection(int fieldWidth, int margin)
 469        {
 470            int innerMargin = 8;
 471            int innerWidth = fieldWidth - innerMargin * 2;
 472            int yy = 10;
 473            var settings = TitlePageData.Instance.Gemini;
 474
 475            // API Keys label with add button
 476            AddLabelToPanel(geminiSection, "API ключи:", innerMargin, yy);
 477
 478            // Add key button
 479            btnAddApiKey = new Button();
 480            btnAddApiKey.Text = "+";
 481            btnAddApiKey.Location = new Point(innerMargin + 70, yy - 3);
 482            btnAddApiKey.Size = new Size(24, 20);
 483            btnAddApiKey.FlatStyle = FlatStyle.Flat;
 484            btnAddApiKey.FlatAppearance.BorderColor = BorderColor;
 485            btnAddApiKey.BackColor = DarkField;
 486            btnAddApiKey.ForeColor = TextColor;
 487            btnAddApiKey.Font = new Font("Segoe UI", 8f);
 488            btnAddApiKey.Click += BtnAddApiKey_Click;
 489            geminiSection.Controls.Add(btnAddApiKey);
 490
 491            // Remove key button
 492            btnRemoveApiKey = new Button();
 493            btnRemoveApiKey.Text = "-";
 494            btnRemoveApiKey.Location = new Point(innerMargin + 96, yy - 3);
 495            btnRemoveApiKey.Size = new Size(24, 20);
 496            btnRemoveApiKey.FlatStyle = FlatStyle.Flat;
 497            btnRemoveApiKey.FlatAppearance.BorderColor = BorderColor;
 498            btnRemoveApiKey.BackColor = DarkField;
 499            btnRemoveApiKey.ForeColor = TextColor;
 500            btnRemoveApiKey.Font = new Font("Segoe UI", 8f);
 501            btnRemoveApiKey.Click += BtnRemoveApiKey_Click;
 502            geminiSection.Controls.Add(btnRemoveApiKey);
 503            yy += 20;
 504
 505            // API Keys list
 506            lstApiKeys = new ListBox();
 507            lstApiKeys.Location = new Point(innerMargin, yy);
 508            lstApiKeys.Size = new Size(innerWidth, 50);
 509            lstApiKeys.BackColor = DarkField;
 510            lstApiKeys.ForeColor = TextColor;
 511            lstApiKeys.BorderStyle = BorderStyle.FixedSingle;
 512            lstApiKeys.Font = new Font("Segoe UI", 8f);
 513            lstApiKeys.DrawMode = DrawMode.OwnerDrawFixed;
 514            lstApiKeys.ItemHeight = 16;
 515            lstApiKeys.DrawItem += LstApiKeys_DrawItem;
 516            lstApiKeys.SelectedIndexChanged += LstApiKeys_SelectedIndexChanged;
 517            RefreshApiKeysList();
 518            geminiSection.Controls.Add(lstApiKeys);
 519            yy += 55;
 520
 521            // New API key input
 522            txtGeminiApiKey = new TextBox();
 523            txtGeminiApiKey.Location = new Point(innerMargin, yy);
 524            txtGeminiApiKey.Size = new Size(innerWidth, 24);
 525            txtGeminiApiKey.BackColor = DarkField;
 526            txtGeminiApiKey.ForeColor = TextColor;
 527            txtGeminiApiKey.BorderStyle = BorderStyle.FixedSingle;
 528            txtGeminiApiKey.Font = new Font("Segoe UI", 8f);
 529            txtGeminiApiKey.Text = "";
 530            // Placeholder text
 531            txtGeminiApiKey.GotFocus += (s, e) => { if (txtGeminiApiKey.Text == "Новый ключ...") { txtGeminiApiKey.Text = ""; txtGeminiApiKey.ForeColor = TextColor; } };
 532            txtGeminiApiKey.LostFocus += (s, e) => { if (string.IsNullOrEmpty(txtGeminiApiKey.Text)) { txtGeminiApiKey.Text = "Новый ключ..."; txtGeminiApiKey.ForeColor = DimTextColor; } };
 533            txtGeminiApiKey.Text = "Новый ключ...";
 534            txtGeminiApiKey.ForeColor = DimTextColor;
 535            geminiSection.Controls.Add(txtGeminiApiKey);
 536            yy += 30;
 537
 538            // Model selection with status colors
 539            AddLabelToPanel(geminiSection, "Модель:", innerMargin, yy);
 540
 541            // Reset button
 542            btnResetModelStatuses = new Button();
 543            btnResetModelStatuses.Text = "Сброс";
 544            btnResetModelStatuses.Location = new Point(innerMargin + 50, yy - 3);
 545            btnResetModelStatuses.Size = new Size(50, 20);
 546            btnResetModelStatuses.FlatStyle = FlatStyle.Flat;
 547            btnResetModelStatuses.FlatAppearance.BorderColor = BorderColor;
 548            btnResetModelStatuses.BackColor = DarkField;
 549            btnResetModelStatuses.ForeColor = TextColor;
 550            btnResetModelStatuses.Font = new Font("Segoe UI", 7f);
 551            btnResetModelStatuses.Click += BtnResetModelStatuses_Click;
 552            geminiSection.Controls.Add(btnResetModelStatuses);
 553            yy += 20;
 554
 555            // ListBox for models with colored status indicators
 556            lstGeminiModels = new ListBox();
 557            lstGeminiModels.Location = new Point(innerMargin, yy);
 558            lstGeminiModels.Size = new Size(innerWidth, 120);
 559            lstGeminiModels.BackColor = DarkField;
 560            lstGeminiModels.ForeColor = TextColor;
 561            lstGeminiModels.BorderStyle = BorderStyle.FixedSingle;
 562            lstGeminiModels.Font = new Font("Segoe UI", 8f);
 563            lstGeminiModels.DrawMode = DrawMode.OwnerDrawFixed;
 564            lstGeminiModels.ItemHeight = 18;
 565            lstGeminiModels.DrawItem += LstGeminiModels_DrawItem;
 566            lstGeminiModels.SelectedIndexChanged += LstGeminiModels_SelectedIndexChanged;
 567            // Add fallback models
 568            foreach (var model in GeminiIntegration.FallbackModels)
 569            {
 570                lstGeminiModels.Items.Add(model);
 571            }
 572            // Select current model
 573            int idx = lstGeminiModels.Items.IndexOf(settings.Model);
 574            if (idx >= 0) lstGeminiModels.SelectedIndex = idx;
 575            geminiSection.Controls.Add(lstGeminiModels);
 576            yy += 125;
 577
 578            // Thinking Budget
 579            AddLabelToPanel(geminiSection, "Thinking Budget:", innerMargin, yy);
 580            txtGeminiThinkingBudget = new TextBox();
 581            txtGeminiThinkingBudget.Location = new Point(innerMargin + 100, yy - 2);
 582            txtGeminiThinkingBudget.Size = new Size(50, 24);
 583            txtGeminiThinkingBudget.BackColor = DarkField;
 584            txtGeminiThinkingBudget.ForeColor = TextColor;
 585            txtGeminiThinkingBudget.BorderStyle = BorderStyle.FixedSingle;
 586            txtGeminiThinkingBudget.Font = new Font("Segoe UI", 8f);
 587            txtGeminiThinkingBudget.Text = settings.ThinkingBudget.ToString();
 588            txtGeminiThinkingBudget.Leave += (s, e) => {
 589                int val;
 590                if (int.TryParse(txtGeminiThinkingBudget.Text, out val))
 591                {
 592                    settings.ThinkingBudget = val;
 593                    TitlePageData.Instance.Save();
 594                }
 595            };
 596            geminiSection.Controls.Add(txtGeminiThinkingBudget);
 597            yy += 28;
 598
 599            // Media Resolution
 600            AddLabelToPanel(geminiSection, "Media Resolution:", innerMargin, yy);
 601            yy += 20;
 602
 603            cmbGeminiMediaResolution = new ComboBox();
 604            cmbGeminiMediaResolution.Location = new Point(innerMargin, yy);
 605            cmbGeminiMediaResolution.Size = new Size(innerWidth, 24);
 606            cmbGeminiMediaResolution.BackColor = DarkField;
 607            cmbGeminiMediaResolution.ForeColor = TextColor;
 608            cmbGeminiMediaResolution.FlatStyle = FlatStyle.Flat;
 609            cmbGeminiMediaResolution.Font = new Font("Segoe UI", 8f);
 610            cmbGeminiMediaResolution.DropDownStyle = ComboBoxStyle.DropDownList;
 611            cmbGeminiMediaResolution.Items.Add("MEDIA_RESOLUTION_LOW");
 612            cmbGeminiMediaResolution.Items.Add("MEDIA_RESOLUTION_MEDIUM");
 613            cmbGeminiMediaResolution.Items.Add("MEDIA_RESOLUTION_HIGH");
 614            string mediaRes = settings.MediaResolution ?? "MEDIA_RESOLUTION_LOW";
 615            int resIdx = cmbGeminiMediaResolution.Items.IndexOf(mediaRes);
 616            cmbGeminiMediaResolution.SelectedIndex = resIdx >= 0 ? resIdx : 0;
 617            cmbGeminiMediaResolution.SelectedIndexChanged += (s, e) => {
 618                settings.MediaResolution = cmbGeminiMediaResolution.SelectedItem.ToString();
 619                TitlePageData.Instance.Save();
 620            };
 621            geminiSection.Controls.Add(cmbGeminiMediaResolution);
 622            yy += 30;
 623
 624            // System Instruction
 625            AddLabelToPanel(geminiSection, "Системная инструкция:", innerMargin, yy);
 626            yy += 20;
 627
 628            txtGeminiInstruction = new TextBox();
 629            txtGeminiInstruction.Location = new Point(innerMargin, yy);
 630            txtGeminiInstruction.Size = new Size(innerWidth, 120);
 631            txtGeminiInstruction.BackColor = DarkField;
 632            txtGeminiInstruction.ForeColor = TextColor;
 633            txtGeminiInstruction.BorderStyle = BorderStyle.FixedSingle;
 634            txtGeminiInstruction.Font = new Font("Segoe UI", 8f);
 635            txtGeminiInstruction.Multiline = true;
 636            txtGeminiInstruction.ScrollBars = ScrollBars.Vertical;
 637            txtGeminiInstruction.Text = settings.SystemInstruction;
 638            txtGeminiInstruction.TextChanged += (s, e) => { TitlePageData.Instance.Gemini.SystemInstruction = txtGeminiInstruction.Text; TitlePageData.Instance.Save(); };
 639            geminiSection.Controls.Add(txtGeminiInstruction);
 640            yy += 130;
 641
 642            // Export/Import buttons
 643            var btnExport = new Button();
 644            btnExport.Text = "Экспорт";
 645            btnExport.Location = new Point(innerMargin, yy);
 646            btnExport.Size = new Size((innerWidth - 5) / 2, 26);
 647            btnExport.FlatStyle = FlatStyle.Flat;
 648            btnExport.FlatAppearance.BorderColor = BorderColor;
 649            btnExport.BackColor = DarkField;
 650            btnExport.ForeColor = TextColor;
 651            btnExport.Font = new Font("Segoe UI", 8f);
 652            btnExport.Cursor = Cursors.Hand;
 653            btnExport.Click += BtnExportSettings_Click;
 654            geminiSection.Controls.Add(btnExport);
 655
 656            var btnImport = new Button();
 657            btnImport.Text = "Импорт";
 658            btnImport.Location = new Point(btnExport.Right + 5, yy);
 659            btnImport.Size = new Size((innerWidth - 5) / 2, 26);
 660            btnImport.FlatStyle = FlatStyle.Flat;
 661            btnImport.FlatAppearance.BorderColor = BorderColor;
 662            btnImport.BackColor = DarkField;
 663            btnImport.ForeColor = TextColor;
 664            btnImport.Font = new Font("Segoe UI", 8f);
 665            btnImport.Cursor = Cursors.Hand;
 666            btnImport.Click += BtnImportSettings_Click;
 667            geminiSection.Controls.Add(btnImport);
 668            yy += 32;
 669
 670            // Store total height
 671            geminiSection.Tag = yy;
 672        }
 673
 674        private void BtnGeminiToggle_Click(object sender, EventArgs e)
 675        {
 676            geminiExpanded = !geminiExpanded;
 677
 678            if (geminiExpanded)
 679            {
 680                btnGeminiToggle.Text = "Gemini AI ▼";
 681                geminiSection.Visible = true;
 682                int expandedHeight = (int)geminiSection.Tag;
 683                geminiSection.Size = new Size(geminiSection.Width, expandedHeight);
 684                // Refresh model list colors
 685                if (lstGeminiModels != null)
 686                    lstGeminiModels.Invalidate();
 687            }
 688            else
 689            {
 690                btnGeminiToggle.Text = "Gemini AI ▶";
 691                geminiSection.Size = new Size(geminiSection.Width, 0);
 692                geminiSection.Visible = false;
 693            }
 694
 695            UpdateSectionPositions(12); // margin
 696        }
 697
 698        private void LstGeminiModels_DrawItem(object sender, DrawItemEventArgs e)
 699        {
 700            if (e.Index < 0) return;
 701
 702            var listBox = (ListBox)sender;
 703            string model = listBox.Items[e.Index].ToString();
 704            var settings = TitlePageData.Instance.Gemini;
 705            var status = settings.GetModelStatus(model);
 706
 707            // Background
 708            e.DrawBackground();
 709
 710            // Status indicator color
 711            Color statusColor;
 712            switch (status)
 713            {
 714                case ModelStatusType.Working:
 715                    statusColor = Color.FromArgb(50, 200, 50); // Green
 716                    break;
 717                case ModelStatusType.RateLimited:
 718                    statusColor = Color.FromArgb(220, 180, 50); // Yellow
 719                    break;
 720                case ModelStatusType.Broken:
 721                    statusColor = Color.FromArgb(200, 50, 50); // Red
 722                    break;
 723                default: // Unknown
 724                    statusColor = Color.FromArgb(100, 100, 100); // Gray
 725                    break;
 726            }
 727
 728            // Draw status circle
 729            using (var brush = new SolidBrush(statusColor))
 730            {
 731                e.Graphics.FillEllipse(brush, e.Bounds.X + 4, e.Bounds.Y + 4, 10, 10);
 732            }
 733
 734            // Draw text
 735            Color textColor = (e.State & DrawItemState.Selected) != 0 ? Color.White : TextColor;
 736            if (status == ModelStatusType.Broken)
 737                textColor = Color.FromArgb(180, 100, 100); // Dimmed for broken models
 738
 739            using (var brush = new SolidBrush(textColor))
 740            {
 741                e.Graphics.DrawString(model, e.Font, brush, e.Bounds.X + 18, e.Bounds.Y + 2);
 742            }
 743
 744            // Focus rectangle
 745            e.DrawFocusRectangle();
 746        }
 747
 748        private void LstGeminiModels_SelectedIndexChanged(object sender, EventArgs e)
 749        {
 750            if (lstGeminiModels.SelectedIndex < 0) return;
 751            string model = lstGeminiModels.SelectedItem.ToString();
 752            TitlePageData.Instance.Gemini.Model = model;
 753            TitlePageData.Instance.Save();
 754        }
 755
 756        private void BtnResetModelStatuses_Click(object sender, EventArgs e)
 757        {
 758            TitlePageData.Instance.Gemini.ResetModelStatuses();
 759            TitlePageData.Instance.Save();
 760            lstGeminiModels.Invalidate(); // Refresh colors
 761            App.TrayApp.Instance.ShowNotification("Статусы сброшены", "Все модели снова доступны", ToolTipIcon.Info);
 762        }
 763
 764        private void RefreshApiKeysList()
 765        {
 766            lstApiKeys.Items.Clear();
 767            var settings = TitlePageData.Instance.Gemini;
 768
 769            // Migrate legacy key if needed
 770            if ((settings.ApiKeys == null || settings.ApiKeys.Count == 0) && !string.IsNullOrEmpty(TitlePageData.Instance.GeminiApiKey))
 771            {
 772                settings.AddApiKey(TitlePageData.Instance.GeminiApiKey);
 773                TitlePageData.Instance.Save();
 774            }
 775
 776            if (settings.ApiKeys != null)
 777            {
 778                foreach (var key in settings.ApiKeys)
 779                {
 780                    lstApiKeys.Items.Add(key);
 781                }
 782            }
 783
 784            // Select current key
 785            if (settings.CurrentApiKeyIndex >= 0 && settings.CurrentApiKeyIndex < lstApiKeys.Items.Count)
 786                lstApiKeys.SelectedIndex = settings.CurrentApiKeyIndex;
 787            else if (lstApiKeys.Items.Count > 0)
 788                lstApiKeys.SelectedIndex = 0;
 789        }
 790
 791        private void LstApiKeys_DrawItem(object sender, DrawItemEventArgs e)
 792        {
 793            if (e.Index < 0) return;
 794
 795            var listBox = (ListBox)sender;
 796            string key = listBox.Items[e.Index].ToString();
 797            var settings = TitlePageData.Instance.Gemini;
 798
 799            e.DrawBackground();
 800
 801            // Current key indicator
 802            bool isCurrent = e.Index == settings.CurrentApiKeyIndex;
 803            string displayText = MaskApiKey(key);
 804            if (isCurrent)
 805                displayText = "► " + displayText;
 806
 807            Color textColor = (e.State & DrawItemState.Selected) != 0 ? Color.White : TextColor;
 808            using (var brush = new SolidBrush(textColor))
 809            {
 810                e.Graphics.DrawString(displayText, e.Font, brush, e.Bounds.X + 2, e.Bounds.Y + 1);
 811            }
 812
 813            e.DrawFocusRectangle();
 814        }
 815
 816        private string MaskApiKey(string key)
 817        {
 818            if (string.IsNullOrEmpty(key) || key.Length < 8)
 819                return "****";
 820            return key.Substring(0, 4) + "..." + key.Substring(key.Length - 4);
 821        }
 822
 823        private void LstApiKeys_SelectedIndexChanged(object sender, EventArgs e)
 824        {
 825            if (lstApiKeys.SelectedIndex < 0) return;
 826            var settings = TitlePageData.Instance.Gemini;
 827            settings.CurrentApiKeyIndex = lstApiKeys.SelectedIndex;
 828            TitlePageData.Instance.Save();
 829            lstApiKeys.Invalidate(); // Refresh to show current indicator
 830        }
 831
 832        private void BtnAddApiKey_Click(object sender, EventArgs e)
 833        {
 834            string key = txtGeminiApiKey.Text.Trim();
 835            if (string.IsNullOrEmpty(key) || key == "Новый ключ...")
 836            {
 837                App.TrayApp.Instance.ShowNotification("Ошибка", "Введите API ключ", ToolTipIcon.Warning);
 838                return;
 839            }
 840
 841            var settings = TitlePageData.Instance.Gemini;
 842            if (settings.AddApiKey(key))
 843            {
 844                TitlePageData.Instance.Save();
 845                RefreshApiKeysList();
 846                txtGeminiApiKey.Text = "Новый ключ...";
 847                txtGeminiApiKey.ForeColor = DimTextColor;
 848                App.TrayApp.Instance.ShowNotification("Ключ добавлен", "API ключ сохранён", ToolTipIcon.Info);
 849            }
 850            else
 851            {
 852                App.TrayApp.Instance.ShowNotification("Ошибка", "Ключ уже существует", ToolTipIcon.Warning);
 853            }
 854        }
 855
 856        private void BtnRemoveApiKey_Click(object sender, EventArgs e)
 857        {
 858            if (lstApiKeys.SelectedIndex < 0) return;
 859
 860            var settings = TitlePageData.Instance.Gemini;
 861            if (settings.ApiKeys != null && settings.ApiKeys.Count > 0)
 862            {
 863                settings.ApiKeys.RemoveAt(lstApiKeys.SelectedIndex);
 864                if (settings.CurrentApiKeyIndex >= settings.ApiKeys.Count)
 865                    settings.CurrentApiKeyIndex = Math.Max(0, settings.ApiKeys.Count - 1);
 866                TitlePageData.Instance.Save();
 867                RefreshApiKeysList();
 868            }
 869        }
 870
 871        private void BtnExportSettings_Click(object sender, EventArgs e)
 872        {
 873            using (var sfd = new SaveFileDialog())
 874            {
 875                sfd.Filter = "JSON файл|*.json";
 876                sfd.Title = "Экспорт настроек";
 877                sfd.FileName = "windowcapture_settings.json";
 878
 879                if (sfd.ShowDialog() == DialogResult.OK)
 880                {
 881                    if (TitlePageData.Instance.ExportSettings(sfd.FileName))
 882                    {
 883                        App.TrayApp.Instance.ShowNotification("Экспорт", "Настройки сохранены (без API ключей)", ToolTipIcon.Info);
 884                    }
 885                    else
 886                    {
 887                        App.TrayApp.Instance.ShowNotification("Ошибка", "Не удалось экспортировать настройки", ToolTipIcon.Error);
 888                    }
 889                }
 890            }
 891        }
 892
 893        private void BtnImportSettings_Click(object sender, EventArgs e)
 894        {
 895            using (var ofd = new OpenFileDialog())
 896            {
 897                ofd.Filter = "JSON файл|*.json";
 898                ofd.Title = "Импорт настроек";
 899
 900                if (ofd.ShowDialog() == DialogResult.OK)
 901                {
 902                    if (TitlePageData.Instance.ImportSettings(ofd.FileName))
 903                    {
 904                        App.TrayApp.Instance.ShowNotification("Импорт", "Настройки загружены", ToolTipIcon.Info);
 905                        // Refresh UI
 906                        RefreshSettingsUI();
 907                    }
 908                    else
 909                    {
 910                        App.TrayApp.Instance.ShowNotification("Ошибка", "Не удалось импортировать настройки", ToolTipIcon.Error);
 911                    }
 912                }
 913            }
 914        }
 915
 916        private void RefreshSettingsUI()
 917        {
 918            var settings = TitlePageData.Instance.Gemini;
 919
 920            // Refresh model list
 921            if (lstGeminiModels != null)
 922            {
 923                int idx = lstGeminiModels.Items.IndexOf(settings.Model);
 924                if (idx >= 0) lstGeminiModels.SelectedIndex = idx;
 925                lstGeminiModels.Invalidate();
 926            }
 927
 928            // Refresh thinking budget
 929            if (txtGeminiThinkingBudget != null)
 930                txtGeminiThinkingBudget.Text = settings.ThinkingBudget.ToString();
 931
 932            // Refresh media resolution
 933            if (cmbGeminiMediaResolution != null)
 934            {
 935                int resIdx = cmbGeminiMediaResolution.Items.IndexOf(settings.MediaResolution ?? "MEDIA_RESOLUTION_LOW");
 936                if (resIdx >= 0) cmbGeminiMediaResolution.SelectedIndex = resIdx;
 937            }
 938
 939            // Refresh system instruction
 940            if (txtGeminiInstruction != null)
 941                txtGeminiInstruction.Text = settings.SystemInstruction;
 942
 943            // Refresh title page data
 944            LoadTitlePageData();
 945        }
 946
 947        private void InitializeFormattingSection(int fieldWidth, int margin)
 948        {
 949            int innerMargin = 8;
 950            int innerWidth = fieldWidth - innerMargin * 2;
 951            int yy = 10;
 952
 953            // Page numbering button
 954            btnAddPageNumbering = new Button();
 955            btnAddPageNumbering.Text = "Добавить нумерацию страниц";
 956            btnAddPageNumbering.Location = new Point(innerMargin, yy);
 957            btnAddPageNumbering.Size = new Size(innerWidth, 28);
 958            btnAddPageNumbering.FlatStyle = FlatStyle.Flat;
 959            btnAddPageNumbering.FlatAppearance.BorderColor = AccentColor;
 960            btnAddPageNumbering.BackColor = DarkField;
 961            btnAddPageNumbering.ForeColor = TextColor;
 962            btnAddPageNumbering.Font = new Font("Segoe UI", 9f);
 963            btnAddPageNumbering.Cursor = Cursors.Hand;
 964            btnAddPageNumbering.TabStop = false;
 965            btnAddPageNumbering.Click += BtnAddPageNumbering_Click;
 966            formattingSection.Controls.Add(btnAddPageNumbering);
 967            yy += 32;
 968
 969            // Description label
 970            var lblDesc = new Label();
 971            lblDesc.Text = "(Первая страница без номера,\n со 2-й: снизу по центру, TNR 14)";
 972            lblDesc.Location = new Point(innerMargin, yy);
 973            lblDesc.Size = new Size(innerWidth, 35);
 974            lblDesc.ForeColor = DimTextColor;
 975            lblDesc.Font = new Font("Segoe UI", 8f);
 976            formattingSection.Controls.Add(lblDesc);
 977            yy += 42;
 978
 979            // Originalize button
 980            btnOriginalize = new Button();
 981            btnOriginalize.Text = "Оригинализировать описания";
 982            btnOriginalize.Location = new Point(innerMargin, yy);
 983            btnOriginalize.Size = new Size(innerWidth, 28);
 984            btnOriginalize.FlatStyle = FlatStyle.Flat;
 985            btnOriginalize.FlatAppearance.BorderColor = AccentColor;
 986            btnOriginalize.BackColor = DarkField;
 987            btnOriginalize.ForeColor = TextColor;
 988            btnOriginalize.Font = new Font("Segoe UI", 9f);
 989            btnOriginalize.Cursor = Cursors.Hand;
 990            btnOriginalize.TabStop = false;
 991            btnOriginalize.Click += BtnOriginalize_Click;
 992            formattingSection.Controls.Add(btnOriginalize);
 993            yy += 30;
 994
 995            var lblOrigDesc = new Label();
 996            lblOrigDesc.Text = "(Перефразирует описания и\n названия рисунков через AI)";
 997            lblOrigDesc.Location = new Point(innerMargin, yy);
 998            lblOrigDesc.Size = new Size(innerWidth, 35);
 999            lblOrigDesc.ForeColor = DimTextColor;
1000            lblOrigDesc.Font = new Font("Segoe UI", 8f);
1001            formattingSection.Controls.Add(lblOrigDesc);
1002            yy += 40;
1003
1004            // Store total height
1005            formattingSection.Tag = yy;
1006        }
1007
1008        private void BtnAddPageNumbering_Click(object sender, EventArgs e)
1009        {
1010            if (cmbDocuments.SelectedItem == null)
1011            {
1012                lblStatus.Text = "Выберите документ";
1013                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1014                return;
1015            }
1016
1017            var doc = cmbDocuments.SelectedItem as WordDocumentInfo;
1018            if (doc == null) return;
1019
1020            WordIntegration.SelectedDocument = doc;
1021
1022            lblStatus.Text = "Добавление нумерации...";
1023            lblStatus.ForeColor = DimTextColor;
1024            btnAddPageNumbering.Enabled = false;
1025            Application.DoEvents();
1026
1027            bool result = WordIntegration.AddPageNumbering();
1028
1029            if (result)
1030            {
1031                lblStatus.Text = "Нумерация добавлена!";
1032                lblStatus.ForeColor = SuccessColor;
1033            }
1034            else
1035            {
1036                lblStatus.Text = "Ошибка нумерации";
1037                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1038            }
1039
1040            btnAddPageNumbering.Enabled = true;
1041        }
1042
1043        private void UpdateSectionPositions(int margin)
1044        {
1045            // Calculate Y position after title page section
1046            int y = btnTitlePageToggle.Bottom + 5;
1047
1048            if (titlePageExpanded)
1049            {
1050                titlePageSection.Location = new Point(margin, y);
1051                y += (int)titlePageSection.Tag;
1052            }
1053
1054            // Position formatting toggle
1055            btnFormattingToggle.Location = new Point(margin, y);
1056            y += btnFormattingToggle.Height + 5;
1057
1058            if (formattingExpanded)
1059            {
1060                formattingSection.Location = new Point(margin, y);
1061                y += (int)formattingSection.Tag;
1062            }
1063
1064            // Position Gemini toggle
1065            btnGeminiToggle.Location = new Point(margin, y);
1066            y += btnGeminiToggle.Height + 5;
1067
1068            if (geminiExpanded)
1069            {
1070                geminiSection.Location = new Point(margin, y);
1071            }
1072
1073            // Refresh glass scroll after layout changes
1074            if (glassScroll != null) glassScroll.SetupScroll();
1075        }
1076
1077        private int AddComboBoxWithAdd(Panel parent, string labelText, int x, int y, int width,
1078            out ComboBox combo, string fieldName)
1079        {
1080            AddLabelToPanel(parent, labelText, x, y);
1081            y += 20;
1082
1083            combo = new ComboBox();
1084            combo.Location = new Point(x, y);
1085            combo.Size = new Size(width - 35, 24);
1086            combo.DropDownStyle = ComboBoxStyle.DropDown; // Allow editing
1087            combo.BackColor = DarkField;
1088            combo.ForeColor = TextColor;
1089            combo.FlatStyle = FlatStyle.Flat;
1090            combo.Font = new Font("Segoe UI", 9f);
1091            combo.Tag = fieldName; // Store field name for lookup
1092            // Auto-save when leaving the combo (on lost focus or selection change)
1093            combo.Leave += ComboBox_Leave;
1094            combo.SelectedIndexChanged += ComboBox_SelectedIndexChanged;
1095            parent.Controls.Add(combo);
1096
1097            var btnAdd = new Button();
1098            btnAdd.Text = "+";
1099            btnAdd.Location = new Point(combo.Right + 3, y);
1100            btnAdd.Size = new Size(30, 24);
1101            btnAdd.FlatStyle = FlatStyle.Flat;
1102            btnAdd.FlatAppearance.BorderColor = BorderColor;
1103            btnAdd.BackColor = DarkField;
1104            btnAdd.ForeColor = TextColor;
1105            btnAdd.TabStop = false;
1106            btnAdd.Tag = combo; // Reference to associated combo
1107            btnAdd.Click += BtnAddToList_Click;
1108            parent.Controls.Add(btnAdd);
1109
1110            return y + 32;
1111        }
1112
1113        private void ComboBox_Leave(object sender, EventArgs e)
1114        {
1115            // Auto-save value when leaving the combo box
1116            var combo = sender as ComboBox;
1117            if (combo == null) return;
1118
1119            string value = combo.Text.Trim();
1120            if (string.IsNullOrEmpty(value)) return;
1121
1122            string fieldName = combo.Tag as string;
1123            if (string.IsNullOrEmpty(fieldName)) return;
1124
1125            var data = TitlePageData.Instance;
1126            if (data.AddToList(fieldName, value))
1127            {
1128                // Refresh combo items to include the new value
1129                var list = data.GetList(fieldName);
1130                if (list != null)
1131                {
1132                    int idx = list.IndexOf(value);
1133                    LoadComboItems(combo, list, idx);
1134                }
1135            }
1136        }
1137
1138        private void ComboBox_SelectedIndexChanged(object sender, EventArgs e)
1139        {
1140            // Save selected index when changed
1141            var combo = sender as ComboBox;
1142            if (combo == null || combo.SelectedIndex < 0) return;
1143
1144            string fieldName = combo.Tag as string;
1145            if (string.IsNullOrEmpty(fieldName)) return;
1146
1147            var data = TitlePageData.Instance;
1148            data.SetLastSelectedIndex(fieldName, combo.SelectedIndex);
1149        }
1150
1151        private void BtnFormattingToggle_Click(object sender, EventArgs e)
1152        {
1153            formattingExpanded = !formattingExpanded;
1154
1155            if (formattingExpanded)
1156            {
1157                btnFormattingToggle.Text = "Форматирование ▼";
1158                formattingSection.Visible = true;
1159                int expandedHeight = (int)formattingSection.Tag;
1160                formattingSection.Size = new Size(formattingSection.Width, expandedHeight);
1161            }
1162            else
1163            {
1164                btnFormattingToggle.Text = "Форматирование ▶";
1165                formattingSection.Size = new Size(formattingSection.Width, 0);
1166                formattingSection.Visible = false;
1167            }
1168
1169            UpdateSectionPositions(12); // margin
1170        }
1171
1172        private void BtnTitlePageToggle_Click(object sender, EventArgs e)
1173        {
1174            titlePageExpanded = !titlePageExpanded;
1175
1176            if (titlePageExpanded)
1177            {
1178                btnTitlePageToggle.Text = "Титульный лист ▼";
1179                titlePageSection.Visible = true;
1180                int expandedHeight = (int)titlePageSection.Tag;
1181                titlePageSection.Size = new Size(titlePageSection.Width, expandedHeight);
1182                LoadTitlePageData();
1183            }
1184            else
1185            {
1186                btnTitlePageToggle.Text = "Титульный лист ▶";
1187                titlePageSection.Size = new Size(titlePageSection.Width, 0);
1188                titlePageSection.Visible = false;
1189            }
1190
1191            UpdateSectionPositions(12); // margin
1192        }
1193
1194        private void LoadTitlePageData()
1195        {
1196            var data = TitlePageData.Instance;
1197
1198            txtTemplatePath.Text = data.TemplatePath ?? "";
1199            txtWorkNumber.Text = data.LastWorkNumber ?? "1";
1200
1201            LoadComboItems(cmbDiscipline, data.Disciplines, data.LastSelected.Discipline);
1202            LoadComboItems(cmbTopic, data.Topics, data.LastSelected.Topic);
1203            LoadComboItems(cmbStudent, data.Students, data.LastSelected.Student);
1204            LoadComboItems(cmbGroup, data.Groups, data.LastSelected.Group);
1205            LoadComboItems(cmbTeacher, data.Teachers, data.LastSelected.Teacher);
1206        }
1207
1208        private void LoadComboItems(ComboBox combo, List<string> items, int selectedIndex)
1209        {
1210            combo.Items.Clear();
1211            if (items != null)
1212            {
1213                foreach (var item in items)
1214                    combo.Items.Add(item);
1215            }
1216
1217            if (selectedIndex >= 0 && selectedIndex < combo.Items.Count)
1218                combo.SelectedIndex = selectedIndex;
1219            else if (combo.Items.Count > 0)
1220                combo.SelectedIndex = 0;
1221        }
1222
1223        private void BtnBrowseTemplate_Click(object sender, EventArgs e)
1224        {
1225            using (var ofd = new OpenFileDialog())
1226            {
1227                ofd.Filter = "Word Documents|*.docx;*.doc|All Files|*.*";
1228                ofd.Title = "Выберите шаблон титульного листа";
1229
1230                if (ofd.ShowDialog() == DialogResult.OK)
1231                {
1232                    txtTemplatePath.Text = ofd.FileName;
1233                    var data = TitlePageData.Instance;
1234                    data.TemplatePath = ofd.FileName;
1235                    data.Save();
1236                }
1237            }
1238        }
1239
1240        private void BtnAddToList_Click(object sender, EventArgs e)
1241        {
1242            var btn = sender as Button;
1243            if (btn == null) return;
1244
1245            var combo = btn.Tag as ComboBox;
1246            if (combo == null) return;
1247
1248            string value = combo.Text.Trim();
1249            if (string.IsNullOrEmpty(value)) return;
1250
1251            string fieldName = combo.Tag as string;
1252            if (string.IsNullOrEmpty(fieldName)) return;
1253
1254            var data = TitlePageData.Instance;
1255            if (data.AddToList(fieldName, value))
1256            {
1257                // Refresh combo items
1258                var list = data.GetList(fieldName);
1259                if (list != null)
1260                {
1261                    int idx = list.IndexOf(value);
1262                    LoadComboItems(combo, list, idx);
1263                }
1264            }
1265        }
1266
1267        private void BtnInsertTitlePage_Click(object sender, EventArgs e)
1268        {
1269            if (cmbDocuments.SelectedItem == null)
1270            {
1271                lblStatus.Text = "Выберите документ";
1272                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1273                return;
1274            }
1275
1276            if (string.IsNullOrEmpty(txtTemplatePath.Text) || !System.IO.File.Exists(txtTemplatePath.Text))
1277            {
1278                lblStatus.Text = "Выберите шаблон";
1279                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1280                return;
1281            }
1282
1283            var doc = cmbDocuments.SelectedItem as WordDocumentInfo;
1284            if (doc == null) return;
1285
1286            WordIntegration.SelectedDocument = doc;
1287
1288            // Save current selections
1289            SaveTitlePageSelections();
1290
1291            // Prepare replacements
1292            var replacements = new Dictionary<string, string>
1293            {
1294                { "%WORKNUMBER%", txtWorkNumber.Text.Trim() },
1295                { "%DISCIPLINE%", cmbDiscipline.Text.Trim() },
1296                { "%TOPIC%", cmbTopic.Text.Trim() },
1297                { "%STUDENT%", cmbStudent.Text.Trim() },
1298                { "%GROUP%", cmbGroup.Text.Trim() },
1299                { "%TEACHER%", cmbTeacher.Text.Trim() }
1300            };
1301
1302            lblStatus.Text = "Вставка титульного листа...";
1303            lblStatus.ForeColor = DimTextColor;
1304            btnInsertTitlePage.Enabled = false;
1305            Application.DoEvents();
1306
1307            bool result = WordIntegration.InsertTitlePage(txtTemplatePath.Text, replacements);
1308
1309            if (result)
1310            {
1311                lblStatus.Text = "Титульный лист вставлен!";
1312                lblStatus.ForeColor = SuccessColor;
1313            }
1314            else
1315            {
1316                lblStatus.Text = "Ошибка вставки";
1317                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1318            }
1319
1320            btnInsertTitlePage.Enabled = true;
1321        }
1322
1323        private void SaveTitlePageSelections()
1324        {
1325            var data = TitlePageData.Instance;
1326            data.LastWorkNumber = txtWorkNumber.Text;
1327
1328            // Add current values to lists if not present
1329            data.AddToList("discipline", cmbDiscipline.Text.Trim());
1330            data.AddToList("topic", cmbTopic.Text.Trim());
1331            data.AddToList("student", cmbStudent.Text.Trim());
1332            data.AddToList("group", cmbGroup.Text.Trim());
1333            data.AddToList("teacher", cmbTeacher.Text.Trim());
1334
1335            // Save selected indices
1336            data.SetLastSelectedIndex("discipline", cmbDiscipline.SelectedIndex);
1337            data.SetLastSelectedIndex("topic", cmbTopic.SelectedIndex);
1338            data.SetLastSelectedIndex("student", cmbStudent.SelectedIndex);
1339            data.SetLastSelectedIndex("group", cmbGroup.SelectedIndex);
1340            data.SetLastSelectedIndex("teacher", cmbTeacher.SelectedIndex);
1341        }
1342
1343        private Label AddLabelToScroll(string text, int x, int y)
1344        {
1345            var lbl = new Label();
1346            lbl.Text = text;
1347            lbl.Location = new Point(x, y);
1348            lbl.AutoSize = true;
1349            lbl.ForeColor = TextColor;
1350            lbl.Font = new Font("Segoe UI", 9f);
1351            scrollPanel.Controls.Add(lbl);
1352            return lbl;
1353        }
1354
1355        private Label AddLabelToPanel(Panel panel, string text, int x, int y)
1356        {
1357            var lbl = new Label();
1358            lbl.Text = text;
1359            lbl.Location = new Point(x, y);
1360            lbl.AutoSize = true;
1361            lbl.ForeColor = TextColor;
1362            lbl.Font = new Font("Segoe UI", 9f);
1363            panel.Controls.Add(lbl);
1364            return lbl;
1365        }
1366
1367        private void TextBox_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
1368        {
1369            // Block space at preview level before it reaches the control
1370            if (e.KeyCode == Keys.Space && ignoreSpaceUntilRelease)
1371            {
1372                e.IsInputKey = false;
1373            }
1374        }
1375
1376        private void TextBox_KeyDown(object sender, KeyEventArgs e)
1377        {
1378            // Ctrl+Space to insert (check BEFORE ignoreSpaceUntilRelease)
1379            if (e.KeyCode == Keys.Space && e.Control)
1380            {
1381                e.SuppressKeyPress = true;
1382                e.Handled = true;
1383                BtnInsert_Click(null, null);
1384                return;
1385            }
1386
1387            if (e.KeyCode == Keys.Space && ignoreSpaceUntilRelease)
1388            {
1389                e.SuppressKeyPress = true;
1390                e.Handled = true;
1391                return;
1392            }
1393
1394            // Enter to insert
1395            if (e.KeyCode == Keys.Enter)
1396            {
1397                bool isDescription = (sender == txtDescription);
1398                // In description, require Ctrl+Enter; in caption, just Enter
1399                if (!isDescription || e.Control)
1400                {
1401                    e.SuppressKeyPress = true;
1402                    e.Handled = true;
1403                    BtnInsert_Click(null, null);
1404                }
1405            }
1406        }
1407
1408        private void TextBox_KeyUp(object sender, KeyEventArgs e)
1409        {
1410            if (e.KeyCode == Keys.Space)
1411            {
1412                ignoreSpaceUntilRelease = false;
1413                if (pendingFocus)
1414                {
1415                    pendingFocus = false;
1416                    txtDescription.Focus();
1417                }
1418            }
1419        }
1420
1421        private void Panel_KeyDown(object sender, KeyEventArgs e)
1422        {
1423            // Ctrl+Space to insert from anywhere in panel
1424            if (e.KeyCode == Keys.Space && e.Control)
1425            {
1426                e.SuppressKeyPress = true;
1427                e.Handled = true;
1428                BtnInsert_Click(null, null);
1429            }
1430        }
1431
1432        /// <summary>
1433        /// Called from EditorForm when space key is released
1434        /// </summary>
1435        public void OnSpaceReleased()
1436        {
1437            ignoreSpaceUntilRelease = false;
1438            spaceWasReleased = true;
1439            TrySetFocus();
1440        }
1441
1442        private bool spaceWasReleased = false;
1443
1444        private void TrySetFocus()
1445        {
1446            // Only set focus if: space was released AND animation finished AND we have pending focus
1447            if (pendingFocus && spaceWasReleased && !isAnimating)
1448            {
1449                pendingFocus = false;
1450                spaceWasReleased = false;
1451                // Small delay to ensure everything is settled
1452                var focusTimer = new System.Windows.Forms.Timer();
1453                focusTimer.Interval = 50;
1454                focusTimer.Tick += (s, e) =>
1455                {
1456                    focusTimer.Stop();
1457                    focusTimer.Dispose();
1458                    if (Width > 0) // Panel is still visible
1459                    {
1460                        txtDescription.Focus();
1461                        txtDescription.SelectAll();
1462                    }
1463                };
1464                focusTimer.Start();
1465            }
1466        }
1467
1468        private Label AddLabel(string text, int x, int y)
1469        {
1470            // Legacy method - now redirects to scrollPanel
1471            return AddLabelToScroll(text, x, y);
1472        }
1473
1474        private void SetupRefreshTimer()
1475        {
1476            // Timer disabled to prevent interference with Word context menus
1477            // Preview is updated manually when document is selected or on Show()
1478            refreshTimer = new System.Windows.Forms.Timer();
1479            refreshTimer.Interval = 60000; // 60 seconds - disabled effectively
1480            refreshTimer.Tick += RefreshTimer_Tick;
1481        }
1482
1483        private void RefreshTimer_Tick(object sender, EventArgs e)
1484        {
1485            // Disabled - was causing Word context menus to close due to COM automation calls
1486            // Preview now updates only on document selection change
1487        }
1488
1489        public void Show(Bitmap screenshotImage)
1490        {
1491            imageToInsert = screenshotImage;
1492            ignoreSpaceUntilRelease = true; // Ignore space until user releases it
1493            pendingFocus = true; // Will focus after space is released
1494            spaceWasReleased = false; // Reset
1495            LoadDocuments();
1496
1497            if (cmbDocuments.Items.Count > 0)
1498            {
1499                targetWidth = PanelWidth;
1500                isAnimating = true;
1501                var animTimer = new System.Windows.Forms.Timer();
1502                animTimer.Interval = 16;
1503                animTimer.Tick += (s, e) =>
1504                {
1505                    if (Width < targetWidth)
1506                    {
1507                        Width = Math.Min(Width + AnimationStep, targetWidth);
1508                        parentEditor.Invalidate();
1509                    }
1510                    else
1511                    {
1512                        isAnimating = false;
1513                        animTimer.Stop();
1514                        animTimer.Dispose();
1515                        if (glassScroll != null) glassScroll.SetupScroll();
1516                        // Try to set focus (if space was already released)
1517                        TrySetFocus();
1518                    }
1519                };
1520                animTimer.Start();
1521                // refreshTimer.Start(); // Disabled to prevent Word interference
1522            }
1523        }
1524
1525        public new void Hide()
1526        {
1527            // Already hiding — don't create another timer
1528            if (isAnimating && targetWidth == 0) return;
1529
1530            // If docked Fill inside a separate window, close that window instead
1531            // (Width animation can't work with Dock=Fill — layout overrides it)
1532            if (Dock == DockStyle.Fill && Parent is Form)
1533            {
1534                ((Form)Parent).Close();
1535                return;
1536            }
1537
1538            targetWidth = 0;
1539            isAnimating = true;
1540            var animTimer = new System.Windows.Forms.Timer();
1541            animTimer.Interval = 16;
1542            animTimer.Tick += (s, e) =>
1543            {
1544                if (Width > 0)
1545                {
1546                    Width = Math.Max(Width - AnimationStep, 0);
1547                    parentEditor.Invalidate();
1548                }
1549                else
1550                {
1551                    isAnimating = false;
1552                    animTimer.Stop();
1553                    animTimer.Dispose();
1554                    if (PanelClosed != null)
1555                        PanelClosed(this, EventArgs.Empty);
1556                }
1557            };
1558            animTimer.Start();
1559        }
1560
1561        public bool IsExpanded
1562        {
1563            get { return Width > 0 || isAnimating; }
1564        }
1565
1566        private void LoadDocuments()
1567        {
1568            cmbDocuments.Items.Clear();
1569            var docs = WordIntegration.GetOpenDocuments();
1570
1571            if (docs.Count == 0)
1572            {
1573                lblStatus.Text = "Word не найден";
1574                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1575                btnInsert.Enabled = false;
1576            }
1577            else
1578            {
1579                foreach (var doc in docs)
1580                {
1581                    cmbDocuments.Items.Add(doc);
1582                }
1583
1584                // Select previously selected or first
1585                var prev = WordIntegration.SelectedDocument;
1586                int selIdx = 0;
1587                if (prev != null)
1588                {
1589                    for (int i = 0; i < cmbDocuments.Items.Count; i++)
1590                    {
1591                        var item = cmbDocuments.Items[i] as WordDocumentInfo;
1592                        if (item != null && item.FullPath == prev.FullPath)
1593                        {
1594                            selIdx = i;
1595                            break;
1596                        }
1597                    }
1598                }
1599                cmbDocuments.SelectedIndex = selIdx;
1600
1601                lblStatus.Text = "Готово к вставке";
1602                lblStatus.ForeColor = SuccessColor;
1603                btnInsert.Enabled = true;
1604            }
1605        }
1606
1607        private void CmbDocuments_SelectedIndexChanged(object sender, EventArgs e)
1608        {
1609            var doc = cmbDocuments.SelectedItem as WordDocumentInfo;
1610            if (doc != null)
1611            {
1612                WordIntegration.SelectedDocument = doc;
1613                lblDocName.Text = doc.Name;
1614                UpdatePreview();
1615            }
1616        }
1617
1618        private void UpdatePreview()
1619        {
1620            try
1621            {
1622                // Method 1: Try CopyAsPicture from Word (works from memory, no file needed)
1623                var docPreview = WordIntegration.GetDocumentPreview();
1624                if (docPreview != null)
1625                {
1626                    SetPreviewImage(ScalePreview(docPreview));
1627                    return;
1628                }
1629
1630                // Method 2: Try to render docx file directly
1631                var selectedDoc = WordIntegration.SelectedDocument;
1632                if (selectedDoc != null && !string.IsNullOrEmpty(selectedDoc.FullPath) &&
1633                    System.IO.File.Exists(selectedDoc.FullPath))
1634                {
1635                    string tempPath = System.IO.Path.Combine(
1636                        System.IO.Path.GetTempPath(),
1637                        "wc_preview_" + Guid.NewGuid().ToString("N") + ".docx");
1638
1639                    try
1640                    {
1641                        using (var source = new System.IO.FileStream(selectedDoc.FullPath,
1642                            System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite))
1643                        using (var dest = new System.IO.FileStream(tempPath,
1644                            System.IO.FileMode.Create, System.IO.FileAccess.Write))
1645                        {
1646                            source.CopyTo(dest);
1647                        }
1648
1649                        var docxPreview = DocxRenderer.RenderDocx(tempPath, 140);
1650                        try { System.IO.File.Delete(tempPath); } catch { }
1651
1652                        if (docxPreview != null)
1653                        {
1654                            SetPreviewImage(docxPreview);
1655                            return;
1656                        }
1657                    }
1658                    catch
1659                    {
1660                        try { System.IO.File.Delete(tempPath); } catch { }
1661                    }
1662                }
1663
1664                // Method 3: Fallback to window screenshot
1665                var screenshot = WordIntegration.GetWordWindowScreenshot();
1666                if (screenshot != null)
1667                {
1668                    SetPreviewImage(ScalePreview(screenshot));
1669                }
1670                else
1671                {
1672                    ShowPlaceholder();
1673                }
1674            }
1675            catch { }
1676        }
1677
1678        private void SetPreviewImage(Bitmap bmp)
1679        {
1680            if (previewBox.Image != null)
1681            {
1682                previewBox.Image.Dispose();
1683            }
1684            previewBox.Image = bmp;
1685        }
1686
1687        private Bitmap ScalePreview(Bitmap source)
1688        {
1689            // Scale to fit preview box
1690            int maxWidth = previewBox.Width > 0 ? previewBox.Width : 280;
1691            int maxHeight = previewBox.Height > 0 ? previewBox.Height : 140;
1692
1693            float scale = Math.Min((float)maxWidth / source.Width, (float)maxHeight / source.Height);
1694            if (scale >= 1f) return source;
1695
1696            int newW = (int)(source.Width * scale);
1697            int newH = (int)(source.Height * scale);
1698
1699            var scaled = new Bitmap(newW, newH);
1700            using (var g = Graphics.FromImage(scaled))
1701            {
1702                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
1703                g.DrawImage(source, 0, 0, newW, newH);
1704            }
1705            source.Dispose();
1706            return scaled;
1707        }
1708
1709        private void ShowPlaceholder()
1710        {
1711            var bmp = new Bitmap(previewBox.Width > 0 ? previewBox.Width : 280,
1712                                  previewBox.Height > 0 ? previewBox.Height : 140);
1713            using (var g = Graphics.FromImage(bmp))
1714            {
1715                g.Clear(DarkField);
1716                using (var brush = new SolidBrush(DimTextColor))
1717                using (var font = new Font("Segoe UI", 9f))
1718                {
1719                    var text = "Документ не сохранён";
1720                    var size = g.MeasureString(text, font);
1721                    g.DrawString(text, font, brush,
1722                        (bmp.Width - size.Width) / 2,
1723                        (bmp.Height - size.Height) / 2);
1724                }
1725            }
1726            if (previewBox.Image != null)
1727                previewBox.Image.Dispose();
1728            previewBox.Image = bmp;
1729        }
1730
1731        private void BtnGenerateDescription_Click(object sender, EventArgs e)
1732        {
1733            if (imageToInsert == null)
1734            {
1735                lblStatus.Text = "Нет изображения";
1736                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1737                return;
1738            }
1739
1740            if (!GeminiIntegration.IsConfigured)
1741            {
1742                lblStatus.Text = "API ключ не настроен (GEMINI_API_KEY)";
1743                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1744                return;
1745            }
1746
1747            // Disable button and show progress
1748            btnGenerateDescription.Enabled = false;
1749            btnGenerateDescription.Text = "Генерация...";
1750            lblStatus.Text = "Запрос к AI...";
1751            lblStatus.ForeColor = DimTextColor;
1752
1753            // Use async method with callbacks
1754            GeminiIntegration.GenerateDescriptionAsync(imageToInsert,
1755                // On success
1756                (result) =>
1757                {
1758                    if (result != null && (!string.IsNullOrEmpty(result.Item1) || !string.IsNullOrEmpty(result.Item2)))
1759                    {
1760                        // Fill in the fields
1761                        if (!string.IsNullOrEmpty(result.Item1))
1762                            txtDescription.Text = result.Item1;
1763                        if (!string.IsNullOrEmpty(result.Item2))
1764                            txtCaption.Text = result.Item2;
1765
1766                        lblStatus.Text = "Описание сгенерировано";
1767                        lblStatus.ForeColor = SuccessColor;
1768                    }
1769                    else
1770                    {
1771                        // Show detailed error
1772                        string error = GeminiIntegration.LastError ?? "Неизвестная ошибка";
1773                        lblStatus.Text = "Ошибка: " + error;
1774                        lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1775                        System.Diagnostics.Debug.WriteLine("Gemini generation failed: " + error);
1776
1777                        // Show response in debug if available
1778                        if (!string.IsNullOrEmpty(GeminiIntegration.LastResponse))
1779                        {
1780                            System.Diagnostics.Debug.WriteLine("Last response: " + GeminiIntegration.LastResponse);
1781                        }
1782                    }
1783                    btnGenerateDescription.Enabled = true;
1784                    btnGenerateDescription.Text = "Сгенерировать описание (AI)";
1785                },
1786                // On error
1787                (ex) =>
1788                {
1789                    string errorMsg = ex.Message;
1790                    if (!string.IsNullOrEmpty(GeminiIntegration.LastError))
1791                        errorMsg = GeminiIntegration.LastError;
1792                    lblStatus.Text = "Ошибка: " + errorMsg;
1793                    lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1794                    System.Diagnostics.Debug.WriteLine("Generate description error: " + ex);
1795                    btnGenerateDescription.Enabled = true;
1796                    btnGenerateDescription.Text = "Сгенерировать описание (AI)";
1797                }
1798            );
1799        }
1800
1801        /// <summary>
1802        /// Public method to trigger insert (called from EditorForm on Ctrl+Space)
1803        /// </summary>
1804        public void DoInsert()
1805        {
1806            BtnInsert_Click(null, null);
1807        }
1808
1809        private void BtnInsert_Click(object sender, EventArgs e)
1810        {
1811            if (cmbDocuments.SelectedItem == null || imageToInsert == null)
1812            {
1813                lblStatus.Text = "Выберите документ";
1814                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1815                return;
1816            }
1817
1818            var doc = cmbDocuments.SelectedItem as WordDocumentInfo;
1819            if (doc == null) return;
1820
1821            WordIntegration.SelectedDocument = doc;
1822
1823            lblStatus.Text = "Вставка...";
1824            lblStatus.ForeColor = DimTextColor;
1825            btnInsert.Enabled = false;
1826            Application.DoEvents();
1827
1828            bool result = WordIntegration.InsertImageWithCaption(
1829                imageToInsert,
1830                txtDescription.Text.Trim(),
1831                txtCaption.Text.Trim());
1832
1833            if (result)
1834            {
1835                lblStatus.Text = "Вставлено!";
1836                lblStatus.ForeColor = SuccessColor;
1837
1838                if (InsertSuccess != null)
1839                    InsertSuccess(this, EventArgs.Empty);
1840
1841                // Close the editor after successful insert
1842                parentEditor.Close();
1843            }
1844            else
1845            {
1846                lblStatus.Text = "Ошибка вставки";
1847                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1848                btnInsert.Enabled = true;
1849            }
1850        }
1851
1852        private void BtnOriginalize_Click(object sender, EventArgs e)
1853        {
1854            if (cmbDocuments.SelectedItem == null)
1855            {
1856                lblStatus.Text = "Выберите документ";
1857                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1858                return;
1859            }
1860
1861            if (!GeminiIntegration.IsConfigured)
1862            {
1863                lblStatus.Text = "API ключ не настроен";
1864                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1865                return;
1866            }
1867
1868            var doc = cmbDocuments.SelectedItem as WordDocumentInfo;
1869            if (doc == null) return;
1870
1871            WordIntegration.SelectedDocument = doc;
1872
1873            // Step 1: Extract descriptions and captions
1874            lblStatus.Text = "Сканирование документа...";
1875            lblStatus.ForeColor = DimTextColor;
1876            btnOriginalize.Enabled = false;
1877            Application.DoEvents();
1878
1879            var items = WordIntegration.ExtractDescriptionsAndCaptions(doc.DocumentObject);
1880
1881            if (items == null || items.Count == 0)
1882            {
1883                lblStatus.Text = "Описания не найдены";
1884                lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1885                btnOriginalize.Enabled = true;
1886                return;
1887            }
1888
1889            lblStatus.Text = "Найдено " + items.Count + " текстов. Перефразирование...";
1890            lblStatus.ForeColor = DimTextColor;
1891            Application.DoEvents();
1892
1893            // Step 2: Collect texts for rephrasing
1894            var textsToRephrase = new List<string>();
1895            foreach (var item in items)
1896                textsToRephrase.Add(item.TextToRephrase);
1897
1898            // Step 3: Send to Gemini async
1899            GeminiIntegration.RephraseTextsAsync(textsToRephrase,
1900                // On success
1901                (rephrasedTexts) =>
1902                {
1903                    // Populate NewText in items
1904                    for (int i = 0; i < items.Count && i < rephrasedTexts.Count; i++)
1905                        items[i].NewText = rephrasedTexts[i];
1906
1907                    lblStatus.Text = "Перефразировано. Открытие превью...";
1908                    lblStatus.ForeColor = SuccessColor;
1909                    btnOriginalize.Enabled = true;
1910
1911                    // Step 4: Show preview dialog
1912                    using (var dialog = new OriginalizePreviewDialog(items))
1913                    {
1914                        if (dialog.ShowDialog(parentEditor) == DialogResult.OK)
1915                        {
1916                            // Step 5: Apply replacements
1917                            lblStatus.Text = "Применение замен...";
1918                            lblStatus.ForeColor = DimTextColor;
1919                            Application.DoEvents();
1920
1921                            int count = WordIntegration.ApplyReplacements(doc.DocumentObject, items);
1922                            lblStatus.Text = "Заменено: " + count + " текстов";
1923                            lblStatus.ForeColor = SuccessColor;
1924                        }
1925                        else
1926                        {
1927                            lblStatus.Text = "Отменено";
1928                            lblStatus.ForeColor = DimTextColor;
1929                        }
1930                    }
1931                },
1932                // On error
1933                (error) =>
1934                {
1935                    lblStatus.Text = "Ошибка: " + error;
1936                    lblStatus.ForeColor = Color.FromArgb(200, 85, 85);
1937                    btnOriginalize.Enabled = true;
1938                }
1939            );
1940        }
1941
1942        protected override void Dispose(bool disposing)
1943        {
1944            if (disposing)
1945            {
1946                if (refreshTimer != null)
1947                {
1948                    refreshTimer.Stop();
1949                    refreshTimer.Dispose();
1950                }
1951                if (previewBox.Image != null)
1952                {
1953                    previewBox.Image.Dispose();
1954                }
1955            }
1956            base.Dispose(disposing);
1957        }
1958    }
1959}