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

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

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

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

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

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

unit ProgrammesListFrame;

{$MODE DELPHI}

interface

uses
    Classes,
    SysUtils,
    Forms,
    Graphics,
    Controls,
    StdCtrls,
    Types,
    LCLType,
    Lang,
    Images,
    Manifests,
    EmulConstants,
    EmulThemes,
    EmulProgrammes,
    EmulatorInterfaces;

{%region public }
type
    TProgrammesList = class(TFrame, _Interface, GraphicListener)
        programmesList: TListBox;
        procedure programmesListDrawItem(control: TWinControl; index: integer; arect: TRect; state: TOwnerDrawState);
    private
        cursorWithFocus: TBitmap;
        cursorWithoutFocus: TBitmap;
        image: TBitmap;
        progIcon: TIcon;
        info: ProgrammeInfo_Array1d;
        manifest: ProgrammeManifest;
        guiTheme: Theme;
    public
        constructor create(theOwner: TComponent); override;
        destructor destroy; override;
        { _Interface }
        function getClass(): _Class;
        function asObject(): TObject;
        { GraphicListener }
        procedure putPixel(x, y, argb: int);
        { Собственные методы }
        procedure setProgrammesInfo(const info: ProgrammeInfo_Array1d; count: int);
        function getProgrammesInfo(): ProgrammeInfo_Array1d;
        function getProgrammesCount(): int;
        function getSelectedProgrammeInfo(): ProgrammeInfo;
    end;
{%endregion}

implementation

{$R *.LFM}

{%region TProgrammesList }
    constructor TProgrammesList.create(theOwner: TComponent);
    begin
        inherited create(theOwner);
        cursorWithFocus := TBitmap.create();
        cursorWithoutFocus := TBitmap.create();
        progIcon := TIcon.create();
        manifest := ProgrammeManifest.create();
        guiTheme := getTheme(0);
    end;

    destructor TProgrammesList.destroy;
    begin
        manifest.free();
        progIcon.free();
        cursorWithoutFocus.free();
        cursorWithFocus.free();
        inherited destroy;
    end;

    procedure TProgrammesList.programmesListDrawItem(control: TWinControl; index: integer; arect: TRect; state: TOwnerDrawState);
    var
        canvas: TCanvas;
        clip: TRect;
        m: ProgrammeManifest;
        n: TIcon;
        i: int;
        j: int;
        c: int;
        w: int;
        h: int;
        tw: int;
        s1: AnsiString;
        s2: AnsiString;
        s: UnicodeString;
    begin
        m := manifest;
        n := progIcon;
        with info[index] do begin
            try
                loadManifest(m);
                loadIcon(n);
            except
                exit;
            end;
        end;
        canvas := TListBox(control).canvas;
        canvas.clipRect := arect;
        if TOwnerDrawStateType.odSelected in state then begin
            if control.focused() then begin
                i := $000101;
                image := cursorWithFocus;
            end else begin
                i := $000001;
                image := cursorWithoutFocus;
            end;
            w := arect.right - arect.left;
            h := arect.bottom - arect.top;
            if (image.empty) or (image.width <> w) or (image.height <> h) then begin
                image.clear();
                image.setSize(w, h);
                with image.canvas do begin
                    brush.style := bsSolid;
                    brush.color := clWindow;
                    fillRect(0, 0, w, h);
                end;
                guiTheme.drawElement(self, i, 0, 0, w, h);
            end;
            canvas.draw(arect.left, arect.top, image);
        end else begin
            canvas.brush.style := bsSolid;
            canvas.brush.color := clWindow;
            canvas.fillRect(arect);
        end;
        clip.left := arect.left + 2;
        clip.top := arect.top + 2;
        clip.right := clip.left + 48;
        clip.bottom := clip.top + 48;
        canvas.clipRect := clip;
        findBestIconSize(n, 48);
        w := n.width;
        h := n.height;
        if (w > 48) or (h > 48) then begin
            if w >= h then begin
                h := int(round(48.0 * toReal(h) / toReal(w)));
                w := 48;
            end else begin
                w := int(round(48.0 * toReal(w) / toReal(h)));
                h := 48;
            end;
            canvas.stretchDraw(bounds(clip.left + ((48 - w) div 2), clip.top + ((48 - h) div 2), w, h), n);
        end else begin
            canvas.draw(clip.left + ((48 - w) div 2), clip.top + ((48 - h) div 2), n);
        end;
        clip.left := arect.left + 2;
        clip.top := arect.top + 2;
        clip.right := arect.right - 2;
        clip.bottom := arect.bottom - 2;
        canvas.clipRect := clip;
        s1 := trim(m.getValue(MANIFEST_PROPERTY_PROGRAMME_NAME));
        s2 := trim(m.getValue(MANIFEST_PROPERTY_PROGRAMME_DESCRIPTION));
        canvas.brush.style := bsClear;
        canvas.font.color := clBlack;
        if length(s2) > 0 then begin
            canvas.textOut(arect.left + 52, arect.top + 2, s1);
        end else begin
            canvas.textOut(arect.left + 52, arect.top + 18, s1);
        end;
        canvas.font.color := clGray;
        s1 := s2;
        s2 := '';
        s := toUTF16String(s1);
        tw := arect.right - arect.left - 54;
        w := 0;
        i := 1;
        while i <= length(s) do begin
            c := int(s[i]);
            if (c >= $d800) and (c <= $dbff) and (i < length(s)) then begin
                inc(w, canvas.textWidth(toUTF8String(s[i] + s[i + 1])));
                inc(i);
            end else begin
                inc(w, canvas.textWidth(toUTF8String(s[i])));
            end;
            if w > tw then begin
                for j := i downto 1 do begin
                    if s[j] = #32 then begin
                        s1 := toUTF8String(copy(s, 1, j - 1));
                        s2 := toUTF8String(copy(s, j + 1, length(s) - j));
                        break;
                    end;
                end;
                break;
            end;
            inc(i);
        end;
        canvas.textOut(arect.left + 52, arect.top + 18, s1);
        canvas.textOut(arect.left + 52, arect.top + 34, s2);
    end;

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

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

    procedure TProgrammesList.putPixel(x, y, argb: int);
    var
        canvas: TCanvas;
    begin
        canvas := image.canvas;
        canvas.pixels[x, y] := byteSwap(computePixel(byteSwap(canvas.pixels[x, y]) shr 8, argb, false, true)) shr 8;
    end;

    procedure TProgrammesList.setProgrammesInfo(const info: ProgrammeInfo_Array1d; count: int);
    var
        i: int;
        m: ProgrammeManifest;
        list: TListBox;
        items: TStrings;
        event: TSelectionChangeEvent;
    begin
        self.info := ProgrammeInfo_Array1d_create(count);
        arraycopy(info, 0, self.info, 0, count);
        m := manifest;
        list := programmesList;
        items := list.items;
        items.beginUpdate();
        try
            items.clear();
            for i := 0 to count - 1 do begin
                info[i].loadManifest(m);
                items.add(m.getValue(MANIFEST_PROPERTY_PROGRAMME_NAME));
            end;
            if count > 0 then begin
                list.itemIndex := 0;
            end else begin
                list.itemIndex := -1;
            end;
            event := list.onSelectionChange;
            if @event <> nil then begin
                event(list, false);
            end;
        finally
            items.endUpdate();
        end;
    end;

    function TProgrammesList.getProgrammesInfo(): ProgrammeInfo_Array1d;
    begin
        result := info;
    end;

    function TProgrammesList.getProgrammesCount(): int;
    begin
        result := length(info);
    end;

    function TProgrammesList.getSelectedProgrammeInfo(): ProgrammeInfo;
    var
        i: int;
    begin
        i := programmesList.itemIndex;
        if (i >= 0) and (i < length(info)) then begin
            result := info[i];
        end else begin
            result := nil;
        end;
    end;
{%endregion}

end.

