    001package org.vishia.gral.cfg;
    002
    003import java.io.File;
    004import java.io.FileInputStream;
    005import java.io.IOException;
    006import java.io.InputStream;
    007import java.text.ParseException;
    008import java.util.LinkedList;
    009import java.util.List;
    010import java.util.Map;
    011import java.util.Set;
    012import java.util.TreeMap;
    013
    014import org.vishia.gral.base.GralCurveView;
    015import org.vishia.gral.base.GralMouseWidgetAction_ifc;
    016import org.vishia.gral.base.GralPos;
    017import org.vishia.gral.base.GralWidget;
    018import org.vishia.gral.base.GralWindow;
    019import org.vishia.gral.cfg.GralCfgElement;
    020import org.vishia.gral.ifc.GralColor;
    021import org.vishia.gral.ifc.GralMngBuild_ifc;
    022import org.vishia.gral.ifc.GralPoint;
    023import org.vishia.gral.ifc.GralUserAction;
    024import org.vishia.gral.ifc.GralWidget_ifc;
    025import org.vishia.msgDispatch.LogMessage;
    026import org.vishia.util.CalculatorExpr;
    027import org.vishia.util.Debugutil;
    028import org.vishia.util.KeyCode;
    029
    030public class GralCfgBuilder
    031{
    032
    033  /**Version and history
    034   * <ul>
    035   * <li>2015-04-27 Hartmut chg: {@link #buildGui(LogMessage, int)} Now regards only one panel in the window, not only tabbed panels.
    036   * <li>2014-02-24 Hartmut new element help now also in config.
    037   * <li>2012-09-17 Hartmut chg: showMethod now split functionName and parameters. The function name is used to get
    038   *   the {@link GralUserAction} for {@link GralWidget#setActionShow(GralUserAction, String[])}. The parameter are stored
    039   *   in {@link GralWidget#cfg} as {@link GralWidget.ConfigData#showParam}.
    040   * <li>2011-05-00 Hartmut created, the old ZbnfCfg.. class is obsolte now.
    041   * </ul>
    042   *
    043   * <b>Copyright/Copyleft</b>:<br>
    044   * For this source the LGPL Lesser General Public License,
    045   * published by the Free Software Foundation is valid.
    046   * It means:
    047   * <ol>
    048   * <li> You can use this source without any restriction for any desired purpose.
    049   * <li> You can redistribute copies of this source to everybody.
    050   * <li> Every user of this source, also the user of redistribute copies
    051   *    with or without payment, must accept this license for further using.
    052   * <li> But the LPGL is not appropriate for a whole software product,
    053   *    if this source is only a part of them. It means, the user
    054   *    must publish this part of source,
    055   *    but doesn't need to publish the whole source of the own product.
    056   * <li> You can study and modify (improve) this source
    057   *    for own using or for redistribution, but you have to license the
    058   *    modified sources likewise under this LGPL Lesser General Public License.
    059   *    You mustn't delete this Copyright/Copyleft inscription in this source file.
    060   * </ol>
    061   * If you intent to use this source without publishing its usage, you can get
    062   * a second license subscribing a special contract with the author.
    063   *
    064   * @author Hartmut Schorrig = hartmut.schorrig@vishia.de
    065   */
    066  public static final int version = 20120303;
    067
    068  private final GralCfgData cfgData;
    069
    070  private final GralMngBuild_ifc gralMng;
    071
    072  /**The current directory is that directory, where the config file is located.
    073   * It is used if other files are given with relative path.*/
    074  private final File currentDir;
    075
    076  private final Map<String, String> indexAlias = new TreeMap<String, String>();
    077
    078
    079  public GralCfgBuilder(GralCfgData cfgData, GralMngBuild_ifc gui, File currentDir)
    080  {
    081    this.cfgData = cfgData;
    082    this.gralMng = gui;
    083    this.currentDir = currentDir;
    084    if(currentDir !=null) {
    085      String sCanonicalPath = org.vishia.util.FileSystem.getCanonicalPath(currentDir);
    086      indexAlias.put("cfg", sCanonicalPath);
    087    }
    088  }
    089
    090
    091  public GralCfgElement XXXnewCfgElement(GralCfgElement previous)
    092  { //GuiCfgElement cfge = new GuiCfgElement(cfgData);
    093    GralCfgElement cfge = previous.clone();
    094    cfge.next = previous.next;
    095    cfge.previous = previous;
    096    previous.next = cfge;
    097    return cfge;
    098  }
    099
    100  /**Builds the appearance of the whole graphic with the given {@link GralCfgData} cfgData.
    101   * Calls {@link #buildPanel(org.vishia.gral.cfg.GralCfgPanel)} for the any panel
    102   * in the {@link GralCfgData#idxPanels}. Fills the panels one after another.
    103   *
    104   * @param log maybe null, errors and warnings are written
    105   * @param msgIdent The message identification for output.
    106   * @return null if ok, elsewhere the error hints which maybe written to log too, one per line.
    107   */
    108  /**
    109   * @param log
    110   * @param msgIdent
    111   * @return
    112   */
    113  public String buildGui(LogMessage log, int msgIdent)
    114  {
    115    String sError = null;
    116    gralMng.getReplacerAlias().addDataReplace(cfgData.dataReplace);
    117
    118    Set<Map.Entry<String, GralCfgPanel>> setIdxPanels = cfgData.getPanels();
    119    if(setIdxPanels.size()==0){
    120      //gralMng.selectPanel(cfgData.actPanel);
    121      //==================>
    122      String sErrorPanel = buildPanel(cfgData.actPanel);
    123      if(sErrorPanel !=null){
    124        if(log !=null){
    125          log.sendMsg(msgIdent, "GralCfgBuilder - cfg error; %s", sErrorPanel);
    126        }
    127        if(sError == null){ sError = sErrorPanel; }
    128        else { sError += "\n" + sErrorPanel; }
    129      }
    130    } else {
    131      //some panels are given, therefore selects given panels by name or create tabbed panels.
    132      for(Map.Entry<String, GralCfgPanel> panelEntry: setIdxPanels){  //cfgData.idxPanels.entrySet()){
    133        GralCfgPanel cfgPanel = panelEntry.getValue();
    134        if(cfgPanel.windTitle !=null) {
    135          Debugutil.stop();
    136          GralWindow wind = new GralWindow(cfgPanel.windPos, cfgPanel.name, cfgPanel.windTitle, GralWindow.windOnTop | GralWindow.windResizeable);
    137          wind.createImplWidget_Gthread();
    138        }
    139        else {
    140          //A tab in the main window
    141          //==================>
    142          gralMng.selectPanel(cfgPanel.name);
    143        }
    144        String sErrorPanel = buildPanel(cfgPanel);
    145        if(sErrorPanel !=null){
    146          if(log !=null){
    147            log.sendMsg(msgIdent, "GralCfgBuilder - cfg error; %s", sErrorPanel);
    148          }
    149          if(sError == null){ sError = sErrorPanel; }
    150          else { sError += "\n" + sErrorPanel; }
    151        } else {
    152          stop();
    153        }
    154      }
    155    }
    156    return sError;
    157  }
    158
    159
    160  /**Builds the appearance of one panel with the given {@link GralCfgPanel} cfgData.
    161   * @param cfgDataPanel
    162   * @return null if ok, elsewhere the error hints, one per line.
    163   */
    164  public String buildPanel(GralCfgPanel cfgDataPanel)
    165  {
    166    String sError = null;
    167    for(GralCfgElement cfge: cfgDataPanel.listElements){
    168      //=================>>
    169      String sErrorWidgd;
    170      try{ sErrorWidgd = buildWidget(cfge); }
    171      catch(ParseException exc) { sErrorWidgd = exc.getMessage(); }
    172      if(sErrorWidgd !=null){
    173        if(sError == null){ sError = sErrorWidgd; }
    174        else { sError += "\n" + sErrorWidgd; }
    175      }
    176    }
    177    return sError;
    178  }
    179
    180
    181
    182  /**Builds the graphical widget inclusive its {@link GralWidget} and place it in the GUI.
    183   * @param cfge The configuration element data read from config file or set from the GUI-editor.
    184   * @return null if OK, an error String for a user info message on warning or error.
    185   *         It is possible that a named user action is not found etc.
    186   */
    187  public String buildWidget(GralCfgElement cfge)
    188  throws ParseException
    189  {
    190    String sError = null;
    191    cfge.setPos(gralMng);
    192    if(cfge.widgetType.type !=null){
    193      GralCfgData.GuiCfgWidget typeData = cfgData.idxTypes.get(cfge.widgetType.type);
    194      if(typeData == null){
    195        throw new IllegalArgumentException("GralCfgBuilder.buildWidget - unknown type; " + cfge.widgetType.type + "; in " + cfge.content);
    196      } else {
    197        cfge.widgetType.setFromType(typeData);
    198      }
    199    }
    200
    201    GralWidget widgd = null;
    202    String sName = cfge.widgetType.name;
    203    if(sName !=null && sName.equals("msgOfDay"))
    204      stop();
    205
    206    if(sName ==null && cfge.widgetType.text !=null ){ sName = cfge.widgetType.text; }  //text of button etc.
    207    if(sName ==null && cfge.widgetType.prompt !=null){ sName = cfgData.actPanel.name + "/" + cfge.widgetType.prompt; } //the prompt as name
    208    //the name may be null, then the widget is not registered.
    209    //
    210
    211    String sDataPath = cfge.widgetType.data;
    212    //text is the default for a datapath.
    213    if(sDataPath ==null && cfge.widgetType.text !=null){ sDataPath = cfge.widgetType.text; }
    214    /*
    215    if(sDataPath !=null){
    216      //replace a prefix before ':' with its replacement, if the prefix is found.
    217      //This is a possibility to shorten data path.
    218      int posSep = sDataPath.indexOf(':');
    219      if(posSep > 0){
    220        String sPre = cfge.itsCfgData.dataReplace.get(sDataPath.substring(0, posSep));
    221        if(sPre !=null){
    222          sDataPath = sPre + sDataPath.substring(posSep+1);
    223        }
    224      } else {
    225        String sReplace = cfge.itsCfgData.dataReplace.get(sDataPath);
    226        if(sReplace !=null){
    227          sDataPath = sReplace;
    228        }
    229      }
    230    }
    231    */
    232    //
    233    final GralUserAction userAction; //, mouseAction;
    234    final String[] sUserActionArgs;
    235    GralWidget_ifc.ActionChangeWhen whenUserAction = null;
    236    if(cfge.widgetType.userAction !=null){
    237      String sUserAction = cfge.widgetType.userAction;
    238      if(sUserAction.startsWith("@")){
    239        int posEnd = sUserAction.indexOf(':');
    240        if(posEnd < 0) { sError = "GuiCfgBuilder - @m: ':' not found. ";  sUserAction = null; }
    241        else {
    242          for(int ix = 1; ix < posEnd; ++ix){
    243            char whatMouseKey = sUserAction.charAt(ix);
    244            switch(whatMouseKey){
    245            case (char)(KeyCode.mouse1Double & 0xff): whenUserAction = GralWidget_ifc.ActionChangeWhen.onMouse1Double; break;
    246            }
    247          }
    248          sUserAction = sUserAction.substring(posEnd+1).trim();
    249        }
    250      }
    251      if(sUserAction !=null) {
    252        String[] sMethod = CalculatorExpr.splitFnNameAndParams(sUserAction);
    253        userAction = gralMng.getRegisteredUserAction(sMethod[0]);
    254        if(userAction == null){
    255          sError = "GuiCfgBuilder - user action ignored because not found: " + cfge.widgetType.userAction;
    256          sUserActionArgs = null;
    257        } else {
    258          sUserActionArgs = sMethod[1] == null ? null : CalculatorExpr.splitFnParams(sMethod[1]);
    259        }
    260
    261      } else { userAction = null; sUserActionArgs = null; }
    262    } else { userAction = null; sUserActionArgs = null; }
    263
    264
    265
    266
    267    /*
    268    if(cfge.widgetType.mouseAction !=null){
    269      mouseAction = gralMng.getRegisteredUserAction(cfge.widgetType.mouseAction);
    270      if(mouseAction == null){
    271        sError = "GuiCfgBuilder - mouse action ignored because not found: " + cfge.widgetType.mouseAction;
    272      }
    273    } else { mouseAction = null; }
    274    */
    275    //
    276    if(cfge.widgetType instanceof GralCfgData.GuiCfgButton){
    277      GralCfgData.GuiCfgButton wButton = (GralCfgData.GuiCfgButton) cfge.widgetType;
    278      if(wButton.bSwitch){
    279        widgd = gralMng.addSwitchButton(cfge.widgetType.name, userAction, cfge.widgetType.cmd
    280          , cfge.widgetType.data, cfge.widgetType.text, cfge.widgetType.color0.color, cfge.widgetType.color1.color);
    281      } else {
    282        widgd = gralMng.addButton(cfge.widgetType.name, userAction, cfge.widgetType.cmd, cfge.widgetType.data, cfge.widgetType.text);
    283      }
    284    } else if(cfge.widgetType instanceof GralCfgData.GuiCfgText){
    285      GralCfgData.GuiCfgText wText = (GralCfgData.GuiCfgText)cfge.widgetType;
    286      final int colorValue;
    287      if(wText.color0 !=null){ colorValue = gralMng.getColorValue(wText.color0.color); }
    288      //else if(wText.colorName !=null){ colorValue = gralMng.getColorValue(wText.colorName.color);}
    289      else{ colorValue = 0; } //black
    290      cfge.widgetType.color0 = null;  //it is used, don't set background.
    291      widgd = gralMng.addText(cfge.widgetType.text);
    292    } else if(cfge.widgetType instanceof GralCfgData.GuiCfgLed){
    293      GralCfgData.GuiCfgLed ww = (GralCfgData.GuiCfgLed)cfge.widgetType;
    294      widgd = gralMng.addLed(sName, sDataPath);
    295
    296    } else if(cfge.widgetType instanceof GralCfgData.GuiCfgImage){
    297      GralCfgData.GuiCfgImage wImage = (GralCfgData.GuiCfgImage)cfge.widgetType;
    298      File fileImage = new File(currentDir, wImage.file_);
    299      if(fileImage.exists()){
    300        try{ InputStream imageStream = new FileInputStream(fileImage);
    301          gralMng.addImage(sName, imageStream, 10, 20, "?cmd");
    302          imageStream.close();
    303        } catch(IOException exc){ }
    304
    305      }
    306
    307    } else if(cfge.widgetType instanceof GralCfgData.GuiCfgShowField){
    308      //GuiCfgData.GuiCfgShowField wShow = (GuiCfgData.GuiCfgShowField)cfge.widgetType;
    309      //char cPromptPosition = cfge.widgetType.promptPosition ==null || cfge.widgetType.promptPosition.length() <1
    310      //                     ? '.' :  cfge.widgetType.promptPosition.charAt(0);
    311      widgd = gralMng.addTextField(sName, cfge.widgetType.editable, cfge.widgetType.prompt, cfge.widgetType.promptPosition);
    312      widgd.setDataPath(sDataPath);
    313    } else if(cfge.widgetType instanceof GralCfgData.GuiCfgInputFile){
    314      GralCfgData.GuiCfgInputFile widgt = (GralCfgData.GuiCfgInputFile)cfge.widgetType;
    315      final String dirMask;
    316      if(widgt.data !=null){
    317        dirMask = replaceAlias(widgt.data);
    318      } else { dirMask = ""; }
    319      widgd = gralMng.addFileSelectField(sName, null, dirMask, null, "t");
    320      widgd.setDataPath(sDataPath);
    321    } else if(cfge.widgetType instanceof GralCfgData.GuiCfgTable){
    322      GralCfgData.GuiCfgTable widgt = (GralCfgData.GuiCfgTable)cfge.widgetType;
    323      List<Integer> columns = widgt.getColumnWidths();
    324      int zColumn = columns.size();
    325      int[] aCol = new int[zColumn];
    326      int ix = -1;
    327      for(Integer column: columns){ aCol[++ix] = column; }
    328      widgd = gralMng.addTable(sName, widgt.height, aCol);
    329      widgd.setDataPath(sDataPath);
    330    } else if(cfge.widgetType instanceof GralCfgData.GuiCfgCurveview){
    331      GralCfgData.GuiCfgCurveview widgt = (GralCfgData.GuiCfgCurveview)cfge.widgetType;
    332      int nrofTracks = widgt.lines.size();
    333      GralCurveView widgc = gralMng.addCurveViewY(sName, widgt.nrofPoints, null);
    334      widgc.activate(widgt.activate);
    335      for(GralCfgData.GuiCfgCurveLine line: widgt.lines){
    336        String sDataPathLine = line.data;
    337        final GralColor colorLine;
    338        if(line.color0 !=null){
    339          colorLine = GralColor.getColor(line.color0.color);
    340        } else {
    341          colorLine = GralColor.getColor(line.colorValue);  //maybe 0 = black if not given.
    342        }
    343        widgc.addTrack(line.name, sDataPathLine, colorLine, 0, line.nullLine, line.scale, line.offset);
    344      }
    345      widgd = widgc;
    346    } else {
    347      switch(cfge.widgetType.whatIs){
    348        case 'T':{
    349          widgd = gralMng.addTextField(sName, true, cfge.widgetType.prompt, cfge.widgetType.promptPosition);
    350          widgd.setDataPath(sDataPath);
    351        } break;
    352        case 't':{
    353          char promptPosition = cfge.widgetType.promptPosition == null ? '.' : cfge.widgetType.promptPosition.charAt(0);
    354          widgd = gralMng.addTextBox(sName, true, cfge.widgetType.prompt, promptPosition);
    355          widgd.setDataPath(sDataPath);
    356        } break;
    357        case 'U':{
    358          widgd = gralMng.addValueBar(sName, sDataPath);
    359        } break;
    360        case 'I':{
    361          GralCfgData.GuiCfgLine cfgLine = (GralCfgData.GuiCfgLine)cfge.widgetType;
    362          //copy the points from the type GuiCfgCoord to GralPoint
    363          List<GralPoint> points = new LinkedList<GralPoint>();
    364          for(GralCfgData.GuiCfgCoord coord: cfgLine.coords){
    365            points.add(new GralPoint(coord.x, coord.y));
    366          }
    367          gralMng.addLine(GralColor.getColor(cfgLine.color0.color), points);
    368        } break;
    369        default: {
    370          widgd = null;
    371        }//default
    372      }
    373
    374    }
    375    if(widgd !=null){
    376      //set common attributes for widgets:
    377      //widgd.pos = gui.getPositionInPanel();
    378      String sShowMethod1 = cfge.widgetType.showMethod;
    379      if(sShowMethod1 !=null){
    380        String[] sShowMethod = CalculatorExpr.splitFnNameAndParams(sShowMethod1);
    381
    382        GralUserAction actionShow = gralMng.getRegisteredUserAction(sShowMethod[0]);
    383        if(actionShow == null){
    384          sError = "GuiCfgBuilder - show method not found: " + sShowMethod[0];
    385        } else {
    386          String[] param = sShowMethod[1] == null ? null : CalculatorExpr.splitFnParams(sShowMethod[1]);
    387          widgd.setActionShow(actionShow, param);
    388        }
    389      }
    390      widgd.sCmd = cfge.widgetType.cmd;
    391      /*
    392      String sCmd = cfge.widgetType.cmd;
    393      if(sCmd !=null){
    394        GralUserAction actionCmd = gralMng.getRegisteredUserAction(sCmd);
    395        if(actionCmd == null){
    396          sError = "GuiCfgBuilder - cmd action not found: " + sCmd;
    397        } else {
    398          widgd.setActionChange(actionCmd);
    399        }
    400      }*/
    401      if(userAction !=null){
    402        if(whenUserAction == null) { widgd.specifyActionChange(cfge.widgetType.userAction, userAction, sUserActionArgs); }
    403        else { widgd.specifyActionChange(cfge.widgetType.userAction, userAction, sUserActionArgs, whenUserAction); }
    404      }
    405      //if(mouseAction !=null){
    406        //fauly type, does not work: widgd.setActionMouse(mouseAction, 0);
    407      //}
    408      String sFormat = cfge.widgetType.format;
    409      if(sFormat !=null){
    410         widgd.setFormat(sFormat);
    411      }
    412      if(cfge.widgetType.help!=null){
    413        widgd.setHtmlHelp(cfge.widgetType.help);
    414      }
    415      if(cfge.widgetType.color0 != null){
    416        widgd.setBackColor(GralColor.getColor(cfge.widgetType.color0.color), 0);
    417      }
    418      if(cfge.widgetType.color1 != null){
    419        widgd.setLineColor(GralColor.getColor(cfge.widgetType.color1.color), 0);
    420      }
    421      if(cfge.widgetType.dropFiles !=null){
    422        GralUserAction actionDrop = gralMng.getRegisteredUserAction(cfge.widgetType.dropFiles);
    423        if(actionDrop == null){
    424          sError = "GuiCfgBuilder - action for drop not found: " + cfge.widgetType.dropFiles;
    425        } else {
    426          widgd.setDropEnable(actionDrop, KeyCode.dropFiles);
    427        }
    428      }
    429      if(cfge.widgetType.dragFiles !=null){
    430        GralUserAction actionDrag = gralMng.getRegisteredUserAction(cfge.widgetType.dragFiles);
    431        if(actionDrag == null){
    432          sError = "GuiCfgBuilder - action for drag not found: " + cfge.widgetType.dragFiles;
    433        } else {
    434          widgd.setDragEnable(actionDrag, KeyCode.dragFiles);
    435        }
    436      }
    437      if(cfge.widgetType.dragText !=null){
    438        GralUserAction actionDrag = gralMng.getRegisteredUserAction(cfge.widgetType.dragText);
    439        if(actionDrag == null){
    440          sError = "GuiCfgBuilder - action for drag not found: " + cfge.widgetType.dragText;
    441        } else {
    442          widgd.setDragEnable(actionDrag, KeyCode.dragText);
    443        }
    444      }
    445      //save the configuration element as association from the widget.
    446      widgd.setCfgElement(cfge);
    447    }
    448    return sError;
    449  }
    450
    451
    452  public void updatePanel(String panelName)
    453  {
    454
    455  }
    456
    457
    458
    459  String replaceAlias(String src)
    460  {
    461    int posSep;
    462    if((posSep = src.indexOf("<*")) < 0) { return src; } //unchanged
    463    else {
    464      StringBuilder u = new StringBuilder(src);
    465      do{
    466        int posEnd = u.indexOf(">", posSep+2);
    467        String sAlias = src.substring(posSep + 2, posEnd);
    468        String sValue = indexAlias.get(sAlias);
    469        if(sValue == null){ sValue = "??" + sAlias + "??";}
    470        u.replace(posSep, posEnd+1, sValue);
    471      } while((posSep = u.indexOf("<*", posSep)) >=0);  //note nice side effect: replace in sValue too
    472      return u.toString();
    473    }
    474  }
    475
    476
    477  void stop(){}
    478}