1 /***************************************************************************
2 rkmatrixinput - description
3 -------------------
4 begin : Tue Oct 09 2012
5 copyright : (C) 2012-2016 by Thomas Friedrichsmeier
6 email : thomas.friedrichsmeier@kdemail.net
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "rkmatrixinput.h"
19
20 #include <QVBoxLayout>
21 #include <QLabel>
22 #include <QHeaderView>
23
24 #include "../misc/rktableview.h"
25 #include "../dataeditor/rktextmatrix.h"
26 #include "kstandardaction.h"
27 #include "KLocalizedString"
28
29 #include "../misc/xmlhelper.h"
30
31 #include "../debug.h"
32
RKMatrixInput(const QDomElement & element,RKComponent * parent_component,QWidget * parent_widget)33 RKMatrixInput::RKMatrixInput (const QDomElement& element, RKComponent* parent_component, QWidget* parent_widget) : RKComponent (parent_component, parent_widget) {
34 RK_TRACE (PLUGIN);
35
36 is_valid = true;
37
38 // get xml-helper
39 XMLHelper *xml = parent_component->xmlHelper ();
40
41 // create layout
42 QVBoxLayout *vbox = new QVBoxLayout (this);
43 vbox->setContentsMargins (0, 0, 0, 0);
44
45 QLabel *label = new QLabel (xml->i18nStringAttribute (element, "label", i18n ("Enter data:"), DL_INFO), this);
46 vbox->addWidget (label);
47
48 display = new RKTableView (this);
49 vbox->addWidget (display);
50
51 mode = static_cast<Mode> (xml->getMultiChoiceAttribute (element, "mode", "integer;real;string", 1, DL_WARNING));
52 if (mode == Integer) {
53 min = xml->getIntAttribute (element, "min", INT_MIN, DL_INFO) - .1; // we'll only allow ints anyway. Make sure not to run into floating point precision issues.
54 max = xml->getIntAttribute (element, "max", INT_MAX, DL_INFO) + .1;
55 } else if (mode == Real) {
56 min = xml->getDoubleAttribute (element, "min", -FLT_MAX, DL_INFO);
57 max = xml->getDoubleAttribute (element, "max", FLT_MAX, DL_INFO);
58 } else {
59 min = -FLT_MAX;
60 max = FLT_MAX;
61 }
62
63 min_rows = xml->getIntAttribute (element, "min_rows", 0, DL_INFO);
64 min_columns = xml->getIntAttribute (element, "min_columns", 0, DL_INFO);
65
66 // Note: string type matrix allows missings, implicitly (treating them as empty strings)
67 allow_missings = xml->getBoolAttribute (element, "allow_missings", false, DL_INFO);
68 if (mode == String) allow_missings = true;
69 allow_user_resize_columns = xml->getBoolAttribute (element, "allow_user_resize_columns", true, DL_INFO);
70 allow_user_resize_rows = xml->getBoolAttribute (element, "allow_user_resize_rows", true, DL_INFO);
71 trailing_rows = allow_user_resize_rows ? 1 : 0;
72 trailing_columns = allow_user_resize_columns ? 1 : 0;
73
74 row_count = new RKComponentPropertyInt (this, false, xml->getIntAttribute (element, "rows", qMax (2, min_rows), DL_INFO));
75 column_count = new RKComponentPropertyInt (this, false, xml->getIntAttribute (element, "columns", qMax (2, min_columns), DL_INFO));
76 tsv_data = new RKComponentPropertyBase (this, false);
77 row_count->setInternal (true);
78 addChild ("rows", row_count);
79 column_count->setInternal (true);
80 addChild ("columns", column_count);
81 addChild ("tsv", tsv_data);
82 connect (row_count, &RKComponentPropertyBase::valueChanged, this, &RKMatrixInput::dimensionPropertyChanged);
83 connect (column_count, &RKComponentPropertyBase::valueChanged, this, &RKMatrixInput::dimensionPropertyChanged);
84 connect (tsv_data, &RKComponentPropertyBase::valueChanged, this, &RKMatrixInput::tsvPropertyChanged);
85 updating_tsv_data = false;
86
87 model = new RKMatrixInputModel (this);
88 QString headers = xml->getStringAttribute (element, "horiz_headers", QString (), DL_INFO);
89 if (!headers.isEmpty ()) model->horiz_header = headers.split (';');
90 else if (!headers.isNull ()) display->horizontalHeader ()->hide (); // attribute explicitly set to ""
91 headers = xml->getStringAttribute (element, "vert_headers", QString (), DL_INFO);
92 if (!headers.isEmpty ()) model->vert_header = headers.split (';');
93 else if (!headers.isNull ()) display->verticalHeader ()->hide ();
94 updateAll ();
95 display->setModel (model);
96 display->setAlternatingRowColors (true);
97 if (xml->getBoolAttribute (element, "fixed_width", false, DL_INFO)) {
98 display->horizontalHeader ()->setStretchLastSection (true);
99 }
100 if (xml->getBoolAttribute (element, "fixed_height", false, DL_INFO)) {
101 int max_row = row_count->intValue () - 1;
102 display->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
103 display->setFixedHeight (display->horizontalHeader ()->height () + display->rowViewportPosition (max_row) + display->rowHeight (max_row));
104 }
105
106 // define standard actions
107 QAction *cut = KStandardAction::cut (this, SLOT (cut()), this);
108 display->addAction (cut);
109 QAction *copy = KStandardAction::copy (this, SLOT (copy()), this);
110 display->addAction (copy);
111 QAction *paste = KStandardAction::paste (this, SLOT (paste()), this);
112 display->addAction (paste);
113 display->setContextMenuPolicy (Qt::ActionsContextMenu);
114
115 display->setRKItemDelegate (new RKItemDelegate (display, model, true));
116 connect (display, &RKTableView::blankSelectionRequest, this, &RKMatrixInput::clearSelectedCells);
117 }
118
~RKMatrixInput()119 RKMatrixInput::~RKMatrixInput () {
120 RK_TRACE (PLUGIN);
121 }
122
value(const QString & modifier)123 QVariant RKMatrixInput::value (const QString& modifier) {
124 if (modifier.isEmpty () || (modifier == "cbind")) {
125 QStringList ret;
126 for (int i = 0; i < column_count->intValue (); ++i) {
127 ret.append ("\tc (" + makeColumnString (i, ", ") + ')');
128 }
129 return QString ("cbind (\n" + ret.join (",\n") + "\n)");
130 } else if (modifier.startsWith (QLatin1String ("row."))) {
131 bool ok;
132 int row = modifier.mid (4).toInt (&ok);
133 if ((row >= 0) && ok) {
134 return (rowStrings (row));
135 }
136 }
137
138 bool ok;
139 int col = modifier.toInt (&ok);
140 if ((col >= 0) && ok) {
141 QStringList l;
142 if (col < columns.size ()) l = columns[col].storage;
143 while (l.size () < row_count->intValue ()) {
144 l.append (QString ());
145 }
146 if (l.size () > row_count->intValue ()) l = l.mid (0, row_count->intValue ());
147 return l;
148 }
149 return (QString ("Modifier '" + modifier + "' not recognized\n"));
150 }
151
expandStorageForColumn(int column)152 bool RKMatrixInput::expandStorageForColumn (int column) {
153 RK_TRACE (PLUGIN);
154
155 if ((column < 0) || (!allow_user_resize_columns && (column >= column_count->intValue ()))) {
156 RK_ASSERT (false);
157 return false;
158 }
159
160 while (column >= columns.size ()) {
161 Column col;
162 col.last_valid_row = -1;
163 columns.append (col);
164 }
165 return true;
166 }
167
cellValue(int row,int column) const168 QString RKMatrixInput::cellValue (int row, int column) const {
169 if ((column < 0) || (column >= columns.size ())) return (QString ());
170 return columns[column].storage.value (row);
171 }
172
173
setCellValue(int row,int column,const QString & value)174 void RKMatrixInput::setCellValue (int row, int column, const QString& value) {
175 RK_TRACE (PLUGIN);
176
177 if ((!expandStorageForColumn (column)) || (row < 0) || (!allow_user_resize_rows && (row >= row_count->intValue ()))) {
178 RK_ASSERT (false);
179 return;
180 }
181
182 Column &col = columns[column];
183 if (col.storage.value (row) == value) return;
184
185 while (row >= col.storage.size ()) {
186 col.storage.append (QString ());
187 }
188 col.storage[row] = value;
189 updateColumn (column);
190 model->dataChanged (model->index (row, column), model->index (row, column));
191 }
192
setColumnValue(int column,const QString & value)193 void RKMatrixInput::setColumnValue (int column, const QString& value) {
194 RK_TRACE (PLUGIN);
195
196 if (!expandStorageForColumn (column)) return;
197 columns[column].storage = value.split ('\t', QString::KeepEmptyParts);
198 updateColumn (column);
199 model->dataChanged (model->index (0, column), model->index (row_count->intValue () + trailing_rows, column));
200 }
201
updateColumn(int column)202 void RKMatrixInput::updateColumn (int column) {
203 RK_TRACE (PLUGIN);
204 RK_ASSERT ((column >= 0) && (column < columns.size ()));
205
206 Column &col = columns[column];
207
208 // check for trailing empty rows:
209 int last_row = col.storage.size ();
210 while ((--last_row >= min_rows) && col.storage[last_row].isEmpty ()) { // strip empty trailing strings
211 col.storage.pop_back ();
212 }
213
214 col.last_valid_row = -1;
215 col.cached_tab_joined_string.clear (); // == no valid cache
216
217 updateAll ();
218 }
219
pasteableValue(const QString & in,bool string)220 QString pasteableValue (const QString &in, bool string) {
221 if (string) return (RObject::rQuote (in));
222 else if (in.isEmpty ()) return ("NA");
223 else return in;
224 }
225
makeColumnString(int column,const QString & sep,bool r_pasteable)226 QString RKMatrixInput::makeColumnString (int column, const QString& sep, bool r_pasteable) {
227 RK_TRACE (PLUGIN);
228
229 QStringList storage;
230 if (column < columns.size ()) {
231 storage = columns[column].storage;
232 }
233 QString ret;
234 ret.reserve (3 * row_count->intValue ()); // a rather conservative estimate for most purposes
235 for (int i = 0; i < row_count->intValue (); ++i) {
236 if (i > 0) ret.append (sep);
237 const QString val = storage.value (i);
238 if (r_pasteable) ret.append (pasteableValue (val, mode == String));
239 else ret.append (val);
240 }
241 return ret;
242 }
243
rowStrings(int row)244 QStringList RKMatrixInput::rowStrings (int row) {
245 RK_TRACE (PLUGIN);
246
247 QStringList ret;
248 ret.reserve (column_count->intValue ());
249 for (int i = 0; i < column_count->intValue (); ++i) {
250 if (i < columns.size ()) ret.append (columns[i].storage.value (row));
251 else ret.append (QString ());
252 }
253 return ret;
254 }
255
updateAll()256 void RKMatrixInput::updateAll () {
257 RK_TRACE (PLUGIN);
258
259 if (updating_tsv_data) return;
260 updating_tsv_data = true;
261
262 int max_row = row_count->intValue () - 1;
263 if (allow_user_resize_rows) {
264 max_row = -1;
265 for (int i = columns.size () - 1; i >= 0; --i) {
266 max_row = qMax (max_row, columns[i].storage.size () - 1);
267 }
268 }
269 max_row = qMax (min_rows - 1, max_row);
270 if (max_row != row_count->intValue () - 1) {
271 row_count->setIntValue (max_row + 1);
272 }
273
274 int max_col = column_count->intValue () - 1;
275 if (allow_user_resize_columns) {
276 for (max_col = columns.size () - 1; max_col >= 0; --max_col) {
277 if (!columns[max_col].storage.isEmpty ()) {
278 break;
279 }
280 }
281 }
282 max_col = qMax (min_columns - 1, max_col);
283 if (max_col != column_count->intValue () - 1) {
284 column_count->setIntValue (max_col + 1);
285 }
286
287 QStringList tsv;
288 int i = 0;
289 for (; i < columns.size (); ++i) {
290 Column& col = columns[i];
291 if (col.cached_tab_joined_string.isEmpty ()) {
292 col.cached_tab_joined_string = makeColumnString (i, "\t", false);
293 }
294 tsv.append (col.cached_tab_joined_string);
295 }
296 for (; i < max_col; ++i) {
297 tsv.append (QString (max_row, '\t'));
298 }
299 tsv_data->setValue (tsv.join ("\n"));
300
301 updating_tsv_data = false;
302
303 // finally, check whether table is valid, and signal change
304 bool new_valid = true;
305 for (int i = 0; i < column_count->intValue (); ++i) {
306 if (!isColumnValid (i)) {
307 new_valid = false;
308 break;
309 }
310 }
311 if (new_valid != is_valid) {
312 is_valid = new_valid;
313 model->headerDataChanged (Qt::Horizontal, 0, column_count->intValue () - 1);
314 }
315 changed ();
316 }
317
dimensionPropertyChanged(RKComponentPropertyBase * property)318 void RKMatrixInput::dimensionPropertyChanged (RKComponentPropertyBase *property) {
319 RK_TRACE (PLUGIN);
320
321 if (allow_user_resize_rows && (property == row_count)) {
322 RK_ASSERT (updating_tsv_data);
323 }
324 if (allow_user_resize_columns && (property == column_count)) {
325 RK_ASSERT (updating_tsv_data);
326 }
327
328 if (property == row_count) { // invalidates column caches
329 for (int i = columns.size () - 1; i >= 0; --i) {
330 columns[i].last_valid_row = qMin (columns[i].last_valid_row, row_count->intValue () - 1);
331 columns[i].cached_tab_joined_string.clear ();
332 }
333 }
334
335 model->layoutAboutToBeChanged ();
336 updateAll ();
337 model->layoutChanged ();
338 }
339
tsvPropertyChanged()340 void RKMatrixInput::tsvPropertyChanged () {
341 if (updating_tsv_data) return;
342 updating_tsv_data = true;
343 RK_TRACE (PLUGIN);
344
345 columns.clear ();
346 QStringList coldata = fetchStringValue (tsv_data).split ('\n', QString::KeepEmptyParts);
347 for (int i = 0; i < coldata.size (); ++i) {
348 setColumnValue (i, coldata[i]);
349 }
350
351 updating_tsv_data = false;
352 updateAll ();
353 }
354
isValueValid(const QString & value) const355 bool RKMatrixInput::isValueValid (const QString& value) const {
356 if (mode == String) return true;
357 if (value.isEmpty ()) return allow_missings;
358
359 bool number_ok;
360 double val;
361 if (mode == Integer) {
362 val = value.toInt (&number_ok);
363 } else {
364 val = value.toFloat (&number_ok);
365 }
366 if (!number_ok) return false;
367 if (val < min) return false;
368 return (val <= max);
369 }
370
isColumnValid(int column)371 bool RKMatrixInput::isColumnValid (int column) {
372 if (column < 0) {
373 RK_ASSERT (false);
374 return false;
375 }
376
377 if (mode == String) return true;
378
379 if (column >= columns.size ()) return (allow_missings || (row_count->intValue () == 0));
380
381 Column &col = columns[column];
382 if (col.last_valid_row >= (row_count->intValue () - 1)) {
383 return true;
384 } else if (allow_missings && (col.last_valid_row >= (col.storage.size () - 1))) {
385 return true;
386 }
387
388 int row = col.last_valid_row + 1;
389 for (; row < col.storage.size (); ++row) {
390 if (!isValueValid (col.storage[row])) {
391 col.last_valid_row = row - 1;
392 return false;
393 }
394 }
395 col.last_valid_row = row - 1;
396
397 if (col.last_valid_row < (row_count->intValue () - 1)) {
398 return allow_missings;
399 }
400 return true;
401 }
402
clearSelectedCells()403 void RKMatrixInput::clearSelectedCells () {
404 RK_TRACE (PLUGIN);
405
406 QItemSelectionRange range = display->getSelectionBoundaries ();
407 if (!range.isValid ()) return;
408
409 updating_tsv_data = true;
410 for (int col = range.left (); col <= range.right (); ++col) {
411 for (int row = range.top (); row <= range.bottom (); ++row) {
412 setCellValue (row, col, QString ());
413 }
414 }
415 updating_tsv_data = false;
416 updateAll ();
417 }
418
copy()419 void RKMatrixInput::copy () {
420 RK_TRACE (PLUGIN);
421
422 QItemSelectionRange range = display->getSelectionBoundaries ();
423 if (!range.isValid ()) return;
424
425 RKTextMatrix ret;
426 for (int col = range.left (); col <= range.right (); ++col) {
427 for (int row = range.top (); row <= range.bottom (); ++row) {
428 ret.setText (row - range.top (), col - range.left (), cellValue (row, col));
429 }
430 }
431 ret.copyToClipboard ();
432 }
433
cut()434 void RKMatrixInput::cut () {
435 RK_TRACE (PLUGIN);
436
437 copy ();
438 clearSelectedCells ();
439 }
440
paste()441 void RKMatrixInput::paste () {
442 RK_TRACE (PLUGIN);
443
444 int left = 0;
445 int top = 0;
446 QItemSelectionRange range = display->getSelectionBoundaries ();
447 if (range.isValid ()) {
448 left = range.left ();
449 top = range.top ();
450 }
451
452 RKTextMatrix pasted = RKTextMatrix::matrixFromClipboard ();
453 int height = allow_user_resize_rows ? pasted.numRows () : qMin (pasted.numRows (), row_count->intValue () - top);
454 int width = allow_user_resize_columns ? pasted.numColumns () : qMin (pasted.numColumns (), column_count->intValue () - left);
455
456 updating_tsv_data = true;
457 for (int c = 0; c < width; ++c) {
458 for (int r = 0; r < height; ++r) {
459 setCellValue (r + top, c + left, pasted.getText (r, c));
460 }
461 }
462 updating_tsv_data = false;
463 updateAll ();
464 }
465
466
467
468
469 ///////////////////////////////////////////////////////////////////////////////////////
470
RKMatrixInputModel(RKMatrixInput * _matrix)471 RKMatrixInputModel::RKMatrixInputModel (RKMatrixInput* _matrix) : QAbstractTableModel (_matrix) {
472 RK_TRACE (PLUGIN);
473
474 matrix = _matrix;
475 }
476
~RKMatrixInputModel()477 RKMatrixInputModel::~RKMatrixInputModel () {
478 RK_TRACE (PLUGIN);
479 }
480
columnCount(const QModelIndex & parent) const481 int RKMatrixInputModel::columnCount (const QModelIndex& parent) const {
482 if (parent.isValid ()) return 0;
483 return matrix->column_count->intValue () + matrix->trailing_columns;
484 }
485
rowCount(const QModelIndex & parent) const486 int RKMatrixInputModel::rowCount (const QModelIndex& parent) const {
487 if (parent.isValid ()) return 0;
488 return matrix->row_count->intValue () + matrix->trailing_rows;
489 }
490
data(const QModelIndex & index,int role) const491 QVariant RKMatrixInputModel::data (const QModelIndex& index, int role) const {
492 if (!index.isValid ()) return QVariant ();
493
494 int row = index.row ();
495 int column = index.column ();
496
497 // handle trailing rows / cols in user expandable tables
498 bool trailing = false;
499 if (row >= matrix->row_count->intValue ()) {
500 if ((!matrix->allow_user_resize_rows) || (row >= matrix->row_count->intValue () + matrix->trailing_rows)) {
501 RK_ASSERT (false);
502 return QVariant ();
503 }
504 trailing = true;
505 }
506 if (column >= matrix->column_count->intValue ()) {
507 if ((!matrix->allow_user_resize_columns) || (column >= matrix->column_count->intValue () + matrix->trailing_columns)) {
508 RK_ASSERT (false);
509 return QVariant ();
510 }
511 trailing = true;
512 };
513 if (trailing) {
514 if (role == Qt::BackgroundRole) return QVariant (QBrush (Qt::gray));
515 if (role == Qt::ToolTipRole || role == Qt::StatusTipRole) return QVariant (i18n ("Type on these cells to expand the table"));
516 return QVariant ();
517 }
518
519 // regular cells
520 QString value = matrix->cellValue (row, column);
521 if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) {
522 return QVariant (value);
523 } else if (role == Qt::BackgroundRole) {
524 if (!matrix->is_valid && !matrix->isValueValid (value)) return QVariant (QColor (Qt::red));
525 } else if ((role == Qt::ToolTipRole) || (role == Qt::StatusTipRole)) {
526 if (!matrix->is_valid && (value.isEmpty () && !matrix->allow_missings)) return QVariant (i18n ("Empty values are not allowed"));
527 if (!matrix->is_valid && !matrix->isValueValid (value)) return QVariant (i18n ("This value is not allowed, here"));
528 }
529 return QVariant ();
530 }
531
flags(const QModelIndex & index) const532 Qt::ItemFlags RKMatrixInputModel::flags (const QModelIndex& index) const {
533 // handle trailing rows / cols in user expandable tables
534 if ((index.row () > matrix->row_count->intValue ()) || (index.column () > matrix->column_count->intValue ())) {
535 return (Qt::ItemIsEditable | Qt::ItemIsEnabled);
536 }
537 return (Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
538 }
539
headerData(int section,Qt::Orientation orientation,int role) const540 QVariant RKMatrixInputModel::headerData (int section, Qt::Orientation orientation, int role) const {
541 if (role == Qt::DisplayRole) {
542 const QStringList* list;
543 if (orientation == Qt::Horizontal) list = &horiz_header;
544 else list = &vert_header;
545 if (section < list->size ()) return QVariant ((*list)[section]);
546 return QVariant (QString::number (section + 1));
547 } else if (orientation == Qt::Horizontal) {
548 if (section < matrix->column_count->intValue ()) {
549 if ((role == Qt::BackgroundRole) && !matrix->isColumnValid (section)) return QVariant (QColor (Qt::red));
550 if (((role == Qt::ToolTipRole) || (role == Qt::StatusTipRole)) && !matrix->isColumnValid (section)) return QVariant (i18n ("This column contains illegal values in some of its cells"));
551 }
552 }
553 return QVariant ();
554 }
555
setData(const QModelIndex & index,const QVariant & value,int role)556 bool RKMatrixInputModel::setData (const QModelIndex& index, const QVariant& value, int role) {
557 RK_TRACE (PLUGIN);
558
559 if (!index.isValid ()) return false;
560 if (role != Qt::EditRole) return false;
561 matrix->setCellValue (index.row (), index.column (), value.toString ());
562 return true;
563 }
564
565