1 // Gmsh - Copyright (C) 1997-2021 C. Geuzaine, J.-F. Remacle
2 //
3 // See the LICENSE.txt file in the Gmsh root directory for license information.
4 // Please report all issues on https://gitlab.onelab.info/gmsh/gmsh/issues.
5 
6 #include <stdlib.h>
7 #include <FL/Fl_Box.H>
8 #include "GModel.h"
9 #include "Context.h"
10 #include "StringUtils.h"
11 #include "FlGui.h"
12 #include "paletteWindow.h"
13 #include "onelab.h"
14 #include "onelabGroup.h"
15 #include "onelabContextWindow.h"
16 #include "drawContext.h"
17 
handle(int event)18 int contextWindow::handle(int event)
19 {
20   if(event == FL_HIDE) {
21     GModel::current()->setSelection(0);
22     if(_redraw) drawContext::global()->draw();
23   }
24   return paletteWindow::handle(event);
25 }
26 
getDimName(int dim)27 static std::string getDimName(int dim)
28 {
29   switch(dim) {
30   case 0: return "Point"; break;
31   case 1: return "Curve"; break;
32   case 2: return "Surface"; break;
33   case 3: return "Volume"; break;
34   }
35   return "";
36 }
37 
replaceTemplateString(onelab::number & n,const std::string & in,const std::string & out)38 static void replaceTemplateString(onelab::number &n,
39                                   const std::string &in,
40                                   const std::string &out)
41 {
42   // for number parameter replace the template only in attributes
43   auto attr = n.getAttributes();
44   for(auto &a : attr) { ReplaceSubStringInPlace(in, out, a.second); }
45   n.setAttributes(attr);
46 }
47 
replaceTemplateString(onelab::string & n,const std::string & in,const std::string & out)48 static void replaceTemplateString(onelab::string &n,
49                                   const std::string &in,
50                                   const std::string &out)
51 {
52   // for string parameters replace the template in the values, the attributes
53   // and the choices
54   auto vals = n.getValues();
55   for(auto &v : vals) { ReplaceSubStringInPlace(in, out, v); }
56   n.setValues(vals);
57 
58   auto choices = n.getChoices();
59   for(auto &c : choices) { ReplaceSubStringInPlace(in, out, c); }
60   n.setChoices(choices);
61 
62   auto attr = n.getAttributes();
63   for(auto &a : attr) { ReplaceSubStringInPlace(in, out, a.second); }
64   n.setAttributes(attr);
65 }
66 
67 template <typename T>
_addOnelabWidget(T & p,const std::string & pattern,std::set<std::pair<std::string,Fl_Widget * >> & widgets)68 void onelabContextWindow::_addOnelabWidget(
69   T &p, const std::string &pattern,
70   std::set<std::pair<std::string, Fl_Widget *> > &widgets)
71 {
72   if(p.getName().find(pattern) == std::string::npos) return;
73   // does the parameter exist?
74   std::string in = getDimName(_dim) + " Template";
75   std::string out;
76   if(_choice->value() == 0) // elementary
77     out = getDimName(_dim) + " " + std::to_string(_tag);
78   else // physical
79     out = "Physical " + getDimName(_dim) + " " +
80           std::to_string(_physicalGroups[_choice->value() - 1].first);
81   std::string name = ReplaceSubString(in, out, p.getName());
82   std::vector<T> pn;
83   onelab::server::instance()->get(pn, name);
84   T n;
85   if(pn.empty()) {
86     n = p;
87     n.setName(name);
88     // replace the template string in the parameter so that e.g. the attributes
89     // can be used in entity-dependent server actions, the value can be used in
90     // a user action, etc.
91     replaceTemplateString(n, in, out);
92     onelab::server::instance()->set(n);
93   }
94   else
95     n = pn[0];
96 
97   if(n.getVisible()) {
98     bool highlight = false;
99     Fl_Color c;
100     if(getParameterColor(n.getAttribute("Highlight"), c)) highlight = true;
101 
102     int xx = 0, yy = 0, ww = _width / 2;
103     std::string aspect = n.getAttribute("Aspect");
104     if(aspect.find("Left") != std::string::npos) {
105       xx = 1;
106       yy = 1;
107       ww = _width / 3;
108     }
109     if(aspect.find("Middle") != std::string::npos) {
110       xx = 2;
111       yy = 1;
112       ww = _width / 3;
113     }
114     if(aspect.find("Right") != std::string::npos) {
115       xx = 3;
116       yy = 1;
117       ww = _width / 3;
118     }
119     Fl_Widget *w = addParameterWidget(n, xx, yy, ww, BH, 1., n.getName(),
120                                       highlight, c, win->color(), _toFree);
121     w->copy_label(n.getShortName().c_str());
122     std::string help = n.getHelp();
123     if(help.empty()) help = n.getLabel();
124     if(help.empty()) help = n.getShortName();
125     w->copy_tooltip(help.c_str());
126     widgets.insert(std::make_pair(n.getName(), w));
127     _onelabWidgets.push_back(w);
128   }
129 }
130 
choice_cb(Fl_Widget * w,void * data)131 static void choice_cb(Fl_Widget *w, void *data)
132 {
133   FlGui::instance()->onelabContext->rebuild(false);
134   FlGui::instance()->onelabContext->highlightSelection();
135   // allow the solver code to react to the selection of the elementary or
136   // physical group
137   if(CTX::instance()->solver.autoCheck)
138      onelab_cb(nullptr, (void *)"check");
139 }
140 
onelabContextWindow(int deltaFontSize)141 onelabContextWindow::onelabContextWindow(int deltaFontSize)
142 {
143   FL_NORMAL_SIZE -= deltaFontSize;
144 
145   _dim = _tag = -1;
146   _entity = nullptr;
147 
148   _width = 26 * FL_NORMAL_SIZE;
149   _height = 2 * WB + 1 * BH;
150 
151   win = new contextWindow(_width, _height,
152                           CTX::instance()->nonModalWindows ? true : false,
153                           "Parameters");
154   win->box(GMSH_WINDOW_BOX);
155 
156   _choice = new Fl_Choice(WB, WB, _width - 2 * WB, BH);
157   _choice->callback(choice_cb);
158 
159   win->position(CTX::instance()->ctxPosition[0],
160                 CTX::instance()->ctxPosition[1]);
161   win->end();
162 
163   FL_NORMAL_SIZE += deltaFontSize;
164 }
165 
~onelabContextWindow()166 onelabContextWindow::~onelabContextWindow() { Fl::delete_widget(win); }
167 
show(int dim,int tag)168 void onelabContextWindow::show(int dim, int tag)
169 {
170   FlGui::instance()->lastContextWindow = 4;
171 
172   _dim = dim;
173   _tag = tag;
174   _name = GModel::current()->getElementaryName(dim, tag);
175   _entity = GModel::current()->getEntityByTag(dim, tag);
176   _physicalGroups.clear();
177   _physicalGroupEntities.clear();
178   if(_entity) {
179     std::map<int, std::vector<GEntity *> > groups;
180     GModel::current()->getPhysicalGroups(_dim, groups);
181     for(auto &p : _entity->physicals) {
182       int n = std::abs(p); // can be < 0 to switch orientation
183       _physicalGroups.push_back(
184         std::make_pair(n, GModel::current()->getPhysicalName(dim, n)));
185       _physicalGroupEntities.push_back(groups[n]);
186     }
187   }
188 
189   // create menu with possible entities (elementary and all associated
190   // physicals)
191   std::vector<Fl_Menu_Item> menu;
192   static std::vector<char *> toFree;
193   for(std::size_t i = 0; i < toFree.size(); i++) free(toFree[i]);
194   toFree.clear();
195   {
196     std::string label = getDimName(_dim) + " " + std::to_string(_tag);
197     if(_name.size()) label += ": " + _name;
198     char *str = strdup(label.c_str());
199     Fl_Menu_Item item = {str, 0, nullptr, nullptr, 0};
200     toFree.push_back(str);
201     menu.push_back(item);
202   }
203   for(auto &p : _physicalGroups) {
204     std::string label =
205       "Physical " + getDimName(_dim) + " " + std::to_string(p.first);
206     if(p.second.size()) label += ": " + p.second;
207     char *str = strdup(label.c_str());
208     Fl_Menu_Item item = {str, 0, nullptr, nullptr, 0};
209     toFree.push_back(str);
210     menu.push_back(item);
211   }
212   Fl_Menu_Item item = {nullptr};
213   menu.push_back(item);
214   _choice->copy(&menu[0]);
215 
216   if(menu.size() > 1) _choice->value(menu.size() - 2); // last physical
217 
218   rebuild(true);
219   highlightSelection();
220 
221   if(!win->shown()) win->show();
222   win->enableRedraw();
223 }
224 
hide()225 void onelabContextWindow::hide() { win->hide(); }
226 
rebuild(bool deleteWidgets)227 void onelabContextWindow::rebuild(bool deleteWidgets)
228 {
229   if(!_entity) return; // no entity selected
230 
231   // hide old widgets and record if one had the focus
232   std::string focus;
233   for(auto &w : _onelabWidgets) {
234     if(w == Fl::focus()) focus = w->label();
235     w->hide();
236   }
237 
238   // delete widgets if requested (as this is called every time the main ONELAB
239   // tree is rebuilt)
240   if(deleteWidgets) {
241     for(auto &w : _onelabWidgets) Fl::delete_widget(w);
242     _onelabWidgets.clear();
243     for(std::size_t i = 0; i < _toFree.size(); i++) free(_toFree[i]);
244     _toFree.clear();
245   }
246 
247   // get all ONELAB parameters
248   std::vector<onelab::number> pn, fpn;
249   onelab::server::instance()->get(pn);
250   std::vector<onelab::string> ps, fps;
251   onelab::server::instance()->get(ps);
252 
253   // for parameters with names containing "ONELAB Context/... Template/", check
254   // if the corresponding parameter exists; if not, create it and add it to the
255   // server; then create the widget
256   std::string pat = "ONELAB Context/" + getDimName(_dim) + " Template/";
257   std::set<std::pair<std::string, Fl_Widget *> > wset;
258   for(auto &p : pn) _addOnelabWidget(p, pat, wset);
259   for(auto &p : ps) _addOnelabWidget(p, pat, wset);
260   std::vector<std::pair<std::string, Fl_Widget *> > wvec(wset.begin(),
261                                                          wset.end());
262   int h = _height;
263   int w = win->w();
264   if(wvec.empty()) {
265     Fl_Box *b = new Fl_Box(WB, h, w - 2 * WB, BH, "No parameters");
266     _onelabWidgets.push_back(b);
267     win->add(b);
268     h += BH;
269   }
270   else {
271     for(std::size_t i = 0; i < wvec.size(); i++) {
272       // x, y of widgets used to decide how to position them
273       int ww = wvec[i].second->w();
274       int xx =
275         (wvec[i].second->x() == 3) ? _width - ww - WB : // right
276         (wvec[i].second->x() == 2) ? _width - 2 * ww - 2 * WB : // middle
277         (wvec[i].second->x() == 1) ? _width - 3 * ww - 3 * WB : // left
278         WB; // left
279       int yy =
280         (wvec[i].second->y() == 1) ? h + 2 * WB : // vertical padding
281         h; // none
282       if((i < wvec.size() - 1) && (wvec[i].second->x() > 0) &&
283          (wvec[i + 1].second->x() == (wvec[i].second->x() + 1))) {
284         // next button to the right, don't increment horizontal position
285       }
286       else if(wvec[i].second->y() == 1) {
287         // padding
288         h += wvec[i].second->h() + 2 * WB;
289       }
290       else { // default
291         h += wvec[i].second->h();
292       }
293       wvec[i].second->position(xx, yy);
294       win->add(wvec[i].second);
295     }
296   }
297   h += WB;
298 
299   // resize the window and restore the focus
300   win->resize(win->x(), win->y(), win->w(), h);
301   win->size_range(_width, win->h(), 0, win->h());
302 
303   for(auto w : _onelabWidgets) {
304     if(w->label() && focus == std::string(w->label())) { w->take_focus(); }
305   }
306 
307   // TODO: we should add a "Check" button if Solver.AutoCheck is not set (as in
308   // the main ONELAB tree)
309 }
310 
highlightSelection()311 void onelabContextWindow::highlightSelection()
312 {
313   if(!_entity) return;
314 
315   GModel::current()->setSelection(0);
316   if(_choice->value() == 0) { // elementary
317     _entity->setVisibility(1);
318     _entity->setSelection(2);
319   }
320   else { // physical
321     if(_choice->value() - 1 < (int)_physicalGroupEntities.size()) {
322       for(auto e : _physicalGroupEntities[_choice->value() - 1]) {
323         e->setVisibility(1);
324         e->setSelection(2);
325       }
326     }
327   }
328   drawContext::global()->draw();
329 }
330 
disableRedraw()331 void onelabContextWindow::disableRedraw() { win->disableRedraw(); }
332