1 /*
2     This file is part of the Okteta Kasten module, made within the KDE community.
3 
4     SPDX-FileCopyrightText: 2003, 2008-2009 Friedrich W. H. Kossebau <kossebau@kde.org>
5 
6     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8 
9 #include "bytearrayframerenderer.hpp"
10 
11 // lib
12 #include "printcolumnstylist.hpp"
13 // Okteta gui
14 #include <Okteta/OffsetColumnRenderer>
15 #include <Okteta/BorderColumnRenderer>
16 #include <Okteta/ValueByteArrayColumnRenderer>
17 #include <Okteta/CharByteArrayColumnRenderer>
18 #include <Okteta/ByteArrayTableLayout>
19 #include <Okteta/ByteArrayTableRanges>
20 // Okteta core
21 #include <Okteta/AbstractByteArrayModel>
22 #include <Okteta/ValueCodec>
23 #include <Okteta/CharCodec>
24 // Qt
25 #include <QHash>
26 #include <QDateTime>
27 #include <QPainter>
28 #include <QFontMetrics>
29 #include <QFontDatabase>
30 #include <QApplication>
31 
32 static constexpr Okteta::Address DefaultStartOffset = 0;
33 static constexpr Okteta::Address DefaultFirstLineOffset = 0;
34 static constexpr int DefaultNoOfBytesPerLine =  16;
35 static constexpr LayoutStyle DefaultResizeStyle = FixedLayoutStyle; // krazy:exclude=staticobjects
36 static constexpr Okteta::ValueCoding DefaultValueCoding =  Okteta::HexadecimalCoding; // krazy:exclude=staticobjects
37 static constexpr Okteta::CharCoding DefaultCharCoding = Okteta::LocalEncoding; // krazy:exclude=staticobjects
38 
39 static constexpr int BAFInitialHeight = 50;
40 static constexpr int BAFInitialWidth = 50;
41 
ByteArrayFrameRenderer()42 ByteArrayFrameRenderer::ByteArrayFrameRenderer()
43     : mHeight(BAFInitialHeight)
44     , mWidth(BAFInitialWidth)
45     , mResizeStyle(DefaultResizeStyle)
46 {
47     mLayout = new Okteta::ByteArrayTableLayout(DefaultNoOfBytesPerLine, DefaultFirstLineOffset, DefaultStartOffset, 0, 0);
48     mLayout->setNoOfLinesPerPage(noOfLinesPerFrame());
49     mTableRanges = new Okteta::ByteArrayTableRanges(mLayout);
50 
51     // set codecs
52     mValueCodec = Okteta::ValueCodec::createCodec((Okteta::ValueCoding)DefaultValueCoding);
53     mValueCoding = DefaultValueCoding;
54     mCharCodec = Okteta::CharCodec::createCodec((Okteta::CharCoding)DefaultCharCoding);
55     mCharCoding = DefaultCharCoding;
56 
57     mStylist = new Okteta::PrintColumnStylist();
58 
59     // creating the columns in the needed order
60     mOffsetColumnRenderer =
61         new Okteta::OffsetColumnRenderer(mStylist, mLayout, Okteta::OffsetFormat::Hexadecimal);
62     mFirstBorderColumnRenderer =
63         new Okteta::BorderColumnRenderer(mStylist, true, false);
64     mValueColumnRenderer =
65         new Okteta::ValueByteArrayColumnRenderer(mStylist, mByteArrayModel, mLayout, mTableRanges);
66     mSecondBorderColumnRenderer =
67         new Okteta::BorderColumnRenderer(mStylist, true, false);
68     mCharColumnRenderer =
69         new Okteta::CharByteArrayColumnRenderer(mStylist, mByteArrayModel, mLayout, mTableRanges);
70 
71     addColumn(mOffsetColumnRenderer);
72     addColumn(mFirstBorderColumnRenderer);
73     addColumn(mValueColumnRenderer);
74     addColumn(mSecondBorderColumnRenderer);
75     addColumn(mCharColumnRenderer);
76 
77     mValueColumnRenderer->setValueCodec((Okteta::ValueCoding)mValueCoding, mValueCodec);
78     mValueColumnRenderer->setCharCodec(mCharCodec);
79     mCharColumnRenderer->setCharCodec(mCharCodec);
80 
81     setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
82 }
83 
~ByteArrayFrameRenderer()84 ByteArrayFrameRenderer::~ByteArrayFrameRenderer()
85 {
86     delete mStylist;
87     delete mTableRanges;
88     delete mLayout;
89     delete mValueCodec;
90     delete mCharCodec;
91 }
92 
byteArrayModel() const93 Okteta::AbstractByteArrayModel* ByteArrayFrameRenderer::byteArrayModel() const { return mByteArrayModel; }
offset() const94 Okteta::Address ByteArrayFrameRenderer::offset()                         const { return mLayout->startOffset(); }
length() const95 Okteta::Size ByteArrayFrameRenderer::length()                            const { return mLayout->length(); }
96 
noOfBytesPerLine() const97 int ByteArrayFrameRenderer::noOfBytesPerLine()               const { return mLayout->noOfBytesPerLine(); }
firstLineOffset() const98 Okteta::Address ByteArrayFrameRenderer::firstLineOffset()    const { return mLayout->firstLineOffset(); }
startOffset() const99 Okteta::Address ByteArrayFrameRenderer::startOffset()        const { return mLayout->startOffset(); }
layoutStyle() const100 LayoutStyle ByteArrayFrameRenderer::layoutStyle()            const { return mResizeStyle; }
valueCoding() const101 Okteta::ValueCoding ByteArrayFrameRenderer::valueCoding()   const { return mValueCoding; }
byteSpacingWidth() const102 Okteta::PixelX ByteArrayFrameRenderer::byteSpacingWidth()           const { return mValueColumnRenderer->byteSpacingWidth(); }
noOfGroupedBytes() const103 int ByteArrayFrameRenderer::noOfGroupedBytes()               const { return mValueColumnRenderer->noOfGroupedBytes(); }
groupSpacingWidth() const104 Okteta::PixelX ByteArrayFrameRenderer::groupSpacingWidth()          const { return mValueColumnRenderer->groupSpacingWidth(); }
binaryGapWidth() const105 Okteta::PixelX ByteArrayFrameRenderer::binaryGapWidth()             const { return mValueColumnRenderer->binaryGapWidth(); }
showsNonprinting() const106 bool ByteArrayFrameRenderer::showsNonprinting()              const { return mCharColumnRenderer->isShowingNonprinting(); }
substituteChar() const107 QChar ByteArrayFrameRenderer::substituteChar()               const { return mCharColumnRenderer->substituteChar(); }
undefinedChar() const108 QChar ByteArrayFrameRenderer::undefinedChar()                const { return mCharColumnRenderer->undefinedChar(); }
charCoding() const109 Okteta::CharCoding ByteArrayFrameRenderer::charCoding()     const { return mCharCoding; }
charCodingName() const110 const QString& ByteArrayFrameRenderer::charCodingName()      const { return mCharCodec->name(); }
111 
offsetColumnVisible() const112 bool ByteArrayFrameRenderer::offsetColumnVisible() const { return mOffsetColumnRenderer->isVisible(); }
visibleByteArrayCodings() const113 int ByteArrayFrameRenderer::visibleByteArrayCodings() const
114 { return (mValueColumnRenderer->isVisible() ? ValueCodingId : 0) | (mCharColumnRenderer->isVisible() ? CharCodingId : 0); }
115 
height() const116 int ByteArrayFrameRenderer::height() const { return mHeight; }
width() const117 int ByteArrayFrameRenderer::width() const { return mWidth; }
118 
framesCount() const119 int ByteArrayFrameRenderer::framesCount() const
120 {
121     const int charsPerFrame = mLayout->noOfBytesPerLine() * noOfLinesPerFrame();
122 
123     // clever calculation works: at least one page for the rest
124     // hard to describe, think yourself
125     // TODO: needs to include the offset in the first line
126     const int frames = ((mLayout->length() - 1) / charsPerFrame) + 1;
127 
128     return frames;
129 }
130 
setByteArrayModel(Okteta::AbstractByteArrayModel * byteArrayModel,Okteta::Address offset,Okteta::Size length)131 void ByteArrayFrameRenderer::setByteArrayModel(Okteta::AbstractByteArrayModel* byteArrayModel,
132                                                Okteta::Address offset, Okteta::Size length)
133 {
134     mByteArrayModel = byteArrayModel;
135     length = (!byteArrayModel) ?                            0 :
136              (length == -1) ?                               byteArrayModel->size() - offset :
137              (length <= byteArrayModel->size() - offset) ?  length :
138              /* else */                                     byteArrayModel->size() - offset;
139 
140     mValueColumnRenderer->set(byteArrayModel);
141     mCharColumnRenderer->set(byteArrayModel);
142 
143     // affected:
144     // length -> no of lines -> width
145     mLayout->setByteArrayOffset(offset);
146     mLayout->setLength(length);
147 
148     adjustLayoutToSize();
149 }
150 
setHeight(int height)151 void ByteArrayFrameRenderer::setHeight(int height) { mHeight = height; }
setWidth(int width)152 void ByteArrayFrameRenderer::setWidth(int width)
153 {
154     if (mWidth == width) {
155         return;
156     }
157 
158     mWidth = width;
159 
160     adjustToWidth();
161 }
162 
setFirstLineOffset(Okteta::Address firstLineOffset)163 void ByteArrayFrameRenderer::setFirstLineOffset(Okteta::Address firstLineOffset)
164 {
165     if (!mLayout->setFirstLineOffset(firstLineOffset)) {
166         return;
167     }
168 
169     // affects:
170     // the no of lines -> width
171     adjustLayoutToSize();
172 }
173 
setStartOffset(Okteta::Address startOffset)174 void ByteArrayFrameRenderer::setStartOffset(Okteta::Address startOffset)
175 {
176     if (!mLayout->setStartOffset(startOffset)) {
177         return;
178     }
179 
180     // affects:
181     // the no of lines -> width
182     adjustLayoutToSize();
183 }
184 
setBufferSpacing(Okteta::PixelX byteSpacing,int noOfGroupedBytes,Okteta::PixelX groupSpacing)185 void ByteArrayFrameRenderer::setBufferSpacing(Okteta::PixelX byteSpacing, int noOfGroupedBytes, Okteta::PixelX groupSpacing)
186 {
187     if (!mValueColumnRenderer->setSpacing(byteSpacing, noOfGroupedBytes, groupSpacing)) {
188         return;
189     }
190 
191     adjustToWidth();
192 }
193 
setLayoutStyle(LayoutStyle style)194 void ByteArrayFrameRenderer::setLayoutStyle(LayoutStyle style)
195 {
196     if (mResizeStyle == style) {
197         return;
198     }
199 
200     mResizeStyle = style;
201 
202     adjustToWidth();
203 }
204 
setNoOfBytesPerLine(int noOfBytesPerLine)205 void ByteArrayFrameRenderer::setNoOfBytesPerLine(int noOfBytesPerLine)
206 {
207     // if the number is explicitly set we expect a wish for no automatic resize
208     mResizeStyle = FixedLayoutStyle;
209 
210     if (!mLayout->setNoOfBytesPerLine(noOfBytesPerLine)) {
211         return;
212     }
213     adjustToWidth();
214 }
215 
setByteSpacingWidth(Okteta::PixelX byteSpacingWidth)216 void ByteArrayFrameRenderer::setByteSpacingWidth(Okteta::PixelX byteSpacingWidth)
217 {
218     if (!mValueColumnRenderer->setByteSpacingWidth(byteSpacingWidth)) {
219         return;
220     }
221     adjustToWidth();
222 }
223 
setNoOfGroupedBytes(int noOfGroupedBytes)224 void ByteArrayFrameRenderer::setNoOfGroupedBytes(int noOfGroupedBytes)
225 {
226     if (!mValueColumnRenderer->setNoOfGroupedBytes(noOfGroupedBytes)) {
227         return;
228     }
229     adjustToWidth();
230 }
231 
setGroupSpacingWidth(Okteta::PixelX groupSpacingWidth)232 void ByteArrayFrameRenderer::setGroupSpacingWidth(Okteta::PixelX groupSpacingWidth)
233 {
234     if (!mValueColumnRenderer->setGroupSpacingWidth(groupSpacingWidth)) {
235         return;
236     }
237     adjustToWidth();
238 }
239 
setBinaryGapWidth(Okteta::PixelX binaryGapWidth)240 void ByteArrayFrameRenderer::setBinaryGapWidth(Okteta::PixelX binaryGapWidth)
241 {
242     if (!mValueColumnRenderer->setBinaryGapWidth(binaryGapWidth)) {
243         return;
244     }
245     adjustToWidth();
246 }
247 
setSubstituteChar(QChar substituteChar)248 void ByteArrayFrameRenderer::setSubstituteChar(QChar substituteChar)
249 {
250     mCharColumnRenderer->setSubstituteChar(substituteChar);
251 }
252 
setUndefinedChar(QChar undefinedChar)253 void ByteArrayFrameRenderer::setUndefinedChar(QChar undefinedChar)
254 {
255     mCharColumnRenderer->setUndefinedChar(undefinedChar);
256 }
257 
setShowsNonprinting(bool showsNonprinting)258 void ByteArrayFrameRenderer::setShowsNonprinting(bool showsNonprinting)
259 {
260     mCharColumnRenderer->setShowingNonprinting(showsNonprinting);
261 }
262 
setValueCoding(Okteta::ValueCoding valueCoding)263 void ByteArrayFrameRenderer::setValueCoding(Okteta::ValueCoding valueCoding)
264 {
265     if (mValueCoding == valueCoding) {
266         return;
267     }
268 
269     const uint oldCodingWidth = mValueCodec->encodingWidth();
270 
271     Okteta::ValueCodec* newValueCodec =
272         Okteta::ValueCodec::createCodec(valueCoding);
273     if (!newValueCodec) {
274         return;
275     }
276 
277     delete mValueCodec;
278     mValueCodec = newValueCodec;
279     mValueCoding = valueCoding;
280 
281     mValueColumnRenderer->setValueCodec((Okteta::ValueCoding)mValueCoding, mValueCodec);
282 
283     const uint newCodingWidth = mValueCodec->encodingWidth();
284 
285     // change in the width?
286     if (newCodingWidth != oldCodingWidth) {
287         adjustToWidth();
288     }
289 }
290 
setCharCoding(Okteta::CharCoding charCoding)291 void ByteArrayFrameRenderer::setCharCoding(Okteta::CharCoding charCoding)
292 {
293     if (mCharCoding == charCoding) {
294         return;
295     }
296 
297     Okteta::CharCodec* newCharCodec = Okteta::CharCodec::createCodec(charCoding);
298     if (!newCharCodec) {
299         return;
300     }
301 
302     delete mCharCodec;
303     mCharCodec = newCharCodec;
304     mCharCoding = charCoding;
305 
306     mValueColumnRenderer->setCharCodec(mCharCodec);
307     mCharColumnRenderer->setCharCodec(mCharCodec);
308 }
309 
310 // TODO: join with function above!
setCharCoding(const QString & newCharCodingName)311 void ByteArrayFrameRenderer::setCharCoding(const QString& newCharCodingName)
312 {
313     if (charCodingName() == newCharCodingName) {
314         return;
315     }
316 
317     Okteta::CharCodec* newCharCodec = Okteta::CharCodec::createCodec(newCharCodingName);
318     if (!newCharCodec) {
319         return;
320     }
321 
322     delete mCharCodec;
323     mCharCodec = newCharCodec;
324     mCharCoding = Okteta::LocalEncoding; // TODO: add encoding no to every known codec
325 
326     mValueColumnRenderer->setCharCodec(mCharCodec);
327     mCharColumnRenderer->setCharCodec(mCharCodec);
328 }
329 
setFont(const QFont & font)330 void ByteArrayFrameRenderer::setFont(const QFont& font)
331 {
332     mFont = font;
333 
334     // get new values
335     QFontMetrics fontMetrics(font);
336 
337     setLineHeight(fontMetrics.height());
338 
339     // update all dependent structures
340     mLayout->setNoOfLinesPerPage(noOfLinesPerFrame());
341 
342     mOffsetColumnRenderer->setFontMetrics(fontMetrics);
343     mValueColumnRenderer->setFontMetrics(fontMetrics);
344     mCharColumnRenderer->setFontMetrics(fontMetrics);
345 
346     adjustToWidth();
347 }
348 
prepare()349 void ByteArrayFrameRenderer::prepare()
350 {
351 }
352 
renderFrame(QPainter * painter,int frameIndex)353 void ByteArrayFrameRenderer::renderFrame(QPainter* painter, int frameIndex)
354 {
355     painter->setFont(mFont);
356     AbstractColumnFrameRenderer::renderFrame(painter, frameIndex);
357 }
358 
adjustToWidth()359 void ByteArrayFrameRenderer::adjustToWidth()
360 {
361     adjustToLayoutNoOfBytesPerLine();
362     adjustLayoutToSize();
363 }
364 
adjustLayoutToSize()365 void ByteArrayFrameRenderer::adjustLayoutToSize()
366 {
367     // check whether there is a change with the numbers of fitting bytes per line
368     if (mResizeStyle != FixedLayoutStyle) {
369         const int bytesPerLine = fittingBytesPerLine();
370 
371         // changes?
372         if (mLayout->setNoOfBytesPerLine(bytesPerLine)) {
373             adjustToLayoutNoOfBytesPerLine();
374         }
375     }
376 
377     setNoOfLines(mLayout->noOfLines());
378 }
379 
adjustToLayoutNoOfBytesPerLine()380 void ByteArrayFrameRenderer::adjustToLayoutNoOfBytesPerLine()
381 {
382     mValueColumnRenderer->resetXBuffer();
383     mCharColumnRenderer->resetXBuffer();
384 
385     updateWidths();
386 }
387 
showOffsetColumn(bool visible)388 void ByteArrayFrameRenderer::showOffsetColumn(bool visible)
389 {
390     bool OCVisible = mOffsetColumnRenderer->isVisible();
391     // no change?
392     if (OCVisible == visible) {
393         return;
394     }
395 
396     mOffsetColumnRenderer->setVisible(visible);
397     mFirstBorderColumnRenderer->setVisible(visible);
398 
399     adjustToWidth();
400 }
401 
402 #if 0
403 QSize ByteArrayFrameRenderer::sizeHint() const
404 {
405     return QSize(columnsWidth(), columnsHeight());
406 }
407 
408 QSize ByteArrayFrameRenderer::minimumSizeHint() const
409 {
410     // TODO: better minimal width (visibility!)
411 
412     const int width =
413         mOffsetColumnRenderer->visibleWidth()
414         + mFirstBorderColumnRenderer->visibleWidth()
415         + mSecondBorderColumnRenderer->visibleWidth()
416         + mValueColumnRenderer->byteWidth()
417         + mCharColumnRenderer->byteWidth();
418     const int height = lineHeight() * noOfLines();
419 
420     return QSize(width, height);
421 }
422 #endif
423 
fittingBytesPerLine() const424 int ByteArrayFrameRenderer::fittingBytesPerLine() const
425 {
426     const Okteta::PixelX nonDataWidth =
427         mOffsetColumnRenderer->visibleWidth()
428         + mFirstBorderColumnRenderer->visibleWidth()
429         + mSecondBorderColumnRenderer->visibleWidth();
430 
431     // abstract offset and border columns width
432     const Okteta::PixelX maxDataWidth = width() - nonDataWidth;
433 
434     // prepare needed values
435     const Okteta::PixelX charByteWidth = mCharColumnRenderer->isVisible() ? mCharColumnRenderer->digitWidth() : 0;
436     const Okteta::PixelX valueByteWidth = mValueColumnRenderer->isVisible() ? mValueColumnRenderer->byteWidth() : 0;
437     const Okteta::PixelX valueByteSpacingWidth = mValueColumnRenderer->isVisible() ? mValueColumnRenderer->byteSpacingWidth() : 0;
438     Okteta::PixelX valueByteGroupSpacingWidth;
439     int noOfGroupedBytes = mValueColumnRenderer->noOfGroupedBytes();
440     // no grouping?
441     if (noOfGroupedBytes == 0) {
442         // faking grouping by 1
443         noOfGroupedBytes = 1;
444         valueByteGroupSpacingWidth = 0;
445     } else {
446         valueByteGroupSpacingWidth = mValueColumnRenderer->isVisible() ? mValueColumnRenderer->groupSpacingWidth() : 0;
447     }
448 
449     Okteta::PixelX valueByteGroupWidth =  noOfGroupedBytes * valueByteWidth + (noOfGroupedBytes - 1) * valueByteSpacingWidth;
450     Okteta::PixelX charByteGroupWidth =   noOfGroupedBytes * charByteWidth;
451     Okteta::PixelX charAndValueGroupWidth = (valueByteGroupWidth + valueByteGroupSpacingWidth) + charByteGroupWidth;
452 
453     // calculate fitting groups per line
454 
455     // the last value byte group does not need a group spacing behind, but it gets into the calculation.
456     // so we simply add one to the max width to match that
457     const int fittingGroupsPerLine = (maxDataWidth + valueByteGroupSpacingWidth)
458                                      / charAndValueGroupWidth;
459 
460     // calculate the fitting bytes per line by groups
461     int fittingBytesPerLine = noOfGroupedBytes * fittingGroupsPerLine;
462 
463     // groups can be split and not only full groups are requested?
464     if (noOfGroupedBytes > 1 && mResizeStyle == FullSizeLayoutStyle) {
465         const int leftDataWidth = maxDataWidth - fittingGroupsPerLine * charAndValueGroupWidth;
466 
467         if (leftDataWidth > 0) {
468             const int charAndValueByteWidth = valueByteWidth + valueByteSpacingWidth + charByteWidth;
469             // the last value byte  does not need a spacing behind, but it gets into the calculation.
470             // so we simply add one to the left width to match that
471             const int ungroupedBytes = (leftDataWidth + valueByteSpacingWidth)
472                                        / charAndValueByteWidth;
473             fittingBytesPerLine += ungroupedBytes;
474         }
475 
476         // is there not even the space for a single byte?
477 //         if( fittingBytesPerLine < 1 )
478         // ensure at least one byte per line
479 //             fittingBytesPerLine = 1;
480     } else {
481         // is there not the space for a single group?
482 //         if( fittingBytesPerLine < 1 )
483         // ensures at least one group
484 //             fittingBytesPerLine = noOfGroupedBytes;
485     }
486 
487     return fittingBytesPerLine;
488 }
489 
showByteArrayColumns(int newColumns)490 void ByteArrayFrameRenderer::showByteArrayColumns(int newColumns)
491 {
492     int columns = visibleByteArrayCodings();
493 
494     // no changes or no column selected?
495     if (newColumns == columns || !(newColumns & (ValueCodingId | CharCodingId))) {
496         return;
497     }
498 
499     mValueColumnRenderer->setVisible(ValueCodingId & newColumns);
500     mCharColumnRenderer->setVisible(CharCodingId & newColumns);
501     mSecondBorderColumnRenderer->setVisible(newColumns == (ValueCodingId | CharCodingId));
502 
503     adjustToWidth();
504 }
505