File indexing completed on 2023-05-30 11:09:00
0001 /*************************************************************************** 0002 kimearea.cpp - description 0003 ------------------- 0004 begin : Thu Jun 14 2001 0005 copyright : (C) 2001 by Jan Schaefer 0006 email : janschaefer@users.sourceforge.net 0007 ***************************************************************************/ 0008 0009 /*************************************************************************** 0010 * * 0011 * This program is free software; you can redistribute it and/or modify * 0012 * it under the terms of the GNU General Public License as published by * 0013 * the Free Software Foundation; either version 2 of the License, or * 0014 * (at your option) any later version. * 0015 * * 0016 ***************************************************************************/ 0017 0018 #include "kimearea.h" 0019 0020 #include <QBitmap> 0021 #include <QBrush> 0022 #include <QColor> 0023 #include <QImage> 0024 #include <QPainter> 0025 #include <QPalette> 0026 #include <QPen> 0027 #include <QPixmap> 0028 #include <QPolygon> 0029 0030 #include "kimagemapeditor_debug.h" 0031 0032 #include "kimecommon.h" 0033 0034 0035 // The size of Selection Points 0036 0037 SelectionPoint::SelectionPoint(QPoint p, QCursor c) 0038 { 0039 point = p; 0040 state = Normal; 0041 _cursor = c; 0042 } 0043 0044 SelectionPoint::~SelectionPoint() { 0045 } 0046 0047 void SelectionPoint::setState(SelectionPoint::State s) { 0048 state = s; 0049 } 0050 0051 SelectionPoint::State SelectionPoint::getState() const { 0052 return state; 0053 } 0054 0055 void SelectionPoint::setPoint(QPoint p) { 0056 point = p; 0057 } 0058 0059 void SelectionPoint::translate(int dx, int dy) { 0060 point += QPoint(dx,dy); 0061 } 0062 0063 0064 QPoint SelectionPoint::getPoint() const { 0065 return point; 0066 } 0067 0068 QRect SelectionPoint::getRect() const { 0069 QRect r(0,0,SELSIZE,SELSIZE); 0070 r.moveCenter(point); 0071 return r; 0072 } 0073 0074 QCursor SelectionPoint::cursor() { 0075 return _cursor; 0076 } 0077 0078 void SelectionPoint::setCursor(QCursor c) { 0079 _cursor = c; 0080 } 0081 0082 0083 void SelectionPoint::draw(QPainter* p, double scalex) { 0084 QColor brushColor; 0085 0086 switch (state) { 0087 case Normal: 0088 brushColor = Qt::white; 0089 break; 0090 case HighLighted: 0091 brushColor = Qt::green; 0092 break; 0093 case AboutToRemove: 0094 brushColor = Qt::red; 0095 break; 0096 case Inactive: 0097 brushColor = Qt::gray; 0098 break; 0099 } 0100 0101 QPoint scaledCenter((int)(point.x()*scalex), 0102 (int)(point.y()*scalex)); 0103 0104 if (state == HighLighted || state == AboutToRemove) { 0105 QRect r2(0,0,SELSIZE+4,SELSIZE+4); 0106 0107 r2.moveCenter(scaledCenter); 0108 QColor color(brushColor); 0109 color.setAlpha(100); 0110 p->setPen(QPen(color,4,Qt::SolidLine)); 0111 p->setBrush(Qt::NoBrush); 0112 p->drawRect(r2); 0113 0114 } 0115 0116 // brushColor.setAlpha(230); 0117 brushColor.setAlpha(200); 0118 p->setBrush(QBrush(brushColor,Qt::SolidPattern)); 0119 0120 QColor penColor = Qt::black; 0121 penColor.setAlpha(120); 0122 QPen pen(penColor, 2, Qt::SolidLine); 0123 0124 QRect r(0,0,SELSIZE,SELSIZE); 0125 r.moveCenter( scaledCenter ); 0126 0127 p->setPen(pen); 0128 p->drawRect(r); 0129 0130 0131 } 0132 0133 0134 bool Area::highlightArea; 0135 bool Area::showAlt; 0136 0137 0138 Area::Area() 0139 { 0140 _finished=false; 0141 _isSelected=false; 0142 _name=i18n("noname"); 0143 _listViewItem = nullptr; 0144 currentHighlighted=-1; 0145 _type=Area::None; 0146 } 0147 0148 Area* Area::clone() const 0149 { 0150 Area* areaClone = new Area(); 0151 areaClone->setArea( *this ); 0152 return areaClone; 0153 } 0154 0155 QPolygon Area::coords() const { 0156 return _coords; 0157 } 0158 0159 QString Area::getHTMLAttributes() const 0160 { 0161 QString retStr=""; 0162 0163 AttributeIterator it = attributeIterator(); 0164 while (it.hasNext()) 0165 { 0166 it.next(); 0167 retStr+=it.key()+"=\""+it.value()+"\" "; 0168 } 0169 0170 return retStr; 0171 } 0172 0173 void Area::resetSelectionPointState() { 0174 setSelectionPointStates(SelectionPoint::Normal); 0175 } 0176 0177 void Area::setSelectionPointStates(SelectionPoint::State st) { 0178 for (int i=0;i<_selectionPoints.size();i++) { 0179 _selectionPoints.at(i)->setState(st); 0180 } 0181 } 0182 0183 0184 0185 0186 void Area::deleteSelectionPoints() { 0187 for (int i=0;i<_selectionPoints.size();i++) { 0188 delete _selectionPoints.at(i); 0189 } 0190 _selectionPoints.clear(); 0191 } 0192 0193 Area::~Area() { 0194 deleteSelectionPoints(); 0195 } 0196 0197 bool Area::contains(const QPoint &) const { 0198 return false; 0199 } 0200 0201 QString Area::getHTMLCode() const { 0202 return ""; 0203 } 0204 0205 QString Area::attribute(const QString & name) const 0206 { 0207 return _attributes[name.toLower()]; 0208 } 0209 0210 void Area::setAttribute(const QString & name, const QString & value) 0211 { 0212 _attributes.insert(name.toLower(), value); 0213 if (value.isEmpty()) 0214 _attributes.remove(name.toLower()); 0215 } 0216 0217 AttributeIterator Area::attributeIterator() const 0218 { 0219 return AttributeIterator(_attributes); 0220 } 0221 0222 bool Area::setCoords(const QString &) { 0223 return true; 0224 } 0225 0226 void Area::moveSelectionPoint(SelectionPoint*, const QPoint &) 0227 {} 0228 0229 // Default implementation; is specified by subclasses 0230 QString Area::coordsToString() const 0231 { 0232 return ""; 0233 } 0234 0235 0236 Area::ShapeType Area::type() const { 0237 return _type; 0238 } 0239 0240 void Area::setArea(const Area & copy) 0241 { 0242 deleteSelectionPoints(); 0243 _coords.clear(); 0244 _coords += copy.coords(); 0245 currentHighlighted=-1; 0246 0247 SelectionPointList points = copy.selectionPoints(); 0248 for (int i=0; i<points.size(); i++) { 0249 SelectionPoint* np = 0250 new SelectionPoint(points.at(i)->getPoint(),points.at(i)->cursor()); 0251 _selectionPoints.append(np); 0252 } 0253 0254 _finished=copy.finished(); 0255 _isSelected=copy.isSelected(); 0256 _rect = copy.rect(); 0257 0258 AttributeIterator it = copy.attributeIterator(); 0259 while (it.hasNext()) { 0260 it.next(); 0261 setAttribute(it.key(),it.value()); 0262 } 0263 0264 setMoving(copy.isMoving()); 0265 } 0266 0267 void Area::setFinished(bool b, bool ) { 0268 _finished=b; 0269 } 0270 0271 0272 void Area::setListViewItem(QTreeWidgetItem* item) { 0273 _listViewItem=item; 0274 } 0275 0276 void Area::deleteListViewItem() 0277 { 0278 delete _listViewItem; 0279 _listViewItem = nullptr; 0280 } 0281 0282 0283 void Area::setRect(const QRect & r) 0284 { 0285 _rect=r; 0286 updateSelectionPoints(); 0287 } 0288 0289 QRect Area::rect() const { 0290 return _rect; 0291 } 0292 0293 void Area::setMoving(bool b) { 0294 _isMoving=b; 0295 } 0296 0297 0298 void Area::moveBy(int dx, int dy) { 0299 _rect.translate(dx,dy); 0300 _coords.translate(dx,dy); 0301 0302 for (int i=0;i < _selectionPoints.size(); i++) { 0303 _selectionPoints.at(i)->translate(dx,dy); 0304 } 0305 } 0306 0307 0308 void Area::moveTo(int x, int y) { 0309 int dx = x-rect().left(); 0310 int dy = y-rect().top(); 0311 moveBy(dx,dy); 0312 } 0313 0314 int Area::countSelectionPoints() const 0315 { 0316 return selectionPoints().size(); 0317 } 0318 0319 int Area::addCoord(const QPoint & p) 0320 { 0321 _coords.resize(_coords.size()+1); 0322 _coords.setPoint(_coords.size()-1,p); 0323 _selectionPoints.append(new SelectionPoint(p,QCursor(Qt::PointingHandCursor))); 0324 setRect(_coords.boundingRect()); 0325 0326 return _coords.size()-1; 0327 } 0328 0329 void Area::insertCoord(int pos, const QPoint & p) 0330 { 0331 _coords.resize(_coords.size()+1); 0332 0333 0334 for (int i=_coords.size()-1;i>pos;i--) { 0335 _coords.setPoint(i,_coords.point(i-1)); 0336 } 0337 _coords.setPoint(pos, p); 0338 0339 _selectionPoints.insert(pos,new SelectionPoint(p,QCursor(Qt::PointingHandCursor))); 0340 setRect(_coords.boundingRect()); 0341 } 0342 0343 void Area::removeCoord(int pos) { 0344 0345 int count =_coords.size(); 0346 0347 if (count<4){ 0348 qCDebug(KIMAGEMAPEDITOR_LOG) << "Danger : trying to remove coordinate from Area with less than 4 coordinates !"; 0349 return; 0350 } 0351 0352 for (int i=pos;i<(count-1);i++) 0353 _coords.setPoint(i, _coords.point(i+1)); 0354 0355 _coords.resize(count-1); 0356 delete _selectionPoints.takeAt(pos); 0357 setRect(_coords.boundingRect()); 0358 } 0359 0360 bool Area::removeSelectionPoint(SelectionPoint * p) 0361 { 0362 if (_selectionPoints.contains(p)) 0363 { 0364 removeCoord(_selectionPoints.indexOf(p)); 0365 return true; 0366 } 0367 0368 return false; 0369 } 0370 0371 0372 void Area::moveCoord(int pos, const QPoint & p) { 0373 _coords.setPoint(pos,p); 0374 _selectionPoints.at(pos)->setPoint(p); 0375 setRect(_coords.boundingRect()); 0376 } 0377 0378 void Area::setSelected(bool b) 0379 { 0380 _isSelected=b; 0381 if (_listViewItem) { 0382 _listViewItem->setSelected(b); 0383 } 0384 } 0385 0386 void Area::highlightSelectionPoint(int number){ 0387 currentHighlighted=number; 0388 } 0389 0390 QRect Area::selectionRect() const { 0391 QRect r = rect(); 0392 r.translate(-SELSIZE*2,-SELSIZE*2); 0393 r.setSize(r.size()+QSize(SELSIZE*4,SELSIZE*4)); 0394 0395 return r; 0396 } 0397 0398 void Area::setPenAndBrush(QPainter* p) { 0399 QBrush brush(Qt::NoBrush); 0400 if (highlightArea) { 0401 QColor back = Qt::white; 0402 back.setAlpha(80); 0403 brush = QBrush(back,Qt::SolidPattern); 0404 } 0405 0406 p->setBrush(brush); 0407 0408 QColor front = Qt::white; 0409 front.setAlpha(200); 0410 p->setPen(QPen(front,1)); 0411 } 0412 0413 0414 void Area::drawAlt(QPainter* p) 0415 { 0416 double x,y; 0417 0418 const double scalex = p->transform().m11(); 0419 // double scaley = p.matrix().m12(); 0420 0421 const QTransform oldTransform = p->transform(); 0422 0423 p->setTransform(QTransform(1,oldTransform.m12(), oldTransform.m21(), 1, oldTransform.dx(), oldTransform.dy() )); 0424 0425 x = (rect().x()+rect().width()/2)*scalex; 0426 y = (rect().y()+rect().height()/2)*scalex; 0427 0428 const QFontMetrics metrics = p->fontMetrics(); 0429 0430 const int w = metrics.boundingRect(attribute("alt")).width(); 0431 x -= w/2; 0432 y += metrics.height()/4; 0433 0434 0435 0436 if (highlightArea) { 0437 p->setPen(Qt::black); 0438 } else { 0439 p->setPen(QPen(QColor("white"),1)); 0440 } 0441 0442 p->drawText(myround(x),myround(y),attribute("alt")); 0443 0444 p->setTransform(oldTransform); 0445 } 0446 0447 void Area::draw(QPainter * p) 0448 { 0449 0450 // Only draw the selection points at base class 0451 // the rest is done in the derived classes 0452 if (_isSelected) { 0453 // We do not want to have the selection points 0454 // scaled, so calculate the unscaled version 0455 const double scalex = p->transform().m11(); 0456 const QTransform oldTransform = p->transform(); 0457 p->setTransform(QTransform(1,oldTransform.m12(), 0458 oldTransform.m21(), 1, 0459 oldTransform.dx(), 0460 oldTransform.dy() )); 0461 0462 for (int i=0; i<_selectionPoints.size(); i++) { 0463 _selectionPoints.at(i)->draw(p,scalex); 0464 } 0465 p->setTransform(oldTransform); 0466 } 0467 0468 if (showAlt) { 0469 drawAlt(p); 0470 } 0471 0472 } 0473 0474 SelectionPoint* Area::onSelectionPoint(const QPoint & p, double zoom) const 0475 { 0476 0477 for (int i=0; i<_selectionPoints.size(); i++) { 0478 SelectionPoint* sp = _selectionPoints.at(i); 0479 0480 QRect r = sp->getRect(); 0481 0482 r.moveCenter(sp->getPoint()*zoom); 0483 0484 if (r.contains(p)) 0485 { 0486 return sp; 0487 } 0488 } 0489 0490 return nullptr; 0491 } 0492 0493 0494 0495 0496 /** 0497 * returns only the part of the image which is 0498 * covered by the area 0499 */ 0500 QPixmap Area::cutOut(const QImage & image) 0501 { 0502 if ( 0>=rect().width() || 0503 0>=rect().height() || 0504 !rect().intersects(image.rect()) ) 0505 { 0506 QPixmap dummyPix(10,10); 0507 dummyPix.fill(); 0508 return dummyPix; 0509 } 0510 0511 // Get the mask from the subclasses 0512 QBitmap mask=getMask(); 0513 0514 // The rectangle which is part of the image 0515 QRect partOfImage=rect(); 0516 QRect partOfMask(0,0,mask.width(),mask.height()); 0517 0518 0519 // If the area is outside of the image make the 0520 // preview smaller 0521 if ( (rect().x()+rect().width()) > image.width() ) { 0522 partOfImage.setWidth( image.width()-rect().x() ); 0523 partOfMask.setWidth( image.width()-rect().x() ); 0524 } 0525 0526 if ( (rect().x() < 0) ) { 0527 partOfImage.setX(0); 0528 partOfMask.setX(myabs(rect().x())); 0529 } 0530 0531 if ( (rect().y()+rect().height()) > image.height() ) { 0532 partOfImage.setHeight( image.height()-rect().y() ); 0533 partOfMask.setHeight ( image.height()-rect().y() ); 0534 } 0535 0536 if ( (rect().y() < 0) ) { 0537 partOfImage.setY(0); 0538 partOfMask.setY(myabs(rect().y())); 0539 } 0540 0541 QImage tempImage=mask.toImage().copy(partOfMask); 0542 mask = QPixmap::fromImage(tempImage); 0543 0544 // partOfImage = partOfImage.normalize(); 0545 QImage cut=image.copy(partOfImage); 0546 0547 QPixmap pix; 0548 0549 // partOfMask = partOfMask.normalize(); 0550 if (!partOfMask.isValid()) 0551 qCDebug(KIMAGEMAPEDITOR_LOG) << "PartofMask not valid : " << partOfMask.x() << "," << partOfMask.y() << "," 0552 << partOfMask.width() << "," << partOfMask.height() << ","; 0553 0554 /* 0555 QBitmap mask2(partOfMask.width(), partOfMask.height()); 0556 QPainter p4(&mask2); 0557 p4.drawPixmap( QPoint(0,0) ,mask,partOfMask); 0558 p4.flush(); 0559 p4.end(); 0560 */ 0561 0562 pix = QPixmap::fromImage(cut); 0563 0564 // setHighlightedPixmap(cut, mask); 0565 0566 QPixmap retPix(pix.width(),pix.height()); 0567 QPainter p3(&retPix); 0568 0569 // if transparent image fill the background 0570 // with gimp-like rectangles 0571 if (!pix.mask().isNull()) { 0572 QPixmap backPix(32,32); 0573 0574 // Gimp like transparent rectangle 0575 QPainter p2(&backPix); 0576 p2.fillRect(0,0,32,32,QColor(156,149,156)); 0577 p2.fillRect(0,16,16,16,QColor(98,105,98)); 0578 p2.fillRect(16,0,16,16,QColor(98,105,98)); 0579 0580 p3.setPen(QPen()); 0581 p3.fillRect(0,0,pix.width(),pix.height(),QBrush(QColor("black"),backPix)); 0582 } 0583 0584 0585 p3.drawPixmap(QPoint(0,0),pix); 0586 p3.end(); 0587 retPix.setMask(mask); 0588 0589 return retPix; 0590 } 0591 0592 QBitmap Area::getMask() const 0593 { 0594 QBitmap b; 0595 return b; 0596 } 0597 0598 /******************************************************************** 0599 * RECTANGLE 0600 *******************************************************************/ 0601 0602 0603 RectArea::RectArea() 0604 : Area() 0605 { 0606 _type=Area::Rectangle; 0607 QPoint p(0,0); 0608 _selectionPoints.append(new SelectionPoint(p,Qt::SizeFDiagCursor)); 0609 _selectionPoints.append(new SelectionPoint(p,Qt::SizeBDiagCursor)); 0610 _selectionPoints.append(new SelectionPoint(p,Qt::SizeBDiagCursor)); 0611 _selectionPoints.append(new SelectionPoint(p,Qt::SizeFDiagCursor)); 0612 _selectionPoints.append(new SelectionPoint(p,Qt::SizeVerCursor)); 0613 _selectionPoints.append(new SelectionPoint(p,Qt::SizeHorCursor)); 0614 _selectionPoints.append(new SelectionPoint(p,Qt::SizeVerCursor)); 0615 _selectionPoints.append(new SelectionPoint(p,Qt::SizeHorCursor)); 0616 } 0617 0618 RectArea::~RectArea() { 0619 } 0620 0621 Area* RectArea::clone() const 0622 { 0623 Area* areaClone = new RectArea(); 0624 areaClone->setArea( *this ); 0625 return areaClone; 0626 } 0627 0628 void RectArea::draw(QPainter * p) 0629 { 0630 setPenAndBrush(p); 0631 0632 QRect r(rect()); 0633 r.setWidth(r.width()+1); 0634 r.setHeight(r.height()+1); 0635 p->drawRect(r); 0636 0637 Area::draw(p); 0638 } 0639 0640 QBitmap RectArea::getMask() const 0641 { 0642 QBitmap mask(rect().width(),rect().height()); 0643 0644 mask.fill(Qt::color0); 0645 QPainter p(&mask); 0646 p.setBackground(QBrush(Qt::color0)); 0647 p.setPen(Qt::color1); 0648 p.setBrush(Qt::color1); 0649 mask.fill(Qt::color1); 0650 p.end(); 0651 0652 return mask; 0653 } 0654 0655 QString RectArea::coordsToString() const 0656 { 0657 QString retStr=QString("%1,%2,%3,%4") 0658 .arg(rect().left()) 0659 .arg(rect().top()) 0660 .arg(rect().right()) 0661 .arg(rect().bottom()); 0662 0663 return retStr; 0664 } 0665 0666 bool RectArea::contains(const QPoint & p) const{ 0667 return rect().contains(p); 0668 } 0669 0670 void RectArea::moveSelectionPoint(SelectionPoint* selectionPoint, const QPoint & p) 0671 { 0672 selectionPoint->setPoint(p); 0673 int i = _selectionPoints.indexOf(selectionPoint); 0674 0675 QRect r2(_rect); 0676 switch (i) { 0677 case 0 : 0678 _rect.setLeft(p.x()); 0679 _rect.setTop(p.y()); 0680 break; 0681 case 1 : 0682 _rect.setRight(p.x()); 0683 _rect.setTop(p.y()); 0684 break; 0685 case 2 : 0686 _rect.setLeft(p.x()); 0687 _rect.setBottom(p.y()); 0688 break; 0689 case 3 : 0690 _rect.setRight(p.x()); 0691 _rect.setBottom(p.y()); 0692 break; 0693 case 4 : // top line 0694 _rect.setTop(p.y()); 0695 break; 0696 case 5 : // right line 0697 _rect.setRight(p.x()); 0698 break; 0699 case 6 : // bottom 0700 _rect.setBottom(p.y()); 0701 break; 0702 case 7 : // left 0703 _rect.setLeft(p.x()); 0704 break; 0705 0706 } 0707 if (! _rect.isValid()) 0708 _rect=r2; 0709 0710 updateSelectionPoints(); 0711 } 0712 0713 void RectArea::updateSelectionPoints() 0714 { 0715 int d = 2; 0716 QRect r(_rect); 0717 r.adjust(0,0,1,1); 0718 int xmid = r.left()+(r.width()/d); 0719 int ymid = r.top()+(r.height()/d); 0720 0721 0722 _selectionPoints[0]->setPoint(r.topLeft()); 0723 _selectionPoints[1]->setPoint(r.topRight()); 0724 _selectionPoints[2]->setPoint(r.bottomLeft()); 0725 _selectionPoints[3]->setPoint(r.bottomRight()); 0726 _selectionPoints[4]->setPoint(QPoint(xmid,r.top())); 0727 _selectionPoints[5]->setPoint(QPoint(r.right(),ymid)); 0728 _selectionPoints[6]->setPoint(QPoint(xmid,r.bottom())); 0729 _selectionPoints[7]->setPoint(QPoint(r.left(),ymid)); 0730 } 0731 0732 bool RectArea::setCoords(const QString & s) 0733 { 0734 _finished=true; 0735 0736 const QStringList list = s.split(','); 0737 QRect r; 0738 bool ok=true; 0739 QStringList::ConstIterator it = list.begin(); 0740 r.setLeft((*it).toInt(&ok,10));it++; 0741 r.setTop((*it).toInt(&ok,10));it++; 0742 r.setRight((*it).toInt(&ok,10));it++; 0743 r.setBottom((*it).toInt(&ok,10)); 0744 if (ok) { 0745 setRect(r); 0746 return true; 0747 } else { 0748 return false; 0749 } 0750 } 0751 0752 QString RectArea::getHTMLCode() const { 0753 QString retStr; 0754 retStr+="<area "; 0755 retStr+="shape=\"rect\" "; 0756 0757 retStr+=getHTMLAttributes(); 0758 0759 retStr+="coords=\""+coordsToString()+"\" "; 0760 retStr+="/>"; 0761 return retStr; 0762 0763 } 0764 0765 /******************************************************************** 0766 * CIRCLE 0767 *******************************************************************/ 0768 0769 0770 CircleArea::CircleArea() 0771 : Area() 0772 { 0773 _type = Area::Circle; 0774 QPoint p(0,0); 0775 _selectionPoints.append(new SelectionPoint(p,Qt::SizeFDiagCursor)); 0776 _selectionPoints.append(new SelectionPoint(p,Qt::SizeBDiagCursor)); 0777 _selectionPoints.append(new SelectionPoint(p,Qt::SizeBDiagCursor)); 0778 _selectionPoints.append(new SelectionPoint(p,Qt::SizeFDiagCursor)); 0779 } 0780 0781 CircleArea::~CircleArea() { 0782 } 0783 0784 Area* CircleArea::clone() const 0785 { 0786 Area* areaClone = new CircleArea(); 0787 areaClone->setArea( *this ); 0788 return areaClone; 0789 } 0790 0791 void CircleArea::draw(QPainter * p) 0792 { 0793 setPenAndBrush(p); 0794 0795 QRect r(_rect); 0796 r.setWidth(r.width()+1); 0797 r.setHeight(r.height()+1); 0798 p->drawEllipse(r); 0799 0800 Area::draw(p); 0801 } 0802 0803 QBitmap CircleArea::getMask() const 0804 { 0805 QBitmap mask(_rect.width(),_rect.height()); 0806 0807 mask.fill(Qt::color0); 0808 QPainter p(&mask); 0809 p.setBackground(QBrush(Qt::color0)); 0810 p.setPen(Qt::color1); 0811 p.setBrush(Qt::color1); 0812 p.drawPie(QRect(0,0,_rect.width(),_rect.height()),0,5760); 0813 p.end(); 0814 0815 0816 return mask; 0817 0818 } 0819 0820 QString CircleArea::coordsToString() const 0821 { 0822 QString retStr=QString("%1,%2,%3") 0823 .arg(_rect.center().x()) 0824 .arg(_rect.center().y()) 0825 .arg(_rect.width()/2); 0826 0827 return retStr; 0828 } 0829 0830 bool CircleArea::contains(const QPoint & p) const 0831 { 0832 QRegion r(_rect,QRegion::Ellipse); 0833 return r.contains(p); 0834 } 0835 0836 void CircleArea::moveSelectionPoint(SelectionPoint* selectionPoint, const QPoint & p) 0837 { 0838 selectionPoint->setPoint(p); 0839 0840 int i = _selectionPoints.indexOf(selectionPoint); 0841 0842 // The code below really sucks, but I have no better idea. 0843 // it only makes sure that the circle is perfectly round 0844 0845 QPoint newPoint; 0846 int diff=myabs(p.x()-_rect.center().x()); 0847 if (myabs(p.y()-_rect.center().y())>diff) 0848 diff=myabs(p.y()-_rect.center().y()); 0849 0850 newPoint.setX( p.x()-_rect.center().x()<0 0851 ? _rect.center().x()-diff 0852 : _rect.center().x()+diff); 0853 0854 newPoint.setY( p.y()-_rect.center().y()<0 0855 ? _rect.center().y()-diff 0856 : _rect.center().y()+diff); 0857 0858 switch (i) { 0859 case 0 : if (newPoint.x() < _rect.center().x() && 0860 newPoint.y() < _rect.center().y()) 0861 { 0862 _rect.setLeft(newPoint.x()); 0863 _rect.setTop(newPoint.y()); 0864 } 0865 break; 0866 case 1 : if (newPoint.x() > _rect.center().x() && 0867 newPoint.y() < _rect.center().y()) 0868 { 0869 _rect.setRight(newPoint.x()); 0870 _rect.setTop(newPoint.y()); 0871 } 0872 break; 0873 case 2 : if (newPoint.x() < _rect.center().x() && 0874 newPoint.y() > _rect.center().y()) 0875 { 0876 _rect.setLeft(newPoint.x()); 0877 _rect.setBottom(newPoint.y()); 0878 } 0879 break; 0880 case 3 : if (newPoint.x() > _rect.center().x() && 0881 newPoint.y() > _rect.center().y()) 0882 { 0883 _rect.setRight(newPoint.x()); 0884 _rect.setBottom(newPoint.y()); 0885 } 0886 break; 0887 } 0888 0889 0890 0891 updateSelectionPoints(); 0892 0893 } 0894 0895 void CircleArea::setRect(const QRect & r) 0896 { 0897 QRect r2 = r; 0898 if ( r2.height() != r2.width() ) 0899 r2.setHeight( r2.width() ); 0900 0901 Area::setRect(r2); 0902 } 0903 0904 0905 void CircleArea::updateSelectionPoints() 0906 { 0907 _selectionPoints[0]->setPoint(_rect.topLeft()); 0908 _selectionPoints[1]->setPoint(_rect.topRight()); 0909 _selectionPoints[2]->setPoint(_rect.bottomLeft()); 0910 _selectionPoints[3]->setPoint(_rect.bottomRight()); 0911 } 0912 0913 bool CircleArea::setCoords(const QString & s) 0914 { 0915 _finished=true; 0916 const QStringList list = s.split(','); 0917 bool ok=true; 0918 QStringList::ConstIterator it = list.begin(); 0919 int x=(*it).toInt(&ok,10);it++; 0920 int y=(*it).toInt(&ok,10);it++; 0921 int rad=(*it).toInt(&ok,10); 0922 if (!ok) return false; 0923 QRect r; 0924 r.setWidth(rad*2); 0925 r.setHeight(rad*2); 0926 r.moveCenter(QPoint(x,y)); 0927 setRect(r); 0928 return true; 0929 } 0930 0931 QString CircleArea::getHTMLCode() const { 0932 QString retStr; 0933 retStr+="<area "; 0934 retStr+="shape=\"circle\" "; 0935 0936 retStr+=getHTMLAttributes(); 0937 0938 retStr+="coords=\""+coordsToString()+"\" "; 0939 retStr+="/>"; 0940 return retStr; 0941 0942 } 0943 0944 0945 /******************************************************************** 0946 * POLYGON 0947 *******************************************************************/ 0948 0949 0950 PolyArea::PolyArea() 0951 : Area() 0952 { 0953 _type = Area::Polygon; 0954 } 0955 0956 PolyArea::~PolyArea() { 0957 } 0958 0959 Area* PolyArea::clone() const 0960 { 0961 Area* areaClone = new PolyArea(); 0962 areaClone->setArea( *this ); 0963 return areaClone; 0964 } 0965 0966 void PolyArea::draw(QPainter * p) 0967 { 0968 setPenAndBrush(p); 0969 0970 if (_finished) 0971 p->drawPolygon( _coords.constData(),_coords.count()); 0972 else { 0973 p->drawPolyline(_coords.constData(),_coords.count()); 0974 } 0975 0976 Area::draw(p); 0977 } 0978 0979 QBitmap PolyArea::getMask() const 0980 { 0981 QBitmap mask(_rect.width(),_rect.height()); 0982 0983 mask.fill(Qt::color0); 0984 QPainter p(&mask); 0985 p.setBackground(QBrush(Qt::color0)); 0986 p.setPen(Qt::color1); 0987 p.setBrush(Qt::color1); 0988 p.setClipping(true); 0989 QRegion r(_coords); 0990 r.translate(-_rect.left(),-_rect.top()); 0991 p.setClipRegion(r); 0992 p.fillRect(QRect(0,0,_rect.width(),_rect.height()),Qt::color1); 0993 p.end(); 0994 0995 return mask; 0996 } 0997 0998 QString PolyArea::coordsToString() const 0999 { 1000 QString retStr; 1001 1002 for (int i=0;i<_coords.count();i++) { 1003 retStr.append(QString("%1,%2,") 1004 .arg(_coords.point(i).x()) 1005 .arg(_coords.point(i).y())); 1006 } 1007 1008 retStr.remove(retStr.length()-1,1); 1009 1010 return retStr; 1011 } 1012 1013 int PolyArea::distance(const QPoint &p1, const QPoint &p2) 1014 { 1015 QPoint temp = p1-p2; 1016 return temp.manhattanLength(); 1017 } 1018 1019 bool PolyArea::isBetween(const QPoint &p, const QPoint &p1, const QPoint &p2) 1020 { 1021 int dist = distance(p,p1)+distance(p,p2)-distance(p1,p2); 1022 1023 if (myabs(dist)<1) 1024 return true; 1025 else 1026 return false; 1027 } 1028 1029 void PolyArea::simplifyCoords() 1030 { 1031 if (_coords.size()<4) 1032 return; 1033 1034 QPoint p = _coords.point(0) - _coords.point(1); 1035 1036 int i = 1; 1037 1038 1039 while( (i<_coords.size()) && (_coords.size() > 3) ) 1040 { 1041 p = _coords.point(i-1) - _coords.point(i); 1042 1043 if (p.manhattanLength() < 3) 1044 removeCoord(i); 1045 else 1046 i++; 1047 } 1048 1049 p = _coords.point(0) - _coords.point(1); 1050 1051 double angle2; 1052 double angle1; 1053 1054 if (p.y()==0) 1055 angle1 = 1000000000; 1056 else 1057 angle1 = (double) p.x() / (double) p.y(); 1058 1059 i=2; 1060 1061 while( (i<_coords.size()) && (_coords.size() > 3) ) 1062 { 1063 p = _coords.point(i-1) - _coords.point(i); 1064 1065 if (p.y()==0) 1066 angle2 = 1000000000; 1067 else 1068 angle2 = (double) p.x() / (double) p.y(); 1069 1070 if ( angle2==angle1 ) 1071 { 1072 qCDebug(KIMAGEMAPEDITOR_LOG) << "removing " << i-1; 1073 removeCoord(i-1); 1074 } 1075 else 1076 { 1077 i++; 1078 qCDebug(KIMAGEMAPEDITOR_LOG) << "skipping " << i-1 << " cause " << angle1 << "!= " << angle2; 1079 angle1 = angle2; 1080 1081 } 1082 1083 } 1084 1085 1086 1087 } 1088 1089 1090 int PolyArea::addCoord(const QPoint & p) 1091 { 1092 if (_coords.size()<3) 1093 { 1094 return Area::addCoord(p); 1095 } 1096 1097 if (_coords.point(_coords.size()-1) == p) 1098 { 1099 qCDebug(KIMAGEMAPEDITOR_LOG) << "equal Point added"; 1100 return -1; 1101 1102 } 1103 1104 int n=_coords.size(); 1105 1106 // QPoint temp = p-_coords.point(0); 1107 int nearest = 0; 1108 int olddist = distance(p,_coords.point(0)); 1109 int mindiff = 999999999; 1110 1111 // find the two points, which are the nearest one to the new point 1112 for (int i=1; i <= n; i++) 1113 { 1114 int dist = distance(p,_coords.point(i%n)); 1115 int dist2 = distance(_coords.point(i-1),_coords.point(i%n)); 1116 int diff = myabs(dist+olddist-dist2); 1117 if ( diff<mindiff ) 1118 { 1119 mindiff = diff; 1120 nearest = i%n; 1121 } 1122 olddist=dist; 1123 } 1124 1125 insertCoord(nearest, p); 1126 1127 return nearest; 1128 1129 } 1130 1131 bool PolyArea::contains(const QPoint & p) const 1132 { 1133 // A line can't contain a point 1134 if (_coords.count() >2 ) { 1135 QRegion r(_coords); 1136 return r.contains(p); 1137 } 1138 else 1139 return false; 1140 } 1141 1142 void PolyArea::moveSelectionPoint(SelectionPoint* selectionPoint, const QPoint & p) 1143 { 1144 selectionPoint->setPoint(p); 1145 1146 int i = _selectionPoints.indexOf(selectionPoint); 1147 _coords.setPoint(i,p); 1148 _rect=_coords.boundingRect(); 1149 } 1150 1151 void PolyArea::updateSelectionPoints() 1152 { 1153 for (int i = 0; i < _selectionPoints.size(); ++i) { 1154 _selectionPoints.at(i)->setPoint(_coords.point(i)); 1155 } 1156 } 1157 1158 bool PolyArea::setCoords(const QString & s) 1159 { 1160 _finished=true; 1161 const QStringList list = s.split(','); 1162 _coords.clear(); 1163 _selectionPoints.clear(); 1164 1165 for (QStringList::ConstIterator it = list.begin(); it !=list.end(); ++it) 1166 { 1167 bool ok=true; 1168 int newXCoord=(*it).toInt(&ok,10); 1169 if (!ok) return false; 1170 it++; 1171 if (it==list.end()) break; 1172 int newYCoord=(*it).toInt(&ok,10); 1173 if (!ok) return false; 1174 insertCoord(_coords.size(), QPoint(newXCoord,newYCoord)); 1175 } 1176 1177 return true; 1178 1179 } 1180 1181 QString PolyArea::getHTMLCode() const { 1182 QString retStr; 1183 retStr+="<area "; 1184 retStr+="shape=\"poly\" "; 1185 1186 retStr+=getHTMLAttributes(); 1187 1188 retStr+="coords=\""+coordsToString()+"\" "; 1189 retStr+="/>"; 1190 return retStr; 1191 1192 } 1193 1194 void PolyArea::setFinished(bool b, bool removeLast = true) 1195 { 1196 // The last Point is the same as the first 1197 // so delete it 1198 if (b && removeLast) { 1199 _coords.resize(_coords.size()-1); 1200 _selectionPoints.removeLast(); 1201 } 1202 1203 _finished = b; 1204 } 1205 1206 QRect PolyArea::selectionRect() const 1207 { 1208 QRect r = _rect; 1209 1210 r.translate(-10,-10); 1211 r.setSize(r.size()+QSize(21,21)); 1212 1213 return r; 1214 } 1215 1216 1217 1218 /******************************************************************** 1219 * DEFAULT 1220 *******************************************************************/ 1221 1222 1223 DefaultArea::DefaultArea() 1224 : Area() 1225 { 1226 _type=Area::Default; 1227 } 1228 1229 DefaultArea::~DefaultArea() { 1230 } 1231 1232 Area* DefaultArea::clone() const 1233 { 1234 Area* areaClone = new DefaultArea(); 1235 areaClone->setArea( *this ); 1236 return areaClone; 1237 } 1238 1239 void DefaultArea::draw(QPainter *) 1240 {} 1241 1242 1243 QString DefaultArea::getHTMLCode() const { 1244 QString retStr; 1245 retStr+="<area "; 1246 retStr+="shape=\"default\" "; 1247 1248 retStr+=getHTMLAttributes(); 1249 1250 retStr+="/>"; 1251 return retStr; 1252 1253 } 1254 1255 1256 /******************************************************************** 1257 * AreaSelection 1258 *******************************************************************/ 1259 1260 AreaSelection::AreaSelection() 1261 : Area() 1262 { 1263 _areas = new AreaList(); 1264 _name = "Selection"; 1265 invalidate(); 1266 } 1267 1268 AreaSelection::~AreaSelection() { 1269 delete _areas; 1270 } 1271 1272 Area* AreaSelection::clone() const 1273 { 1274 AreaSelection* areaClone = new AreaSelection(); 1275 1276 // we want a deep copy of the Areas 1277 AreaListIterator it=getAreaListIterator(); 1278 while (it.hasNext()) { 1279 areaClone->add( it.next()->clone() ); 1280 } 1281 1282 return areaClone; 1283 } 1284 1285 1286 void AreaSelection::add(Area *a) 1287 { 1288 // if a selection of areas was added get the areas of it 1289 AreaSelection *selection = nullptr; 1290 if ( (selection = dynamic_cast <AreaSelection*> ( a ) ) ) { 1291 AreaList list = selection->getAreaList(); 1292 Area* area; 1293 foreach(area,list) { 1294 if ( !_areas->contains( area )) { 1295 _areas->append( area ); // Must come before area->setSelected 1296 area->setSelected( true ); 1297 } 1298 } 1299 } else { 1300 if ( !_areas->contains( a )) { 1301 _areas->append( a ); // Must come before a->setSelected 1302 a->setSelected( true ); 1303 } 1304 } 1305 1306 invalidate(); 1307 } 1308 1309 1310 void AreaSelection::setSelectionPointStates(SelectionPoint::State st) { 1311 AreaListIterator it=getAreaListIterator(); 1312 while(it.hasNext()) { 1313 it.next()->setSelectionPointStates( st ); 1314 } 1315 } 1316 1317 void AreaSelection::updateSelectionPointStates() { 1318 SelectionPoint::State st = SelectionPoint::Normal; 1319 1320 if (_areas->count() > 1) 1321 st = SelectionPoint::Inactive; 1322 1323 setSelectionPointStates(st); 1324 } 1325 1326 1327 void AreaSelection::remove(Area *a) 1328 { 1329 if (!_areas->contains(a)) 1330 return; 1331 1332 a->setSelected( false ); 1333 _areas->removeAt(_areas->indexOf(a)); 1334 invalidate(); 1335 } 1336 1337 void AreaSelection::reset() 1338 { 1339 AreaListIterator it=getAreaListIterator(); 1340 while (it.hasNext()) { 1341 it.next()->setSelected( false ); 1342 } 1343 1344 _areas->clear(); 1345 invalidate(); 1346 } 1347 1348 bool AreaSelection::contains(const QPoint & p) const 1349 { 1350 AreaListIterator it=getAreaListIterator(); 1351 while (it.hasNext()) { 1352 if ( it.next()->contains( p ) ) { 1353 return true; 1354 } 1355 } 1356 1357 return false; 1358 } 1359 1360 SelectionPoint* AreaSelection::onSelectionPoint(const QPoint & p, double zoom) const 1361 { 1362 1363 if (_areas->count() != 1) 1364 return nullptr; 1365 1366 return _areas->first()->onSelectionPoint(p,zoom); 1367 } 1368 1369 void AreaSelection::moveSelectionPoint(SelectionPoint* selectionPoint, const QPoint & p) 1370 { 1371 // It's only possible to move a SelectionPoint if only one Area is selected 1372 if (_areas->count() != 1) 1373 return; 1374 1375 _areas->first()->moveSelectionPoint(selectionPoint,p); 1376 1377 invalidate(); 1378 } 1379 1380 1381 void AreaSelection::moveBy(int dx, int dy) 1382 { 1383 AreaListIterator it=getAreaListIterator(); 1384 while (it.hasNext()) { 1385 it.next()->moveBy(dx,dy); 1386 } 1387 1388 Area::moveBy( dx, dy ); 1389 invalidate(); 1390 } 1391 1392 QString AreaSelection::typeString() const 1393 { 1394 // if there is only one Area selected 1395 // show the name of that Area 1396 if ( _areas->count()==0 ) 1397 return ""; 1398 else if ( _areas->count()==1 ) 1399 return _areas->first()->typeString(); 1400 else 1401 return i18n("Number of Areas"); 1402 1403 } 1404 1405 Area::ShapeType AreaSelection::type() const 1406 { 1407 // if there is only one Area selected 1408 // take the type of that Area 1409 if ( _areas->count()==0 ) 1410 return Area::None; 1411 else if ( _areas->count()==1 ) 1412 return _areas->first()->type(); 1413 else 1414 return Area::Selection; 1415 } 1416 1417 void AreaSelection::resetSelectionPointState() { 1418 updateSelectionPointStates(); 1419 } 1420 1421 void AreaSelection::updateSelectionPoints() 1422 { 1423 AreaListIterator it=getAreaListIterator(); 1424 while (it.hasNext()) { 1425 it.next()->updateSelectionPoints(); 1426 } 1427 1428 invalidate(); 1429 } 1430 1431 1432 1433 QRect AreaSelection::selectionRect() const 1434 { 1435 if (!_selectionCacheValid) { 1436 _selectionCacheValid=true; 1437 QRect r; 1438 AreaListIterator it=getAreaListIterator(); 1439 while (it.hasNext()) { 1440 r = r | it.next()->selectionRect(); 1441 } 1442 _cachedSelectionRect=r; 1443 } 1444 1445 return _cachedSelectionRect; 1446 } 1447 1448 int AreaSelection::count() const { 1449 return _areas->count(); 1450 } 1451 1452 bool AreaSelection::isEmpty() const 1453 { 1454 return _areas->isEmpty(); 1455 } 1456 1457 1458 AreaList AreaSelection::getAreaList() const { 1459 AreaList list(*_areas); 1460 return list; 1461 } 1462 1463 AreaListIterator AreaSelection::getAreaListIterator() const { 1464 AreaListIterator it(*_areas); 1465 return it; 1466 } 1467 1468 void AreaSelection::setArea(const Area & copy) 1469 { 1470 Area *area = copy.clone(); 1471 AreaSelection *selection = dynamic_cast<AreaSelection*>(area); 1472 if (selection) 1473 setAreaSelection(*selection); 1474 else { 1475 Area::setArea(copy); 1476 invalidate(); 1477 } 1478 } 1479 1480 void AreaSelection::setAreaSelection(const AreaSelection & copy) 1481 { 1482 AreaList* areasCopy = copy._areas; 1483 1484 if (_areas->count() != areasCopy->count()) 1485 return; 1486 1487 AreaListIterator it(*_areas); 1488 AreaListIterator it2(*areasCopy); 1489 while (it.hasNext()) { 1490 it.next()->setArea(*it2.next()); 1491 } 1492 1493 Area::setArea(copy); 1494 invalidate(); 1495 } 1496 1497 void AreaSelection::setAreaList( const AreaList & areas ) 1498 { 1499 delete _areas; 1500 _areas = new AreaList(areas); 1501 invalidate(); 1502 } 1503 1504 void AreaSelection::setRect(const QRect & r) 1505 { 1506 if ( _areas->count()==1 ) 1507 { 1508 _areas->first()->setRect(r); 1509 } 1510 1511 invalidate(); 1512 _rect=rect(); 1513 updateSelectionPoints(); 1514 } 1515 1516 QRect AreaSelection::rect() const 1517 { 1518 if (!_rectCacheValid) 1519 { 1520 _rectCacheValid=true; 1521 QRect r; 1522 AreaListIterator it=getAreaListIterator(); 1523 1524 while (it.hasNext()) { 1525 r = r | it.next()->rect(); 1526 } 1527 1528 _cachedRect=r; 1529 } 1530 1531 return _cachedRect; 1532 } 1533 1534 1535 int AreaSelection::addCoord(const QPoint & p) 1536 { 1537 if ( _areas->count()==1 ) 1538 { 1539 return _areas->first()->addCoord(p); 1540 invalidate(); 1541 } 1542 1543 return 0; 1544 } 1545 1546 void AreaSelection::insertCoord(int pos, const QPoint & p) 1547 { 1548 if ( _areas->count()==1 ) 1549 { 1550 _areas->first()->insertCoord(pos, p); 1551 invalidate(); 1552 } 1553 } 1554 1555 void AreaSelection::removeCoord(int pos) 1556 { 1557 if ( _areas->count()==1 ) 1558 { 1559 _areas->first()->removeCoord(pos); 1560 invalidate(); 1561 } 1562 } 1563 1564 bool AreaSelection::removeSelectionPoint(SelectionPoint* p) 1565 { 1566 bool result=false; 1567 1568 if ( _areas->count()==1 ) 1569 { 1570 result = _areas->first()->removeSelectionPoint(p); 1571 invalidate(); 1572 } 1573 1574 return result; 1575 } 1576 1577 const SelectionPointList & AreaSelection::selectionPoints() const 1578 { 1579 if ( _areas->count()==1 ) 1580 { 1581 return _areas->first()->selectionPoints(); 1582 } 1583 1584 return _selectionPoints; 1585 } 1586 1587 1588 void AreaSelection::moveCoord(int pos,const QPoint & p) 1589 { 1590 if ( _areas->count()==1 ) 1591 { 1592 _areas->first()->moveCoord(pos,p); 1593 invalidate(); 1594 } 1595 } 1596 1597 void AreaSelection::highlightSelectionPoint(int i) 1598 { 1599 if ( _areas->count()==1 ) 1600 { 1601 _areas->first()->highlightSelectionPoint(i); 1602 invalidate(); 1603 } 1604 } 1605 1606 1607 QPolygon AreaSelection::coords() const 1608 { 1609 if ( _areas->count()==1 ) 1610 { 1611 return _areas->first()->coords(); 1612 } 1613 1614 return Area::coords(); 1615 } 1616 1617 QString AreaSelection::attribute(const QString & name) const 1618 { 1619 if ( _areas->count()==1 ) 1620 { 1621 return _areas->first()->attribute(name); 1622 } 1623 1624 return Area::attribute(name); 1625 } 1626 1627 void AreaSelection::setAttribute(const QString & name, const QString & value) 1628 { 1629 AreaListIterator it=getAreaListIterator(); 1630 1631 while (it.hasNext()) { 1632 it.next()->setAttribute(name,value); 1633 } 1634 1635 Area::setAttribute(name,value); 1636 } 1637 1638 AttributeIterator AreaSelection::attributeIterator() const 1639 { 1640 if ( _areas->count()==1 ) 1641 { 1642 return _areas->first()->attributeIterator(); 1643 } 1644 1645 return AttributeIterator(_attributes); 1646 } 1647 1648 void AreaSelection::setMoving(bool b) 1649 { 1650 AreaListIterator it=getAreaListIterator(); 1651 1652 while (it.hasNext()) { 1653 it.next()->setMoving(b); 1654 } 1655 1656 Area::setMoving(b); 1657 } 1658 1659 bool AreaSelection::isMoving() const 1660 { 1661 if ( _areas->count()==1 ) 1662 { 1663 return _areas->first()->isMoving(); 1664 } 1665 1666 return Area::isMoving(); 1667 } 1668 1669 1670 /** 1671 * Checks if an area is outside the rectangle parameter 1672 * returns false if an area has no pixel in common with the rectangle parameter 1673 **/ 1674 bool AreaSelection::allAreasWithin(const QRect & r) const 1675 { 1676 if ( ! r.contains(rect()) ) 1677 { 1678 AreaListIterator it=getAreaListIterator(); 1679 1680 while (it.hasNext()) { 1681 if (!it.next()->rect().intersects(r)) 1682 return false; 1683 } 1684 } 1685 1686 return true; 1687 } 1688 1689 1690 void AreaSelection::draw(QPainter *) 1691 {} 1692 1693 void AreaSelection::invalidate() { 1694 _selectionCacheValid=false; 1695 _rectCacheValid=false; 1696 updateSelectionPointStates(); 1697 } 1698 1699