]> git.sven.stormbind.net Git - sven/vym.git/blob - src/linkablemapobj.cpp
Replace Pierre as the maintainer
[sven/vym.git] / src / linkablemapobj.cpp
1 #include <cstdlib>
2 #include <iostream>
3 #include <math.h>
4
5 #include "branchobj.h"
6 #include "linkablemapobj.h"
7 #include "vymmodel.h"
8
9 extern bool debug;
10
11 /////////////////////////////////////////////////////////////////
12 // LinkableMapObj
13 /////////////////////////////////////////////////////////////////
14
15 LinkableMapObj::LinkableMapObj(QGraphicsItem *parent, TreeItem *ti)
16     : MapObj(parent, ti)
17 {
18     // qDebug() << "Const LinkableMapObj this="<<this<<"  ti="<<ti<<"
19     // treeItem="<<treeItem;
20     parObj = (LinkableMapObj *)
21         parent; // FIXME-4 try to get rid of parObj and use parentItem() instead
22     init();
23 }
24
25 LinkableMapObj::~LinkableMapObj()
26 {
27     // qDebug()<< "Destructor LMO  this="<<this<<" style="<<style<<" l="<<l<<"
28     // p="<<p<<"  segment="<<segment.count();
29     delLink();
30 }
31
32 void LinkableMapObj::init()
33 {
34     parObjTmpBuf = NULL;
35     tmpParent = false;
36     parPos = QPointF(0, 0);
37     childRefPos = QPointF(0, 0);
38     floatRefPos = QPointF(0, 0);
39     link2ParPos = false;
40     l = NULL;
41     p = NULL;
42     orientation = UndefinedOrientation;
43     linkwidth = 20;
44     thickness_start = 8;
45     style = UndefinedStyle;
46     linkpos = Bottom;
47     arcsegs = 13;
48
49     // TODO instead of linkcolor pen.color() could be used      all around
50     pen.setWidth(1);
51     pen.setColor(linkcolor);
52     pen.setCapStyle(Qt::RoundCap);
53
54     useBottomline = false;
55     bottomline = NULL;
56
57     topPad = botPad = leftPad = rightPad = 0;
58
59     repositionRequest = false;
60
61     // Rel Positions
62     relPos = QPointF(0, 0);
63     useRelPos = false;
64 }
65
66 void LinkableMapObj::createBottomLine()
67 {
68     bottomline = scene()->addLine(QLineF(1, 1, 1, 1), pen);
69     bottomline->setZValue(dZ_LINK);
70 }
71
72 void LinkableMapObj::delLink()
73 {
74     if (bottomline) {
75         delete (bottomline);
76         bottomline = NULL;
77     }
78     switch (style) {
79     case Line:
80         delete (l);
81         break;
82     case Parabel:
83         while (!segment.isEmpty())
84             delete segment.takeFirst();
85         break;
86     case PolyLine:
87         delete (p);
88         break;
89     case PolyParabel:
90         delete (p);
91         break;
92     default:
93         break;
94     }
95 }
96
97 void LinkableMapObj::copy(LinkableMapObj *other)
98 {
99     MapObj::copy(other);
100     bboxTotal = other->bboxTotal;
101     setLinkStyle(other->style);
102     setLinkColor(other->linkcolor);
103     relPos = other->relPos;
104     treeItem = other->treeItem;
105 }
106
107 void LinkableMapObj::setParObj(LinkableMapObj *o)
108 {
109     parObj = o;
110     setParentItem(parObj);
111 }
112
113 void LinkableMapObj::setParObjTmp(LinkableMapObj *, QPointF, int) {}
114
115 void LinkableMapObj::unsetParObjTmp() {}
116
117 bool LinkableMapObj::hasParObjTmp() { return tmpParent; }
118
119 void LinkableMapObj::setUseRelPos(const bool &b) { useRelPos = b; }
120
121 bool LinkableMapObj::getUseRelPos() { return useRelPos; }
122
123 void LinkableMapObj::setRelPos()
124 {
125     if (parObj)
126         setRelPos(absPos - parObj->getChildRefPos());
127     else
128         qWarning() << "LMO::setRelPos parObj==0   this=" << this;
129 }
130
131 void LinkableMapObj::setRelPos(const QPointF &p)
132 {
133     if (parObj) {
134         relPos = p;
135         useRelPos = true;
136         setOrientation();
137     }
138     else
139         qWarning() << "LMO::setRelPos (p)  parObj==0   this=" << this;
140 }
141
142 QPointF LinkableMapObj::getRelPos()
143 {
144     if (!parObj) {
145         qWarning() << "LMO::getRelPos parObj==0   this=" << this;
146         return QPointF();
147     }
148     return relPos;
149 }
150
151 qreal LinkableMapObj::getTopPad() { return topPad; }
152
153 qreal LinkableMapObj::getLeftPad() { return leftPad; }
154
155 qreal LinkableMapObj::getRightPad() { return rightPad; }
156
157 LinkableMapObj::Style LinkableMapObj::getDefLinkStyle(TreeItem *parent)
158 {
159     VymModel *model = treeItem->getModel();
160     if (!model) {
161         qWarning("LMO::getDefLinkStyle   model=NULL");
162         // return UndefinedStyle;
163     }
164     Style ls = model->getMapLinkStyle();
165     int depth = 1 + parent->depth();
166     if (depth == 0)
167         return UndefinedStyle;
168     switch (ls) {
169     case Line:
170         return ls;
171         break;
172     case Parabel:
173         return ls;
174         break;
175     case PolyLine:
176         if (depth > 1)
177             return Line;
178         else
179             return ls;
180         break;
181     case PolyParabel:
182         if (depth > 1)
183             return Parabel;
184         else
185             return ls;
186         break;
187     default:
188         break;
189     }
190     return UndefinedStyle;
191 }
192
193 void LinkableMapObj::setLinkStyle(Style newstyle)
194 {
195     // qDebug()<<"LMO::setLinkStyle s="<<newstyle;      //FIXME-4 called very
196     // often?!?! qDebug()<<"LMO::setLinkStyle s="<<newstyle<<" for "<<this<<"
197     // "<<treeItem->getHeading()<<"  parObj="<<parObj;
198     delLink();
199
200     style = newstyle;
201
202     QGraphicsLineItem *cl;
203     switch (style) {
204     case Line:
205         l = scene()->addLine(QLineF(1, 1, 1, 1), pen);
206         l->setZValue(dZ_LINK);
207         if (visible)
208             l->show();
209         else
210             l->hide();
211         createBottomLine();
212         break;
213     case Parabel:
214         for (int i = 0; i < arcsegs; i++) {
215             cl = scene()->addLine(QLineF(i * 5, 0, i * 10, 100), pen);
216             cl->setZValue(dZ_LINK);
217             if (visible)
218                 cl->show();
219             else
220                 cl->hide();
221             segment.append(cl);
222         }
223         pa0.resize(arcsegs + 1);
224         createBottomLine();
225         break;
226     case PolyLine:
227         p = scene()->addPolygon(QPolygonF(), pen, linkcolor);
228         p->setZValue(dZ_LINK);
229         if (visible)
230             p->show();
231         else
232             p->hide();
233         pa0.resize(3);
234         createBottomLine();
235         break;
236     case PolyParabel:
237         p = scene()->addPolygon(QPolygonF(), pen, linkcolor);
238         p->setZValue(dZ_LINK);
239         if (visible)
240             p->show();
241         else
242             p->hide();
243         pa0.resize(arcsegs * 2 + 2);
244         pa1.resize(arcsegs + 1);
245         pa2.resize(arcsegs + 1);
246         createBottomLine();
247         break;
248     default:
249         break;
250     }
251 }
252
253 LinkableMapObj::Style LinkableMapObj::getLinkStyle() { return style; }
254
255 void LinkableMapObj::setLinkPos(Position lp) { linkpos = lp; }
256
257 LinkableMapObj::Position LinkableMapObj::getLinkPos() { return linkpos; }
258
259 void LinkableMapObj::setLinkColor()
260 {
261     // Overloaded in BranchObj and children
262     // here only set default color
263     VymModel *model = treeItem->getModel();
264     if (!model)
265         return;
266
267     if (model->getMapLinkColorHint() == HeadingColor) {
268         if (treeItem->isBranchLikeType() )
269             LinkableMapObj::setLinkColor(treeItem->getHeading().getColor());
270         else
271             // For images use color of parent heading
272             LinkableMapObj::setLinkColor(treeItem->parent()->getHeading().getColor());
273     } else
274         LinkableMapObj::setLinkColor(model->getMapDefLinkColor());
275 }
276
277 void LinkableMapObj::setLinkColor(QColor col)
278 {
279     linkcolor = col;
280     pen.setColor(col);
281     if (bottomline)
282         bottomline->setPen(pen);
283     switch (style) {
284     case Line:
285         l->setPen(pen);
286         break;
287     case Parabel:
288         for (int i = 0; i < segment.size(); ++i)
289             segment.at(i)->setPen(pen);
290         break;
291     case PolyLine:
292         p->setBrush(QBrush(col));
293         p->setPen(pen);
294         break;
295     case PolyParabel:
296         p->setBrush(QBrush(col));
297         p->setPen(pen);
298         break;
299     default:
300         break;
301     }
302 }
303
304 QColor LinkableMapObj::getLinkColor() { return linkcolor; }
305
306 void LinkableMapObj::setVisibility(bool v)
307 {
308     MapObj::setVisibility(v);
309     updateVisibility();
310 }
311
312 void LinkableMapObj::setOrientation()
313 {
314     if (!parObj) {
315         orientation = UndefinedOrientation;
316         return;
317     }
318
319     // calc orientation depending on position rel to parent
320     if (useRelPos) {
321         if (relPos.x() < 0)
322             orientation = LeftOfCenter;
323         else
324             orientation = RightOfCenter;
325     }
326     else
327         // use the orientation of the parent:
328         orientation = parObj->getOrientation();
329 }
330
331 void LinkableMapObj::updateVisibility()
332 {
333     bool visnow = visible;
334
335     // Hide links of unselected objects (if wanted)
336     if (((MapItem *)treeItem)->getHideLinkUnselected() &&
337         !treeItem->getModel()->isSelected(treeItem))
338         visnow = false;
339
340     if (visnow) {
341         if (bottomline) {
342             if (useBottomline)
343                 bottomline->show();
344             else
345                 bottomline->hide();
346         }
347
348         switch (style) {
349         case Line:
350             if (l)
351                 l->show();
352             break;
353         case Parabel:
354             for (int i = 0; i < segment.size(); ++i)
355                 segment.at(i)->show();
356             break;
357         case PolyLine:
358             if (p)
359                 p->show();
360             else
361                 qDebug() << "LMO::updateVis p==0 (PolyLine)"; // FIXME-4
362             break;
363         case PolyParabel:
364             if (p)
365                 p->show();
366             else
367                 qDebug() << "LMO::updateVis p==0 (PolyParabel) "
368                          << treeItem->getHeadingPlain(); // FIXME-4
369             break;
370         default:
371             break;
372         }
373     }
374     else {
375         if (bottomline)
376             bottomline->hide();
377         switch (style) {
378         case Line:
379             if (l)
380                 l->hide();
381             break;
382         case Parabel:
383             for (int i = 0; i < segment.size(); ++i)
384                 segment.at(i)->hide();
385             break;
386         case PolyLine:
387             if (p)
388                 p->hide();
389             break;
390         case PolyParabel:
391             if (p)
392                 p->hide();
393             break;
394         default:
395             break;
396         }
397     }
398 }
399
400 void LinkableMapObj::updateLinkGeometry()
401 {
402     // needs:
403     //  childRefPos of parent
404     //  orient   of parent
405     //  style
406     //
407     // sets:
408     //  orientation
409     //  childRefPos    (by calling setDockPos())
410     //  parPos      (by calling setDockPos())
411     //  bottomlineY
412     //  drawing of the link itself
413
414     // updateLinkGeometry is called from move, but called from constructor we
415     // don't have parents yet...
416
417     if (style == UndefinedStyle) {
418         setDockPos();
419         return;
420     }
421
422     switch (linkpos) {
423     case Middle:
424         bottomlineY =
425             bbox.top() + bbox.height() / 2; // draw link to middle (of frame)
426         break;
427     case Bottom:
428         // bottomlineY = bbox.bottom()-1;  // draw link to bottom of box
429         bottomlineY = bbox.bottom() - botPad;
430         break;
431     }
432
433     double p2x, p2y; // Set P2 Before setting
434     if (!link2ParPos) {
435         p2x = QPointF(parObj->getChildRefPos()).x(); // P1, we have to look at
436         p2y = QPointF(parObj->getChildRefPos()).y(); // orientation
437     }
438     else {
439         p2x = QPointF(parObj->getParPos()).x();
440         p2y = QPointF(parObj->getParPos()).y();
441     }
442
443     setOrientation();
444     setDockPos(); // Call overloaded method
445
446     double p1x = parPos.x(); // Link is drawn from P1 to P2
447     double p1y = parPos.y();
448
449     double vx = p2x - p1x; // V=P2-P1
450     double vy = p2y - p1y;
451
452     int z;
453     // Hack to z-move links to MapCenter (d==1) below MCOs frame (d==0)
454     // //FIXME-4 no longer used?
455     if (treeItem->depth() < 2)
456         // z=(treeItem->depth() -2)*dZ_DEPTH + dZ_LINK;
457         z = -dZ_LINK;
458     else
459         z = dZ_LINK;
460
461     // qDebug()<<"LMO::updateGeo d="<<treeItem->depth()<<"  this="<<this<<"
462     // "<<treeItem->getHeading();
463
464     // Draw the horizontal line below heading (from childRefPos to ParPos)
465     if (bottomline) {
466         bottomline->setLine(QLineF(childRefPos.x(), childRefPos.y(), p1x, p1y));
467         bottomline->setZValue(z);
468     }
469
470     double a; // angle
471     if (vx > -0.000001 && vx < 0.000001)
472         a = M_PI_2;
473     else
474         a = atan(vy / vx);
475     // "turning point" for drawing polygonal links
476     QPointF tp(-qRound(sin(a) * thickness_start),
477                qRound(cos(a) * thickness_start));
478
479     // Draw the link
480     switch (style) {
481     case Line:
482         l->setLine(QLine(qRound(parPos.x()), qRound(parPos.y()), qRound(p2x),
483                          qRound(p2y)));
484         l->setZValue(z);
485         break;
486     case Parabel:
487         parabel(pa0, p1x, p1y, p2x, p2y);
488         for (int i = 0; i < segment.size(); ++i) {
489             segment.at(i)->setLine(QLineF(pa0.at(i).x(), pa0.at(i).y(),
490                                           pa0.at(i + 1).x(),
491                                           pa0.at(i + 1).y()));
492             segment.at(i)->setZValue(z);
493         }
494         break;
495     case PolyLine:
496         pa0.clear();
497         pa0 << QPointF(qRound(p2x + tp.x()), qRound(p2y + tp.y()));
498         pa0 << QPointF(qRound(p2x - tp.x()), qRound(p2y - tp.y()));
499         pa0 << QPointF(qRound(parPos.x()), qRound(parPos.y()));
500         p->setPolygon(QPolygonF(pa0));
501         p->setZValue(z);
502         break;
503     case PolyParabel:
504         parabel(pa1, p1x, p1y, p2x + tp.x(), p2y + tp.y());
505         parabel(pa2, p1x, p1y, p2x - tp.x(), p2y - tp.y());
506         pa0.clear();
507         for (int i = 0; i <= arcsegs; i++)
508             pa0 << QPointF(pa1.at(i));
509         for (int i = 0; i <= arcsegs; i++)
510             pa0 << QPointF(pa2.at(arcsegs - i));
511         p->setPolygon(QPolygonF(pa0));
512         p->setZValue(z);
513         break;
514     default:
515         break;
516     }
517 }
518
519 QPointF LinkableMapObj::getChildRefPos() { return childRefPos; }
520
521 QPointF LinkableMapObj::getFloatRefPos() { return floatRefPos; }
522
523 QPointF LinkableMapObj::getParPos() { return parPos; }
524
525 LinkableMapObj::Orientation LinkableMapObj::getOrientation()
526 {
527     return orientation;
528 }
529
530 void LinkableMapObj::reposition() // virtual
531 {
532 }
533
534 void LinkableMapObj::requestReposition()
535 {
536     if (!repositionRequest) {
537         // Pass on the request to parental objects, if this hasn't
538         // been done yet
539         repositionRequest = true;
540         if (parObj)
541             parObj->requestReposition();
542     }
543 }
544
545 void LinkableMapObj::forceReposition()
546 {
547     // Sometimes a reposition has to be done immediately: For example
548     // if the note editor flag changes, there is no user event in mapeditor
549     // which could collect requests for a reposition.
550     // Then we have to call forceReposition()
551     // But no rule without exception: While loading a map or undoing it,
552     // we want to block expensive repositioning, but just do it once at
553     // the end, thus check first:
554
555     VymModel *model = treeItem->getModel();
556     if (model->isRepositionBlocked())
557         return;
558
559     // Pass on the request to parent objects, if this hasn't been done yet
560     if (parObj)
561         parObj->forceReposition();
562     else
563         reposition();
564 }
565
566 bool LinkableMapObj::repositionRequested() { return repositionRequest; }
567
568 void LinkableMapObj::parabel(QPolygonF &ya, qreal p1x, qreal p1y, qreal p2x,
569                              qreal p2y)
570
571 {
572     qreal vx = p2x - p1x; // V=P2-P1
573     qreal vy = p2y - p1y;
574
575     qreal dx; // delta x during calculation of parabel
576
577     qreal pnx; // next point
578     qreal pny;
579     qreal m;
580
581     if (vx > -0.0001 && vx < 0.0001)
582         m = 0;
583     else
584         m = (vy / (vx * vx));
585     dx = vx / (arcsegs);
586     ya.clear();
587     ya << QPointF(p1x, p1y);
588     for (int i = 1; i <= arcsegs; i++) {
589         pnx = p1x + dx;
590         pny = m * (pnx - parPos.x()) * (pnx - parPos.x()) + parPos.y();
591         ya << QPointF(pnx, pny);
592         p1x = pnx;
593         p1y = pny;
594     }
595 }