Line data Source code
1 : // DUNE DAQ modification notice:
2 : // This file has been modified from the original ATLAS dbe source for the DUNE DAQ project.
3 : // Fork baseline commit: dbe-02-12-17 (2022-05-12).
4 : // Renamed since fork: no.
5 :
6 : /// Including QT Headers
7 : #include "dbe/table.hpp"
8 : #include "dbe/treenode.hpp"
9 : #include "dbe/confaccessor.hpp"
10 : #include "dbe/StyleUtility.hpp"
11 : #include "dbe/Conversion.hpp"
12 : #include "dbe/messenger.hpp"
13 : #include "dbe/Exceptions.hpp"
14 : #include "dbe/dbcontroller.hpp"
15 : #include "dbe/config_api_set.hpp"
16 :
17 : #include <QFont>
18 : #include <QBrush>
19 : #include <QMimeData>
20 :
21 : #include <bitset>
22 :
23 0 : dbe::models::table::table ( QObject * parent )
24 : : QAbstractTableModel ( parent ),
25 0 : enabled ( false )
26 : {
27 0 : model_common_connections();
28 0 : }
29 :
30 0 : dbe::models::table::~table()
31 0 : {}
32 :
33 0 : int dbe::models::table::rowCount ( const QModelIndex & parent ) const
34 : {
35 0 : if ( !parent.isValid() )
36 : {
37 0 : return this_structure.size();
38 : }
39 :
40 : return 0;
41 : }
42 :
43 0 : int dbe::models::table::columnCount ( const QModelIndex & parent ) const
44 : {
45 0 : if( !parent.isValid() ) {
46 0 : return this_headers.size();
47 : }
48 :
49 : return 0;
50 : }
51 :
52 0 : QVariant dbe::models::table::data ( const QModelIndex & index, int role ) const
53 : {
54 :
55 0 : if ( index.isValid() )
56 : {
57 0 : TableNode * TableItem = getnode ( index );
58 :
59 0 : if ( role == Qt::DisplayRole )
60 : {
61 0 : QString Data;
62 :
63 0 : for ( QString const & i : TableItem->GetData() )
64 : {
65 0 : static QString space
66 0 : { ", " };
67 0 : Data.append(i).append(space);
68 0 : }
69 0 : Data.remove(Data.length() - 2, 2);
70 :
71 0 : return QVariant ( Data );
72 0 : }
73 :
74 : if ( role == Qt::ToolTipRole )
75 : {
76 0 : return TableItem->get_tooltip();
77 : }
78 : if ( role == Qt::FontRole )
79 : {
80 0 : if ( dynamic_cast<TableAttributeNode *> ( TableItem ) )
81 0 : return QFont ( "Helvetica", 10, -1,
82 0 : false );
83 0 : else if ( dynamic_cast<TableRelationshipNode *> ( TableItem ) )
84 0 : return QFont ( "Courier", 10,
85 0 : QFont::Bold );
86 : else
87 : {
88 0 : return QFont ( "SansSerif", 10, QFont::Bold );
89 : }
90 : }
91 :
92 : if ( role == Qt::ForegroundRole )
93 : {
94 0 : if ( dynamic_cast<TableAttributeNode *> ( TableItem ) )
95 0 : return QBrush (
96 0 : StyleUtility::TableColorAttribute );
97 0 : else if ( dynamic_cast<TableRelationshipNode *> ( TableItem ) )
98 0 : return QBrush (
99 0 : StyleUtility::TableColorRelationship );
100 : else
101 : {
102 0 : dref obj_desc = this_objects[index.row()];
103 0 : tref Object = dbe::inner::dbcontroller::get ( { obj_desc.UID(), obj_desc.class_name() } );
104 0 : if ( !confaccessor::check_file_rw ( QString::fromStdString ( Object.contained_in() ) ) ) {
105 0 : return QBrush ( StyleUtility::FileReadOnlyForeground );
106 : }
107 0 : return QVariant();
108 0 : }
109 : }
110 :
111 : if ( role == Qt::BackgroundRole )
112 : {
113 0 : auto attr_node = dynamic_cast<TableAttributeNode *> ( TableItem );
114 0 : if ( attr_node != nullptr ) {
115 0 : auto val = attr_node->GetData();
116 0 : if (val.size() == 1 &&
117 0 : val[0].toStdString() == attr_node->GetAttribute().p_default_value) {
118 0 : return QBrush (StyleUtility::DefaultValueBackground );
119 : }
120 0 : return QBrush (StyleUtility::TableAttributeBackground );
121 0 : }
122 0 : else if ( dynamic_cast<TableRelationshipNode *> ( TableItem ) ) {
123 0 : return QBrush (
124 0 : StyleUtility::TableRelationshipBackground );
125 : }
126 : else
127 : {
128 0 : dref obj_desc = this_objects[index.row()];
129 0 : tref Object = dbe::inner::dbcontroller::get ( { obj_desc.UID(), obj_desc.class_name() } );
130 0 : if ( !confaccessor::check_file_rw ( QString::fromStdString ( Object.contained_in() ) ) ) {
131 0 : return QBrush ( StyleUtility::FileReadOnlyBackground );
132 : }
133 0 : }
134 : }
135 : }
136 0 : return QVariant();
137 : }
138 :
139 0 : bool dbe::models::table::setData ( const QModelIndex & index, const QVariant & value,
140 : int role )
141 : {
142 0 : if ( !index.isValid() || role != Qt::EditRole )
143 : {
144 : return false;
145 : }
146 :
147 0 : TableNode * TableItem = getnode ( index );
148 :
149 0 : QStringList OldDataList = TableItem->GetData();
150 :
151 0 : QStringList NewDataList = value.toStringList();
152 :
153 0 : if ( NewDataList == OldDataList )
154 : {
155 : return false;
156 : }
157 :
158 0 : dref obj_desc = this_objects[index.row()];
159 :
160 0 : tref Object = dbe::inner::dbcontroller::get (
161 0 : { obj_desc.UID(), obj_desc.class_name() } );
162 :
163 0 : if ( dynamic_cast<TableRelationshipNode *> ( TableItem ) )
164 : {
165 0 : TableRelationshipNode * RelationshipNode =
166 0 : dynamic_cast<TableRelationshipNode *> ( TableItem );
167 0 : dunedaq::conffwk::relationship_t RelationshipData = RelationshipNode->GetRelationship();
168 0 : dbe::config::api::set::relation ( Object, RelationshipData, NewDataList );
169 0 : }
170 0 : else if ( dynamic_cast<TableAttributeNode *> ( TableItem ) )
171 : {
172 0 : TableAttributeNode * AttributeNode = dynamic_cast<TableAttributeNode *> ( TableItem );
173 0 : dunedaq::conffwk::attribute_t AttributeData = AttributeNode->GetAttribute();
174 0 : dbe::config::api::set::attribute ( Object, AttributeData, NewDataList );
175 0 : }
176 :
177 0 : return true;
178 0 : }
179 :
180 0 : QVariant dbe::models::table::headerData ( int section, Qt::Orientation orientation,
181 : int role ) const
182 : {
183 0 : if ( role == Qt::DisplayRole )
184 : {
185 0 : if ( orientation == Qt::Horizontal )
186 : {
187 0 : return this_headers.at ( section );
188 : }
189 0 : if ( orientation == Qt::Vertical )
190 : {
191 0 : return section + 1;
192 : }
193 : }
194 :
195 0 : if ( role == Qt::FontRole )
196 : {
197 0 : return QFont ( "Helvetica [Cronyx]", 10 );
198 : }
199 :
200 0 : return QVariant();
201 : }
202 :
203 0 : Qt::ItemFlags dbe::models::table::flags ( const QModelIndex & index ) const
204 : {
205 0 : if(index.isValid()) {
206 0 : dref obj_desc = this_objects[index.row()];
207 0 : tref Object = dbe::inner::dbcontroller::get ( { obj_desc.UID(), obj_desc.class_name() } );
208 :
209 0 : if ( confaccessor::check_file_rw ( QString::fromStdString ( Object.contained_in() ) ) )
210 : {
211 0 : return ( Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable |
212 0 : Qt::ItemIsDropEnabled );
213 : }
214 0 : }
215 :
216 0 : return ( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
217 : }
218 :
219 0 : Qt::DropActions dbe::models::table::supportedDropActions() const
220 : {
221 0 : return Qt::CopyAction;
222 : }
223 :
224 0 : QStringList dbe::models::table::mimeTypes() const
225 : {
226 0 : QStringList types;
227 0 : types << "application/vnd.text.list";
228 0 : return types;
229 0 : }
230 :
231 0 : bool dbe::models::table::dropMimeData ( const QMimeData * data, Qt::DropAction action,
232 : int row,
233 : int column, const QModelIndex & parent )
234 : {
235 0 : Q_UNUSED ( row )
236 0 : Q_UNUSED ( column )
237 0 : Q_UNUSED ( parent )
238 :
239 0 : bool Accept = true;
240 :
241 0 : if ( action == Qt::IgnoreAction )
242 : {
243 : return true;
244 : }
245 :
246 0 : if ( !data->hasFormat ( "application/vnd.text.list" ) )
247 : {
248 : return false;
249 : }
250 :
251 : /// Here if parent is valid it indicates that the drop ocurred on an item otherwise it ocurred on a top level item
252 0 : QByteArray encodedData = data->data ( "application/vnd.text.list" );
253 :
254 0 : QDataStream stream ( &encodedData, QIODevice::ReadOnly );
255 :
256 0 : QList<QStringList> newItems;
257 :
258 0 : while ( !stream.atEnd() )
259 : {
260 0 : QStringList text;
261 0 : stream >> text;
262 0 : newItems << text;
263 0 : }
264 :
265 0 : for ( int i = 0; i < newItems.size(); ++i )
266 : {
267 0 : if ( newItems.at ( 0 ).at ( 1 ) != newItems.at ( i ).at ( 1 ) )
268 : {
269 0 : Accept = false;
270 : }
271 : }
272 :
273 0 : if ( Accept )
274 : {
275 0 : BuildTableFromObject ( newItems );
276 0 : emit ResetTab();
277 : }
278 :
279 0 : return Accept;
280 0 : }
281 :
282 0 : bool dbe::models::table::BuildTableFromClass ( const QString & cname, bool include_derived )
283 : {
284 0 : reset ( cname );
285 :
286 0 : cptr<dbe::datahandler> dbaccess_guard = confaccessor::gethandler();
287 :
288 0 : dunedaq::conffwk::class_t classinfo = dbe::config::api::info::onclass::definition (
289 0 : cname.toStdString(),
290 0 : false );
291 :
292 0 : setheader ( classinfo );
293 :
294 0 : if ( treenode * NodeClass = dbaccess_guard->getnode ( cname ) )
295 : {
296 0 : std::vector<treenode *> classnodes
297 0 : { NodeClass };
298 :
299 0 : if ( include_derived )
300 : {
301 :
302 0 : for ( std::string const & sbcname : classinfo.p_subclasses )
303 : {
304 0 : if ( treenode * sbcnode = dbaccess_guard->getnode ( sbcname ) )
305 : {
306 0 : classnodes.push_back ( sbcnode );
307 : }
308 : }
309 : }
310 :
311 : /// Looping over classes
312 : /// Only in the case of derived classes this will be more than one
313 :
314 0 : for ( treenode * clelement : classnodes )
315 : {
316 : /// All objects from that class
317 :
318 0 : for ( treenode * child : clelement->GetChildren() )
319 : {
320 0 : this_structure.append ( createrow ( child ) );
321 0 : }
322 : }
323 :
324 0 : enabled = true;
325 0 : return true;
326 0 : }
327 : else
328 : {
329 : return false;
330 : }
331 0 : }
332 :
333 0 : QList<dbe::models::table::type_datum *> dbe::models::table::createrow (
334 : treenode const * rownode )
335 : {
336 :
337 0 : dref obj = rownode->GetObject();
338 :
339 0 : this_objects.append ( obj );
340 :
341 0 : dunedaq::conffwk::class_t const & cdef = dbe::config::api::info::onclass::definition (
342 0 : obj.class_name(),
343 0 : false );
344 0 : std::vector<dunedaq::conffwk::attribute_t> const & attributes = cdef.p_attributes;
345 0 : std::vector<dunedaq::conffwk::relationship_t> const & relations = cdef.p_relationships;
346 :
347 0 : assert ( attributes.size() + relations.size() < 1025 );
348 0 : std::bitset<1024> hindex; // maximum number of columns to display
349 :
350 0 : {
351 0 : int column = 0;
352 :
353 0 : for ( dunedaq::conffwk::attribute_t const & attr : attributes )
354 : {
355 0 : hindex.set ( column++, this_headers.contains ( QString::fromStdString ( attr.p_name ) ) );
356 : }
357 :
358 0 : for ( dunedaq::conffwk::relationship_t const & rel : relations )
359 : {
360 0 : hindex.set ( column++, this_headers.contains ( QString::fromStdString ( rel.p_name ) ) );
361 : }
362 : }
363 :
364 : // Create the row for this object
365 0 : QList<TableNode *> Row;
366 0 : Row.append ( new TableNode (
367 0 : QStringList {rownode->GetData ( 0 ).toString()},
368 0 : QVariant(QString::fromStdString(cdef.p_description))));
369 0 : {
370 : // Loop over object values and add them to the row
371 : // Values are represent as nodes ( attributes or relations ) and these contain
372 : // the structured data associated either with a attribute / multi-attribute or a relation
373 0 : std::size_t column = 0;
374 :
375 0 : for ( treenode * valuenode : rownode->GetChildren() )
376 : {
377 0 : QStringList values;
378 : // Every valuenode has its attributes and relations defined as its childs
379 :
380 0 : if ( hindex[column++] )
381 : {
382 :
383 0 : for ( treenode * nodevalues : valuenode->GetChildren() )
384 : {
385 : // Here we need to filter such that only values corresponding to the header are kept
386 : // Add only the first of values in a list of values
387 0 : values.append ( nodevalues->GetData ( 0 ).toString() );
388 0 : }
389 :
390 0 : if ( AttributeNode * NodeAttribute = dynamic_cast<AttributeNode *> ( valuenode ) )
391 : {
392 0 : Row.append ( new TableAttributeNode ( NodeAttribute->attribute_t(), values ) );
393 : }
394 0 : else if ( RelationshipNode * NodeRelationship =
395 0 : dynamic_cast<RelationshipNode *> ( valuenode ) )
396 : {
397 0 : Row.append ( new TableRelationshipNode ( NodeRelationship->relation_t(), values ) );
398 : }
399 : }
400 0 : }
401 : }
402 :
403 0 : return Row;
404 0 : }
405 :
406 0 : void dbe::models::table::reset ( QString const & cname )
407 : {
408 :
409 0 : for ( auto & List : this_structure )
410 : {
411 0 : qDeleteAll ( List );
412 : }
413 :
414 0 : this_objects.clear();
415 0 : this_structure.clear();
416 0 : this_headers.clear();
417 0 : this_class_name = cname;
418 0 : }
419 :
420 0 : void dbe::models::table::setheader ( dunedaq::conffwk::class_t const & cinfo )
421 : {
422 0 : if ( !this_headers.contains ( "Object Name" ) )
423 : {
424 0 : this_headers.append ( "Object Name" );
425 : }
426 :
427 0 : for ( auto & i : cinfo.p_attributes )
428 : {
429 0 : if ( !this_headers.contains ( QString::fromStdString ( i.p_name ) ) )
430 : {
431 0 : this_headers.append ( QString::fromStdString ( i.p_name ) );
432 : }
433 : }
434 :
435 0 : for ( auto & i : cinfo.p_relationships )
436 : {
437 0 : if ( !this_headers.contains ( QString::fromStdString ( i.p_name ) ) )
438 : {
439 0 : this_headers.append ( QString::fromStdString ( i.p_name ) );
440 : }
441 : }
442 :
443 0 : }
444 :
445 0 : bool dbe::models::table::BuildTableFromObject ( QList<QStringList> BuildList )
446 : {
447 0 : reset ( BuildList.at ( 0 ).at ( 1 ) );
448 :
449 0 : treenode * classnode = confaccessor::gethandler()->getnode ( this_class_name );
450 :
451 0 : dunedaq::conffwk::class_t classinfo = dbe::config::api::info::onclass::definition (
452 0 : this_class_name.toStdString(),
453 0 : false );
454 :
455 0 : setheader ( classinfo );
456 :
457 0 : confaccessor::gethandler()->FetchMore ( classnode );
458 :
459 0 : for ( const QStringList & i : BuildList )
460 : {
461 0 : QString name = i.at ( 0 );
462 0 : treenode * node = confaccessor::gethandler()->getnode ( this_class_name, name );
463 0 : this_structure.append ( createrow ( node ) );
464 0 : }
465 :
466 0 : enabled = false;
467 0 : return true;
468 0 : }
469 :
470 0 : dbe::tref dbe::models::table::GetTableObject ( int ObjectIndex ) const
471 : {
472 0 : return this_objects.at ( ObjectIndex ).ref();
473 : }
474 :
475 0 : bool dbe::models::table::is_built() const
476 : {
477 0 : return enabled;
478 : }
479 :
480 0 : QString dbe::models::table::get_class_name() const
481 : {
482 0 : return this_class_name;
483 : }
484 :
485 0 : QAbstractItemModel * dbe::models::table::ReturnSourceModel() const
486 : {
487 0 : return nullptr;
488 : }
489 :
490 0 : QList<dbe::dref> * dbe::models::table::GetTableObjects()
491 : {
492 0 : return &this_objects;
493 : }
494 :
495 0 : dbe::TableNode * dbe::models::table::getnode ( const QModelIndex & Index ) const
496 : {
497 0 : if ( Index.isValid() )
498 : {
499 0 : return this_structure.at ( Index.row() ).at ( Index.column() );
500 : }
501 :
502 : return nullptr;
503 : }
504 :
505 0 : void dbe::models::table::ResetModel()
506 : {
507 0 : beginResetModel();
508 0 : endResetModel();
509 0 : }
510 :
511 0 : void dbe::models::table::slot_data_dropped ( QMimeData const & data, Qt::DropAction action )
512 : {
513 0 : QModelIndex dum;
514 0 : this->dropMimeData ( &data, action, 0, 0, dum );
515 0 : }
516 :
517 0 : dbe::tref dbe::models::table::getobject ( QModelIndex const & index ) const
518 : {
519 0 : if ( index.isValid() )
520 : {
521 0 : return this_objects[index.row()].ref();
522 : }
523 :
524 0 : throw daq::dbe::cannot_handle_invalid_qmodelindex ( ERS_HERE );
525 : }
526 :
527 0 : dunedaq::conffwk::class_t dbe::models::table::getclass ( QModelIndex const & index ) const
528 : {
529 0 : if ( index.isValid() )
530 : {
531 0 : return class_type_info;
532 : }
533 :
534 0 : return dunedaq::conffwk::class_t();
535 : }
536 :
537 0 : void dbe::models::table::objectsUpdated(const std::vector<dbe::dref>& objects) {
538 0 : update_multiple_objects(objects);
539 0 : }
540 :
541 : //-----------------------------------------------------------------------------------------------------
542 0 : MODEL_COMMON_INTERFACE_CREATE_THAT_OBJ_IMPL ( dbe::models::table )
543 : {
544 0 : Q_UNUSED(index);
545 0 : if ( treenode * handlerclass = confaccessor::gethandler()->getnode ( obj.class_name() ) )
546 : {
547 0 : if ( obj.class_name() == this_class_name.toStdString()
548 0 : or config::api::info::onclass::derived (
549 0 : this_class_name.toStdString(), obj.class_name() ) )
550 : {
551 :
552 0 : dbe::treenode const * handlernode = dbe::datahandler::findchild (
553 0 : handlerclass, QString::fromStdString ( obj.UID() ) );
554 :
555 0 : if ( handlernode == nullptr )
556 : {
557 0 : handlernode = new ObjectNode ( obj, false, handlerclass );
558 : }
559 :
560 0 : emit layoutAboutToBeChanged();
561 :
562 : // We assume that the objects are sorted and we want to insert a new element
563 :
564 0 : tref const handlerobj = handlernode->GetObject();
565 0 : auto sit = this_structure.begin();
566 0 : auto it = this_objects.begin();
567 :
568 : for ( ;
569 0 : it != this_objects.end() and sit != this_structure.end()
570 0 : and it->UID() < handlerobj.UID(); ++it, ++sit )
571 : {}
572 :
573 0 : this_structure.insert ( ++sit, createrow ( handlernode ) );
574 :
575 0 : this_objects.insert ( ++it, handlerobj );
576 :
577 : // Normally we would have to call changePersistentIndex.
578 : // Because an objectnode is created which had no index
579 : // before this creation had occured there is no need to call it.
580 0 : emit layoutChanged();
581 0 : }
582 : }
583 0 : }
584 :
585 0 : MODEL_COMMON_INTERFACE_DELETE_THAT_OBJ_IMPL ( dbe::models::table )
586 : {
587 0 : if ( index.isValid() )
588 : {
589 0 : try
590 : {
591 0 : this->removeRows ( index.row(), 1, index.parent() );
592 : }
593 0 : catch ( daq::dbe::ObjectChangeWasNotSuccessful const & err )
594 : {
595 0 : WARN ( "Object cannot be deleted", dbe::config::errors::parse ( err ).c_str() );
596 0 : }
597 : }
598 0 : }
599 :
600 0 : MODEL_COMMON_INTERFACE_RENAME_THAT_OBJ_IMPL ( dbe::models::table )
601 : {
602 0 : if ( index.isValid() )
603 : {
604 0 : type_datum * element = getnode ( index );
605 0 : element->resetdata ( QStringList ( QString::fromStdString ( obj.ref().UID() ) ) );
606 0 : this_objects[index.row()] = obj.ref();
607 0 : emit dataChanged ( index, index );
608 : }
609 0 : }
610 :
611 0 : MODEL_COMMON_INTERFACE_UPDATE_THAT_OBJ_IMPL ( dbe::models::table )
612 : {
613 0 : if ( treenode * handlerclass = confaccessor::gethandler()->getnode ( obj.class_name() ) )
614 : {
615 0 : if ( obj.class_name() == this_class_name.toStdString()
616 0 : or config::api::info::onclass::derived (
617 0 : this_class_name.toStdString(), obj.class_name() ) )
618 : {
619 0 : dbe::treenode const * handlernode = dbe::datahandler::findchild (
620 0 : handlerclass, QString::fromStdString ( obj.UID() ) );
621 :
622 0 : tref handlerobj = handlernode->GetObject();
623 :
624 0 : auto sit = this_structure.begin();
625 0 : auto it = this_objects.begin();
626 :
627 : // find the object to be updated by looping through both objects and nodes
628 :
629 : for ( ;
630 0 : it != this_objects.end() and sit != this_structure.end()
631 0 : and it->UID() != handlerobj.UID(); ++it, ++sit )
632 :
633 : ;
634 :
635 : // delete all table nodes in the list ( remove elements of the row )
636 0 : for ( TableNode * x : *sit )
637 : {
638 0 : delete x;
639 : }
640 :
641 : // Recreate the row
642 0 : *sit = createrow ( handlernode );
643 :
644 0 : int row = index.row() == 0 ? 0 : index.row() - 1;
645 :
646 0 : int column = index.column() == 0 ? 0 : index.column() - 1;
647 :
648 0 : emit dataChanged ( createIndex ( row, column ), createIndex ( row + 1, column + 1 ) );
649 0 : }
650 : }
651 0 : }
652 :
653 : //----------------------------------------------------------------------------------------------------
654 :
655 : //-----------------------------------------------------------------------------------------------------
656 0 : MODEL_COMMON_INTERFACE_SLOTS_DEF ( dbe::models::table )
657 : //-----------------------------------------------------------------------------------------------------
658 :
659 : //-----------------------------------------------------------------------------------------------------
660 0 : MODEL_COMMON_INTERFACE_LOOKUP_IMPL ( dbe::models::table )
661 : {
662 0 : for ( int row = 0; row < this_objects.size(); ++row )
663 : {
664 0 : dref ListElement = this_objects.at ( row );
665 :
666 0 : if ( ListElement.UID() == obj.UID() )
667 : {
668 0 : return this->index ( row, 0 );
669 : }
670 0 : }
671 :
672 0 : return QModelIndex();
673 : }
674 :
675 : //-----------------------------------------------------------------------------------------------------
676 :
677 : //-----------------------------------------------------------------------------------------------------
678 0 : MODEL_REMOVE_ROWS_DEF ( dbe::models::table )
679 : {
680 0 : beginRemoveRows ( parent, row, row + count - 1 );
681 :
682 0 : for ( ; count != 0; --count )
683 : {
684 0 : this_structure.removeOne ( this_structure.at ( row + count - 1 ) );
685 0 : this_objects.removeAt ( row + count - 1 );
686 : }
687 :
688 0 : endRemoveRows();
689 0 : return true;
690 : }
691 :
692 : //-----------------------------------------------------------------------------------------------------
|