source: projects/punch-card/punch-card-editor/src/text/editor.cc @ 52

Last change on this file since 52 was 52, checked in by sven, 14 years ago

Punch Card Editor: Code reordering and class renaming, rewriting.
Now using the namespace QPunchCard everywhere.

Parted the whole application into 5 parts/directories:

  • app: The application core with MainWindow and main() function
  • qpunchcard: Everything directly binary card related (no interpretation): modeling, input/output, view. Most important classes are Card, Deck, FileFormat, Widget
  • text: Everything related to text interpretation of Cards/Card Decks. Having the abstract Codec classes and the Text::Editor, Text::EditorDock
  • deckviewer: Application components like binary card editing central widget (CardEditor) and Navigator (Model View Controller classes)
  • driver: Basis for the driver framework, must be written soon.

Deck now hides the complete Storage to implement frontend methods that
implement versioning (Undo framework). All code was cleaned up, but doesn't
compile right now (still quite some non heavy errors).

-- Sven @ workstation

File size: 13.5 KB
Line 
1#include "editor.h"
2
3#include <QPainter>
4#include <QTextBlock>
5#include <QBoxLayout>
6#include <QLabel>
7#include <QComboBox>
8#include <QPushButton>
9
10using namespace QPunchCard;
11
12Text::Editor::Editor(App::MainWindow* main) : QPlainTextEdit(main), main(main) {
13        row_area = new NumberArea(this);
14        col_area = new NumberArea(this);
15
16        connect(this->document(), SIGNAL(contentsChange(int,int,int)),
17                this, SLOT(translateChanges(int, int, int)));
18
19        // Codec erstellen
20        codec = QSharedPointer<const Codec>( CodecFactory::createCodec("o29_code") );
21        if(!codec) {
22                qDebug("Got NULL Codec :-(");
23        }
24
25        // Font einstellen
26        QFont font;
27        font.setFamily("Courier");
28        font.setFixedPitch(true);
29        font.setPointSize(13);
30        setFont(font);
31
32        QFont col_font = font;
33        col_font.setFamily("Verdana");
34        col_font.setPointSize(7);
35        col_area->setFont(col_font);
36
37        connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
38        connect(this, SIGNAL(updateRequest(const QRect &, int)), this, SLOT(updateLineNumberArea(const QRect &, int)));
39        connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentCursorPos()));
40
41        updateLineNumberAreaWidth(0);
42        highlightCurrentCursorPos();
43}
44
45void Text::Editor::contentsChanged(DeckIndex lower_card, DeckIndex upper_card) {
46        // translate indices to text rows and load new text
47        qDebug("Loading new text between [%d, %d]", (int)lower_card, (int)upper_card);
48        for(int i = lower_card; i < upper_card; i++) {
49                DeckIndex index(main->deck, i);
50                if(!index.isValid()) {
51                        qDebug("text update: skipping invalid %d", i);
52                        continue;
53                }
54
55                // Ganzen Block markieren und ueberschreiben
56                QTextCursor cursor( this->document()->findBlockByNumber(i) );
57                cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
58                // an dieser Stelle: Ungueltige Zeichen maskieren (per anderem QTextCharFormat)!
59                cursor.insertText(QString("Zeile %1 von Lochkarte").arg(i));
60        }
61}
62
63// das wird *NICHT* repainten, das muss danach gemacht werden
64bool Text::Editor::checkValidity(int pos) {
65        QChar c = document()->characterAt(pos);
66        if(c.isNull()) {
67                qDebug("checkValidity: %d is not a valid position", pos);
68                return true;
69        }
70        // check character
71        Q_ASSERT(codec);
72        if(codec->canEncode(c.toAscii())) {
73                // everything fine with this character
74                // just take a look wether it was declared as bad sometime
75                // ago...
76                qDebug("Codec says %c can be encoded", c.toAscii());
77                for(int bads = 0; bads < invalid_characters.length(); bads++) {
78                        if( pos == invalid_characters[bads].cursor.anchor() ) {
79                                qDebug("Character at %d is no more bad", pos);
80                                // dieses Zeichen loeschen und iterieren stoppen
81                                invalid_characters.removeAt(bads);
82                                emit invalidCharactersCountChanged( invalid_characters.count() );
83                                break;
84                        }
85                }
86
87                return true;
88        } else {
89                // this is a bad character that cannot be encoded
90                // with the current Codec.
91                qDebug("Codec says %c cannot be encoded", c.toAscii());
92                // #1: Mark character
93                QTextEdit::ExtraSelection selection;
94                selection.cursor = QTextCursor(document());
95                selection.cursor.setPosition(pos);
96                selection.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
97                selection.format.setFontWeight(QFont::Bold);
98                selection.format.setForeground(Qt::red);
99                selection.format.setBackground(Qt::yellow);
100                selection.format.setToolTip(tr("This character cannot be punched on the punch card using the current codec"));
101                // #2: Toggle anywhere in the superclass that the text
102                //     contains bad characters
103                invalid_characters.append(selection);
104                emit invalidCharactersCountChanged( invalid_characters.count() );
105                // das emitten auf ein *Zeichen* hin ist ggf. schlecht, wenn die
106                // Funktion etwa in translateChanges in einer schleife aufgerufen wird
107                // und ein reciever dann sehr viel zeug hintereinander macht...
108
109                // #3: Return that this character is invalid
110                return false;
111        }
112}
113
114void Text::Editor::translateChanges(int position, int charsRemoved, int charsAdded) {
115        // Dieser slot wird durch das QTextDocument aufgerufen, wenn der Benutzer
116        // den Inhalt geaendert hat.
117        // 1) Ueberpruefen, ob Text uebersetzbar ist
118        // 2) Uebersetzen ins Kartenmodell und als Signal weitergeben.
119        Q_ASSERT(main != 0);
120        Q_ASSERT(main->deck != 0);
121        // NATÜRLICH muss ZUSAETZLICH auch noch das MODEL DIREKT VERAENDERT werden.
122        // also die betreffenden Karten AUSTAUSCHEN durch den neuen Inhalt, der dann
123        // per Codec uebersetzt wird!
124        qDebug("Pos: %d, removed: %d, added: %d", position, charsRemoved, charsAdded);
125        // veraenderte Zeilen ausrechnen
126        int start_pos = position;
127        int end_pos = position + charsAdded - charsRemoved;
128        QTextBlock start_block = document()->findBlock(start_pos);
129        QTextBlock end_block = document()->findBlock(end_pos);
130        DeckIndex start_block_number = main->deck->createIndex( start_block.blockNumber() );
131        DeckIndex end_block_number = main->deck->createIndex( end_block.blockNumber() );
132
133
134        if(!(end_pos >= start_pos)) { // sonst waers echt bloed.
135                qDebug("Das momentane END/START-Verhaeltnis ist bloed.");
136        }
137
138        if(! start_block.isValid()) {
139                // keine gute Grundlage.
140                qDebug("Pretty bad: Start block pos is invalid.");
141                return;
142        }
143        if(! start_block_number.isValid()) {
144                // auch keine gute Grundlage
145                qDebug("Pretty bad: Start deck index pos is invalid");
146                qDebug() << start_block_number;
147                return;
148        }
149
150        // Ueber Buchstaben iterieren und auf Gueltigkeit ueberpruefen,
151        // wird diese auch textlich auszeichnen
152        for(int i = start_pos; i < end_pos; i++) {
153                if(!checkValidity(i)) {
154                        qDebug("Got invalid character at %d", i);
155                }
156                // neue text highlights painten
157                paintHighlights();
158        }
159
160        if(! end_block.isValid()) {
161                // das Dokument ist *kuerzer* geworden
162                DeckIndex new_max_blocks = main->deck->createIndex( document()->lineCount() );
163                // sicherheitshalber
164                if(! new_max_blocks.isValid()) {
165                        qDebug() << "new max blocks is invalid " << new_max_blocks;
166                        return;
167                }
168
169                // Alle Blocks von new_max_blocks bis end_block_number loeschen
170                main->deck->erase(new_max_blocks, end_block_number);
171        }
172
173        // start_block und end_block sind innerhalb des Dokumentes.
174        // pruefe, ob Dokument laenger geworden ist
175
176        if(end_block_number > main->deck->count()) {
177                // ja, es sind
178                int new_lines_count = main->deck->count() - end_block_number;
179                // Reihen dazugekommen.
180                qDebug("Inserting %d cards at end", new_lines_count);
181                main->deck->insertTimes( main->deck->createIndex( main->deck->count()-1), new_lines_count);
182        }
183
184        // jetzt: ueber bloecke iterieren.
185        for(QTextBlock current_block = start_block;
186            current_block < end_block || current_block == end_block;
187            current_block = current_block.next()) {
188                if(!translateBlock(current_block)) {
189                        qDebug("Translation stopped at block %d, failing. breaking translation", current_block.blockNumber());
190                        break;
191                }
192        }
193}
194
195/**
196 * BUG: Text den man direkt danach schreibt, wird auch gelb wie ungueltige Chars.
197 *   Ursache: Formatierung wird immer auch auf Zeichen direkt danach angewandt.
198 *   Loesung: Eine weitere Formatierung fuer zeichen  direkt danach anwenden, wenn
199 *   diese eingegeben werden
200 *
201 **/
202bool Text::Editor::translateBlock(const QTextBlock& block) {
203        Q_ASSERT(!codec.isNull());
204        Q_ASSERT(!main->deck.isNull());
205        if(! block.isValid()) {
206                // urhh
207                qDebug("translateBlock called on invalid block");
208                return false;
209        }
210
211        DeckIndex card_index = main->deck->createIndex( block.blockNumber() );
212
213        // und nun: Block uebersetzen.
214        qDebug() << "Going to translate to " << card_index;
215        QString text = block.text();
216        qDebug() << "Contents:" <<  text;
217
218        if(!card_index.isValid()) {
219                qDebug("Invalid card index, won't translate");
220                return false;
221        }
222
223        if(!codec->canEncode(text)) {
224                // gut, der text ist halt schlecht. sollte an anderer Stelle
225                // schon bemerkt worden sein
226                qDebug("Codec will make errors while translating");
227        }
228
229        // start translation
230        qDebug() << "starting translation";
231        codec->fromAscii(text, &main->deck->at( card_index ) );
232        qDebug("Translated successfully");
233        // urhm... that was all the magic.
234        // send "you were changed" thingy! :-)
235        main->deck->emitChanged(card_index, card_index);
236        return true;
237}
238
239QSize Text::Editor::numberAreaSize(const NumberArea* area) const {
240        if(area == row_area) {
241                int digits = 1;
242                int max = qMax(1, blockCount());
243                while (max >= 10) {
244                        max /= 10;
245                        ++digits;
246                }
247
248                int space = 10 + fontMetrics().width(QLatin1Char('9')) * digits;
249
250                return QSize(space, 0);
251        } else if(area == col_area) {
252                return QSize(0, 4 + fontMetrics().height());
253        } else {
254                qDebug("Illegal area at numberAreaSize()");
255                return QSize();
256        }
257}
258
259void Text::Editor::updateLineNumberAreaWidth(int /* newBlockCount */) {
260        // this is called when the block count changed (line count changed)
261        setViewportMargins(numberAreaSize(row_area).width(), numberAreaSize(col_area).height(), 0, 0);
262}
263
264void Text::Editor::updateLineNumberArea(const QRect &rect, int dy) {
265        if (dy)
266                row_area->scroll(0, dy);
267        else
268                row_area->update(0, rect.y(), row_area->width(), rect.height());
269
270        if (rect.contains(viewport()->rect()))
271                updateLineNumberAreaWidth(0);
272}
273
274void Text::Editor::paintEvent(QPaintEvent* e) {
275        QPlainTextEdit::paintEvent(e);
276        // mal testweise hier den 80 column begrenzungsstrich hinpinseln
277        // ggf. besser: *unter* den normalen Kram zeichnen, mit ausgegrautem
278        // Bereich und schoenem Strich. Vgl. Qt Creator Editor-Dingens.
279        QPainter p( viewport() );
280        int x_pos = fontMetrics().width('1') * 80 + this->cursorRect(QTextCursor(document())).x();
281        p.setBrush(Qt::blue);
282        p.drawLine(QPoint(x_pos, e->rect().top()), QPoint(x_pos, e->rect().bottom()));
283 }
284
285void Text::Editor::resizeEvent(QResizeEvent *e) {
286        QPlainTextEdit::resizeEvent(e);
287
288        QRect cr = contentsRect();
289        row_area->setGeometry(QRect(cr.left(), cr.top(), numberAreaSize(row_area).width(), cr.height()));
290        col_area->setGeometry(QRect(cr.left(), cr.top(), cr.width(), numberAreaSize(col_area).height()));
291}
292
293void Text::Editor::highlightCurrentCursorPos() {
294        // emit signal: Cursor position changed!
295        emit cursorRowChanged(textCursor().blockNumber());
296        paintHighlights();
297}
298
299void Text::Editor::highlightRow(DeckIndex i) {
300        // externe eingabe: Highlighted_row machen und so...
301        highlighted_row = i;
302        paintHighlights();
303}
304
305void Text::Editor::paintHighlights() {
306        // first: Update Row Mark
307        QList<QTextEdit::ExtraSelection> extra_selections;
308
309        {
310                // Row Mark
311                QTextEdit::ExtraSelection selection;
312
313                QColor row_color = QColor(Qt::yellow).lighter(160);
314
315                selection.format.setBackground(row_color);
316                selection.format.setProperty(QTextFormat::FullWidthSelection, true);
317                selection.cursor = textCursor();
318                selection.cursor.clearSelection();
319                extra_selections.append(selection);
320        }
321        if(highlighted_row >= 0 && highlighted_row < textCursor().blockNumber()) {
322                // highlighted row mark
323                QTextEdit::ExtraSelection selection;
324                QColor row_color = QColor(Qt::blue).lighter(160);
325
326                selection.format.setBackground(row_color);
327                selection.format.setProperty(QTextFormat::FullWidthSelection, true);
328                selection.cursor = textCursor();
329                selection.cursor.clearSelection();
330                extra_selections.append(selection);
331        }
332        {
333                // Col Mark
334                QTextEdit::ExtraSelection selection_template;
335
336                QColor col_color = QColor(Qt::green).lighter(160);
337                selection_template.format.setBackground(col_color);
338
339                // ueber alle sichtbaren reihen iterieren
340                QTextBlock block = firstVisibleBlock();
341
342                while(block.isValid() && block.isVisible()) {
343                        // Eine Selection *kopieren*
344                        QTextEdit::ExtraSelection selection; // nicht mehr von selection_template
345                        selection.format.setBackground(col_color);
346                        // Cursor am Anfang des Blocks
347                        selection.cursor = QTextCursor(block);
348                        selection.cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, textCursor().columnNumber()-1);
349                        selection.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1);
350                        extra_selections.append(selection);
351
352                        block = block.next();
353                }
354        }
355        {
356                // Third: Bad Characters list
357                extra_selections.append(this->invalid_characters);
358        }
359
360        setExtraSelections(extra_selections);
361}
362
363void Text::Editor::numberAreaPaintEvent(NumberArea* area, QPaintEvent *event) {
364        if(area == row_area) {
365                QPainter painter(row_area);
366                painter.fillRect(event->rect(), Qt::lightGray);
367
368
369                QTextBlock block = firstVisibleBlock();
370                int blockNumber = block.blockNumber();
371                int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
372                int bottom = top + (int) blockBoundingRect(block).height();
373
374                while (block.isValid() && top <= event->rect().bottom()) {
375                        if (block.isVisible() && bottom >= event->rect().top()) {
376                                QString number = QString::number(blockNumber + 1);
377                                painter.setPen(Qt::black);
378                                painter.drawText(0, top + col_area->height(), row_area->width(), fontMetrics().height(),
379                                        Qt::AlignRight, number);
380                        }
381
382                        block = block.next();
383                        top = bottom;
384                        bottom = top + (int) blockBoundingRect(block).height();
385                        ++blockNumber;
386                }
387        } else if(area == col_area) {
388                QPainter painter(col_area);
389                painter.fillRect(event->rect(), Qt::darkGray);
390                painter.setPen(Qt::black);
391                /*QFont font = painter.font();
392                font.setPixelSize(8);
393                painter.setFont(font);*/
394
395                // das holt die Metrik von der Font der
396                // Textarea, nicht die kleinere!
397                int font_width = this->fontMetrics().width('1');
398
399                int x_offset = this->cursorRect(QTextCursor(document())).x();
400
401                // ueber alle *zeichen* iterieren.
402                for(int i = 1; i <= 80; i++) {
403                        /*painter.drawLine(
404                                        QPoint(x_offset + row_area->width() + i * font_width, 4),
405                                        QPoint(x_offset + row_area->width() + i * font_width, 20)
406                        );*/
407
408                        painter.drawText(x_offset + row_area->width() + i * font_width, 4, font_width, fontMetrics().height(),
409                                Qt::AlignHCenter,
410                                QString("%1\n%2").arg(QString::number((int)(i / 10))).arg(QString::number(i%10))
411                        );
412                }
413        } else {
414                qDebug("Illegal number area at numberAreaPaintEvent");
415        }
416}
Note: See TracBrowser for help on using the repository browser.
© 2008 - 2013 technikum29 • Sven Köppel • Some rights reserved
Powered by Trac
Expect where otherwise noted, content on this site is licensed under a Creative Commons 3.0 License