{-----------------------------------------------------------------------------
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/MPL-1.1.html

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

You may retrieve the latest version of this file at the FBA Creator Home Page,
located at http://www.fbacreator.com

Known Issues:
-----------------------------------------------------------------------------}
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, lua , lualib , LuaUtils, SynEdit, LuaSyntax, ComCtrls, ToolWin, StdCtrls,
  Buttons, IniFiles, mwLineStream, Menus, Clipbrd, SynCompletionProposal,
  SynEditMiscClasses, SynEditRegexSearch, JvComponentBase, JvCreateProcess,
  ShellApi, JvComputerInfoEx, ImgList, WSocket, JvBaseDlg,
  JvSelectDirectory, JvBrowseFolder, Unit3, WSocketS, ExtCtrls,
  SynEditSearch, Strlib, SynEditTextBuffer, HttpProt, Resources;

type
  // treeview data record per gli script
  TScriptNode = class
    buffer: TStringList;
    // prima linea visibile
    firstVisibleLine: Integer;
    // teniamo anche il testo dei breakpoint perchè quando aggiorniamo il testo verifichiamo
    // se la linea è cambiata o meno per cancellare i breakpoints non piu' validi
    breakpoints_line, breakpoints_text: TStringList;

    // redo e undo lists
    undolist, redolist: TSynEditUndoList;

    saved: Boolean;
    name: String;
  end;

  // treeview data record per le risorse
  TResourceNode = class
    name: String;
    fullpath: String;
    copied: Boolean;
  end;

  // treeview data record per le risorse esterne
  TExtResourceNode = class
    name: String;
    fullpath: String;
    copied: Boolean;
  end;

  // stato dell'editor
  type IDEStatus = (Editing, Running, Pause);

  TMainForm = class(TForm)
    statusbar: TStatusBar;
    Panel1: TPanel;
    Panel2: TPanel;
    Panel3: TPanel;
    synedit: TSynEdit;
    dbgPanel: TPanel;
    Panel5: TPanel;
    Panel6: TPanel;
    Panel9: TPanel;
    Panel11: TPanel;
    Panel12: TPanel;
    Panel13: TPanel;
    Panel7: TPanel;
    Panel14: TPanel;
    projfiles: TTreeView;
    btnnewsource: TBitBtn;
    btnaddresource: TBitBtn;
    dlgaddresource: TOpenDialog;
    Splitter2: TSplitter;
    dlgsaveproject: TSaveDialog;
    dlgopenproject: TOpenDialog;
    editpopup: TPopupMenu;
    mnucut: TMenuItem;
    mnucopy: TMenuItem;
    mnupaste: TMenuItem;
    N1: TMenuItem;
    mnuselectall: TMenuItem;
    N2: TMenuItem;
    mnufind: TMenuItem;
    mnufindnext: TMenuItem;
    mnureplace: TMenuItem;
    MainMenu1: TMainMenu;
    Help1: TMenuItem;
    AboutFBA1: TMenuItem;
    mainmnuEdit: TMenuItem;
    mainmnucut: TMenuItem;
    mainmnucopy: TMenuItem;
    mainmnupaste: TMenuItem;
    N3: TMenuItem;
    mainmnuselectall: TMenuItem;
    N4: TMenuItem;
    mainmnufind: TMenuItem;
    mainmnufindnext: TMenuItem;
    mainmnureplace: TMenuItem;
    createprocess: TJvCreateProcess;
    pcinfo: TJvComputerInfoEx;
    Splitter3: TSplitter;
    dbgsplittersearchallfiles: TSplitter;
    outputdebugconsole: TMemo;
    popupdebugconsole: TPopupMenu;
    ClearConsole1: TMenuItem;
    btndelfile: TBitBtn;
    imglGutterGlyphs: TImageList;
    panelButtonRun: TPanel;
    panelButtonNormal: TPanel;
    btnsaveproject: TBitBtn;
    btnloadproject: TBitBtn;
    btnrun: TBitBtn;
    btnStep: TBitBtn;
    socket: TWSocket;
    Panel4: TPanel;
    Panel8: TPanel;
    Panel10: TPanel;
    vardump: TListView;
    mnustep: TMenuItem;
    btnHalt: TBitBtn;
    btnContinue: TBitBtn;
    mnuRun: TMenuItem;
    btnPause: TBitBtn;
    File1: TMenuItem;
    mnuMainNewScript: TMenuItem;
    mnuMainAddFile: TMenuItem;
    N5: TMenuItem;
    mnuMainOpenProject: TMenuItem;
    mnuMainSaveProject: TMenuItem;
    N6: TMenuItem;
    Exit1: TMenuItem;
    mnuMainDeleteFile: TMenuItem;
    Program1: TMenuItem;
    mnuMainRun: TMenuItem;
    mnuMainStep: TMenuItem;
    mnuMainStop: TMenuItem;
    N7: TMenuItem;
    Stop1: TMenuItem;
    btnNewProject: TBitBtn;
    mnuMainNewProject: TMenuItem;
    mnuBuild: TMenuItem;
    PocketPC1: TMenuItem;
    mnuPocketPCExportProject: TMenuItem;
    mnuPocketPCBuildInstall: TMenuItem;
    dlgSelectDestDir: TJvBrowseForFolderDialog;
    btnRunStepByStep: TBitBtn;
    mnuRunStepByStep: TMenuItem;
    N8: TMenuItem;
    mnuProjectSettings: TMenuItem;
    Panel15: TPanel;
    btnImportFont: TBitBtn;
    sockserv: TWSocketServer;
    mnuSaveAs: TMenuItem;
    N9: TMenuItem;
    Help2: TMenuItem;
    imagesResources: TImageList;
    Panel16: TPanel;
    Panel17: TPanel;
    Panel18: TPanel;
    txtImmediate: TMemo;
    SynEditSearch: TSynEditSearch;
    timerAlertStatus: TTimer;
    N10: TMenuItem;
    mnuundo: TMenuItem;
    mnuredo: TMenuItem;
    mainmnuundo: TMenuItem;
    mainmnuredo: TMenuItem;
    N11: TMenuItem;
    timerEnumFunctions: TTimer;
    Panel19: TPanel;
    lstEnumFuncts: TListBox;
    Splitter1: TSplitter;
    N12: TMenuItem;
    mainmnufindinfiles: TMenuItem;
    mnufindinfiles: TMenuItem;
    N13: TMenuItem;
    dbgsplitter: TSplitter;
    panelSearchAllFiles: TPanel;
    panelInfoSearchAllFiles: TPanel;
    btnHideSearchProjectFiles: TBitBtn;
    listSearchAllFiles: TListView;
    picsStatus: TImageList;
    httpget: THttpCli;
    CheckForUpdates1: TMenuItem;
    mnucopyconsole: TMenuItem;
    N14: TMenuItem;
    lblWaitappLaunch: TLabel;
    mainmnureopenproject: TMenuItem;
    mnulastopen0: TMenuItem;
    mnulastopen1: TMenuItem;
    mnulastopen2: TMenuItem;
    mnulastopen3: TMenuItem;
    mnulastopen4: TMenuItem;
    mnulastopen5: TMenuItem;
    mnulastopen6: TMenuItem;
    mnulastopen7: TMenuItem;
    mnulastopen8: TMenuItem;
    mnulastopen9: TMenuItem;
    mnulastopen10: TMenuItem;
    mnulastopen11: TMenuItem;
    mnulastopen12: TMenuItem;
    mnulastopen13: TMenuItem;
    mnulastopen14: TMenuItem;
    Windows1: TMenuItem;
    mnuWindowsExport: TMenuItem;
    ools1: TMenuItem;
    EnvironmentOptions1: TMenuItem;
    btnaddextresource: TBitBtn;
    dlgopendat: TOpenDialog;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure syneditChange(Sender: TObject);
    procedure btnnewsourceClick(Sender: TObject);
    function FindBufferID(filename: String): Integer;
    procedure projfilesChange(Sender: TObject; Node: TTreeNode);
    procedure btnaddresourceClick(Sender: TObject);
    procedure btnsaveprojectClick(Sender: TObject);
    procedure btnloadprojectClick(Sender: TObject);
    function SaveProject(saveas: Boolean): Boolean;
    procedure btnrunClick(Sender: TObject);
    procedure FreeTreeView(recreateVoid: Boolean);
    procedure SelectScript(idbuffer: Integer);
    procedure syneditKeyPress(Sender: TObject; var Key: Char);
    procedure syneditKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure syneditScroll(Sender: TObject; ScrollBar: TScrollBarKind);
    procedure syneditMouseCursor(Sender: TObject;
      const aLineCharPos: TBufferCoord; var aCursor: TCursor);
    function SomethingUnsaved(): Boolean;
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure editpopupPopup(Sender: TObject);
    procedure mnucopyClick(Sender: TObject);
    procedure mnupasteClick(Sender: TObject);
    procedure mnucutClick(Sender: TObject);
    procedure mnuselectallClick(Sender: TObject);
    procedure mnufindClick(Sender: TObject);
    procedure ShowSearchReplaceDialog(AReplace: boolean);
    procedure mnureplaceClick(Sender: TObject);
    procedure mnufindnextClick(Sender: TObject);
    procedure syneditReplaceText(Sender: TObject; const ASearch,
      AReplace: String; Line, Column: Integer;
      var Action: TSynReplaceAction);
    procedure mainmnucutClick(Sender: TObject);
    procedure mainmnucopyClick(Sender: TObject);
    procedure mainmnupasteClick(Sender: TObject);
    procedure mainmnuselectallClick(Sender: TObject);
    procedure mainmnufindClick(Sender: TObject);
    procedure mainmnufindnextClick(Sender: TObject);
    procedure mainmnureplaceClick(Sender: TObject);
    procedure mainmnuEditClick(Sender: TObject);
    procedure createprocessTerminate(Sender: TObject; ExitCode: Cardinal);
    procedure ClearConsole1Click(Sender: TObject);
    procedure btndelfileClick(Sender: TObject);
    procedure SockSend(s: String);
    procedure syneditGutterGetText(Sender: TObject; aLine: Integer;
      var aText: String);
    procedure syneditGutterClick(Sender: TObject; Button: TMouseButton; X,
      Y, Line: Integer; Mark: TSynEditMark);
    procedure btnStepClick(Sender: TObject);
    procedure socketSessionConnected(Sender: TObject; Error: Word);
    procedure socketSessionClosed(Sender: TObject; Error: Word);
    procedure socketSocksError(Sender: TObject; Error: Integer;
      Msg: String);
    procedure socketDataAvailable(Sender: TObject; Error: Word);
    procedure mnustepClick(Sender: TObject);
    procedure btnHaltClick(Sender: TObject);
    procedure syneditGutterPaint(Sender: TObject; aLine, X, Y: Integer);
    procedure btnContinueClick(Sender: TObject);
    procedure mnuRunClick(Sender: TObject);
    procedure btnPauseClick(Sender: TObject);
    procedure Stop1Click(Sender: TObject);
    procedure Exit1Click(Sender: TObject);
    procedure mnuMainRunClick(Sender: TObject);
    procedure mnuMainStepClick(Sender: TObject);
    procedure mnuMainStopClick(Sender: TObject);
    procedure mnuMainOpenProjectClick(Sender: TObject);
    procedure mnuMainNewProjectClick(Sender: TObject);
    procedure mnuMainSaveProjectClick(Sender: TObject);
    procedure mnuMainNewScriptClick(Sender: TObject);
    procedure mnuMainAddFileClick(Sender: TObject);
    procedure mnuMainDeleteFileClick(Sender: TObject);
    procedure btnNewProjectClick(Sender: TObject);
    procedure projfilesDblClick(Sender: TObject);
    procedure mnuPocketPCExportProjectClick(Sender: TObject);
    procedure mnuPocketPCBuildInstallClick(Sender: TObject);
    procedure btnRunStepByStepClick(Sender: TObject);
    procedure mnuRunStepByStepClick(Sender: TObject);
    procedure mnuProjectSettingsClick(Sender: TObject);
    procedure vardumpDblClick(Sender: TObject);
    procedure btnImportFontClick(Sender: TObject);
    procedure syneditContextHelp(Sender: TObject; word: String);
    procedure mnuSaveAsClick(Sender: TObject);
    procedure Help2Click(Sender: TObject);
    procedure statusbarDrawPanel(StatusBar: TStatusBar;
      Panel: TStatusPanel; const Rect: TRect);
    procedure AboutFBA1Click(Sender: TObject);
    procedure txtImmediateKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure timerAlertStatusTimer(Sender: TObject);
    procedure mnuundoClick(Sender: TObject);
    procedure mnuredoClick(Sender: TObject);
    procedure mainmnuundoClick(Sender: TObject);
    procedure mainmnuredoClick(Sender: TObject);
    procedure timerEnumFunctionsTimer(Sender: TObject);
    procedure lstEnumFunctsClick(Sender: TObject);
    procedure mnufindinfilesClick(Sender: TObject);
    procedure mainmnufindinfilesClick(Sender: TObject);
    procedure btnHideSearchProjectFilesClick(Sender: TObject);
    procedure listSearchAllFilesClick(Sender: TObject);
    procedure CheckForUpdates1Click(Sender: TObject);
    procedure httpgetDocEnd(Sender: TObject);
    procedure httpgetSocksError(Sender: TObject; Error: Integer;
      Msg: String);
    procedure mnucopyconsoleClick(Sender: TObject);
    procedure mnulastopen14Click(Sender: TObject);
    procedure File1Click(Sender: TObject);
    procedure mainmnureopenprojectClick(Sender: TObject);
    procedure mnuWindowsExportClick(Sender: TObject);
    procedure EnvironmentOptions1Click(Sender: TObject);
    procedure btnaddextresourceClick(Sender: TObject);
  private
    { Private declarations }
    fSearchFromCaret: boolean;
    procedure DoSearchReplaceText(AReplace: boolean; ABackwards: boolean);
    function CheckSyntax: Boolean;
    procedure DelBreakpoint(idbuffer: Integer; index: Integer);
    procedure AddBreakpoint(idbuffer: Integer; line: Integer);
    procedure SetGuiMode(mode: IDEStatus);
    procedure RunProject(stepmode: Boolean);
    procedure SetDefaultProjectSettings();
    function LoadProject(projname: String): Boolean;
    function GetAvailableTCPPort: Integer;
    function GetIconIndexFromFileName(s: String): Integer;
    procedure SetAlertStatusBar(text: String; isalert: Boolean);
    procedure MoveUndoList(source: TSynEditUndoList; dest: TSynEditUndoList);
    procedure UpdateProjectTitle();
    procedure ExportProject(device: Integer);
  public
    { Public declarations }
    function BuildDatFromResourcesList(dat: String; scriptsDat: String) : Integer;
    function CopyExtResourcesToDir(dir: String; device: Integer): Integer;
    function GetExtResourceFilesList(dir: String): TStringList;
    procedure ClearExtResourceFromDir(dir: String);
    procedure BuildLaunchIniFile(filename: String; stepbystep: Boolean; available_connection_port: Integer);
    procedure SetNilHelpForm();
    procedure SearchAllFiles(query: String; casesensitive: Boolean; progress: TProgressBar);
    procedure StopSearchAllFiles();
    procedure SetSettingsFromOptionsInMemory();
  end;

// funzioni esportate da lzab.dll
{$IFDEF USE_LZAB_DLL}
function lzab(text: PChar): Integer; stdcall; external 'lzab.dll';
function lzab_add_from_memory(dat: PChar; filename: PChar; buffer: PChar): Integer; stdcall; external 'lzab.dll';
function lzab_list(filename: PChar; buffer: PChar): LongInt; stdcall; external 'lzab.dll';
//function lzab_ext(filename: PChar; source: PChar; dest: PChar): Boolean; stdcall; external 'lzab.dll';
{$ENDIF}

const
  FILE_VERSION='0.2';
  REQUIRED_LUA_FUNCTIONS : array [1..7] of string =
  (
    'oninit',
    'onmainloop',
    'onstylusdown',
    'onstylusup',
    'onstylusmove',
    'onbuttonup',
    'onbuttondown'
  );

var
  MainForm: TMainForm;
  LUA: Plua_State = nil; //lua state
  current_buffer: Integer;
  project_saved: String;     // progetto salvato o meno (stringa vuota = non salvato)
  scriptNode, resourceNode, extResourceNode: TTreeNode;
  // ultimo modo selezionato
  lastModeSelected :IDEStatus;
  // ultimo help veloce motrato
  lastFastHelpFind: String;
  // attesa creazione file .dat
  creatingDatFileCode: Integer;
  // opzioni forms ricerca
  gbSearchBackwards: boolean;
  gbSearchCaseSensitive: boolean;
  gbSearchFromCaret: boolean;
  gbSearchSelectionOnly: boolean;
  gbSearchTextAtCaret: boolean;
  gbSearchWholeWords: boolean;
  //gbSearchRegex: boolean;

  gsSearchText: string;
  gsSearchTextHistory: string;
  gsReplaceText: string;
  gsReplaceTextHistory: string;

  // ultima selezione nello stackdumper per riselezionarla dopo il reload delle variabili
  lastSelectedStackType, lastSelectedStackName: String;

  // ultima linea segnalata dal loader
  lastLoaderLine: Integer;

  // lista delle funzioni lua supportate dal loader, caricate all'init
  luaFunctionsList: TStringList;

  // porta default di connessione
  DEFAULT_CONNECTION_PORT: Integer;

  // lista dei comandi eseguiti in immediate mode
  immediateHistory: TStringList;
  immediateHistoryPos: Integer;

  // variabile usata per far lampeggiare l'alert nella status bar
  flashingStatusAlert: Integer;

  // flag usato per sapere se il "thread" che enumera le funzioni
  // sta già lavorando e ultima lista inserita nel treeview
  lastEnumFunctionsList, lastEnumFunctionsListPos: TStringList;
  currentEnumFunctionsListPos: TStringList;
  bEnumFunctionsThreadWorking: Boolean;

  // flag usato per interrompere la ricerca in tutti i files
  bStopSearchAllFiles: Boolean;

  // history ricerca in tutti i files
  historySearchAllFiles: TStringList;

  // sto aspettando che venga lanciato il player
  _waiting_For_Player_Launching: Boolean;

resourcestring
  STextNotFound = 'Text not found';
  SNoSelectionAvailable = 'The is no selection available, search whole text?';
  ConfirmRemoveResource = 'Do you want to remove resource file selected?';
  ConfirmRemoveScript = 'Do you want to remove script file selected?';
  PleaseSelectResourceOrScriptToRemove = 'Please select a resource or script to remove.';
  ProjectExportedToSelectedDirectory = 'Project exported to selected directory. You can now copy all files into your device and launch the executable file.';
  Socket658VarNotFound='Debug Error: Variable not found!';
  Socket675WrongParamNumber='Debug Error: Wrong number of params!';
  THIS_IS_NOT_A_FBP_PROJECT='This isn''t an FBA Ide project!';
  Unable_To_Find_A_Free_Connection_Port='Unable to find a free tcp port for debugger!';
  Cannot_Change_Value_Of_A_Local_External_Variable='Cannot change the value of a local external variable';
  Cannot_Find_MainFba_Run_Aborted='Cannot find main.fba. Run aborted';
  Please_Write_A_Filename_For_Your_Script='Please write a filename for your script';
  Unable_To_Launch_Windows_Player='Unable to launch windows player';
  Unable_To_Load_Library_DLL='Unable to load library DLL!';

implementation

uses
  dlgSearchText, dlgReplaceText, dlgConfirmReplace, SynEditTypes, SynEditMiscProcs,
  Unit2, Unit4, Unit5, Unit6, Unit7, Unit8, Unit9, Unit10, Unit11;

var
    FrmHelp: TFrmHelp;

{$R *.dfm}
{$R myres2.RES}

function lua_GenericFakeDispatcher(L: Plua_State): Integer; cdecl;
begin
  // todo: vedere se va bene per fakare il dispatcher..non lo so..
  lua_pushnumber(L, 9999999);
  Result := 1;
end;

procedure TMainForm.UpdateProjectTitle();
begin
  if Length(project_saved)=0 then begin
    self.Caption := Application.Title + ' - [Untitled Project]';
  end else begin
    self.Caption := Application.Title + ' - ['+project_saved+']';
  end;
end;

// setta un alert nella status bar e inizia a far lampeggiare
// l'iconcina di alert
procedure TMainForm.SetAlertStatusBar(text: String; isalert: Boolean);
begin
  lastFastHelpFind := '';
  if isalert then begin
    flashingStatusAlert := 0;
    timerAlertStatus.Enabled := true;
  end;
  statusbar.Panels[5].Text := text;
  statusbar.Repaint;
end;

// setta i valori di default delle opzioni del progetto
procedure TMainForm.SetDefaultProjectSettings();
begin
  SETTINGS[SETTINGS_ORIENTATION]:='0';  // orientation
  SETTINGS[SETTINGS_FPS]:='30';  // fps
  SETTINGS[SETTINGS_SOUNDFREQ]:='44100'; // sound freq
  SETTINGS[SETTINGS_SOUNDBPS]:='16';  // bps
  SETTINGS[SETTINGS_SOUNDSTEREO]:='1'; // stereo
  SETTINGS[SETTINGS_SOUNDMAXMUSCHANNELS]:='2'; // max music channels
  SETTINGS[SETTINGS_SOUNDMAXSFXCHANNELS]:='16';  // max sounds channels
  SETTINGS[SETTINGS_PROJECTNAME]:='';  // project name
  SETTINGS[SETTINGS_PROJECTAUHTOR]:='';  // project author
  SETTINGS[SETTINGS_EXT_SOUNDS]:='1';  // extensions sounds
  SETTINGS[SETTINGS_EXT_TTF]:='1';  // extensions ttf
  SETTINGS[SETTINGS_START_BACKCOL1]:='0'; // colore di avvio della finestra principale
  SETTINGS[SETTINGS_START_BACKCOL2]:='0';
  SETTINGS[SETTINGS_START_BACKCOL3]:='0';
  SETTINGS[SETTINGS_TRANS1]:='255'; // colore trasparenza default delle pics
  SETTINGS[SETTINGS_TRANS2]:='0';
  SETTINGS[SETTINGS_TRANS3]:='255';
  SETTINGS[SETTINGS_VGA]:='0'; // abilita VGA
  SETTINGS[SETTINGS_WINDOWS_WIDTH]:='240'; // dimensioni width windows desktop
  SETTINGS[SETTINGS_WINDOWS_HEIGHT]:='320'; // dimensioni height windows desktop
  SETTINGS[SETTINGS_APPLICATION_ICON]:='';  // default application icon
end;

// imposta o menu la modalità run (abilita/Disabilita menu e pulsanti)
procedure TMainForm.SetGuiMode(mode: IDEStatus);
begin
  lastModeSelected:=mode;
  statusbar.Repaint;

  // stato editor
  synedit.ReadOnly := (mode <> EDITING);
  // main menu file
  mnuMainNewScript.Enabled := (mode = EDITING);
  mnuMainAddFile.Enabled := (mode = EDITING);
  mnuMainDeleteFile.Enabled := (mode = EDITING);
  mnuMainOpenProject.Enabled := (mode = EDITING);
  mnuMainSaveProject.Enabled := (mode = EDITING);
  mnuSaveAs.Enabled := (mode = EDITING);
  mnuMainNewProject.Enabled := (mode = EDITING);
  mainmnureopenproject.Enabled := (mode = EDITING);
  // build menu
  mnuBuild.Enabled := (mode = EDITING);
  // Bottoni save, load, run
  btnSaveProject.Enabled := (mode = EDITING);
  btnLoadProject.Enabled := (mode = EDITING);
  btnRun.Enabled := (mode = EDITING);
  btnRunStepByStep.Enabled := (mode = EDITING);
  btnNewProject.Enabled := (mode = EDITING);

  // abilita vardump e immediate textbox solo se non stai runnando
  vardump.Enabled := (mode <> RUNNING);
  txtImmediate.Enabled := (mode <> RUNNING);

  // edit main menu
  // stato settato in fase di popup
  // program main menu
  if mode = EDITING then begin
    // svuota lista stack se in editing
    vardump.Items.BeginUpdate;
    vardump.Items.Clear;
    vardump.Items.EndUpdate;

    mnuMainRun.Caption := 'Run';
    mnuMainRun.Enabled:=true;
    mnuRunStepByStep.Enabled := true;
    mnuMainStep.Enabled := false;
    mnuMainStop.Enabled := false;
    // bottoni stop, halt, go, pause
    btnStep.Enabled := false;
    btnHalt.Enabled := false;
    btnContinue.Enabled := false;
    btnPause.Enabled := false;
  end else if mode = RUNNING then begin
    mnuMainRun.Caption := 'Pause';
    mnuMainRun.Enabled:=true;
    mnuRunStepByStep.Enabled := false;
    mnuMainStep.Enabled := false;
    mnuMainStop.Enabled := false;
    // bottoni stop, halt, go, pause
    btnStep.Enabled := false;
    btnHalt.Enabled := true;
    btnContinue.Enabled := false;
    btnPause.Enabled := true;
    // svuota editbox immediatemode se stai runnando
    txtImmediate.Text := '';
  end else if mode = PAUSE then begin
    mnuMainRun.Caption := 'Continue';
    mnuMainRun.Enabled:=true;
    mnuRunStepByStep.Enabled := false;
    mnuMainStep.Enabled := true;
    mnuMainStop.Enabled := true;
    // bottoni stop, halt, go, pause
    //
    btnStep.Enabled := true;
    btnHalt.Enabled := true;
    btnContinue.Enabled := true;
    btnPause.Enabled := false;

    // dai il focus al textbox dell'immediate mode quando sei in pausa
    txtImmediate.SetFocus;
    
  end;
  // bottoni newsource, addfile, del file
  btnnewsource.Enabled := (mode = EDITING);
  btnaddresource.Enabled := (mode = EDITING);
  btndelfile.Enabled := (mode = EDITING);
  btnImportFont.Enabled := (mode = EDITING);
  // debug panel
  dbgpanel.Visible := (mode <> EDITING);
  dbgsplitter.Visible := (mode <> EDITING);
  // stato sulla statusbar
  if (mode = EDITING) then statusbar.Panels[2].Text := 'Stopped'
  else if (mode = RUNNING) then statusbar.Panels[2].Text := 'Running'
  else if (mode = PAUSE) then statusbar.Panels[2].Text := 'Paused'
  else statusbar.Panels[2].Text := '';

end;

// copia undolist di tipo synedit, da source a dest
procedure TMainForm.MoveUndoList(source: TSynEditUndoList; dest: TSynEditUndoList);
begin
  source.BeginBlock;
  dest.BeginBlock;
  // prima svuoto del tutto dest
  while dest.ItemCount > 0 do dest.PopItem;
  // poi copia source su dest
  while source.ItemCount > 0 do dest.PushItem(source.PopItem);
  source.EndBlock;
  dest.EndBlock;
end;

procedure TMainForm.DelBreakpoint(idbuffer: Integer; index: Integer);
var
scriptData: TScriptNode;
begin
  scriptData := scriptNode.Item[idbuffer].Data;
  // se il socket è attivo, segnala lo switch del breakpoint
  // al runner pocketfrog
  if socket.State = wsConnected then begin
    SockSend('break '+LowerCase(scriptData.name)+' '+scriptData.breakpoints_line.Strings[index]);
  end;
  // rimuovi testo e linea dalla lista dei breakpoints
  scriptData.breakpoints_line.Delete(index);
  scriptData.breakpoints_text.Delete(index);
end;

procedure TMainForm.AddBreakpoint(idbuffer: Integer; line: Integer);
var
scriptData: TScriptNode;
begin
  scriptData := scriptNode.Item[idbuffer].Data;
  // se il socket è attivo, segnala lo switch del breakpoint
  // al runner pocketfrog
  if socket.State = wsConnected then begin
    SockSend('break '+LowerCase(scriptData.name)+' '+IntToStr(line));
  end;
  // aggiungi testo e linea alla lista dei breakpoint
  scriptData.breakpoints_text.Add(synedit.Lines.Strings[line-1]);
  scriptData.breakpoints_line.Add(IntToStr(line));
end;



// invia una linea sul socket
procedure TMainForm.SockSend(s: String);
begin
  socket.SendStr(s+#13#10);
  // Debug
  {$IFDEF DEBUG_OUTPUT_DEBUGCONSOLE_LOG_TRAFFIC}
    MainForm.outputdebugconsole.Lines.Add('>> '+s);
  {$ENDIF}
end;

// controlla la sintassi del progetto corrente
function TMainForm.CheckSyntax: Boolean;
var
a, idbuffer, b: Integer;
syntaxok, onmainloopfound: Boolean;
nodeData: TScriptNode;
str: String;
begin
  // recupera id buffer del main.fba
  idbuffer := FindBufferID('main.fba');
  if idbuffer = -1 then begin
    SetAlertStatusBar( Cannot_Find_MainFba_Run_Aborted, true);
    Result := false;
    exit;
  end;

  // procedi con il check sintassi del progetto
	LUA := lua_open();
	luaopen_base(LUA);
	luaopen_math(LUA);
	luaopen_string(LUA);
	//luaopen_package(LUA);

  synedit.ReadOnly:=true;
  syntaxok:=true;
  try
    onmainloopfound := false;

    // prova a caricare tutti gli script
    for a:=0 to scriptNode.Count-1 do begin
      nodeData := scriptNode.Item[a].Data;

      // Carica lo script
      if Length(nodeData.buffer.Text) > 0 then begin

        LuaLoadBuffer(LUA, nodeData.buffer.Text, UpperCase(nodeData.name));

        // Se è il main.fba, verifica che ci sia onmainloop dentro
        if UpperCase(nodeData.name)='MAIN.FBA' then begin
          // cicla le righe e prova a trovare la funzione
          // ho sonno, mal di testa e non riesco a trovare un modo migliore per vedere
          // Se la funzione c'è. Inoltre LUA non me lo dice (forse bisogna eseguire
          // il codice?)
          for b:=0 to nodeData.buffer.Count-1 do begin
            if Replace(Replace(UpperCase(Trim(nodeData.buffer.Strings[b])),' ', ''), Chr(9), '')='FUNCTIONONMAINLOOP()' then begin
              onmainloopfound := true;
              break;
            end;
          end;
        end;

      end;
    end;

    // se non trovi onmainloop, segnala ed esci
    if (onmainloopfound = false) then begin
      SetAlertStatusBar( '[MAIN.FBA] - Error: Missing required function onmainloop', true);
      idbuffer := FindBufferID('main.fba');
      MainForm.SelectScript(idbuffer);
      syntaxok:=false;
    end;

  except
    on E: ELuaException do begin
      // segnala errore solo se uno script incluso non ha già dato errore!
        // estrae il nome dello script che ha dato errore
        str := Copy(E.Title, Pos('"', E.Title)+1, Length(E.Title)-Pos('"', E.Title)-1);
        str := Copy(str, 1, Pos('"', str)-1);

        idbuffer := FindBufferID(str);
        if idbuffer > -1 then begin
          // errore script, mostra nella status
          SetAlertStatusBar( '['+str+'] Line '+IntToStr(E.Line) + ' - Error: ' + E.Msg, true);
          // seleziona buffer nella lista
          MainForm.SelectScript(idbuffer);
          synedit.GotoLineAndCenter(E.Line);
        end else begin
          ShowMessage('Unable to find source file with error: ' + str);
        end;
      syntaxok:=false;
    end;
  end;
  lua_close(LUA);
  synedit.ReadOnly:=false;

  if (syntaxok) then begin
    SetAlertStatusBar('Syntax ok', false);
    Result := true;
  end else begin
    Result := false;
  end;

end;

// rimuove le risorse esterne dalla directory specificata
procedure TMainForm.ClearExtResourceFromDir(dir: String);
var
root, destfile: String;
a: Integer;
extresnode: TExtResourceNode;
begin
  root := FPath(dir);
  for a:=0 to ExtresourceNode.Count-1 do begin
    extresnode := ExtresourceNode.Item[a].Data;
    destfile := root + ExtractFileName(extresnode.fullpath);
    if FileExists(destfile) then DeleteFile(destfile);
  end;
end;

// data una rootdir, torna una lista di files delle risorse esterne del progetto
//
function TMainForm.GetExtResourceFilesList(dir: String): TStringList;
var
root,destfile: String;
a: Integer;
extresnode: TExtResourceNode;
res: TStringList;
begin

  res := TStringList.Create;

  // se non c'è nessuna risorsa esterna, esci
  if ExtresourceNode.Count = 0 then begin
    Result := res;
    exit;
  end;
  
  root := FPath(dir);

  // ciclo risorse esterne
  for a:=0 to ExtresourceNode.Count-1 do begin
    extresnode := ExtresourceNode.Item[a].Data;
    destfile := root + ExtractFileName(extresnode.fullpath);
    if FileExists(destfile) then res.Add( destfile )
  end;

  Result := res;
//
end;

// copia i file di risorse esterne all'interno del percorso specificato
function TMainForm.CopyExtResourcesToDir(dir: String; device: Integer): Integer;
var
root,destfile, justdestfile, dlltype: String;
a: Integer;
extresnode: TExtResourceNode;
begin
  // se non c'è nessuna risorsa esterna, esci
  // se abbiamo già cachato e copiato le risorse esterne, esci
  if (ExtresourceNode.Count = 0) or (COPY_EXTERNAL_RESOURCES_CACHED = true) then begin
    Result := 1;
    exit;
  end;

  root := FPath(dir);
  Result := 1;

  // ciclo risorse esterne
  for a:=0 to ExtresourceNode.Count-1 do begin

    extresnode := ExtresourceNode.Item[a].Data;
    justdestfile := ExtractFileName(extresnode.fullpath);
    destfile := root + justdestfile;

    // se è una DLL custom (x86_mydll.dll, arm_mydll.dll) e l'architettura non è quella passata come parametro (device)
    // non copiare, vai al prossimo file
    if (Length(justdestfile) > 3) and (UpperCase(Copy(justdestfile, Length(justdestfile)-3, 4))='.DLL') then begin
      dlltype := UpperCase(Copy(justdestfile, 1, 4));
      //
      if (dlltype='ARM_') and (device <> 1) then continue;
      if (dlltype='X86_') and (device <> 0) then continue;
    end;


    if CopyFile(PChar(extresnode.fullpath), PChar(destfile), false) = false then begin
      ShowMessage('Unable to copy external resource file to: ' + destfile);
      Result := 2;
    end;

  end;


end;

{$IFDEF USE_LZAB_DLL}

// costruisce il file a partire dalla lista delle risorse
// lzab.dll version
function TMainForm.BuildDatFromResourcesList(dat: String; scriptsDat: String) : Integer;
var
finalstr: String;
a: Integer;
resnode: TResourceNode;
scriptres: TScriptNode;
begin

  // se non c'è nessuna risorsa e script, esci con msg positivo
  if (resourceNode.Count = 0) and (scriptNode.Count = 0) then begin
    Result := 0;
    exit;
  end;

  // crea la stringa di comando per lzab se abbiamo risorse
  // e se il .dat non è flaggato già come cachato
  if (resourceNode.Count > 0) and (COPY_INTERNAL_RESOURCES_CACHED = false) then begin

    // cancella il file .dat se già esiste
    if FileExists(dat) then DeleteFile(dat);

    finalstr := 'fake.dll|-a|'+dat;

    // ciclo risorse
    for a:=0 to resourceNode.Count-1 do begin
      resnode := resourceNode.Item[a].Data;
      finalstr := finalstr + '|'+resnode.fullpath;
    end;

    // avvia comando DA DLL
    creatingDatFileCode := lzab(PChar(finalstr));

    if creatingDatFileCode <> 3 then begin
      Result := creatingDatFileCode;
      exit;
    end;
  end;    // if resourceNode.Count > 0 then begin

  // ciclo script (non esiste cache, crea sempre)
  for a:=0 to scriptNode.Count-1 do begin
    scriptres := scriptNode.Item[a].Data;
    creatingDatFileCode := lzab_add_from_memory(PChar(scriptsDat), PChar(scriptres.name), scriptres.buffer.GetText);
    if creatingDatFileCode <> 3 then begin
      Result := creatingDatFileCode;
      exit;
    end;
  end;

  Result := 3;

end;


{$ELSE}
// costruisce il file a partire dalla lista delle risorse
// lzab.exe version
function TMainForm.BuildDatFromResourcesList(dat: String) : Integer;
var
finalstr: String;
a: Integer;
resnode: TResourceNode;
scriptres: TScriptNode;
lzab,tempfile: String;
tempfiles: TStringList;
begin
  // cancella il file .dat se già esiste
  if FileExists(dat) then DeleteFile(dat);

  // se non c'è nessuna risorsa e script, esci con msg positivo
  if (resourceNode.Count = 0) and (scriptNode.Count = 0) then begin
    Result := 0;
    exit;
  end;

  // estrae il file lzab in una dir temporanea
  lzab := FPath(pcinfo.Folders.Temp)+ 'lzab__eee2.exe';
  CopyRes('LZAB', lzab);

  // crea la stringa di comando per lzab
  finalstr := ' -a "'+dat+'" ';

  // lista files temporanei da cancellare all'uscita
  tempfiles := TStringList.Create;

  // ciclo risorse
  for a:=0 to resourceNode.Count-1 do begin
    resnode := resourceNode.Item[a].Data;
    finalstr := finalstr + '"'+resnode.fullpath+'" ';
  end;

  // ciclo script
  for a:=0 to scriptNode.Count-1 do begin
    scriptres := scriptNode.Item[a].Data;

    // crea un file temporaneo
    tempfile := FPath(pcinfo.Folders.Temp) +scriptres.name;
    tempfiles.Add(tempfile);
    scriptres.buffer.SaveToFile(tempfile);

    finalstr := finalstr + '"'+tempfile+'" ';
  end;

  // avvia comando e attendi esito
  creatingDatFileCode := -99999;
  createprocess.ApplicationName := lzab;
  createprocess.CommandLine := finalstr;
  createprocess.Run;

  while creatingDatFileCode = -99999 do begin
    Application.ProcessMessages;
  end;

  // cancella i files temporanei
  for a:=0 to tempfiles.Count- 1  do begin
    if FileExists(tempfiles.Strings[a]) then DeleteFile(tempfiles.Strings[a]);
  end;
  tempfiles.Free;

  // elimina lzab
  if FileExists(lzab) then DeleteFile(PChar(lzab));

  Result := creatingDatFileCode;

end;
{$ENDIF}


function TMainForm.GetIconIndexFromFileName(s: String): Integer;
var
piece: String;
begin
  if UpperCase(s) = 'MAIN.FBA' then begin
    Result := 6;
  end else if Pos('.', s) >= 0 then begin
    piece := LowerCase(Copy(s, Pos('.', s)+1, Length(s)-Pos('.',s)));
    if (piece='wav') or (piece='mod') then Result := 3
    else if (piece='png') or (piece='jpg') then Result := 2
    else if (piece='ttf') then Result := 4
    else if (piece='fba') then Result := 1
    else Result := 5;
  end else begin
    // icona default
    Result := 5;
  end;
end;

procedure TMainForm.DoSearchReplaceText(AReplace: boolean;
  ABackwards: boolean);
var
  Options: TSynSearchOptions;
begin
  SetAlertStatusBar('', false);
  if AReplace then
    Options := [ssoPrompt, ssoReplace, ssoReplaceAll]
  else
    Options := [];
  if ABackwards then
    Include(Options, ssoBackwards);
  if gbSearchCaseSensitive then
    Include(Options, ssoMatchCase);
  if not fSearchFromCaret then
    Include(Options, ssoEntireScope);
  if gbSearchSelectionOnly then
  begin
    if (not synedit.SelAvail) or SameText(synedit.SelText, gsSearchText) then
    begin
      if MessageDlg(SNoSelectionAvailable, mtWarning, [mbYes, mbNo], 0) = mrYes then
        gbSearchSelectionOnly := False
      else
        Exit;
    end
    else
      Include(Options, ssoSelectedOnly);
  end;
  if gbSearchWholeWords then
    Include(Options, ssoWholeWord);
    synedit.SearchEngine := SynEditSearch;
  if synedit.SearchReplace(gsSearchText, gsReplaceText, Options) = 0 then
  begin
    MessageBeep(MB_ICONASTERISK);
    SetAlertStatusBar( STextNotFound , true);
    if ssoBackwards in Options then
      synedit.BlockEnd := synedit.BlockBegin
    else
      synedit.BlockBegin := synedit.BlockEnd;
    synedit.CaretXY := synedit.BlockBegin;
  end;

  if ConfirmReplaceDialog <> nil then
    ConfirmReplaceDialog.Free;
end;

procedure TMainForm.ShowSearchReplaceDialog(AReplace: boolean);
var
  dlg: TTextSearchDialog;
begin
  SetAlertStatusBar('', false);
  if AReplace then
    dlg := TTextReplaceDialog.Create(Self)
  else
    dlg := TTextSearchDialog.Create(Self);
  with dlg do try
    // assign search options
    SearchBackwards := gbSearchBackwards;
    SearchCaseSensitive := gbSearchCaseSensitive;
    SearchFromCursor := gbSearchFromCaret;
    SearchInSelectionOnly := gbSearchSelectionOnly;
    // start with last search text
    SearchText := gsSearchText;

    // maiofix - se c'è qualcosa selezionata in synedit, mostra quello come testo da cercare
    if synedit.SelLength > 0 then SearchText := synedit.SelText;

    if gbSearchTextAtCaret then begin
      // if something is selected search for that text
      if synedit.SelAvail and (synedit.BlockBegin.Line = synedit.BlockEnd.Line)
      then
        SearchText := synedit.SelText
      else
        SearchText := synedit.GetWordAtRowCol(synedit.CaretXY);
    end;
    SearchTextHistory := gsSearchTextHistory;
    if AReplace then with dlg as TTextReplaceDialog do begin
      ReplaceText := gsReplaceText;
      ReplaceTextHistory := gsReplaceTextHistory;
    end;
    SearchWholeWords := gbSearchWholeWords;
    if ShowModal = mrOK then begin
      gbSearchSelectionOnly := SearchInSelectionOnly;
      gbSearchBackwards := SearchBackwards;
      gbSearchCaseSensitive := SearchCaseSensitive;
      gbSearchFromCaret := SearchFromCursor;
      gbSearchWholeWords := SearchWholeWords;
      //gbSearchRegex := SearchRegularExpression;
      gsSearchText := SearchText;
      gsSearchTextHistory := SearchTextHistory;
      if AReplace then with dlg as TTextReplaceDialog do begin
        gsReplaceText := ReplaceText;
        gsReplaceTextHistory := ReplaceTextHistory;
      end;
      fSearchFromCaret := gbSearchFromCaret;
      if gsSearchText <> '' then begin
        DoSearchReplaceText(AReplace, gbSearchBackwards);
        fSearchFromCaret := TRUE;
      end;
    end;
  finally
    dlg.Free;
  end;
end;

// verifica se abbiamo scripts e risorse e se almeno uno non è copiato (risorse)
// o salvato (script)
function TMainForm.SomethingUnsaved(): Boolean;
var
  a: Integer;
  nodeData: TScriptNode;
  resourceData: TResourceNode;
begin
  if (scriptNode.Count > 0) or (resourceNode.Count > 0) then begin
    // cerca uno script non salvato
    for a:=0 to scriptNode.Count-1 do begin
      nodeData := scriptNode.Item[a].Data;
      if nodeData.saved = false then begin
        Result := true;
        exit;
      end;
    end;
    // cerca una risorsa non ancora copiata nel percorso del progetto
    for a:=0 to resourceNode.Count-1 do begin
      resourceData := resourceNode.Item[a].Data;
      if resourceData.copied = false then begin
        Result := true;
        exit;
      end;
    end;
  end;
  // se siamo qui, è tutto salvato/copiato
  Result := false;
end;

// seleziona lo script, salva il buffer corrente, se c'è, aggiorna la status bar
procedure TMainForm.SelectScript(idbuffer: Integer);
var
  nodeData: TScriptNode;
begin

  // se vogliamo selezionare nessun buffer, cancella il testo nel synedit e non selezionare piu' nulla
  if idbuffer = -1 then begin
    current_buffer := -1;
    synedit.Text := '';
    statusbar.Panels[0].Text:='';
    exit;
  end;

  // salva il buffer corrente (e anche la prima riga visualizzata)
  // salva anche la undo e redolist corrente
  if current_buffer > -1 then begin
    nodeData := scriptNode.Item[current_buffer].Data;
    nodeData.buffer.Text := synedit.Text;
    nodeData.firstVisibleLine := synedit.TopLine;
    // salva undo e redolist correnti
    MoveUndoList(synedit.UndoList, nodeData.undolist);
    MoveUndoList(synedit.Redolist, nodeData.Redolist);
  end;

  // aggiorna la status bar (con asterisco se non è salvata)
  nodeData := scriptNode.Item[idbuffer].Data;
  if nodeData.saved = true then begin
    statusbar.Panels[0].Text:='';
  end else begin
    statusbar.Panels[0].Text:='* ';
  end;
  statusbar.Panels[0].Text := statusbar.Panels[0].Text + nodeData.name;

  // carica il buffer nell'editor
  synedit.Text := nodeData.buffer.Text;

  // maio - spostato su perchè i 2 moveundolist piu' in basso
  // causano evento onchange che accede a current_buffer
  current_buffer := idbuffer;

  // e ripristina la prima riga visualizzata
  synedit.TopLine := nodeData.firstVisibleLine;

  // ripristina la undo/redolist di questo buffer
  MoveUndoList(nodeData.undolist, synedit.UndoList);
  MoveUndoList(nodeData.Redolist, synedit.Redolist);

  // aggiorna voce nel treview
  scriptNode.Item[idbuffer].Text := statusbar.Panels[0].Text;

  // aggiorna conteggio scripts
  scriptNode.Text := 'Scripts (' + IntToStr(scriptNode.Count) + ')';

  //current_buffer := idbuffer;

  // seleziona l'elemento nella treeview  se hai selezionato un altro elemento
  scriptNode.Item[idbuffer].Selected:=true;

end;

// libera il treeview
procedure TMainForm.FreeTreeView(recreateVoid: Boolean);
var
  nodeData: TScriptNode;
  resourceData: TResourceNode;
  extResourceData: TExtResourceNode;
begin
  projfiles.Items.BeginUpdate;

  // free dei dati del treeview
  if scriptNode <> nil then begin
    while scriptNode.Count > 0 do begin
      scriptNode.Item[0].Selected:=true;
      nodeData := scriptNode.Item[0].Data;
      nodeData.buffer.Free;
      nodeData.breakpoints_line.Free;
      nodeData.breakpoints_text.Free;
      nodeData.undolist.Free;
      nodeData.redolist.Free;
      nodeData.Free;
      scriptNode.Item[0].Delete;
    end;
  end;

  if resourceNode <> nil then begin
    while resourceNode.Count > 0 do begin
      resourceData := resourceNode.Item[0].Data;
      resourceData.Free;
      resourceNode.Item[0].Delete;
    end;
  end;

  if extResourceNode <> nil then begin
    while extResourceNode.Count > 0 do begin
      extResourceData := extResourceNode.Item[0].Data;
      extResourceData.Free;
      extResourceNode.Item[0].Delete;
    end;
  end;

  projfiles.Items.Clear;

  scriptNode:=nil;
  resourceNode:=nil;
  extResourceNode:=nil;
  
  // ricrea i nodi principali, se richiesto e main.fba
  if recreateVoid then begin
    scriptNode := projfiles.Items.AddNode(nil, nil, 'Scripts (0)', nil, naAddChild);
    resourceNode := projfiles.Items.AddNode(nil, nil, 'Resources (0)', nil, naAddChild);
    extResourceNode := projfiles.Items.AddNode(nil, nil, 'Ext.Resources (0)', nil, naAddChild);
  end;

  current_buffer := -1;

  projfiles.Items.EndUpdate;
end;

// Trova l'id del buffer specificato
function TMainForm.FindBufferID(filename: String): Integer;
var
a: Integer;
nodeData: TScriptNode;
begin
  for a:=0 to scriptNode.Count-1 do begin

    nodeData := scriptNode.Item[a].Data;

    if (UpperCase(nodeData.name) = UpperCase(filename)) then begin
      Result:=a;
      exit;
    end;
  end;
  Result:=-1;
end;


function TMainForm.SaveProject(saveas: Boolean): Boolean;
var
resdir, project: String;
numres, a: Integer;
nodeData: TScriptNode;
resourceData: TResourceNode;
extResourceData: TExtResourceNode;
ini: TIniFile;
begin
  // se non abbiamo risorse e script, non c'è nulla da salvare
  if ( (scriptNode.Count = 0) and (resourceNode.Count = 0) ) or (current_buffer = -1) then begin
    ShowMessage('Cannot save an empty project!');
    Result := false;
    exit;
  end;

  // se non abbiamo ancora salvato il progetto, dobbiamo mostrare finestra selezione file
  // mostra finestra selezione file, anche se selezioniamo "save as"
  if (Length(project_saved) = 0) or (saveas) then begin
    if dlgsaveproject.Execute = false then begin
      Result:=false;
      exit;
    end;
    project := dlgsaveproject.FileName;
    // aggiungi .fbp alla fine se non presente
    if Pos('.FBP', UpperCase(project)) <= 0 then project := project + '.fbp';

    // save progetto (nuovo path): flagga risorse, exe e risorse esterne come non cachate
    // cosi' ci assicuriamo di ricreare tutto quando serve
    COPY_INTERNAL_RESOURCES_CACHED := false;
    COPY_FBA_PLAYER_CACHED := false;
    COPY_EXTERNAL_RESOURCES_CACHED := False;

  end else begin
    project := project_saved;
  end;


  // crea directory risorse/scripts per questo progetto
  resdir := FExtractFilePath(project)+ExtractFileName(project)+'.res\';
  try
    if DirectoryExists(resdir) = false then begin
      mkdir(resdir);
    end;
  except
    ShowMessage('Unable to create export directory: ' + resdir);
    Result:=false;
    exit;
  end;

  // sincronizza il buffer
  nodeData := scriptNode.Item[current_buffer].Data;
  nodeData.buffer.Text := synedit.Text;

  // ok procedi con il salvataggio...

  // cancella il file se già esiste
  if FileExists(project) then begin
    DeleteFile(project);
  end;

  ini := TIniFile.Create(project);

  ini.WriteString('general', 'version', FILE_VERSION);

  // scriva directory risorse progetto
  ini.WriteString('general', 'resdir', ExtractFileName(project));
  
  // Segna quanti script abbiamo nel file di progetto
  ini.WriteInteger('general', 'numscripts', scriptNode.Count);

  // cicla ogni sorgente e salvalo su file, nella cartella
  for a:=0 to scriptNode.Count-1 do begin

    nodeData := scriptNode.Item[a].Data;

    // aggiorna il treeview
    scriptNode.Item[a].Text := nodeData.name;
    // salva il file
    nodeData.buffer.SaveToFile(resdir+ExtractFileName(nodeData.name));

    // Salva il nome dello script nel file di progetto
    ini.WriteString('scripts', IntToStr(a), nodeData.name);

    // lo script è ora salvato
    nodeData.saved:=true;
  end;

  // tocca alle risorse adesso!
  numres:=0;
  for a:=0 to resourceNode.Count-1 do begin

    resourceData := resourceNode.Item[a].Data;

    // se la risorsa è da spostare nella dir del progetto, oppure abbiamo selezionato "save as"
    if (resourceData.copied = false) or (saveas) then begin

      // copia il file nella directory delle risorse
      if FileExists(resourceData.fullpath) then begin
        CopyFile(PChar(resourceData.fullpath), PChar(resdir+resourceData.name), false);
        // aggiorna nuovo path nella struct
        resourceData.fullpath :=  resdir+resourceData.name;
        resourceData.copied := true;
        // segna file nel file di progetto
        ini.WriteString('resources', IntToStr(numres), resourceData.name);
        numres:=numres+1;
      end else begin
        ShowMessage('Resource file not found, skipped: ' + resourceData.fullpath);
      end;

    end else begin

      // è già spostata  nella dir delle risorse
      if FileExists(resourceData.fullpath) then begin
        // segna file nel file di progetto
        ini.WriteString('resources', IntToStr(numres), resourceData.name);
        numres:=numres+1;
      end else begin
        ShowMessage('Resource file not found, skipped: ' + resourceData.fullpath);
      end;

    end;
  end;

  // segna nel file di progetto il numero di risorse
  ini.WriteInteger('general', 'numresources', numres);


  // tocca alle risorse ESTERNE adesso!
  numres:=0;
  for a:=0 to extResourceNode.Count-1 do begin

    ExtResourceData := extResourceNode.Item[a].Data;

    // se la risorsa è da spostare nella dir del progetto, oppure abbiamo selezionato "save as"
    if (ExtResourceData.copied = false) or (saveas) then begin

      // copia il file nella directory delle risorse
      if FileExists(ExtResourceData.fullpath) then begin
        CopyFile(PChar(ExtResourceData.fullpath), PChar(resdir+ExtResourceData.name), false);
        // aggiorna nuovo path nella struct
        ExtResourceData.fullpath :=  resdir+ExtResourceData.name;
        ExtResourceData.copied := true;
        // segna file nel file di progetto
        ini.WriteString('extresources', IntToStr(numres), ExtResourceData.name);
        numres:=numres+1;
      end else begin
        ShowMessage('External resource file not found, skipped: ' + ExtResourceData.fullpath);
      end;

    end else begin

      // è già spostata  nella dir delle risorse
      if FileExists(ExtResourceData.fullpath) then begin
        // segna file nel file di progetto
        ini.WriteString('extresources', IntToStr(numres), ExtResourceData.name);
        numres:=numres+1;
      end else begin
        ShowMessage('External Resource file not found, skipped: ' + ExtResourceData.fullpath);
      end;

    end;
  end;

  // segna nel file di progetto il numero di risorse
  ini.WriteInteger('general', 'numextresources', numres);


  // salva settings progetto
  for a:=1 to SETTINGS_MAX_SETTINGS do begin
    ini.WriteString('PROJECT_SETTINGS', IntToStr(a), SETTINGS[a]);
  end;

  ini.Free;

  // recupera il nome del file aperto e scrivilo nella status bar
  statusbar.Panels[0].Text := scriptNode.Item[current_buffer].Text;

  // adesso il progetto è salvato
  project_saved := project;
  UpdateProjectTitle();

  Result:=true;
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  // recupera address dalle funzioni dentro la DLL
  GetDLLFuncs();
  // inizializza lista ultimi progetti aperti
  InitLastOpenedProjectsList();
  // non stai aspettando il player
  _waiting_For_Player_Launching := false;
  // crea history per ricerche in tutti i files
  historySearchAllFiles := TStringList.Create;
  // crea history per comandi immediate mode
  immediateHistory := TStringList.Create;
  immediateHistoryPos := 0;
  // setta porta default di connessione
  DEFAULT_CONNECTION_PORT:=5556;
  // associa files di progetto all'editor
  RegisterFileType('fbp', Application.ExeName);
  // Carica valori settings project default
  SetDefaultProjectSettings();
  // applica highlither lua
  synedit.Highlighter := TSynLuaSyn.Create(nil);
  synedit.DoubleBuffered:=true;
  // aggiunge i 2 rami nella project list (sorgenti/risorse)
  scriptNode:=nil;
  resourceNode:=nil;
  extResourceNode:=nil;
  MainForm.FreeTreeView(true);
  // nessun buffer in editing
  current_buffer:=-1;
  // progetto non salvato
  project_saved:='';
  UpdateProjectTitle();
  // non c'è nessuna linea runnante
  lastLoaderLine:=-1;
  // setta modalità editing
  SetGuiMode(EDITING);
  // recupera lista delle funzioni lua supportate da lua
  luaFunctionsList := TStringList.Create;
  luaFunctionsList := GetLUAFunctionsFromFile('LOADERLUA');
  // inizializza fast help
  InitLuaFunctionsImmediateHelp();
  lastFastHelpFind:='';
  // finestra di help non aperta
  FrmHelp:=nil;
  // thread che enumera le funzioni non sta lavorando
  bEnumFunctionsThreadWorking := False;
  lastEnumFunctionsList := TStringList.Create;
  lastEnumFunctionsListPos := TStringList.Create;
  currentEnumFunctionsListPos := TStringList.Create;
  // massimizza la finestra (bugfix per i temi di windows?)
  MainForm.WindowState := wsMaximized;
  // se c'è un parametro, consideralo come un file da caricare
  if ParamCount() > 0 then LoadProject(ParamStr(1));
  // carica i settaggi dell'editor in memoria
  LoadEditorOptionsInMemory();
  // setta i colori dai settaggi in memoria
  SetSettingsFromOptionsInMemory();
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  // distrugge history per ricerca in tutti i fils
  historySearchAllFiles.Free;
  // distrugge lista ultimi files aperti
  FreeLastOpenedProjectsList();
  // distrugge lista temp delle funzioni enumerate
  timerEnumFunctions.Enabled := false;
  lastEnumFunctionsList.Free;
  lastEnumFunctionsListPos.Free;
  currentEnumFunctionsListPos.Free;
  // distrugge history immediate mode
  immediateHistory.Free;
  // distrugge i dati nel treeview
  FreeTreeView(false);
  // distrugge lista funzioni lua
  luaFunctionsList.Free;
  // distrugge fast help
  FreeLuaFunctionsImmediateHelp();
  // finalizza la DLL
  FreeDLL();
end;

procedure TMainForm.syneditChange(Sender: TObject);
var
  nodeData: TScriptNode;
  node: TTreeNode;
begin

  // se non stai editando alcun file, setta che stai editando il main
  if Length(statusbar.Panels[0].Text) = 0 then begin
    statusbar.Panels[0].Text:='* main.fba';
    // aggiungi main.fba alla lista dei files
    nodeData := TScriptNode.Create;
    nodeData.firstVisibleLine:=1;
    nodeData.buffer := TStringList.Create;
    nodeData.breakpoints_line := TStringList.Create;
    nodeData.breakpoints_text := TStringList.Create;

    nodeData.undolist := TSynEditUndoList.Create;
    nodeData.redolist := TSynEditUndoList.Create;

    nodeData.saved := false;
    nodeData.name := 'main.fba';
    node := projfiles.Items.AddNode(nil, scriptNode, '* main.fba', nodeData, naAddChild	);
    // il primo main.fba - bugged - scriptNode.AlphaSort(false);
    node.ImageIndex := GetIconIndexFromFileName('main.fba');
    node.SelectedIndex := GetIconIndexFromFileName('main.fba');
    scriptNode.Expand(true);
    // stiamo editando il buffer 0
    current_buffer:=0;

  end else begin

    // setta lo script che non è stato salvato e aggiorna statusbar
    nodeData := scriptNode.Item[current_buffer].Data;
    nodeData.saved := false;
    statusbar.Panels[0].Text:='* '+nodeData.name;
    // E il treeview
    scriptNode.Item[current_buffer].Text:='* '+nodeData.name;
  end;

  // Maio - pare che il repaint sia un po' bacato..lo riforzo sempre...ma c'è
  // da vedere se non rallenta troppo...
  synedit.Repaint;
end;

procedure TMainForm.btnnewsourceClick(Sender: TObject);
var
  newname: String;
  ClickedOK: Boolean;
  a: Integer;
  nodeData: TScriptNode;
  node: TTreeNode;
begin

  // aggiungi elemento se non c'era già
  ClickedOK := InputQuery('Add new source script', 'Enter name', newname);
  if ClickedOK=false then exit;

  // fixa il nome
  newname := MakeValidPath(newname);
  
  // missing nome
  if Length(newname) = 0 then begin
    ShowMessage(Please_Write_A_Filename_For_Your_Script);
    exit;
  end;

  // aggiungi .fba alla fine, se mancante
  if Length(newname) > 3 then begin
    if Copy(UpperCase(newname), Length(newname)-3, 4) <> '.FBA' then newname := newname + '.fba';
  end else begin
    newname := newname + '.fba';
  end;

  for a:=0 to scriptNode.Count-1 do begin
      if ( (UpperCase(scriptNode.Item[a].Text) = UpperCase(newname)) or
          (UpperCase(scriptNode.Item[a].Text) = UpperCase('* '+newname))) then begin
        ShowMessage('This filename is already in list!');
        exit;
      end;
  end;

  // salva il buffer corrente, se selezionato
  if current_buffer > -1 then begin
    nodeData := scriptNode.Item[current_buffer].Data;
    nodeData.buffer.Text := synedit.Text;
    // copia la undo/Redolist corrente dal synedit
    MoveUndoList(synedit.UndoList, nodeData.undolist);
    MoveUndoList(synedit.Redolist, nodeData.Redolist);
  end;

  // aggiungi nodo files
  nodeData := TScriptNode.Create;
  nodeData.firstVisibleLine:=1;
  nodeData.buffer := TStringList.Create;
  nodeData.breakpoints_text := TStringList.Create;
  nodeData.breakpoints_line := TStringList.Create;

  nodeData.undolist := TSynEditUndoList.Create;
  nodeData.redolist := TSynEditUndoList.Create;
  
  nodeData.saved := false;
  nodeData.name := newname;
  node := projfiles.Items.AddNode(nil, scriptNode, '* '+newname, nodeData, naAddChild);
  // il primo main.fba - bugged - scriptNode.AlphaSort(false);
  node.ImageIndex := GetIconIndexFromFileName(newname);
  node.SelectedIndex := GetIconIndexFromFileName(newname);
  // aggiorna label scripts treeview
  scriptNode.Text:='Scripts ('+IntToStr(scriptNode.Count)+')';
  // svuota buffer
  current_buffer:=scriptNode.Count-1;
  // ora stai editando quel file
  statusbar.Panels[0].Text:='* '+newname;
  synedit.Text:='';
  // espandi threeview
  scriptNode.Expand(true);
  // seleziona l'ultimo script nel treeview (si spera sia questo:D)
  scriptNode.Item[scriptNode.Count-1].Selected:=true;
end;

procedure TMainForm.projfilesChange(Sender: TObject; Node: TTreeNode);
var
i: Integer;
begin
  // se hai cliccato su uno script, selezionalo
  if projfiles.Selected <> nil then begin
    if projfiles.Selected.Parent = scriptNode then begin
      // cerca id relativo al sottonodo
      for i:=0 to scriptNode.Count-1 do begin
        if scriptNode.Item[i].Selected then begin
          // seleziona quel buffer
          MainForm.SelectScript(i);
          break;
        end;
      end;
    end;
  end;
end;

procedure TMainForm.btnaddresourceClick(Sender: TObject);
var
a,b: Integer;
found, is_a_script: Boolean;
resourceData: TResourceNode;
scriptData: TScriptNode;
node: TTreeNode;
begin
  if dlgaddresource.Execute=false then exit;

  // flagga risorse non cachate
  // cosi' ci assicuriamo di ricreare tutto quando serve
  COPY_INTERNAL_RESOURCES_CACHED := false;

  // aggiungi ogni file alla lista delle risorse, se non già aggiunto
  for a:=0 to dlgaddresource.Files.Count-1 do begin

    // Verifica se il file esiste
    if FileExists(dlgaddresource.Files[a])=false then begin
      ShowMessage(dlgaddresource.Files[a] + ' not found.');
      continue;
    end;

    // verifica se c'era già
    // controlla se è uno script o una risorsa
    is_a_script:=false;
    if Length(ExtractFileName(dlgaddresource.Files.Strings[a])) > 3 then begin
      if Copy(UpperCase(ExtractFileName(dlgaddresource.Files.Strings[a])), Length(ExtractFileName(dlgaddresource.Files.Strings[a]))-3, 4) = '.FBA' then begin
        is_a_script:=true;
      end;
    end;

    // e poi verifica se è già in lista
    found:=false;
    if is_a_script=false then begin
      for b:=0 to resourceNode.Count-1 do begin
        resourceData := resourceNode.Item[b].Data;
        if UpperCase(resourceData.name) =
            UpperCase(ExtractFileName(dlgaddresource.Files.Strings[a])) then begin
            found:=true;
            break;
        end;
      end;
    end else begin
      for b:=0 to scriptNode.Count-1 do begin
        scriptData := scriptNode.Item[b].Data;
        if UpperCase(scriptData.name) =
            UpperCase(ExtractFileName(dlgaddresource.Files.Strings[a])) then begin
            found:=true;
            break;
        end;
      end;
    end;


    if found = true then begin
      ShowMessage('File '+ExtractFileName(dlgaddresource.Files.Strings[a])+' is already in list and will be skipped!');
    end else begin

      // se è un .fba, dobbiamo aggiungerlo ai files degli script, dopo averlo caricato
      // nel buffer

      if is_a_script then begin
        // è uno script
        // crea nodo treeview
        scriptData := TScriptNode.Create;
        scriptData.firstVisibleLine:=1;
        scriptData.saved := false;
        scriptData.name := ExtractFileName(dlgaddresource.Files.Strings[a]);
        scriptData.buffer := TStringList.Create;
        scriptData.breakpoints_text := TStringList.Create;
        scriptData.breakpoints_line := TStringList.Create;

        scriptData.undolist := TSynEditUndoList.Create;
        scriptData.redolist := TSynEditUndoList.Create;

        scriptData.buffer.LoadFromFile(dlgaddresource.Files.Strings[a]);

        // ok, possiamo aggiungere il file in lista
        node := projfiles.Items.AddNode(nil, scriptNode,
          '* '+scriptData.name, scriptData, naAddChild);
        // il primo main.fba - bugged - scriptNode.AlphaSort(false);
        node.ImageIndex := GetIconIndexFromFileName(scriptData.name);
        node.SelectedIndex := GetIconIndexFromFileName(scriptData.name);

        // aggiorna label scripts treeview
        scriptNode.Text:='Scripts ('+IntToStr(scriptNode.Count)+')';

      end else begin
        // è una risorsa
        // crea data nodo treeview
        resourceData := TResourceNode.Create;
        resourceData.name := ExtractFileName(dlgaddresource.Files.Strings[a]);
        resourceData.fullpath := dlgaddresource.Files.Strings[a];
        resourceData.copied := false;

        // ok, possiamo aggiungere il file in lista
        node := projfiles.Items.AddNode(nil, resourceNode,
          resourceData.name, resourceData, naAddChild);
        resourceNode.AlphaSort(false);
        node.ImageIndex := GetIconIndexFromFileName(resourceData.name);
        node.SelectedIndex := GetIconIndexFromFileName(resourceData.name);

        // aggiorna label scripts treeview
        resourceNode.Text:='Resources ('+IntToStr(resourceNode.Count)+')';

       end;

    end;

  end;

  // espandi nodo risorse
  resourceNode.Expand(true);
  // espandi nodo scripts
  scriptNode.Expand(true);
  // seleziona il primo script se non avevamo niente selezionato nel buffer
  if (scriptNode.Count > 0) and (current_buffer = -1) then begin
    SelectScript(0);
    scriptNode.Item[0].Selected:=true;
  end;
end;

function TMainForm.LoadProject(projname: String): Boolean;
var
ini: TIniFile;
a: Integer;
wantfile, resdir: String;
nodeData: TScriptNode;
resourceData: TResourceNode;
ExtResourceData: TExtResourceNode;
node: TTreeNode;
begin
  // apre il file di progetto
  ini := TIniFile.Create(projname);

  // Verifica se c'è una chiave valida, ossia se è un file di proj valido
  if ini.ValueExists('general', 'numscripts') = false then begin
    ini.Free;
    ShowMessage(THIS_IS_NOT_A_FBP_PROJECT);
    Result := False;
    exit;
  end;

  // load progetto: flagga risorse, exe e risorse esterne come non cachate
  // cosi' ci assicuriamo di ricreare tutto quando serve
  COPY_INTERNAL_RESOURCES_CACHED := false;
  COPY_FBA_PLAYER_CACHED := false;
  COPY_EXTERNAL_RESOURCES_CACHED := False;

  // aggiunge progetto alla lista dei files aperti di recente
  AddOpenedProjectsList(projname);
  
  // cancella tutti i buffers
  MainForm.FreeTreeView(true);

  // directory risorse script
  //resdir := FExtractFilePath(projname)+ExtractFileName(projname)+'.res\';
  resdir := FExtractFilePath(projname)+ini.ReadString('general', 'resdir', '')+'.res\';

  // cicla per il numero di scripts
  for a:=0 to ini.ReadInteger('general', 'numscripts', 0)-1 do begin
    wantfile := ini.ReadString('scripts', IntToStr(a), '');
    // se il file esiste, aggiungilo nel treeview altrimenti avverti l'utente
    if FileExists(resdir + wantfile) then begin
      // aggiunge a treeview
      nodeData := TScriptNode.Create;
      nodeData.firstVisibleLine:=1;
      nodeData.buffer := TStringList.Create;
      nodeData.breakpoints_text := TStringList.Create;
      nodeData.breakpoints_line := TStringList.Create;

      nodeData.undolist := TSynEditUndoList.Create;
      nodeData.redolist := TSynEditUndoList.Create;

      nodeData.saved := true;
      nodeData.name := wantfile;

      node := projfiles.Items.AddNode(nil, scriptNode, wantfile, nodeData, naAddChild	);
      // il primo main.fba - bugged - scriptNode.AlphaSort(false);

      node.ImageIndex := GetIconIndexFromFileName(wantfile);
      node.SelectedIndex := GetIconIndexFromFileName(wantfile);
      // aggiorna label scripts treeview
      scriptNode.Text:='Scripts ('+IntToStr(scriptNode.Count)+')';
      // leggilo nel buffer
      nodeData := scriptNode.Item[scriptNode.Count-1].Data;
      nodeData.buffer.Clear;
      nodeData.breakpoints_text.Clear;
      nodeData.breakpoints_line.Clear;

      nodeData.buffer.LoadFromFile(resdir + wantfile);
    end else begin
      ShowMessage('Unable to find script file, it will be skipped: ' +wantfile);
    end;
  end;

  // cicla per il numero di risorse
  for a:=0 to ini.ReadInteger('general', 'numresources', 0)-1 do begin
    wantfile := ini.ReadString('resources', IntToStr(a), '');
    // se il file esiste, aggiungilo nel treeview altrimenti avverti l'utente
    if FileExists(resdir + wantfile) then begin

      resourceData := TResourceNode.Create;
      resourceData.name := wantfile;
      resourceData.fullpath := resdir + wantfile;
      resourceData.copied := true;

      // aggiunge a treeview
      node := projfiles.Items.AddNode(nil, resourceNode, wantfile, resourceData, naAddChild	);
      resourceNode.AlphaSort(false);
      node.ImageIndex := GetIconIndexFromFileName(wantfile);
      node.SelectedIndex := GetIconIndexFromFileName(wantfile);

      // aggiorna label resources treeview
      resourceNode.Text:='Resources ('+IntToStr(resourceNode.Count)+')';

    end else begin
      ShowMessage('Unable to find resource file, it will be skipped: ' +wantfile);
    end;
  end;


  // cicla per il numero di risorse esterne
  for a:=0 to ini.ReadInteger('general', 'numextresources', 0)-1 do begin
    wantfile := ini.ReadString('extresources', IntToStr(a), '');
    // se il file esiste, aggiungilo nel treeview altrimenti avverti l'utente
    if FileExists(resdir + wantfile) then begin

      ExtResourceData := TExtResourceNode.Create;
      ExtResourceData.name := wantfile;
      ExtResourceData.fullpath := resdir + wantfile;
      ExtResourceData.copied := true;

      // aggiunge a treeview
      node := projfiles.Items.AddNode(nil, ExtResourceNode, wantfile, ExtResourceData, naAddChild	);
      extResourceNode.AlphaSort(false);
      node.ImageIndex := GetIconIndexFromFileName(wantfile);
      node.SelectedIndex := GetIconIndexFromFileName(wantfile);

      // aggiorna label resources treeview
      ExtResourceNode.Text:='Ext.Resources ('+IntToStr(ExtResourceNode.Count)+')';

    end else begin
      ShowMessage('Unable to find external resource file, it will be skipped: ' +wantfile);
    end;
  end;

  // carica settings progetto
  for a:=1 to SETTINGS_MAX_SETTINGS do begin
    SETTINGS[a]:=ini.ReadString('PROJECT_SETTINGS', IntToStr(a), '0');
  end;

  // il progetto è salvato
  //project_saved:=projname;
  project_saved:=FExtractFilePath(projname) + ini.ReadString('general', 'resdir', '');
  UpdateProjectTitle();

  ini.Free;

  // espandi i treeview
  scriptNode.Expand(true);
  resourceNode.Expand(true);
  extResourceNode.Expand(true);
  
  // seleziona il primo script, se presente
  if scriptNode.Count > 0 then begin
    MainForm.SelectScript(0);
  end else begin
    // se il primo script non c'è, non selezionare nulla
    current_buffer:=-1;
    statusbar.Panels[0].Text:='';
  end;

  Result := true;
end;

procedure TMainForm.btnsaveprojectClick(Sender: TObject);
begin
  SaveProject(false);
end;

procedure TMainForm.btnloadprojectClick(Sender: TObject);
begin
  // avverti se il progetto non è salvato
  if SomethingUnsaved() then begin
    if MessageDlg('Warning! This project is unsaved. Ignore changes and load another project?',
      mtConfirmation, [mbYes, mbNo], 0) = mrNo then exit;
  end;

  dlgopenproject.FileName:='*.fbp';
  if dlgopenproject.Execute = false then exit;

  // carica il progetto
  LoadProject(dlgopenproject.FileName);
end;


// costruisce il file ini per lanciare il loader
procedure TMainForm.BuildLaunchIniFile(filename: String; stepbystep: Boolean; available_connection_port: Integer);
var
ini: TIniFile;
scriptData: TScriptNode;
a: Integer;
ignoreIncludeFiles: String;
begin
  ini := TIniFile.Create(filename);
  //
  ini.WriteInteger('startup', 'orientation', StrToInt(SETTINGS[SETTINGS_ORIENTATION]));

  ini.WriteInteger('startup', 'vga', StrToInt(SETTINGS[SETTINGS_VGA]));
  ini.WriteInteger('startup', 'windowswidth', StrToInt(SETTINGS[SETTINGS_WINDOWS_WIDTH]));
  ini.WriteInteger('startup', 'windowsheight', StrToInt(SETTINGS[SETTINGS_WINDOWS_HEIGHT]));

  ini.WriteInteger('startup', 'fps', StrToInt(SETTINGS[SETTINGS_FPS]));
  if stepbystep then begin
    ini.WriteInteger('startup', 'start-step-by-step', 1);
  end else begin
    ini.WriteInteger('startup', 'start-step-by-step', 0);
  end;
  ini.WriteString('startup', 'debugport', IntToStr(available_connection_port));
  ini.WriteInteger('startup', 'socket_timeout', 600);

  {$IFDEF DEBUG_VERBOSE_SOCKET_DEBUG}
  ini.WriteInteger('startup', 'debug', 1);
  {$ELSE}
  ini.DeleteKey('startup', 'debug');
  {$ENDIF}
  //
  ini.WriteInteger('sound', 'frequency', StrToInt(SETTINGS[SETTINGS_SOUNDFREQ]));
  ini.WriteInteger('sound', 'bps', StrToInt(SETTINGS[SETTINGS_SOUNDBPS]));
  ini.WriteInteger('sound', 'stereo', StrToInt(SETTINGS[SETTINGS_SOUNDSTEREO]));
  ini.WriteInteger('sound', 'maxmodchannels', StrToInt(SETTINGS[SETTINGS_SOUNDMAXMUSCHANNELS]));
  ini.WriteInteger('sound', 'maxsfxchannels', StrToInt(SETTINGS[SETTINGS_SOUNDMAXSFXCHANNELS]));

  ini.WriteInteger('extensions', 'ttf', StrToInt(SETTINGS[SETTINGS_EXT_SOUNDS]));
  ini.WriteInteger('extensions', 'sounds', StrToInt(SETTINGS[SETTINGS_EXT_TTF]));

  ini.WriteInteger('startup', 'bgcolor_1', StrToInt(SETTINGS[SETTINGS_START_BACKCOL1]));
  ini.WriteInteger('startup', 'bgcolor_2', StrToInt(SETTINGS[SETTINGS_START_BACKCOL2]));
  ini.WriteInteger('startup', 'bgcolor_3', StrToInt(SETTINGS[SETTINGS_START_BACKCOL3]));

  ini.WriteInteger('startup', 'trans_1', StrToInt(SETTINGS[SETTINGS_TRANS1]));
  ini.WriteInteger('startup', 'trans_2', StrToInt(SETTINGS[SETTINGS_TRANS2]));
  ini.WriteInteger('startup', 'trans_3', StrToInt(SETTINGS[SETTINGS_TRANS3]));

  //
  // patch lzab, crea una lista dei files .fba che sono lunghi 0 bytes
  // in questo modo non verranno inclusi nel file lzab ma potremo ignorare gli errori
  // di inclusione per file non trovati, al momento di includerli
  //
  ignoreIncludeFiles := '';
  for a:=0 to scriptNode.Count-1 do begin
    scriptData := scriptNode.Item[a].Data;
    if Length(scriptData.buffer.Text) = 0 then ignoreIncludeFiles := ignoreIncludeFiles + '||'+UpperCase(scriptData.name) + '||';
  end;
  ini.WriteString('startup', 'ignore_include_void', ignoreIncludeFiles);

  // player top most o no?
  ini.WriteBool('startup', 'windowtopmost', OPTIONS_BOOL[IDE_OPTIONS_LAUNCER_TOPMOST]);

  ini.Free;
end;

procedure TMainForm.RunProject(stepmode: Boolean);
var
//retcodeCard: DWORD;
retcode: Integer;
rundir: String;
scriptRes: TScriptNode;
available_connection_port: Integer;
waitBool: Boolean;
begin
  // sto aspettando il player dal precedente run
  if _waiting_For_Player_Launching then exit;

  // sincronizza il buffer corrente con l'editor
  if current_buffer > -1 then begin
    scriptRes := scriptNode.Item[current_buffer].Data;
    scriptRes.buffer.Text := synedit.Text;
  end;

  // check sintassi
  if CheckSyntax = false then begin
    exit;
  end;

  // se non puoi salvare il progetto, esci
  // if SaveProject() = false then exit;
  // nota: il salvataggio non serve, esportiamo script e risorse nella directory finale


  // se il progetto è salvato usa la sua dir, altrimenti una sottocartella dell'editor fba
  if Length(project_saved) > 0 then begin
    rundir := project_saved+'.res\fba_win32\';
  end else begin
    rundir := FExtractFilePath(Application.ExeName)+'fba_win32\';
  end;

  // se non esiste, crea la dir di esportazione
  if DirectoryExists(rundir) = false then begin
    MkDir(rundir);
  end;

  // crea i file .dat li dentro
  retcode := BuildDatFromResourcesList(rundir+'fba.dat', rundir+'fba.dats');
  if retcode <> 3 then begin
    ShowMessage('Error #'+IntToStr(retcode)+'. Unable to create resource .dat file: ' +rundir+'fba.dat');
    exit;
  end;

  // copia le risorse esterne all'interno della directory destinazione
  retcode := CopyExtResourcesToDir(rundir, 0);
  if retcode <> 1 then begin
    ShowMessage('Error #'+IntToStr(retcode)+'. Unable to copy external resources file');
    exit;
  end;

  // cerca una porta tcp libera
  available_connection_port := MainForm.GetAvailableTCPPort();
  if available_connection_port = -1 then begin
    ShowMessage(Unable_To_Find_A_Free_Connection_Port);
    exit;
  end;

  // copia il loader li dentro (Dalle risorse o da una dir di debug)
  {$IFDEF DEBUG_COPY_LAUNCHER_FROM_DIR}
    CopyFile(PChar(DEBUG_VC_X86RELEASE_LOADER_DIR+LauncherVCBuildName(0,'')), PChar(rundir+'fba.exe'), false);
  {$ELSE}
    // copia player se non è già flaggato come cachato
    CopyRes(LauncherResName(0, ''), rundir+'fba.exe', true);
  {$ENDIF}

  // crea .ini con i valori di default per l'avvio del loader
  BuildLaunchIniFile(rundir+'fba.ini', stepmode, available_connection_port);

  // avvia il loader
  //ShellExecute(0, 'open', PChar(rundir+'fba.exe'),nil,PChar(rundir),SW_SHOWNORMAL) ;
  //WinExec(PChar(rundir+'fba.exe'), SW_NORMAL);
  //_Sto_ShellExecute(rundir+'fba.exe','',retcodeCard, 10000, false);
  lblWaitappLaunch.Visible := True;
  _waiting_For_Player_Launching:=true;

  waitBool := LaunchWithTempFileAndWaitKill(pcinfo.Folders.Temp, rundir+'fba.exe', 10000);

  lblWaitappLaunch.Visible := False;
  _waiting_For_Player_Launching := false;

  if waitBool  = false then begin
    ShowMessage(Unable_To_Launch_Windows_Player);
    exit;
  end;

  // connetti alla porta
  socket.Port := IntToStr(available_connection_port);
  socket.Addr := 'localhost';
  socket.Connect;

  // apri metti modalità run
  SetGuiMode(RUNNING);

  // segna risorse, player e risorse esterne come già cachate
  // cosi' al prossimo run senza modifiche non le ricreiamo
  // tuttavia se nel conf abbiamo disabilitato il caching, non settarli mai a true
  if OPTIONS_BOOL[IDE_OPTIONS_CACHE_BUILD] then begin
    COPY_INTERNAL_RESOURCES_CACHED := true;
    COPY_FBA_PLAYER_CACHED := true;
    COPY_EXTERNAL_RESOURCES_CACHED := true;
  end;
end;

procedure TMainForm.btnrunClick(Sender: TObject);
begin
  RunProject(false);
end;

procedure TMainForm.syneditKeyPress(Sender: TObject; var Key: Char);
begin
  statusbar.Panels[1].Text := 'Col: '+IntToStr(synedit.CaretX)+' Row: '+IntToStr(synedit.CaretY);
end;

procedure TMainForm.syneditKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  statusbar.Panels[1].Text := 'Col: '+IntToStr(synedit.CaretX)+' Row: '+IntToStr(synedit.CaretY);
end;

procedure TMainForm.syneditScroll(Sender: TObject;
  ScrollBar: TScrollBarKind);
begin
  statusbar.Panels[1].Text := 'Col: '+IntToStr(synedit.CaretX)+' Row: '+IntToStr(synedit.CaretY);
end;

procedure TMainForm.syneditMouseCursor(Sender: TObject;
  const aLineCharPos: TBufferCoord; var aCursor: TCursor);
begin
  statusbar.Panels[1].Text := 'Col: '+IntToStr(synedit.CaretX)+' Row: '+IntToStr(synedit.CaretY);
end;

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin

  // bugfix, chiudi la finestra di help se era aperta
  if FrmHelp <> nil then begin
    FrmHelp.Close;
  end;

  // se c'è qualcosa da salvare, chiedi se vuoi uscire senza salvare
  if SomethingUnsaved() then begin
    if MessageDlg('Warning! This project is unsaved. Quit without saving it?',
      mtConfirmation, [mbYes, mbNo], 0) = mrNo then CanClose := false;
  end;

end;

procedure TMainForm.editpopupPopup(Sender: TObject);
begin
  mnucut.Enabled := not synedit.ReadOnly;
  mnucopy.Enabled := not synedit.ReadOnly;
  mnureplace.Enabled := not synedit.ReadOnly;
  //mnufind.Enabled := not synedit.ReadOnly;
  //mnufindnext.Enabled := not synedit.ReadOnly;
  mnupaste.Enabled := not synedit.ReadOnly;
  mnuselectall.Enabled := not synedit.ReadOnly;
  // se l'editor è readonly, abilita solo copy, find select all
  if synedit.ReadOnly then begin
    mnucopy.Enabled:=true;
    //mnufind.Enabled:=true;
    //mnufindnext.Enabled:=true;
    mnuselectall.Enabled:=true;
    // menu replace
    // locka undo e redo
    mnuundo.Enabled := false;
    mnuredo.Enabled := false;
  end else begin
    // se l'editor non è readonly..
    // se c'è qualcosa selezionato o meno, nascondi cut&copy
    mnucopy.Enabled := (synedit.SelLength > 0);
    mnucut.Enabled := (synedit.SelLength > 0);
    // se possiamo undo-are e redo-are
    mnuundo.Enabled := synedit.UndoList.CanUndo;
    mnuredo.Enabled := synedit.RedoList.CanUndo;
  end;

end;

procedure TMainForm.mnucopyClick(Sender: TObject);
begin
  // copia negli appunti la selezione (eeeeasy)
  synedit.CopyToClipboard;
end;

procedure TMainForm.mnupasteClick(Sender: TObject);
begin
  // paste
  synedit.PasteFromClipboard;
end;

procedure TMainForm.mnucutClick(Sender: TObject);
begin
  // cut dagli appunti
  synedit.CutToClipboard;
end;

procedure TMainForm.mnuselectallClick(Sender: TObject);
begin
  // seleziona tutto
  synedit.SelectAll;
end;

procedure TMainForm.mnufindClick(Sender: TObject);
begin
  ShowSearchReplaceDialog(FALSE);
end;

procedure TMainForm.mnureplaceClick(Sender: TObject);
begin
  ShowSearchReplaceDialog(TRUE);
end;

procedure TMainForm.mnufindnextClick(Sender: TObject);
begin
  DoSearchReplaceText(FALSE, FALSE);
end;

procedure TMainForm.syneditReplaceText(Sender: TObject; const ASearch,
  AReplace: String; Line, Column: Integer; var Action: TSynReplaceAction);
var
  APos: TPoint;
  EditRect: TRect;
begin
  if ASearch = AReplace then
    Action := raSkip
  else begin
    APos := synedit.ClientToScreen(
      synedit.RowColumnToPixels(
      synedit.BufferToDisplayPos(
        BufferCoord(Column, Line) ) ) );
    EditRect := ClientRect;
    EditRect.TopLeft := ClientToScreen(EditRect.TopLeft);
    EditRect.BottomRight := ClientToScreen(EditRect.BottomRight);

    if ConfirmReplaceDialog = nil then
      ConfirmReplaceDialog := TConfirmReplaceDialog.Create(Application);
    ConfirmReplaceDialog.PrepareShow(EditRect, APos.X, APos.Y,
      APos.Y + synedit.LineHeight, ASearch);
    case ConfirmReplaceDialog.ShowModal of
      mrYes: Action := raReplace;
      mrYesToAll: Action := raReplaceAll;
      mrNo: Action := raSkip;
      else Action := raCancel;
    end;
  end;
end;

procedure TMainForm.mainmnucutClick(Sender: TObject);
begin
  mnucutClick(Sender);
end;

procedure TMainForm.mainmnucopyClick(Sender: TObject);
begin
  mnucopyClick(Sender);
end;

procedure TMainForm.mainmnupasteClick(Sender: TObject);
begin
  mnupasteClick(Sender);
end;

procedure TMainForm.mainmnuselectallClick(Sender: TObject);
begin
  mnuselectallClick(Sender);
end;

procedure TMainForm.mainmnufindClick(Sender: TObject);
begin
  mnufindClick(Sender);
end;

procedure TMainForm.mainmnufindnextClick(Sender: TObject);
begin
  mnufindnextClick(Sender);
end;

procedure TMainForm.mainmnureplaceClick(Sender: TObject);
begin
  mnureplaceClick(Sender);
end;

procedure TMainForm.mainmnuEditClick(Sender: TObject);
begin
  mainmnucut.Enabled := not synedit.ReadOnly;
  mainmnucopy.Enabled := not synedit.ReadOnly;
  mainmnureplace.Enabled := not synedit.ReadOnly;
  //mainmnufind.Enabled := not synedit.ReadOnly;
  //mainmnufindnext.Enabled := not synedit.ReadOnly;
  mainmnupaste.Enabled := not synedit.ReadOnly;
  mainmnuselectall.Enabled := not synedit.ReadOnly;
  // se l'editor è readonly, abilita solo copy, find select all
  if synedit.ReadOnly then begin
    mainmnucopy.Enabled:=true;
    //mainmnufind.Enabled:=true;
    //mainmnufindnext.Enabled:=true;
    mainmnuselectall.Enabled:=true;
    // menu replace
    // locka undo e redo
    mainmnuundo.Enabled := false;
    mainmnuredo.Enabled := false;
  end else begin
    // se l'editor non è readonly..
    // se c'è qualcosa selezionato o meno, nascondi cut&copy
    mainmnucopy.Enabled := (synedit.SelLength > 0);
    mainmnucut.Enabled := (synedit.SelLength > 0);
    // se possiamo undo-are e redo-are
    mainmnuundo.Enabled := synedit.UndoList.CanUndo;
    mainmnuredo.Enabled := synedit.RedoList.CanUndo;
  end;
end;

procedure TMainForm.createprocessTerminate(Sender: TObject;
  ExitCode: Cardinal);
begin
  creatingDatFileCode := ExitCode;
end;

procedure TMainForm.ClearConsole1Click(Sender: TObject);
begin
  outputdebugconsole.Lines.BeginUpdate;
  outputdebugconsole.Clear;
  outputdebugconsole.Lines.EndUpdate;
end;

procedure TMainForm.btndelfileClick(Sender: TObject);
var
a: Integer;
something_selected: Boolean;
resourceData: TResourceNode;
extResourceData: TExtResourceNode;
scriptData: TScriptNode;
resdir: String;
begin
  something_selected:=false;

  // controlla se abbiamo selezionato una risorsa
  for a:=0 to resourceNode.Count-1 do begin
    if resourceNode.Item[a].Selected=true then begin
      // selezionata una risorsa
      something_selected:=true;
      if MessageDlg(ConfirmRemoveResource, mtConfirmation	, [mbYes, mbNo], 0) = mrYes then begin

        resourceData := resourceNode.Item[a].Data;

        // flagga risorse non cachate cosi' ci assicuriamo di ricreare tutto quando serve
        COPY_INTERNAL_RESOURCES_CACHED := false;

        // se la risorsa era già stata copiata nella dir del progetto, eliminala anche da li
        if resourceData.copied then begin
          if FileExists(resourceData.fullpath) then begin
            // commentato - teniamo la risorsa sul filesystem..
            //DeleteFile(resourceData.fullpath);
          end;
        end;

        // rimuovi data treeview
        resourceData.Free;

        // rimuovi voce treeview
        resourceNode.Item[a].Delete;

        // aggiorna contatore treeview
        resourceNode.Text := 'Resources ('+IntToStr(resourceNode.Count)+')';
        break;
      end;
    end;
  end;

  // controlla se abbiamo selezionato una risorsa esterna
  for a:=0 to ExtresourceNode.Count-1 do begin
    if ExtresourceNode.Item[a].Selected=true then begin
      // selezionata una risorsa
      something_selected:=true;
      if MessageDlg(ConfirmRemoveResource, mtConfirmation	, [mbYes, mbNo], 0) = mrYes then begin

        ExtResourceData := ExtresourceNode.Item[a].Data;

        // flagga risorse non cachate cosi' ci assicuriamo di ricreare tutto quando serve
        COPY_EXTERNAL_RESOURCES_CACHED := false;

        // se la risorsa era già stata copiata nella dir del progetto, eliminala anche da li
        if ExtResourceData.copied then begin
          if FileExists(ExtResourceData.fullpath) then begin
            // commentato - teniamo la risorsa sul filesystem..
            //DeleteFile(resourceData.fullpath);
          end;
        end;

        // rimuovi data treeview
        ExtResourceData.Free;

        // rimuovi voce treeview
        ExtresourceNode.Item[a].Delete;

        // aggiorna contatore treeview
        ExtresourceNode.Text := 'Ext.Resources ('+IntToStr(ExtresourceNode.Count)+')';
        break;
      end;
    end;
  end;

  // controlla se abbiamo selezionato uno script
  for a:=0 to scriptNode.Count-1 do begin
    if scriptNode.Item[a].Selected=true then begin
      // selezionato uno script
      something_selected:=true;
      if MessageDlg(ConfirmRemoveScript, mtConfirmation	, [mbYes, mbNo], 0) = mrYes then begin
        scriptData := scriptNode.Item[a].Data;

        // se il progetto era già stato salvato, elimina il file .fba dalla directory del progetto
        if Length(project_saved) > 0 then begin

          resdir := FExtractFilePath(project_saved)+ExtractFileName(project_saved)+'.res\';

          if FileExists(resdir+scriptData.name) then begin
            // commentato - teniamo la risorsa sul filesystem..
            //DeleteFile(resdir+scriptData.name);
          end;
        end;

        // rimuovi data treeview
        scriptData.buffer.Free;
        scriptData.breakpoints_line.Free;
        scriptData.breakpoints_text.Free;
        scriptData.undolist.Free;
        scriptData.redolist.Free;
        scriptData.Free;

        // rimuovi voce treeview
        scriptNode.Item[a].Delete;

        // aggiorna contatore treeview
        scriptNode.Text := 'Scripts ('+IntToStr(scriptNode.Count)+')';


        // se era la voce selezionata, seleziona il primo scripts, se c'è..
        if a = current_buffer then begin
          current_buffer := -1;
          if scriptNode.Count > 0 then begin
            MainForm.SelectScript(0);
          end;
        end else begin
          current_buffer := -1;
        end;

        break;
      end;
    end;
  end;

  // non abbiamo selezionato nulla
  if something_selected = false then begin
    ShowMessage(PleaseSelectResourceOrScriptToRemove);
  end;
end;

procedure TMainForm.syneditGutterGetText(Sender: TObject; aLine: Integer;
  var aText: String);
var
numrows, a: Integer;
scriptData: TScriptNode;
begin
  // nessun buffer, in edit, esci
  if current_buffer = -1 then exit;
  // se la linea che stai ridisegnando è presente tra i
  // breakpoints e la linea è diversa da quella settata nel momento
  // in cui è settato il breakpoint, rimuovi il breakpoint!
  scriptData := scriptNode.Item[current_buffer].Data;
  for a:=0 to scriptData.breakpoints_line.Count-1 do begin
    if (
        (a < synedit.Lines.Count) and
        (StrToInt(scriptData.breakpoints_line.Strings[a]) = aLine) and
        (scriptData.breakpoints_text.Strings[a] <> synedit.Lines.Strings[aLine-1])
        ) then
    begin
        // linea diversa, rimuovi breakpoint
        MainForm.DelBreakpoint(current_buffer, a);
        break;
    end;
  end;
  // rimuovi tutti i breakpoints oltre il numero di righe dell'editor
  numrows := synedit.Lines.Count;
  for a:=scriptData.breakpoints_line.Count-1 downto 0 do begin
    if StrToInt(scriptData.breakpoints_line.Strings[a]) > numrows then begin
      MainForm.DelBreakpoint(current_buffer, a);
    end;
  end;
end;

procedure TMainForm.syneditGutterClick(Sender: TObject;
  Button: TMouseButton; X, Y, Line: Integer; Mark: TSynEditMark);
var
a: Integer;
scriptData: TScriptNode;
found: Integer;
begin
  // nessun testo nella linea
  if Length(synedit.Lines.Strings[Line-1]) = 0 then exit;

  // verifica se in quella linea c'è già un breakpoint
  found := -1;
  scriptData := scriptNode.Item[current_buffer].Data;
  for a:=0 to scriptData.breakpoints_line.Count-1 do begin
    if StrToInt(scriptData.breakpoints_line.Strings[a]) = Line then begin
      found := a;
      break;
    end;
  end;

  // aggiunge o rimuove il breakpoint
  if found > -1 then begin
    MainForm.DelBreakpoint(current_buffer, found);
  end else begin
    MainForm.AddBreakpoint(current_buffer, Line);
  end;

  // invalida gutter
  synedit.InvalidateGutterLine(Line);
  
end;

procedure TMainForm.btnStepClick(Sender: TObject);
begin
  SetGuiMode(RUNNING);
  
  // manda richiesta prossimo step
  if socket.State = wsConnected then begin
    // non c'è nessuna linea runnante
    // invalida ultima linea, se presente
    if lastLoaderLine > -1 then synedit.InvalidateGutterLine(lastLoaderLine);
    lastLoaderLine:=-1;
    MainForm.SockSend('next');
  end;

end;

procedure TMainForm.socketSessionConnected(Sender: TObject; Error: Word);


begin

    // mostra pannello debug
    dbgpanel.Show;
    dbgsplitter.Show;
    outputdebugconsole.Visible := false;
    outputdebugconsole.Lines.BeginUpdate;
    outputdebugconsole.Lines.Clear;
    outputdebugconsole.Lines.Add('>> Output Debug Console');
    outputdebugconsole.Lines.EndUpdate;
    outputdebugconsole.Visible := true;

end;

procedure TMainForm.socketSessionClosed(Sender: TObject; Error: Word);
begin
  // rimettiti in modalità edit
  SetGuiMode(EDITING);
  vardump.ITems.BeginUpdate;
  vardump.Items.Clear;
  vardump.Items.EndUpdate;
  //tcp.Active:=false;
  // invalida gutter per repaint
  if lastLoaderLine > -1 then synedit.InvalidateGutterLine(lastLoaderLine);
  lastLoaderLine:=-1;
end;

procedure TMainForm.socketSocksError(Sender: TObject; Error: Integer;
  Msg: String);
begin
  socketSessionClosed(Sender, Error);
end;

procedure TMainForm.socketDataAvailable(Sender: TObject; Error: Word);
var
  line, cmd, script: String;
  a, b, idbuffer, numlinea: Integer;
  token: TStrings;
  item: TListITem;
  scriptData: TScriptNode;
begin
  // leggi la linea dal socket
  line := socket.ReceiveStr;
  if Pos(#$A, line) > 0 then line := Copy(line, 0, Pos(#$A, line)-1);
  if Length(line) = 0 then exit;

  // stampa linea di debug se richiesto
  {$IFDEF DEBUG_OUTPUT_DEBUGCONSOLE_LOG_TRAFFIC}
    outputdebugconsole.Lines.Add('<< '+line);
  {$ENDIF}


  // estrai tokens
  token := SplitStrings(line, '||', nil);

  // processa comando
  // estrai comando
  cmd := Copy(line,1,3);

    // ricevuta riga debug
  if cmd ='DBG' then begin
    outputdebugconsole.Lines.Add('DEBUG: '+Copy(line,6,Length(line)-5));

  end else if cmd = 'ERR' then begin

    outputdebugconsole.Lines.Add('ERROR: '+Copy(line,6,Length(line)-5));

  end else if cmd = 'INF' then begin

    outputdebugconsole.Lines.Add('INFO: '+Copy(line,6,Length(line)-5));

  end else if cmd = '001' then begin

    // ricevuto benvenuto dal loader
    // mandiamo l'intera lista di breakpoints all'applicazione
    for a:=0 to scriptNode.Count-1 do begin
      scriptData := scriptNode.Item[a].Data;
      for b:=0 to scriptData.breakpoints_line.Count-1 do begin
        SockSend('break '+LowerCase(scriptData.name)+' '+scriptData.breakpoints_line.Strings[b]);
      end;
    end;

    // e dopo manda l'ok per il load del mainscript
    SockSend('canstart');

  end else if cmd ='701' then begin
      // inizio dump variabili
      vardump.Items.BeginUpdate;

      // backuppa nome e tipo della variabile opzionalmente selezionata
      lastSelectedStackType := '';
      lastSelectedStackName := '';
      for a:=0 to vardump.Items.Count-1 do begin
        if vardump.Items[a].Selected then begin
          lastSelectedStackType := vardump.Items[a].Caption;
          lastSelectedStackName := vardump.Items[a].SubItems.Strings[0];
        end;
      end;

      vardump.Clear;
      synedit.SetFocus;

  end else if cmd ='702' then begin
        // ricevuta variabile
        if (UpperCase(token.Strings[2]) <> '(*TEMPORARY)') and (UpperCase(token.Strings[2]) <> '_VERSION') then begin
          item := vardump.Items.Add;
          //if UpperCase(token.Strings[2]) = '(*TEMPORARY)' then begin
          //  item.Caption := '(*temporary)';
          //end else begin
          //  item.Caption := token.Strings[2];
          //end;
          if token.Strings[1] = 'G' then begin
            // globale
            //item.ImageIndex := 0;
            item.Caption := 'G';
          end else if token.Strings[1] = 'U' then begin
            // UPVALUE
            //item.ImageIndex := 1;
            item.Caption := 'U';
          end else begin
            // LOCALE
            //item.ImageIndex := 2;
            item.Caption := 'L';
          end;

          item.SubItems.Add(token.Strings[2]);
          item.SubItems.Add(token.Strings[3]);
          //item.Caption := token.Strings[2];
          //item.SubItems.Add(token.Strings[3]);
        end;

        synedit.SetFocus;
  end else if cmd ='705' then begin

      // restora ultima variabile selezionata prima del reload, se presente
      if ( (Length(lastSelectedStackName) > 0) and (Length(lastSelectedStackType) > 0) ) then begin
        for a:=0 to vardump.Items.Count-1 do begin
          if ( (UpperCase(lastSelectedStackType) = UpperCase(vardump.Items[a].Caption)) and (UpperCase(lastSelectedStackName) = UpperCase(vardump.Items[a].SubItems.Strings[0])) ) then begin
            vardump.Items[a].Selected:=true;
            break;
          end;
        end;
      end;

      // fine dump variabili
      vardump.Items.EndUpdate;

      //synedit.SetFocus;
      txtImmediate.SetFocus;
      
  end else if cmd ='310' then begin
      // ricevuta riga 310, mostra posizione corrente programma
      // invalida ultima riga runnante, se presente
      if lastLoaderLine > -1 then synedit.InvalidateGutterLine(lastLoaderLine);
      lastLoaderLine:=-1;
      // mostra pulsante prossimo step e continue
      SetGuiMode(PAUSE);
      // estrai nome script e numero linea
      script:=token.Strings[1];
      // trova buffer di questo script
      idbuffer := MainForm.FindBufferID(script);
      if idbuffer > -1 then begin
        // seleziona quel buffer (se non già selezionato) e quella linea
        if idbuffer <> current_buffer then MainForm.SelectScript(idbuffer);
        numlinea:=StrToInt(token.Strings[2]);
        lastLoaderLine:=numlinea;
        // invalida linee gutter
        synedit.InvalidateGutterLine(lastLoaderLine);
        synedit.GotoLineAndCenter(numlinea);
      end;
      // aggiungi a pannello di debug: info sullo stop
      MainForm.outputdebugconsole.Lines.Add('PAUSE: File: '+script+' - Line: '+token.Strings[2]);
      // richiedi dump della memoria
      MainForm.SockSend('dumpmem');
      MainForm.BringToFront;
      synedit.SetFocus;

  end else if cmd ='658' then begin

      // variabile non trovata (dopo aver provato a cambiare
      // il valore di una var nello stack)
      ShowMessage(Socket658VarNotFound);

  end else if cmd ='675' then begin

      // ricevuto su socket un set<qualcosa> (number,boolean,string)
      // ma senza i parametri giusti
      ShowMessage(Socket675WrongParamNumber);

  end else if cmd ='659' then begin

      // variabile settata in seguito ad un set<qualcosa> (number,boolean,string)
      // richiedi dump della memoria
      MainForm.SockSend('dumpmem');

  end else if cmd ='496' then begin

      // risultato positivo dopo l'esecuzione di una riga in modalità immediata, richiedi lo stato
      // dello stack
      // aggiungi ok alla finestra
      outputdebugconsole.Lines.Add('I-MODE LINE EXECUTED');
      MainForm.SockSend('dumpmem');

  end else if cmd ='497' then begin

      // risultato negativo dopo l'esecuzione di una riga in modalità immediata
      // aggiungi errore nella finestra
      outputdebugconsole.Lines.Add('I-MODE ERROR: '+Copy(line,6,Length(line)-5));
      // e richiedi lo stato dello stack
      MainForm.SockSend('dumpmem');

  end;

  token.Free;


end;

procedure TMainForm.mnustepClick(Sender: TObject);
begin
  if btnStep.Enabled then btnStepClick(Sender);
end;

procedure TMainForm.btnHaltClick(Sender: TObject);
begin
  // non c'è nessuna linea runnante
  lastLoaderLine:=-1;
  synedit.InvalidateGutter;
  // chiudi socket
  SockSend('quit');
  socket.Close;
end;

procedure TMainForm.syneditGutterPaint(Sender: TObject; aLine, X,
  Y: Integer);
var
scriptData: TScriptNode;
a: Integer;
found: Boolean;
begin

  // controlla se c'è un breakpoint li
  found:=false;
  if current_buffer > -1 then begin
    scriptData := scriptNode.Item[current_buffer].Data;
    for a:=0 to scriptData.breakpoints_line.Count-1 do begin
      if aLine = StrToInt(scriptData.breakpoints_line.Strings[a]) then begin
        found:=true;
        break;
      end;
    end;
  end;
  // disegna breakpoint
  if found then begin
    imglGutterGlyphs.Draw(synedit.Canvas, X, Y, 4);
  end;

  // disegna freccia o meno se è la linea runnante e/o c'è il breakpoint
  if aLine = lastLoaderLine then begin
  imglGutterGlyphs.Draw(synedit.Canvas, X+10, Y, 1);
  end;



end;

procedure TMainForm.btnContinueClick(Sender: TObject);
begin

  SetGuiMode(RUNNING);

  // manda richiesta continua run
  if socket.State = wsConnected then begin
    // non c'è nessuna linea runnante
    lastLoaderLine:=-1;
    synedit.InvalidateGutter;
    MainForm.SockSend('run');
  end;

end;

procedure TMainForm.mnuRunClick(Sender: TObject);
begin
  // shortcut F5 - se il socket è connesso premi "Continue o pause"
  // altrimenti premi run
  if socket.State = wsConnected then begin
    if btnContinue.Enabled then begin
      btnContinueClick(Sender);
    end else if btnPause.Enabled then begin
      btnPauseClick(Sender);
    end;
  end else begin
    if btnRun.Enabled then btnRunClick(Sender);
  end;
end;

procedure TMainForm.btnPauseClick(Sender: TObject);
begin
  SetGuiMode(PAUSE);

  // manda richiesta pausa
  if socket.State = wsConnected then begin
    MainForm.SockSend('pause');
  end;
end;

procedure TMainForm.Stop1Click(Sender: TObject);
begin
  if btnHalt.Enabled then btnHaltClick(Sender);
end;

procedure TMainForm.Exit1Click(Sender: TObject);
begin
  MainForm.Close;
end;

procedure TMainForm.mnuMainRunClick(Sender: TObject);
begin
  mnuRunClick(Sender);
end;

procedure TMainForm.mnuMainStepClick(Sender: TObject);
begin
  mnustepClick(Sender);
end;

procedure TMainForm.mnuMainStopClick(Sender: TObject);
begin
  Stop1Click(Sender);
end;

procedure TMainForm.mnuMainOpenProjectClick(Sender: TObject);
begin
  btnloadprojectClick(Sender);
end;

procedure TMainForm.mnuMainNewProjectClick(Sender: TObject);
begin
  btnNewProjectClick(Sender);
end;

procedure TMainForm.mnuMainSaveProjectClick(Sender: TObject);
begin
  btnsaveprojectClick(Sender);
end;

procedure TMainForm.mnuMainNewScriptClick(Sender: TObject);
begin
  btnnewsourceClick(Sender);
end;

procedure TMainForm.mnuMainAddFileClick(Sender: TObject);
begin
  btnaddresourceClick(Sender);
end;

procedure TMainForm.mnuMainDeleteFileClick(Sender: TObject);
begin
  btndelfileClick(Sender);
end;

procedure TMainForm.btnNewProjectClick(Sender: TObject);
begin

  // avverti se il progetto non è salvato
  if SomethingUnsaved() then begin
    if MessageDlg('Warning! This project is unsaved. Ignore changes and create a new project?',
      mtConfirmation, [mbYes, mbNo], 0) = mrNo then exit;
  end;

  // Carica valori settings project default
  SetDefaultProjectSettings();

  // cancella tutti i buffers
  MainForm.FreeTreeView(true);

  // new progetto: flagga risorse, exe e risorse esterne come non cachate
  // cosi' ci assicuriamo di ricreare tutto quando serve
  COPY_INTERNAL_RESOURCES_CACHED := false;
  COPY_FBA_PLAYER_CACHED := false;
  COPY_EXTERNAL_RESOURCES_CACHED := False;

  // svuota synedit e segna che non editi nulla
  synedit.Text := '';
  statusbar.Panels[0].Text:='';
  current_buffer:=-1;
  project_saved:='';
  UpdateProjectTitle();
end;

procedure TMainForm.projfilesDblClick(Sender: TObject);
var
a: Integer;
resourceData: TResourceNode;
extResourceData: TExtResourceNode;
begin
  // apri la risorsa, se selezionata
  for a:=0 to resourceNode.Count-1 do begin
    if resourceNode.Item[a].Selected=true then begin
      resourceData := resourceNode.Item[a].Data;
      ShellExecute(0, 'open', PChar(resourceData.fullpath), '', '', 1);
      exit;
    end;
  end;
  // apri la risorsa externa, se selezionata
  for a:=0 to ExtResourceNode.Count-1 do begin
    if ExtResourceNode.Item[a].Selected=true then begin
      extResourceData := ExtResourceNode.Item[a].Data;
      ShellExecute(0, 'open', PChar(extResourceData.fullpath), '', '', 1);
      exit;
    end;
  end;
end;

procedure TMainForm.ExportProject(device: Integer);
var
  scriptRes: TScriptNode;
  retcode: Integer;
  selectedir: String;
begin
  // lanciata esportazione progetto flagga tutti i files
  // come ancora non cachati per assicurarci di ricreare tutto
  COPY_INTERNAL_RESOURCES_CACHED := false;
  COPY_FBA_PLAYER_CACHED := false;
  COPY_EXTERNAL_RESOURCES_CACHED := False;

  // sincronizza il buffer corrente con l'editor
  if current_buffer > -1 then begin
    scriptRes := scriptNode.Item[current_buffer].Data;
    scriptRes.buffer.Text := synedit.Text;
  end;

  // check sintassi
  if CheckSyntax = false then begin
    exit;
  end;

  // seleziona directory destinazione
  if MainForm.dlgSelectDestDir.Execute = false then exit;
  selectedir := FPath(dlgSelectDestDir.Directory);

  // se non esiste, crea la dir di esportazione
  if DirectoryExists(selectedir) = false then begin
    MkDir(selectedir);
  end;

  // crea i file .dat li dentro
  retcode := BuildDatFromResourcesList(selectedir+'fba.dat', selectedir+'fba.dat');
  if retcode <> 3 then begin
    ShowMessage('Error #'+IntToStr(retcode)+'. Unable to create resource .dat file: ' +selectedir+'fba.dat');
    exit;
  end;

  // copia le risorse esterne all'interno della directory destinazione
  retcode := CopyExtResourcesToDir(selectedir, device);
  if retcode <> 1 then begin
    ShowMessage('Error #'+IntToStr(retcode)+'. Unable to copy external resources file');
    exit;
  end;

  // copia il loader li dentro
  {$IFDEF DEBUG_COPY_LAUNCHER_FROM_DIR}
    if device=0 then begin
      CopyFile(PChar(DEBUG_VC_X86RELEASE_LOADER_DIR+LauncherVCBuildName(0, 'R')), PChar(selectedir+'fba.exe'), false);
    end else begin
      CopyFile(PChar(DEBUG_VC_ARMRELEASE_LOADER_DIR+LauncherVCBuildName(1,'')), PChar(selectedir+'fba.exe'), false);
    end;
  {$ELSE}
    if device=0 then begin
      CopyRes(LauncherResName(0, 'R'), selectedir+'fba.exe', true);
    end else begin
      CopyRes(LauncherResName(1, ''), selectedir+'fba.exe', true);
    end;
  {$ENDIF}

  // se abbiamo selezionato di cambiare l'icona, cambiala!
  if (Length(SETTINGS[SETTINGS_APPLICATION_ICON]) > 0) and (SETTINGS[SETTINGS_APPLICATION_ICON] <> '0') then begin
    if ChangeIconExe(Pchar(selectedir+'fba.exe'), PChar(SETTINGS[SETTINGS_APPLICATION_ICON])) = false then begin
    end;
  end;

  // applica registrazione
  ERegisterExeFile(PChar(selectedir+'fba.exe'));

  // se non è la versione vga, rimuovi risorsa HI_RES_AWARE (solo per release pocketpc)
  if device = 1 then begin
    if SETTINGS[SETTINGS_VGA]<>'1' then begin
      KillResource(selectedir+'fba.exe', 'CEUX', 'HI_RES_AWARE',1040);
    end;
  end;

  // crea .ini con i valori di default per l'avvio del loader
  BuildLaunchIniFile(selectedir+'fba.ini', false, 0);

  ShowMessage(ProjectExportedToSelectedDirectory);
end;

procedure TMainForm.mnuPocketPCExportProjectClick(Sender: TObject);
begin
  ExportProject(1);
end;


procedure TMainForm.mnuPocketPCBuildInstallClick(Sender: TObject);
var
  project: String;
  scriptRes: TScriptNode;
  //retcode: Integer;
  frmInstall: TFrmPocketPCINstall;
begin

  // sincronizza il buffer corrente con l'editor
  if current_buffer > -1 then begin
    scriptRes := scriptNode.Item[current_buffer].Data;
    scriptRes.buffer.Text := synedit.Text;
  end;

  // check sintassi
  if CheckSyntax = false then begin
    exit;
  end;

  // il progetto deve essere salvato!
  //if SaveProject=false then exit;
  if Length(project_saved) > 0 then begin
    project := project_saved+'.res\fba_win32\';
  end else begin
    project := FExtractFilePath(Application.ExeName)+'fba_win32\';
  end;

  // build setup, flagga risorse, exe e risorse esterne come non cachate
  // cosi' ricreiamo tutto durante il build
  COPY_INTERNAL_RESOURCES_CACHED := false;
  COPY_FBA_PLAYER_CACHED := false;
  COPY_EXTERNAL_RESOURCES_CACHED := False;

  // apri form costruzione package
  frmInstall := TFrmPocketPCInstall.Create(nil);
  frmInstall.inipath.Caption := project+'fba.ini';
  frmInstall.txtAppName.Text := SETTINGS[SETTINGS_PROJECTNAME];
  frmInstall.txtCompanyName.Text := SETTINGS[SETTINGS_PROJECTAUHTOR];

  // carica icona se selezionata
  if (Length(SETTINGS[SETTINGS_APPLICATION_ICON]) > 0) and (SETTINGS[SETTINGS_APPLICATION_ICON] <> '0') then begin
    try
      frmInstall.imgIcon.Picture.LoadFromFile(SETTINGS[SETTINGS_APPLICATION_ICON]);
      frmInstall.txtNewIcon.Caption := SETTINGS[SETTINGS_APPLICATION_ICON];
    except
      frmInstall.txtNewIcon.Caption := '';
    end;
  end;

  frmInstall.ShowModal;
  frmInstall.Free;
  
end;

procedure TMainForm.btnRunStepByStepClick(Sender: TObject);
begin
  RunProject(true);
end;

procedure TMainForm.mnuRunStepByStepClick(Sender: TObject);
begin
  btnRunStepByStepClick(Sender);
end;

procedure TMainForm.mnuProjectSettingsClick(Sender: TObject);
var
frmsettings: TFrmSettings;
begin
frmsettings := TFrmSettings.Create(nil);
frmsettings.ShowModal;
frmsettings.Free;
end;

procedure TMainForm.vardumpDblClick(Sender: TObject);
var
a: Integer;
FrmSetVar: TFrmSetVar;
begin
  if vardump.Items.Count = 0 then exit;

  // Cambia la variabile selezionata
  for a:=0 to vardump.Items.Count-1 do begin
    if vardump.Items[a].Selected then begin

      // non permetto di cambiare var temporanee
      if ( (Length(vardump.Items[a].SubItems.Strings[0]) > 0) and ( Copy(vardump.Items[a].SubItems.Strings[0],1,1)='(' ) ) then exit;

      // :( non so come mai ma non funge con gli upvalues...
      if vardump.Items[a].Caption = 'U' then begin
        ShowMessage(Cannot_Change_Value_Of_A_Local_External_Variable);
        exit;
      end;

      FrmSetVar := TFrmSetVar.Create(nil);
      if vardump.Items[a].Caption = 'G' then begin
        FrmSetVar.Caption := 'Change global variable value';
        FrmSetVar.lbl_type.Caption := 'G';
      end else if vardump.Items[a].Caption = 'U' then begin
        FrmSetVar.Caption := 'Change upvalue variable value';
        FrmSetVar.lbl_type.Caption := 'U';
      end else begin
        FrmSetVar.Caption := 'Change local variable value';
        FrmSetVar.lbl_type.Caption := 'L';
      end;
      FrmSetVar.lblVarNAme.Caption := vardump.Items[a].SubItems.Strings[0];
      if UpperCase(vardump.Items[a].SubItems.Strings[1]) <> '(NULL)' then begin
        FrmSetVar.txtNewValue.Text := vardump.Items[a].SubItems.Strings[1];
      end;
      // Setta l'indice della variabile nello stack
      FrmSetVar.lblVarIndex.Caption := IntToStr(a);
      FrmSetVar.ShowModal;
      FrmSetVar.Free;
      exit;
    end;
  end;

end;

procedure TMainForm.btnImportFontClick(Sender: TObject);
var
FrmImportFont: TFrmImportFont;
begin
  FrmImportFont := TFrmImportFont.Create(nil);
  FrmImportFont.ShowModal;
  FrmImportFont.Free;
end;

procedure TMainForm.syneditContextHelp(Sender: TObject; word: String);
var
help: String;
begin
  // se l'help richiesto è lo stesso dell'ultima volta, apri finestra di help
  // e puntalo su quel comando
  if UpperCase(word) = UpperCase(lastFastHelpFind) then begin
    if FrmHelp = nil then begin
      FrmHelp := TFrmHelp.Create(nil);
      FrmHelp.synmemo.Refresh;
      FrmHelp.LoadHelp();
    end else begin
      FrmHelp.Show;
    end;
    FrmHelp.SelectCommand(word);
  end else begin

  if Length(word) > 0 then begin
    help := FindQuickHelp(word);
    if Length(help) > 0 then begin
      SetAlertStatusBar( 'Syntax: '+help, true);
    end else begin
      SetAlertStatusBar( 'Command not found', true);
    end;
  end;
  lastFastHelpFind := word;

  end;
end;

//
// trova una porta tcp disponibile per l'ascolto!
//
function TMainForm.GetAvailableTCPPort: Integer;
var
found: Boolean;
numretries: Integer;
begin
  found := False;
  numretries:=10;

  sockserv.Addr:='localhost';
  while (found = false) and (DEFAULT_CONNECTION_PORT < 65535) and (numretries > 0) do begin
    sockserv.Port := IntToStr(DEFAULT_CONNECTION_PORT);
    try
      sockserv.Listen;
      // ok, trovata !
      found:=true;
    except
      // switcho la porta
      DEFAULT_CONNECTION_PORT:=DEFAULT_CONNECTION_PORT+1;
    end;
    sockserv.Close;
    numretries := numretries -1;
  end;

  if found then begin
    Result := DEFAULT_CONNECTION_PORT;
  end else begin
    Result := -1;
  end;
end;

procedure TMainForm.mnuSaveAsClick(Sender: TObject);
begin
  SaveProject(true);
end;

procedure TMainForm.Help2Click(Sender: TObject);
begin
  if FrmHelp = nil then begin
    FrmHelp := TFrmHelp.Create(nil);
    FrmHelp.synmemo.Refresh;
    FrmHelp.LoadHelp();
  end else begin
    FrmHelp.Show;
  end;
end;

procedure TMainForm.SetNilHelpForm();
begin
  FrmHelp := nil;
end;

procedure TMainForm.SearchAllFiles(query: String; casesensitive: Boolean; progress: TProgressBar);
var
a, b: Integer;
line: String;
totlines: LongInt;
scriptRes: TScriptNode;
item: TListItem;
begin

if historySearchAllFiles.Count > 500 then historySearchAllFiles.Delete(0);
historySearchAllFiles.Add(query);

panelSearchAllFiles.Visible := true;
dbgsplittersearchallfiles.Visible := true;

panelInfoSearchAllFiles.Caption := '              0 result(s) found in 0 project file(s)';
listSearchAllFiles.Items.BeginUpdate;
listSearchAllFiles.Clear;
listSearchAllFiles.Items.EndUpdate;

// conta tutte le linee di tutti i progetti
totlines := 0;
for a:=0 to scriptNode.Count-1 do begin
  scriptRes := scriptNode.Item[a].Data;
  totlines := totlines + scriptRes.buffer.Count;
end;

// abbiamo almeno una riga di buffer, go con la ricerca
if totlines > 0 then begin
  // bStopSearchAllFiles
  // imposta progressbar
  progress.Min := 0;
  progress.Position := 0;
  progress.Max := totlines;

  // ora cerca in tutte le linee di tutti i sorgenti
  for a:=0 to scriptNode.Count-1 do begin
    scriptRes := scriptNode.Item[a].Data;
    for b:=0 to scriptRes.buffer.count-1 do begin
      if progress.Position < progress.Max-1 then progress.Position := progress.Position + 1;

      line := scriptRes.buffer.Strings[b];

      // matched!
      if (
        ( (casesensitive) and (Pos(query, line) > 0) ) or
        ( (casesensitive = false) and (Pos(UpperCase(query), UpperCase(line)) > 0) )
        ) then
        begin
          item := listSearchAllFiles.Items.Add;
          item.Caption := scriptRes.name;
          item.SubItems.Add(IntToStr(b+1));
          item.SubItems.Add(line);
          listSearchAllFiles.Items[listSearchAllFiles.Items.Count-1].Selected:=true;
          listSearchAllFiles.Items[listSearchAllFiles.Items.Count-1].MakeVisible(true);
          //
          panelInfoSearchAllFiles.Caption := '              '+IntToStr(listSearchAllFiles.Items.Count)+' result(s) found in '+IntToStr(scriptNode.Count)+' project file(s)';
      end;

      Application.ProcessMessages;
      if bStopSearchAllFiles then break;
    end;
    if bStopSearchAllFiles then break;
  end;

end;

//listSearchAllFiles.Items.EndUpdate;
end;

procedure TMainForm.statusbarDrawPanel(StatusBar: TStatusBar;
  Panel: TStatusPanel; const Rect: TRect);
begin

  // pannello con semaforo esecuzione
  if Panel = statusbar.Panels[3] then begin
    if (lastModeSelected = EDITING) then picsStatus.Draw(statusbar.Canvas,Rect.Left,Rect.Top,0)
    else if (lastModeSelected = RUNNING) then picsStatus.Draw(statusbar.Canvas,Rect.Left,Rect.Top,2)
    else if (lastModeSelected = PAUSE) then picsStatus.Draw(statusbar.Canvas,Rect.Left,Rect.Top,1);
  end;

  // pannello con segnale di alert
  if (Panel = statusbar.Panels[4]) and (timerAlertStatus.Enabled = true) then begin
    if flashingStatusAlert mod 2 = 0 then picsStatus.Draw(statusbar.Canvas,Rect.Left,Rect.Top,3);
  end;

end;

procedure TMainForm.AboutFBA1Click(Sender: TObject);
begin
  FrmAbout := TFrmAbout.Create(nil);
  FrmAbout.ShowModal;
  FrmAbout.Free;
end;

procedure TMainForm.txtImmediateKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin

  if ((immediateHistoryPos > 0) and (Key = 38)) then begin

    //freccia su
    // fai salire il posizionatore della history su, se puoi
    immediateHistoryPos := immediateHistoryPos - 1;

    txtImmediate.Text := immediateHistory.Strings[immediateHistoryPos];

    txtImmediate.SelStart := Length(txtImmediate.Text);
    txtImmediate.SelLength := 0;
  end else if (Key = 40) then begin

    // freccia giu'
    // fai salire il posizionatore della history giu, se puoi
    if (immediateHistoryPos < immediateHistory.Count) then begin
      immediateHistoryPos := immediateHistoryPos + 1;
    end;
    if immediateHistoryPos < immediateHistory.Count then begin
      txtImmediate.Text := immediateHistory.Strings[immediateHistoryPos];
    end else begin
      txtImmediate.Text := '';
    end;

    txtImmediate.SelStart := Length(txtImmediate.Text);
    txtImmediate.SelLength := 0;

  end else if ((Key = 13) and (Length(txtImmediate.Text) > 0)) then begin
    //  se il socket è attivo, segnala la linea da eseguire
    // altrimenti (??) segnala errore
    // non dovrebbe mai succedere perchè la textbox non è visibile
    // se non sei connesso al player
    // al runner pocketfrog
    if socket.State = wsConnected then begin
      // manda al player pocketfrog
      SockSend('exec 0 '+txtImmediate.Text);
      // aggiungi alla history e posiziona il contatore all'ultima linea
      immediateHistory.Add(txtImmediate.Text);
      immediateHistoryPos := immediateHistory.Count;
    end else begin
      ShowMessage('Debugger not active! Unable to exec line!');
    end;
    txtImmediate.Text := '';
  end;
end;

procedure TMainForm.timerAlertStatusTimer(Sender: TObject);
begin
  flashingStatusAlert := flashingStatusAlert+ 1;
  if flashingStatusAlert > 40 then timerAlertStatus.Enabled := false;
  statusbar.Repaint;
end;

procedure TMainForm.mnuundoClick(Sender: TObject);
begin
  synedit.Undo;
end;

procedure TMainForm.mnuredoClick(Sender: TObject);
begin
  synedit.Redo;
end;

procedure TMainForm.mainmnuundoClick(Sender: TObject);
begin
  mnuundoClick(Sender);
end;

procedure TMainForm.mainmnuredoClick(Sender: TObject);
begin
  mnuredoClick(Sender);
end;


procedure TMainForm.timerEnumFunctionsTimer(Sender: TObject);
var
c, a, firstpar, placepos: Integer;
line, stag: String;
samelist: Boolean;
begin
  // questo processa il buffer corrente per cercare
  // definizioni di funzioni

  // se sto già lavorando, esci
  if bEnumFunctionsThreadWorking then exit;


  // ok, processa il buffer corrente
  bEnumFunctionsThreadWorking := true;
  try
    lastEnumFunctionsList.Clear;
    lastEnumFunctionsListPos.Clear;

    // processa il buffer corrente, se presente

      c := 0;
      while c < synedit.Lines.Count do begin

        Application.ProcessMessages;

        // Se il timer è disabilitato, stiamo per chiudere la form, quindi qui interrompi
        if timerEnumFunctions.Enabled = false then exit;

        line := LTrim(synedit.Lines[c]);

        // inizia con function<spazio>? allora ci interessa
        stag := 'function ';
        if Pos(stag, LowerCase(line)) = 1 then begin

          // cerca la prima parentesi aperta
          firstpar := Pos('(', line);
          
          if firstpar > 0 then begin
            line := LowerCase(Copy(line, Length(stag)+1, firstpar - Length(stag)-1));
            
            // verifica dove va messo per rispettare l'ordine alfabetico
            placepos := lastEnumFunctionsList.Count;
            for a:=0 to lastEnumFunctionsList.Count-1 do begin
            
              Application.ProcessMessages;

              //  Se il timer è disabilitato, stiamo per chiudere la form, quindi qui interrompi
              if timerEnumFunctions.Enabled = false then exit;

              if LowerCase(lastEnumFunctionsList.Strings[a]) > line then begin
                placepos := a;
                break;
              end;

            end;

            lastEnumFunctionsList.Insert(placepos, line);
            lastEnumFunctionsListPos.Insert(placepos, IntToStr(c+1));

          end;
        end;

        c:=c+1;
      end;


    // Se la nuova lista costruita è diversa dalla precedente, svuotala e
    // riempila con i nuovi valori
    samelist := true;
    if lastEnumFunctionsList.Count <> lstEnumFuncts.Items.Count then begin
      samelist := false;
    end else begin
      for a:=0 to lstEnumFuncts.Items.Count-1 do begin

        Application.ProcessMessages;

        // Se il timer è disabilitato, stiamo per chiudere la form, quindi qui interrompi
        if timerEnumFunctions.Enabled = false then exit;

        if (lastEnumFunctionsList.Strings[a] <> lstEnumFuncts.Items.Strings[a]) or
          (lastEnumFunctionsListPos.Strings[a] <> currentEnumFunctionsListPos.Strings[a]) then begin
            samelist := false;
            break;
        end;
      end;
    end;

    if samelist = False then begin
      lstEnumFuncts.Items.BeginUpdate;
      lstEnumFuncts.Clear;
      currentEnumFunctionsListPos.Clear;
      for a:=0 to lastEnumFunctionsList.Count-1 do begin
            Application.ProcessMessages;

            // Se il timer è disabilitato, stiamo per chiudere la form, quindi qui interrompi
            if timerEnumFunctions.Enabled = false then exit;

            currentEnumFunctionsListPos.Add(lastEnumFunctionsListPos.Strings[a]);
            lstEnumFuncts.ITems.Add(lastEnumFunctionsList.Strings[a]);
      end;
      lstEnumFuncts.Items.EndUpdate;
    end;

  finally
    bEnumFunctionsThreadWorking := False;
  end;


end;

procedure TMainForm.lstEnumFunctsClick(Sender: TObject);
var
a: Integer;
begin
  for a:=0 to lstEnumFuncts.Count-1 do begin
    if lstEnumFuncts.Selected[a] then begin
      synedit.GotoLineAndCenter(StrToInt(currentEnumFunctionsListPos.Strings[a]));
      exit;
    end;
  end;
end;

procedure TMainForm.mnufindinfilesClick(Sender: TObject);
var
a: Integer;
scriptRes: TScriptNode;
begin
  // sincronizza il buffer corrente con l'editor
  if current_buffer > -1 then begin
    scriptRes := scriptNode.Item[current_buffer].Data;
    scriptRes.buffer.Text := synedit.Text;
  end;

  bStopSearchAllFiles := false;
  FrmSearchAllProjectFiles := TFrmSearchAllProjectFiles.Create(nil);

  // riempie combo history
  for a:=0 to historySearchAllFiles.Count-1 do begin
    FrmSearchAllProjectFiles.cmbQuery.Items.Add(historySearchAllFiles.Strings[a])
  end;

  // maiofix - se c'è qualcosa selezionata in synedit, mostra quello come testo da cercare
  if synedit.SelLength > 0 then FrmSearchAllProjectFiles.cmbQuery.Text := synedit.SelText;

  FrmSearchAllProjectFiles.ShowModal;
  FrmSearchAllProjectFiles.Free;
end;

procedure TMainForm.mainmnufindinfilesClick(Sender: TObject);
begin
  mnufindinfilesClick(Sender);
end;

procedure TMainForm.btnHideSearchProjectFilesClick(Sender: TObject);
begin
  dbgsplittersearchallfiles.Visible := false;
  panelSearchAllFiles.Visible := false;
end;

procedure TMainForm.StopSearchAllFiles();
begin
  bStopSearchAllFiles := true;
end;

procedure TMainForm.listSearchAllFilesClick(Sender: TObject);
var
a: Integer;
idbuffer: Integer;
row: LongInt;
begin
  for a:= 0 to listSearchAllFiles.Items.Count-1 do begin
    // trova la selezione e posizionati nel sorgente su quella riga
    if listSearchAllFiles.Items[a].Selected then begin
      idbuffer := FindBufferID(listSearchAllFiles.Items[a].Caption);
      // trovato il sorgente, prova a posizionarti sulla riga
      if idbuffer > -1 then begin
        // prima seleziona il sorgente
        SelectScript(idbuffer);
        // e poi la riga
        row := StrToInt(listSearchAllFiles.Items[a].SubItems[0]);
        synedit.GotoLineAndCenter(row);
      end;
      exit;
    end;
  end;
end;

procedure TMainForm.CheckForUpdates1Click(Sender: TObject);
begin
  CheckForUpdates1.Enabled := false;
  httpget.URL := 'http://www.fbacreator.com/lastversion.php';
  httpget.Get;
end;

procedure TMainForm.httpgetDocEnd(Sender: TObject);
var
curr, rest: String;
begin
  curr := GetAppVersion;
  rest := Replace(httpget.LastResponse, chr(13), '');
  if UpperCase(rest) = UpperCase(curr) then begin
    ShowMessage('You have latest version: '+rest);
  end else begin
    ShowMessage('Update Available!'+Chr(13)+'Current version: ' + curr + Chr(13)+'Available version: ' + rest);
  end;
  CheckForUpdates1.Enabled := true;
end;

procedure TMainForm.httpgetSocksError(Sender: TObject; Error: Integer;
  Msg: String);
begin
  ShowMessage('Unable to check version. Are you connected to internet?');
  CheckForUpdates1.Enabled := true;
end;

procedure TMainForm.mnucopyconsoleClick(Sender: TObject);
begin
  if outputdebugconsole.Focused then outputdebugconsole.CopyToClipboard;
end;

procedure TMainForm.mnulastopen14Click(Sender: TObject);
var
cap, prj: String;
begin
  // avverti se il progetto non è salvato
  if SomethingUnsaved() then begin
    if MessageDlg('Warning! This project is unsaved. Ignore changes and load another project?',
      mtConfirmation, [mbYes, mbNo], 0) = mrNo then exit;
  end;

  // carica il progetto
  cap := TMenuItem(Sender).Caption;
  prj := Copy(cap, 4, Length(cap)-3);
  LoadProject(prj);
end;

procedure TMainForm.File1Click(Sender: TObject);
begin
  mainmnureopenproject.Visible := (GetLastOpenedProjectCount > 0);
end;

procedure TMainForm.mainmnureopenprojectClick(Sender: TObject);
var
a: Integer;
l, str: String;
AComponent: TComponent;
begin
  // mostra submenus con la lista dei progetti aperti di recente
  for a:=0 to 14 do begin

    if a < 10 then begin
      l := IntToStr(a);
    end else if a = 10 then begin
      l := 'A';
    end else if a = 11 then begin
      l := 'B';
    end else if a = 12 then begin
      l := 'C';
    end else if a = 13 then begin
      l := 'D';
    end else if a = 14 then begin
      l := 'E';
    end;

    str := GetLastOpenedProject(a);
    if Length(str) > 0 then begin
      AComponent := FindComponent('mnulastopen'+IntToStr(a));
      if Assigned(AComponent) then begin
        if AComponent is TMenuItem then begin
          TMenuItem(AComponent).Visible := (Length(str) > 0);
          TMenuItem(AComponent).Caption := l + ' ' + str;
        end;
      end;
    end;
  end;
end;

procedure TMainForm.mnuWindowsExportClick(Sender: TObject);
begin
  ExportProject(0);
end;

procedure TMainForm.EnvironmentOptions1Click(Sender: TObject);
var
frmenvoptions: TFrmEnvOptions;
begin
// flagga tutte le risorse non cachate, cosi' se cambiamo qualcosa nel conf che modifica
// la generazione delle risorse, al prossimo passo le rigeneriamo in ogni caso!
//
COPY_INTERNAL_RESOURCES_CACHED := false;
COPY_FBA_PLAYER_CACHED := false;
COPY_EXTERNAL_RESOURCES_CACHED := False;
// apri la finestra delle opzioni
frmenvoptions := TFrmEnvOptions.Create(nil);
frmenvoptions.ShowModal;
frmenvoptions.Free;
end;

// setta i colori dai settaggi in memoria
procedure TMainForm.SetSettingsFromOptionsInMemory();
begin
  synedit.TabWidth := OPTIONS_NUMBER[IDE_OPTIONS_TABS_SPACE];
  synedit.ActiveLineColor := StringToColor( OPTIONS_STRING[IDE_OPTIONS_COLOR_ACTIVELINEBACK] );
  synedit.Color := StringToColor( OPTIONS_STRING[IDE_OPTIONS_COLOR_EDITORBACK] );
  synedit.SelectedColor.Background :=  StringToColor( OPTIONS_STRING[IDE_OPTIONS_COLOR_SELECTEDBACK] );
  synedit.SelectedColor.Foreground :=  StringToColor( OPTIONS_STRING[IDE_OPTIONS_COLOR_SELECTEDFORE] );

  synedit.Highlighter.KeywordAttribute.Foreground :=  StringToColor( OPTIONS_STRING[IDE_OPTIONS_COLOR_SYNTAX1] );
  synedit.Highlighter.IdentifierAttribute.Foreground :=  StringToColor( OPTIONS_STRING[IDE_OPTIONS_COLOR_SYNTAX2] );
  synedit.Highlighter.StringAttribute.Foreground :=  StringToColor( OPTIONS_STRING[IDE_OPTIONS_COLOR_SYNTAX3] );
  synedit.Highlighter.CommentAttribute.Foreground :=  StringToColor( OPTIONS_STRING[IDE_OPTIONS_COLOR_SYNTAX4] );
  synedit.Highlighter.SymbolAttribute.Foreground :=  StringToColor( OPTIONS_STRING[IDE_OPTIONS_COLOR_SYNTAX5] );
  synedit.Font.Color :=  StringToColor( OPTIONS_STRING[IDE_OPTIONS_COLOR_SYNTAX6] );

  synedit.Repaint;
end;

procedure TMainForm.btnaddextresourceClick(Sender: TObject);
var
a,b: Integer;
found: Boolean;
ExtResourceData: TExtResourceNode;
node: TTreeNode;
begin
  if dlgaddresource.Execute=false then exit;

  // flagga risorse non cachate
  // cosi' ci assicuriamo di ricreare tutto quando serve
  COPY_EXTERNAL_RESOURCES_CACHED := false;


  // aggiungi ogni file alla lista delle risorse, se non già aggiunto
  for a:=0 to dlgaddresource.Files.Count-1 do begin

    // impedisce di aggiungere i files fba.dat e fba.ini (un po' di sanity)
    if ( UpperCase(ExtractFileName(dlgaddresource.Files[a]))='FBA.DAT' ) or
        ( UpperCase(ExtractFileName(dlgaddresource.Files[a]))='SETUP.INF' ) or
        ( UpperCase(ExtractFileName(dlgaddresource.Files[a]))='SETUP.INI' ) or
        ( UpperCase(ExtractFileName(dlgaddresource.Files[a]))='FBA.INI' ) then begin
      ShowMessage('File ' + ExtractFileName(dlgaddresource.Files[a]) + ' cannot be added (reserved name)!');
      continue;
    end;

    // Verifica se il file esiste
    if FileExists(dlgaddresource.Files[a])=false then begin
      ShowMessage(dlgaddresource.Files[a] + ' not found.');
      continue;
    end;

    // verifica se c'era già
    found:=false;
    for b:=0 to ExtResourceNode.Count-1 do begin
        ExtResourceData := ExtResourceNode.Item[b].Data;
        if UpperCase(ExtResourceData.name) =
            UpperCase(ExtractFileName(dlgaddresource.Files.Strings[a])) then begin
            found:=true;
            break;
        end;
    end;



    if found = true then begin
      ShowMessage('File '+ExtractFileName(dlgaddresource.Files.Strings[a])+' is already in list and will be skipped!');
    end else begin

        // crea data nodo treeview
        ExtResourceData := TExtResourceNode.Create;
        ExtResourceData.name := ExtractFileName(dlgaddresource.Files.Strings[a]);
        ExtResourceData.fullpath := dlgaddresource.Files.Strings[a];
        ExtResourceData.copied := false;

        // ok, possiamo aggiungere il file in lista
        node := projfiles.Items.AddNode(nil, ExtResourceNode,
          ExtResourceData.name, ExtResourceData, naAddChild);
        extResourceNode.AlphaSort(false);
        node.ImageIndex := GetIconIndexFromFileName(ExtResourceData.name);
        node.SelectedIndex := GetIconIndexFromFileName(ExtResourceData.name);

        // aggiorna label scripts treeview
        ExtResourceNode.Text:='Ext.Resources ('+IntToStr(ExtResourceNode.Count)+')';

    end;

  end;

  // espandi nodo risorse
  ExtResourceNode.Expand(true);

end;

end.

