unit UnitTooltipPopup;
{
    Purpose:
        Show a single clipboard item using a Tooltip window - aka Tooltip Popup

    Notes:
        The ClearToolip must be the only way used to clear the tooltip,
        this ShowTooltip threads from infinitely looping waiting for a click,
        or another "Show" call

    Updates
        SHIFT & CTRL to paste & Dissmiss
        Full header Tooltip option

}


interface

uses Windows, CommCtrl, Messages, Controls, Classes, Graphics { for colors}
    , UnitFrmDummyUnicodeTooltip, UnitTWideChar, UnitClipQueue;




type TTooltipThread = class(TThread)
    private
        fx, fy : integer;
        fclip : TClipItem;
        findex : integer;
        fabort : boolean;
        ftooltip : TTooltipWindow;
        fshowing : boolean;
    protected
        procedure execute; override;
    public
        procedure abort;
        function aborted : boolean;
        procedure SetTooltip(tooltip : TTooltipWindow);
        procedure SetClip(clip : TClipItem; index : integer);
        procedure SetPoint(x,y : integer);
        function GetShowing : boolean;
end;
Type TTooltipPopup = class(TObject)
    private
        ClipIndex : integer;






        Tooltip : TTooltipWindow;
        tooltipthread : TTooltipThread;
    protected
    public
        constructor Create;

        procedure ShowTooltipNext( x,y : integer);
        procedure ShowTooltipPrev( x,y : integer);

end;




implementation


uses Forms, UnitKeyboardQuery, Math, Dialogs, SysUtils,
   UnitPaste, UnitFrmConfig, UnitMisc, System.UITypes;




constructor TTooltipPopup.Create;
begin
    inherited;
    Tooltip := TTooltipWindow.Create;
    Tooltip.SetMaxHeight(300);
    Tooltip.EnforceSizeRestriction(true);

    tooltipthread := TTooltipThread.create(true);
end;

//
// The tooltip index isn't altered until the tooltip is shown again
// while it's already visible
//
procedure TTooltipPopup.ShowTooltipNext(x,y : integer);
begin
    if (tooltipthread.GetShowing) then begin
        inc(ClipIndex);
        if (Cardinal(ClipIndex) >= ClipQueue.GetQueueCount) then ClipIndex := ClipQueue.GetQueueCount - 1;
    end;
    Tooltip.CloseTooltip;

    tooltipthread.abort;
    application.ProcessMessages;
    tooltipthread.SetPoint(x,y);
    tooltipthread.SetClip(clipqueue.GetClipItem(clipindex), clipindex);
    tooltipthread.SetTooltip(tooltip);
    tooltipthread.Execute;
    tooltipthread.abort;
end;
procedure TTooltipPopup.ShowTooltipPrev(x,y : integer);
begin
    if (tooltipthread.GetShowing) then begin
        dec(ClipIndex);
        if (ClipIndex < 0 ) then ClipIndex := 0;
    end;
    Tooltip.CloseTooltip;


    tooltipthread.abort;
    application.ProcessMessages;
    tooltipthread.SetPoint(x,y);
    tooltipthread.SetClip(clipqueue.GetClipItem(clipindex), clipindex);
    tooltipthread.SetTooltip(tooltip);
    tooltipthread.Execute;
    tooltipthread.abort;
end;

procedure TTooltipThread.abort;
begin
    self.fabort := true;
end;

function TTooltipThread.aborted: boolean;
begin
    result := self.Terminated
end;

procedure TTooltipThread.execute;
var s : string;

    ShiftPressed,
    CtrlPressed,
    LeftPressed,
    RightPressed  : boolean;

    //ci : TClipItem;
    wc : TWideChar;

    function GetHeader : string;
    var sid : string;
    begin
        // Generate header (simple of full
        //
        sid := '';
        if (cardinal(findex) = ClipQueue.GetQueueCount - 1) then begin
            sid := '(last)';
        end;
        if (FrmConfig.cbFullHeader.checked) then begin
            result := ''+IntToSTr(findex+1)+':'+sid+'  Click or Shift Pastes  |  RightClick or Ctrl Closes';
        end else begin
            result := IntToStr(findex+1)+':'+sid+' ';
        end;
    end;
var Clicked : boolean;
begin
    inherited;
    fabort := false;
    fshowing := true;
    //
    // Show the text or unicode version of clip
    //
    s := Getheader;
    wc := TWideChar.Create;
    wc.appendunicode(fclip.GetAsPlaintext);

    ftooltip.ShowTooltip(wc,point(fx,fy),false, s);
    myfree(wc);

    if (FrmConfig.cbCopyVisibleItem.Checked) then begin
        Paste.SetClipboardOnlyOnce;
        Paste.SendText('', fclip);
    end;

    // show
    // - wait for ESC to be pressed, or
    // - the user clicked this tooltip (handled via a Message)
    // - the user hit the hotkey again
    // Git rid of the window

    ShiftPressed := false;
    CtrlPressed := false;
    LeftPressed := false;

    Clicked := false;


    unitmisc.appendlog('detecting held keys');
    While not fabort and KeyboardQuery.IsPressed(VK_SHIFT) do begin
        mysleep(10);
    end;
    While not fabort and KeyboardQuery.IsPressed(VK_CONTROL) do begin
        mysleep(10);
    end;
    unitmisc.AppendLog('pre-pre-loop exit');
    if fabort then begin
        fTooltip.CloseTooltip;
        fabort := false;
        unitmisc.AppendLog('pre-loop exit');
        fshowing := false;
        EXIT;
    end;

    unitmisc.AppendLog('pre-loop');
    while not (
        ShiftPressed or
        CtrlPressed or
        KeyboardQuery.IsPressed(VK_ESCAPE) or
        Clicked or
        fabort
    ) do begin

        ShiftPressed := KeyboardQuery.IsPressed(VK_SHIFT);
        CtrlPressed := KeyboardQuery.IsPressed(VK_CONTROL);
        LeftPRessed := KeyboardQuery.IsClicked(leftButton);
        RightPRessed := KeyboardQuery.IsClicked(rightButton);

        Clicked := (LeftPressed or RightPressed)
            and fTooltip.IsHit(Mouse.CursorPos);

        Application.ProcessMessages;
        MySleep(50);
    end;
    unitmisc.AppendLog('post-loop');
    //
    // A second hotkey can dismiss this instace of the event.
    // Ctrl and Shift are allowed to be held to activate the hotkey a second time
    // to advance the current clip.
    //
    if (not fabort) then begin

        While not fabort and KeyboardQuery.IsPressed(VK_SHIFT) do begin
            mysleep(10);
        end;
        While not fabort and KeyboardQuery.IsPressed(VK_CONTROL) do begin
            mysleep(10);
        end;
        While not fabort and KeyboardQuery.IsClicked(LeftButton) do begin
            mysleep(10);
            application.ProcessMessages;
        end;
        While not fabort and KeyboardQuery.IsClicked (RightButton) do begin
            mysleep(10);
            application.ProcessMessages;
        end;

        //
        // In case SHIFT is part of the hotkey to show this window, wait for
        // it to be released before monitoring user input - then wait for it
        // to be released if it was user input
        //
        if ((LeftPressed) or (ShiftPressed)) and not fabort then begin
            Paste.SendText('', fclip);
        end;

    end;


    fabort := true;
    //fabort := false;
    fshowing := false;
    ftooltip.CloseTooltip;
end;

function TTooltipThread.GetShowing: boolean;
begin
    result := fshowing;
end;

procedure TTooltipThread.SetClip(clip: TClipItem; index : integer);
begin
    fclip := clip;
    findex := index;
end;

procedure TTooltipThread.SetPoint(x, y: integer);
begin
    fx := x;
    fy := y;
end;

procedure TTooltipThread.SetTooltip(tooltip: TTooltipWindow);
begin
    ftooltip := tooltip;
end;

end.
