source: projects/punch-card/punch-card-editor/src/app/decktexteditor.cc @ 49

Last change on this file since 49 was 49, checked in by sven, 10 years ago
  • Text editing part:

Improved Column counting
80 column line bar

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