1 
2 /* The evaluation types for a cell.
3 CT_DATA: "Data"
4 CT_CODE: "Operation"
5 CT_VARD: "Variable Assign"
6 CT_VARU: "Variable Read"
7 CT_VIEWH: "Horizontal View"
8 CT_VIEWV: "Vertical View"
9 */
10 enum { CT_DATA = 0, CT_CODE, CT_VARD, CT_VIEWH, CT_VARU, CT_VIEWV };
11 
12 /* The drawstyles for a cell:
13 
14 */
15 enum { DS_GRID, DS_BLOBSHIER, DS_BLOBLINE };
16 
17 /**
18     The Cell structure represents the editable cells in the sheet.
19 
20     They are mutable structures containing a text and grid object. Along with
21     formatting information.
22 */
23 struct Cell {
24     Cell *parent;
25     int sx, sy, ox, oy, minx, miny, ycenteroff, txs, tys;
26     int celltype;
27     Text text;
28     Grid *grid;
29     uint cellcolor, textcolor, actualcellcolor;
30     bool tiny;
31     bool verticaltextandgrid;
32     wxUint8 drawstyle;
33 
34     Cell(Cell *_p = nullptr, Cell const *_clonefrom = nullptr, int _ct = CT_DATA,
35          Grid *_g = nullptr)
parentCell36         : parent(_p),
37           sx(0),
38           sy(0),
39           ox(0),
40           oy(0),
41           minx(0),
42           miny(0),
43           celltype(_ct),
44           grid(_g),
45           tiny(false),
46           verticaltextandgrid(true),
47           drawstyle(DS_GRID),
48           cellcolor(0xFFFFFF),
49           textcolor(0x000000) {
50         text.cell = this;
51         if (_g) _g->cell = this;
52         if (_p) {
53             text.relsize = _p->text.relsize;
54             verticaltextandgrid = _p->verticaltextandgrid;
55         }
56         if (_clonefrom) CloneStyleFrom(_clonefrom);
57     }
58 
~CellCell59     ~Cell() { DELETEP(grid); }
ClearCell60     void Clear() {
61         DELETEP(grid);
62         text.t.Clear();
63         text.image = nullptr;
64         Reset();
65     }
66 
HasTextCell67     bool HasText() const { return !text.t.empty(); }
HasTextSizeCell68     bool HasTextSize() const { return HasText() || text.relsize; }
HasTextStateCell69     bool HasTextState() const { return HasTextSize() || text.image; }
HasHeaderCell70     bool HasHeader() const { return HasText() || text.image; }
HasContentCell71     bool HasContent() const { return HasHeader() || grid; }
GridShownCell72     bool GridShown(Document *doc) const {
73         return grid && (!grid->folded || this == doc->curdrawroot);
74     }
MinRelsizeCell75     int MinRelsize()  // the smallest relsize is actually the biggest text
76     {
77         int rs = INT_MAX;
78         if (grid) {
79             rs = grid->MinRelsize(rs);
80         } else if (HasText()) {
81             // the "else" causes oversized titles but a readable grid when you zoom, if only
82             // the grid has been shrunk
83             rs = text.MinRelsize(rs);
84         }
85         return rs;
86     }
87 
EstimatedMemoryUseCell88     size_t EstimatedMemoryUse() {
89         return sizeof(Cell) + text.EstimatedMemoryUse() + (grid ? grid->EstimatedMemoryUse() : 0);
90     }
91 
LayoutCell92     void Layout(Document *doc, wxDC &dc, int depth, int maxcolwidth, bool forcetiny) {
93         tiny = (text.filtered && !grid) || forcetiny ||
94                doc->PickFont(dc, depth, text.relsize, text.stylebits);
95         int ixs = 0, iys = 0;
96         if (!tiny) sys->ImageSize(text.DisplayImage(), ixs, iys);
97         int leftoffset = 0;
98         if (!HasText()) {
99             if (!ixs || !iys) {
100                 sx = sy = tiny ? 1 : dc.GetCharHeight();
101             } else {
102                 leftoffset = dc.GetCharHeight();
103             }
104         } else {
105             text.TextSize(dc, sx, sy, tiny, leftoffset, maxcolwidth);
106         }
107         if (ixs && iys) {
108             sx += ixs + 2;
109             sy = max(iys + 2, sy);
110         }
111         text.extent = sx + depth * dc.GetCharHeight();
112         txs = sx;
113         tys = sy;
114         if (GridShown(doc)) {
115             if (HasHeader()) {
116                 if (verticaltextandgrid) {
117                     int osx = sx;
118                     if (drawstyle == DS_BLOBLINE && !tiny) sy += 4;
119                     grid->Layout(doc, dc, depth, sx, sy, leftoffset, sy, tiny || forcetiny);
120                     sx = max(sx, osx);
121                 } else {
122                     int osy = sy;
123                     if (drawstyle == DS_BLOBLINE && !tiny) sx += 18;
124                     grid->Layout(doc, dc, depth, sx, sy, sx, 0, tiny || forcetiny);
125                     sy = max(sy, osy);
126                 }
127             } else
128                 tiny = grid->Layout(doc, dc, depth, sx, sy, 0, 0, forcetiny);
129         }
130         ycenteroff = !verticaltextandgrid ? (sy - tys) / 2 : 0;
131         if (!tiny) {
132             sx += g_margin_extra * 2;
133             sy += g_margin_extra * 2;
134         }
135     }
136 
RenderCell137     void Render(Document *doc, int bx, int by, wxDC &dc, int depth, int ml, int mr, int mt, int mb,
138                 int maxcolwidth, int cell_margin) {
139         // Choose color from celltype (program operations)
140         switch (celltype) {
141             case CT_VARD: actualcellcolor = 0xFF8080; break;
142             case CT_VARU: actualcellcolor = 0xFFA0A0; break;
143             case CT_VIEWH:
144             case CT_VIEWV: actualcellcolor = 0x80FF80; break;
145             case CT_CODE: actualcellcolor = 0x8080FF; break;
146             default: actualcellcolor = cellcolor; break;
147         }
148         uint parentcolor = doc->Background();
149         if (parent && this != doc->curdrawroot) {
150             Cell *p = parent;
151             while (p && p->drawstyle == DS_BLOBLINE)
152                 p = p == doc->curdrawroot ? nullptr : p->parent;
153             if (p) parentcolor = p->actualcellcolor;
154         }
155         if (drawstyle == DS_GRID && actualcellcolor != parentcolor) {
156             DrawRectangle(dc, actualcellcolor, bx - ml, by - mt, sx + ml + mr, sy + mt + mb);
157         }
158         if (drawstyle != DS_GRID && HasContent() && !tiny) {
159             if (actualcellcolor == parentcolor)
160             {
161                 uchar *cp = (uchar *)&actualcellcolor;
162                 loop(i, 4) cp[i] = cp[i] * 850 / 1000;
163             }
164             dc.SetBrush(wxBrush(actualcellcolor));
165             dc.SetPen(wxPen(actualcellcolor));
166 
167             if (drawstyle == DS_BLOBSHIER)
168                 dc.DrawRoundedRectangle(bx - cell_margin, by - cell_margin, minx + cell_margin * 2,
169                                         miny + cell_margin * 2, sys->roundness);
170             else if (HasHeader())
171                 dc.DrawRoundedRectangle(bx - cell_margin + g_margin_extra / 2,
172                                         by - cell_margin + ycenteroff + g_margin_extra / 2,
173                                         txs + cell_margin * 2 + g_margin_extra,
174                                         tys + cell_margin * 2 + g_margin_extra, sys->roundness);
175             // FIXME: this half a g_margin_extra is a bit of hack
176         }
177         dc.SetTextBackground(wxColour(actualcellcolor));
178         int xoff = verticaltextandgrid ? 0 : text.extent - depth * dc.GetCharHeight();
179         int yoff = text.Render(doc, bx, by + ycenteroff, depth, dc, xoff, maxcolwidth);
180         yoff = verticaltextandgrid ? yoff : 0;
181         if (GridShown(doc)) grid->Render(doc, bx, by, dc, depth, sx - xoff, sy - yoff, xoff, yoff);
182     }
183 
CloneStyleFromCell184     void CloneStyleFrom(Cell const *o) {
185         cellcolor = o->cellcolor;
186         textcolor = o->textcolor;
187         verticaltextandgrid = o->verticaltextandgrid;
188         drawstyle = o->drawstyle;
189         text.stylebits = o->text.stylebits;
190     }
191 
192     /* Clones _p making a new copy of it. This does not mutate the called on cell */
CloneCell193     Cell *Clone(Cell *_p) const {
194         Cell *c = new Cell(_p, this, celltype, grid ? new Grid(grid->xs, grid->ys) : nullptr);
195         c->text = text;
196         c->text.cell = c;
197         if (grid) {
198             grid->Clone(c->grid);
199         }
200         return c;
201     }
202 
IsInsideCell203     bool IsInside(int x, int y) const { return x >= 0 && y >= 0 && x < sx && y < sy; }
GetXCell204     int GetX(Document *doc) const { return ox + (parent ? parent->GetX(doc) : doc->hierarchysize); }
GetYCell205     int GetY(Document *doc) const { return oy + (parent ? parent->GetY(doc) : doc->hierarchysize); }
DepthCell206     int Depth() const { return parent ? parent->Depth() + 1 : 0; }
ParentCell207     Cell *Parent(int i) { return i ? parent->Parent(i - 1) : this; }
SetParentCell208     Cell *SetParent(Cell *g) {
209         parent = g;
210         return this;
211     }
IsParentOfCell212     bool IsParentOf(const Cell *c) { return c->parent == this || (c->parent && IsParentOf(c->parent)); }
213 
SwapColorCell214     uint SwapColor(uint c) { return ((c & 0xFF) << 16) | (c & 0xFF00) | ((c & 0xFF0000) >> 16); }
ToTextCell215     wxString ToText(int indent, const Selection &s, int format, Document *doc) {
216         wxString str = text.ToText(indent, s, format);
217         if (format == A_EXPCSV) {
218             if (grid) return grid->ToText(indent, s, format, doc);
219             str.Replace(L"\"", L"\"\"");
220             return L"\"" + str + L"\"";
221         }
222         if (s.cursor != s.cursorend) return str;
223         str.Append(L"\n");
224         if (grid) str.Append(grid->ToText(indent, s, format, doc));
225         if (format == A_EXPXML) {
226             str.Prepend(L">");
227             if (text.relsize) {
228                 str.Prepend(L"\"");
229                 str.Prepend(wxString() << -text.relsize);
230                 str.Prepend(L" relsize=\"");
231             }
232             if (text.stylebits) {
233                 str.Prepend(L"\"");
234                 str.Prepend(wxString() << text.stylebits);
235                 str.Prepend(L" stylebits=\"");
236             }
237             if (cellcolor != doc->Background()) {
238                 str.Prepend(L"\"");
239                 str.Prepend(wxString() << cellcolor);
240                 str.Prepend(L" colorbg=\"");
241             }
242             if (textcolor != 0x000000) {
243                 str.Prepend(L"\"");
244                 str.Prepend(wxString() << textcolor);
245                 str.Prepend(L" colorfg=\"");
246             }
247             str.Prepend(L"<cell");
248             str.Append(L' ', indent);
249             str.Append(L"</cell>\n");
250         } else if (format == A_EXPHTMLT) {
251             wxString style;
252             if (text.stylebits & STYLE_BOLD) style += L"font-weight: bold;";
253             if (text.stylebits & STYLE_ITALIC) style += L"font-style: italic;";
254             if (text.stylebits & STYLE_FIXED) style += L"font-family: monospace;";
255             if (text.stylebits & STYLE_UNDERLINE) style += L"text-decoration: underline;";
256             if (cellcolor != doc->Background())
257                 style += wxString::Format(L"background-color: #%06X;", SwapColor(cellcolor));
258             if (textcolor != 0x000000)
259                 style += wxString::Format(L"color: #%06X;", SwapColor(textcolor));
260             str.Prepend(L"<td style=\"" + style + L"\">");
261             str.Append(L' ', indent);
262             str.Append(L"</td>\n");
263         } else if (format == A_EXPHTMLO && text.t.Len()) {
264             wxString h = wxString(L"h") + wxChar(L'0' + indent / 2) + L">";
265             str.Prepend(L"<" + h);
266             str.Append(L' ', indent);
267             str.Append(L"</" + h + L"\n");
268         }
269         str.Pad(indent, L' ', false);
270         return str;
271     }
272 
RelSizeCell273     void RelSize(int dir, int zoomdepth) {
274         text.RelSize(dir, zoomdepth);
275         if (grid) grid->RelSize(dir, zoomdepth);
276     }
277 
ResetCell278     void Reset() { ox = oy = sx = sy = minx = miny = 0; }
ResetChildrenCell279     void ResetChildren() {
280         Reset();
281         if (grid) grid->ResetChildren();
282     }
283 
ResetLayoutCell284     void ResetLayout() {
285         Reset();
286         if (parent) parent->ResetLayout();
287     }
288 
LazyLayoutCell289     void LazyLayout(Document *doc, wxDC &dc, int depth, int maxcolwidth, bool forcetiny) {
290         if (sx == 0) {
291             Layout(doc, dc, depth, maxcolwidth, forcetiny);
292             minx = sx;
293             miny = sy;
294         } else {
295             sx = minx;
296             sy = miny;
297         }
298     }
299 
AddUndoCell300     void AddUndo(Document *doc) {
301         ResetLayout();
302         doc->AddUndo(this);
303     }
304 
SaveCell305     void Save(wxDataOutputStream &dos) const {
306         dos.Write8(celltype);
307         dos.Write32(cellcolor);
308         dos.Write32(textcolor);
309         dos.Write8(drawstyle);
310         if (HasTextState()) {
311             dos.Write8(grid ? TS_BOTH : TS_TEXT);
312             text.Save(dos);
313             if (grid) grid->Save(dos);
314         } else if (grid) {
315             dos.Write8(TS_GRID);
316             grid->Save(dos);
317         } else {
318             dos.Write8(TS_NEITHER);
319         }
320     }
321 
322     Grid *AddGrid(int x = 1, int y = 1) {
323         if (!grid) {
324             grid = new Grid(x, y, this);
325             grid->InitCells(this);
326             if (parent) grid->CloneStyleFrom(parent->grid);
327         }
328         return grid;
329     }
330 
LoadGridCell331     Cell *LoadGrid(wxDataInputStream &dis, int &numcells, int &textbytes) {
332         int xs = dis.Read32();
333         Grid *g = new Grid(xs, dis.Read32());
334         grid = g;
335         g->cell = this;
336         if (!g->LoadContents(dis, numcells, textbytes)) return nullptr;
337         return this;
338     }
339 
LoadWhichCell340     static Cell *LoadWhich(wxDataInputStream &dis, Cell *_p, int &numcells, int &textbytes) {
341         Cell *c = new Cell(_p, nullptr, dis.Read8());
342         numcells++;
343         if (sys->versionlastloaded >= 8) {
344             c->cellcolor = dis.Read32() & 0xFFFFFF;
345             c->textcolor = dis.Read32() & 0xFFFFFF;
346         }
347         if (sys->versionlastloaded >= 15) c->drawstyle = dis.Read8();
348         int ts;
349         switch (ts = dis.Read8()) {
350             case TS_BOTH:
351             case TS_TEXT:
352                 c->text.Load(dis);
353                 textbytes += c->text.t.Len();
354                 if (ts == TS_TEXT) return c;
355             case TS_GRID: return c->LoadGrid(dis, numcells, textbytes);
356             case TS_NEITHER: return c;
357             default: return nullptr;
358         }
359     }
360 
EvalCell361     Cell *Eval(Evaluator &ev) {
362         // Evaluates the internal grid if it exists, otherwise, evaluate the text.
363         return grid ? grid->Eval(ev) : text.Eval(ev);
364     }
365 
PasteCell366     void Paste(Document *doc, const Cell *c, Selection &s) {
367         parent->AddUndo(doc);
368         ResetLayout();
369         if (c->HasText()) {
370             if (!HasText() || !s.TextEdit()) {
371                 cellcolor = c->cellcolor;
372                 textcolor = c->textcolor;
373                 text.stylebits = c->text.stylebits;
374             }
375             text.Insert(doc, c->text.t, s);
376         }
377         if (c->text.image) text.image = c->text.image;
378         if (c->grid) {
379             auto cg = new Grid(c->grid->xs, c->grid->ys);
380             cg->cell = this;
381             c->grid->Clone(cg);
382             // Note: deleting grid may invalidate c if its a child of grid, so clear it.
383             c = nullptr;
384             DELETEP(grid);  // FIXME: could merge instead?
385             grid = cg;
386             if (!HasText()) grid->MergeWithParent(parent->grid, s);  // deletes grid/this.
387         }
388     }
389 
FindNextSearchMatchCell390     Cell *FindNextSearchMatch(wxString &search, Cell *best, Cell *selected, bool &lastwasselected) {
391         if (text.t.Lower().Find(search) >= 0) {
392             if (lastwasselected) best = this;
393             lastwasselected = false;
394         }
395         if (selected == this) lastwasselected = true;
396         if (grid) best = grid->FindNextSearchMatch(search, best, selected, lastwasselected);
397         return best;
398     }
399 
FindLinkCell400     Cell *FindLink(Selection &s, Cell *link, Cell *best, bool &lastthis, bool &stylematch,
401                    bool forward) {
402         if (grid) best = grid->FindLink(s, link, best, lastthis, stylematch, forward);
403         if (link == this) {
404             lastthis = true;
405             return best;
406         }
407         if (link->text.ToText(0, s, A_EXPTEXT) == text.t) {
408             if (link->text.stylebits != text.stylebits || link->cellcolor != cellcolor ||
409                 link->textcolor != textcolor) {
410                 if (!stylematch) best = nullptr;
411                 stylematch = true;
412             } else if (stylematch) {
413                 return best;
414             }
415             if (!best || lastthis) {
416                 lastthis = false;
417                 return this;
418             }
419         }
420         return best;
421     }
422 
FindReplaceAllCell423     void FindReplaceAll(const wxString &str) {
424         if (grid) grid->FindReplaceAll(str);
425         text.ReplaceStr(str);
426     }
427 
FindExactCell428     Cell *FindExact(wxString &s) {
429         return text.t == s ? this : (grid ? grid->FindExact(s) : nullptr);
430     }
431 
ImageRefCountCell432     void ImageRefCount() {
433         if (grid) grid->ImageRefCount();
434         if (text.image) text.image->trefc++;
435     }
436 
SetBorderCell437     void SetBorder(int width) {
438         if (grid) grid->user_grid_outer_spacing = width;
439     }
440 
ColorChangeCell441     void ColorChange(int which, uint color) {
442         switch (which) {
443             case A_CELLCOLOR: cellcolor = color; break;
444             case A_TEXTCOLOR: textcolor = color; break;
445             case A_BORDCOLOR:
446                 if (grid) grid->bordercolor = color;
447                 break;
448         }
449     }
450 
SetGridTextLayoutCell451     void SetGridTextLayout(int ds, bool vert, bool noset) {
452         if (!noset) verticaltextandgrid = vert;
453         if (ds != -1) drawstyle = ds;
454         if (grid) grid->SetGridTextLayout(ds, vert, noset, grid->SelectAll());
455     }
456 
IsTagCell457     bool IsTag(Document *doc) { return doc->tags.find(text.t) != doc->tags.end(); }
MaxDepthLeavesCell458     void MaxDepthLeaves(int curdepth, int &maxdepth, int &leaves) {
459         if (curdepth > maxdepth) maxdepth = curdepth;
460         if (grid)
461             grid->MaxDepthLeaves(curdepth + 1, maxdepth, leaves);
462         else
463             leaves++;
464     }
465 
ColWidthCell466     int ColWidth() {
467         return parent ? parent->grid->colwidths[parent->grid->FindCell(this).x]
468                       : sys->defaultmaxcolwidth;
469     }
470 
471     void CollectCells(Vector<Cell *> &itercells, bool recurse = true) {
472         itercells.push() = this;
473         if (grid && recurse) grid->CollectCells(itercells);
474     }
475 };
476