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}