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

Last change on this file since 53 was 53, checked in by sven, 10 years ago

Punch Card Editor, ongoing development

  • Extended new Deck interface, expanding the undo framework
  • Implemented editor changes via undo framework
  • revised the menu and toolbar actions and structure (now dynamic construction at deck load time), implemented undo viewer
  • Started implementation of device driver framework in menu
  • Embedded the Qextserialport library (http://qextserialport.sourceforge.net/)
  • Started the Documation M200 Client device driver (well, just created the directory structure and qmake project file infrastructure)
  • At the current state, the complete project compiles :-)

Statistics: About 3500 Lines of code (without libqextserialport)

-- sven @ workstation

File size: 13.8 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        Card translated;
232        codec->fromAscii(text, &translated); //&main->deck->at( card_index ) );
233        qDebug("Translation done. Creating undo command");
234
235        DeckModifyCard* command = new DeckModifyCard(main->deck, card_index, translated);
236        command->setText(tr("Set card %1 content using codec %2 to '%3'").arg(card_index).arg("CodecName").arg(text));
237        main->deck->run(command, true);
238
239        qDebug("Command executed.");
240
241        // urhm... that was all the magic.
242        // send "you were changed" thingy! :-)
243        //main->deck->emitChanged(card_index, card_index);
244        // no. that does the command!
245        return true;
246}
247
248QSize Text::Editor::numberAreaSize(const NumberArea* area) const {
249        if(area == row_area) {
250                int digits = 1;
251                int max = qMax(1, blockCount());
252                while (max >= 10) {
253                        max /= 10;
254                        ++digits;
255                }
256
257                int space = 10 + fontMetrics().width(QLatin1Char('9')) * digits;
258
259                return QSize(space, 0);
260        } else if(area == col_area) {
261                return QSize(0, 4 + fontMetrics().height());
262        } else {
263                qDebug("Illegal area at numberAreaSize()");
264                return QSize();
265        }
266}
267
268void Text::Editor::updateLineNumberAreaWidth(int /* newBlockCount */) {
269        // this is called when the block count changed (line count changed)
270        setViewportMargins(numberAreaSize(row_area).width(), numberAreaSize(col_area).height(), 0, 0);
271}
272
273void Text::Editor::updateLineNumberArea(const QRect &rect, int dy) {
274        if (dy)
275                row_area->scroll(0, dy);
276        else
277                row_area->update(0, rect.y(), row_area->width(), rect.height());
278
279        if (rect.contains(viewport()->rect()))
280                updateLineNumberAreaWidth(0);
281}
282
283void Text::Editor::paintEvent(QPaintEvent* e) {
284        QPlainTextEdit::paintEvent(e);
285        // mal testweise hier den 80 column begrenzungsstrich hinpinseln
286        // ggf. besser: *unter* den normalen Kram zeichnen, mit ausgegrautem
287        // Bereich und schoenem Strich. Vgl. Qt Creator Editor-Dingens.
288        QPainter p( viewport() );
289        int x_pos = fontMetrics().width('1') * 80 + this->cursorRect(QTextCursor(document())).x();
290        p.setBrush(Qt::blue);
291        p.drawLine(QPoint(x_pos, e->rect().top()), QPoint(x_pos, e->rect().bottom()));
292 }
293
294void Text::Editor::resizeEvent(QResizeEvent *e) {
295        QPlainTextEdit::resizeEvent(e);
296
297        QRect cr = contentsRect();
298        row_area->setGeometry(QRect(cr.left(), cr.top(), numberAreaSize(row_area).width(), cr.height()));
299        col_area->setGeometry(QRect(cr.left(), cr.top(), cr.width(), numberAreaSize(col_area).height()));
300}
301
302void Text::Editor::highlightCurrentCursorPos() {
303        // emit signal: Cursor position changed!
304        emit cursorRowChanged(textCursor().blockNumber());
305        paintHighlights();
306}
307
308void Text::Editor::highlightRow(DeckIndex i) {
309        // externe eingabe: Highlighted_row machen und so...
310        highlighted_row = i;
311        paintHighlights();
312}
313
314void Text::Editor::paintHighlights() {
315        // first: Update Row Mark
316        QList<QTextEdit::ExtraSelection> extra_selections;
317
318        {
319                // Row Mark
320                QTextEdit::ExtraSelection selection;
321
322                QColor row_color = QColor(Qt::yellow).lighter(160);
323
324                selection.format.setBackground(row_color);
325                selection.format.setProperty(QTextFormat::FullWidthSelection, true);
326                selection.cursor = textCursor();
327                selection.cursor.clearSelection();
328                extra_selections.append(selection);
329        }
330        if(highlighted_row >= 0 && highlighted_row < textCursor().blockNumber()) {
331                // highlighted row mark
332                QTextEdit::ExtraSelection selection;
333                QColor row_color = QColor(Qt::blue).lighter(160);
334
335                selection.format.setBackground(row_color);
336                selection.format.setProperty(QTextFormat::FullWidthSelection, true);
337                selection.cursor = textCursor();
338                selection.cursor.clearSelection();
339                extra_selections.append(selection);
340        }
341        {
342                // Col Mark
343                QTextEdit::ExtraSelection selection_template;
344
345                QColor col_color = QColor(Qt::green).lighter(160);
346                selection_template.format.setBackground(col_color);
347
348                // ueber alle sichtbaren reihen iterieren
349                QTextBlock block = firstVisibleBlock();
350
351                while(block.isValid() && block.isVisible()) {
352                        // Eine Selection *kopieren*
353                        QTextEdit::ExtraSelection selection; // nicht mehr von selection_template
354                        selection.format.setBackground(col_color);
355                        // Cursor am Anfang des Blocks
356                        selection.cursor = QTextCursor(block);
357                        selection.cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, textCursor().columnNumber()-1);
358                        selection.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1);
359                        extra_selections.append(selection);
360
361                        block = block.next();
362                }
363        }
364        {
365                // Third: Bad Characters list
366                extra_selections.append(this->invalid_characters);
367        }
368
369        setExtraSelections(extra_selections);
370}
371
372void Text::Editor::numberAreaPaintEvent(NumberArea* area, QPaintEvent *event) {
373        if(area == row_area) {
374                QPainter painter(row_area);
375                painter.fillRect(event->rect(), Qt::lightGray);
376
377
378                QTextBlock block = firstVisibleBlock();
379                int blockNumber = block.blockNumber();
380                int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
381                int bottom = top + (int) blockBoundingRect(block).height();
382
383                while (block.isValid() && top <= event->rect().bottom()) {
384                        if (block.isVisible() && bottom >= event->rect().top()) {
385                                QString number = QString::number(blockNumber + 1);
386                                painter.setPen(Qt::black);
387                                painter.drawText(0, top + col_area->height(), row_area->width(), fontMetrics().height(),
388                                        Qt::AlignRight, number);
389                        }
390
391                        block = block.next();
392                        top = bottom;
393                        bottom = top + (int) blockBoundingRect(block).height();
394                        ++blockNumber;
395                }
396        } else if(area == col_area) {
397                QPainter painter(col_area);
398                painter.fillRect(event->rect(), Qt::darkGray);
399                painter.setPen(Qt::black);
400                /*QFont font = painter.font();
401                font.setPixelSize(8);
402                painter.setFont(font);*/
403
404                // das holt die Metrik von der Font der
405                // Textarea, nicht die kleinere!
406                int font_width = this->fontMetrics().width('1');
407
408                int x_offset = this->cursorRect(QTextCursor(document())).x();
409
410                // ueber alle *zeichen* iterieren.
411                for(int i = 1; i <= 80; i++) {
412                        /*painter.drawLine(
413                                        QPoint(x_offset + row_area->width() + i * font_width, 4),
414                                        QPoint(x_offset + row_area->width() + i * font_width, 20)
415                        );*/
416
417                        painter.drawText(x_offset + row_area->width() + i * font_width, 4, font_width, fontMetrics().height(),
418                                Qt::AlignHCenter,
419                                QString("%1\n%2").arg(QString::number((int)(i / 10))).arg(QString::number(i%10))
420                        );
421                }
422        } else {
423                qDebug("Illegal number area at numberAreaPaintEvent");
424        }
425}
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