File indexing completed on 2024-04-21 05:43:40

0001 /***************************************************************************
0002  *   Copyright (C) 2003-2006 by David Saxton                               *
0003  *   david@bluehaze.org                                                    *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  ***************************************************************************/
0010 
0011 #include "itemselector.h"
0012 #include "circuitdocument.h"
0013 #include "docmanager.h"
0014 #include "flowcodedocument.h"
0015 #include "itemdocument.h"
0016 #include "itemlibrary.h"
0017 #include "katemdi.h"
0018 #include "libraryitem.h"
0019 #include "mechanicsdocument.h"
0020 #include <vector> // Temporay fix for pthread.h problem
0021 
0022 #include <KConfigGroup>
0023 #include <KLocalizedString>
0024 #include <KSharedConfig>
0025 
0026 // #include <q3dragobject.h>
0027 // #include <q3popupmenu.h>
0028 #include <QLayout>
0029 #include <QMenu>
0030 #include <QMimeData>
0031 
0032 #include <cassert>
0033 
0034 #include <ktechlab_debug.h>
0035 
0036 ILVItem::ILVItem(QTreeWidget *parent, const QString &id)
0037     : QTreeWidgetItem(parent, 0 /* note: add item types */)
0038 {
0039     setData(0, DataRole_ID, QVariant(id));
0040     //  m_id = id;  // 2018.08.12 - use value()
0041     b_isRemovable = false;
0042     m_pProjectItem = nullptr;
0043 }
0044 
0045 ILVItem::ILVItem(QTreeWidgetItem *parent, const QString &id)
0046     : QTreeWidgetItem(parent, 0 /* note: add item types */)
0047 {
0048     // m_id = id;    // 2018.08.12 - use value()
0049     setData(0, DataRole_ID, QVariant(id));
0050     b_isRemovable = false;
0051     m_pProjectItem = nullptr;
0052 }
0053 
0054 ItemSelector::ItemSelector(QWidget *parent)
0055     : QTreeWidget(parent)
0056 {
0057     qCDebug(KTL_LOG) << " this=" << this;
0058 
0059     setDragDropMode(QAbstractItemView::DragOnly);
0060     setColumnCount(1);
0061     setHeaderLabel(i18n("Component"));
0062     // addColumn( i18n( "Component" ) ); // 2018.08.12 - use setHeaderLabel()
0063     // setFullWidth(true);       // 2018.06.02 - need to be fixed
0064     setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred));
0065     // setSorting( -1, false ); // 2018.08.12 - use setSortingEnabled
0066     setSortingEnabled(false);
0067     setRootIsDecorated(true);
0068     // setDragEnabled(true);     // 2018.06.02 - needed?
0069     setFocusPolicy(Qt::NoFocus);
0070 
0071     setSelectionMode(QAbstractItemView::SingleSelection); // 2015.12.10 - need to allow selection for removing items
0072 
0073     if (parent->layout()) {
0074         parent->layout()->addWidget(this);
0075         qCDebug(KTL_LOG) << " added item selector to parent's layout " << parent;
0076     } else {
0077         qCWarning(KTL_LOG) << " unexpected null layout on parent " << parent;
0078     }
0079 
0080     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // ?
0081 
0082     //  connect( this, SIGNAL(executed(K3ListViewItem*) ), this, SLOT(slotItemExecuted(K3ListViewItem*)) );
0083     connect(this, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(slotItemClicked(QTreeWidgetItem *, int)));
0084     /*TODO Can't connect to itemClicked(QTreeWidgetItem *, int)
0085     connect(this, qOverload<QTreeWidgetItem*, int>(&ItemSelector::itemClicked),
0086             this, qOverload<QTreeWidgetItem*, int>(&ItemSelector::slotItemClicked));*/
0087     connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotItemDoubleClicked(QTreeWidgetItem *, int)));
0088     /*TODO The same problem as above
0089     connect(this, qOverload<QTreeWidgetItem*, int>(&ItemSelector::itemClicked),
0090         this, qOverload<QTreeWidgetItem*, int>(&ItemSelector::slotItemDoubleClicked));*/
0091     //  connect( this, SIGNAL(contextMenuRequested(Q3ListViewItem*, const QPoint&, int )), this,
0092     //              SLOT(slotContextMenuRequested(Q3ListViewItem*, const QPoint&, int )) ); // 2018.08.12 - use signal from below
0093     setContextMenuPolicy(Qt::CustomContextMenu);
0094     connect(this, &ItemSelector::customContextMenuRequested, this, &ItemSelector::slotContextMenuRequested);
0095 
0096     connect(this, &ItemSelector::itemSelectionChanged, this, &ItemSelector::slotItemSelected);
0097 }
0098 
0099 ItemSelector::~ItemSelector()
0100 {
0101     writeOpenStates();
0102 }
0103 
0104 void ItemSelector::clear()
0105 {
0106     m_categories.clear();
0107     QTreeWidget::clear();
0108 }
0109 
0110 void ItemSelector::addItem(const QString &caption, const QString &id, const QString &_category, const QIcon &icon, bool removable)
0111 {
0112     qCDebug(KTL_LOG) << "id=" << id;
0113     ILVItem *parentItem = nullptr;
0114 
0115     QString category = _category;
0116     if (!category.startsWith("/")) {
0117         category.prepend('/');
0118     }
0119 
0120     do {
0121         category.remove(0, 1);
0122         QString cat;
0123         category.replace("\\/", "|");
0124         int pos = category.indexOf('/');
0125         if (pos == -1)
0126             cat = category;
0127         else
0128             cat = category.left(pos);
0129 
0130         cat.replace("|", "/");
0131 
0132         if (m_categories.indexOf(cat) == -1) {
0133             m_categories.append(cat);
0134 
0135             if (parentItem) {
0136                 parentItem = new ILVItem(parentItem, "");
0137             } else {
0138                 parentItem = new ILVItem(this, "");
0139             }
0140             // parentItem->setExpandable(true); // 2018.08.12 - is it needed?
0141 
0142             parentItem->setExpanded(readOpenState(cat));
0143 
0144             parentItem->setText(0, cat);
0145         } else {
0146             QList<QTreeWidgetItem *> foundList = findItems(cat, Qt::MatchExactly);
0147             if (foundList.size() > 1) {
0148                 qCWarning(KTL_LOG) << "found multiple categories for '" << cat << "'";
0149             }
0150             parentItem = dynamic_cast<ILVItem *>(foundList.front());
0151         }
0152 
0153         category.remove(0, pos);
0154     } while (category.contains('/'));
0155 
0156     if (!parentItem) {
0157         qCCritical(KTL_LOG) << "Unexpected error in finding parent item for category list";
0158         return;
0159     }
0160 
0161     ILVItem *item = new ILVItem(parentItem, id);
0162     // item->setPixmap( 0, icon );  // 2018.08.12 - replaced with line below
0163     item->setIcon(0, icon);
0164     item->setText(0, caption);
0165     // item->setDragEnabled(true); // 2018.08.12 - replaced with line below
0166     item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
0167     item->setRemovable(removable);
0168 }
0169 
0170 void ItemSelector::writeOpenStates()
0171 {
0172     // KConfig *config = kapp->config();
0173     KSharedConfigPtr configPtr = KSharedConfig::openConfig();
0174     // config->setGroup( name() );
0175     KConfigGroup configGroup = configPtr->group(objectName());
0176 
0177     const QStringList::iterator end = m_categories.end();
0178     for (QStringList::iterator it = m_categories.begin(); it != end; ++it) {
0179         QList<QTreeWidgetItem *> itemsFound = findItems(*it, Qt::MatchExactly);
0180         if (itemsFound.size() > 1) {
0181             qCWarning(KTL_LOG) << " too many items " << itemsFound.size() << " for category '" << *it << "'";
0182         }
0183         QTreeWidgetItem *item = itemsFound.first() /* findItem( *it, 0 ) */;
0184         if (item) {
0185             configGroup.writeEntry(*it + "IsOpen", item->isExpanded() /* isOpen() */);
0186         }
0187     }
0188 }
0189 
0190 bool ItemSelector::readOpenState(const QString &id)
0191 {
0192     // KConfig *config = kapp->config();
0193     KSharedConfigPtr configPtr = KSharedConfig::openConfig();
0194     // config->setGroup( name() );
0195     KConfigGroup configGroup = configPtr->group(objectName());
0196 
0197     return configGroup.readEntry<bool>(id + "IsOpen", true);
0198 }
0199 
0200 QTreeWidgetItem *ItemSelector::selectedItem() const
0201 {
0202     QList<QTreeWidgetItem *> selectedList = selectedItems();
0203     if (selectedList.empty()) {
0204         return nullptr;
0205     }
0206     if (selectedList.size() > 1) {
0207         qCWarning(KTL_LOG) << " expected 1 item in selection, got " << selectedList.size();
0208     }
0209     return selectedList.first();
0210 }
0211 
0212 QMimeData *ItemSelector::mimeData(const QList<QTreeWidgetItem *> items) const
0213 {
0214     qCDebug(KTL_LOG) << " begin ";
0215     if (items.size() > 1) {
0216         qCWarning(KTL_LOG) << "expected 1 item, got " << items.size();
0217     }
0218     QTreeWidgetItem *theItem = items.first();
0219     if (!theItem) {
0220         qCWarning(KTL_LOG) << "unexpected null item";
0221         return nullptr;
0222     }
0223     qCDebug(KTL_LOG) << " theItem = " << theItem;
0224     QVariant idAsVariant = theItem->data(0, ILVItem::DataRole_ID);
0225     qCDebug(KTL_LOG) << " idAsVariant = " << idAsVariant;
0226     const QString id = idAsVariant.toString();
0227     qCDebug(KTL_LOG) << "id='" << id << "'";
0228 
0229     QMimeData *mime = new QMimeData();
0230 
0231     QByteArray data;
0232     QDataStream stream(&data, QIODevice::WriteOnly);
0233     stream << id;
0234 
0235     if (id.startsWith("flow/")) {
0236         mime->setData("ktechlab/flowpart", data);
0237     } else if (id.startsWith("ec/")) {
0238         mime->setData("ktechlab/component", data);
0239     } else if (id.startsWith("sc/")) {
0240         mime->setData("ktechlab/subcircuit", data);
0241     } else if (id.startsWith("mech/")) {
0242         mime->setData("ktechlab/mechanical", data);
0243     } else {
0244         qCWarning(KTL_LOG) << "returning unset mime; unknown id '" << id << "'";
0245     }
0246 
0247     // A pixmap cursor is often hard to make out
0248     //  QPixmap *pixmap = const_cast<QPixmap*>(currentItem()->pixmap(0));
0249     //  if (pixmap) d->setPixmap(*pixmap);
0250 
0251     return mime;
0252 }
0253 
0254 void ItemSelector::slotContextMenuRequested(const QPoint &pos)
0255 {
0256     QTreeWidgetItem *item = itemAt(pos);
0257     if (!item || !(static_cast<ILVItem *>(item))->isRemovable()) {
0258         return;
0259     }
0260 
0261     QMenu *menu = new QMenu(this);
0262     /* menu->insertItem(
0263         //, Qt::Key_Delete // 2015.12.29 - do not specify shortcut key, because it does not work
0264     ); - 2018.12.01 */
0265     menu->addAction(i18n("Remove %1", item->text(0)), this, SLOT(slotRemoveSelectedItem()));
0266     QPoint globalPos = mapToGlobal(pos);
0267     menu->popup(globalPos);
0268 }
0269 
0270 void ItemSelector::slotRemoveSelectedItem()
0271 {
0272     qCDebug(KTL_LOG) << "removing selected item";
0273     QList<QTreeWidgetItem *> selectedList = selectedItems();
0274     if (selectedList.empty()) {
0275         qCDebug(KTL_LOG) << "selection is empty";
0276         return;
0277     }
0278     QTreeWidgetItem *selectedItem = selectedList.first();
0279     ILVItem *item = dynamic_cast<ILVItem *>(selectedItem);
0280     if (!item) {
0281         qCDebug(KTL_LOG) << "no selected item to remove";
0282         return;
0283     }
0284 
0285     emit itemRemoved(item->data(0, ILVItem::DataRole_ID).toString() /*key( 0, 0 ) */);
0286     ILVItem *parent = dynamic_cast<ILVItem *>(item->QTreeWidgetItem::parent());
0287     delete item;
0288     // Get rid of the category as well if it has no children
0289     if (parent && !parent->childCount() /* firstChild() */) {
0290         m_categories.removeAll(parent->text(0));
0291         delete parent;
0292     }
0293 }
0294 
0295 void ItemSelector::setListCaption(const QString &caption)
0296 {
0297     // setColumnText( 0, caption ); // 2018.08.12 - see below
0298     setHeaderLabel(caption);
0299 }
0300 
0301 void ItemSelector::slotItemSelected()
0302 {
0303     QTreeWidgetItem *item = selectedItem();
0304     if (!item) {
0305         return;
0306     }
0307 
0308     emit itemSelected(item->data(0, ILVItem::DataRole_ID).toString() /* item->key( 0, 0 ) */);
0309 }
0310 
0311 void ItemSelector::slotItemClicked(QTreeWidgetItem *item, int)
0312 {
0313     if (!item)
0314         return;
0315 
0316     if (ItemDocument *itemDocument = dynamic_cast<ItemDocument *>(DocManager::self()->getFocusedDocument()))
0317         itemDocument->slotUnsetRepeatedItemId();
0318 
0319     const QString &itemIdString = item->data(0, ILVItem::DataRole_ID).toString();
0320 
0321     emit itemClicked(itemIdString /* item->key( 0, 0 ) */);
0322 }
0323 
0324 void ItemSelector::slotItemDoubleClicked(QTreeWidgetItem *item, int)
0325 {
0326     if (!item)
0327         return;
0328 
0329     // QString id = item->key( 0, 0 );
0330     const QString &id = item->data(0, ILVItem::DataRole_ID).toString();
0331 
0332     if (Document *doc = DocManager::self()->getFocusedDocument()) {
0333         if (doc->type() == Document::dt_flowcode && id.startsWith("flow/"))
0334             (static_cast<FlowCodeDocument *>(doc))->slotSetRepeatedItemId(id);
0335 
0336         else if (doc->type() == Document::dt_circuit && (id.startsWith("ec/") || id.startsWith("sc/")))
0337             (static_cast<CircuitDocument *>(doc))->slotSetRepeatedItemId(id);
0338 
0339         else if (doc->type() == Document::dt_mechanics && id.startsWith("mech/"))
0340             (static_cast<MechanicsDocument *>(doc))->slotSetRepeatedItemId(id);
0341     }
0342 
0343     emit itemDoubleClicked(id);
0344 }
0345 
0346 #if 0 // 2018.08.12 - needed?
0347 // Q3DragObject* ItemSelector::dragObject()
0348 // {
0349 //  const QString &id = currentItem()->data(0, ILVItem::DataRole_ID).toString() /* key(0,0) */;
0350 //
0351 //  Q3StoredDrag * d = nullptr;
0352 //
0353 //  if ( id.startsWith("flow/") )
0354 //      d = new Q3StoredDrag( "ktechlab/flowpart", this );
0355 //
0356 //  else if ( id.startsWith("ec/") )
0357 //      d = new Q3StoredDrag( "ktechlab/component", this );
0358 //
0359 //  else if ( id.startsWith("sc/") )
0360 //      d = new Q3StoredDrag( "ktechlab/subcircuit", this );
0361 //
0362 //  else if ( id.startsWith("mech/") )
0363 //      d = new Q3StoredDrag( "ktechlab/mechanical", this );
0364 //
0365 //  if (d)
0366 //  {
0367 //      QByteArray data;
0368 //      QDataStream stream( &data, QIODevice::WriteOnly );
0369 //      stream << id;
0370 //      d->setEncodedData(data);
0371 //  } else {
0372 //         qCWarning(KTL_LOG) << " null drag returned";
0373 //     }
0374 //
0375 //  // A pixmap cursor is often hard to make out
0376 // //   QPixmap *pixmap = const_cast<QPixmap*>(currentItem()->pixmap(0));
0377 // //   if (pixmap) d->setPixmap(*pixmap);
0378 //
0379 //     return d;
0380 // }
0381 #endif
0382 
0383 // BEGIN class ComponentSelector
0384 ComponentSelector *ComponentSelector::m_pSelf = nullptr;
0385 
0386 ComponentSelector *ComponentSelector::self(KateMDI::ToolView *parent)
0387 {
0388     if (!m_pSelf) {
0389         assert(parent);
0390         m_pSelf = new ComponentSelector(parent);
0391         m_pSelf->setObjectName("Component Selector");
0392     }
0393     return m_pSelf;
0394 }
0395 
0396 ComponentSelector::ComponentSelector(KateMDI::ToolView *parent)
0397     : ItemSelector(parent)
0398 {
0399     qCDebug(KTL_LOG) << " creating " << this;
0400 
0401     setWhatsThis(
0402         i18n("Add components to the circuit diagram by dragging them into the circuit.<br><br>"
0403 
0404              "To add more than one component of the same type, doubleclick on a component, and click repeatedly in the circuit to place the component. Right click to stop placement.<br><br>"
0405 
0406              "Some components (such as subcircuits) can be removed by right clicking on the item and selecting \"Remove\"."));
0407 
0408     setListCaption(i18n("Component"));
0409 
0410     LibraryItemList *items = itemLibrary()->items();
0411     qCDebug(KTL_LOG) << " there are " << items->count() << " items";
0412     const LibraryItemList::iterator end = items->end();
0413     for (LibraryItemList::iterator it = items->begin(); it != end; ++it) {
0414         if ((*it)->type() == LibraryItem::lit_component)
0415             addItem((*it)->name(), (*it)->activeID(), (*it)->category(), (*it)->icon());
0416     }
0417 }
0418 // END class ComponentSelector
0419 
0420 // BEGIN class FlowPartSelector
0421 FlowPartSelector *FlowPartSelector::m_pSelf = nullptr;
0422 
0423 FlowPartSelector *FlowPartSelector::self(KateMDI::ToolView *parent)
0424 {
0425     if (!m_pSelf) {
0426         assert(parent);
0427         m_pSelf = new FlowPartSelector(parent);
0428         m_pSelf->setObjectName("Part Selector");
0429     }
0430     return m_pSelf;
0431 }
0432 
0433 FlowPartSelector::FlowPartSelector(KateMDI::ToolView *parent)
0434     : ItemSelector(static_cast<QWidget *>(parent))
0435 {
0436     setWhatsThis(
0437         i18n("Add FlowPart to the FlowCode document by dragging them there.<br><br>To add more than one FlowPart of the same type, doubleclick on a FlowPart, and click repeatedly in the FlowChart to place the component. Right click to "
0438              "stop placement."));
0439 
0440     setListCaption(i18n("Flow Part"));
0441 
0442     LibraryItemList *items = itemLibrary()->items();
0443     const LibraryItemList::iterator end = items->end();
0444     for (LibraryItemList::iterator it = items->begin(); it != end; ++it) {
0445         if ((*it)->type() == LibraryItem::lit_flowpart)
0446             addItem((*it)->name(), (*it)->activeID(), (*it)->category(), (*it)->icon());
0447     }
0448 }
0449 // END class FlowPartSelector
0450 
0451 // BEGIN class MechanicsSelector
0452 MechanicsSelector *MechanicsSelector::m_pSelf = nullptr;
0453 
0454 MechanicsSelector *MechanicsSelector::self(KateMDI::ToolView *parent)
0455 {
0456     if (!m_pSelf) {
0457         assert(parent);
0458         m_pSelf = new MechanicsSelector(static_cast<QWidget *>(parent));
0459         m_pSelf->setObjectName("Mechanics Selector");
0460     }
0461     return m_pSelf;
0462 }
0463 
0464 MechanicsSelector::MechanicsSelector(QWidget *parent)
0465     : ItemSelector(static_cast<QWidget *>(parent))
0466 {
0467     setWhatsThis(i18n("Add mechanical parts to the mechanics work area by dragging them there."));
0468 
0469     LibraryItemList *items = itemLibrary()->items();
0470     const LibraryItemList::iterator end = items->end();
0471     for (LibraryItemList::iterator it = items->begin(); it != end; ++it) {
0472         if ((*it)->type() == LibraryItem::lit_mechanical) {
0473             addItem((*it)->name(), (*it)->activeID(), (*it)->category(), (*it)->icon());
0474         }
0475     }
0476 }
0477 // END class MechanicsSelector
0478 
0479 #include "moc_itemselector.cpp"