File indexing completed on 2024-04-21 05:51:36

0001 /*
0002  *  SPDX-FileCopyrightText: 2002-2003 Jesper K. Pedersen <blackie@kde.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-only
0005  **/
0006 
0007 #include "concwidget.h"
0008 
0009 #include <QMouseEvent>
0010 #include <QPainter>
0011 
0012 #include "concregexp.h"
0013 #include "dragaccepter.h"
0014 
0015 ConcWidget::ConcWidget(RegExpEditorWindow *editorWindow, QWidget *parent)
0016     : MultiContainerWidget(editorWindow, parent)
0017 {
0018     init();
0019     DragAccepter *accepter = new DragAccepter(editorWindow, this);
0020     accepter->show();
0021     _children.append(accepter);
0022 }
0023 
0024 ConcWidget::ConcWidget(RegExpEditorWindow *editorWindow, RegExpWidget *child, QWidget *parent)
0025     : MultiContainerWidget(editorWindow, parent)
0026 {
0027     init();
0028     DragAccepter *accepter = new DragAccepter(editorWindow, this);
0029     _children.append(accepter);
0030     child->setParent(this);
0031     addNewChild(accepter, child);
0032 }
0033 
0034 ConcWidget::ConcWidget(RegExpEditorWindow *editorWindow, ConcWidget *origConc, unsigned int start, unsigned int end)
0035     : MultiContainerWidget(editorWindow, nullptr)
0036 {
0037     Q_UNUSED(start);
0038     Q_UNUSED(end);
0039     init();
0040     _children.prepend(new DragAccepter(editorWindow, this));
0041     QMutableListIterator<RegExpWidget *> i(origConc->_children);
0042     while (i.hasNext()) {
0043         RegExpWidget *child = i.next();
0044         i.remove();
0045         _children.prepend(child);
0046         child->setParent(this);
0047     }
0048 
0049     _children.prepend(new DragAccepter(editorWindow, this));
0050 }
0051 
0052 ConcWidget::ConcWidget(ConcRegExp *regexp, RegExpEditorWindow *editorWindow, QWidget *parent)
0053     : MultiContainerWidget(editorWindow, parent)
0054 {
0055     init();
0056     DragAccepter *accepter = new DragAccepter(editorWindow, this);
0057     _children.append(accepter);
0058 
0059     const RegExpList list = regexp->children();
0060     for (RegExp *r : list) {
0061         RegExpWidget *child = WidgetFactory::createWidget(r, editorWindow, this);
0062         append(child);
0063     }
0064 }
0065 
0066 void ConcWidget::init()
0067 {
0068     _maxSelectedHeight = 0;
0069 }
0070 
0071 QSize ConcWidget::sizeHint() const
0072 {
0073     int childrenWidth = 0;
0074     int childrenHeight = 0;
0075 
0076     for (const RegExpWidget *regExpWidget : std::as_const(_children)) {
0077         QSize thisChildSize = regExpWidget->sizeHint();
0078         childrenWidth += thisChildSize.width();
0079         childrenHeight = qMax(childrenHeight, thisChildSize.height());
0080     }
0081 
0082     return QSize(childrenWidth, childrenHeight);
0083 }
0084 
0085 void ConcWidget::paintEvent(QPaintEvent *e)
0086 {
0087     Q_ASSERT(dynamic_cast<DragAccepter *>(_children.at(0)));
0088     // if this fails, then I should check the location of the show()
0089     Q_ASSERT(_children.count() == 1 || (_children.count() >= 3 && dynamic_cast<DragAccepter *>(_children.at(_children.count() - 1))));
0090 
0091     if (_children.count() == 1) {
0092         // There is only an accepter, lets give it all the space.
0093         _children.at(0)->setGeometry(0, 0, size().width(), size().height());
0094     } else {
0095         QSize myReqSize = sizeHint();
0096         QSize mySize(qMax(myReqSize.width(), size().width()), qMax(myReqSize.height(), size().height()));
0097 
0098         // If the widget needs less space than it can get then this space should
0099         // be given to the leftmost and rightmost accepter. So lets calculate
0100         // this extra space.
0101         int extra = 0;
0102         if (size().width() > myReqSize.width()) {
0103             extra = (size().width() - myReqSize.width()) / 2;
0104         }
0105 
0106         QPainter painter(this);
0107 
0108         drawPossibleSelection(painter, mySize);
0109 
0110         int lastHeight = 0;
0111         int offset = 0;
0112 
0113         for (int i = 1; i < _children.count(); i += 2) {
0114             DragAccepter *accepter = dynamic_cast<DragAccepter *>(_children.at(i - 1));
0115             if (!accepter) {
0116                 continue;
0117             }
0118 
0119             RegExpWidget *child = _children.at(i);
0120 
0121             QSize childSize = child->sizeHint();
0122             QSize curChildSize = child->size();
0123 
0124             //----------------------------- first place the accepter
0125             int x = offset;
0126             int w = accepter->sizeHint().width();
0127             if (i == 1) {
0128                 w += extra;
0129             }
0130 
0131             int h = qMax(lastHeight, childSize.height());
0132             int y = (mySize.height() - h) / 2;
0133             accepter->setGeometry(x, y, w, h);
0134 
0135             offset += w;
0136             lastHeight = childSize.height();
0137 
0138             //------------------------------ Draw Accepter selection
0139             if (accepter->isSelected()) {
0140                 y = (mySize.height() - _maxSelectedHeight) / 2;
0141                 h = _maxSelectedHeight;
0142                 painter.fillRect(x, y, w, h, QBrush(Qt::gray));
0143             }
0144 
0145             //-------------------------------------- place the child
0146             x = offset;
0147             h = childSize.height();
0148             w = childSize.width();
0149             y = (mySize.height() - h) / 2;
0150             child->setGeometry(x, y, w, h);
0151             if (childSize != curChildSize) {
0152                 // I resized the child, so give it a chance to relect thus.
0153                 child->update();
0154             }
0155 
0156             offset += w;
0157 
0158             //------------------------------ Draw Accepter selection
0159             if (child->isSelected()) {
0160                 y = (mySize.height() - _maxSelectedHeight) / 2;
0161                 h = _maxSelectedHeight;
0162                 painter.fillRect(x, y, w, h, QBrush(Qt::gray));
0163             }
0164         }
0165 
0166         //---------------------- Finally place the last accepter.
0167         DragAccepter *accepter = static_cast<DragAccepter *>(_children.at(_children.count() - 1));
0168         // dynamic_cast is ASSERTed at top
0169         int x = offset;
0170         int h = lastHeight;
0171         int w = accepter->sizeHint().width() + extra;
0172         int y = (mySize.height() - h) / 2;
0173         accepter->setGeometry(x, y, w, h);
0174     }
0175     MultiContainerWidget::paintEvent(e);
0176 }
0177 
0178 void ConcWidget::mousePressEvent(QMouseEvent *event)
0179 {
0180     if (event->button() == Qt::RightButton) {
0181         _editorWindow->showRMBMenu(_editorWindow->hasSelection());
0182     } else {
0183         RegExpWidget::mousePressEvent(event);
0184     }
0185 }
0186 
0187 void ConcWidget::sizeAccepter(DragAccepter *accepter, int height, int totHeight)
0188 {
0189     if (accepter->height() != height) {
0190         accepter->resize(accepter->width(), height);
0191     }
0192 
0193     if (accepter->y() != (totHeight - height) / 2) {
0194         accepter->move(accepter->x(), (totHeight - height) / 2);
0195     }
0196 }
0197 
0198 RegExp *ConcWidget::regExp() const
0199 {
0200     QList<RegExpWidget *>::const_iterator it = _children.constBegin();
0201     ++it; // start with the second element.
0202 
0203     if (_children.count() == 3) {
0204         // Exactly one child (and two drag accepters)
0205         return (*it)->regExp();
0206     } else {
0207         ConcRegExp *regexp = new ConcRegExp(isSelected());
0208 
0209         for (; it != _children.constEnd(); it += 2) {
0210             regexp->addRegExp((*it)->regExp());
0211         }
0212         return regexp;
0213     }
0214 }
0215 
0216 bool ConcWidget::updateSelection(bool parentSelected)
0217 {
0218     bool isSel = _isSelected;
0219     bool changed = MultiContainerWidget::updateSelection(parentSelected);
0220 
0221     _maxSelectedHeight = 0;
0222 
0223     QList<RegExpWidget *>::const_iterator it = _children.constBegin();
0224     ++it; // Skip past the first DragAccepter
0225     for (; it != _children.constEnd(); it += 2) {
0226         if ((*it)->isSelected()) {
0227             _maxSelectedHeight = qMax(_maxSelectedHeight, (*it)->sizeHint().height());
0228         }
0229     }
0230 
0231     changed = changed || isSel != _isSelected;
0232     if (changed) {
0233         repaint();
0234     }
0235 
0236     return changed;
0237 }
0238 
0239 void ConcWidget::getSelectionIndexes(int *start, int *end)
0240 {
0241     *start = -1;
0242     *end = -1;
0243 
0244     // Start with element at index 1, and skip every second element, as we
0245     // know they are dragAccepters.
0246     for (int index = 1; index < _children.count(); index += 2) {
0247         RegExpWidget *child = _children.at(index);
0248 
0249         if (child->isSelected()) {
0250             // The child is selected at topmost level.
0251             if (*start == -1) {
0252                 *start = index;
0253             }
0254         } else if (*start != -1) {
0255             // Found the end of the selection.
0256             *end = index - 2;
0257             break;
0258         }
0259     }
0260 
0261     if (*start != -1 && *end == -1) {
0262         *end = _children.count() - 2;
0263     }
0264 }
0265 
0266 void ConcWidget::applyRegExpToSelection(RegExpType type)
0267 {
0268     int start, end;
0269     getSelectionIndexes(&start, &end);
0270 
0271     if (start == -1) {
0272         // No item selected at top level
0273 
0274         QList<RegExpWidget *>::const_iterator it = _children.constBegin();
0275         ++it; // Skip past the first DragAccepter
0276         for (; it != _children.constEnd(); it += 2) {
0277             if ((*it)->hasSelection()) {
0278                 (*it)->applyRegExpToSelection(type);
0279                 break;
0280             }
0281         }
0282     } else {
0283         // Apply RegExp to selection.
0284         RegExpWidget *newElm = WidgetFactory::createWidget(_editorWindow, this, type);
0285 
0286         if (newElm) {
0287             ConcWidget *subSequence = new ConcWidget(_editorWindow, this, start, end);
0288             newElm->setConcChild(subSequence);
0289 
0290             subSequence->resize(0, 0); // see note (1)
0291             subSequence->setParent(newElm);
0292             _children.insert(start, newElm);
0293             newElm->show();
0294         }
0295     }
0296 }
0297 
0298 bool ConcWidget::isSelected() const
0299 {
0300     // A ConcWidget should be considered selected when all its elements has been selected
0301     // otherwise empty ConcWidgets may be left behind when for example selection is deleted.
0302     bool allSelected = true;
0303     QList<RegExpWidget *>::const_iterator it = _children.constBegin();
0304     ++it; // Skip past first DragAccepter.
0305     for (; (it != _children.constEnd()) && allSelected; it += 2) {
0306         allSelected = (*it)->isSelected();
0307     }
0308 
0309     return allSelected;
0310 }
0311 
0312 RegExp *ConcWidget::selection() const
0313 {
0314     if (isSelected()) {
0315         return regExp();
0316     }
0317 
0318     bool foundAny = false;
0319     bool foundMoreThanOne = false;
0320     RegExp *regexp = nullptr;
0321 
0322     QList<RegExpWidget *>::const_iterator it = _children.constBegin();
0323     ++it; // Skip past the first DragAccepter
0324     for (; it != _children.constEnd(); it += 2) {
0325         if ((*it)->hasSelection()) {
0326             if (!foundAny) {
0327                 regexp = (*it)->selection();
0328                 foundAny = true;
0329             } else if (!foundMoreThanOne) {
0330                 ConcRegExp *reg = new ConcRegExp(isSelected());
0331                 reg->addRegExp(regexp);
0332                 reg->addRegExp((*it)->selection());
0333                 regexp = reg;
0334                 foundMoreThanOne = true;
0335             } else {
0336                 dynamic_cast<ConcRegExp *>(regexp)->addRegExp((*it)->selection());
0337             }
0338         }
0339     }
0340 
0341     Q_ASSERT(foundAny);
0342     return regexp;
0343 }
0344 
0345 void ConcWidget::addNewConcChild(DragAccepter *accepter, ConcWidget *other)
0346 {
0347     for (int i = 0; i < _children.count(); i += 2) {
0348         RegExpWidget *ch = _children.at(i);
0349         if (ch == accepter) {
0350             // Move all the element from the `child' ConcWidget to this one.
0351             // Do not copy the first one as this is a dragAccepter, and we place the widgets
0352             // after this drag accepter.
0353             // We must take them in pairs to avoid breaking the invariant for paintEvent,
0354             // namely that every second element is a dragAccepter
0355             for (unsigned int j = other->_children.count() - 1; j > 0; j -= 2) {
0356                 RegExpWidget *newChildA = other->_children.takeAt(j);
0357                 newChildA->setParent(this);
0358                 _children.insert(i + 1, newChildA);
0359                 RegExpWidget *newChildB = other->_children.takeAt(j - 1);
0360                 newChildB->setParent(this);
0361                 _children.insert(i + 1, newChildB);
0362                 newChildA->show();
0363                 newChildB->show();
0364             }
0365             delete other;
0366             return;
0367         }
0368     }
0369     qFatal("accepter not found");
0370 }
0371 
0372 bool ConcWidget::validateSelection() const
0373 {
0374     bool cont = true;
0375     QList<RegExpWidget *>::const_iterator it = _children.constBegin();
0376     ++it; // skip past the DragAccepter.
0377     for (; (it != _children.constEnd()) && cont; it += 2) {
0378         cont = (*it)->validateSelection();
0379     }
0380     return cont;
0381 }