package osa;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
/**
 * Класс, создающий на экране главное окно программы. Интерфейс ActionListener
 * реализуется для использования таймера при загрузке из командной строки
 * файла с проектом.
*/
public class MainFrame extends JFrame implements ActionListener
{
  /**
   * Указатель на класс Wasp, через который затем все остальные классы смогут
   * обращаться к его функциям.
  */
  public Wasp mWasp;

  private JLayeredPane layeredPane;
  private JPanel contentPane;
  private int contentPaneLayer;
  private JPanel glassPane;
  private static final int glassPaneLayerDifference = 1;

  private boolean GlassPaneVisible = false;
  public boolean isGlassPaneVisible()
  { return(GlassPaneVisible); }
  public void setGlassPaneVisible(boolean glassPaneVisible)
  { GlassPaneVisible = glassPaneVisible;
    SwingUtilities.invokeLater(new classGlassPaneVisible(glassPaneVisible));
  }
  private class classGlassPaneVisible implements Runnable
  { boolean glassPaneVisible;
    classGlassPaneVisible(boolean glassPaneVisible)
    { this.glassPaneVisible = glassPaneVisible; }
    public void run()
    { if(glassPaneVisible)
        layeredPane.setLayer(glassPane,contentPaneLayer+glassPaneLayerDifference);
      else layeredPane.setLayer(glassPane,contentPaneLayer-glassPaneLayerDifference);
    }
  }
  public JPanel glassPane()
  { return(glassPane); }

  private TopFrame topFrame = null;
  /**
   * Меню главного окна.
  */
  public JMenuBar mMenuBar = new JMenuBar();
  /**
   * Панель инструментов главного окна.
  */
  public JToolBar mToolBar = new JToolBar("Toolbar");
  /**
   * Двойная панель, используемая для разделения окна на две части с
   * возможностью динамически менять содержимое верхней и нижней частей.
  */
  public JSplitPane mSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,true);
  /**
   * Элемент GUI, который используется для создания вкладок с разными данными.
  */
  public JTabbedPane mTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);

  /**
   * Панель-контейнер, находящаяся на вкладке "Грамматика".
  */
  public JPanel mPanel1 = new JPanel();
  private Tab1 mTab1;
  /**
   * Панель-контейнер, находящаяся на вкладке "Правила".
  */
  public JPanel mPanel2 = new JPanel();
  private Tab2 mTab2;
  /**
   * Панель-контейнер, находящаяся на вкладке "Словари".
  */
  public JPanel mPanel3 = new JPanel();
  private Tab3 mTab3;
  /**
   * Панель-контейнер, находящаяся на вкладке "Цепочки".
  */
  public JPanel mPanel4 = new JPanel();
  private Tab4 mTab4;
  private Tab4A mTab4A;
  private Tab4B mTab4B;
  /**
   * Панель-контейнер, находящаяся на вкладке "Функции Выбора".
  */
  public JPanel mPanel5 = new JPanel();
  private Tab5 mTab5;
  private Tab5A mTab5A;
  /**
   * Панель-контейнер, находящаяся на вкладке "Функции Переходов".
  */
  public JPanel mPanel6 = new JPanel();
  private Tab6 mTab6;
  private Tab6A mTab6A;
  /**
   * Панель-контейнер, находящаяся на вкладке "Распознаватель".
  */
  public JPanel mPanel7 = new JPanel();
  private Tab7 mTab7;
  /**
   * Панель-контейнер, находящаяся на вкладке "Преобразователь".
  */
  public JPanel mPanel8 = new JPanel();





  private String MainFrameTitle = new String("OSA 3.0b Java Application Edition");
  private File ProjectFile = null;
  private File arg = null;
  /**
   * Строка с секретным ключевым словом для доступа к окну с настройками.
  */
  public String ps = null;
  /**
   * Флаг, имеющий значение true, если нужно открывать второе окно, когда
   * используется обе части окна.
  */
  public boolean ShowTop = false;
  private int CurrentIndex = -1;
  /**
   * Флаг, имеющий значение true, когда есть компонент в нижней части окна.
  */
  public boolean isBottomComponentActive = false;
  /**
   * Индекс компонента в нижней части окна.
  */
  public int CurrentBottomIndex = -1;
  /**
   * Индекс компонента в верхней части окна в момент добавления компонента в
   * нижнюю часть окна.
  */
  public int TopComponentIndex = -1;
  /**
   * Флаг, имеющий значение true, если нужно создать новую цепочку или список
   * функций переходов. Если он - false, то выполняется редактирование.
  */
  public boolean isNew=true;
  /**
   * Указатель на объект типа Font,
  */
  public Font mFont = null;
  private javax.swing.Timer timer = new javax.swing.Timer(0x400,this);
  /**
   * Счетчик количества элементов в стеке отката в момент сохранения проекта с
   * грамматикой.
  */
  public int reSaveMainTextAreaCounter;
  /**
   * Флаг, который имеет значение true, если после последнего сохранения
   * грамматика изменялась.
  */
  public boolean reSaveMainTextArea;

  //Analyse
  /**
   * Массив строк текста грамматики из редактора
  */
  public ArrayList alMainTextArea = new ArrayList(0x400);
  /**
   * Результат работы функции Analyse. Возможны следующие значения:
   * 0 - состояние грамматики определить не удалось; 1 - в грамматике обнаружены
   * синтаксические ошибки; 2 - граматика не приведена; 3 - найдены конфликты
   * (грамматика не LL(1)); 4 - все в порядке.
  */
  public int AnalyseReturn;
  /**
   * Счетчик количества элементов в стеке отката в момент выполнения функции
   * Analyse.
  */
  public int reAnalyseCounter;
  /**
   * Флаг, который имеет значение true, если грамматика менялась с момента
   * последнего вызова функции Analyse.
  */
  public boolean reAnalyse;
  /**
   * Массив для правил грамматики и синтаксических ошибок.
  */
  public ArrayList alRuleList = new ArrayList(0x400);
  /**
   * Массив для нетерминалов и непроизводящих нетерминалов.
  */
  public ArrayList alNonTerminalList = new ArrayList(0x200);
  /**
   * Массив для терминалов и недостижимых символов.
  */
  public ArrayList alTerminalList = new ArrayList(0x200);
  /**
   * Флаг, который имеет значение true, если список правил нужно обновить.
  */
  public boolean reListRule;
  /**
   *  Флаг, который имеет значение true, если список терминалов нужно обновить.
  */
  public boolean reListTerminal;
  /**
   * Флаг, который имеет значение true, если список функций выбора
   * нужно обновить.
  */
  public boolean reListSelection;
  /**
   * Флаг, который имеет значение true, если список тактов распознавателя и
   * преобразователя нужно обновить.
  */
  public boolean reListFunctions;
  /**
   * Адаптер для функции Analyse из класса Wasp.
  */
  public void Analyse()//Adapter
  { TextAreaToArrayList(mTab1.t1TextArea,alMainTextArea,false); mWasp.Analyse();
    alMainTextArea.clear();
    reAnalyseCounter=mTab1.t1UndoStack.size(); mTab1.t1UndoableEditUsed();
    reListRule=true; reListTerminal=true;
    reListSelection=true; reListFunctions=true; }
  //Chains
  /**
   * Массив строк с названиями цепочек.
  */
  public ArrayList alChainName = new ArrayList(0x10);
  /**
   * Массив с массивами строк текста цепочек.
  */
  public ArrayList alChainText = new ArrayList(0x10);
  /**
   * Флаг, который имеет значение true, если цепочки изменились.
  */
  public boolean reSaveChains;
  /**
   * Индекс выделенной пользователем цепочки в списке цепочек.
  */
  public int CurrentChainIndex;
  //Chain Generate
  /**
   * Массив строк текста цепочки на текущем шаге генерации цепочки.
  */
  public volatile ArrayList alCurrentChain = new ArrayList(0x08);
  /**
   * Длина цепочки на текущем шаге генерации цепочки.
  */
  public volatile int ccLength=0;
  /**
   * Номер строки текста цепочки, где находится выделенный участок текста.
  */
  public volatile int sLine=-1;
  /**
   * Номер символа в строке, где начинается выделенный участок текста.
  */
  public volatile int sStart=-1;
  /**
   * Длина выделенного участка текста.
  */
  public volatile int sLength=-1;
  /**
   * Массив с альтернативными правилами на данном шаге генерации цепочки.
  */
  public volatile ArrayList alCurrentRules = new ArrayList(0x10);
  /**
   * Номер выбранного правила-альтернативы на текущем шаге генерации цепочки.
  */
  public volatile int SelectedRuleNumber=-1;
  /**
   * Номер текущего шага генерации цепочки.
  */
  public volatile int CurrentStepNumber=-1;
  /**
   * Значение ползунка, управляющего скоростью генерации цепочки.
  */
  public volatile int SliderValue=0;
  /**
   * Флаг, который имеет значение true, если нужно использовать правый вывод.
  */
  public volatile boolean RightStyle=false;
  //Selection
  /**
   * Массив массивов индексов терминалов для правил
  */
  public ArrayList alSelection = new ArrayList(0x200);
  /**
   * Флаг, который имеет значение true, если в данный момент включен режим
   * тренировки нахождения функций переходов.
  */
  public boolean SelectionSecurity=false;
  /**
   * Число правильных функций выбора в % в режиме тестирования знаний правил
   * построения функций выбора.
  */
  public int sk = 0;//Число Правильных В %
  //Functions
  /**
   * Массив строк с названиями наборов функций переходов.
  */
  public ArrayList alFunctionName = new ArrayList(0x10);
  /**
   * Массив массивов со строками наборов функций переходов.
  */
  public ArrayList alFunctionText = new ArrayList(0x80);
  /**
   * Флаг, который имеет значение true, если функции выбора изменились и нужно
   * следить за тем, чтобы они были сохранены.
  */
  public boolean reSaveFunctions;
  /**
   * Индекс (номер) набора функций переходов, который в данный момент выбран
   * пользователем.
  */
  public int CurrentFunctionIndex;
  /**
   * Массив строк, в который заносятся сгенерированные функции переходов или
   * список конфликтов грамматики.
  */
  public ArrayList alGeneratedFinctions = new ArrayList(0x100);
  /**
   * Число генерируемых по запросу пользователей функций переходов в %.
  */
  public int fk = 0;//Число Сгенерированных В %
  //Translator
  /**
   * Результат работы функции Translate из класса Wasp. Возможны значения:
   * 0 - не удалось обработать входные данные; 1 - в наборе функций переходов
   * обнаружены синтаксические ошибки; 2 - функция переходов не определена;
   * 3 - транслятор отработал успешно.
  */
  public int TranslateReturn;
  /**
   * Флаг, который имеет значение true, если входные данные для транслятора
   * изменились (цепочка и функции переходов).
  */
  public boolean reTranslate;
  /**
   * Флаг, который имеет значение true, если нужно обновить данные на вкладке
   * транслятора (распознаватель и преобразователь).
  */
  public boolean reListTranslator;
  /**
   * Массив строк входной цепочки для выбранного такта работы транслятора.
  */
  public ArrayList alInputChain = new ArrayList(0x10);
  /**
   * Массив строк выходной цепочки для выбранного такта работы транслятора.
  */
  public ArrayList alOutputChain = new ArrayList(0x10);
  /**
   * Массив строк в магазине для выбранного такта работы транслятора.
  */
  public ArrayList alTStack = new ArrayList(0x100);
  /**
   * Функция перехода для выбранного такта работы транслятора.
  */
  public String TFunction="";
  /**
   * Состояние транслятора для выбранного такта работы транслятора.
  */
  public String TState="";
  /**
   * Число тактов в трансляторе.
  */
  public int TStepQ=0;
  /**
   * Массив строк, в который заносится протокол работы транслятора перед
   * сохранением его в файле.
  */
  public ArrayList alTLog = new ArrayList(0x1000);
  /**
   * Адаптер для функции Translate из класса Wasp.
  */
  public void Translate()//Adapter
  { mWasp.Translate((ArrayList)alChainText.get(CurrentChainIndex),
      (ArrayList)alFunctionText.get(CurrentFunctionIndex));
    reTranslate=false; reListTranslator=true; }
  /**
   * Конструктор класса.
   * @param args параметры командной строки.
  */
  public MainFrame(String args[])
  { mWasp = new Wasp(this);
    try
    {
      enableEvents(AWTEvent.WINDOW_EVENT_MASK);
      super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      Dimension sSize = Toolkit.getDefaultToolkit().getScreenSize();
      Insets insets = Toolkit.getDefaultToolkit()
          .getScreenInsets(getGraphicsConfiguration());
      setLocation(insets.right,insets.top);
      setSize((int)sSize.getWidth()-insets.left-insets.right,
              (int)sSize.getHeight()-insets.top-insets.bottom);
      setIconImage(Toolkit.getDefaultToolkit().createImage(MainFrame.class.getResource("osa.gif")));
      setTitle(MainFrameTitle+" - New Project");
      topFrame = new TopFrame(this);
      if(sSize.height<752) mFont = new Font("Monospaced",Font.PLAIN,13);//800x600
      else if(sSize.height<848) mFont = new Font("Monospaced",Font.PLAIN,16);//1024x768
      else if(sSize.height<1008) mFont = new Font("Monospaced",Font.PLAIN,19);//1152x864
      else if(sSize.height<1184) mFont = new Font("Monospaced",Font.PLAIN,22);//1280x1024
      else mFont = new Font("Monospaced",Font.PLAIN,25);//1600x1200
      setJMenuBar(mMenuBar);
      contentPane = (JPanel)getContentPane();
      contentPane.setLayout(new BorderLayout());
      layeredPane = getLayeredPane();
      contentPaneLayer = layeredPane.getLayer(contentPane);
      glassPane = new JPanel(null);
      glassPane.setOpaque(false);
      layeredPane.add(glassPane,
                      new Integer(contentPaneLayer - glassPaneLayerDifference));

      contentPane.add(mToolBar,BorderLayout.NORTH);
      mToolBar.setFloatable(false);
      contentPane.add(mSplitPane,BorderLayout.CENTER);
      editor.EditorUI.putUIClassID();
      mSplitPane.setDividerSize(12);
      mSplitPane.setOneTouchExpandable(true);
      mSplitPane.setTopComponent(mTabbedPane);
      mTabbedPane.addTab("Грамматика",mPanel1);
      mTab1 = new Tab1(this);
      mTabbedPane.addTab("Правила",mPanel2);
      mTab2 = new Tab2(this);
      mTabbedPane.addTab("Словари",mPanel3);
      mTab3 = new Tab3(this);
      mTabbedPane.addTab("Цепочки",mPanel4);
      mTab4 = new Tab4(this);
      mTab4A = new Tab4A(this,mTab4);
      mTab4B = new Tab4B(this,mTab4);
      mTabbedPane.addTab("Функции Выбора",mPanel5);
      mTab5 = new Tab5(this);
      mTab5A = new Tab5A(this);
      mTabbedPane.addTab("Функции Переходов",mPanel6);
      mTab6 = new Tab6(this);
      mTab6A = new Tab6A(this,mTab6);
      mTabbedPane.addTab("Распознаватель",mPanel7);
      mTabbedPane.addTab("Преобразователь",mPanel8);
      mTab7 = new Tab7(this);


// lopata smejacco!!! nah!
// begin MMV tabs!
    mTabbedPane.addTab("Эксперимент",new osa.tabs.MyTab(this));
// end   MMV tabs!




      mTabbedPane.addChangeListener(new ChangeListener()
      { public void stateChanged(ChangeEvent e)
        { if(e.getSource()==mTabbedPane) mTabbedPaneChangeEvent(); }});
      mTabbedPaneChangeEvent();
      validate();
      setVisible(true);
      // setExtendedState(MAXIMIZED_BOTH);
      // Вызов метода должен осуществляться после того как окну заданы размеры
      // и оно стало видимым - в противном случае ничего не получится.
      InputStream is=MainFrame.class.getResourceAsStream("osa.cfg");
      if(is==null) throw new Exception();
      BufferedReader br
        = new BufferedReader(new InputStreamReader(is,"UTF-16"),0x100);
      if(br==null) throw new Exception();
      String rs="",ts="",rl,kw=""; boolean ba[]; int i,j,k,n,q,bi,ski,fki,crc;
      while((rl=br.readLine())!=null) ts+=rl; rl=null; br.close();
      q=ts.length(); for(i=0;i<q;++i) if((i%9)==3) rs+=ts.charAt(i); ts=null;
      rs=rs.toLowerCase(); q=rs.length();
      ba=new boolean[q]; bi=0; for(i=0;i<q;++i)
      { k=((int)rs.charAt(i))-'a'; if((k<0)||(k>25)) throw new Exception();
        ba[bi]=false; if((k%2)!=0) ba[bi]=true; ++bi; }
      rs=null; k=q-3*32; n=k/16; if((k<0)||(n*16!=k)) throw new Exception();
      bi=0; for(i=0;i<n;++i)
      { k=0; for(j=0;j<16;++j) { if(ba[bi]) k|=1<<j; ++bi; } kw+=(char)k; }
      ski=0; for(j=0;j<32;++j) { if(ba[bi]) ski|=1<<j; ++bi; }
      fki=0; for(j=0;j<32;++j) { if(ba[bi]) fki|=1<<j; ++bi; }
      crc=0; for(j=0;j<32;++j) { if(ba[bi]) crc|=1<<j; ++bi; }
      ba=null; q=kw.length(); n=(q+ski+fki+1367)*11;
      for(i=0;i<q;++i) n+=(int)kw.charAt(i);
      if(n!=crc) throw new Exception();
      if((ski<0)||(ski>100)) throw new Exception();
      if((fki<0)||(fki>100)) throw new Exception();
      ps=kw; sk=ski; fk=fki;
      timer.setRepeats(false);
      File farg; String sarg; q=args.length;
      for(i=0;(i<q)&&(arg==null);++i)
      { sarg=StringToBegin(StringNormalize(args[i],false));
        if(sarg.toLowerCase().endsWith(".osa")) farg=new File(sarg);
        else farg=new File(sarg+".osa");
        if((farg!=null)&&(farg.isFile())&&(farg.canRead())) arg=farg; }
      if(arg!=null) timer.start();
    }
    catch(Exception e){ System.exit(0); }
  }
  /**
   * Функция, которая вызывается таймером автоматически через 1 секунду после
   * запуска программы, но только в случае, если в командной строке найдено
   * корректное имя файла проекта. В данной функции есть вызов функции открытия
   * проекта.
   * @param e объект с информацией о событии.
   * @see FileOpenProject()
  */
  public void actionPerformed(ActionEvent e)
  { timer.stop(); FileOpenProject(); }
  /**
   * Функция, сохраняющая строки из массива в файле.
   * @param d краткое название типа файла
   * @param e расширение типа файла
   * @param al массив строк для сохранения
   * @param enc номер кодировки для сохранения: 0 - ANSI, 1 - ASCII,
   * остальные номера - Unicode.
  */
  public void SaveText(String d,String e,ArrayList al,int enc)
  { int q=al.size(),i; char c; String s; BufferedWriter bw;
    File f=getSaveFile(d,e,ProjectFile); if(f!=null)
    try { switch(enc) {
      case 0: bw = new BufferedWriter(new OutputStreamWriter
        (new FileOutputStream(f),"Cp1251"),0x4000); break;
      case 1: bw = new BufferedWriter(new OutputStreamWriter
        (new FileOutputStream(f),"Cp866"),0x4000); break;
      default: bw = new BufferedWriter(new OutputStreamWriter
        (new FileOutputStream(f),"UTF-16"),0x4000); break; }
      if(bw!=null) { for(i=0;i<q;++i) { s=(String)al.get(i);
        bw.write(StringNormalize(s,true)); bw.newLine(); }
        bw.flush(); bw.close(); } } catch(IOException ex) {} }
  /**
   * Функция возвращает указатель на объект типа File для выбранного в
   * диалоговом окне пользователем файла. Указатель может быть равен null,
   * если возникли какие-то проблемы или пользователь передумал.
   * @param d краткое название типа файла
   * @param e расширение типа файла
   * @param pf имя файла с проектом, с тем, чтобы при открытии окна выбора
   * текущей была директория с файлом проекта
   * @return указатель на объект типа File или null
  */
  public File getSaveFile(String d,String e,File pf)
  { SFileFilter ff = new SFileFilter();
    ff.setDescription(d); ff.set(e);
    File f; int o,op; boolean cancel,test,confirm=false;
    JFileChooser fc = new JFileChooser(pf); fc.setSelectedFile(pf);
    fc.addChoosableFileFilter(ff); fc.setAcceptAllFileFilterUsed(false);
    do { cancel=false; test=false; do { o=fc.showSaveDialog(this);
      if((o==JFileChooser.CANCEL_OPTION)||(o==JFileChooser.ERROR_OPTION))
        { cancel=true; f=null; }
      else { f=fc.getSelectedFile(); if((f!=null)&&(!ff.acceptName(f)))
        f=new File(f+e.toLowerCase()); try {
        if((f!=null)&&(((f.isFile())&&(f.canWrite()))
        ||((!f.exists())&&(f.createNewFile())))) test=true; }
        catch(Exception ex){} } } while((!cancel)&&(!test));
        if((f==null)||(f.length()==0)) confirm=true; else {
          op = JOptionPane.showConfirmDialog(this,f+" already exists.\n"
            +"Do you want to replace it?","Save",
            JOptionPane.YES_NO_CANCEL_OPTION,JOptionPane.QUESTION_MESSAGE);
          if((op==JOptionPane.CANCEL_OPTION)||(op==JOptionPane.CLOSED_OPTION))
          {confirm=true; f=null;}
          else if(op==JOptionPane.YES_OPTION) confirm=true; } }
    while(!confirm); return(f); }
  /**
   * Функция, сохраняющая в массив строки из файла.
   * @param d краткое название типа файла
   * @param e расширение типа файла
   * @param al массив для строк
   * @param enc номер кодировки для сохранения: 0 - ANSI, 1 - ASCII,
   * остальные номера - Unicode.
   * @return true, если информация считалась.
  */
  public boolean OpenText(String d,String e,ArrayList al,int enc)
  { String s; BufferedReader br; StringBuffer sb; boolean r=false; al.clear();
    File f=getOpenFile(d,e,ProjectFile); if(f!=null)
    try { r=true; switch(enc) {
      case 0: br = new BufferedReader(new InputStreamReader
        (new FileInputStream(f),"Cp1251"),0x4000); break;
      case 1: br = new BufferedReader(new InputStreamReader
        (new FileInputStream(f),"Cp866"),0x4000); break;
      default: br = new BufferedReader(new InputStreamReader
        (new FileInputStream(f),"UTF-16"),0x4000); break; }
      if(br!=null) { while((s=br.readLine())!=null)
      { al.add(StringNormalize(s,true)); } br.close(); } }
      catch(IOException ex) {} return(r); }
  /**
   * Функция возвращает указатель на объект типа File для выбранного в
   * диалоговом окне пользователем файла. Указатель может быть равен null,
   * если возникли какие-то проблемы или пользователь передумал.
   * @param d краткое название типа файла
   * @param e расширение типа файла
   * @param pf имя файла с проектом, с тем, чтобы при открытии окна выбора
   * текущей была директория с файлом проекта
   * @return указатель на объект типа File или null
  */
  public File getOpenFile(String d,String e,File pf)
  { SFileFilter ff = new SFileFilter();
    ff.setDescription(d); ff.set(e);
    File f; int o; boolean cancel=false,ok=false;
    JFileChooser fc = new JFileChooser(pf); fc.setSelectedFile(pf);
    fc.addChoosableFileFilter(ff); fc.setAcceptAllFileFilterUsed(false);
    do { o=fc.showOpenDialog(this);
      if((o==JFileChooser.CANCEL_OPTION)||(o==JFileChooser.ERROR_OPTION))
        { cancel=true; f=null; }
      else { f=fc.getSelectedFile(); if((f!=null)&&(!ff.acceptName(f)))
        f=new File(f+e.toLowerCase()); if((f!=null)&&(f.isFile())&&(f.canRead()))
        ok=true; } } while((!cancel)&&(!ok)); return(f); }
  /**
   * Функция убирает из строки все служебные символы, а также пробелы
   * в конце строки. Возможна замена табуляций на нужное число пробелов.
   * @param s исходная строка
   * @param t режим обработки: true - оставлять табуляции, false - заменить на
   * пробелы
   * @return обработанная строка
  */
  public String StringNormalize(String s,boolean t)//t=true - оставить \t
  { int j,k=0,l=s.length()-1; char c; StringBuffer sb = new StringBuffer();
    while((l>=0)&&(((c=s.charAt(l))==' ')
      ||(c=='\b')||(c=='\f')||(c=='\n')||(c=='\r')||(c=='\t'))) --l; ++l;
    for(j=0;j<l;++j) { c=s.charAt(j); if((c!='\b')&&(c!='\f')&&(c!='\n')&&(c!='\r'))
    { if((c=='\t')&&(!t)) switch(k%8)
      { case 0: sb.append("        "); k+=8; break;
        case 1: sb.append("       ");  k+=7; break;
        case 2: sb.append("      ");   k+=6; break;
        case 3: sb.append("     ");    k+=5; break;
        case 4: sb.append("    ");     k+=4; break;
        case 5: sb.append("   ");      k+=3; break;
        case 6: sb.append("  ");       k+=2; break;
        case 7: sb.append(" ");        k+=1; break;
        default:sb.append(' ');        ++k;  break; }
      else { sb.append(c); ++k; } }} return(sb.toString()); }
  /**
   * Функция убирает пробеля в начале строки и обычно применяется после функции
   * StringNormalize(String s,boolean t).
   * @param s исходная строка
   * @return обработанная строка
   * @see StringNormalize(String s,boolean t)
  */
  public String StringToBegin(String s)
  { int i,k=0,l=s.length(); StringBuffer sb = new StringBuffer();
    while((k<l)&&(s.charAt(k)==' ')) ++k;
    for(i=k;i<l;++i) sb.append(s.charAt(i)); return(sb.toString()); }
  /**
   * Функция копирует строки из элемента интерфейса JTestArea в массив ArrayList
   * @param ta указатель на JTextArea
   * @param al указатель на ArrayList
   * @param t режим работы
   * @see StringNormalize(String s,boolean t)
  */
  public void TextAreaToArrayList(JTextArea ta,ArrayList al,boolean t)
  { int q=ta.getLineCount(),so,eo,i; String s; al.clear(); --q;
    try { for(i=0;i<=q;++i)
    { so=ta.getLineStartOffset(i); eo=ta.getLineEndOffset(i); if(i!=q) --eo;
      s=ta.getText(so,eo-so); al.add(StringNormalize(s,t)); }
    } catch(Exception e) {} }
  /**
   * Функция преобразует строки из массива ArrayList в одну строку в формате
   * элемента интерфейса JTestArea.
   * @param al указатель на ArrayList
   * @return строка для занесения в JTextArea
  */
  public String ArrayListToString(ArrayList al)
  { int i,q=al.size(); StringBuffer sb = new StringBuffer(); for(i=0;i<q;++i)
    { sb.append((String)al.get(i)); sb.append('\n'); } return(sb.toString()); }
  /**
   * Функция сохраняет текущий проект в файле.
  */
  private void SaveProjectData()
  { BufferedWriter bw; ArrayList al,cal; String s; int i,j,k,q;
    try { bw = new BufferedWriter(new OutputStreamWriter
      (new FileOutputStream(ProjectFile),"UTF-16"),0x4000);
    if(bw!=null) {
      //Сохранение Индексов
      q=CurrentIndex; if((q<0)||(q>7)) q=-1; ++q;
      for(i=0;i<q;++i) bw.write('-'); bw.newLine();
      i=alChainName.size(); q=alChainText.size(); if(i>q) i=q;
      q=CurrentChainIndex; if((q<0)||(q>=i)) q=-1; ++q;
      for(i=0;i<q;++i) bw.write('-'); bw.newLine();
      s=StringToBegin(StringNormalize(mTab4A.t4ATCTextField.getText(),false));
      bw.write(s); bw.newLine();
      q=SliderValue; if((q<0)||(q>10)) q=-1; ++q;
      for(i=0;i<q;++i) bw.write('-'); bw.newLine();
      if(RightStyle) q=0; else q=1;
      for(i=0;i<q;++i) bw.write('-'); bw.newLine();
      i=alFunctionName.size(); q=alFunctionText.size(); if(i>q) i=q;
      q=CurrentFunctionIndex; if((q<0)||(q>=i)) q=-1; ++q;
      for(i=0;i<q;++i) bw.write('-'); bw.newLine();
      bw.newLine();
      //Сохранение Текста Грамматики
      al=new ArrayList(mTab1.t1TextArea.getLineCount());
      TextAreaToArrayList(mTab1.t1TextArea,al,true);
      q=al.size(); for(i=0;i<q;++i) { s=(String)al.get(i);
      bw.write(StringNormalize(s,true)); bw.newLine(); }
      //Сохранение Цепочек
      q=alChainName.size(); i=alChainText.size(); if(q>i) q=i;
      for(i=0;i<q;++i) { bw.write("\t"); bw.newLine();
      s=(String)alChainName.get(i); bw.write(StringNormalize(s,true)); bw.newLine();
      cal=(ArrayList)alChainText.get(i); k=cal.size();
      for(j=0;j<k;++j) { s=(String)cal.get(j);
      bw.write(StringNormalize(s,true)); bw.newLine(); } }
      //Сохранение Функций Переходов
      bw.write("\t"); bw.newLine();
      q=alFunctionName.size(); i=alFunctionText.size(); if(q>i) q=i;
      for(i=0;i<q;++i) { bw.write("\t"); bw.newLine();
      s=(String)alFunctionName.get(i); bw.write(StringNormalize(s,true)); bw.newLine();
      cal=(ArrayList)alFunctionText.get(i); k=cal.size();
      for(j=0;j<k;++j) { s=(String)cal.get(j);
      bw.write(StringNormalize(s,true)); bw.newLine(); } }
      //Можно Еще Добавить
      //bw.write("\t"); bw.newLine();
      reSaveMainTextAreaCounter=mTab1.t1UndoStack.size();
      mTab1.t1UndoableEditUsed();
      reSaveChains=false; reSaveFunctions=false;
    bw.flush(); bw.close(); } } catch(IOException ex) {} }
  /**
   * Функция проверяет, был ли модифицирован проект со времени загрузки или
   * сохранения и если был, то выдает предупреждение
   * о возможности потери данных.
   * @param ms заголовок диалогового окна с предупреждением
   * @return true, если пользователь разрешил потерю данных из проекта
  */
  private boolean ClearProjectConfirm(String ms)
  { boolean r=true; int op; String s;
    if((reSaveMainTextArea==true)
      ||(reSaveChains==true)
      ||(reSaveFunctions==true))
    //Изменение Индексов Не Приводит К Выдаче Запроса
    { if(ProjectFile==null) s="New Project"; else s=ProjectFile.toString();
        op=JOptionPane.showConfirmDialog(this,
        "The project "+s+" data has been modified.\n"
        +"Do you really want to lose it?",ms,
        JOptionPane.YES_NO_OPTION,JOptionPane.QUESTION_MESSAGE);
        if(op!=JOptionPane.YES_OPTION) r=false; } return(r); }
  /**
   * Функция вызывается для создания нового проекта.
  */
  public void FileNewProject()
  { if(ClearProjectConfirm("New")) {
      mTabbedPane.setSelectedIndex(0);
      mTab1.t1TextArea.selectAll();
      mTab1.t1TextArea.replaceSelection("");
      mTab1.t1UndoStack.removeAllElements();
      mTab1.t1RedoStack.removeAllElements();
      reSaveMainTextAreaCounter=mTab1.t1UndoStack.size();
      reAnalyseCounter=-1;
      mTab1.t1UndoableEditUsed();
      if(mTab1.t1UndoStack.empty()) mTab1.aEditUndo.setEnabled(false);
      else mTab1.aEditUndo.setEnabled(true);
      if(mTab1.t1RedoStack.empty()) mTab1.aEditRedo.setEnabled(false);
      else mTab1.aEditRedo.setEnabled(true);
      if(mTab1.t1TextArea.getSelectionEnd()-mTab1.t1TextArea.getSelectionStart()==0)
      { mTab1.aEditCut.setEnabled(false); mTab1.aEditCopy.setEnabled(false);
      mTab1.aEditDelete.setEnabled(false); }
      else { mTab1.aEditCut.setEnabled(true); mTab1.aEditCopy.setEnabled(true);
      mTab1.aEditDelete.setEnabled(true); }
      alChainName.clear(); alChainText.clear();
      reSaveChains=false; CurrentChainIndex=-1;
      mTab4.t4SetListData();
      mTab4A.t4ATCTextField.selectAll();
      mTab4A.t4ATCTextField.replaceSelection("");
      SliderValue=0; mTab4B.t4BSlider.setValue(SliderValue); RightStyle=false;
      alFunctionName.clear(); alFunctionText.clear();
      reSaveFunctions=false; CurrentFunctionIndex=-1;
      mTab6.t6SetListData();
      reTranslate=true;
      ProjectFile = null; setTitle(MainFrameTitle+" - New Project");
      topFrame.setTitle("Info Window - New Project"); } }
  /**
   * Функция вызывается для открытия проекта.
  */
  public void FileOpenProject()
  { File f; BufferedReader br; String s,si=""; ArrayList ta;
    ArrayList cal=null; int state=0;
    int i1=-1,i2=-1,i3=-1,i4=-1,i5=-1,i6=-1;
    if(ClearProjectConfirm("Open")) { if(arg!=null) { f=arg; arg=null; }
      else f=getOpenFile("Project (*.osa)",".osa",ProjectFile);
      if(f!=null) { try { br = new BufferedReader(new InputStreamReader
        (new FileInputStream(f),"UTF-16"),0x4000); if(br!=null) {
      mTabbedPane.setSelectedIndex(0);
      ta = new ArrayList(0x400);
      alChainName.clear(); alChainText.clear(); reSaveChains=false;
      SliderValue=0; mTab4B.t4BSlider.setValue(SliderValue); RightStyle=false;
      alFunctionName.clear(); alFunctionText.clear(); reSaveFunctions=false;
      reTranslate=true;

      while((s=br.readLine())!=null) switch(state) {
        case 0: i1=s.length(); --i1; state=1; break;
        case 1: i2=s.length(); --i2; state=2; break;
        case 2: si=StringToBegin(StringNormalize(s,false)); state=3; break;
        case 3: i3=s.length(); --i3; if((i3>=0)&&(i3<=10))
          { SliderValue=i3; mTab4B.t4BSlider.setValue(SliderValue); }
          state=4; break;
        case 4: i4=s.length(); if(i4==0) RightStyle=true; state=5; break;
        case 5: i5=s.length(); --i5; state=6; break;
        case 6: state=7; break;
        case 7: if(s.equals("\t")) state=8;
          else ta.add(StringNormalize(s,true)); break;
        case 8: if(s.equals("\t")) state=10;
          else { alChainName.add(StringNormalize(s,true));
          state=9; cal=new ArrayList(0x10); alChainText.add(cal); } break;
        case 9: if(s.equals("\t")) state=8;
          else cal.add(StringNormalize(s,true)); break;
        case 10: if(s.equals("\t")) state=12;
          else { alFunctionName.add(StringNormalize(s,true));
          state=11; cal=new ArrayList(0x80); alFunctionText.add(cal); } break;
        case 11: if(s.equals("\t")) state=10;
          else cal.add(StringNormalize(s,true)); break;
        case 12: break; }
      mTab1.t1TextArea.selectAll();
      mTab1.t1TextArea.replaceSelection(ArrayListToString(ta));
      mTab1.t1TextArea.setCaretPosition(0);
      mTab1.t1UndoStack.removeAllElements();
      mTab1.t1RedoStack.removeAllElements();
      reSaveMainTextAreaCounter=mTab1.t1UndoStack.size();
      reAnalyseCounter=-1;
      mTab1.t1UndoableEditUsed();
      if(mTab1.t1UndoStack.empty()) mTab1.aEditUndo.setEnabled(false);
      else mTab1.aEditUndo.setEnabled(true);
      if(mTab1.t1RedoStack.empty()) mTab1.aEditRedo.setEnabled(false);
      else mTab1.aEditRedo.setEnabled(true);
      if(mTab1.t1TextArea.getSelectionEnd()-mTab1.t1TextArea.getSelectionStart()==0)
      { mTab1.aEditCut.setEnabled(false); mTab1.aEditCopy.setEnabled(false);
      mTab1.aEditDelete.setEnabled(false); }
      else { mTab1.aEditCut.setEnabled(true); mTab1.aEditCopy.setEnabled(true);
      mTab1.aEditDelete.setEnabled(true); }
      ta.clear();
      CurrentChainIndex=-1; mTab4.t4SetListData();
      mTab4A.t4ATCTextField.selectAll();
      mTab4A.t4ATCTextField.replaceSelection(si);
      if((i2>=0)&&(i2<alChainName.size())&&(i2<alChainText.size()))
      { mTab4.t4List.setSelectedIndex(i2); mTab4.t4List.ensureIndexIsVisible(i2); }
      CurrentFunctionIndex=-1; mTab6.t6SetListData();
      if((i5>=0)&&(i5<alFunctionName.size())&&(i5<alFunctionText.size()))
      { mTab6.t6List.setSelectedIndex(i5); mTab6.t6List.ensureIndexIsVisible(i5); }
      if((i1>=0)&&(i1<=7)) mTabbedPane.setSelectedIndex(i1);
      br.close(); } } catch(IOException ex) {}
    ProjectFile=f; setTitle(MainFrameTitle+" - "+ProjectFile);
    topFrame.setTitle("Info Window - "+ProjectFile); } } }
  /**
   * Функция вызывается для сохранения текущего проекта.
  */
  public void FileSaveProject()
  { if(ProjectFile==null) FileSaveProjectAs();
    else { try { if(((ProjectFile.isFile())&&(ProjectFile.canWrite()))
                   ||((!ProjectFile.exists())&&(ProjectFile.createNewFile())))
                   SaveProjectData(); } catch(Exception e){} } }
  /**
   * Функция вызывается для сохранения текущего проекта под заданным именем.
  */
  public void FileSaveProjectAs()
  { File f=getSaveFile("Project (*.osa)",".osa",ProjectFile); if(f!=null)
    { ProjectFile=f; SaveProjectData();
      setTitle(MainFrameTitle+" - "+ProjectFile);
      topFrame.setTitle("Info Window - "+ProjectFile); } }
  /**
   * Функция вызывается для выхода из программы.
  */
  public void FileExit()
  { boolean cb=true; if(isBottomComponentActive) cb=CloseBottom();
    if(cb) if(ClearProjectConfirm("Exit"))
    { topFrame.dispose(); dispose(); System.exit(0); } }
  /**
   * Функция вызывается для показа помощи.
   * @param tn индекс компонента, для котороко нужно вызвать справку
  */
  public void HelpHelpTopics(int tn)
  { URL html=null;
    switch(tn)
    { case 0x01: html=MainFrame.class.getResource("ht1.html"); break;
      case 0x02: html=MainFrame.class.getResource("ht2.html"); break;
      case 0x03: html=MainFrame.class.getResource("ht3.html"); break;
      case 0x04: html=MainFrame.class.getResource("ht4.html"); break;
      case 0x4A: html=MainFrame.class.getResource("ht4a.html"); break;
      case 0x4B: html=MainFrame.class.getResource("ht4b.html"); break;
      case 0x05: html=MainFrame.class.getResource("ht5.html"); break;
      case 0x5A: html=MainFrame.class.getResource("ht5a.html"); break;
      case 0x06: html=MainFrame.class.getResource("ht6.html"); break;
      case 0x6A: html=MainFrame.class.getResource("ht6a.html"); break;
      case 0x07: html=MainFrame.class.getResource("ht7.html"); break;
      case 0x08: html=MainFrame.class.getResource("ht8.html"); break;
      default:   html=MainFrame.class.getResource("index.html"); break;
    }
    if(html!=null)
    { String[] args = {"explorer",html.toString()};
      try { Runtime.getRuntime().exec(args); } catch(Exception e) {  }
    }
  }
  /**
   *  Функция вызывается для показа информации о разработчиках.
  */
  public void HelpAbout()
  { new AboutFrame(this,MainFrameTitle); }
  /**
   * Функция обрабатывает события, происходящие с окном. Точнее интерес
   * представляет только закрытие окна.
   * @param e объект, в котором хранятся данные о событии.
  */
  protected void processWindowEvent(WindowEvent e)
  { boolean cb=true; if(e.getID()==WindowEvent.WINDOW_CLOSING)
    { if(isBottomComponentActive) cb=CloseBottom();
      if(cb) if(ClearProjectConfirm("Close"))
      { topFrame.dispose(); super.processWindowEvent(e); }
    } else super.processWindowEvent(e); }
  /**
   * Весьма специфическая функция, нужна для автоматического запуска транслятора
   * при сохранении цепочки или функций переходов в соответствующих редакторах.
   * @param ti индекс текущей выбранной вкладки.
  */
  public void TranslatorUpdate(int ti)
  { int i=CurrentIndex; if((i==6)||(i==7))
    { mTabbedPane.setSelectedIndex(ti); mTabbedPane.setSelectedIndex(i); } }
  /**
   * Обычная функция validate(), но для двух классов (точнее - окон):
   * MainFrame и TopFrame.
  */
  public void validate()
  { super.validate(); topFrame.validate();
    Rectangle contentPaneBounds = contentPane.getBounds();
    glassPane.setBounds(contentPaneBounds); glassPane.validate();
  }
  /**
   * Функция для закрытия окна с вынесенной панелью с вкладками.
  */
  public void TopHide()
  { topFrame.hide();
    topFrame.contentPane.removeAll();
    mSplitPane.setTopComponent(mTabbedPane);
    validate(); }
  /**
   * Функция для выноса панели с вкладками в отдельное окно, если в главном
   * окне есть нижний компонент.
  */
  public void TopShow()
  { mSplitPane.setTopComponent(null);
    topFrame.contentPane.add(mTabbedPane,BorderLayout.CENTER);
    topFrame.show();
    validate(); }
  /**
   * Функция для закрытия нижнего компонента.
   * @return true, если закрытие произошло
  */
  public boolean CloseBottom()
  { boolean cb=true;
    switch(CurrentBottomIndex) {
      case 0x4A: cb=mTab4A.tClose(); break;
      case 0x4B: cb=mTab4B.tClose(); break;
      case 0x5A: cb=mTab5A.tClose(); break;
      case 0x6A: cb=mTab6A.tClose(); break; }
    if(cb) { IDeSelect(CurrentIndex); isBottomComponentActive=false;
      if(CurrentBottomIndex==0x5A) SelectionSecurity=false;
      ISelect(CurrentIndex); CurrentBottomIndex=-1;
      mTabbedPane.setSelectedIndex(TopComponentIndex); TopComponentIndex=-1;
      if(ShowTop) TopHide(); } return(cb); }
  /**
   * Функция для открытия нижнего компонента.
   * @param i номер нижнего компонента для открытия
  */
  public void OpenBottom(int i)
  { if(ShowTop) TopShow();
    TopComponentIndex=CurrentIndex;
    IDeSelect(CurrentIndex); isBottomComponentActive=true;
    if(i==0x5A) SelectionSecurity=true;
    ISelect(CurrentIndex); CurrentBottomIndex=i;
    switch(i) {
      case 0x4A: mTab4A.tOpen(); break;
      case 0x4B: mTab4B.tOpen(); break;
      case 0x5A: mTab5A.tOpen(); break;
      case 0x6A: mTab6A.tOpen(); break; } }

  private void mTabbedPaneChangeEvent()
  { int NewIndex = mTabbedPane.getSelectedIndex();
    IDeSelect(CurrentIndex); CurrentIndex=NewIndex; ISelect(NewIndex); }

  private void IDeSelect(int i)
  { switch(i) {
      case 0: mTab1.tDeSelect(); break;
      case 1: mTab2.tDeSelect(); break;
      case 2: mTab3.tDeSelect(); break;
      case 3: if(!SelectionSecurity) mTab4.tDeSelect(); break;
      case 4: if(!SelectionSecurity) mTab5.tDeSelect(); break;
      case 5: if(!SelectionSecurity) mTab6.tDeSelect(); break;
      case 6: if(!SelectionSecurity) mTab7.tDeSelect(); break;
      case 7: if(!SelectionSecurity) mTab7.tDeSelect(); break; } }

  private void ISelect(int i)
  { switch(i) {
      case 0: mTab1.tSelect(); break;
      case 1: mTab2.tSelect(); break;
      case 2: mTab3.tSelect(); break;
      case 3: if(!SelectionSecurity) mTab4.tSelect(); break;
      case 4: if(!SelectionSecurity) mTab5.tSelect(); break;
      case 5: if(!SelectionSecurity) mTab6.tSelect(); break;
      case 6: if(!SelectionSecurity) mTab7.tSelect(false); break;
      case 7: if(!SelectionSecurity) mTab7.tSelect(true); break; } }

// MMV

  public Grammar getGrammar()
    {
    Analyse();
    return mWasp.getGrammar();
    }

}
