{
    SettingsWindow используется для создания окна, в котором пользователь
    производит настройку Малик Эмулятора на свой вкус и предпочтения.
    Этот исходный текст является частью Малик Эмулятора.

    Следующие файлы используются этим исходным текстом:
        settingswindow.lfm
    На них так же распространяются те же права, как и на этот исходный текст.

    Copyright © 2016–2017, 2019–2022 Малик Разработчик

    Малик Эмулятор – свободная программа: вы можете перераспространять её и/или
    изменять её на условиях Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Малик Эмулятор распространяется в надежде, что он может быть полезен,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <http://www.gnu.org/licenses/>.
}

unit SettingsWindow;

{$MODE DELPHI}

interface

uses
    Classes,
    SysUtils,
    Forms,
    Graphics,
    Dialogs,
    StdCtrls,
    ComCtrls,
    ExtCtrls,
    Lang,
    Images,
    EmulThemes,
    StaticRecompilers,
    EmulatorInterfaces;

{%region public }
type
    TRadioButton_Array1d = array of TRadioButton;
    AdjustableRecompiler_Array1d = array of AdjustableRecompiler;

    TSettingsForm = class(TForm, _Interface, GraphicListener)
        settingsTabs: TPageControl;
        emulatorTab: TTabSheet;
        behavior: TGroupBox;
        fullscreenOnLaunch: TCheckBox;
        closeOnTerminate: TCheckBox;
        graphicScalingAlgorithm: TGroupBox;
        graphicScalingWhyAlgorithm: TLabel;
        graphicScalingSystem: TRadioButton;
        graphicScalingNearest: TRadioButton;
        graphicScalingScaleNx: TRadioButton;
        graphicScalingHqNx: TRadioButton;
        graphicScalingStandard: TRadioButton;
        maximumFrames: TGroupBox;
        maximumFramesValue: TTrackBar;
        maximumFramesLabel: TLabel;
        themeTab: TTabSheet;
        themeSelectorLabel: TLabel;
        themeSelector: TComboBox;
        themePreviewLabel: TLabel;
        themePreview: TPaintBox;
        themeComment: TLabel;
        disasmTab: TTabSheet;
        disasmSelectorLabel: TLabel;
        disasmSelector: TComboBox;
        disasmColorLabel: TLabel;
        disasmColor: TColorButton;
        disasmPreviewLabel: TLabel;
        disasmPreview: TPaintBox;
        recompilerTab: TTabSheet;
        recompilerSelectorLabel: TLabel;
        recompilerSelector: TComboBox;
        recompilerBevel: TBevel;
        recompilerAlignment: TCheckBox;
        recompilerCompression: TGroupBox;
        recompilerCompressionSelector: TTrackBar;
        recompilerCompressionLabel1: TLabel;
        recompilerCompressionLabel2: TLabel;
        recompilerStack: TGroupBox;
        recompilerStackSelector: TTrackBar;
        recompilerStackLabel1: TLabel;
        recompilerStackLabel2: TLabel;
        recompilerStackLabel3: TLabel;
        recompilerHeap: TGroupBox;
        recompilerHeapSelector: TTrackBar;
        recompilerHeapLabel1: TLabel;
        recompilerHeapLabel2: TLabel;
        recompilerHeapLabel3: TLabel;
        recompilerExecFileNameLabel: TLabel;
        recompilerExecFileName: TEdit;
        recompilerExecFileExt: TLabel;
        closeButton: TButton;
        procedure formShow(sender: TObject);
        procedure radioButtonEnter(sender: TObject);
        procedure maximumFramesValueChange(sender: TObject);
        procedure themeSelectorChange(sender: TObject);
        procedure themePreviewPaint(sender: TObject);
        procedure disasmSelectorChange(sender: TObject);
        procedure disasmColorChange(sender: TObject);
        procedure disasmPreviewPaint(sender: TObject);
        procedure recompilerSelectorChange(sender: TObject);
        procedure recompilerAlignmentChange(sender: TObject);
        procedure recompilerCompressionSelectorChange(sender: TObject);
        procedure recompilerStackSelectorChange(sender: TObject);
        procedure recompilerHeapSelectorChange(sender: TObject);
        procedure recompilerExecFileNameChange(sender: TObject);
    private
        owner: MainWindowInterface;
        algorithms: TRadioButton_Array1d;
        themeImage: TBitmap;
        themeImagePixels: PIntArray;
        disasmColors: int_Array1d;
        disasmImage: TBitmap;
        recompilers: AdjustableRecompiler_Array1d;
        function getScalingAlgorithm(): AnsiString;
        procedure drawWindow(t: Theme; state, left, top, width, height: int);
        procedure drawTheme(t: Theme);
        procedure drawDisasm();
    public
        constructor create(theOwner: TComponent); override;
        destructor destroy; override;
        procedure afterConstruction(); override;
        procedure beforeDestruction(); override;
        { _Interface }
        function getClass(): _Class;
        function asObject(): TObject;
        { GraphicListener }
        procedure putPixel(x, y, argb: int);
    end;
{%endregion}

implementation

{$R *.LFM}

{%region private }
var
    SCALING_ALGORITHMS: AnsiString_Array1d;
{%endregion}

{%region routine }
    function toSettingString(value: boolean): AnsiString; overload;
    begin
        if value then begin
            result := '1';
        end else begin
            result := '0';
        end;
    end;

    function toSettingString(value: long): AnsiString; overload;
    begin
        result := toDecString(value);
    end;

    function TRadioButton_Array1d_create(length: int): TRadioButton_Array1d;
    begin
        setLength(result, length);
    end;

    function toTRadioButtonArray1d(const arr: array of TRadioButton): TRadioButton_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    procedure arraycopy(const src: TRadioButton_Array1d; srcOffset: int; const dst: TRadioButton_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        move(src[srcOffset], dst[dstOffset], length * sizeof(_Object));
    end;

    function AdjustableRecompiler_Array1d_create(length: int): AdjustableRecompiler_Array1d;
    begin
        setLength(result, length);
    end;

    function toAdjustableRecompilerArray1d(const arr: array of AdjustableRecompiler): AdjustableRecompiler_Array1d;
    var
        i: int;
    begin
        setLength(result, length(arr));
        for i := length(result) - 1 downto 0 do begin
            result[i] := arr[i];
        end;
    end;

    procedure arraycopy(const src: AdjustableRecompiler_Array1d; srcOffset: int; const dst: AdjustableRecompiler_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
        i: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        if (src = dst) and (srcOffset < dstOffset) then begin
            for i := length - 1 downto 0 do begin
                dst[dstOffset + i] := src[srcOffset + i];
            end;
        end else begin
            for i := 0 to length - 1 do begin
                dst[dstOffset + i] := src[srcOffset + i];
            end;
        end;
    end;
{%endregion}

{%region TSettingsForm }
    constructor TSettingsForm.create(theOwner: TComponent);
    begin
        inherited create(nil);
        theOwner.getInterface(stringToGUID(MAIN_WINDOW_INTERFACE_GUID), owner);
        themeImage := TBitmap.create();
        themeImagePixels := createPixelImage(themeImage, themePreview.width, themePreview.height);
        disasmImage := TBitmap.create();
    end;

    destructor TSettingsForm.destroy;
    begin
        disasmImage.free();
        themeImage.free();
        inherited destroy;
    end;

    procedure TSettingsForm.formShow(sender: TObject);
    begin
        disasmColor.height := disasmSelector.height;
    end;

    procedure TSettingsForm.radioButtonEnter(sender: TObject);
    begin
        (sender as TRadioButton).checked := true;
    end;

    procedure TSettingsForm.maximumFramesValueChange(sender: TObject);
    begin
        maximumFramesLabel.caption := toDecString(long(maximumFramesValue.position));
    end;

    procedure TSettingsForm.themeSelectorChange(sender: TObject);
    begin
        drawTheme(getTheme(themeSelector.itemIndex));
        themePreviewPaint(themePreview);
    end;

    procedure TSettingsForm.themePreviewPaint(sender: TObject);
    begin
        themePreview.canvas.draw(0, 0, themeImage);
    end;

    procedure TSettingsForm.disasmSelectorChange(sender: TObject);
    begin
        disasmColor.buttonColor := byteSwap(disasmColors[disasmSelector.itemIndex]) shr 8;
    end;

    procedure TSettingsForm.disasmColorChange(sender: TObject);
    begin
        disasmColors[disasmSelector.itemIndex] := byteSwap(disasmColor.buttonColor) shr 8;
        drawDisasm();
        disasmPreviewPaint(disasmPreview);
    end;

    procedure TSettingsForm.disasmPreviewPaint(sender: TObject);
    begin
        disasmPreview.canvas.draw(0, 0, disasmImage);
    end;

    procedure TSettingsForm.recompilerSelectorChange(sender: TObject);
    var
        recompiler: AdjustableRecompiler;
    begin
        recompiler := self.recompilers[recompilerSelector.itemIndex];
        recompilerAlignment.checked := recompiler.getAlignment();
        recompilerCompressionSelector.position := recompiler.getCompressionLevel();
        recompilerStackSelector.position := recompiler.getStackSize();
        recompilerHeapSelector.position := recompiler.getHeapSize() shr 10;
        recompilerExecFileName.text := recompiler.getOutputExecutableFileName();
    end;

    procedure TSettingsForm.recompilerAlignmentChange(sender: TObject);
    begin
        recompilers[recompilerSelector.itemIndex].setAlignment(recompilerAlignment.checked);
    end;

    procedure TSettingsForm.recompilerCompressionSelectorChange(sender: TObject);
    var
        value: int;
    begin
        value := recompilerCompressionSelector.position;
        recompilers[recompilerSelector.itemIndex].setCompressionLevel(value);
    end;

    procedure TSettingsForm.recompilerStackSelectorChange(sender: TObject);
    var
        value: int;
    begin
        value := recompilerStackSelector.position;
        recompilers[recompilerSelector.itemIndex].setStackSize(value);
        recompilerStackLabel2.caption := toDecString(value);
    end;

    procedure TSettingsForm.recompilerHeapSelectorChange(sender: TObject);
    var
        value: int;
        r: AdjustableRecompiler;
    begin
        value := recompilerHeapSelector.position;
        r := recompilers[recompilerSelector.itemIndex];
        r.setHeapSize(value shl 10);
        r.setDescriptorsSize(value shl 5);
        recompilerHeapLabel2.caption := toDecString(value);
    end;

    procedure TSettingsForm.recompilerExecFileNameChange(sender: TObject);
    var
        value: AnsiString;
        i: int;
    begin
        value := recompilerExecFileName.text;
        for i := length(value) downto 1 do begin
            if value[i] in ['\', '|', '*', ':', '?', '"', '<', '>'] then begin
                delete(value, i, 1);
            end;
        end;
        recompilers[recompilerSelector.itemIndex].setOutputExecutableFileName(value);
        recompilerExecFileName.text := value;
    end;

    function TSettingsForm.getScalingAlgorithm(): AnsiString;
    var
        i: int;
        j: int;
    begin
        j := 0;
        for i := 1 to length(algorithms) - 1 do begin
            if algorithms[i].checked then begin
                j := i;
                break;
            end;
        end;
        result := SCALING_ALGORITHMS[j];
    end;

    procedure TSettingsForm.drawWindow(t: Theme; state, left, top, width, height: int);
    var
        tbh: int;
        bbh: int;
        lbw: int;
        rbw: int;
        bis: int;
        b1s: int;
        b2s: int;
        b3s: int;
        b4s: int;
        b5s: int;
        w: int;
        h: int;
        i: int;
        j: int;
        k: int;
    begin
        case state of
         1: bis := 3;
         2: bis := 5;
        else
            bis := 0;
        end;
        state := (state shl 16) + $06;
        tbh := short(t.getSizes(state) shr 16);
        bbh := short(t.getSizes(state + $0400) shr 16);
        lbw := short(t.getSizes(state + $0200));
        rbw := short(t.getSizes(state + $0300));
        bis := (bis shl 16) + $06;
        b1s := t.getSizes(bis + $1600);
        b2s := t.getSizes(bis + $1500);
        b3s := t.getSizes(bis + $1200);
        b4s := t.getSizes(bis + $1100);
        b5s := t.getSizes(bis + $1000);
        tbh := max(tbh, max(short(b1s shr 16), max(short(b2s shr 16), max(short(b3s shr 16), max(short(b4s shr 16), short(b5s shr 16))))));
        t.drawElement(self, state, left, top, width, tbh);
        t.drawElement(self, state + $0200, left, top + tbh, lbw, height - tbh - bbh);
        t.drawElement(self, state + $0300, left + width - rbw, top + tbh, rbw, height - tbh - bbh);
        t.drawElement(self, state + $0400, left, top + height - bbh, width, bbh);
        t.drawElement(self, state + $0500, left + lbw, top + tbh, width - lbw - rbw, height - tbh - bbh);
        t.drawElement(self, bis + $1600, left + width - rbw - short(b1s) - short(b2s) - short(b3s) - short(b4s) - short(b5s), top, short(b1s), short(b1s shr 16));
        t.drawElement(self, bis + $1500, left + width - rbw - short(b2s) - short(b3s) - short(b4s) - short(b5s), top, short(b2s), short(b2s shr 16));
        t.drawElement(self, bis + $1200, left + width - rbw - short(b3s) - short(b4s) - short(b5s), top, short(b3s), short(b3s shr 16));
        t.drawElement(self, bis + $1100, left + width - rbw - short(b4s) - short(b5s), top, short(b4s), short(b4s shr 16));
        t.drawElement(self, bis + $1000, left + width - rbw - short(b5s), top, short(b5s), short(b5s shr 16));
        if (state shr 16) <> 0 then begin
            exit;
        end;
        inc(left, lbw);
        inc(top, tbh);
        dec(width, lbw + rbw);
        dec(height, tbh + bbh);
        bbh := max(16, short(t.getSizes($020d) shr 16));
        w := max(100, short(t.getSizes($0004)));
        h := max(16, short(t.getSizes($0004) shr 16));
        t.drawElement(self, $0004, left, top, w, h);
        t.drawElement(self, $0304, left, top + h + 4, w, h);
        t.drawElement(self, $0104, left, top + 2 * h + 8, w, h);
        t.drawElement(self, $0204, left, top + 3 * h + 12, w, h);
        i := (633 * width) shr 10;
        t.drawElement(self, $010d, left, top + height - bbh, i, bbh);
        t.drawElement(self, $020d, left + i, top + height - bbh, width - i, bbh);
        inc(left, w + 4);
        inc(top, 0);
        dec(width, w + 4);
        dec(height, bbh);
        t.drawElement(self, $0108, left, top, width, height);
        w := max(8, short(t.getSizes($050a)));
        h := max(8, short(t.getSizes($040a) shr 16));
        k := height - 3 * h - 4;
        i := k div 3;
        j := (k div 3) + 1;
        k := k - i - j;
        t.drawElement(self, $000a, left + width - w - 2, top + 2, w, h);
        t.drawElement(self, $080a, left + width - w - 2, top + h + 2, w, i);
        t.drawElement(self, $050a, left + width - w - 2, top + h + i + 2, w, j);
        t.drawElement(self, $090a, left + width - w - 2, top + h + i + j + 2, w, k);
        t.drawElement(self, $010a, left + width - w - 2, top + height - 2 * h - 2, w, h);
        k := width - 3 * w - 4;
        i := k div 3;
        j := (k div 3) + 1;
        k := k - i - j;
        t.drawElement(self, $020a, left + 2, top + height - h - 2, w, h);
        t.drawElement(self, $060a, left + w + 2, top + height - h - 2, i, h);
        t.drawElement(self, $040a, left + w + i + 2, top + height - h - 2, j, h);
        t.drawElement(self, $070a, left + w + i + j + 2, top + height - h - 2, k, h);
        t.drawElement(self, $030a, left + width - 2 * w - 2, top + height - h - 2, w, h);
        t.drawElement(self, $0c0a, left + width - w - 2, top + height - h - 2, w, h);
    end;

    procedure TSettingsForm.drawTheme(t: Theme);
    var
        w: int;
        h: int;
        c: int;
        i: int;
    begin
        w := themeImage.width;
        h := themeImage.height;
        c := t.getSystemColor(1) and $00ffffff;
        for i := w * h - 1 downto 0 do begin
            themeImagePixels[i] := c;
        end;
        drawWindow(t, 1, 10, 10, 400, 140);
        drawWindow(t, 0, 20, 30, 400, 140);
    end;

    procedure TSettingsForm.drawDisasm();
    var
        f: int;
        i: int;
        j: int;
        k: int;
        l: int;
        t: int;
        w: int;
        h: int;
        c1: int;
        c2: int;
        r: AnsiString;
        s: AnsiString_Array1d;
        strings: AnsiString_Array2d;
        rect: TRect;
    begin
        disasmImage.setSize(disasmPreview.width, disasmPreview.height);
        with disasmImage.canvas do begin
            font := disasmPreview.font;
            f := abs(font.height);
            textWidth(#32); { без этой строки не будет корректно работать brush.color := clBtnFace }
            brush.style := bsSolid;
            brush.color := clBtnFace;
            fillRect(0, 0, width, height);
            brush.style := bsClear;
            pen.style := psSolid;
            pen.color := clWindowText;
            pen.width := 1;
            { стак }
            strings := toStringArray2d([
                toStringArray1d(['null', 'overflow']),
                toStringArray1d(['<пусто>']),
                toStringArray1d(['<пусто>']),
                toStringArray1d(['<пусто>']),
                toStringArray1d(['$8219ed IF=0', 'ireturn']),
                toStringArray1d(['1 ($1)', 'int']),
                toStringArray1d(['$0094c578', 'object']),
                toStringArray1d(['EIP=$8219f4 EBP=$240…', 'except']),
                toStringArray1d(['<пусто>']),
                toStringArray1d(['$24017d0', 'oldebp']),
                toStringArray1d(['$817e07', 'return']),
                toStringArray1d(['null', 'object'])
            ]);
            w := textWidth('<пусто>');
            h := height;
            for i := 0 to length(strings) - 1 do begin
                k := 0;
                s := strings[i];
                for j := 0 to length(s) - 1 do begin
                    inc(k, textWidth(s[j]));
                    if j > 0 then begin
                        inc(k, textWidth(#32));
                    end;
                end;
                if k > w then begin
                    w := k;
                end;
            end;
            inc(w, 2);
            l := width - w;
            t := 0;
            rectangle(bounds(l, t, w, h));
            rect := bounds(l + 1, t + 1, w - 2, h - 2);
            font.color := clWindowText;
            textRect(rect, l + 1, t, 'Стак');
            k := t + f + 1;
            moveTo(l, k);
            lineTo(l + w, k);
            if f = 0 then begin
                exit;
            end;
            j := (h - k - 1) div f;
            font.color := byteSwap(disasmColors[0]) shr 8;
            for i := -length(strings) + j - 1 downto 0 do begin
                textRect(rect, l + 1, k, '<пусто>');
                inc(k, f);
            end;
            for i := 0 to length(strings) - 1 do begin
                s := strings[i];
                if length(s) > 1 then begin
                    r := s[1];
                    if r = 'overflow' then begin
                        c1 := byteSwap(disasmColors[2]) shr 8;
                        c2 := byteSwap(disasmColors[13]) shr 8;
                    end else
                    if r = 'object' then begin
                        c1 := byteSwap(disasmColors[2]) shr 8;
                        c2 := byteSwap(disasmColors[8]) shr 8;
                    end else
                    if r = 'except' then begin
                        c1 := byteSwap(disasmColors[3]) shr 8;
                        c2 := byteSwap(disasmColors[9]) shr 8;
                    end else
                    if r = 'oldebp' then begin
                        c1 := byteSwap(disasmColors[4]) shr 8;
                        c2 := byteSwap(disasmColors[10]) shr 8;
                    end else
                    if r = 'return' then begin
                        c1 := byteSwap(disasmColors[5]) shr 8;
                        c2 := byteSwap(disasmColors[11]) shr 8;
                    end else
                    if r = 'ireturn' then begin
                        c1 := byteSwap(disasmColors[6]) shr 8;
                        c2 := byteSwap(disasmColors[12]) shr 8;
                    end else begin
                        c1 := byteSwap(disasmColors[1]) shr 8;
                        c2 := byteSwap(disasmColors[7]) shr 8;
                    end;
                end else begin
                    c1 := byteSwap(disasmColors[0]) shr 8;
                    c2 := 0;
                end;
                font.color := c1;
                textRect(rect, l + 1, k, s[0]);
                if length(s) > 1 then begin
                    font.color := c2;
                    r := s[1];
                    textRect(rect, l + w - textWidth(r) - 1, k, r);
                end;
                inc(k, f);
            end;
            { дизассемблер }
            strings := toStringArray2d([
                toStringArray1d(['java.lang.MalikInterrupt . enable(boolean)']),
                toStringArray1d(['    00836a7c', 'fb0000', 'enter   $0']),
                toStringArray1d(['    00836a7f', '0815000000', 'load    except java.lang.MalikInte']),
                toStringArray1d(['  MalikInterrupt.java[378]']),
                toStringArray1d(['    00836a84', 'e20200', 'load    int [ebp+$20]']),
                toStringArray1d(['    00836a87', 'ff2404', 'je      int 0 java.lang.MalikInte'])
            ]);
            w := l - 1;
            h := f * (length(strings) + 1) + 3;
            l := 0;
            t := 0;
            rectangle(bounds(l, t, w, h));
            rect := bounds(l + 1, t + 1, w - 2, h - 2);
            font.color := clWindowText;
            textRect(rect, l + 1, t, 'Дизассемблер');
            k := t + f + 1;
            moveTo(l, k);
            lineTo(l + w, k);
            c1 := l + textWidth('    00000000 ');
            c2 := c1 + textWidth('000000000000 ');
            for i := 0 to length(strings) - 1 do begin
                s := strings[i];
                r := s[0];
                if startsWith('    ', r) then begin
                    font.color := clWindowText;
                    font.style := [];
                end else
                if startsWith('  ', r) then begin
                    font.color := byteSwap(disasmColors[15]) shr 8;
                    font.style := [fsBold];
                end else begin
                    font.color := byteSwap(disasmColors[14]) shr 8;
                    font.style := [fsBold];
                end;
                textRect(rect, l + 1, k, r);
                if length(s) > 1 then begin
                    textRect(rect, c1, k, s[1]);
                end;
                if length(s) > 2 then begin
                    textRect(rect, c2, k, s[2]);
                end;
                inc(k, f);
            end;
            { инспектор отладки }
            s := toStringArray1d([
                '    length: int',
                '    chars: char[]',
                'Унаследовано от java.lang.Object',
                '    monitor: java.lang.Thread$Monitor',
                '    refcount: int',
                '    objclass: java.lang.Class'
            ]);
            l := 0;
            t := h + 1;
            h := height - t;
            rectangle(bounds(l, t, w, h));
            rect := bounds(l + 1, t + 1, w - 2, h - 2);
            font.color := clWindowText;
            textRect(rect, l + 1, t, 'Инспектор отладки');
            k := t + f + 1;
            moveTo(l, k);
            lineTo(l + w, k);
            for i := 0 to 5 do begin
                case i of
                0, 4: begin
                    font.color := byteSwap(disasmColors[1]) shr 8;
                    font.style := [];
                end;
                1, 3, 5: begin
                    font.color := byteSwap(disasmColors[2]) shr 8;
                    font.style := [];
                end;
                else
                    font.color := byteSwap(disasmColors[16]) shr 8;
                    font.style := [fsBold];
                end;
                textRect(rect, l + 1, k, s[i]);
                inc(k, f);
            end;
            font.style := [];
        end;
    end;

    procedure TSettingsForm.afterConstruction();
    var
        owner: MainWindowInterface;
        recompilers: AdjustableRecompiler_Array1d;
        recompiler: StaticRecompiler;
        adjustable: AdjustableRecompiler;
        index: int;
        i: int;
        c: int;
        s: AnsiString;
    begin
        inherited afterConstruction();
        owner := self.owner;
        { Вкладка «Эмулятор» }
        algorithms := toTRadioButtonArray1d([
            graphicScalingSystem, graphicScalingNearest, graphicScalingScaleNx,
            graphicScalingHqNx, graphicScalingStandard
        ]);
        fullscreenOnLaunch.checked := owner.getSetting(SECTION_EMULATOR, KEY_AUTO_FULLSCREEN, '0') = '1';
        closeOnTerminate.checked := owner.getSetting(SECTION_EMULATOR, KEY_AUTO_CLOSEWND, '0') = '1';
        s := toLowerCase(owner.getSetting(SECTION_EMULATOR, KEY_GRAPHIC_SCALING, ''));
        c := 0;
        for i := 0 to length(SCALING_ALGORITHMS) - 1 do begin
            if s = toLowerCase(SCALING_ALGORITHMS[i]) then begin
                c := i;
                break;
            end;
        end;
        algorithms[c].checked := true;
        maximumFramesValue.position := parseDecInt(owner.getSetting(SECTION_EMULATOR, KEY_MAXIMUM_FPS, ''), 25);
        maximumFramesValueChange(maximumFramesValue);
        { Вкладка «Тема» }
        c := getThemesCount();
        with themeSelector.items do begin
            for i := 0 to c - 1 do begin
                add(getTheme(i).getName());
            end;
        end;
        index := 0;
        s := owner.getSetting(SECTION_EMULATOR, KEY_THEME, '');
        for i := 0 to c - 1 do begin
            if getTheme(i).getName() = s then begin
                index := i;
                break;
            end;
        end;
        themeSelector.itemIndex := index;
        drawTheme(getTheme(index));
        { Вкладка «Дизассемблер» }
        disasmColors := toIntArray1d([
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_EMPTY_DATA, ''), DEFAULT_DEBUGGER_COLOR_EMPTY_DATA),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_NORMAL_DATA, ''), DEFAULT_DEBUGGER_COLOR_NORMAL_DATA),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_OBJECT_DATA, ''), DEFAULT_DEBUGGER_COLOR_OBJECT_DATA),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_EXCEPT_DATA, ''), DEFAULT_DEBUGGER_COLOR_EXCEPT_DATA),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_OLDEBP_DATA, ''), DEFAULT_DEBUGGER_COLOR_OLDEBP_DATA),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_RETURN_DATA, ''), DEFAULT_DEBUGGER_COLOR_RETURN_DATA),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_IRETURN_DATA, ''), DEFAULT_DEBUGGER_COLOR_IRETURN_DATA),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_NORMAL_TYPE, ''), DEFAULT_DEBUGGER_COLOR_NORMAL_TYPE),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_OBJECT_TYPE, ''), DEFAULT_DEBUGGER_COLOR_OBJECT_TYPE),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_EXCEPT_TYPE, ''), DEFAULT_DEBUGGER_COLOR_EXCEPT_TYPE),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_OLDEBP_TYPE, ''), DEFAULT_DEBUGGER_COLOR_OLDEBP_TYPE),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_RETURN_TYPE, ''), DEFAULT_DEBUGGER_COLOR_RETURN_TYPE),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_IRETURN_TYPE, ''), DEFAULT_DEBUGGER_COLOR_IRETURN_TYPE),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_OVERFLOW_TYPE, ''), DEFAULT_DEBUGGER_COLOR_OVERFLOW_TYPE),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_FUNCTION_NAME, ''), DEFAULT_DEBUGGER_COLOR_FUNCTION_NAME),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_SOURCE_LINE, ''), DEFAULT_DEBUGGER_COLOR_SOURCE_LINE),
            parseHexInt(owner.getSetting(SECTION_DEBUGGER_COLORS, KEY_INHERITED, ''), DEFAULT_DEBUGGER_COLOR_INHERITED)
        ]);
        disasmColor.buttonColor := disasmColors[0];
        drawDisasm();
        { Вкладка «Статический рекомпилятор» }
        recompilers := AdjustableRecompiler_Array1d_create(getRecompilersCount());
        c := 0;
        for i := 0 to length(recompilers) - 1 do begin
            recompiler := getRecompiler(i);
            recompiler.getInterface(stringToGUID(ADJUSTABLE_RECOMPILER_GUID), adjustable);
            if adjustable <> nil then begin
                recompilers[c] := adjustable;
                inc(c);
            end;
        end;
        self.recompilers := AdjustableRecompiler_Array1d_create(c);
        arraycopy(recompilers, 0, self.recompilers, 0, c);
        with recompilerSelector.items do begin
            for i := 0 to c - 1 do begin
                add(recompilers[i].getPlatformName());
            end;
        end;
        recompilerSelector.itemIndex := 0;
        recompilerSelectorChange(recompilerSelector);
    end;

    procedure TSettingsForm.beforeDestruction();
    var
        owner: MainWindowInterface;
        i: int;
        r: AdjustableRecompiler;
        s: AnsiString;
    begin
        owner := self.owner;
        owner.setSetting(SECTION_EMULATOR, KEY_AUTO_FULLSCREEN, toSettingString(fullscreenOnLaunch.checked));
        owner.setSetting(SECTION_EMULATOR, KEY_AUTO_CLOSEWND, toSettingString(closeOnTerminate.checked));
        owner.setSetting(SECTION_EMULATOR, KEY_GRAPHIC_SCALING, getScalingAlgorithm());
        owner.setSetting(SECTION_EMULATOR, KEY_MAXIMUM_FPS, toDecString(maximumFramesValue.position));
        owner.setSetting(SECTION_EMULATOR, KEY_THEME, themeSelector.text);
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_EMPTY_DATA, toHexString(disasmColors[0], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_NORMAL_DATA, toHexString(disasmColors[1], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_OBJECT_DATA, toHexString(disasmColors[2], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_EXCEPT_DATA, toHexString(disasmColors[3], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_OLDEBP_DATA, toHexString(disasmColors[4], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_RETURN_DATA, toHexString(disasmColors[5], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_IRETURN_DATA, toHexString(disasmColors[6], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_NORMAL_TYPE, toHexString(disasmColors[7], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_OBJECT_TYPE, toHexString(disasmColors[8], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_EXCEPT_TYPE, toHexString(disasmColors[9], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_OLDEBP_TYPE, toHexString(disasmColors[10], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_RETURN_TYPE, toHexString(disasmColors[11], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_IRETURN_TYPE, toHexString(disasmColors[12], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_OVERFLOW_TYPE, toHexString(disasmColors[13], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_FUNCTION_NAME, toHexString(disasmColors[14], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_SOURCE_LINE, toHexString(disasmColors[15], 6));
        owner.setSetting(SECTION_DEBUGGER_COLORS, KEY_INHERITED, toHexString(disasmColors[16], 6));
        for i := 0 to length(recompilers) - 1 do begin
            r := recompilers[i];
            s := 'platform: ' + r.getPlatformName();
            owner.setSetting(s, KEY_ALIGNMENT, toSettingString(r.getAlignment()));
            owner.setSetting(s, KEY_COMPRESSION, toSettingString(r.getCompressionLevel()));
            owner.setSetting(s, KEY_STACK, toSettingString(r.getStackSize()));
            owner.setSetting(s, KEY_HEAP, toSettingString(r.getHeapSize() shr 10));
            owner.setSetting(s, KEY_EXECUTABLE, r.getOutputExecutableFileName());
        end;
        owner.saveSettings();
        inherited beforeDestruction();
    end;

    function TSettingsForm.getClass(): _Class;
    begin
        result := ClassData.create(classType());
    end;

    function TSettingsForm.asObject(): TObject;
    begin
        result := self;
    end;

    procedure TSettingsForm.putPixel(x, y, argb: int);
    var
        width: int;
        height: int;
        pindex: int;
    begin
        width := themeImage.width;
        height := themeImage.height;
        if (x < 0) or (y < 0) or (x >= width) or (y >= height) then begin
            exit;
        end;
        pindex := x + (height - y - 1) * width;
        themeImagePixels[pindex] := computePixel(themeImagePixels[pindex], argb, false, true);
    end;
{%endregion}

initialization {%region}
    SCALING_ALGORITHMS := toStringArray1d([
        '', 'near', 'scaleNx', 'hqNx', 'standard'
    ]);
{%endregion}

end.

