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