this repo has no description
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2/*
3 * Main authors:
4 * Guido Tack <tack@gecode.org>
5 *
6 * Copyright:
7 * Guido Tack, 2006
8 *
9 * This file is part of Gecode, the generic constraint
10 * development environment:
11 * http://www.gecode.org
12 *
13 * Permission is hereby granted, free of charge, to any person obtaining
14 * a copy of this software and associated documentation files (the
15 * "Software"), to deal in the Software without restriction, including
16 * without limitation the rights to use, copy, modify, merge, publish,
17 * distribute, sublicense, and/or sell copies of the Software, and to
18 * permit persons to whom the Software is furnished to do so, subject to
19 * the following conditions:
20 *
21 * The above copyright notice and this permission notice shall be
22 * included in all copies or substantial portions of the Software.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 *
32 */
33
34#include <QtGui/QPainter>
35#include <QPrinter>
36#include <QPrintDialog>
37
38#include <stack>
39#include <fstream>
40
41#include <gecode/gist/treecanvas.hh>
42
43#include <gecode/gist/nodevisitor.hh>
44#include <gecode/gist/visualnode.hh>
45#include <gecode/gist/drawingcursor.hh>
46
47#include <gecode/search.hh>
48#include <gecode/search/support.hh>
49
50namespace Gecode { namespace Gist {
51
52 TreeCanvas::TreeCanvas(Space* rootSpace, bool bab,
53 QWidget* parent, const Options& opt)
54 : QWidget(parent)
55 , mutex(QMutex::Recursive)
56 , layoutMutex(QMutex::Recursive)
57 , finishedFlag(false)
58 , compareNodes(false), compareNodesBeforeFP(false)
59 , autoHideFailed(true), autoZoom(false)
60 , refresh(500), refreshPause(0), smoothScrollAndZoom(false)
61 , moveDuringSearch(false)
62 , zoomTimeLine(500)
63 , scrollTimeLine(1000), targetX(0), sourceX(0), targetY(0), sourceY(0)
64 , targetW(0), targetH(0), targetScale(0)
65 , layoutDoneTimerId(0) {
66 QMutexLocker locker(&mutex);
67 curBest = (bab ? new BestNode(nullptr) : nullptr);
68 if (rootSpace->status() == SS_FAILED) {
69 if (!opt.clone)
70 delete rootSpace;
71 rootSpace = nullptr;
72 } else {
73 rootSpace = Gecode::Search::snapshot(rootSpace,opt);
74 }
75 na = new Node::NodeAllocator(bab);
76 int rootIdx = na->allocate(rootSpace);
77 assert(rootIdx == 0); (void) rootIdx;
78 root = (*na)[0];
79 root->layout(*na);
80 root->setMarked(true);
81 currentNode = root;
82 pathHead = root;
83 scale = LayoutConfig::defScale / 100.0;
84
85 setAutoFillBackground(true);
86
87 connect(&searcher, SIGNAL(update(int,int,int)), this,
88 SLOT(layoutDone(int,int,int)));
89 connect(&searcher, SIGNAL(statusChanged(bool)), this,
90 SLOT(statusChanged(bool)));
91
92 connect(&searcher, SIGNAL(solution(const Space*)),
93 this, SIGNAL(solution(const Space*)),
94 Qt::BlockingQueuedConnection);
95 connect(this, SIGNAL(solution(const Space*)),
96 this, SLOT(inspectSolution(const Space*)));
97
98 connect(&searcher, SIGNAL(moveToNode(VisualNode*,bool)),
99 this, SLOT(setCurrentNode(VisualNode*,bool)),
100 Qt::BlockingQueuedConnection);
101
102 connect(&searcher, SIGNAL(searchFinished(void)), this, SIGNAL(searchFinished(void)));
103
104 connect(&scrollTimeLine, SIGNAL(frameChanged(int)),
105 this, SLOT(scroll(int)));
106 scrollTimeLine.setCurveShape(QTimeLine::EaseInOutCurve);
107
108 scaleBar = new QSlider(Qt::Vertical, this);
109 scaleBar->setObjectName("scaleBar");
110 scaleBar->setMinimum(LayoutConfig::minScale);
111 scaleBar->setMaximum(LayoutConfig::maxScale);
112 scaleBar->setValue(LayoutConfig::defScale);
113 connect(scaleBar, SIGNAL(valueChanged(int)),
114 this, SLOT(scaleTree(int)));
115 connect(this, SIGNAL(scaleChanged(int)), scaleBar, SLOT(setValue(int)));
116 connect(&searcher, SIGNAL(scaleChanged(int)),
117 scaleBar, SLOT(setValue(int)));
118
119 connect(&zoomTimeLine, SIGNAL(frameChanged(int)),
120 scaleBar, SLOT(setValue(int)));
121 zoomTimeLine.setCurveShape(QTimeLine::EaseInOutCurve);
122
123 qRegisterMetaType<Statistics>("Statistics");
124 update();
125 }
126
127 TreeCanvas::~TreeCanvas(void) {
128 if (root) {
129 DisposeCursor dc(root,*na);
130 PreorderNodeVisitor<DisposeCursor>(dc).run();
131 }
132 delete na;
133 }
134
135 void
136 TreeCanvas::addDoubleClickInspector(Inspector* i) {
137 doubleClickInspectors.append(QPair<Inspector*,bool>(i,false));
138 }
139
140 void
141 TreeCanvas::activateDoubleClickInspector(int i, bool active) {
142 assert(i < doubleClickInspectors.size());
143 doubleClickInspectors[i].second = active;
144 }
145
146 void
147 TreeCanvas::addSolutionInspector(Inspector* i) {
148 solutionInspectors.append(QPair<Inspector*,bool>(i,false));
149 }
150
151 void
152 TreeCanvas::activateSolutionInspector(int i, bool active) {
153 assert(i < solutionInspectors.size());
154 solutionInspectors[i].second = active;
155 }
156
157 void
158 TreeCanvas::addMoveInspector(Inspector* i) {
159 moveInspectors.append(QPair<Inspector*,bool>(i,false));
160 }
161
162 void
163 TreeCanvas::activateMoveInspector(int i, bool active) {
164 assert(i < moveInspectors.size());
165 moveInspectors[i].second = active;
166 }
167
168 void
169 TreeCanvas::addComparator(Comparator* c) {
170 comparators.append(QPair<Comparator*,bool>(c,false));
171 }
172
173 void
174 TreeCanvas::activateComparator(int i, bool active) {
175 assert(i < comparators.size());
176 comparators[i].second = active;
177 }
178
179 void
180 TreeCanvas::scaleTree(int scale0, int zoomx, int zoomy) {
181 QMutexLocker locker(&layoutMutex);
182
183 QSize viewport_size = size();
184 QAbstractScrollArea* sa =
185 static_cast<QAbstractScrollArea*>(parentWidget()->parentWidget());
186
187 if (zoomx==-1)
188 zoomx = viewport_size.width()/2;
189 if (zoomy==-1)
190 zoomy = viewport_size.height()/2;
191
192 int xoff = (sa->horizontalScrollBar()->value()+zoomx)/scale;
193 int yoff = (sa->verticalScrollBar()->value()+zoomy)/scale;
194
195 BoundingBox bb;
196 scale0 = std::min(std::max(scale0, LayoutConfig::minScale),
197 LayoutConfig::maxScale);
198 scale = (static_cast<double>(scale0)) / 100.0;
199 bb = root->getBoundingBox();
200 int w =
201 static_cast<int>((bb.right-bb.left+Layout::extent)*scale);
202 int h =
203 static_cast<int>(2*Layout::extent+
204 root->getShape()->depth()*Layout::dist_y*scale);
205
206 sa->horizontalScrollBar()->setRange(0,w-viewport_size.width());
207 sa->verticalScrollBar()->setRange(0,h-viewport_size.height());
208 sa->horizontalScrollBar()->setPageStep(viewport_size.width());
209 sa->verticalScrollBar()->setPageStep(viewport_size.height());
210 sa->horizontalScrollBar()->setSingleStep(Layout::extent);
211 sa->verticalScrollBar()->setSingleStep(Layout::extent);
212
213 xoff *= scale;
214 yoff *= scale;
215
216 sa->horizontalScrollBar()->setValue(xoff-zoomx);
217 sa->verticalScrollBar()->setValue(yoff-zoomy);
218
219 emit scaleChanged(scale0);
220 QWidget::update();
221 }
222
223 void
224 TreeCanvas::update(void) {
225 QMutexLocker locker(&mutex);
226 layoutMutex.lock();
227 if (root != nullptr) {
228 root->layout(*na);
229 BoundingBox bb = root->getBoundingBox();
230
231 int w = static_cast<int>((bb.right-bb.left+Layout::extent)*scale);
232 int h =
233 static_cast<int>(2*Layout::extent+
234 root->getShape()->depth()*Layout::dist_y*scale);
235 xtrans = -bb.left+(Layout::extent / 2);
236
237 QSize viewport_size = size();
238 QAbstractScrollArea* sa =
239 static_cast<QAbstractScrollArea*>(parentWidget()->parentWidget());
240 sa->horizontalScrollBar()->setRange(0,w-viewport_size.width());
241 sa->verticalScrollBar()->setRange(0,h-viewport_size.height());
242 sa->horizontalScrollBar()->setPageStep(viewport_size.width());
243 sa->verticalScrollBar()->setPageStep(viewport_size.height());
244 sa->horizontalScrollBar()->setSingleStep(Layout::extent);
245 sa->verticalScrollBar()->setSingleStep(Layout::extent);
246 }
247 if (autoZoom)
248 zoomToFit();
249 layoutMutex.unlock();
250 QWidget::update();
251 }
252
253 void
254 TreeCanvas::scroll(void) {
255 QWidget::update();
256 }
257
258 void
259 TreeCanvas::layoutDone(int w, int h, int scale0) {
260 targetW = w; targetH = h; targetScale = scale0;
261
262 QSize viewport_size = size();
263 QAbstractScrollArea* sa =
264 static_cast<QAbstractScrollArea*>(parentWidget()->parentWidget());
265 sa->horizontalScrollBar()->setRange(0,w-viewport_size.width());
266 sa->verticalScrollBar()->setRange(0,h-viewport_size.height());
267
268 if (layoutDoneTimerId == 0)
269 layoutDoneTimerId = startTimer(15);
270 }
271
272 void
273 TreeCanvas::statusChanged(bool finished) {
274 if (finished) {
275 update();
276 centerCurrentNode();
277 }
278 emit statusChanged(currentNode, stats, finished);
279 }
280
281 void
282 SearcherThread::search(VisualNode* n, bool all, TreeCanvas* ti) {
283 node = n;
284
285 depth = -1;
286 for (VisualNode* p = n; p != nullptr; p = p->getParent(*ti->na))
287 depth++;
288
289 a = all;
290 t = ti;
291 start();
292 }
293
294 void
295 SearcherThread::updateCanvas(void) {
296 t->layoutMutex.lock();
297 if (t->root == nullptr)
298 return;
299
300 if (t->autoHideFailed) {
301 t->root->hideFailed(*t->na,true);
302 }
303 for (VisualNode* n = t->currentNode; n != nullptr; n=n->getParent(*t->na)) {
304 if (n->isHidden()) {
305 t->currentNode->setMarked(false);
306 t->currentNode = n;
307 t->currentNode->setMarked(true);
308 break;
309 }
310 }
311
312 t->root->layout(*t->na);
313 BoundingBox bb = t->root->getBoundingBox();
314
315 int w = static_cast<int>((bb.right-bb.left+Layout::extent)*t->scale);
316 int h = static_cast<int>(2*Layout::extent+
317 t->root->getShape()->depth()
318 *Layout::dist_y*t->scale);
319 t->xtrans = -bb.left+(Layout::extent / 2);
320
321 int scale0 = static_cast<int>(t->scale*100);
322 if (t->autoZoom) {
323 QWidget* p = t->parentWidget();
324 if (p) {
325 double newXScale =
326 static_cast<double>(p->width()) / (bb.right - bb.left +
327 Layout::extent);
328 double newYScale =
329 static_cast<double>(p->height()) /
330 (t->root->getShape()->depth() * Layout::dist_y + 2*Layout::extent);
331
332 scale0 = static_cast<int>(std::min(newXScale, newYScale)*100);
333 if (scale0<LayoutConfig::minScale)
334 scale0 = LayoutConfig::minScale;
335 if (scale0>LayoutConfig::maxAutoZoomScale)
336 scale0 = LayoutConfig::maxAutoZoomScale;
337 double scale = (static_cast<double>(scale0)) / 100.0;
338
339 w = static_cast<int>((bb.right-bb.left+Layout::extent)*scale);
340 h = static_cast<int>(2*Layout::extent+
341 t->root->getShape()->depth()*Layout::dist_y*scale);
342 }
343 }
344
345 t->layoutMutex.unlock();
346 emit update(w,h,scale0);
347 }
348
349 /// A stack item for depth first search
350 class SearchItem {
351 public:
352 /// The node
353 VisualNode* n;
354 /// The currently explored child
355 int i;
356 /// The number of children
357 int noOfChildren;
358 /// Constructor
359 SearchItem(VisualNode* n0, int noOfChildren0)
360 : n(n0), i(-1), noOfChildren(noOfChildren0) {}
361 };
362
363 void
364 SearcherThread::run(void) {
365 {
366 if (!node->isOpen())
367 return;
368 t->mutex.lock();
369 emit statusChanged(false);
370
371 unsigned int kids =
372 node->getNumberOfChildNodes(*t->na, t->curBest, t->stats,
373 t->c_d, t->a_d);
374 if (kids == 0 || node->getStatus() == STOP) {
375 t->mutex.unlock();
376 updateCanvas();
377 emit statusChanged(true);
378 return;
379 }
380
381 std::stack<SearchItem> stck;
382 stck.push(SearchItem(node,kids));
383 t->stats.maxDepth =
384 std::max(static_cast<long unsigned int>(t->stats.maxDepth),
385 static_cast<long unsigned int>(depth+stck.size()));
386
387 VisualNode* sol = nullptr;
388 int nodeCount = 0;
389 t->stopSearchFlag = false;
390 while (!stck.empty() && !t->stopSearchFlag) {
391 if (t->refresh > 0 && nodeCount >= t->refresh) {
392 node->dirtyUp(*t->na);
393 updateCanvas();
394 emit statusChanged(false);
395 nodeCount = 0;
396 if (t->refreshPause > 0)
397 msleep(t->refreshPause);
398 }
399 SearchItem& si = stck.top();
400 si.i++;
401 if (si.i == si.noOfChildren) {
402 stck.pop();
403 } else {
404 VisualNode* n = si.n->getChild(*t->na,si.i);
405 if (n->isOpen()) {
406 if (n->getStatus() == UNDETERMINED)
407 nodeCount++;
408 kids = n->getNumberOfChildNodes(*t->na, t->curBest, t->stats,
409 t->c_d, t->a_d);
410 if (t->moveDuringSearch)
411 emit moveToNode(n,false);
412 if (kids == 0) {
413 if (n->getStatus() == SOLVED) {
414 assert(n->hasCopy());
415 emit solution(n->getWorkingSpace());
416 n->purge(*t->na);
417 sol = n;
418 if (!a)
419 break;
420 }
421 } else {
422 if ( n->getStatus() != STOP )
423 stck.push(SearchItem(n,kids));
424 else if (!a)
425 break;
426 t->stats.maxDepth =
427 std::max(static_cast<long unsigned int>(t->stats.maxDepth),
428 static_cast<long unsigned int>(depth+stck.size()));
429 }
430 }
431 }
432 }
433 node->dirtyUp(*t->na);
434 t->stopSearchFlag = false;
435 t->mutex.unlock();
436 if (sol != nullptr) {
437 t->setCurrentNode(sol,true,false);
438 } else {
439 t->setCurrentNode(node,true,false);
440 }
441 }
442 updateCanvas();
443 emit statusChanged(true);
444 if (t->finishedFlag)
445 emit searchFinished();
446 }
447
448 void
449 TreeCanvas::searchAll(void) {
450 QMutexLocker locker(&mutex);
451 searcher.search(currentNode, true, this);
452 }
453
454 void
455 TreeCanvas::searchOne(void) {
456 QMutexLocker locker(&mutex);
457 searcher.search(currentNode, false, this);
458 }
459
460 void
461 TreeCanvas::toggleHidden(void) {
462 QMutexLocker locker(&mutex);
463 currentNode->toggleHidden(*na);
464 update();
465 centerCurrentNode();
466 emit statusChanged(currentNode, stats, true);
467 }
468
469 void
470 TreeCanvas::hideFailed(void) {
471 QMutexLocker locker(&mutex);
472 currentNode->hideFailed(*na);
473 update();
474 centerCurrentNode();
475 emit statusChanged(currentNode, stats, true);
476 }
477
478 void
479 TreeCanvas::unhideAll(void) {
480 QMutexLocker locker(&mutex);
481 QMutexLocker layoutLocker(&layoutMutex);
482 currentNode->unhideAll(*na);
483 update();
484 centerCurrentNode();
485 emit statusChanged(currentNode, stats, true);
486 }
487
488 void
489 TreeCanvas::toggleStop(void) {
490 QMutexLocker locker(&mutex);
491 currentNode->toggleStop(*na);
492 update();
493 centerCurrentNode();
494 emit statusChanged(currentNode, stats, true);
495 }
496
497 void
498 TreeCanvas::unstopAll(void) {
499 QMutexLocker locker(&mutex);
500 QMutexLocker layoutLocker(&layoutMutex);
501 currentNode->unstopAll(*na);
502 update();
503 centerCurrentNode();
504 emit statusChanged(currentNode, stats, true);
505 }
506
507 void
508 TreeCanvas::timerEvent(QTimerEvent* e) {
509 if (e->timerId() == layoutDoneTimerId) {
510 if (!smoothScrollAndZoom) {
511 scaleTree(targetScale);
512 } else {
513 zoomTimeLine.stop();
514 int zoomCurrent = static_cast<int>(scale*100);
515 int targetZoom = targetScale;
516 targetZoom = std::min(std::max(targetZoom, LayoutConfig::minScale),
517 LayoutConfig::maxAutoZoomScale);
518 zoomTimeLine.setFrameRange(zoomCurrent,targetZoom);
519 zoomTimeLine.start();
520 }
521 QWidget::update();
522 killTimer(layoutDoneTimerId);
523 layoutDoneTimerId = 0;
524 }
525 }
526
527 void
528 TreeCanvas::zoomToFit(void) {
529 QMutexLocker locker(&layoutMutex);
530 if (root != nullptr) {
531 BoundingBox bb;
532 bb = root->getBoundingBox();
533 QWidget* p = parentWidget();
534 if (p) {
535 double newXScale =
536 static_cast<double>(p->width()) / (bb.right - bb.left +
537 Layout::extent);
538 double newYScale =
539 static_cast<double>(p->height()) / (root->getShape()->depth() *
540 Layout::dist_y +
541 2*Layout::extent);
542 int scale0 = static_cast<int>(std::min(newXScale, newYScale)*100);
543 if (scale0<LayoutConfig::minScale)
544 scale0 = LayoutConfig::minScale;
545 if (scale0>LayoutConfig::maxAutoZoomScale)
546 scale0 = LayoutConfig::maxAutoZoomScale;
547
548 if (!smoothScrollAndZoom) {
549 scaleTree(scale0);
550 } else {
551 zoomTimeLine.stop();
552 int zoomCurrent = static_cast<int>(scale*100);
553 int targetZoom = scale0;
554 targetZoom = std::min(std::max(targetZoom, LayoutConfig::minScale),
555 LayoutConfig::maxAutoZoomScale);
556 zoomTimeLine.setFrameRange(zoomCurrent,targetZoom);
557 zoomTimeLine.start();
558 }
559 }
560 }
561 }
562
563 void
564 TreeCanvas::centerCurrentNode(void) {
565 QMutexLocker locker(&mutex);
566 int x=0;
567 int y=0;
568
569 VisualNode* c = currentNode;
570 while (c != nullptr) {
571 x += c->getOffset();
572 y += Layout::dist_y;
573 c = c->getParent(*na);
574 }
575
576 x = static_cast<int>((xtrans+x)*scale); y = static_cast<int>(y*scale);
577
578 QAbstractScrollArea* sa =
579 static_cast<QAbstractScrollArea*>(parentWidget()->parentWidget());
580
581 x -= sa->viewport()->width() / 2;
582 y -= sa->viewport()->height() / 2;
583
584 sourceX = sa->horizontalScrollBar()->value();
585 targetX = std::max(sa->horizontalScrollBar()->minimum(), x);
586 targetX = std::min(sa->horizontalScrollBar()->maximum(),
587 targetX);
588 sourceY = sa->verticalScrollBar()->value();
589 targetY = std::max(sa->verticalScrollBar()->minimum(), y);
590 targetY = std::min(sa->verticalScrollBar()->maximum(),
591 targetY);
592 if (!smoothScrollAndZoom) {
593 sa->horizontalScrollBar()->setValue(targetX);
594 sa->verticalScrollBar()->setValue(targetY);
595 } else {
596 scrollTimeLine.stop();
597 scrollTimeLine.setFrameRange(0,100);
598 scrollTimeLine.setDuration(std::max(200,
599 std::min(1000,
600 std::min(std::abs(sourceX-targetX),
601 std::abs(sourceY-targetY)))));
602 scrollTimeLine.start();
603 }
604 }
605
606 void
607 TreeCanvas::scroll(int i) {
608 QAbstractScrollArea* sa =
609 static_cast<QAbstractScrollArea*>(parentWidget()->parentWidget());
610 double p = static_cast<double>(i)/100.0;
611 double xdiff = static_cast<double>(targetX-sourceX)*p;
612 double ydiff = static_cast<double>(targetY-sourceY)*p;
613 sa->horizontalScrollBar()->setValue(sourceX+static_cast<int>(xdiff));
614 sa->verticalScrollBar()->setValue(sourceY+static_cast<int>(ydiff));
615 }
616
617 void
618 TreeCanvas::inspectCurrentNode(bool fix, int inspectorNo) {
619 QMutexLocker locker(&mutex);
620
621 if (currentNode->isHidden()) {
622 toggleHidden();
623 return;
624 }
625
626 int failedInspectorType = -1;
627 int failedInspector = -1;
628 bool needCentering = false;
629 try {
630 switch (currentNode->getStatus()) {
631 case UNDETERMINED:
632 {
633 unsigned int kids =
634 currentNode->getNumberOfChildNodes(*na,curBest,stats,c_d,a_d);
635 int depth = -1;
636 for (VisualNode* p = currentNode; p != nullptr; p=p->getParent(*na))
637 depth++;
638 if (kids > 0) {
639 needCentering = true;
640 depth++;
641 }
642 stats.maxDepth =
643 std::max(stats.maxDepth, depth);
644 if (currentNode->getStatus() == SOLVED) {
645 assert(currentNode->hasCopy());
646 emit solution(currentNode->getWorkingSpace());
647 }
648 emit statusChanged(currentNode,stats,true);
649 for (int i=0; i<moveInspectors.size(); i++) {
650 if (moveInspectors[i].second) {
651 failedInspectorType = 0;
652 failedInspector = i;
653 if (currentNode->getStatus() == FAILED) {
654 if (!currentNode->isRoot()) {
655 Space* curSpace =
656 currentNode->getSpace(*na,curBest,c_d,a_d);
657 moveInspectors[i].first->inspect(*curSpace);
658 delete curSpace;
659 }
660 } else {
661 moveInspectors[i].first->
662 inspect(*currentNode->getWorkingSpace());
663 }
664 failedInspectorType = -1;
665 }
666 }
667 if (currentNode->getStatus() == SOLVED) {
668 currentNode->purge(*na);
669 }
670 }
671 break;
672 case FAILED:
673 case STOP:
674 case UNSTOP:
675 case BRANCH:
676 case SOLVED:
677 {
678 Space* curSpace;
679
680 if (fix) {
681 if (currentNode->isRoot() && currentNode->getStatus() == FAILED)
682 break;
683 curSpace = currentNode->getSpace(*na,curBest,c_d,a_d);
684 if (currentNode->getStatus() == SOLVED &&
685 curSpace->status() != SS_SOLVED) {
686 // in the presence of weakly monotonic propagators, we may have
687 // to use search to find the solution here
688 assert(curSpace->status() == SS_BRANCH &&
689 "Something went wrong - probably an incorrect brancher");
690 Space* dfsSpace = Gecode::dfs(curSpace);
691 delete curSpace;
692 curSpace = dfsSpace;
693 }
694 } else {
695 if (currentNode->isRoot())
696 break;
697 VisualNode* p = currentNode->getParent(*na);
698 curSpace = p->getSpace(*na,curBest,c_d,a_d);
699 switch (curSpace->status()) {
700 case SS_SOLVED:
701 case SS_FAILED:
702 break;
703 case SS_BRANCH:
704 curSpace->commit(*p->getChoice(),
705 currentNode->getAlternative(*na));
706 break;
707 default:
708 GECODE_NEVER;
709 }
710 }
711
712 if (inspectorNo==-1) {
713 for (int i=0; i<doubleClickInspectors.size(); i++) {
714 if (doubleClickInspectors[i].second) {
715 failedInspectorType = 1;
716 failedInspector = i;
717 doubleClickInspectors[i].first->inspect(*curSpace);
718 failedInspectorType = -1;
719 }
720 }
721 } else {
722 failedInspectorType = 1;
723 failedInspector = inspectorNo;
724 doubleClickInspectors[inspectorNo].first->inspect(*curSpace);
725 failedInspectorType = -1;
726 }
727 delete curSpace;
728 }
729 break;
730 }
731 } catch (Exception& e) {
732 switch (failedInspectorType) {
733 case 0:
734 qFatal("Exception in move inspector %d: %s.\n Stopping.",
735 failedInspector, e.what());
736 break;
737 case 1:
738 qFatal("Exception in double click inspector %d: %s.\n Stopping.",
739 failedInspector, e.what());
740 break;
741 default:
742 qFatal("Exception: %s.\n Stopping.", e.what());
743 break;
744 }
745 }
746
747 currentNode->dirtyUp(*na);
748 update();
749 if (needCentering)
750 centerCurrentNode();
751 }
752
753 void
754 TreeCanvas::inspectBeforeFP(void) {
755 inspectCurrentNode(false);
756 }
757
758 void
759 TreeCanvas::labelBranches(void) {
760 QMutexLocker locker(&mutex);
761 currentNode->labelBranches(*na,curBest,c_d,a_d);
762 update();
763 centerCurrentNode();
764 emit statusChanged(currentNode, stats, true);
765 }
766 void
767 TreeCanvas::labelPath(void) {
768 QMutexLocker locker(&mutex);
769 currentNode->labelPath(*na,curBest,c_d,a_d);
770 update();
771 centerCurrentNode();
772 emit statusChanged(currentNode, stats, true);
773 }
774
775 void
776 TreeCanvas::inspectSolution(const Space* s) {
777 int failedInspectorType = -1;
778 int failedInspector = -1;
779 try {
780 Space* c = nullptr;
781 for (int i=0; i<solutionInspectors.size(); i++) {
782 if (solutionInspectors[i].second) {
783 if (c == nullptr)
784 c = s->clone();
785 failedInspectorType = 1;
786 failedInspector = i;
787 solutionInspectors[i].first->inspect(*c);
788 failedInspectorType = -1;
789 }
790 }
791 delete c;
792 } catch (Exception& e) {
793 switch (failedInspectorType) {
794 case 0:
795 qFatal("Exception in move inspector %d: %s.\n Stopping.",
796 failedInspector, e.what());
797 break;
798 case 1:
799 qFatal("Exception in solution inspector %d: %s.\n Stopping.",
800 failedInspector, e.what());
801 break;
802 default:
803 qFatal("Exception: %s.\n Stopping.", e.what());
804 break;
805 }
806 }
807 }
808
809 void
810 TreeCanvas::stopSearch(void) {
811 stopSearchFlag = true;
812 layoutDoneTimerId = startTimer(15);
813 }
814
815 void
816 TreeCanvas::reset(void) {
817 QMutexLocker locker(&mutex);
818 Space* rootSpace =
819 root->getStatus() == FAILED ? nullptr :
820 root->getSpace(*na,curBest,c_d,a_d);
821 if (curBest != nullptr) {
822 delete curBest;
823 curBest = new BestNode(nullptr);
824 }
825 if (root) {
826 DisposeCursor dc(root,*na);
827 PreorderNodeVisitor<DisposeCursor>(dc).run();
828 }
829 delete na;
830 na = new Node::NodeAllocator(curBest != nullptr);
831 int rootIdx = na->allocate(rootSpace);
832 assert(rootIdx == 0); (void) rootIdx;
833 root = (*na)[0];
834 root->setMarked(true);
835 currentNode = root;
836 pathHead = root;
837 scale = 1.0;
838 stats = Statistics();
839 for (int i=bookmarks.size(); i--;)
840 emit removedBookmark(i);
841 bookmarks.clear();
842 root->layout(*na);
843
844 emit statusChanged(currentNode, stats, true);
845 update();
846 }
847
848 void
849 TreeCanvas::bookmarkNode(void) {
850 QMutexLocker locker(&mutex);
851 if (!currentNode->isBookmarked()) {
852 bool ok;
853 QString text =
854 QInputDialog::getText(this, "Add bookmark", "Name:",
855 QLineEdit::Normal,"",&ok);
856 if (ok) {
857 currentNode->setBookmarked(true);
858 bookmarks.append(currentNode);
859 if (text == "")
860 text = QString("Node ")+QString().setNum(bookmarks.size());
861 emit addedBookmark(text);
862 }
863 } else {
864 currentNode->setBookmarked(false);
865 int idx = bookmarks.indexOf(currentNode);
866 bookmarks.remove(idx);
867 emit removedBookmark(idx);
868 }
869 currentNode->dirtyUp(*na);
870 update();
871 }
872
873 void
874 TreeCanvas::setPath(void) {
875 QMutexLocker locker(&mutex);
876 if(currentNode == pathHead)
877 return;
878
879 pathHead->unPathUp(*na);
880 pathHead = currentNode;
881
882 currentNode->pathUp(*na);
883 currentNode->dirtyUp(*na);
884 update();
885 }
886
887 void
888 TreeCanvas::inspectPath(void) {
889 QMutexLocker locker(&mutex);
890 setCurrentNode(root);
891 if (currentNode->isOnPath()) {
892 inspectCurrentNode();
893 int nextAlt = currentNode->getPathAlternative(*na);
894 while (nextAlt >= 0) {
895 setCurrentNode(currentNode->getChild(*na,nextAlt));
896 inspectCurrentNode();
897 nextAlt = currentNode->getPathAlternative(*na);
898 }
899 }
900 update();
901 }
902
903 void
904 TreeCanvas::startCompareNodes(void) {
905 QMutexLocker locker(&mutex);
906 compareNodes = true;
907 compareNodesBeforeFP = false;
908 setCursor(QCursor(Qt::CrossCursor));
909 }
910
911 void
912 TreeCanvas::startCompareNodesBeforeFP(void) {
913 QMutexLocker locker(&mutex);
914 compareNodes = true;
915 compareNodesBeforeFP = true;
916 setCursor(QCursor(Qt::CrossCursor));
917 }
918
919 void
920 TreeCanvas::emitStatusChanged(void) {
921 emit statusChanged(currentNode, stats, true);
922 }
923
924 void
925 TreeCanvas::navUp(void) {
926 QMutexLocker locker(&mutex);
927
928 VisualNode* p = currentNode->getParent(*na);
929
930 setCurrentNode(p);
931
932 if (p != nullptr) {
933 centerCurrentNode();
934 }
935 }
936
937 void
938 TreeCanvas::navDown(void) {
939 QMutexLocker locker(&mutex);
940 if (!currentNode->isHidden()) {
941 switch (currentNode->getStatus()) {
942 case STOP:
943 case UNSTOP:
944 case BRANCH:
945 {
946 int alt = std::max(0, currentNode->getPathAlternative(*na));
947 VisualNode* n = currentNode->getChild(*na,alt);
948 setCurrentNode(n);
949 centerCurrentNode();
950 break;
951 }
952 case SOLVED:
953 case FAILED:
954 case UNDETERMINED:
955 break;
956 }
957 }
958 }
959
960 void
961 TreeCanvas::navLeft(void) {
962 QMutexLocker locker(&mutex);
963 VisualNode* p = currentNode->getParent(*na);
964 if (p != nullptr) {
965 int alt = currentNode->getAlternative(*na);
966 if (alt > 0) {
967 VisualNode* n = p->getChild(*na,alt-1);
968 setCurrentNode(n);
969 centerCurrentNode();
970 }
971 }
972 }
973
974 void
975 TreeCanvas::navRight(void) {
976 QMutexLocker locker(&mutex);
977 VisualNode* p = currentNode->getParent(*na);
978 if (p != nullptr) {
979 unsigned int alt = currentNode->getAlternative(*na);
980 if (alt + 1 < p->getNumberOfChildren()) {
981 VisualNode* n = p->getChild(*na,alt+1);
982 setCurrentNode(n);
983 centerCurrentNode();
984 }
985 }
986 }
987
988 void
989 TreeCanvas::navRoot(void) {
990 QMutexLocker locker(&mutex);
991 setCurrentNode(root);
992 centerCurrentNode();
993 }
994
995 void
996 TreeCanvas::navNextSol(bool back) {
997 QMutexLocker locker(&mutex);
998 NextSolCursor nsc(currentNode,back,*na);
999 PreorderNodeVisitor<NextSolCursor> nsv(nsc);
1000 nsv.run();
1001 VisualNode* n = nsv.getCursor().node();
1002 if (n != root) {
1003 setCurrentNode(n);
1004 centerCurrentNode();
1005 }
1006 }
1007
1008 void
1009 TreeCanvas::navPrevSol(void) {
1010 navNextSol(true);
1011 }
1012
1013 void
1014 TreeCanvas::exportNodePDF(VisualNode* n) {
1015#if QT_VERSION >= 0x040400
1016 QString filename = QFileDialog::getSaveFileName(this, tr("Export tree as pdf"), "", tr("PDF (*.pdf)"));
1017 if (filename != "") {
1018 QPrinter printer(QPrinter::ScreenResolution);
1019 QMutexLocker locker(&mutex);
1020
1021 BoundingBox bb = n->getBoundingBox();
1022 printer.setFullPage(true);
1023 printer.setPaperSize(QSizeF(bb.right-bb.left+Layout::extent,
1024 n->getShape()->depth() * Layout::dist_y +
1025 Layout::extent), QPrinter::Point);
1026 printer.setOutputFileName(filename);
1027 QPainter painter(&printer);
1028
1029 painter.setRenderHint(QPainter::Antialiasing);
1030
1031 QRect pageRect = printer.pageRect();
1032 double newXScale =
1033 static_cast<double>(pageRect.width()) / (bb.right - bb.left +
1034 Layout::extent);
1035 double newYScale =
1036 static_cast<double>(pageRect.height()) /
1037 (n->getShape()->depth() * Layout::dist_y +
1038 Layout::extent);
1039 double printScale = std::min(newXScale, newYScale);
1040 painter.scale(printScale,printScale);
1041
1042 int printxtrans = -bb.left+(Layout::extent / 2);
1043
1044 painter.translate(printxtrans, Layout::dist_y / 2);
1045 QRect clip(0,0,0,0);
1046 DrawingCursor dc(n, *na, curBest, painter, clip, showCopies);
1047 currentNode->setMarked(false);
1048 PreorderNodeVisitor<DrawingCursor>(dc).run();
1049 currentNode->setMarked(true);
1050 }
1051#else
1052 (void) n;
1053#endif
1054 }
1055
1056 void
1057 TreeCanvas::exportWholeTreePDF(void) {
1058#if QT_VERSION >= 0x040400
1059 exportNodePDF(root);
1060#endif
1061 }
1062
1063 void
1064 TreeCanvas::exportPDF(void) {
1065#if QT_VERSION >= 0x040400
1066 exportNodePDF(currentNode);
1067#endif
1068 }
1069
1070 void
1071 TreeCanvas::print(void) {
1072 QPrinter printer;
1073 if (QPrintDialog(&printer, this).exec() == QDialog::Accepted) {
1074 QMutexLocker locker(&mutex);
1075
1076 BoundingBox bb = root->getBoundingBox();
1077 QRect pageRect = printer.pageRect();
1078 double newXScale =
1079 static_cast<double>(pageRect.width()) / (bb.right - bb.left +
1080 Layout::extent);
1081 double newYScale =
1082 static_cast<double>(pageRect.height()) /
1083 (root->getShape()->depth() * Layout::dist_y +
1084 2*Layout::extent);
1085 double printScale = std::min(newXScale, newYScale)*100;
1086 if (printScale<1.0)
1087 printScale = 1.0;
1088 if (printScale > 400.0)
1089 printScale = 400.0;
1090 printScale = printScale / 100.0;
1091
1092 QPainter painter(&printer);
1093 painter.setRenderHint(QPainter::Antialiasing);
1094 painter.scale(printScale,printScale);
1095 painter.translate(xtrans, 0);
1096 QRect clip(0,0,0,0);
1097 DrawingCursor dc(root, *na, curBest, painter, clip, showCopies);
1098 PreorderNodeVisitor<DrawingCursor>(dc).run();
1099 }
1100 }
1101
1102 VisualNode*
1103 TreeCanvas::eventNode(QEvent* event) {
1104 int x = 0;
1105 int y = 0;
1106 switch (event->type()) {
1107 case QEvent::ToolTip:
1108 {
1109 QHelpEvent* he = static_cast<QHelpEvent*>(event);
1110 x = he->x();
1111 y = he->y();
1112 break;
1113 }
1114 case QEvent::MouseButtonDblClick:
1115 case QEvent::MouseButtonPress:
1116 case QEvent::MouseButtonRelease:
1117 case QEvent::MouseMove:
1118 {
1119 QMouseEvent* me = static_cast<QMouseEvent*>(event);
1120 x = me->x();
1121 y = me->y();
1122 break;
1123 }
1124 case QEvent::ContextMenu:
1125 {
1126 QContextMenuEvent* ce = static_cast<QContextMenuEvent*>(event);
1127 x = ce->x();
1128 y = ce->y();
1129 break;
1130 }
1131 default:
1132 return nullptr;
1133 }
1134 QAbstractScrollArea* sa =
1135 static_cast<QAbstractScrollArea*>(parentWidget()->parentWidget());
1136 int xoff = sa->horizontalScrollBar()->value()/scale;
1137 int yoff = sa->verticalScrollBar()->value()/scale;
1138
1139 BoundingBox bb = root->getBoundingBox();
1140 int w =
1141 static_cast<int>((bb.right-bb.left+Layout::extent)*scale);
1142 if (w < sa->viewport()->width())
1143 xoff -= (sa->viewport()->width()-w)/2;
1144
1145 VisualNode* n;
1146 n = root->findNode(*na,
1147 static_cast<int>(x/scale-xtrans+xoff),
1148 static_cast<int>((y-30)/scale+yoff));
1149 return n;
1150 }
1151
1152 bool
1153 TreeCanvas::event(QEvent* event) {
1154 if (mutex.tryLock()) {
1155 if (event->type() == QEvent::ToolTip) {
1156 VisualNode* n = eventNode(event);
1157 if (n != nullptr) {
1158 QHelpEvent* he = static_cast<QHelpEvent*>(event);
1159 QToolTip::showText(he->globalPos(),
1160 QString(n->toolTip(*na,curBest,
1161 c_d,a_d).c_str()));
1162 } else {
1163 QToolTip::hideText();
1164 }
1165 }
1166 mutex.unlock();
1167 }
1168 return QWidget::event(event);
1169 }
1170
1171 void
1172 TreeCanvas::resizeToOuter(void) {
1173 if (autoZoom)
1174 zoomToFit();
1175 }
1176
1177 void
1178 TreeCanvas::paintEvent(QPaintEvent* event) {
1179 QMutexLocker locker(&layoutMutex);
1180 QPainter painter(this);
1181 painter.setRenderHint(QPainter::Antialiasing);
1182
1183 QAbstractScrollArea* sa =
1184 static_cast<QAbstractScrollArea*>(parentWidget()->parentWidget());
1185 int xoff = sa->horizontalScrollBar()->value()/scale;
1186 int yoff = sa->verticalScrollBar()->value()/scale;
1187
1188 BoundingBox bb = root->getBoundingBox();
1189 int w =
1190 static_cast<int>((bb.right-bb.left+Layout::extent)*scale);
1191 if (w < sa->viewport()->width())
1192 xoff -= (sa->viewport()->width()-w)/2;
1193
1194 QRect origClip = event->rect();
1195 painter.translate(0, 30);
1196 painter.scale(scale,scale);
1197 painter.translate(xtrans-xoff, -yoff);
1198 QRect clip(static_cast<int>(origClip.x()/scale-xtrans+xoff),
1199 static_cast<int>(origClip.y()/scale+yoff),
1200 static_cast<int>(origClip.width()/scale),
1201 static_cast<int>(origClip.height()/scale));
1202 DrawingCursor dc(root, *na, curBest, painter, clip, showCopies);
1203 PreorderNodeVisitor<DrawingCursor>(dc).run();
1204
1205 // int nodesLayouted = 1;
1206 // clock_t t0 = clock();
1207 // while (v.next()) { nodesLayouted++; }
1208 // double t = (static_cast<double>(clock()-t0) / CLOCKS_PER_SEC) * 1000.0;
1209 // double nps = static_cast<double>(nodesLayouted) /
1210 // (static_cast<double>(clock()-t0) / CLOCKS_PER_SEC);
1211 // std::cout << "Drawing done. " << nodesLayouted << " nodes in "
1212 // << t << " ms. " << nps << " nodes/s." << std::endl;
1213
1214 }
1215
1216 void
1217 TreeCanvas::mouseDoubleClickEvent(QMouseEvent* event) {
1218 if (mutex.tryLock()) {
1219 if(event->button() == Qt::LeftButton) {
1220 VisualNode* n = eventNode(event);
1221 if(n == currentNode) {
1222 inspectCurrentNode();
1223 event->accept();
1224 mutex.unlock();
1225 return;
1226 }
1227 }
1228 mutex.unlock();
1229 }
1230 event->ignore();
1231 }
1232
1233 void
1234 TreeCanvas::contextMenuEvent(QContextMenuEvent* event) {
1235 if (mutex.tryLock()) {
1236 VisualNode* n = eventNode(event);
1237 if (n != nullptr) {
1238 setCurrentNode(n);
1239 emit contextMenu(event);
1240 event->accept();
1241 mutex.unlock();
1242 return;
1243 }
1244 mutex.unlock();
1245 }
1246 event->ignore();
1247 }
1248
1249 void
1250 TreeCanvas::resizeEvent(QResizeEvent* e) {
1251 QAbstractScrollArea* sa =
1252 static_cast<QAbstractScrollArea*>(parentWidget()->parentWidget());
1253
1254 int w = sa->horizontalScrollBar()->maximum()+e->oldSize().width();
1255 int h = sa->verticalScrollBar()->maximum()+e->oldSize().height();
1256
1257 sa->horizontalScrollBar()->setRange(0,w-e->size().width());
1258 sa->verticalScrollBar()->setRange(0,h-e->size().height());
1259 sa->horizontalScrollBar()->setPageStep(e->size().width());
1260 sa->verticalScrollBar()->setPageStep(e->size().height());
1261 }
1262
1263 void
1264 TreeCanvas::wheelEvent(QWheelEvent* event) {
1265 if (event->modifiers() & Qt::ShiftModifier) {
1266 event->accept();
1267 if (event->orientation() == Qt::Vertical && !autoZoom)
1268 scaleTree(scale*100+ceil(static_cast<double>(event->delta())/4.0),
1269 event->x(), event->y());
1270 } else {
1271 event->ignore();
1272 }
1273 }
1274
1275 bool
1276 TreeCanvas::finish(void) {
1277 if (finishedFlag)
1278 return true;
1279 stopSearchFlag = true;
1280 finishedFlag = true;
1281 for (int i=0; i<doubleClickInspectors.size(); i++)
1282 doubleClickInspectors[i].first->finalize();
1283 for (int i=0; i<solutionInspectors.size(); i++)
1284 solutionInspectors[i].first->finalize();
1285 for (int i=0; i<moveInspectors.size(); i++)
1286 moveInspectors[i].first->finalize();
1287 for (int i=0; i<comparators.size(); i++)
1288 comparators[i].first->finalize();
1289 return !searcher.isRunning();
1290 }
1291
1292 void
1293 TreeCanvas::setCurrentNode(VisualNode* n, bool finished, bool update) {
1294 if (finished)
1295 mutex.lock();
1296 if (update && n != nullptr && n != currentNode &&
1297 n->getStatus() != UNDETERMINED && !n->isHidden()) {
1298 Space* curSpace = nullptr;
1299 for (int i=0; i<moveInspectors.size(); i++) {
1300 if (moveInspectors[i].second) {
1301 if (curSpace == nullptr)
1302 curSpace = n->getSpace(*na,curBest,c_d,a_d);
1303 try {
1304 moveInspectors[i].first->inspect(*curSpace);
1305 } catch (Exception& e) {
1306 qFatal("Exception in move inspector %d: %s.\n Stopping.",
1307 i, e.what());
1308 }
1309 }
1310 }
1311 }
1312 if (n != nullptr) {
1313 currentNode->setMarked(false);
1314 currentNode = n;
1315 currentNode->setMarked(true);
1316 emit statusChanged(currentNode,stats,finished);
1317 if (update) {
1318 compareNodes = false;
1319 setCursor(QCursor(Qt::ArrowCursor));
1320 QWidget::update();
1321 }
1322 }
1323 if (finished)
1324 mutex.unlock();
1325 }
1326
1327 void
1328 TreeCanvas::mousePressEvent(QMouseEvent* event) {
1329 if (mutex.tryLock()) {
1330 if (event->button() == Qt::LeftButton) {
1331 VisualNode* n = eventNode(event);
1332 if (compareNodes) {
1333 if (n != nullptr && n->getStatus() != UNDETERMINED &&
1334 currentNode != nullptr &&
1335 currentNode->getStatus() != UNDETERMINED) {
1336 Space* curSpace = nullptr;
1337 Space* compareSpace = nullptr;
1338 for (int i=0; i<comparators.size(); i++) {
1339 if (comparators[i].second) {
1340 if (curSpace == nullptr) {
1341 curSpace = currentNode->getSpace(*na,curBest,c_d,a_d);
1342
1343 if (!compareNodesBeforeFP || n->isRoot()) {
1344 compareSpace = n->getSpace(*na,curBest,c_d,a_d);
1345 } else {
1346 VisualNode* p = n->getParent(*na);
1347 compareSpace = p->getSpace(*na,curBest,c_d,a_d);
1348 switch (compareSpace->status()) {
1349 case SS_SOLVED:
1350 case SS_FAILED:
1351 break;
1352 case SS_BRANCH:
1353 compareSpace->commit(*p->getChoice(),
1354 n->getAlternative(*na));
1355 break;
1356 default:
1357 GECODE_NEVER;
1358 }
1359 }
1360 }
1361 try {
1362 comparators[i].first->compare(*curSpace,*compareSpace);
1363 } catch (Exception& e) {
1364 qFatal("Exception in comparator %d: %s.\n Stopping.",
1365 i, e.what());
1366 }
1367 }
1368 }
1369 }
1370 } else {
1371 setCurrentNode(n);
1372 }
1373 compareNodes = false;
1374 setCursor(QCursor(Qt::ArrowCursor));
1375 if (n != nullptr) {
1376 event->accept();
1377 mutex.unlock();
1378 return;
1379 }
1380 }
1381 mutex.unlock();
1382 }
1383 event->ignore();
1384 }
1385
1386 void
1387 TreeCanvas::setRecompDistances(int c_d0, int a_d0) {
1388 c_d = c_d0; a_d = a_d0;
1389 }
1390
1391 void
1392 TreeCanvas::setAutoHideFailed(bool b) {
1393 autoHideFailed = b;
1394 }
1395
1396 void
1397 TreeCanvas::setAutoZoom(bool b) {
1398 autoZoom = b;
1399 if (autoZoom) {
1400 zoomToFit();
1401 }
1402 emit autoZoomChanged(b);
1403 scaleBar->setEnabled(!b);
1404 }
1405
1406 void
1407 TreeCanvas::setShowCopies(bool b) {
1408 showCopies = b;
1409 }
1410 bool
1411 TreeCanvas::getShowCopies(void) {
1412 return showCopies;
1413 }
1414
1415 bool
1416 TreeCanvas::getAutoHideFailed(void) {
1417 return autoHideFailed;
1418 }
1419
1420 bool
1421 TreeCanvas::getAutoZoom(void) {
1422 return autoZoom;
1423 }
1424
1425 void
1426 TreeCanvas::setRefresh(int i) {
1427 refresh = i;
1428 }
1429
1430 void
1431 TreeCanvas::setRefreshPause(int i) {
1432 refreshPause = i;
1433 if (refreshPause > 0)
1434 refresh = 1;
1435 }
1436
1437 bool
1438 TreeCanvas::getSmoothScrollAndZoom(void) {
1439 return smoothScrollAndZoom;
1440 }
1441
1442 void
1443 TreeCanvas::setSmoothScrollAndZoom(bool b) {
1444 smoothScrollAndZoom = b;
1445 }
1446
1447 bool
1448 TreeCanvas::getMoveDuringSearch(void) {
1449 return moveDuringSearch;
1450 }
1451
1452 void
1453 TreeCanvas::setMoveDuringSearch(bool b) {
1454 moveDuringSearch = b;
1455 }
1456
1457}}
1458
1459// STATISTICS: gist-any