{
  Original version 2004-2005 Richard B. Winston, U.S. Geological Survey (USGS)
  Modifications copyright 2005 Michalis Kamburelis
  Additional modifications by Richard B. Winston, April 26, 2005.

  This file is part of pasdoc_gui.

  pasdoc_gui is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  pasdoc_gui is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with pasdoc_gui; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
}

{
  @abstract(@name contains the main form of Help Generator.)
  @author(Richard B. Winston <rbwinst@usgs.gov>)
  @author(Michalis Kamburelis)
  @author(Arno Garrels <first name.name@nospamgmx.de>)
  @created(2004-11-28)
}

unit frmHelpGeneratorUnit;

{$R *.dfm}

interface

{$IFDEF ConditionalExpressions}
  {$IF CompilerVersion >= 15}
    {$WARN UNSAFE_TYPE OFF}
    {$WARN UNSAFE_CAST OFF}
    {$WARN UNSAFE_CODE OFF}
    {$WARN UNIT_PLATFORM OFF}
  {$IFEND}
  {$IF CompilerVersion >= 20}
    {$DEFINE STRING_UNICODE}
  {$IFEND}
{$ENDIF}

uses
  Windows, Classes, SysUtils, Graphics, Controls, Forms, Dialogs,
  FileCtrl, ComCtrls, ExtCtrls, Menus, Buttons, Spin, CheckLst,
  PasDoc_Gen, PasDoc_GenHtml, PasDoc_Base, StdCtrls, PasDoc_Types,
  PasDoc_Languages, PasDoc_GenLatex, PasDoc_Serialize,
  IniFiles, PasDoc_GenHtmlHelp, PasDoc_Utils, PasDoc_Items;

type
  EInvalidSpellingLanguage = class(Exception);
  
  // @abstract(TfrmHelpGenerator is the class of the main form of Help
  // Generator.) Its published fields are mainly components that are used to
  // save the project settings.

  { TfrmHelpGenerator }

  TfrmHelpGenerator = class(TForm)
    // Click @name  to select a directory that may
    // have include directories.
    btnBrowseIncludeDirectory: TButton;
    // Click @name to select one or more sorce files for the
    // project.
    btnBrowseSourceFiles: TButton;
    // Click @name to generate output
    ButtonGenerateDocs: TButton;
    ButtonAspellURL: TButton;
    ButtonGraphVizURL: TButton;
    cbCheckSpelling: TCheckBox;
    cbVizGraphClasses: TCheckBox;
    cbVizGraphUses: TCheckBox;
    CheckAutoAbstract: TCheckBox;
    CheckAutoLink: TCheckBox;
    CheckStoreRelativePaths: TCheckBox;
    CheckHandleMacros: TCheckBox;
    CheckUseTipueSearch: TCheckBox;
    // @name controls what members (based on visibility)
    // will be included in generated output.
    CheckListVisibleMembers: TCheckListBox;
    CheckWriteUsesList: TCheckBox;
    clbSorting: TCheckListBox;
    // @name determines what sort of files will be created
    comboGenerateFormat: TComboBox;
    // comboLanguages is used to set the language in which the web page will
    // be written.  Of course, this only affects tha language for the text
    // generated by the program, not the comments about the program.
    comboLanguages: TComboBox;
    comboLatexGraphicsPackage: TComboBox;
    // @name is used to set the name of the project.
    edProjectName: TEdit;
    edTitle: TEdit;
    Label1: TLabel;
    Label10: TLabel;
    Label11: TLabel;
    Label12: TLabel;
    Label17: TLabel;
    Label4: TLabel;
    LabelHeader: TLabel;
    LabelFooter: TLabel;
    LabelImplicitVisibility: TLabel;
    Label14: TLabel;
    Label15: TLabel;
    Label16: TLabel;
    Label18: TLabel;
    Label19: TLabel;
    Label2: TLabel;
    Label20: TLabel;
    LabelVisibleMembers: TLabel;
    Label22: TLabel;
    Label23: TLabel;
    Label24: TLabel;
    Label3: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Label9: TLabel;
    lbNavigation: TListBox;
    memoCommentMarkers: TMemo;
    memoDefines: TMemo;
    // @name holds the complete paths of all the source files
    // in the project.
    memoFiles: TMemo;
    memoFooter: TMemo;
    memoHeader: TMemo;
    memoHyphenatedWords: TMemo;
    // The lines in @name are the paths of the files that
    // may have include files that are part of the project.
    memoIncludeDirectories: TMemo;
    // memoMessages displays compiler warnings.  See also @link(seVerbosity);
    memoMessages: TMemo;
    memoSpellCheckingIgnore: TMemo;
    MenuAbout: TMenuItem;
    MenuContextHelp: TMenuItem;
    MenuEdit: TMenuItem;
    MenuGenerate: TMenuItem;
    MenuGenerateRun: TMenuItem;
    MenuSave: TMenuItem;
    MenuPreferences: TMenuItem;
    NotebookMain: TNotebook;
    PanelLatexHyphenation: TPanel;
    PanelFooterHidden: TPanel;
    PanelHeaderHidden: TPanel;
    pnlEditCommentInstructions: TPanel;
    PanelMarkers: TPanel;
    PanelDefinesTop: TPanel;
    PanelGenerateTop: TPanel;
    PanelIncludeDirectoriesTop: TPanel;
    PanelSourceFilesTop: TPanel;
    PanelSpellCheckingTop1: TPanel;
    OpenDialog1: TOpenDialog;
    RadioImplicitVisibility: TRadioGroup;
    rgCommentMarkers: TRadioGroup;
    rgLineBreakQuality: TRadioGroup;
    SaveDialog1: TSaveDialog;
    OpenDialog2: TOpenDialog;
    MainMenu1: TMainMenu;
    MenuFile: TMenuItem;
    MenuOpen: TMenuItem;
    MenuSaveAs: TMenuItem;
    MenuExit: TMenuItem;
    MenuNew: TMenuItem;
    seVerbosity: TSpinEdit;
    Splitter1: TSplitter;
    Splitter2: TSplitter;
    MenuHelp: TMenuItem;
    tvUnits: TTreeView;
    edOutPut: TEdit;
    EditCssFileName: TEdit;
    EditIntroductionFileName: TEdit;
    PasDoc1: TPasDoc;
    EditConclusionFileName: TEdit;
    HTMLDocGenerator: THTMLDocGenerator;
    HTMLHelpDocGenerator: THTMLHelpDocGenerator;
    TexDocGenerator: TTexDocGenerator;
    ButtonIntroFileName: TButton;
    ButtonConclusionFileName: TButton;
    ButtonCssFileName: TButton;
    ButtonOutPutPathName: TButton;
    OpenDialog3: TOpenDialog;
    seComment: TMemo;
    PanelLeft: TPanel;
    PanelLeftTop: TPanel;
    ButtonGenerate: TButton;
    PanelSort: TPanel;
    PanelSpellCheckingBottom: TPanel;
    PanelGenerateBottom: TPanel;
    PanelDisplayCommentsMid: TPanel;
    PanelDisplayCommentsBottom: TPanel;
    procedure ButtonURLClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure MenuContextHelpClick(Sender: TObject);
    procedure MenuGenerateRunClick(Sender: TObject);
    procedure MenuPreferencesClick(Sender: TObject);
    procedure MenuSaveClick(Sender: TObject);
    procedure SomethingChanged(Sender: TObject);
    procedure MenuAboutClick(Sender: TObject);
    procedure PasDoc1Warning(const MessageType: TPasDocMessageType;
      const AMessage: string; const AVerbosity: Cardinal);
    procedure btnBrowseSourceFilesClick(Sender: TObject);
    procedure cbCheckSpellingChange(Sender: TObject);
    procedure CheckListVisibleMembersClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ButtonGenerateDocsClick(Sender: TObject);
    procedure comboLanguagesChange(Sender: TObject);
    procedure btnBrowseIncludeDirectoryClick(Sender: TObject);
    procedure btnOpenClick(Sender: TObject);
    procedure MenuSaveAsClick(Sender: TObject);
    procedure Exit1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure MenuNewClick(Sender: TObject);
    procedure comboGenerateFormatChange(Sender: TObject);
    procedure lbNavigationClick(Sender: TObject);
    procedure rgCommentMarkersClick(Sender: TObject);
    // @name displays the comment associated with the selected node of
    // @link(tvUnits) in @link(seComment).
    procedure tvUnitsClick(Sender: TObject);
    procedure LocationsButtonsClick(Sender: TObject);
  private
    function GetCheckListVisibleMembersValue: TVisibilities;
    procedure SetCheckListVisibleMembersValue(const AValue: TVisibilities);
  private
    pageHeadFoot: TPage;
    pageLatexOptions: TPage;
    pageGenerate: TPage;

    FChanged: boolean;
    FSettingsFileName: string;
    MisspelledWords: TStringList;
    InsideCreateWnd: boolean;
    
    { If Changed then this offers user the chance to save the project.
      Returns @false when user chose to Cancel the whole operation
      (not only file saving, but also the parent operation -- you
      should always check the result of this function and cancel
      anything further if result is false). }
    function SaveChanges: boolean;
    
    procedure SetChanged(const AValue: boolean);
    procedure SetDefaults;
    procedure SetSettingsFileName(const AValue: string);
    procedure UpdateCaption;
    function LanguageIdToString(const LanguageID: TLanguageID): string;
    procedure CheckIfSpellCheckingAvailable;
    procedure FillNavigationListBox;
    procedure SetOutputDirectory(const FileName: string);
    // @name fills @link(tvUnits) with a heirarchical representation of the
    // TPasItems in PasDoc1.
    procedure FillTreeView;
    procedure PasDocMessages(const MessageType: TPasDocMessageType;
      const AMessage: string; const AVerbosity: Cardinal);
    procedure LoadSettings;

    { This property allows to get and set all
      CheckListVisibleMembers.Checked[] values as a simple
      TVisibilities type. }
    property CheckListVisibleMembersValue: TVisibilities
      read GetCheckListVisibleMembersValue
      write SetCheckListVisibleMembersValue;

    { Saves current settings to FileName. Additionally may
      also do some other things commonly done at saving time:
      
      if SetSettingsFileName then sets SettingsFileName property
      to FileName.
      
      if ClearChanged then sets Changed to false. }
    procedure SaveSettingsToFile(const FileName: string;
      SetSettingsFileName, ClearChanged: boolean);
  protected
    procedure CreateWnd; override;
  public
    DefaultDirectives: TStringList;
    // @name is @true when the user has changed the project settings.
    // Otherwise it is @false.
    property SChanged: boolean read FChanged write SetChanged;
    
    { This is the settings filename (.pds file) that is currently
      opened. You can look at pasdoc_gui as a "program to edit pds files".
      It is '' if current settings are not associated with any filename
      (because user did not opened any pds file, or he chose "New" menu item). }
    property SettingsFileName: string read FSettingsFileName
      write SetSettingsFileName;
      
    { If SettingsFileName <> '',
      this returns ExtractFileName(SettingsFileName),
      else it returns 'Unsaved PasDoc settings'. This is good
      when you want to nicely present the value of SettingsFileName 
      to the user.

      This follows GNOME HIG standard for window caption. }
    function SettingsFileNameNice: string;


  end;

var
  // @name is the main form of Help Generator
  frmHelpGenerator: TfrmHelpGenerator;

implementation

uses PasDoc_SortSettings, frmAboutUnit, HelpProcessor,
  WWWBrowserRunnerDM, PreferencesFrm, PasDocGuiSettings;

procedure TfrmHelpGenerator.PasDoc1Warning(const MessageType: TPasDocMessageType;
  const AMessage: string; const AVerbosity: Cardinal);
const
  MisText = 'Word misspelled "';
var
  MisspelledWord: string;
begin
  memoMessages.Lines.Add(AMessage);
  if Pos(MisText, AMessage) =1 then begin
    MisspelledWord := Copy(AMessage, Length(MisText)+1, MAXINT);
    SetLength(MisspelledWord, Length(MisspelledWord) -1);
    MisspelledWords.Add(MisspelledWord)
  end;
end;

procedure TfrmHelpGenerator.MenuAboutClick(Sender: TObject);
begin
  frmAbout.ShowModal;
end;

procedure TfrmHelpGenerator.SetOutputDirectory(const FileName: string);
begin
  edOutput.Text := ExtractFileDir(FileName)
    + PathDelim + 'PasDoc';
end;

procedure TfrmHelpGenerator.SomethingChanged(Sender: TObject);
begin
  { Some components (in Lazarus 0.9.10, this concerns at
    least TMemo with GTK 1 interface) generate some OnChange
    event when creating their widget (yes, I made sure: it doesn't
    happen when reading their properties.)
    
    This is not good, because when we open pasdoc_gui,
    the default project should be left with Changed = false.
    
    Checking ComponentState and ControlState to safeguard
    against this is not possible. I'm using InsideCreateWnd to
    safeguard against this. }
  if InsideCreateWnd then Exit;

  SChanged := true;
  if (memoFiles.Lines.Count > 0) and (edOutput.Text = '') then begin
    SetOutputDirectory(memoFiles.Lines[0]);
  end;
end;

procedure TfrmHelpGenerator.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_F1 then
  begin
    MenuContextHelpClick(ActiveControl);
  end;
end;

procedure TfrmHelpGenerator.ButtonURLClick(Sender: TObject);
begin
  WWWBrowserRunner.RunBrowser((Sender as TButton).Caption);
end;

procedure TfrmHelpGenerator.FormDestroy(Sender: TObject);
begin
  DefaultDirectives.Free;
  MisspelledWords.Free;
end;

procedure TfrmHelpGenerator.btnBrowseSourceFilesClick(Sender: TObject);
var
  Directory: string;
  FileIndex: integer;
  Files: TStringList;
begin
  if OpenDialog1.Execute then
  begin
    Files := TStringList.Create;
    try

      if edOutput.Text = '' then
      begin
        SetOutputDirectory(OpenDialog1.FileName);
      end;

      Files.Sorted := True;
      Files.Duplicates := dupIgnore;

      Files.AddStrings(memoFiles.Lines);
      Files.AddStrings(OpenDialog1.Files);

      memoFiles.Lines := Files;

      for FileIndex := 0 to OpenDialog1.Files.Count - 1 do
      begin
        Directory := ExtractFileDir(OpenDialog1.Files[FileIndex]);
        if memoIncludeDirectories.Lines.IndexOf(Directory) < 0 then
        begin
          memoIncludeDirectories.Lines.Add(Directory);
        end;
      end;
    finally
      Files.Free;
    end;
  end;
end;

procedure TfrmHelpGenerator.CheckIfSpellCheckingAvailable;
var
  CheckIfSpellCheckingAvailable: boolean;
begin
  if not cbCheckSpelling.Enabled or not cbCheckSpelling.Checked then
  begin
    Exit;
  end;
  
  CheckIfSpellCheckingAvailable := comboGenerateFormat.ItemIndex in [0,1];
  if CheckIfSpellCheckingAvailable then
  begin
    try
      LanguageIdToString(TLanguageID(comboLanguages.ItemIndex));
    except on E: EInvalidSpellingLanguage do
      begin
        //CheckIfSpellCheckingAvailable := False;
        Beep;
        MessageDlg(E.Message, Dialogs.mtError, [mbOK], 0);
      end;
    end;
  end;
end;

procedure TfrmHelpGenerator.FillNavigationListBox;
var
  Index: integer;
  page: TPage;
begin
  { Under GTK interface, lbNavigation.OnClick event may occur
    when we change lbNavigation.Items. Our lbNavigationClick
    is not ready to handle this, so we turn him off. }
  lbNavigation.OnClick := nil;
  try
    lbNavigation.Items.Clear;
    for Index := 0 to NotebookMain.Pages.Count -1 do
    begin
      page := NotebookMain.Pages.Objects[Index] as TPage;
      if page.Tag = 1 then begin
        lbNavigation.Items.AddObject(page.Caption, page);
      end;
    end;
  finally
    lbNavigation.OnClick := lbNavigationClick;
  end;
end;

procedure TfrmHelpGenerator.cbCheckSpellingChange(Sender: TObject);
begin
  SChanged := True;
  if cbCheckSpelling.Checked then
  begin
    CheckIfSpellCheckingAvailable;
  end;
end;

procedure TfrmHelpGenerator.CheckListVisibleMembersClick(Sender: TObject);
var
  NewValue: TVisibilities;
begin
  NewValue := CheckListVisibleMembersValue;

  if PasDoc1.ShowVisibilities <> NewValue then
  begin
    SChanged := True;
    PasDoc1.ShowVisibilities := NewValue;
  end;
end;

procedure TfrmHelpGenerator.SetDefaults;
var
  SortIndex: TSortSetting;
begin
  CheckListVisibleMembersValue := DefaultVisibilities;
  RadioImplicitVisibility.ItemIndex := 0;

  comboLanguages.ItemIndex := Ord(lgEnglish);
  comboLanguagesChange(nil);
  
  comboGenerateFormat.ItemIndex := 0;
  comboGenerateFormatChange(nil);

  edTitle.Text := '';
  edProjectName.Text := '';
  edOutput.Text := '';
  seVerbosity.Value := 2;
  comboGenerateFormat.ItemIndex := 0;
  memoFiles.Clear;
  memoIncludeDirectories.Clear;
  memoMessages.Clear;
  memoCommentMarkers.Clear;
  rgCommentMarkers.ItemIndex := 1;

  memoDefines.Lines.Assign(DefaultDirectives);

  EditCssFileName.Text := '';
  EditIntroductionFileName.Text := '';
  EditConclusionFileName.Text := '';
  CheckWriteUsesList.Checked := false;
  CheckAutoAbstract.Checked := false;
  CheckAutoLink.Checked := false;
  CheckHandleMacros.Checked := false;
  CheckUseTipueSearch.Checked := false;

  for SortIndex := Low(TSortSetting) to High(TSortSetting) do
    clbSorting.Checked[Ord(SortIndex)] := false;

  CheckStoreRelativePaths.Checked := true;

  SChanged := False;
end;

procedure TfrmHelpGenerator.UpdateCaption;
var
  NewCaption: string;
begin
  { Caption value follows GNOME HIG 2.0 standard }
  NewCaption := '';
  if SChanged then NewCaption := NewCaption + '*';
  NewCaption := NewCaption + SettingsFileNameNice;
  NewCaption := NewCaption + ' - PasDoc GUI';
  Caption := NewCaption;
end;

function TfrmHelpGenerator.LanguageIdToString(
  const LanguageID: TLanguageID): string;
begin
  try
    result := 'en';
    case LanguageID of
    {$IFDEF STRING_UNICODE}
      lgBosnian: result := 'bs';
      lgBrazilian: result := 'pt';  // Portuguese used for brazilian.
      lgCatalan: result := 'ca';
      lgChinese:
        begin
          if cbCheckSpelling.Checked then
          raise EInvalidSpellingLanguage.Create(
            'Sorry, that language is not supported for spell checking');
          result := 'zh';
        end;
      lgDanish: result := 'da';
      lgDutch: result := 'nl';
      lgEnglish: result := 'en';
      lgFrench: result := 'fr';
      lgGerman: result := 'de';
      lgIndonesian: result := 'id';
      lgItalian: result := 'it';
      lgJavanese: result := 'jv';
      lgPolish: result := 'pl';
      lgRussian: result := 'ru';
      lgSlovak: result := 'sk';
      lgSpanish: result := 'es';
      lgSwedish: result := 'sv';
      lgHungarian: result := 'hu';
    {$ELSE}
      lgBosnian: result := 'bs';
      lgBrazilian_1252: result := 'pt'; // Portuguese used for brazilian.
      lgBrazilian_utf8: result := 'pt';
      lgCatalan: result := 'ca';
      lgDanish: result := 'da';
      lgDutch: result := 'nl';
      lgEnglish: result := 'en';
      lgFrench_ISO_8859_15: result := 'fr';
      lgFrench_UTF_8: result := 'fr';
      lgGerman: result := 'de';
      lgIndonesian: result := 'id';
      lgItalian: result := 'it';
      lgJavanese: result := 'jv';
      lgPolish_CP1250: result := 'pl';
      lgPolish_ISO_8859_2: result := 'pl';
      lgRussian_1251: result := 'ru';
      lgRussian_866: result := 'ru';
      lgRussian_koi8: result := 'ru';
      lgSlovak: result := 'sk';
      lgSpanish: result := 'es';
      lgSwedish: result := 'sv';
      lgHungarian_1250: result := 'hu';
    {$ENDIF}
    else raise EInvalidSpellingLanguage.Create(
        'Sorry, that language is not supported for spell checking');
    end;
  except on EInvalidSpellingLanguage do
    begin
      cbCheckSpelling.Checked := False;
      raise;
    end;
  end;
end;

procedure TfrmHelpGenerator.SetChanged(const AValue: boolean);
begin
  if FChanged = AValue then Exit;
  FChanged := AValue;
  UpdateCaption;
end;

procedure TfrmHelpGenerator.SetSettingsFileName(const AValue: string);
begin
  FSettingsFileName := AValue;
  UpdateCaption;
end;

procedure TfrmHelpGenerator.FormCreate(Sender: TObject);
var
  LanguageIndex: TLanguageID;
  Index: integer;
  Vis: TVisibility;
begin
{$IFDEF CONDITIONALEXPRESSIONS}
  {$IF COMPILERVERSION > 17}
    ReportMemoryLeaksOnShutDown := DebugHook <> 0;
  {$IFEND}
{$ENDIF}
  MisspelledWords:= TStringList.Create;
  MisspelledWords.Sorted := True;
  MisspelledWords.Duplicates := dupIgnore;

  pageHeadFoot := TPage(NotebookMain.Pages.Objects[NotebookMain.Pages.IndexOf('Header / Footer')]);
  pageLatexOptions := TPage(NotebookMain.Pages.Objects[NotebookMain.Pages.IndexOf('LaTeX Options')]);
  pageGenerate := TPage(NotebookMain.Pages.Objects[NotebookMain.Pages.IndexOf('Generate')]);

  comboLanguages.Items.Capacity :=
    Ord(High(TLanguageID)) - Ord(Low(TLanguageID)) + 1;
  for LanguageIndex := Low(TLanguageID) to High(TLanguageID) do
  begin
    comboLanguages.Items.Add(LanguageDescriptor(LanguageIndex)^.Name);
  end;

  Constraints.MinWidth := Width;
  Constraints.MinHeight := Height;

  DefaultDirectives := TStringList.Create;
  
  { Original HelpGenerator did here
    DefaultDirectives.Assign(memoDefines.Lines)
    I like this solution, but unfortunately current Lazarus seems
    to sometimes "lose" value of TMemo.Lines...
    So I'm setting these values at runtime. }
    
  {$IFDEF FPC}
  DefaultDirectives.Append('FPC');
  {$ENDIF}
  {$IFDEF UNIX}
  DefaultDirectives.Append('UNIX');
  {$ENDIF}
  {$IFDEF LINUX}
  DefaultDirectives.Append('LINUX');
  {$ENDIF}
  {$IFDEF DEBUG}
  //DefaultDirectives.Append('DEBUG');
  {$ENDIF}

  {$IFDEF VER130}
  DefaultDirectives.Append('VER130');
  {$ENDIF}
  {$IFDEF VER140}
  DefaultDirectives.Append('VER140');
  {$ENDIF}
  {$IFDEF VER150}
  DefaultDirectives.Append('VER150');
  {$ENDIF}
  {$IFDEF VER160}
  DefaultDirectives.Append('VER160');
  {$ENDIF}
  {$IFDEF VER170}
  DefaultDirectives.Append('VER170');
  {$ENDIF}
  {$IFDEF VER180} { Delphi 2006 and 2007 }
    {$IFDEF VER185} { Delphi 2007 }
      DefaultDirectives.Append('VER185');
    {$ELSE}
      DefaultDirectives.Append('VER185');
    {$ENDIF}
  {$ENDIF}
  {$IFDEF VER200} { Delphi 2009 }
    DefaultDirectives.Append('VER200');
  {$ENDIF}
  {$IFDEF VER210} { Delphi 2010 }
    DefaultDirectives.Append('VER210');
  {$ENDIF}
  {$IFDEF VER220} { Delphi XE }
    DefaultDirectives.Append('VER220');
  {$ENDIF}
  {$IFDEF UNICODE} { Delphi 2009+ }
    DefaultDirectives.Append('UNICODE');
  {$ENDIF}
  {$IFDEF MSWINDOWS}
  DefaultDirectives.Append('MSWINDOWS');
  {$ENDIF}
  {$IFDEF WIN32}
  DefaultDirectives.Append('WIN32');
  {$ENDIF}
  {$IFDEF CPU386}
  DefaultDirectives.Append('CPU386');
  {$ENDIF}
  {$IFDEF CONDITIONALEXPRESSIONS}
  DefaultDirectives.Append('CONDITIONALEXPRESSIONS');
  {$ENDIF}

  CheckListVisibleMembers.Items.Clear;
  for Vis := Low(TVisibility) to High(TVisibility) do
  begin
    CheckListVisibleMembers.Items.Add(string(VisibilityStr[Vis]));
  end;

  SetDefaults;

  { It's too easy to change it at design-time, so we set it at runtime. }
  NotebookMain.PageIndex := 0;
  Application.ProcessMessages;

  {$IFDEF WIN32}
  // Deal with bug in display of TSpinEdit in Win32.
  seVerbosity.Constraints.MinWidth := 60;
  seVerbosity.Width := seVerbosity.Constraints.MinWidth;
  {$ENDIF}
  
  { Workaround for Lazarus bug 0000713,
    [http://www.lazarus.freepascal.org/mantis/view.php?id=713]:
    we set menu shortcuts at runtime.
    (the bug is only for Win32, but we must do this workaround for every
    target). }

  //MenuOpen.ShortCut := ShortCut(VK_O, [ssCtrl]);
  //MenuSave.ShortCut := ShortCut(VK_S, [ssCtrl]);
  MenuOpen.ShortCut := ShortCut(Ord('O'), [ssCtrl]);
  MenuSave.ShortCut := ShortCut(Ord('S'), [ssCtrl]);
  MenuGenerateRun.ShortCut := ShortCut(VK_F9, []);

  // A Tag of 1 means the page should be visible.
  for Index := NotebookMain.Pages.Count -1 downto 0 do
  begin
    //NotebookMain.CustomPage(Index).Tag := 1;
    TPage(NotebookMain.Pages.Objects[Index]).Tag := 1;
  end;

  comboGenerateFormatChange(nil);
  
  FillNavigationListBox;
  SChanged := False;

  SettingsFileName := IniFile.ReadString('Main', 'LastProject', '');
  if (SettingsFileName <> '') and (AutoLoadLastProject) then
    LoadSettings;
end;

procedure TfrmHelpGenerator.FillTreeView;
var
   Lang: TPasDocLanguages;

  procedure TreeAddCio(const ALLCiosNode: TTreeNode);
  var
    LCio: TPasCio;
    LCios: TPasNestedCios;
    I, J: Integer;
    ClassNode: TTreeNode;
    FieldsNode: TTreeNode;
    MethodNode: TTreeNode;
    PropertiesNode: TTreeNode;
    TypesNode: TTreeNode;
    PasItem: TPasItem;
  begin
    LCios := TPasNestedCios(ALLCiosNode.Data);
    for J := 0 to LCios.Count - 1 do
    begin
      LCio := TPasCio(LCios.PasItemAt[J]);

      ClassNode := tvUnits.Items.AddChildObject(ALLCiosNode,
        LCio.Name, LCio);

      if LCio.Fields.Count > 0 then
      begin
        FieldsNode := tvUnits.Items.AddChildObject(ClassNode,
          Lang.Translation[trFields], LCio.Fields);
        for I := 0 to LCio.Fields.Count -1 do
        begin
          PasItem := LCio.Fields.PasItemAt[I];
          tvUnits.Items.AddChildObject(FieldsNode, PasItem.Name, PasItem);
        end;
      end;
      if LCio.Methods.Count > 0 then
      begin
        MethodNode := tvUnits.Items.AddChildObject(ClassNode,
          Lang.Translation[trMethods], LCio.Methods);
        for I := 0 to LCio.Methods.Count -1 do
        begin
          PasItem := LCio.Methods.PasItemAt[I];
          tvUnits.Items.AddChildObject(MethodNode, PasItem.Name, PasItem);
        end;
      end;
      if LCio.Properties.Count > 0 then
      begin
        PropertiesNode := tvUnits.Items.AddChildObject(ClassNode,
          Lang.Translation[trProperties], LCio.Properties);
        for I := 0 to LCio.Properties.Count -1 do
        begin
          PasItem := LCio.Properties.PasItemAt[I];
          tvUnits.Items.AddChildObject(PropertiesNode, PasItem.Name, PasItem);
        end;
      end;
      if LCio.Types.Count > 0 then
      begin
        TypesNode := tvUnits.Items.AddChildObject(ClassNode,
          Lang.Translation[trNestedTypes], LCio.Types);
        for I := 0 to LCio.Types.Count -1 do
        begin
          PasItem := LCio.Types.PasItemAt[I];
          tvUnits.Items.AddChildObject(TypesNode, PasItem.Name, PasItem);
        end;
      end;
      if LCio.Cios.Count > 0 then
      begin
        ClassNode := tvUnits.Items.AddChildObject(ClassNode,
          Lang.Translation[trNestedCR], LCio.CIOs);
        TreeAddCio(ClassNode);
      end;
    end;
  end;

var
  UnitItem: TPasUnit;
  AllUnitsNode: TTreeNode;
  UnitIndex: integer;
  UnitNode: TTreeNode;
  AllTypesNode: TTreeNode;
  AllVariablesNode: TTreeNode;
  AllCIOs_Node: TTreeNode;
  AllConstantsNode: TTreeNode;
  AllProceduresNode: TTreeNode;
  UsesNode: TTreeNode;
  PasItemIndex: integer;
  PasItem: TPasItem;
  UsesIndex: integer;
begin
  tvUnits.Items.Clear;
  Lang := TPasDocLanguages.Create;
  try
    Lang.Language := TLanguageID(comboLanguages.ItemIndex);
    if PasDoc1.IntroductionFileName <> '' then
    begin
      tvUnits.Items.AddObject(nil, PasDoc1.IntroductionFileName, PasDoc1.Introduction);
    end;
    AllUnitsNode := tvUnits.Items.AddObject(nil,
      Lang.Translation[trUnits], PasDoc1.Units);
    for UnitIndex := 0 to PasDoc1.Units.Count -1 do
    begin
      UnitItem := PasDoc1.Units.UnitAt[UnitIndex];
      UnitNode := tvUnits.Items.AddChildObject(AllUnitsNode,
        UnitItem.SourceFileName, UnitItem);
      if UnitItem.Types.Count > 0 then
      begin
        AllTypesNode := tvUnits.Items.AddChildObject(UnitNode,
          Lang.Translation[trTypes], UnitItem.Types);
        for PasItemIndex := 0 to UnitItem.Types.Count -1 do
        begin
          PasItem := UnitItem.Types.PasItemAt[PasItemIndex];
          tvUnits.Items.AddChildObject(AllTypesNode, PasItem.Name, PasItem);
        end;
      end;
      if UnitItem.Variables.Count > 0 then
      begin
        AllVariablesNode := tvUnits.Items.AddChildObject(UnitNode,
          Lang.Translation[trVariables], UnitItem.Variables);
        for PasItemIndex := 0 to UnitItem.Variables.Count -1 do
        begin
          PasItem := UnitItem.Variables.PasItemAt[PasItemIndex];
          tvUnits.Items.AddChildObject(AllVariablesNode, PasItem.Name, PasItem);
        end;
      end;
      if UnitItem.CIOs.Count > 0 then
      begin
        AllCIOs_Node := tvUnits.Items.AddChildObject(UnitNode,
          Lang.Translation[trCio], UnitItem.CIOs);
        TreeAddCio(AllCIOs_Node);
      end;
      if UnitItem.Constants.Count > 0 then
      begin
        AllConstantsNode := tvUnits.Items.AddChildObject(UnitNode,
          Lang.Translation[trConstants], UnitItem.Constants);
        for PasItemIndex := 0 to UnitItem.Constants.Count -1 do
        begin
          PasItem := UnitItem.Constants.PasItemAt[PasItemIndex];
          tvUnits.Items.AddChildObject(AllConstantsNode, PasItem.Name, PasItem);
        end;
      end;
      if UnitItem.FuncsProcs.Count > 0 then
      begin
        AllProceduresNode := tvUnits.Items.AddChildObject(UnitNode,
          Lang.Translation[trFunctionsAndProcedures], UnitItem.FuncsProcs);
        for PasItemIndex := 0 to UnitItem.FuncsProcs.Count -1 do
        begin
          PasItem := UnitItem.FuncsProcs.PasItemAt[PasItemIndex];
          tvUnits.Items.AddChildObject(AllProceduresNode, PasItem.Name, PasItem);
        end;
      end;
      if UnitItem.UsesUnits.Count > 0 then
      begin
        UsesNode := tvUnits.Items.AddChildObject(UnitNode,
          Lang.Translation[trUses], UnitItem.UsesUnits);
        for UsesIndex := 0 to UnitItem.UsesUnits.Count -1 do
        begin
          tvUnits.Items.AddChild(UsesNode, UnitItem.UsesUnits[UsesIndex]);
        end;
      end;
    end;
    if PasDoc1.ConclusionFileName <> '' then
    begin
      tvUnits.Items.AddObject(nil, PasDoc1.ConclusionFileName,
        PasDoc1.Conclusion);
    end;
  finally
    Lang.Free;
  end;
end;

procedure TfrmHelpGenerator.ButtonGenerateDocsClick(Sender: TObject);
var
  Files: TStringList;
  index: integer;
  SortIndex: TSortSetting;
const
  VizGraphImageExtension = 'png';
begin
  if edOutput.Text = '' then
  begin
    Beep;
    MessageDlg('You need to specify the output directory on the "Locations" tab.',
      Dialogs.mtWarning, [mbOK], 0);
    Exit;
  end;
  Screen.Cursor := crHourGlass;
  try
    memoMessages.Clear;
    Update;
    case comboGenerateFormat.ItemIndex of
      0: PasDoc1.Generator := HtmlDocGenerator;
      1: PasDoc1.Generator := HtmlHelpDocGenerator;
      2, 3:
         begin
           PasDoc1.Generator := TexDocGenerator;
           TexDocGenerator.Latex2rtf := (comboGenerateFormat.ItemIndex = 3);

           TexDocGenerator.LatexHead.Clear;
           if rgLineBreakQuality.ItemIndex = 1 then
           begin
             TexDocGenerator.LatexHead.Add('\sloppy');
           end;
           if memoHyphenatedWords.Lines.Count > 0 then
           begin
             TexDocGenerator.LatexHead.Add('\hyphenation{');
             for Index := 0 to memoHyphenatedWords.Lines.Count -1 do
             begin
               TexDocGenerator.LatexHead.Add(memoHyphenatedWords.Lines[Index]);
             end;
             TexDocGenerator.LatexHead.Add('}');
           end;
           
          case comboLatexGraphicsPackage.ItemIndex of
            0: // none
              begin
                // do nothing
              end;
            1: // PDF
              begin
                TexDocGenerator.LatexHead.Add('\usepackage[pdftex]{graphicx}');
              end;
            2: // DVI
              begin
                TexDocGenerator.LatexHead.Add('\usepackage[dvips]{graphicx}');
              end;
          else Assert(False);
          end;

           
         end;
    else
      Assert(False);
    end;
    
    PasDoc1.Generator.Language := TLanguageID(comboLanguages.ItemIndex);

    if PasDoc1.Generator is TGenericHTMLDocGenerator then
    begin
      TGenericHTMLDocGenerator(PasDoc1.Generator).Header := memoHeader.Lines.Text;
      TGenericHTMLDocGenerator(PasDoc1.Generator).Footer := memoFooter.Lines.Text;
      
      if EditCssFileName.Text <> '' then
        TGenericHTMLDocGenerator(PasDoc1.Generator).CSS :=
          FileToString(EditCssFileName.Text) else
        TGenericHTMLDocGenerator(PasDoc1.Generator).CSS := DefaultPasDocCss;
        
      TGenericHTMLDocGenerator(PasDoc1.Generator).UseTipueSearch :=
        CheckUseTipueSearch.Checked;
      TGenericHTMLDocGenerator(PasDoc1.Generator).AspellLanguage := LanguageIdToString(TLanguageID(comboLanguages.ItemIndex));
      TGenericHTMLDocGenerator(PasDoc1.Generator).CheckSpelling := cbCheckSpelling.Checked;
      if cbCheckSpelling.Checked then
      begin
        TGenericHTMLDocGenerator(PasDoc1.Generator).SpellCheckIgnoreWords.Assign(memoSpellCheckingIgnore.Lines);
      end;
    end;

    // Create the output directory if it does not exist.
    if not SysUtils.DirectoryExists(edOutput.Text) then
    begin
      CreateDir(edOutput.Text)
    end;
    PasDoc1.Generator.DestinationDirectory := edOutput.Text;
    
    PasDoc1.Generator.WriteUsesClause := CheckWriteUsesList.Checked;
    PasDoc1.Generator.AutoAbstract := CheckAutoAbstract.Checked;
    PasDoc1.AutoLink := CheckAutoLink.Checked;
    PasDoc1.HandleMacros := CheckHandleMacros.Checked;
    
    PasDoc1.ProjectName := edProjectName.Text;
    PasDoc1.IntroductionFileName := EditIntroductionFileName.Text;
    PasDoc1.ConclusionFileName := EditConclusionFileName.Text;

    { CheckListVisibleMembersClick event *should* already
      take care of setting PasDoc1.ShowVisibilities.
      Unfortunately CheckListVisibleMembersClick is not guarenteed
      to be fired on every change of state of
      CheckListVisibleMembersValue. See Lazarus bug
      [http://www.lazarus.freepascal.org/mantis/view.php?id=905].
      So sometimes user will click on CheckListVisibleMembers
      and Changed will not be updated as it should.
      Below we at least make sure that PasDoc1.ShowVisibilities
      is always updated. }
    PasDoc1.ShowVisibilities := CheckListVisibleMembersValue;

    PasDoc1.ImplicitVisibility :=
      TImplicitVisibility(RadioImplicitVisibility.ItemIndex);

    Files := TStringList.Create;
    try
      Files.AddStrings(memoFiles.Lines);
      PasDoc1.SourceFileNames.Clear;
      PasDoc1.AddSourceFileNames(Files);

      Files.Clear;
      Files.AddStrings(memoIncludeDirectories.Lines);
      PasDoc1.IncludeDirectories.Assign(Files);

      Files.Clear;
      Files.AddStrings(memoDefines.Lines);
      PasDoc1.Directives.Assign(Files);
    finally
      Files.Free;
    end;
    PasDoc1.Verbosity := seVerbosity.Value;
    
    case rgCommentMarkers.ItemIndex of
      0:
        begin
          PasDoc1.CommentMarkers.Clear;
          PasDoc1.MarkerOptional := True;
        end;
      1:
        begin
          PasDoc1.MarkerOptional := True;
          PasDoc1.CommentMarkers.Assign(memoCommentMarkers.Lines);
        end;
      2:
        begin
          PasDoc1.MarkerOptional := False;
          PasDoc1.CommentMarkers.Assign(memoCommentMarkers.Lines);
        end;
    else
      Assert(False);
    end;
    
    if edTitle.Text = '' then begin
      PasDoc1.Title := edProjectName.Text;
    end
    else begin
      PasDoc1.Title := edTitle.Text;
    end;
    
    if cbVizGraphClasses.Checked then begin
      PasDoc1.Generator.OutputGraphVizClassHierarchy := True;
      PasDoc1.Generator.LinkGraphVizClasses := VizGraphImageExtension;
    end
    else begin
      PasDoc1.Generator.OutputGraphVizClassHierarchy := False;
      PasDoc1.Generator.LinkGraphVizClasses := '';
    end;
    
    if cbVizGraphUses.Checked then begin
      PasDoc1.Generator.OutputGraphVizUses := True;
      PasDoc1.Generator.LinkGraphVizUses := VizGraphImageExtension;
    end
    else begin
      PasDoc1.Generator.OutputGraphVizUses := False;
      PasDoc1.Generator.LinkGraphVizUses := '';
    end;
    
    Assert(Ord(High(TSortSetting)) = clbSorting.Items.Count -1);
    PasDoc1.SortSettings := [];
    for SortIndex := Low(TSortSetting) to High(TSortSetting) do
    begin
      if clbSorting.Checked[Ord(SortIndex)] then begin
        PasDoc1.SortSettings := PasDoc1.SortSettings + [SortIndex];
      end;
    end;
    
    MisspelledWords.Clear;
    PasDoc1.OnMessage := PasDocMessages;
    PasDoc1.Execute;
    PasDoc1.OnMessage := nil;

    if MisspelledWords.Count > 0 then
    begin
      memoMessages.Lines.Add('');
      memoMessages.Lines.Add('Misspelled Words');
      memoMessages.Lines.AddStrings(MisspelledWords)
    end;

    FillTreeView;

    if cbVizGraphUses.Checked or cbVizGraphClasses.Checked then begin
      // To do: actually start dot here.
      MessageDlg('You will have to run the GraphViz "dot" program to generate '
        + 'the images used in your documentation.', Dialogs.mtInformation,
        [mbOK], 0);
    end;

    if PasDoc1.Generator is TGenericHTMLDocGenerator then
      WWWBrowserRunner.RunBrowser(
        PasDoc1.Generator.DestinationDirectory + 'index.html');
  finally
    Screen.Cursor := crDefault;
  end;
end;

procedure TfrmHelpGenerator.PasDocMessages(const MessageType: TPasDocMessageType;
 const AMessage: string; const AVerbosity: Cardinal);
begin
  MemoMessages.Lines.Add(AMessage);
end;

procedure TfrmHelpGenerator.LocationsButtonsClick(Sender: TObject);
var
  LEdit: TEdit;
  LDirectory: string;
begin
  LEdit := nil;
  //OpenDialog3.Options := [ofHideReadOnly, ofFileMustExist, ofEnableSizing];
  if Sender = ButtonIntroFileName then
  begin
     OpenDialog3.DefaultExt := '.html';
     OpenDialog3.Filter := 'HTML files *.html|*.html,*.htm|All Files *.*|*.*';
     OpenDialog3.Title  := 'Select a Introduction HTML File';
     LEdit := EditIntroductionFileName;
  end
  else if Sender = ButtonConclusionFileName then
  begin
    OpenDialog3.DefaultExt := '.html';
    OpenDialog3.Filter := 'HTML files *.html|*.html,*.htm|All Files *.*|*.*';
    OpenDialog3.Title  := 'Select a Conclusion HTML File';
    LEdit := EditConclusionFileName;
  end
  else if Sender = ButtonCssFileName then
  begin
    OpenDialog3.DefaultExt := '.css';
    OpenDialog3.Filter := 'Css files *.css|*.css|All Files *.*|*.*';
    OpenDialog3.Title  := 'Select a Cascade Stylesheet File';
    LEdit := EditCssFileName;
  end
  else if Sender = ButtonOutPutPathName then
  begin
    LDirectory := edOutPut.Text;
    if SelectDirectory('Select output directory', LDirectory, LDirectory) then
        edOutPut.Text := LDirectory;
  end;

  if Assigned(LEdit) and OpenDialog3.Execute then
      LEdit.Text := OpenDialog3.FileName;
end;

procedure TfrmHelpGenerator.comboLanguagesChange(Sender: TObject);
begin
  CheckIfSpellCheckingAvailable;
  SChanged := True;
end;

procedure TfrmHelpGenerator.btnBrowseIncludeDirectoryClick(Sender: TObject);
var
  directory: string;
begin
  if memoIncludeDirectories.Lines.Count > 0 then
  begin
    directory := memoIncludeDirectories.Lines[
      memoIncludeDirectories.Lines.Count - 1];
  end
  else
  begin
    directory := '';
  end;

  if SelectDirectory('Select directory to include', '', directory)
    then
  begin
    if memoIncludeDirectories.Lines.IndexOf(directory) < 0 then
    begin
      memoIncludeDirectories.Lines.Add(directory);
    end
    else
    begin
      MessageDlg('The directory you selected, (' + directory
        + ') is already included.', Dialogs.mtInformation, [mbOK], 0);
    end;
  end;
end;

procedure TfrmHelpGenerator.LoadSettings;
var
  Ini: TIniFile;

  procedure ReadStrings(const Section: string; S: TStrings);
  var i: Integer;
  begin
    S.Clear;
    for i := 0 to Ini.ReadInteger(Section, 'Count', 0) - 1 do
      S.Append(Ini.ReadString(Section, 'Item_' + IntToStr(i), ''));
  end;

  { When reading any filename from Ini file, we make sure
    that it's an absolute filename. This is needed to
    properly handle the case when user choses "Save As"
    and stores the same project within a different directory.
    So it's safest to always keep absolute filenames
    when project is loaded in pasdoc_gui.

    Below are some helper wrappers around ExpandFileName
    that help us with this. }

  { This returns '' if FileName is '', else returns
    ExpandFileName(FileName). It's useful because often
    FileName = '' has special meaning:
    it means that "given filename was not chosen by user",
    so calling ExpandFileName is not wanted in this case. }
  function ExpandNotEmptyFileName(const FileName: string): string;
  begin
    if FileName = '' then
      Result := '' else
      Result := ExpandFileName(FileName);
  end;

  { Call ExpandNotEmptyFileName on each item. }
  procedure ExpandFileNames(List: TStrings);
  var
    I: Integer;
  begin
    for I := 0 to List.Count - 1 do
      List[I] := ExpandNotEmptyFileName(List[I]);
  end;

var
  i: Integer;
  SettingsFileNamePath: string;
  LanguageSyntax: string;
  LanguageId: TLanguageID;
begin
  if not SaveChanges then Exit;
    SaveDialog1.FileName := SettingsFileName;
    { Change current directory now to SettingsFileNamePath,
      this is needed to make all subsequent ExpandFileName
      operations work with respect to SettingsFileNamePath. }
    SettingsFileNamePath := ExtractFilePath(SettingsFileName);
    if not SetCurrentDir(SettingsFileNamePath) then
      raise Exception.CreateFmt('Cannot change current directory to "%s"',
        [SettingsFileNamePath]);

    Ini := TIniFile.Create(SettingsFileName);
    try
      { Default values for ReadXxx() methods here are not so important,
        don't even try to set them right.
        *Good* default values are set in SetDefaults method of this class.
        Here we can assume that values are always present in ini file.

        Well, OK, in case user will modify settings file by hand we should
        set here some sensible default values... also in case we will add
        in the future some new values to this file...
        so actually we should set here sensible "default values".
        We can think of them as "good default values for user opening a settings
        file written by older version of pasdoc_gui program".
        They need not necessarily be equal to default values set by
        SetDefaults method, and this is very good, as it may give us
        additional possibilities. }

      CheckStoreRelativePaths.Checked :=
        Ini.ReadBool('Main', 'StoreRelativePaths', true);

      { Compatibility: in version < 0.11.0, we stored only the "id" (just an
        index to LANGUAGE_ARRAY) of the language. This was very wrong, as the
        id can change between pasdoc releases (items can get shifted and moved
        in the LANGUAGE_ARRAY). So now we store language "syntax" code
        (the same thing as is used for --language command-line option),
        as this is guaranteed to stay "stable".

        To do something mildly sensible when opening pds files from older
        versions, we set language to default (English) when language string
        is not recognized. }

      LanguageSyntax := Ini.ReadString('Main', 'Language',
        LanguageDescriptor(DEFAULT_LANGUAGE)^.Syntax);
      if not LanguageFromStr(LanguageSyntax, LanguageId) then
        LanguageId := DEFAULT_LANGUAGE;
      comboLanguages.ItemIndex := Ord(LanguageId);
      comboLanguagesChange(nil);

      edOutput.Text := ExpandNotEmptyFileName(
        Ini.ReadString('Main', 'OutputDir', ''));

      comboGenerateFormat.ItemIndex := Ini.ReadInteger('Main', 'GenerateFormat', 0);
      comboGenerateFormatChange(nil);

      edProjectName.Text := Ini.ReadString('Main', 'ProjectName', '');
      seVerbosity.Value := Ini.ReadInteger('Main', 'Verbosity', 0);

      Assert(Ord(High(TVisibility)) = CheckListVisibleMembers.Items.Count -1);
      for i := Ord(Low(TVisibility)) to Ord(High(TVisibility)) do
        CheckListVisibleMembers.Checked[i] := Ini.ReadBool(
          'Main', 'ClassMembers_' + IntToStr(i), true);
      CheckListVisibleMembersClick(nil);

      RadioImplicitVisibility.ItemIndex :=
        Ini.ReadInteger('Main', 'ImplicitVisibility', 0);

      Assert(Ord(High(TSortSetting)) = clbSorting.Items.Count -1);
      for i := Ord(Low(TSortSetting)) to Ord(High(TSortSetting)) do
      begin
        clbSorting.Checked[i] := Ini.ReadBool(
          'Main', 'Sorting_' + IntToStr(i), True);
      end;

      ReadStrings('Defines', memoDefines.Lines);
      ReadStrings('Header', memoHeader.Lines);
      ReadStrings('Footer', memoFooter.Lines);

      ReadStrings('IncludeDirectories', memoIncludeDirectories.Lines);
      ExpandFileNames(memoIncludeDirectories.Lines);

      ReadStrings('Files', memoFiles.Lines);
      ExpandFileNames(memoFiles.Lines);

      EditCssFileName.Text := ExpandNotEmptyFileName(
        Ini.ReadString('Main', 'CssFileName', ''));
      EditIntroductionFileName.Text := ExpandNotEmptyFileName(
        Ini.ReadString('Main', 'IntroductionFileName', ''));
      EditConclusionFileName.Text := ExpandNotEmptyFileName(
        Ini.ReadString('Main', 'ConclusionFileName', ''));
      CheckWriteUsesList.Checked := Ini.ReadBool('Main', 'WriteUsesList', false);
      CheckAutoAbstract.Checked := Ini.ReadBool('Main', 'AutoAbstract', false);
      CheckAutoLink.Checked := Ini.ReadBool('Main', 'AutoLink', false);
      CheckHandleMacros.Checked := Ini.ReadBool('Main', 'HandleMacros', true);
      CheckUseTipueSearch.Checked := Ini.ReadBool('Main', 'UseTipueSearch', false);
      rgLineBreakQuality.ItemIndex := Ini.ReadInteger('Main', 'LineBreakQuality', 0);
      ReadStrings('HyphenatedWords', memoHyphenatedWords.Lines);
      rgCommentMarkers.ItemIndex := Ini.ReadInteger('Main', 'SpecialMarkerTreatment', 1);
      ReadStrings('SpecialMarkers', memoCommentMarkers.Lines);
      edTitle.Text := Ini.ReadString('Main', 'Title', '');
      cbVizGraphClasses.Checked := Ini.ReadBool('Main', 'VizGraphClasses', false);
      cbVizGraphUses.Checked := Ini.ReadBool('Main', 'VizGraphUses', false);

      cbCheckSpelling.Checked :=
        Ini.ReadBool('Main', 'CheckSpelling', false);
      comboLatexGraphicsPackage.ItemIndex :=
        Ini.ReadInteger('Main', 'LatexGraphicsPackage', 0);

      ReadStrings('IgnoreWords', memoSpellCheckingIgnore.Lines);
    finally Ini.Free end;

    SChanged := False;

end;

procedure TfrmHelpGenerator.btnOpenClick(Sender: TObject);
begin
  if not SaveChanges then Exit;
  if OpenDialog2.Execute then
  begin
    SettingsFileName := OpenDialog2.FileName;
    LoadSettings;
  end;
end;

procedure TfrmHelpGenerator.SaveSettingsToFile(const FileName: string;
  SetSettingsFileName, ClearChanged: boolean);
var
  Ini: TIniFile;

  procedure WriteStrings(const Section: string; S: TStrings);
  var
    i: Integer;
  begin
    { It's not really necessary for correctness but it's nice to protect
      user privacy by removing trash data from file (in case previous
      value of S had larger Count). }
    Ini.EraseSection(Section);

    Ini.WriteInteger(Section, 'Count', S.Count);
    for i := 0 to S.Count - 1 do
      Ini.WriteString(Section, 'Item_' + IntToStr(i), S[i]);
  end;

  { If CheckStoreRelativePaths.Checked and FileNameToCorrect <> '',
    this returns relative filename (with respect to
    directory where FileName is stored), else returns just
    FileNameToCorrect. }
  function CorrectFileName(const FileNameToCorrect: string): string;
  begin
    if CheckStoreRelativePaths.Checked and (FileNameToCorrect <> '') then
      Result := ExtractRelativepath(FileName, FileNameToCorrect) else
      Result := FileNameToCorrect;
  end;

  { Modified version of WriteStrings that always write
    CorrectFileName(S[I]) instead of just S[I]. }
  procedure WriteFileNames(const Section: string; S: TStrings);
  var
    i: Integer;
  begin
    { It's not really necessary for correctness but it's nice to protect
      user privacy by removing trash data from file (in case previous
      value of S had larger Count). }
    Ini.EraseSection(Section);

    Ini.WriteInteger(Section, 'Count', S.Count);
    for i := 0 to S.Count - 1 do
      Ini.WriteString(Section, 'Item_' + IntToStr(i),
        CorrectFileName(S[i]));
  end;

var
  i: Integer;
begin
  Ini := TIniFile.Create(FileName);
  try
    Ini.WriteBool('Main', 'StoreRelativePaths', CheckStoreRelativePaths.Checked);

    Ini.WriteString('Main', 'Language',
      LanguageDescriptor(TLanguageID(comboLanguages.ItemIndex))^.Syntax);
    Ini.WriteString('Main', 'OutputDir', CorrectFileName(edOutput.Text));
    Ini.WriteInteger('Main', 'GenerateFormat', comboGenerateFormat.ItemIndex);
    Ini.WriteString('Main', 'ProjectName', edProjectName.Text);
    Ini.WriteInteger('Main', 'Verbosity', seVerbosity.Value);

    for i := Ord(Low(TVisibility)) to Ord(High(TVisibility)) do
      Ini.WriteBool('Main', 'ClassMembers_' + IntToStr(i),
        CheckListVisibleMembers.Checked[i]);

    Ini.WriteInteger('Main', 'ImplicitVisibility',
      RadioImplicitVisibility.ItemIndex);

    for i := Ord(Low(TSortSetting)) to Ord(High(TSortSetting)) do
    begin
      Ini.WriteBool('Main', 'Sorting_' + IntToStr(i),
        clbSorting.Checked[i]);
    end;

    WriteStrings('Defines', memoDefines.Lines);
    WriteStrings('Header', memoHeader.Lines);
    WriteStrings('Footer', memoFooter.Lines);
    WriteFileNames('IncludeDirectories', memoIncludeDirectories.Lines);
    WriteFileNames('Files', memoFiles.Lines);

    Ini.WriteString('Main', 'CssFileName', CorrectFileName(
      EditCssFileName.Text));
    Ini.WriteString('Main', 'IntroductionFileName', CorrectFileName(
      EditIntroductionFileName.Text));
    Ini.WriteString('Main', 'ConclusionFileName', CorrectFileName(
      EditConclusionFileName.Text));
    Ini.WriteBool('Main', 'WriteUsesList', CheckWriteUsesList.Checked);
    Ini.WriteBool('Main', 'AutoAbstract', CheckAutoAbstract.Checked);
    Ini.WriteBool('Main', 'AutoLink', CheckAutoLink.Checked);
    Ini.WriteBool('Main', 'HandleMacros', CheckHandleMacros.Checked);
    Ini.WriteBool('Main', 'UseTipueSearch', CheckUseTipueSearch.Checked);
    Ini.WriteInteger('Main', 'LineBreakQuality', rgLineBreakQuality.ItemIndex);
    WriteStrings('HyphenatedWords', memoHyphenatedWords.Lines);
    Ini.WriteInteger('Main', 'SpecialMarkerTreatment', rgCommentMarkers.ItemIndex);
    WriteStrings('SpecialMarkers', memoCommentMarkers.Lines);
    Ini.WriteString('Main', 'Title', edTitle.Text);

    Ini.WriteBool('Main', 'VizGraphClasses', cbVizGraphClasses.Checked);
    Ini.WriteBool('Main', 'VizGraphUses', cbVizGraphUses.Checked);

    Ini.WriteBool('Main', 'CheckSpelling', cbCheckSpelling.Checked);
    Ini.WriteInteger('Main', 'LatexGraphicsPackage', comboLatexGraphicsPackage.ItemIndex);
    WriteStrings('IgnoreWords', memoSpellCheckingIgnore.Lines);

    Ini.UpdateFile;

  finally Ini.Free end;
  
  if SetSettingsFileName then
  begin
    SettingsFileName := FileName;
    IniFile.WriteString('Main', 'LastProject', SettingsFileName);
  end;

  if ClearChanged then
    SChanged := false;
end;

procedure TfrmHelpGenerator.MenuSaveAsClick(Sender: TObject);
begin
  if SaveDialog1.Execute then
    SaveSettingsToFile(SaveDialog1.FileName, true, true);
end;

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

function TfrmHelpGenerator.SaveChanges: boolean;
var
  MessageResult: integer;
begin
  Result := true;
  if SChanged then
  begin
    MessageResult := MessageDlg(
      Format('Project "%s" was modified. ' +
        'Do you want to save it now ?', [SettingsFileNameNice]),
      Dialogs.mtInformation, [mbYes, mbNo, mbCancel], 0);
    case MessageResult of
      mrYes:
        begin
          MenuSaveClick(MenuSave);
        end;
      mrNo:
        begin
          // do nothing.
        end;
    else
      Result := false;
    end;
  end;
end;

procedure TfrmHelpGenerator.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  if not SaveChanges then
    Action := caNone;
end;

procedure TfrmHelpGenerator.MenuNewClick(Sender: TObject);
begin
  if not SaveChanges then Exit;

  SetDefaults;

  SettingsFileName := '';

  SChanged := False;
end;

procedure TfrmHelpGenerator.comboGenerateFormatChange(Sender: TObject);

  { With WinAPI interface, this is useful to give user indication of
    Edit.Enabled state. Other WinAPI programs also do this.
    With other widgetsets, like GTK, this is not needed, Lazarus + GTK
    already handle such things (e.g. edit boxes have automatically
    slightly dimmed background when they are disabled). }
 (* procedure SetColorFromEnabled(Edit: TFileNameEdit); overload;
  begin
    {$ifdef WIN32}
    if Edit.Enabled then
      Edit.Color := clWindow else
      Edit.Color := clBtnFace;
    {$endif}
  end;
  *)
  procedure SetColorFromEnabled(Edit: TEdit); overload;
  begin
    {$ifdef WIN32}
    if Edit.Enabled then
      Edit.Color := clWindow else
      Edit.Color := clBtnFace;
    {$endif}
  end;

begin
  CheckUseTipueSearch.Enabled := comboGenerateFormat.ItemIndex = 0;
  PageHeadFoot.Tag := Ord(comboGenerateFormat.ItemIndex in [0,1]);
  PageLatexOptions.Tag := Ord(comboGenerateFormat.ItemIndex in [2,3]);
  
  edProjectName.Enabled := comboGenerateFormat.ItemIndex <> 0;
  SetColorFromEnabled(edProjectName);
  
  EditCssFileName.Enabled := comboGenerateFormat.ItemIndex in [0,1];
  SetColorFromEnabled(EditCssFileName);
  
  comboLatexGraphicsPackage.Enabled := comboGenerateFormat.ItemIndex in [2,3];
  FillNavigationListBox;
  SChanged := true;
end;

procedure TfrmHelpGenerator.lbNavigationClick(Sender: TObject);
var
  Page: TPage;
begin
  if lbNavigation.ItemIndex = -1 then Exit;
  
  Page := lbNavigation.Items.Objects[lbNavigation.ItemIndex] as TPage;
    
  NotebookMain.PageIndex := NotebookMain.Pages.IndexOfObject(Page);
end;

procedure TfrmHelpGenerator.MenuContextHelpClick(Sender: TObject);
var
  HelpControl: TControl;
begin
  HelpControl := nil;
  if (Sender is TMenuItem) or (Sender = lbNavigation) then
  begin
    HelpControl := TPage(NotebookMain.Pages.Objects[NotebookMain.PageIndex]);
    GetHelpControl(HelpControl, HelpControl);
  end
  else if (Sender is TControl) then
  begin
    GetHelpControl(TControl(Sender), HelpControl);
  end;
  
  if HelpControl <> nil then
  begin
    Assert(HelpControl.HelpType = htKeyword);
    WWWBrowserRunner.RunBrowser(
      WWWHelpServer + HelpControl.HelpKeyword);
  end;
end;

procedure TfrmHelpGenerator.MenuGenerateRunClick(Sender: TObject);
begin
  { Switch to "Generate" page }
  lbNavigation.ItemIndex := lbNavigation.Items.IndexOfObject(pageGenerate);
  lbNavigationClick(nil);
  
  ButtonGenerateDocsClick(nil);
end;

procedure TfrmHelpGenerator.MenuPreferencesClick(Sender: TObject);
begin
  TPreferences.Execute;
end;

procedure TfrmHelpGenerator.MenuSaveClick(Sender: TObject);
begin
  if SettingsFileName = '' then
    MenuSaveAsClick(nil)
  else
    SaveSettingsToFile(SettingsFileName, true, true);
end;

procedure TfrmHelpGenerator.rgCommentMarkersClick(Sender: TObject);
begin
  SChanged := True;
  memoCommentMarkers.Enabled := (rgCommentMarkers.ItemIndex >= 1);
  if memoCommentMarkers.Enabled then begin
    memoCommentMarkers.Color := clWindow;
  end
  else begin
    memoCommentMarkers.Color := clBtnFace;
  end;
end;

procedure TfrmHelpGenerator.tvUnitsClick(Sender: TObject);
var
  Item: TBaseItem;
begin
  seComment.Lines.Clear;
  seComment.Hint := '';
  if (tvUnits.Selected <> nil) and (tvUnits.Selected.Data <> nil) then
  begin
    if TObject(tvUnits.Selected.Data) is TBaseItem then
    begin
      Item := TBaseItem(tvUnits.Selected.Data);
      seComment.Lines.Text := Item.RawDescription;
      seComment.Hint := Format(
        'Comment in stream "%s", on position %d - %d',
        [ Item.RawDescriptionInfo.StreamName,
          Item.RawDescriptionInfo.BeginPosition,
          Item.RawDescriptionInfo.EndPosition ]);
    end;
  end;
end;

function TfrmHelpGenerator.GetCheckListVisibleMembersValue: TVisibilities;
var
  V: TVisibility;
begin
  Result := [];
  for V := Low(V) to High(V) do
  begin
    if CheckListVisibleMembers.Checked[Ord(V)] then
      Include(Result, V);
  end;
end;

procedure TfrmHelpGenerator.SetCheckListVisibleMembersValue(
  const AValue: TVisibilities);
var
  V: TVisibility;
begin
  for V := Low(V) to High(V) do
    CheckListVisibleMembers.Checked[Ord(V)] := V in AValue;
end;

procedure TfrmHelpGenerator.CreateWnd;
begin
  InsideCreateWnd := true;
  try
    inherited;
  finally
    InsideCreateWnd := false;
  end;
end;

function TfrmHelpGenerator.SettingsFileNameNice: string;
begin
  if SettingsFileName = '' then
    Result := 'Unsaved PasDoc settings' else
    Result := ExtractFileName(SettingsFileName);
end;

end.
