unit fImages;   //kt Entire unit and form added 8/19/05
{$O-}
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  fPage, StdCtrls, ExtCtrls, Menus, ComCtrls, ORCtrls, ORFn, uConst, ORDtTm,
  uPCE, ORClasses, fDrawers, ImgList, rTIU, uTIU, uDocTree, fRptBox, fPrintList,
  OleCtrls, SHDocVw,
  ORNet, TRPCB, fHSplit, Buttons, ExtDlgs, DKLang;
type
  TImgDelMode = (idmNone,idmDelete,idmRetract); //NOTE: DO NOT change order
  TImageInfo = class
    private
    public
      IEN :                int64;    //IEN in file# 2005
      ServerPathName :     AnsiString;
      ServerFName :        AnsiString;
      ServerThumbPathName: AnsiString;
      ServerThumbFName :   AnsiString;
      //Note: if there is no thumbnail to download, CacheThumbFName will still
      //      contain a file name and path, but a test for FileExists() will wail.
      CacheThumbFName :    AnsiString; // local cache path and File name of thumbnail image
      CacheFName :         AnsiString; // local cache path and File name of image
      ShortDesc :          AnsiString;
      LongDesc :           TStringList; //will be nil unless holds data.
      DateTime :           AnsiString;  //fileman format
      ImageType :          Integer;
      ProcName :           AnsiString;
      DisplayDate :        AnsiString;
      ParentDataFileIEN:   int64;
      AbsType :            char;      //'M' magnetic 'W' worm  'O' offline
      Accessibility :      char;      //'A' accessable  or  'O' offline
      DicomSeriesNum :     int64;
      DicomImageNum :      int64;
      GroupCount :         integer;
      TabIndex :           integer;
      TabImageIndex :      integer;
    published
  end;
  TImgTransferMethod = (itmDropbox,itmDirect,itmRPC);
  TfrmImages = class(TfrmPage)
    mnuNotes: TMainMenu;
    mnuView: TMenuItem;
    mnuViewChart: TMenuItem;
    mnuChartReports: TMenuItem;
    mnuChartLabs: TMenuItem;
    mnuChartDCSumm: TMenuItem;
    mnuChartCslts: TMenuItem;
    mnuChartNotes: TMenuItem;
    mnuChartOrders: TMenuItem;
    mnuChartMeds: TMenuItem;
    mnuChartProbs: TMenuItem;
    mnuChartCover: TMenuItem;
    mnuAct: TMenuItem;
    Z3: TMenuItem;
    mnuOptions: TMenuItem;
    timLoadImages: TTimer;
    N3: TMenuItem;
    mnuIconLegend: TMenuItem;
    mnuChartSurgery: TMenuItem;
    ThumbsImageList: TImageList;
    CurrentNoteMemo: TMemo;
    pnlTop: TPanel;
    HorizSplitter: TSplitter;
    Splitter2: TSplitter;
    UploadImagesButton: TBitBtn;
    OpenPictureDialog: TOpenPictureDialog;
    ButtonPanel: TPanel;
    CurrentImageMemo: TMemo;
    MemosPanel: TPanel;
    UploadImagesMnuAction: TMenuItem;
    pnlBottom: TPanel;
    TabControl: TTabControl;
    WebBrowser: TWebBrowser;
    AutoScanUpload: TMenuItem;
    PickScanFolder: TMenuItem;
    OpenDialog: TOpenDialog;
    mnuPopup: TPopupMenu;
    mnuPopDeleteImage: TMenuItem;
    mnuDeleteImage: TMenuItem;
    procedure mnuChartTabClick(Sender: TObject);
    procedure mnuActNewClick(Sender: TObject);
    procedure timLoadImagesTimer(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure mnuActClick(Sender: TObject);
    procedure UploadImagesButtonClick(Sender: TObject);
    procedure FormHide(Sender: TObject);
    procedure TabControlChange(Sender: TObject);
    procedure TabControlGetImageIndex(Sender: TObject; TabIndex: Integer;
      var ImageIndex: Integer);
    procedure TabControlResize(Sender: TObject);
    procedure EnableAutoScanUploadClick(Sender: TObject);
    procedure PickScanFolderClick(Sender: TObject);
    procedure TabControlMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure mnuPopupPopup(Sender: TObject);
    procedure mnuPopDeleteImageClick(Sender: TObject);
    procedure mnuDeleteImageClick(Sender: TObject);
  private
    ImageInfoList : TList;
    LastDisplayedTIUIEN : AnsiString;
    ImageIndexLastDownloaded : integer;
    FDeleteImageIndex : integer;
    FEditIsActive : boolean;
    FImageDeleteMode : TImgDelMode;
    procedure EnsureImageListLoaded();
    procedure ClearImageList();
    procedure DownloadToCache(ImageIndex : integer);
    procedure EmptyCache();
    procedure ClearTabPages();
    procedure SetupTab(i : integer);
    procedure UpdateNoteInfoMemo();
    procedure UpdateImageInfoMemo(Rec: TImageInfo);
    function FileSize(fileName : wideString) : Int64;
    function GetImagesCount : integer;
    function GetImageInfo(Index : integer) : TImageInfo;
    procedure SetupTimer;
    function CanDeleteImages : boolean;
    procedure DeleteImageIndex(ImageIndex : integer; DeleteMode : TImgDelMode; boolPromptUser: boolean);
    procedure DeleteImage(var DeleteSts: TActionRec; ImageFileName: string; ImageIEN, DocIEN: Integer;
                          DeleteMode : TImgDelMode; const Reason: string);
  public
    CacheDir : AnsiString;
    TransferMethod : TImgTransferMethod;
    DropBoxDir : string;
    NullImageName : AnsiString;
    NumImagesAvailableOnServer : integer;
    DownloadImagesInBackground : boolean;
    function Decode(input: AnsiString) : AnsiString;
    function Encode(input: AnsiString) : AnsiString;
    function DownloadFileViaDropbox(FPath,FName,LocalSaveFNamePath: AnsiString;CurrentImage,TotalImages: Integer): boolean;
    function DownloadFile(FPath,FName,LocalSaveFNamePath: AnsiString;CurrentImage,TotalImages: Integer): boolean;
    function UploadFileViaDropBox(LocalFNamePath,FPath,FName: AnsiString;CurrentImage,TotalImages: Integer): boolean;
    function UploadFile(LocalFNamePath,FPath,FName: AnsiString;CurrentImage,TotalImages: Integer): boolean;
    procedure SplitLinuxFilePath(FullPathName : AnsiString;
                                 var Path : AnsiString;
                                 var FName : AnsiString);
    procedure GetImageList();
    procedure NewNoteSelected(EditIsActive : boolean);
    function CreateBarcode(MsgStr: AnsiString; ImageType: AnsiString): AnsiString;
    function DecodeBarcode(LocalFNamePath,ImageType: AnsiString): AnsiString;
    procedure EnsureImagesDownloaded(ImagesList : TStringList);
    procedure EnsureALLImagesDownloaded;
    procedure DeleteAll(DeleteMode: TImgDelMode);
    property ImagesCount : integer read GetImagesCount;
    property ImageInfo[index : integer] : TImageInfo read GetImageInfo;
    procedure GetThumbnailBitmapForFName (FName : string; Bitmap : TBitmap);
    function ThumbnailIndexForFName (FName : string) : integer;
  published
  end;
Const
  IMAGE_TRANSFER_METHODS : Array[itmDropbox..itmRPC] of string[32] = (
    'Dropbox Transfer', 'Direct Access', 'Embedded in RPCs');
  IMAGE_DOWNLOAD_DELAY_BACKGROUND = 30000;
  IMAGE_DOWNLOAD_DELAY_FOREGROUND = 100;
  NOT_YET_CHECKED_SERVER = -2;
  //NOTE: If order is changed in ThumbsImageList, these numbers should be changed
  IMAGE_INDEX_IMAGE = 0;
  IMAGE_INDEX_ADOBE  = 1;
  IMAGE_INDEX_VIDEO  = 2;
  IMAGE_INDEX_SOUND  = 3;
  IMAGE_INDEX_MISC   = 4;
var
  frmImages: TfrmImages;
implementation
{$R *.DFM}
uses fFrame, fVisit, fEncnt, rCore, uCore, fNoteBA, fNoteBD, fSignItem, fEncounterFrame,
     rPCE, Clipbrd, fNoteCslt, fNotePrt, rVitals, fAddlSigners, fNoteDR, fConsults, uSpell,
     fTIUView, fTemplateEditor, uReminders, fReminderDialog, uOrders, rConsults, fReminderTree,
     fNoteProps, fNotesBP, fTemplateFieldEditor, dShared, rTemplates,
     FIconLegend, fPCEEdit, fNoteIDParents, rSurgery, uSurgery, uTemplates,
     uAccessibleTreeView, uAccessibleTreeNode, fTemplateDialog, DateUtils,
     StrUtils, mshtml, UploadImages,
     uTMGOptions,  //kt 3/10/10
     rHTMLTools, fNotes, frmImageTransferProgress, fImagePickExisting;  {//kt added 5-27-05 for IsHTMLDocument}
procedure TfrmImages.FormCreate(Sender: TObject);
//var i : integer;
begin
  inherited;
  LastDisplayedTIUIEN := '0';
  FDeleteImageIndex := -1;
  ImageInfoList := TList.Create;
  ClearImageList(); //sets up other needed variables.
  DownloadImagesInBackground := true;
  CacheDir := ExtractFilePath(ParamStr(0))+ 'Cache';
  NullImageName := 'about:blank';
  if not DirectoryExists(CacheDir) then ForceDirectories(CacheDir);
  TransferMethod := TImgTransferMethod(uTMGOptions.ReadInteger('ImageTransferMethod',2));
  DropBoxDir := uTMGOptions.ReadString('Dropbox directory','??');
  if DropBoxDir='??' then begin  //just on first run.
    uTMGOptions.WriteBool('Use dropbox directory for transfers',false);
    uTMGOptions.WriteString('Dropbox directory','');
  end;
  AutoScanUpload.Checked := uTMGOptions.ReadBool('Scan Enabled',false);
end;
procedure TfrmImages.FormDestroy(Sender: TObject);
begin
  inherited;
  ClearImageList;
  ImageInfoList.Free;
  EmptyCache;
end;
procedure TfrmImages.FormShow(Sender: TObject);
var  TIUIEN : AnsiString;
begin
  inherited;
  mnuDeleteImage.Enabled := CanDeleteImages;
  TIUIEN := IntToStr(frmNotes.lstNotes.ItemID);
  DownloadImagesInBackground := false;
  SetupTimer;
  if LastDisplayedTIUIEN <> TIUIEN then begin
    UpdateNoteInfoMemo();
    LastDisplayedTIUIEN := TIUIEN;
  end;
end;
procedure TfrmImages.timLoadImagesTimer(Sender: TObject);
//This function's goal is to download images in the background,
// with one image to be downloaded each time the timer fires
begin
  inherited;
  timLoadImages.Enabled := false;
  EnsureImageListLoaded();
  if NumImagesAvailableOnServer = 0 then exit;
  if (ImageIndexLastDownloaded >= (ImageInfoList.Count-1)) then exit;
  ImageTransferForm.ProgressMsg.Caption := 'Downloading Images';
  DownloadToCache(ImageIndexLastDownloaded+1); //Only load 1 image per timer firing.
  SetupTab(ImageIndexLastDownloaded+1);
  Inc(ImageIndexLastDownloaded);
  if TabControl.TabIndex < 0 then TabControl.TabIndex := 0;
  TabControlChange(self);
  SetupTimer;
end;
procedure TfrmImages.SetupTimer;
begin
  if DownloadImagesInBackground then begin
    timLoadImages.Interval := IMAGE_DOWNLOAD_DELAY_BACKGROUND;
  end else begin
    timLoadImages.Interval := IMAGE_DOWNLOAD_DELAY_FOREGROUND;
  end;
  timLoadImages.Enabled := true;
end;
procedure TfrmImages.EnsureImagesDownloaded(ImagesList : TStringList);
//This function's goal is to download images in the FOREground,
// But only images matching those passed in ImagesList will be downloaded;
// The intent is to only download images that have links to them in HTML source
//Thus, if note has a large amount of images attached to it, but not referenced
//  in HTML code, then they will not be downloaded here. (But will be downloaded
//  later via timLoadImagesTimer
var i : integer;
    Rec : TImageInfo;
begin
  if ImagesList.Count = 0 then exit;
  GetImageList();
  if ImageInfoList.Count = 0 then exit;
  if ImageInfoList.Count > 1 then begin
    ImageTransferForm.ProgressMsg.Caption := 'Downloading Images';
    ImageTransferForm.ProgressBar.Min := 0;
    ImageTransferForm.ProgressBar.Position := 0;
    ImageTransferForm.ProgressBar.Max := ImageInfoList.Count-1;
    ImageTransferForm.Show;
  end;
  for i := 0 to ImageInfoList.Count-1 do begin
    ImageTransferForm.ProgressBar.Position := i;
    Rec := TImageInfo(ImageInfoList[i]);
    if ImagesList.IndexOf(Rec.ServerFName)>-1 then begin
      DownloadToCache(i);
    end;
  end;
  ImageTransferForm.Hide;
end;
procedure TfrmImages.EnsureALLImagesDownloaded;
//This function's goal is to download ALL images in the FOREground.
begin
  EnsureImageListLoaded();
  if NumImagesAvailableOnServer = 0 then exit;
  ImageTransferForm.ProgressMsg.Caption := 'Downloading Images';
  while (ImageIndexLastDownloaded < (ImageInfoList.Count-1)) do begin
    DownloadToCache(ImageIndexLastDownloaded+1); //Only load 1 image per timer firing.
    SetupTab(ImageIndexLastDownloaded+1);
    Inc(ImageIndexLastDownloaded);
    if TabControl.TabIndex < 0 then TabControl.TabIndex := 0;
    TabControlChange(self);
  end;
end;
{ TPage common methods --------------------------------------------------------------------- }
procedure TfrmImages.mnuChartTabClick(Sender: TObject);
{ reroute to Chart Tab menu of the parent form: frmFrame }
begin
  inherited;
  frmFrame.mnuChartTabClick(Sender);
end;
procedure TfrmImages.mnuActNewClick(Sender: TObject);
const
  IS_ID_CHILD = False;
{ switches to current new note or creates a new note if none is being edited already }
begin
  inherited;
end;
procedure TfrmImages.mnuActClick(Sender: TObject);
begin
  inherited;
end;
{ General procedures ----------------------------------------------------------------------- }
procedure TfrmImages.UpdateImageInfoMemo(Rec  : TImageInfo);
var s : AnsiString;
    i : integer;
begin
  CurrentImageMemo.Lines.Clear;
  if Rec=nil then exit;
  s := Trim(Rec.ShortDesc);
  if s <> '' then CurrentImageMemo.Lines.Add('Description: ' + s);
  s := Rec.ProcName;
  if s <> '' then CurrentImageMemo.Lines.Add('Procedure: ' + s);
  s := Rec.DisplayDate;
  if s <> '' then CurrentImageMemo.Lines.Add('Upload Date: ' + s);
  //s := Rec.DateTime;
  //if s <> '' then CurrentImageMemo.Lines.Add('Date/Time: ' + s);
  if Rec.LongDesc <> nil then begin
    CurrentImageMemo.Lines.Add('Long Description:');
    for i := 0 to Rec.LongDesc.Count-1 do begin
      CurrentImageMemo.Lines.Add('  ' + Rec.LongDesc.Strings[i]);
    end;
  end;
end;
procedure TfrmImages.UpdateNoteInfoMemo();
var
  NoteInfo,s : AnsiString;
  //dateS      : AnsiString;
const
  U='^';
begin
  CurrentNoteMemo.Lines.Clear;
  with frmNotes.lstNotes do begin
    if ItemIndex > -1 then begin
      NoteInfo := Items[ItemIndex]
      (* example NoteInfo:
        piece# 1:  14321^                        //TIU IEN
        piece# 2:  PRESCRIPTION CALL IN^         //Document Title
        piece# 3:  3050713.0947^                 //Date/Time
        piece# 4:  TEST, KILLME D (T0101)^       //Patient
        piece# 5:  133;JANE A DOE;DOE,JANE A^    //Author
        piece# 6:  Main_Office^                  //Location of Visit
        piece# 7:  completed^                    //Status of Document
        piece# 8:  Visit: 07/13/05;3050713.094721^ //Date/Time
        piece# 9...:          ;^^1^^^1^'         //?
      *)
    end else NoteInfo := '';
  end;
  if NoteInfo <>'' then begin
    s := Piece(NoteInfo, U, 2) + ' -- ';
    s := s + Piece(Piece(NoteInfo, U, 8), ';', 1);
    CurrentNoteMemo.Lines.Add(s);
    s := 'Location: ' + Piece(NoteInfo, U, 6) + ' -- ';
    s := s + 'Note Author: ' + Piece(Piece(NoteInfo, U, 5), ';', 2);
    CurrentNoteMemo.Lines.Add(s);
  end;
end;
procedure TfrmImages.SetupTab(i : integer);
//i is index in ImageInfoList (array of TImageInfo's)
var
  Rec  : TImageInfo; //this will be a copy of record, not pointer (I think)
  Bitmap : TBitmap;
  index : integer;
  Ext : AnsiString;
  (*Notice: A TabControl doesn't directly support specifying which
           images in an ImageList to show for a given tab.  To get
          around this, the help documentation recommends setting up
         a TabControlGetImageIndex event handler.
        I am doing this.  When the event is called, then RecInfo.TabImageIndex
       is returned.
  *)
begin
  if i < ImageInfoList.Count then begin
    Rec := TImageInfo(ImageInfoList[i]);
    if (Rec.TabImageIndex < 1) then begin
      if FileExists(Rec.CacheThumbFName) then begin
        Bitmap := TBitmap.Create;
        Bitmap.Width := 1024;    //something big enough to hold any thumbnail.
        Bitmap.Height := 768;
        Bitmap.LoadFromFile(Rec.CacheThumbFName);
        Bitmap.Width := ThumbsImageList.Width;  //shrinkage crops image
        Bitmap.Height := ThumbsImageList.Height;
        index := ThumbsImageList.Add(Bitmap,nil);
        //TImageInfo(ImageInfoList[i]).TabImageIndex := index;
        Rec.TabImageIndex := index;
        Bitmap.Free;
      end else begin
        Rec.TabImageIndex := ThumbnailIndexForFName(Rec.CacheFName);
      end;
    end;
    TabControl.Tabs.Add(' ');  //add the tab.  Thumbnail should exist before this
  end;
end;
function TfrmImages.ThumbnailIndexForFName (FName : string) : integer;
var
  index : integer;
  Ext : AnsiString;
begin
  Result := 4; //default
  Ext := LowerCase(ExtractFileExt(FName));
  Ext := MidStr(Ext,2,99);
  if   (Ext='jpg')
    or (Ext='jpeg')
    or (Ext='png')
    or (Ext='tif')
    or (Ext='tiff')
    or (Ext='gif')
    or (Ext='bmp') then begin
      Result := IMAGE_INDEX_IMAGE; //camera image
  end else
  if   (Ext='pdf') then begin
      Result := IMAGE_INDEX_ADOBE; //adobe icon
  end else
  if   (Ext='avi')
    or (Ext='qt')
    or (Ext='mpg')
    or (Ext='mpeg') then begin
      Result := IMAGE_INDEX_VIDEO; //video icon
  end else
  if   (Ext='mp3')
    or (Ext='wma')
    or (Ext='au')
    or (Ext='wav') then begin
      Result := IMAGE_INDEX_SOUND; //sound icon
  end else
  begin
    Result := IMAGE_INDEX_MISC; // misc icon
  end;
end;
procedure TfrmImages.GetThumbnailBitmapForFName (FName : string; Bitmap : TBitmap);
var index: integer;
begin
  index := ThumbnailIndexForFName(FName);
  ThumbsImageList.GetBitmap(index,Bitmap);
end;
procedure TfrmImages.ClearTabPages();
begin
  TabControl.Tabs.Clear;
  ClearImageList();
end;
procedure TfrmImages.ClearImageList();
//Note: !! This should also clear any visible images/thumbnails etc.
//Note: Need to remove thumbnail image from image list.
var i    : integer;
begin
  for i := ImageInfoList.Count-1 downto 0 do begin
    if TImageInfo(ImageInfoList[i]).LongDesc <> nil then begin
      TImageInfo(ImageInfoList[i]).LongDesc.Free;
    end;
    TImageInfo(ImageInfoList[i]).Free;
    ImageInfoList.Delete(i);
  end;
  NumImagesAvailableOnServer := NOT_YET_CHECKED_SERVER;
  ImageIndexLastDownloaded := -1;
end;
procedure TfrmImages.EnsureImageListLoaded();
begin
  if NumImagesAvailableOnServer = NOT_YET_CHECKED_SERVER then begin
    GetImageList();
  end;
end;
procedure TfrmImages.GetImageList();
//Sets up ImageInfoList
var
  i,j : integer;
  s,s2 : AnsiString;
  Rec  : TImageInfo;
  ImageIEN : integer;
  TIUIEN : AnsiString;
  ServerFName : AnsiString;
  ServerPathName : AnsiString;
  ImageFPathName :     AnsiString;  //path on server of image  -- original data provided by server
  ThumbnailFPathName : AnsiString;  //path on server of thumbnail -- original data provided by server
begin
  inherited;
  ClearImageList;
  try
    TIUIEN := IntToStr(frmNotes.lstNotes.ItemID);
  except
    //Error occurs after note is signed, and frmNotes.lstNotes.ItemID is "inaccessible"
    on E: Exception do exit;
  end;
  StatusText('Retrieving images information...');
  CallV('MAG3 CPRS TIU NOTE', [TIUIEN]);
  for i:=0 to (RPCBrokerV.Results.Count-1) do begin
    s :=RPCBrokerV.Results[i];
    if i=0 then begin
      if piece(s,'^',1)='0' then break //i.e. abort due to error signal
      else continue;   //ignore rest of header (record #0)
    end;
    if Pos('-1~',s)>0 then continue;  //abort if error signal.
    Rec := TImageInfo.Create; // ImageInfoList will own this.
    Rec.LongDesc := nil;
    Rec.TabIndex := -1;
    Rec.TabImageIndex := 0;
    s2 := piece(s,'^',2); if s2='' then s2 := '0'; //IEN
    Rec.IEN := StrToInt(s2);
    ImageFPathName := piece(s,'^',3);       //Image FullPath and name
    ThumbnailFPathName := piece(s,'^',4);   //Abstract FullPath and Name
    Rec.ShortDesc := piece(s,'^',5);            //SHORT DESCRIPTION field
    s2 := piece(s,'^',6); if s2='' then s2 := '0'; //PROCEDURE/ EXAM DATE/TIME field
    Rec.DateTime := s2;
    s2 := piece(s,'^',7); if s2='' then s2 := '0';  //OBJECT TYPE
    Rec.ImageType := StrToInt(s2);
    Rec.ProcName := piece(s,'^',8);                 //PROCEDURE field
    Rec.DisplayDate := piece(s,'^',9);              //Procedure Date in Display format
    s2 := piece(s,'^',10); if s2='' then s2 := '0'; //PARENT DATA FILE image pointer
    Rec.ParentDataFileIEN := StrToInt(s2);
    Rec.AbsType := piece(s,'^',11)[1];              //the ABSTYPE :  'M' magnetic 'W' worm  'O' offline
    s2 := piece(s,'^',12); if s2='' then s2 :='O';
    Rec.Accessibility := s2[1];                     //Image accessibility   'A' accessable  or  'O' offline
    s2 := piece(s,'^',13); if s2='' then s2 := '0'; //Dicom Series number
    Rec.DicomSeriesNum := StrToInt(s2);
    s2 := piece(s,'^',14); if s2='' then s2 := '0'; //Dicom Image Number
    Rec.DicomImageNum := StrToInt(s2);
    s2 := piece(s,'^',15); if s2='' then s2 := '0'; //Count of images in the group, or 1 if a single image
    Rec.GroupCount := StrToInt(s2);
    SplitLinuxFilePath(ImageFPathName,ServerPathName,ServerFName);
    Rec.ServerPathName := ServerPathName;
    Rec.ServerFName := ServerFName;
    Rec.CacheFName := CacheDir + '\' + ServerFName;
    SplitLinuxFilePath(ThumbnailFPathName,ServerPathName,ServerFName);
    Rec.ServerThumbPathName := ServerPathName;
    Rec.ServerThumbFName := ServerFName;
    Rec.CacheThumbFName := CacheDir + '\' + ServerFName;
    ImageInfoList.Add(Rec);  // ImageInfoList will own Rec.
  end;
  for i:= 0 to ImageInfoList.Count-1 do begin
    Rec := TImageInfo(ImageInfoList.Items[i]);
    ImageIEN := Rec.IEN;
    CallV('TMG GET IMAGE LONG DESCRIPTION', [ImageIEN]);
    for j:=0 to (RPCBrokerV.Results.Count-1) do begin
      if (j>0) then begin
        if Rec.LongDesc = nil then Rec.LongDesc := TStringList.Create;
        Rec.LongDesc.Add(RPCBrokerV.Results.Strings[j]);
      end else begin
        if RPCBrokerV.Results[j]='' then break;
      end;
    end;
  end;
  StatusText('');
  NumImagesAvailableOnServer := ImageInfoList.Count;
end;
procedure TfrmImages.DownloadToCache(ImageIndex : integer);
//Loads image specified in ImageInfoList to Cache (unless already present)
var
  Rec : TImageInfo;
  ServerFName : AnsiString;
  ServerPathName : AnsiString;
begin
  Rec := TImageInfo(ImageInfoList[ImageIndex]);
  ServerFName := Rec.ServerFName;
  ServerPathName := Rec.ServerPathName;
  if not FileExists(Rec.CacheFName) then begin
    DownloadFile(ServerPathName,ServerFName,Rec.CacheFName,(ImageIndex*2)-1,ImageInfoList.Count*2);
  end;
  ServerFName := Rec.ServerThumbFName;
  ServerPathName := Rec.ServerThumbPathName;
  if not FileExists(Rec.CacheThumbFName) then begin
    DownloadFile(ServerPathName,ServerFName,Rec.CacheThumbFName,ImageIndex*2,ImageInfoList.Count*2);
  end;
  Application.ProcessMessages;
end;
procedure TfrmImages.SplitLinuxFilePath(FullPathName : AnsiString;
                                        var Path     : AnsiString;
                                        var FName    : AnsiString);
var  p : integer;
begin
  Path := '';  FName := '';
  repeat
    p := Pos('/',FullPathName);
    if p > 0 then begin
      Path := Path + MidStr(FullPathName,1,p);
      FullPathName := MidStr(FullPathName,p+1,1000);
    end else begin
      FName := FullPathName;
      FullPathName := '';
    end;
  until (FullPathName = '');
end;
function TfrmImages.UploadFileViaDropBox(LocalFNamePath,FPath,FName: AnsiString;CurrentImage,TotalImages: Integer): boolean;
var
  DropboxFile : AnsiString;
begin
  //First copy LocalFileNamePath --> DropBox\FileName
  DropboxFile := ExcludeTrailingBackslash(DropboxDir) + '\' + FName;
  if CopyFile(pchar(LocalFNamePath),pchar(DropboxFile),false)=false then begin
    MessageDlg('Dropbox file transfer failed.  Code='+InttoStr(GetLastError),
               mtError,[mbOK],0);
    result := false;
    exit;
  end;
  CallV('TMG UPLOAD FILE DROPBOX', [FPath,FName]);     //Move file into dropbox.
  {
  RPCBrokerV.ClearParameters := true;
  RPCBrokerV.remoteprocedure := 'TMG UPLOAD FILE DROPBOX';
  RPCBrokerV.param[0].PType := literal;
  RPCBrokerV.param[0].Value := FPath;
  RPCBrokerV.Param[1].PType := literal;
  RPCBrokerV.Param[1].Value := FName;
  RPCBrokerV.Param[2].PType := literal;
  RPCBrokerV.Param[2].Value := '1'; //see comments in UploadFile re '1' hardcoding
  CallBroker; //Move file into dropbox.
  }
  if RPCBrokerV.Results.Count>0 then begin
    Result := (Piece(RPCBrokerV.Results[0],'^',1)='1');  //1=success, 0=failure
  end else Result := false;
end;
function TfrmImages.UploadFile(LocalFNamePath,FPath,FName: AnsiString;CurrentImage,TotalImages: Integer): boolean;
const
  RefreshInterval = 500;
  BlockSize = 512;
var
  ReadCount                     : Word;
  totalReadCount                : Integer;
  ParamIndex                    : LongWord;
  j                             : word;
  InFile                        : TFileStream;
  LocalOutFile                  : TFileStream;
  Buffer                        : array[0..1024] of byte;
  RefreshCountdown              : integer;
  OneLine                       : AnsiString;
  RPCResult                     : AnsiString;
  SavedCursor                   : TCursor;
begin
  result := false;  //default of failure
  if not FileExists(LocalFNamePath) then exit;
  //if UseDropBox then begin
  if TransferMethod = itmDropbox then begin
    Result := UploadFileViaDropBox(LocalFNamePath,FPath,FName,CurrentImage,TotalImages);
    exit;
  end;
  //LATER add support for itmDirect mode
  try
    InFile := TFileStream.Create(LocalFNamePath,fmOpenRead or fmShareCompat);
    LocalOutFile := TFileStream.Create(CacheDir+'\'+FName,fmCreate or fmOpenWrite); //for local copy
    //Note: I may well cut this out.  Most of the delay occurs during
    // the RPC call, and I can't make a progress bar change during that...
    // (or I could, but I'm not going to change the RPC broker...)
    ImageTransferForm.setMax(InFile.Size);
    ImageTransferForm.ProgressMsg.Caption := 'Preparing to upload...';
    ImageTransferForm.Show;
    totalReadCount := 0;
  except
    // catch failure here...  on eError...
    exit;
  end;
  StatusText('Uploading full image...');
  Application.ProcessMessages;
  RPCBrokerV.remoteprocedure := 'TMG UPLOAD FILE';
  RPCBrokerV.ClearParameters := true;
  RPCBrokerV.Param[0].PType := literal;
  RPCBrokerV.Param[0].Value := FPath;
  RPCBrokerV.Param[1].PType := literal;
  RPCBrokerV.Param[1].Value := FName;
  RPCBrokerV.Param[2].PType := literal;
  RPCBrokerV.Param[2].Value := ''; //kt 7/11/10
  //RPCBrokerV.Param[2].Value := '1'; //Specifying a NETWORK LOCATION is now depreciated.
  RPCBrokerV.Param[3].PType := list;
  ParamIndex := 0;
  RefreshCountdown := RefreshInterval;
  repeat
    ReadCount := InFile.Read(Buffer,BlockSize);
    LocalOutFile.Write(Buffer,ReadCount); //for local copy
    totalReadCount := totalReadCount + ReadCount;
    ImageTransferForm.updateProgress(totalReadCount);
    OneLine := '';
    if ReadCount > 0 then begin
      SetLength(OneLine,ReadCount);
      for j := 1 to ReadCount do OneLine[j] := char(Buffer[j-1]);
      RPCBrokerV.Param[3].Mult[IntToStr(ParamIndex)] := Encode(OneLine);
      Inc(ParamIndex);
      Dec(RefreshCountdown);
      if RefreshCountdown < 1 then begin
        Application.ProcessMessages;
        RefreshCountdown := RefreshInterval;
      end;
    end;
  until (ReadCount < BlockSize);
  SavedCursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  ImageTransferForm.ProgressMsg.Caption := 'Uploading file to server...';
  Application.ProcessMessages;
  CallBroker;
  Screen.Cursor := SavedCursor;
  if RPCBrokerV.Results.Count > 0 then begin
    RPCResult := RPCBrokerV.Results[0];
  end else RPCResult := '';  
  result := (Piece(RPCResult,'^',1)='1');
  ImageTransferForm.Hide;
  if result=false then begin
    Application.MessageBox('Error uploading file','Error');
  end;
  InFile.Free;
  LocalOutFile.Free;
  StatusText('');
end;
function TfrmImages.DownloadFileViaDropbox(FPath,FName,LocalSaveFNamePath: AnsiString;CurrentImage,TotalImages: Integer): boolean;
var
  DropboxFile : AnsiString;
  CurrentFileSize : Integer;
  ErrMsg          : string;
begin
  CallV('TMG DOWNLOAD FILE DROPBOX', [FPath,FName]);  //Move file into dropbox.
 {RPCBrokerV.ClearParameters := true;
  RPCBrokerV.remoteprocedure := 'TMG DOWNLOAD FILE DROPBOX';
  RPCBrokerV.param[0].PType := literal;
  RPCBrokerV.param[0].Value := FPath;
  RPCBrokerV.Param[1].PType := literal;
  RPCBrokerV.Param[1].Value := FName;
  RPCBrokerV.Param[2].PType := literal;
  RPCBrokerV.Param[2].Value := '1'; //see comments in UploadFile re '1' hardcoding
  CallBroker; }
  if RPCBrokerV.Results.Count > 0 then begin
    Result := (Piece(RPCBrokerV.Results[0],'^',1)='1');  //1=success, 0=failure
    if Result = false then ErrMsg := Piece(RPCBrokerV.Results[0],'^',2);
  end else begin
    Result := false;
    ErrMsg := 'Error communicating with server to retrieve image.';
  end;
  if Result=true then begin
    if DirectoryExists(DropboxDir) = False then begin     //elh   added to ensure a dropbox directory is valid
      MessageDlg('Invalid Dropbox Directory. Please check your settings and try again.',mtError,[mbOK],0);
      ImageTransferForm.hide;
      exit;
    end;
    CurrentFileSize := strtoint(Piece(RPCBrokerV.Results[0],'^',3));  //Piece 3 = file size
    DropboxFile := ExcludeTrailingBackslash(DropboxDir) + '\' + FName;
    if ImageTransferForm.visible = False then ImageTransferForm.show;
    while FileSize(DropboxFile) <> CurrentFileSize do sleep(1000);   //elh
    ImageTransferForm.ProgressBar.Max := TotalImages;                //elh
    ImageTransferForm.ProgressBar.Position := CurrentImage+2;          //elh
    if TotalImages = (CurrentImage+2) then begin
       Sleep(1000);
       ImageTransferForm.hide;
    end;
    //Now move DropBox\FileName --> LocalFileNamePath
    if MoveFile(pchar(DropboxFile),pchar(LocalSaveFNamePath))=false then begin
      MessageDlg('Dropbox file transfer failed.  Code='+InttoStr(GetLastError),
                 mtError,[mbOK],0);
    end;
  end else begin
    MessageDlg('ERROR: '+ErrMsg,mtError,[mbOK],0);
  end;
end;
function TfrmImages.DownloadFile(FPath,FName,LocalSaveFNamePath: AnsiString;CurrentImage,TotalImages: Integer): boolean;
var
  i,count                       : integer;
  j                             : word;
  OutFile                       : TFileStream;
  s                             : AnsiString;
  Buffer                        : array[0..1024] of byte;
  RefreshCountdown              : integer;
const
  RefreshInterval = 500;
begin
  if FileExists(LocalSaveFNamePath) then begin
    DeleteFile(LocalSaveFNamePath);
  end;
  if TransferMethod = itmDropbox then begin
    Result := DownloadFileViaDropBox(FPath,FName,LocalSaveFNamePath,CurrentImage,TotalImages);
    exit;
  end;
  //LATER add support for itmDirect mode
  Result := true; //default to success;
  StatusText('Retrieving full image...');
  //kt CallV('TMG DOWNLOAD FILE', [FPath,FName,'1']); //kt 7/10/10 Specifying a NETWORK LOCATION is depreciated.
  CallV('TMG DOWNLOAD FILE', [FPath,FName]);
  Application.ProcessMessages;
  RefreshCountdown := RefreshInterval;
  //Note:RPCBrokerV.Results[0]=1 if successful load, =0 if failure
  if (RPCBrokerV.Results.Count>0) and (RPCBrokerV.Results[0]='1') then begin
    OutFile := TFileStream.Create(LocalSaveFNamePath,fmCreate);
    for i:=1 to (RPCBrokerV.Results.Count-1) do begin
      s :=Decode(RPCBrokerV.Results[i]);
      count := Length(s);
      if count>1024 then begin
        Result := false; //failure of load.
        break;
      end;
      for j := 1 to count do Buffer[j-1] := ord(s[j]);
      OutFile.Write(Buffer,count);
      Dec(RefreshCountdown);
      if RefreshCountdown < 1 then begin
        Application.ProcessMessages;
        RefreshCountdown := RefreshInterval;
      end;
    end;
    OutFile.Free;
  end else begin
    result := false;
  end;
  StatusText('');
end;
function TfrmImages.Encode(Input: AnsiString) : AnsiString;
//This function is based on ENCODE^RGUTUU, which is match for
//DECODE^RGUTUU that is used to decode (ascii armouring) on the
//server side.  This is a base64 encoder.
const
  //FYI character set is 64 characters (starting as 'A')
  //  (65 characters if intro '=' is counted)
  CharSet  = '=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var
  //Result : AnsiString;  // RGZ1  //'Result' is implicitly declared by Pascal
  i : integer;            //RGZ2
  j : integer;            //RGZ4
  PlainTrio : longword;   //RGZ3   //unsigned 32-bit
  EncodedByte : Byte;
  PlainByte : byte;       //RGZ5
  EncodedQuad : string[4];//RGZ6
begin
  //e.g. input (10 bytes):
  // 174 231 193   16 29 251   93 138 4    57
  // AE  E7  C1    10 1D FB    5D 8A  04   39
  Result := '';
  i := 1;
  while i<= Length(Input) do begin  //cycle in groups of 3
    PlainTrio := 0;
    EncodedQuad := '';
    //Get 3 bytes, to be converted into 4 characters eventually.
    //Fill with 0's if needed to make an even 3-byte group.
    For j:=0 to 2 do begin
      //e.g. '174'->PlainByte=174
      if (i+j) <= Length(Input) then PlainByte := ord(Input[i+j])
      else PlainByte := 0;
      PlainTrio := (PlainTrio shl 8) or PlainByte;
    end;
    //e.g. first 3 bytes--> PlainTrio= $AEE7C1 (10101110 11100111 11000001)
    //e.g. last  3 bytes--> PlainTrio= $390000 (00111001 00000000 00000000) (note padded 0's)
    //Take each 6 bits and convert into a character.
    //e.g. first 3 bytes--> (101011 101110 011111 000001)
    //                       43      46     31     1
    //e.g. last 3 bytes-->(001110 010000 000000 000000)  (after redivision)
    //                        14     16     0     0  <-- last 2 bytes are padded 0
    //                               ^ last 4 bits of '16' are padded 0's
    For j := 1 to 4 do begin
      //e.g. $AEE7C1 --> (43+2)=45 (46+2)=48 (31+2)=33 (1+2)=3
      //                         r        u         f        b
      //e.g. $39AF00 --> (14+2)=16 (16+2)=18 (0+2)=2 (0+2)=2
      //                         O        Q        A       A <-- 2 padded bytes
      EncodedByte := (PlainTrio and 63)+2;  //63=$3F=b0111111;  0->A 1->B etc
      EncodedQuad := CharSet[EncodedByte]+ EncodedQuad;  //string Concat, not math add
      PlainTrio := PlainTrio shr 6
    end;
    //Append result with latest quad
    Result := Result + EncodedQuad;
    Inc(i,3);
  end;
  // e.g. result: rufb .... .... OQAA <-- 2 padded bytes (and part of Q is padded also)
  i := 3-(Length(Input) mod 3);  //returns 1,2,or 3 (3 needs to be set to 0)
  if (i=3) then i:=0;   //e.g. input=10 -> i=2
  j := Length(Result);
  //i is the number of padded characters that need to be replaced with '='
  if i>=1 then Result[j] := '=';  //replace 1st paddeded char
  if i>=2 then Result[Length(Result)-1] := '=';//replace 2nd paddeded char
  // e.g. result: rufb .... .... OQ==
  //results passed out in Result
end;
function TfrmImages.Decode(Input: AnsiString) : AnsiString;
//This function is based on DECODE^RGUTUU, which is match for
//ENCODE^RGUTUU that is used to encode (ascii armouring) on the
//server side.  This is a Base64 decoder
const
  //FYI character set is 64 characters (starting as 'A')
  //  (65 characters if intro '=' is counted)
  CharSet  = '=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var
  //Result : AnsiString;  //RGZ1  //'Result' is implicitly declared by Pascal
  i : integer;            //RGZ2
  PlainTrio : longword;   //RGZ3  //unsigned 32-bit
  j : integer;            //RGZ4
  EncodedChar : char;
  PlainInt : integer;
  PlainByte : byte;       //RGZ5
  DecodedTrio : string[3];//RGZ6
begin
  Result:='';
  i := 1;
  //e.g. input: rufb .... .... OQ==
  while i <= Length(Input) Do begin  //cycle in groups of 4
    PlainTrio :=0;
    DecodedTrio :='';
    //Get 4 characters, to be converted into 3 bytes.
    For j :=0 to 3 do begin
      //e.g. last 4 chars --> 0A==
      if (i+j) <= Length(Input) then begin
        EncodedChar := Input[i+j];
        PlainInt := Pos(EncodedChar,CharSet)-2; //A=0, B=1 etc.
        if (PlainInt>=0) then PlainByte := (PlainInt and $FF) else PlainByte := 0;
      end else PlainByte := 0;
      //e.g. with last 4 characters:
      //e.g. '0'->14=(b001110) 'Q'->16=(b010000) '='-> -1 -> 0=(b000000) '=' -> 0=(b000000)
      //e.g.-- So last PlainTrio = 001110 010000 000000 000000 = 00111001 00000000 00000000
      //Each encoded character contributes 6 bytes to final 3 bytes.
      //4 chars * 6 bits/char=24 bits -->  24 bits / 8 bits/byte = 3 bytes
      PlainTrio := (PlainTrio shl 6) or PlainByte;  //PlainTrio := PlainTrio*64 + PlainByte;
    end;
    //Now take 3 bytes, and add to cumulative output (in same order)
    For j :=0 to 2 do begin
      DecodedTrio := Chr(PlainTrio and $FF) + DecodedTrio;  //string concat (not math addition)
      PlainTrio := PlainTrio shr 8;  // PlainTrio := PlainTrio div 256
    end;
    //e.g. final DecodedTrio = 'chr($39) + chr(0) + chr(0)'
    Result := Result + DecodedTrio;
    Inc(i,4);
  end;
  //Now remove 1 byte from the output for each '=' in input string
  //(each '=' represents 1 padded 0 added to allow for even groups of 3)
  for j :=0 to 1 do begin
    if (Input[Length(Input)-j] = '=') then begin
      Result := MidStr(Result,1,Length(Result)-1);
    end;
  end;
end;
procedure TfrmImages.NewNoteSelected(EditIsActive : boolean);
//Will be called by  fNotes when a new note has been selected.
//var
begin
  ClearTabPages();
  DownloadImagesInBackground := true;
  SetupTimer;
  //This will start downloading images after few second delay (so that if
  //user is just browsing past note, this won't waste effort.
  //If user selects images tab, then load will occur without delay.
  //Note: OnTimer calls timLoadImagesTimer()
  FEditIsActive := EditIsActive;
  UploadImagesButton.Enabled := EditIsActive;
  UploadImagesMnuAction.Enabled := EditIsActive;
  WebBrowser.Navigate(NullImageName);
end;
procedure TfrmImages.EmptyCache();
//This will delete ALL files in the Cache directory
//Note: This will include the html_note file created by
// the notes tab.
var
  //CacheDir : AnsiString;
  FoundFile : boolean;
  FSearch : TSearchRec;
  Files : TStringList;
  i : integer;
  FName : AnsiString;
begin
  Files := TStringList.Create;
//  CacheDir := ExtractFilePath(ParamStr(0))+ 'Cache';
  FoundFile := (FindFirst(CacheDir+'\*.*',faAnyFile,FSearch)=0);
  while FoundFile do Begin
    FName := FSearch.Name;
    if (FName <> '.') and (FName <> '..') then begin
      FName := CacheDir + '\' + FName;
      Files.Add(FName);
    end;
    FoundFile := (FindNext(FSearch)=0);
  end;
  for i := 0 to Files.Count-1 do begin
    FName := Files.Strings[i];
    if DeleteFile(FName) = false then begin
      //kt raise Exception.Create('Unable to delete file: '+FSearch.Name+#13+'Will try again later...');
    end;
  end;
  Files.Free;
end;
procedure TfrmImages.UploadImagesButtonClick(Sender: TObject);
var
  Node: TORTreeNode;
  AddResult : TModalResult;
begin
  inherited;
  AddResult := UploadForm.ShowModal;
  if not IsAbortResult(AddResult) then begin
    NewNoteSelected(true);  //force a reload to show recently added image.
    timLoadImages.Interval := IMAGE_DOWNLOAD_DELAY_FOREGROUND;
    Node := TORTreeNode(frmNotes.tvNotes.Selected);
    case Node.StateIndex of
      IMG_NO_IMAGES         :  Node.StateIndex := IMG_1_IMAGE;
      IMG_1_IMAGE           :  Node.StateIndex := IMG_2_IMAGES;
      IMG_2_IMAGES          :  Node.StateIndex := IMG_MANY_IMAGES;
      IMG_MANY_IMAGES       :  Node.StateIndex := IMG_MANY_IMAGES;
    end;
  end;
end;
procedure TfrmImages.FormHide(Sender: TObject);
begin
  inherited;
  DownloadImagesInBackground := true;
//  Application.MessageBox('Here I can hide images.','title');
end;
procedure TfrmImages.TabControlChange(Sender: TObject);
var
  FileName : AnsiString;
  Rec  : TImageInfo;
  Selected : integer;
begin
  inherited;
  //here tab has been changed.
  Selected := TabControl.TabIndex;
  if Selected > -1 then begin
    Rec := TImageInfo(ImageInfoList[Selected]);
    FileName := Rec.CacheFName;
    UpdateImageInfoMemo(Rec);
  end else begin
    FileName := NullImageName;
    UpdateImageInfoMemo(nil);
  end;
  WebBrowser.Navigate(FileName);
end;
procedure TfrmImages.TabControlGetImageIndex(Sender: TObject;
                                             TabIndex: Integer;
                                             var ImageIndex: Integer);
//specify which image to display, from ThumbsImageList
begin
  inherited;
  if (ImageInfoList <> nil) and (TabIndex < ImageInfoList.Count) then begin
    ImageIndex := TImageInfo(ImageInfoList[TabIndex]).TabImageIndex;
  end else ImageIndex := 0;
end;
procedure TfrmImages.TabControlResize(Sender: TObject);
begin
  inherited;
  if TabControl.Width < 80 then begin
    TabControl.Width := 80;
  end;
end;
function TfrmImages.CreateBarcode(MsgStr: AnsiString; ImageType: AnsiString): AnsiString;
//Create a local barcode file, in .png format, from MsgStr
//ImageType is optional, default ='png'.  It should NOT contain '.'
//Returns file path on local client of new barcode image.
//Note: this function is not related to uploading or downloading images
//      to the server for attaching to progress notes.  It is included
//      in this unit because the functionality used is nearly identical to
//      the other code.
  function UniqueFName : AnsiString;
    var  FName,tempFName : AnsiString;
         count : integer;
  begin
    FName := 'Barcode-Image';
    count := 0;
    repeat
      tempFName := CacheDir + '\' + FName + '.' + ImageType;
      FName := FName + '1';
      count := count+1;
    until (fileExists(tempFName)=false) or (count> 32);
    result := tempFName;
  end;
var
  i,count                       : integer;
  j                             : word;
  OutFile                       : TFileStream;
  s                             : AnsiString;
  Buffer                        : array[0..1024] of byte;
  LocalSaveFNamePath            : AnsiString;
begin
  StatusText('Getting Barcode...');
  LocalSaveFNamePath := UniqueFName;
  Result := LocalSaveFNamePath;  //default to success;
  // CallV('TMG BARCODE ENCODE', [MsgStr]);
  RPCBrokerV.ClearParameters := true;
  RPCBrokerV.remoteprocedure := 'TMG BARCODE ENCODE';
  RPCBrokerV.param[0].Value := MsgStr;
  RPCBrokerV.param[0].PType := literal;
  RPCBrokerV.Param[1].Value := '.X';  //<-- is this needed or used?
  RPCBrokerV.Param[1].PType := list;
  RPCBrokerV.Param[1].Mult['"IMAGE TYPE"'] := ImageType;
  //RPCBrokerV.Call;
  CallBroker;
  Application.ProcessMessages;
  //Note:RPCBrokerV.Results[0]=1 if successful load, =0 if failure
  if (RPCBrokerV.Results.Count>0) and (RPCBrokerV.Results[0]='1') then begin
    OutFile := TFileStream.Create(LocalSaveFNamePath,fmCreate);
    for i:=1 to (RPCBrokerV.Results.Count-1) do begin
      s :=Decode(RPCBrokerV.Results[i]);
      count := Length(s);
      if count>1024 then begin
        Result := ''; //failure of load.
        break;
      end;
      for j := 1 to count do Buffer[j-1] := ord(s[j]);
      OutFile.Write(Buffer,count);
    end;
    OutFile.Free;
  end else begin
    result := '';
  end;
  StatusText('');
end;
function TfrmImages.DecodeBarcode(LocalFNamePath,ImageType: AnsiString): AnsiString;
//Decode data from barcode on image, or return '' if none
//Note: if I could find a cost-effective way of decoding this on client side,
//      then that code be done here in the function, instead of uploading image
//      to the server for decoding.
const
  RefreshInterval = 500;
  BlockSize = 512;
var
  ReadCount                     : Word;
  ParamIndex                    : LongWord;
  j                             : word;
  InFile                        : TFileStream;
  Buffer                        : array[0..1024] of byte;
  RefreshCountdown              : integer;
  OneLine                       : AnsiString;
  RPCResult                     : AnsiString;
  SavedCursor                   : TCursor;
  totalReadCount                : integer;
begin
  result := '';  //default of failure
  if not FileExists(LocalFNamePath) then exit;
  try
    InFile := TFileStream.Create(LocalFNamePath,fmOpenRead or fmShareCompat);
    //Note: I may well cut this out.  Most of the delay occurs during
    // the RPC call, and I can't make a progress bar change during that...
    // (or I could, but I'm not going to change the RPC broker...)
    ImageTransferForm.setMax(InFile.Size);
    //ImageTransferForm.ResetStartTime;
    ImageTransferForm.ProgressMsg.Caption := 'Preparing to upload...';
    ImageTransferForm.Show;
    totalReadCount := 0;
  except
    // catch failure here...  on eError...
    exit;
  end;
  StatusText('Checking image for barcodes...');
  Application.ProcessMessages;
  RPCBrokerV.ClearParameters := true;
  RPCBrokerV.Param.Clear;
  RPCBrokerV.Param[0].PType := list;
  ParamIndex := 0;
  RefreshCountdown := RefreshInterval;
  //Put image data into parameter 0 (ARRAY parameter of RPC on server side)
  repeat
    ReadCount := InFile.Read(Buffer,BlockSize);
    OneLine := '';
    totalReadCount := totalReadCount + ReadCount;
    ImageTransferForm.updateProgress(totalReadCount);
    if ReadCount > 0 then begin
      SetLength(OneLine,ReadCount);
      for j := 1 to ReadCount do OneLine[j] := char(Buffer[j-1]);
      RPCBrokerV.Param[0].Mult[IntToStr(ParamIndex)] := Encode(OneLine);
      Inc(ParamIndex);
      Dec(RefreshCountdown);
      if RefreshCountdown < 1 then begin
        Application.ProcessMessages;
        RefreshCountdown := RefreshInterval;
      end;
    end;
  until (ReadCount < BlockSize);
  RPCBrokerV.Param[1].PType := literal;
  RPCBrokerV.Param[1].Value := ImageType;
  RPCBrokerV.remoteprocedure := 'TMG BARCODE DECODE';
  SavedCursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  ImageTransferForm.ProgressMsg.Caption := 'Uploading file to server...';
  Application.ProcessMessages;
  CallBroker;  //this is the slow step, pass to server and get response.
  Screen.Cursor := SavedCursor;
  ImageTransferForm.Hide;
  //Get result: 1^DecodedMessage, or 0^Error Message
  RPCResult := RPCBrokerV.Results[0];
  if Piece(RPCResult,'^',1)='0' then begin
    MessageDlg(Piece(RPCResult,'^',2),mtError,[mbOK],0);
  end else begin
    result := Piece(RPCResult,'^',2);
  end;
  InFile.Free;
  StatusText('');
end;
procedure TfrmImages.EnableAutoScanUploadClick(Sender: TObject);
begin
  inherited;
  AutoScanUpload.Checked := not AutoScanUpload.Checked;
  uTMGOptions.WriteBool('Scan Enabled',AutoScanUpload.Checked);
end;
procedure TfrmImages.PickScanFolderClick(Sender: TObject);
var
  CurScanDir : string;
begin
  inherited;
  CurScanDir := UploadForm.ScanDir;
  OpenDialog.InitialDir := CurScanDir;
  MessageDlg('Please pick ANY file in the desired directory.',mtInformation,[mbOK],0);
  if OpenDialog.Execute then begin
    UploadForm.SetScanDir(ExtractFilePath(OpenDialog.FileName));
  end;
  AutoScanUpload.Checked := true;
end;
function TfrmImages.FileSize(fileName : wideString) : Int64;
var
  sr : TSearchRec;
begin
  if FindFirst(fileName, faAnyFile, sr ) = 0 then
     result := Int64(sr.FindData.nFileSizeHigh) shl Int64(32) +  Int64(sr.FindData.nFileSizeLow)
  else
     result := -1;
  FindClose(sr) ;
end;
function TfrmImages.GetImagesCount : integer;
//Returns number of images possible, not just those already downloaded.
begin
  EnsureImageListLoaded();
  Result := NumImagesAvailableOnServer;
end;
function TfrmImages.GetImageInfo(Index : integer) : TImageInfo;
begin
  if (Index > -1) and (Index < ImageInfoList.Count) then begin
    Result := TImageInfo(ImageInfoList[Index]);
  end else begin
    Result := nil;
  end;
end;
procedure TfrmImages.TabControlMouseUp(Sender: TObject; Button: TMouseButton;
                                       Shift: TShiftState; X, Y: Integer);
//kt add 7/6/10
var TabIndex : integer;
    P : TPoint;
begin
  inherited;
  if Button <> mbRight then exit;
  TabIndex := TabControl.IndexOfTabAt(X,Y);
  if TabIndex < 0 then exit;
  FDeleteImageIndex := TabIndex;
  P.X := X; P.Y := Y;
  P := TabControl.ClientToScreen(P);
  TabControl.PopupMenu := mnuPopup;
  mnuPopup.Popup(P.X, P.Y);
  TabControl.PopupMenu := nil;
end;
procedure TfrmImages.mnuPopupPopup(Sender: TObject);
//Determine here if delete option should be enabled.
begin
  inherited;
  mnuPopDeleteImage.Enabled := CanDeleteImages;
end;
function TfrmImages.CanDeleteImages : boolean;
//Determine here if image can be deleted.
var
  ActionSts: TActionRec;
const
  ATTACHED_IMAGES_SERVER_REPLY = 'You must "delete" the Images using the Imaging package before proceeding.';
begin
  FImageDeleteMode := idmNone;
  Result := false; //default action
  if FEditIsActive then begin
    Result := true;
    FImageDeleteMode := idmDelete;
    exit;
  end;
  //Will use same user class managment rules for images as for notes.
  //So if user can delete a note, then they can also delete images.
  ActOnDocument(ActionSts, frmNotes.lstNotes.ItemIEN, 'DELETE RECORD');
  if (ActionSts.Success = false) then begin
    if Pos(ATTACHED_IMAGES_SERVER_REPLY, ActionSts.Reason) > 0 then ActionSts.Success := true;
  end;
  Result := ActionSts.Success;
  if Result then begin
    if AuthorSignedDocument(frmNotes.lstNotes.ItemIEN) then FImageDeleteMode := idmRetract
    else FImageDeleteMode := idmDelete;
  end;
end;
procedure TfrmImages.mnuPopDeleteImageClick(Sender: TObject);
begin
  inherited;
  DeleteImageIndex(FDeleteImageIndex, FImageDeleteMode, True);
end;
procedure TfrmImages.DeleteAll(DeleteMode: TImgDelMode);
begin
  EnsureALLImagesDownloaded;
  while TabControl.Tabs.Count > 0 do begin
    DeleteImageIndex(0,DeleteMode,False);
    NewNoteSelected(False);
    EnsureALLImagesDownloaded;
    frmImages.Formshow(self);
  end;
end;
procedure TfrmImages.DeleteImageIndex(ImageIndex : integer; DeleteMode : TImgDelMode; boolPromptUser: boolean);
//Note: permissions must be checked before running this function
var
  ImageInfo : TImageInfo;
  ReasonForDelete : string;
  DeleteSts : TActionRec;
CONST
  TMG_PRIVACY  = 'FOR PRIVACY';  //Server message (don't translate)
  TMG_ADMIN    = 'ADMINISTRATIVE'; //Server message (don't translate)
begin
  if (ImageIndex<0) or (ImageIndex>=ImagesCount) then begin
    MessageDlg('Invalid image index to delete: '+IntToStr(ImageIndex), mtError,[mbOK],0);
    exit;
  end;
  ImageInfo := Self.ImageInfo[ImageIndex];
  if boolPromptUser then begin
    ReasonForDelete := SelectDeleteReason(frmNotes.lstNotes.ItemIEN);
    if ReasonForDelete = DR_CANCEL then Exit;
    if ReasonForDelete = DR_PRIVACY then begin
      ReasonForDelete := TMG_PRIVACY;
    end else if ReasonForDelete = DR_ADMIN then begin
      ReasonForDelete := TMG_ADMIN;
    end;
  end else begin
    ReasonForDelete := 'DeleteAll';
  end;
  DeleteImage(DeleteSts, ImageInfo.ServerFName, ImageInfo.IEN, frmNotes.lstNotes.ItemIEN, DeleteMode, ReasonForDelete);
end;
procedure TfrmImages.DeleteImage(var DeleteSts: TActionRec;
                                 ImageFileName: String;
                                 ImageIEN, DocIEN: Integer;
                                 DeleteMode : TImgDelMode;
                                 const Reason: string);  //Reason should be 10-60 chars;
  function ServerImageDelete(ImageIEN:integer; DeleteMode:tImgDelMode; Reason:String) : boolean;
  //Returns success
  var RPCResult,IEN,Mode : string;
  begin
    IEN := IntToStr(ImageIEN);
    Mode := IntToStr(Ord(DeleteMode));
    RPCResult := sCallV('TMG IMAGE DELETE', [IEN,Mode,Reason]);
    Result := Piece(RPCResult,'^',1)= '1';
    if Result = false then begin
      MessageDlg(Piece(RPCResult,'^',2),mtError,[mbOK],0);
    end;
  end;
  procedure NoteImageDelete(DocIEN:integer; FileName: string; DeleteMode:tImgDelMode; Reason:String);
  var
     NoteText, tempString: string;
     Beginning, Ending: integer;
     boolFound: boolean;
  // 
  // FEditIsActive
  begin
     if FEditIsActive then begin
       Ending := 1;
       Beginning := 1;
       boolFound := False;
       While (boolFound = False) AND (Beginning > 0) Do Begin
         NoteText := frmNotes.HtmlEditor.HTMLText;
         Beginning := PosEx('![]() ', NoteText, Beginning) + 1;
         tempString := MidStr(NoteText, Beginning, Ending-Beginning);
         if pos(FileName,tempString) > 0 then boolFound := True;
       end;
       if boolFound = false then  begin
         Ending := 1;
         Beginning := 1;
         boolFound := False;
         While (boolFound = False) AND (Beginning > 0) Do Begin
           NoteText := frmNotes.HtmlEditor.HTMLText;
           Beginning := PosEx('
', NoteText, Beginning) + 1;
         tempString := MidStr(NoteText, Beginning, Ending-Beginning);
         if pos(FileName,tempString) > 0 then boolFound := True;
       end;
       if boolFound = false then  begin
         Ending := 1;
         Beginning := 1;
         boolFound := False;
         While (boolFound = False) AND (Beginning > 0) Do Begin
           NoteText := frmNotes.HtmlEditor.HTMLText;
           Beginning := PosEx('