{------------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: SynURIOpener.pas, released 2003-09-25. The Initial Author of this file is Maël Hörz. Unicode translation by Maël Hörz. All Rights Reserved. Contributors to the SynEdit project are listed in the Contributors.txt file. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 2 or later (the "GPL"), in which case the provisions of the GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL and not to allow others to use your version of this file under the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under either the MPL or the GPL. You may retrieve the latest version of SynEdit from the SynEdit home page, located at http://SynEdit.SourceForge.net -------------------------------------------------------------------------------} { @abstract(Plugin for SynEdit to make links (URIs) clickable) @author(Maël Hörz) @created(2003) @lastmod(2004-03-19) The SynURIOpener unit extends SynEdit to make links highlighted by SynURISyn clickable. http://www.mh-net.de.vu } {$IFNDEF QSYNURIOPENER} unit SynURIOpener; {$ENDIF} {$I SynEdit.inc} interface uses {$IFDEF SYN_LINUX} Xlib, {$ELSE} Windows, {$ENDIF} Controls, SynEditTypes, SynEdit, SynHighlighterURI, SynUnicode, Classes; type TSynURIOpener = class(TComponent) private FControlDown: Boolean; FCtrlActivatesLinks: Boolean; FEditor: TCustomSynEdit; FMouseDownX: Integer; FMouseDownY: Integer; FURIHighlighter: TSynURISyn; FVisitedURIs: TStringList; {$IFDEF SYN_LINUX} FFtpClientCmd: string; FGopherClientCmd: string; FMailClientCmd: string; FNewsClientCmd: string; FNntpClientCmd: string; FProsperoClientCmd: string; FTelnetClientCmd: string; FWaisClientCmd: string; FWebBrowserCmd: string; {$ENDIF} procedure OpenLink(URI: string; LinkType: Integer); function MouseInSynEdit: Boolean; protected procedure NewKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure NewKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure NewMouseCursor(Sender: TObject; const aLineCharPos: TBufferCoord; var aCursor: TCursor); procedure NewMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure NewMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure Notification(AComponent: TComponent; Operation: TOperation); override; procedure SetEditor(const Value: TCustomSynEdit); procedure SetURIHighlighter(const Value: TSynURISyn); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function VisitedURI(URI: UnicodeString): Boolean; published property CtrlActivatesLinks: Boolean read FCtrlActivatesLinks write FCtrlActivatesLinks default True; property Editor: TCustomSynEdit read FEditor write SetEditor; property URIHighlighter: TSynURISyn read FURIHighlighter write SetURIHighlighter; {$IFDEF SYN_LINUX} // examples how to set WebBrowserCmd; %s is the placeholder for the URI // 'kfmclient openURL %s' // 'mozilla %s' // 'netscape %s' // 'kfmclient exec %s' similar to Windows ShellExecute // // You should let the user set these properties as there is no command // or environment variable valid/available on all UN*X-systems. // It depends on what window-manager and browser is installed. property FtpClientCmd: string read FFtpClientCmd write FFtpClientCmd; property GopherClientCmd: string read FGopherClientCmd write FGopherClientCmd; property MailClientCmd: string read FMailClientCmd write FMailClientCmd; property NewsClientCmd: string read FNewsClientCmd write FNewsClientCmd; property NntpClientCmd: string read FNntpClientCmd write FNntpClientCmd; property ProsperoClientCmd: string read FProsperoClientCmd write FProsperoClientCmd; property TelnetClientCmd: string read FTelnetClientCmd write FTelnetClientCmd; property WaisClientCmd: string read FWaisClientCmd write FWaisClientCmd; property WebBrowserCmd: string read FWebBrowserCmd write FWebBrowserCmd; {$ENDIF} end; implementation uses {$IFDEF SYN_LINUX} Libc, {$ELSE} ShellAPI, {$ENDIF} Forms, SynEditHighlighter, SynEditKeyConst, SysUtils; type TAccessCustomSynEdit = class(TCustomSynEdit); TAccessSynURISyn = class(TSynURISyn); { TSynURIOpener } constructor TSynURIOpener.Create(AOwner: TComponent); begin inherited; FCtrlActivatesLinks := True; FVisitedURIs := TStringList.Create; FVisitedURIs.Sorted := True; end; destructor TSynURIOpener.Destroy; begin FVisitedURIs.Free; inherited; end; function TSynURIOpener.MouseInSynEdit: Boolean; var pt: TPoint; begin {$IFDEF SYN_COMPILER_6_UP} pt := Mouse.CursorPos; {$ELSE} GetCursorPos(pt); {$ENDIF} Result := PtInRect(FEditor.ClientRect, FEditor.ScreenToClient(pt)) end; procedure TSynURIOpener.NewKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if (Key = SYNEDIT_CONTROL) and not FControlDown and MouseInSynEdit then begin FControlDown := True; TAccessCustomSynEdit(FEditor).UpdateMouseCursor; end; end; procedure TSynURIOpener.NewKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin if (Key = SYNEDIT_CONTROL) and FControlDown then begin FControlDown := False; TAccessCustomSynEdit(FEditor).UpdateMouseCursor; end; end; function IsControlPressed: Boolean; {$IFDEF SYN_LINUX} var keymap: TXQueryKeyMap; {$ENDIF} begin {$IFDEF SYN_LINUX} XQueryKeymap(Xlib.PDisplay(QtDisplay), keymap); Result := (Byte(keymap[4]) and $20 = $20); {$ELSE} Result := GetAsyncKeyState(VK_CONTROL) <> 0; {$ENDIF} end; procedure TSynURIOpener.NewMouseCursor(Sender: TObject; const aLineCharPos: TBufferCoord; var aCursor: TCursor); var TokenType, Start: Integer; Token: UnicodeString; Attri: TSynHighlighterAttributes; begin FControlDown := IsControlPressed; if not(FCtrlActivatesLinks and not FControlDown or (csDesigning in FEditor.ComponentState)) and FEditor.Focused then with FEditor do begin GetHighlighterAttriAtRowColEx(aLineCharPos, Token, TokenType, Start, Attri); if Assigned(URIHighlighter) and ((Attri = URIHighlighter.URIAttri) or (Attri = URIHighlighter.VisitedURIAttri)) and not((eoDragDropEditing in Options) and IsPointInSelection(aLineCharPos)) then aCursor := crHandPoint end end; procedure TSynURIOpener.NewMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if (Button = mbLeft) and not(FCtrlActivatesLinks) or FControlDown then begin FMouseDownX := X; FMouseDownY := Y; end end; procedure TSynURIOpener.NewMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var ptLineCol: TBufferCoord; TokenType, Start: Integer; Token: UnicodeString; Attri: TSynHighlighterAttributes; begin if (Button <> mbLeft) or (FCtrlActivatesLinks and not FControlDown) or (Abs(FMouseDownX - X) > 4) or (Abs(FMouseDownY - Y) > 4) then Exit; with TAccessCustomSynEdit(FEditor) do begin if (eoDragDropEditing in Options) and IsPointInSelection(ptLineCol) then Exit; if X >= fGutterWidth then begin ptLineCol := DisplayToBufferPos(PixelsToRowColumn(X,Y)); GetHighlighterAttriAtRowColEx(ptLineCol, Token, TokenType, Start, Attri); if Assigned(URIHighlighter) and ((Attri = URIHighlighter.URIAttri) or (Attri = URIHighlighter.VisitedURIAttri)) and not((eoDragDropEditing in Options) and IsPointInSelection(ptLineCol)) then begin OpenLink(Token, TokenType); InvalidateLine(ptLineCol.Line); end; end end; end; procedure TSynURIOpener.Notification(AComponent: TComponent; Operation: TOperation); begin inherited; if (Operation = opRemove) and Assigned(Editor) and (AComponent = Editor) then Editor := nil; if (Operation = opRemove) and Assigned(URIHighlighter) and (AComponent = URIHighlighter) then URIHighlighter := nil; end; procedure TSynURIOpener.OpenLink(URI: string; LinkType: Integer); {$IFDEF SYN_LINUX} var CmdLine: string; {$ENDIF} begin FVisitedURIs.Add(URI); case TtkTokenKind(LinkType) of tkMailtoLink: if (Pos('mailto:', URI) <> 1) then URI := 'mailto:' + URI; tkWebLink: URI := 'http://' + URI; end; {$IFDEF SYN_LINUX} case TtkTokenKind(LinkType) of tkFtpLink: CmdLine := Format(FFtpClientCmd, [URI]); tkGopherLink: CmdLine := Format(FGopherClientCmd, [URI]); tkMailtoLink: CmdLine := Format(FMailClientCmd, [URI]); tkNewsLink: CmdLine := Format(FNewsClientCmd, [URI]); tkNntpLink: CmdLine := Format(FNntpClientCmd, [URI]); tkProsperoLink: CmdLine := Format(FProsperoClientCmd, [URI]); tkTelnetLink: CmdLine := Format(FTelnetClientCmd, [URI]); tkWaisLink: CmdLine := Format(FWaisClientCmd, [URI]); tkWebLink, tkHttpLink, tkHttpsLink: CmdLine := Format(FWebBrowserCmd, [URI]); end; Libc.system(PAnsiChar(CmdLine + ' &')); // add an ampersand to return immediately {$ELSE} ShellExecute(0, nil, PChar(URI), nil, nil, 1{SW_SHOWNORMAL}); {$ENDIF} end; procedure TSynURIOpener.SetEditor(const Value: TCustomSynEdit); begin if Editor <> Value then begin if not(csDesigning in ComponentState) and Assigned(FEditor) then begin with FEditor do begin RemoveKeyDownHandler(NewKeyDown); RemoveKeyUpHandler(NewKeyUp); RemoveMouseCursorHandler(NewMouseCursor); RemoveMouseDownHandler(NewMouseDown); RemoveMouseUpHandler(NewMouseUp); end; end; FEditor := Value; if not(csDesigning in ComponentState) and Assigned(FEditor) then begin with FEditor do begin AddKeyDownHandler(NewKeyDown); AddKeyUpHandler(NewKeyUp); AddMouseCursorHandler(NewMouseCursor); AddMouseDownHandler(NewMouseDown); AddMouseUpHandler(NewMouseUp); end; end; end; end; procedure TSynURIOpener.SetURIHighlighter(const Value: TSynURISyn); begin if not(csDesigning in ComponentState) and Assigned(URIHighlighter) then TAccessSynURISyn(FURIHighlighter).SetAlreadyVisitedURIFunc(nil); FURIHighlighter := Value; if not(csDesigning in ComponentState) and Assigned(URIHighlighter) then TAccessSynURISyn(FURIHighlighter).SetAlreadyVisitedURIFunc(VisitedURI); end; function TSynURIOpener.VisitedURI(URI: UnicodeString): Boolean; var Dummy: Integer; begin Result := FVisitedURIs.Find(URI, Dummy); end; const IDC_LINK = MakeIntResource(32649); var CursorHandle: THandle; initialization CursorHandle := LoadCursor(0, IDC_LINK); if CursorHandle <> 0 then Screen.Cursors[crHandPoint] := CursorHandle; end.