Add more assertions.
[tagua/yd.git] / src / movelist.cpp
blob13010d5cf5fba9ac701e55f3f92b1ccb74e43ee4
1 /*
2 Copyright (c) 2006 Paolo Capriotti <p.capriotti@gmail.com>
3 (c) 2006 Maurizio Monge <maurizio.monge@kdemail.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 */
10 #include <QApplication>
11 #include <QPainter>
12 #include <QPaintEvent>
13 #include <QMouseEvent>
14 #include <QTextEdit>
15 #include <QMenu>
16 #include <QHBoxLayout>
17 #include <QVBoxLayout>
18 #include <QScrollArea>
19 #include <QToolButton>
20 #include <QScrollBar>
21 #include <QTimer>
22 #include <cmath>
23 #include <KDebug>
24 #include <KStandardDirs>
26 #include "movelist_widget.h"
27 #include "movelist_table.h"
28 #include "movelist_notifier.h"
29 #include "movelist_p.h"
30 #include "mastersettings.h"
31 #include "pref_theme.h"
33 namespace MoveList {
35 #define MARGIN_LEFT 2
36 #define MARGIN_RIGHT 4
37 #define MARGIN_TOP 1
38 #define MARGIN_BOTTOM 0
39 #define MIDDLE_PAD 3
40 #define COMMENT_INDENTATION 4
41 #define VAR_INDENTATION 16
42 #define BORDER_LEFT 3
43 #define BORDER_RIGHT 3
44 #define BORDER_TOP 3
45 #define BORDER_BOTTOM 3
46 #define MIN_COL_WIDTH 5.0
48 #define DEFAULT_ANIMATION_TIME 350.0
50 //BEGIN FancyItem--------------------------------------------------------------
52 bool FancyItem::showing() {
53 return !(time_opacity!=-1 && target_opacity == 0) &&
54 ((time_opacity!=-1 && target_opacity == 255) || (visible() && opacity() == 255));
57 void FancyItem::appear() {
58 if((time_opacity!=-1 && target_opacity == 255)
59 || (visible() && opacity() == 255))
60 return;
62 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
63 if(!m->m_settings->anim_enabled || !m->m_settings->anim_hideshow)
64 show();
65 else {
66 old_opacity = 0;
67 target_opacity = 255;
68 time_opacity = m->layout_time;
69 setAnimated(true);
73 void FancyItem::disappear() {
74 if((time_opacity!=-1 && target_opacity == 0)
75 || !visible() || opacity() == 0)
76 return;
78 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
79 setHighlight(false);
80 if(!m->m_settings->anim_enabled || !m->m_settings->anim_hideshow)
81 hide();
82 else {
83 old_opacity = 255;
84 target_opacity = 0;
85 time_opacity = m->layout_time;
86 setAnimated(true);
90 void FancyItem::goTo(QPoint p) {
91 if((time_pos!=-1 && target_pos == p) || (time_pos==-1 && pos() == p))
92 return;
94 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
95 if(!m->m_settings->anim_enabled || !m->m_settings->anim_moving)
96 moveTo(p);
97 else {
98 old_pos = pos();
99 target_pos = p;
100 time_pos = m->layout_time;
101 setAnimated(true);
105 void FancyItem::setHighlight(bool h) {
106 if(highlighted == h)
107 return;
109 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
110 if(!m->m_settings->anim_enabled || !m->m_settings->anim_highlight) {
111 curr_highlight = h ? 255 : 0;
112 highlighted = h;
113 changed();
115 else {
116 old_highlight = highlighted ? 255 : 0;
117 target_highlight = h ? 255 : 0;
118 highlighted = h;
119 time_highlight = m->mSecs();
120 setAnimated(true);
124 void FancyItem::advance(int time) {
125 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
126 if(time_highlight != -1) {
127 float fact = (time - time_highlight) / m->m_settings->anim_time;
128 if(fact >= 1.0) {
129 curr_highlight = target_highlight;
130 time_highlight = -1;
132 else
133 curr_highlight = int(target_highlight*fact + old_highlight*(1-fact));
134 changed();
136 if(time_opacity != -1) {
137 float fact = (time - time_opacity) / m->m_settings->anim_time;
138 if(fact >= 1.0) {
139 setOpacity(target_opacity);
140 setVisible(target_opacity != 0);
141 time_opacity = -1;
143 else {
144 setOpacity( int(target_opacity*fact + old_opacity*(1-fact)) );
145 setVisible(true);
148 if(time_pos != -1) {
149 float fact = (time - time_pos) / m->m_settings->anim_time;
150 if(fact >= 1.0) {
151 moveTo(target_pos);
152 time_pos = -1;
154 else {
155 moveTo( int(target_pos.x()*fact + old_pos.x()*(1-fact)+0.5),
156 int(target_pos.y()*fact + old_pos.y()*(1-fact)+0.5));
159 if(canStop()) {
160 setAnimated(false);
164 bool FancyItem::layered() const {
165 return false;
168 //END FancyItem----------------------------------------------------------------
171 //BEGIN Brace------------------------------------------------------------------
173 void Brace::setHeight(int h) {
174 if(h<0) h=0;
175 if((animated() && target_height == h) || height == h)
176 return;
178 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
179 if(!m->m_settings->anim_enabled || !m->m_settings->anim_moving) {
180 height = h;
181 changed();
183 else {
184 old_height = height;
185 target_height = h;
186 time_height = m->layout_time;
187 if(visible())
188 setAnimated(true);
192 void Brace::advance(int time) {
193 if(time_height != -1) {
194 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
195 float fact = (time - time_height) / m->m_settings->anim_time;
196 if(fact >= 1.0) {
197 height = target_height;
198 time_height = -1;
200 else
201 height = int(target_height*fact + old_height*(1-fact) + 0.5);
202 changed();
204 FancyItem::advance(time);
207 void Brace::paint (QPainter *p) {
208 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
209 if(height < m->entry_size)
210 return;
211 QPointF p1((pos().x()*2+width)/2.0, (pos().y()*2+m->entry_size)/2.0);
212 QPointF p2((pos().x()*2+width)/2.0, (pos().y()*2+height*2-m->entry_size)/2.0);
213 p->save();
214 QPen q;
215 if(depth % 2) {
216 p->setBrush(QColor(255,192,224));
217 q = QColor(128,0,64);
219 else {
220 p->setBrush(QColor(192,255,224));
221 q = QColor(0,128,64);
223 q.setWidth(2);
224 p->setPen(q);
225 p->setRenderHint(QPainter::Antialiasing);
226 int s = std::min(m->entry_size, width);
227 float cs1 = (0.4 + 0.2*(curr_highlight/255.0)) * s;
228 float cs2 = 0.2 * s;
230 p->drawLine(p1,p2);
231 p->drawEllipse(QRectF(-cs1/2,-cs1/2,cs1,cs1).translated(p1));
232 p->setBrush(p->pen().color());
233 p->drawEllipse(QRectF(-cs2/2,-cs2/2,cs2,cs2).translated(p2));
234 p->restore();
237 QRect Brace::rect () const {
238 return QRect(pos(), QSize(width, height));
241 //END Brace--------------------------------------------------------------------
244 //BEGIN Text-------------------------------------------------------------------
246 void Text::paint (QPainter *p) {
247 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
248 if(curr_highlight != 0) {
249 p->setBrush(QColor(192,224,208, curr_highlight));
250 p->setPen(QColor(64,128,96, curr_highlight));
251 p->drawRect(rect().adjusted(0,0,-1,-1));
253 p->setFont(selected ? m->m_settings->sel_mv_font : m->m_settings->mv_font);
254 p->setPen(selected ? m->m_settings->select_color : Qt::black);
255 p->drawText(pos()+QPoint(MARGIN_LEFT, MARGIN_TOP+m->m_settings->mv_fmetrics.ascent()), text);
258 QRect Text::rect () const {
259 return QRect(pos(), QSize(width, height));
262 void Text::doUpdate () {
263 if(!needs_update)
264 return;
266 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
267 width = (selected ? m->m_settings->sel_mv_fmetrics
268 : m->m_settings->mv_fmetrics).boundingRect(text).right()
269 + MARGIN_LEFT + MARGIN_RIGHT;
270 height = m->entry_size;
272 needs_update = false;
273 changed();
276 //END Text---------------------------------------------------------------------
279 //BEGIN Comment----------------------------------------------------------------
281 void Comment::paint (QPainter *p) {
282 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
284 if(curr_highlight != 0) {
285 p->setBrush(QColor(255,255,255, curr_highlight));
286 p->setPen(QColor(192,192,192, curr_highlight));
287 p->drawRect(rect().adjusted(0,0,-1,-1));
289 p->setFont(m->m_settings->comm_font);
290 p->setPen(m->m_settings->comment_color);
291 p->drawText(pos().x() + MARGIN_RIGHT,
292 pos().y(),
293 width - MARGIN_LEFT - MARGIN_RIGHT, 9999,
294 Qt::AlignLeft|Qt::AlignTop|Qt::TextWordWrap, text);
297 QRect Comment::rect () const {
298 return QRect(pos(), QSize(width, height));
301 void Comment::doUpdate () {
302 if(!needs_update)
303 return;
305 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
306 QPoint dest = ((time_pos != -1) ? target_pos : pos());
307 width = std::max(m->width() - dest.x(), m->entry_size);
308 height = m->m_settings->comm_fmetrics.boundingRect(0,0,width - MARGIN_LEFT - MARGIN_RIGHT, 99999,
309 Qt::AlignLeft|Qt::AlignTop|Qt::TextWordWrap, text).height()
310 + MARGIN_TOP + MARGIN_BOTTOM;
312 needs_update = false;
313 changed();
316 //END Comment------------------------------------------------------------------
319 //BEGIN Entry------------------------------------------------------------------
321 void Entry::paint (QPainter *p) {
322 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
323 if(curr_highlight != 0) {
324 p->setBrush(QColor(192,224,255, curr_highlight));
325 p->setPen(QColor(64,96,128, curr_highlight));
326 p->drawRect(rect().adjusted(0,0,-1,-1));
328 p->setPen(selected ? m->m_settings->select_color : Qt::black);
329 int x = pos().x()+MARGIN_LEFT;
330 int y = pos().y()+MARGIN_TOP+m_ascent;
332 p->setRenderHint(QPainter::TextAntialiasing);
333 QFont tf = selected ? m->m_settings->sel_mv_font : m->m_settings->mv_font;
334 QFontMetrics& fm = selected ? m->m_settings->sel_mv_fmetrics : m->m_settings->mv_fmetrics;
335 QRect r(0,0,0,0);
337 for(int i=0;i<(int)move.size();i++) {
338 if(move[i].m_type == MovePart::Text) {
339 p->setFont(tf);
340 p->drawText(QPoint(x+r.width(), y), move[i].m_string);
341 QRect b = fm.boundingRect(move[i].m_string);
342 r |= b.translated(r.width()-b.x(), 0);
344 else if(move[i].m_type == MovePart::Figurine) {
345 ::Loader::Glyph g = m->m_loader.getValue< ::Loader::Glyph>(move[i].m_string);
346 QFont font = g.fontValid() ? g.font() : tf;
347 p->setFont(font);
348 p->drawText(QPoint(x+r.width(), y), g.str());
349 QFontMetrics fi(font);
350 QRect b = fi.boundingRect(g.str());
351 r |= b.translated(r.width()-b.x(), 0);
356 QRect Entry::rect () const {
357 return m_rect.translated(pos().x()+MARGIN_LEFT, pos().y()+MARGIN_TOP+m_ascent);
360 void Entry::doUpdate () {
361 if(!needs_update)
362 return;
364 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
365 QFont tf = selected ? m->m_settings->sel_mv_font : m->m_settings->mv_font;
366 QFontMetrics& fm = selected ? m->m_settings->sel_mv_fmetrics : m->m_settings->mv_fmetrics;
367 m_ascent = m->m_settings->mv_fmetrics.ascent();
368 m_rect = QRect(0,0,0,0);
370 for(int i=0;i<(int)move.size();i++) {
371 if(move[i].m_type == MovePart::Text) {
372 QRect b = fm.boundingRect(move[i].m_string);
373 m_rect |= b.translated(m_rect.width()-b.x(), 0);
375 else if(move[i].m_type == MovePart::Figurine) {
376 ::Loader::Glyph g = m->m_loader.getValue< ::Loader::Glyph>(move[i].m_string);
377 QFontMetrics fi(g.fontValid() ? g.font() : tf);
378 QRect b = fi.boundingRect(g.str());
379 m_rect |= b.translated(m_rect.width()-b.x(), 0);
382 m_rect = QRect(m_rect.x(),m_rect.y(),m_rect.width()+MARGIN_RIGHT,m_rect.height());
384 needs_update = false;
385 changed();
388 //END Entry--------------------------------------------------------------------
391 //BEGIN Settings---------------------------------------------------------------
393 void Settings::load() {
394 ::Settings s = settings().group("move-list");
395 ::Settings s_anim = s.group("animations");
397 anim_enabled = s.group("animations").flag("enabled", true);
398 anim_moving = s_anim.group("moving").flag("enabled", true);
399 anim_hideshow = s_anim.group("hideshow").flag("enabled", true);
400 anim_highlight = s_anim.group("highlight").flag("enabled", true);
401 anim_speed = s_anim["speed"] | 16;
402 anim_time = DEFAULT_ANIMATION_TIME*pow(5.0, 1.0 - anim_speed/16.0);
403 anim_smoothness = s_anim["smoothness"] | 16;
404 select_color = s["select-color"] | QColor(Qt::red);
405 comment_color = s["comment-color"] | QColor(64,64,64);
406 mv_font = QApplication::font();
407 if ((use_mv_font = s.group("moves-font").flag("enabled", true)))
408 mv_font = s["moves-font"] | mv_font;
409 sel_mv_font = mv_font;
410 sel_mv_font.setBold(true);
411 comm_font = QApplication::font();
412 comm_font.setItalic(true);
413 if ((use_comm_font = s.group("comment-font").flag("enabled", true)))
414 comm_font = s["CommentFont"] | comm_font;
415 mv_fmetrics = QFontMetrics(mv_font);
416 sel_mv_fmetrics = QFontMetrics(sel_mv_font);
417 comm_fmetrics = QFontMetrics(comm_font);
420 void Settings::save() {
421 ::Settings s = settings().group("move-list");
422 ::Settings s_anim = s.group("animations");
424 s.group("animations").setFlag("enabled", anim_enabled);
425 s_anim.group("moving").setFlag("enabled", anim_moving);
426 s_anim.group("hideshow").setFlag("enabled", anim_hideshow);
427 s_anim.group("highlight").setFlag("enabled", anim_highlight);
428 s_anim["speed"] = anim_speed;
429 s_anim["smoothness"] = anim_smoothness;
430 s["select-color"] = select_color;
431 s["comment-color"] = comment_color;
432 s.group("moves-font").flag("enabled", use_mv_font);
433 s["moves-font"] = mv_font;
434 s.group("comment-font").flag("enabled", use_comm_font);
435 s["comment-font"] = comm_font;
438 //END Settings-----------------------------------------------------------------
441 //BEGIN Widget-----------------------------------------------------------------
443 Widget::Widget(QWidget *parent, Table *o)
444 : KGameCanvasWidget(parent)
445 , curr_highlight(-1)
446 , curr_selected(-1)
447 , comment_editor(NULL)
448 , layout_pending(false)
449 , layout_style(0)
450 , layout_goto_selected(false)
451 , layout_width_changed(true)
452 , layout_must_relayout(true)
453 , notifier(NULL)
454 , owner_table(o)
455 , m_settings(new Settings) {
457 resize(50,100);
458 setSizePolicy ( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
460 setMouseTracking(true);
461 settingsChanged();
462 reset();
465 Widget::~Widget() {
466 delete m_settings;
469 void Widget::reset() {
470 curr_highlight = Index(-1);
471 curr_selected = Index(-1);
472 if(comment_editor) {
473 delete comment_editor;
474 comment_editor = NULL;
477 DecoratedMove mv;
478 mv.push_back(MovePart(QString("Mainline:")));
479 history.clear();
480 history.push_back( EntryPtr(new Entry(-1, mv, Index(0), this)) );
482 layout();
485 EntryPtr Widget::fetch(const Index& ix) {
486 int at;
487 History *vec = fetchRef(ix, &at);
488 return vec ? (*vec)[at] : EntryPtr();
491 History* Widget::fetchRef(const Index& ix, int* idx) {
492 if(ix.num_moves >= (int)history.size() || ix.num_moves < 0 )
493 return NULL;
495 History* aretv = &history;
496 EntryPtr retv = history[ix.num_moves];
497 if(idx) *idx = ix.num_moves;
499 for(int i=0; i<(int)ix.nested.size();i++) {
500 Variations::iterator it = retv->variations.find(ix.nested[i].variation);
501 if(it == retv->variations.end() || ix.nested[i].num_moves >= (int)it->second.size()
502 || ix.nested[i].num_moves < 0 )
503 return NULL;
505 aretv = &it->second;
506 retv = it->second[ix.nested[i].num_moves];
507 if(idx) *idx = ix.nested[i].num_moves;
509 return aretv;
512 Notifier* Widget::getNotifier() {
513 return notifier;
516 void Widget::setNotifier(Notifier* n, bool detach_prev){
517 if(detach_prev && notifier && notifier != n)
518 notifier->onDetachNotifier();
519 notifier = n;
522 void Widget::settingsChanged() {
523 m_settings->load();
525 setAnimationDelay( int(70.0*pow(10.0, -m_settings->anim_smoothness/32.0)) );
527 entry_size = m_settings->mv_fmetrics.height()+MARGIN_TOP+MARGIN_BOTTOM;
528 owner_table->m_scroll_area->setMinimumSize(entry_size*6, entry_size*9);
530 m_loader.setSize(m_settings->mv_font.pointSize());
532 layout_must_relayout = true;
533 layout();
536 void Widget::mouseMoveEvent ( QMouseEvent * event ) {
537 KGameCanvasItem *i = itemAt(event->pos());
538 Entry* e = i ? dynamic_cast<Entry*>(i) : NULL;
539 Text* f = i ? dynamic_cast<Text*>(i) : NULL;
540 Brace* b = i ? dynamic_cast<Brace*>(i) : NULL;
541 Comment* c = i ? dynamic_cast<Comment*>(i) : NULL;
542 EntryPtr olde = fetch(curr_highlight);
543 f = f && f->type == 1 ? f : NULL;
545 int oldtype = curr_highlight_type;
547 if(e) {
548 if(curr_highlight == e->index && curr_highlight_type==-1)
549 return;
550 e->setHighlight(true);
551 curr_highlight = e->index;
552 curr_highlight_type = -1;
554 else if(f) {
555 if(curr_highlight == f->entry->index && curr_highlight_type==-2)
556 return;
557 f->setHighlight(true);
558 curr_highlight = f->entry->index;
559 curr_highlight_type = -2;
561 else if(c) {
562 if( (curr_highlight==c->entry->index) &&
563 ((c->variation==-1) ? (curr_highlight_type == -3) :
564 (curr_highlight_type == -1000-c->variation) ) )
565 return;
566 c->setHighlight(true);
567 curr_highlight = c->entry->index;
568 curr_highlight_type = (c->variation==-1) ? -3 : (-1000-c->variation);
570 else if(b) {
571 if(curr_highlight == b->entry->index && curr_highlight_type==b->variation)
572 return;
573 b->setHighlight(true);
574 curr_highlight = b->entry->index;
575 curr_highlight_type = b->variation;
577 else
578 curr_highlight = Index(-1);
580 if(olde) {
581 if(oldtype==-1)
582 olde->setHighlight(false);
583 else if(oldtype==-2 && olde->fregna)
584 olde->fregna->setHighlight(false);
585 else if(oldtype==-3 && olde->comment)
586 olde->comment->setHighlight(false);
587 else if(oldtype<=-1000 && olde->vcomments.count(-1000-oldtype)==1)
588 olde->vcomments[-1000-oldtype]->setHighlight(false);
589 else if(oldtype>=0 && olde->braces.count(oldtype)==1)
590 olde->braces[oldtype]->setHighlight(false);
594 void Widget::startEditing(const Index& i, int v) {
595 stopEditing();
597 EntryPtr e = fetch(i);
598 if(!e) {
599 kError() << "Invalid index " << i;
600 return;
603 CommentPtr c = v == -1 ? e->comment : (e->vcomments.count(v) ?
604 e->vcomments[v] : CommentPtr());
605 QRect rect;
606 if(c)
607 rect = c->rect();
608 else {
609 TextPtr n = e->number;
610 if(!n && i > Index(0))
611 n = fetch(i.prev())->number;
612 int x = (n ? n->pos().x() : e->pos().x()) + ((v == -1) ? 0 : VAR_INDENTATION);
614 rect = QRect(x, e->pos().y()+entry_size, width()-x, 0);
617 edited_comment_variation = v;
618 edited_comment = boost::weak_ptr<Entry>(e);
619 comment_editor = new QTextEdit(c ? c->text : QString(), this);
620 comment_editor->setGeometry(rect.adjusted(0,0,0,entry_size*3));
621 comment_editor->show();
622 comment_editor->setFocus(Qt::MouseFocusReason);
623 comment_editor->installEventFilter(this);
626 bool Widget::eventFilter(QObject *obj, QEvent *event) {
627 if(obj == comment_editor && event->type() == QEvent::FocusOut ) {
628 stopEditing();
629 return true;
631 return false;
634 void Widget::stopEditing() {
635 EntryPtr e = edited_comment.lock();
636 if(e) {
637 if(comment_editor && notifier) {
638 QString c = comment_editor->toPlainText();
639 c.replace(QRegExp("(?:[ \t]\r?\n\r?|\r?\n\r?[ \t]|\r?\n\r?)"), " ");
640 if(edited_comment_variation == -1)
641 notifier->onUserSetComment(e->index, c);
642 else
643 notifier->onUserSetVComment(e->index, edited_comment_variation, c);
645 edited_comment.reset();
647 if(comment_editor) {
648 comment_editor->deleteLater();
649 comment_editor = NULL;
653 void Widget::mousePressEvent ( QMouseEvent * event ) {
654 stopEditing();
656 KGameCanvasItem *i = itemAt(event->pos());
657 if(!i)
658 return;
660 Text *t = dynamic_cast<Text*>(i);
661 if(t && t->type == 1) {
662 Entry *e = t->entry;
663 if(e->hide_next) {
664 e->hide_next = false;
665 e->expanded = true;
667 else
668 e->expanded = !e->expanded;
669 layout();
670 return;
673 Brace *b = dynamic_cast<Brace*>(i);
674 if(b) {
675 if(event->button() == Qt::LeftButton) {
676 Entry* e = b->entry;
677 EntryPtr first = e->variations[b->variation][0];
678 first->hide_next = !first->hide_next;
679 layout();
681 else if(event->button() == Qt::RightButton) {
682 QAction *a;
683 QMenu m(this);
684 a = m.addAction(KIcon("pen"), i18n("&Set Comment"));
685 a->setData("comment");
686 m.addSeparator();
687 a = m.addAction(KIcon(), i18n("&Promote Variation"));
688 a->setData("promote");
689 a = m.addAction(KIcon("edit-delete"), i18n("&Remove Variation"));
690 a->setData("remove");
691 boost::weak_ptr<Entry> ewptr = boost::weak_ptr<Entry>(fetch(b->entry->index));
692 int v = b->variation;
694 a = m.exec(event->globalPos());
696 /* beware, here, after exec, e could be a dangling pointer */
697 EntryPtr eptr = ewptr.lock();
698 if(a && notifier && eptr && eptr->variations.count(v)) {
699 if(a->data() == "comment")
700 startEditing(eptr->index, v);
701 else if(a->data() == "promote")
702 notifier->onUserPromoteVariation(eptr->index.next(v));
703 else if(a->data() == "remove")
704 notifier->onUserRemoveVariation(eptr->index.next(v));
707 return;
710 Comment *c = dynamic_cast<Comment*>(i);
711 if(c) {
712 startEditing(c->entry->index, c->variation);
713 return;
716 Entry *e = dynamic_cast<Entry*>(i);
717 if(e) {
718 if(event->button() == Qt::LeftButton) {
719 if(notifier)
720 notifier->onUserSelectMove(e->index);
722 else if(event->button() == Qt::RightButton) {
723 QAction *a;
724 QMenu m(this);
725 a = m.addAction(KIcon("pen"), i18n("&Set Comment"));
726 a->setData("comment");
727 a = m.addAction(KIcon("edit-clear"), i18n("&Clear Variations"));
728 a->setEnabled(!e->variations.empty());
729 a->setData("clear");
730 a = m.addAction(KIcon("cut"), i18n("&Truncate"));
731 a->setEnabled(fetch(e->index.next()));
732 a->setData("truncate");
733 m.addSeparator();
734 a = m.addAction(KIcon(), i18n("&Promote variation"));
735 a->setEnabled(e->index.nested.size());
736 a->setData("promote");
737 a = m.addAction(KIcon("edit-delete"), i18n("&Remove variation"));
738 a->setEnabled(e->index.nested.size());
739 a->setData("remove");
740 boost::weak_ptr<Entry> ewptr = boost::weak_ptr<Entry>(fetch(e->index));
742 a = m.exec(event->globalPos());
744 /* beware, here, after exec, e could be a dangling pointer */
745 EntryPtr eptr = ewptr.lock();
746 if(a && notifier && eptr) {
747 if(a->data() == "comment")
748 startEditing(eptr->index, -1);
749 else if(a->data() == "clear")
750 notifier->onUserClearVariations(eptr->index);
751 else if(a->data() == "truncate")
752 notifier->onUserTruncate(eptr->index);
753 else if(a->data() == "promote")
754 notifier->onUserPromoteVariation(eptr->index);
755 else if(a->data() == "remove")
756 notifier->onUserRemoveVariation(eptr->index);
759 return;
763 void Widget::mouseReleaseEvent ( QMouseEvent * /*event*/ ) {
767 void Widget::resizeEvent ( QResizeEvent * event ) {
768 stopEditing();
769 if(event->size().width() != event->oldSize().width()) {
770 layout_width_changed = true;
771 layout();
775 void Widget::layout() {
776 if(layout_pending)
777 return;
779 layout_pending = true;
780 QTimer::singleShot( 0, this, SLOT(doLayout()) );
783 void Widget::doLayout() {
784 layout_time = mSecs();
785 layout_pending = false;
786 layout_max_width = 0;
787 //kDebug() << "layout_must_relayout = " << layout_must_relayout;
788 int h = layoutHistory(history, BORDER_LEFT, BORDER_TOP, -1, 0, 0, true);
790 QSize s(std::max(entry_size*7, layout_max_width+BORDER_RIGHT),
791 std::max(entry_size*10, h+BORDER_BOTTOM) );
792 setMinimumSize(s);
794 layout_width_changed = false;
795 layout_must_relayout = false;
796 if(layout_goto_selected) {
797 EntryPtr e = fetch(curr_selected);
798 if(e)
799 owner_table->m_scroll_area->ensureVisible( int(e->pos().x() + e->m_rect.width()*0.5),
800 int(e->pos().y() + e->m_rect.height()*0.5) );
801 layout_goto_selected = false;
805 int Widget::layoutHistory(History& array, int at_x, int at_y,
806 int a_prev_turn, int mv_num, int sub_mv_num, bool visible) {
807 int flow_y = at_y;
808 int flow_x = at_x;
809 int nflow_x = at_x;
810 int col_num = 0;
811 int prev_turn = a_prev_turn;
813 for(int i=0;i<(int)array.size();i++) {
814 EntryPtr e = array[i];
816 /* if this is not visible, hide the item and hide all the number/fregna tags */
817 if(!visible) {
818 e->disappear();
819 if(e->number)
820 e->number->disappear();
821 if(e->fregna)
822 e->fregna->disappear();
823 if(e->comment)
824 e->comment->disappear();
826 /* hide the subvariations */
827 for(Variations::iterator it = e->variations.begin(); it != e->variations.end(); ++it)
828 layoutHistory(it->second, 0, 0, e->move_turn, mv_num, sub_mv_num, false);
829 for(Braces::iterator it = e->braces.begin(); it != e->braces.end(); ++it)
830 it->second->disappear();
831 for(VComments::iterator it = e->vcomments.begin(); it != e->vcomments.end(); ++it)
832 it->second->disappear();
833 mv_num++;
834 continue;
837 /* adjust the position if this is paired on the right */
838 bool draw_num = false;
841 if(e->move_turn != prev_turn) {
842 mv_num++;
843 sub_mv_num = 0;
845 else
846 sub_mv_num++;
848 if(layout_style==0) {
849 if(e->move_turn == 0 || i==0 || array[i-1]->childs_height != 0) {
850 if(mv_num>=1 && (e->move_turn != prev_turn || i==0 || array[i-1]->childs_height != 0)) {
851 draw_num = true;
852 flow_x = at_x;
854 else
855 flow_x = (mv_num>=1) ? nflow_x : at_x;
857 else {
858 if(e->move_turn != prev_turn) {
859 flow_x = std::max(flow_x + MIDDLE_PAD, int(MIN_COL_WIDTH*entry_size));
860 flow_y -= entry_size;
862 else
863 flow_x = int(MIN_COL_WIDTH*entry_size);
866 else {
867 if(e->move_turn != prev_turn || i==0 || array[i-1]->childs_height != 0) {
868 col_num = 0;
869 flow_x = at_x;
870 if(mv_num>=1)
871 draw_num = true;
873 else if(col_num == layout_style) {
874 col_num = 0;
875 flow_x = nflow_x;
877 else {
878 flow_y -= entry_size;
879 flow_x = std::max(flow_x + MIDDLE_PAD,
880 at_x + col_num*int(MIN_COL_WIDTH*entry_size));
885 col_num++;
887 /* update the number */
888 if(draw_num) {
889 TextPtr& n = e->number;
890 if(!n) {
891 n = TextPtr(new Text(e.get(), 0, this));
892 if(layout_style==0)
893 n->text = QString::number((mv_num+1)/2)+(mv_num&1 ? "." : ". ...");
894 else
895 n->text = QString::number(mv_num)+
896 (sub_mv_num ? "+"+QString::number(sub_mv_num) : QString())+".";
897 n->needs_update = true;
899 else if( !n->showing() || layout_must_relayout)
900 n->needs_update = true;
902 /* Mh, the number should never change, only appear disappear.
903 should this change, add here the code to enable number changes. */
904 QPoint dest(flow_x, flow_y);
906 if(n->pos() != dest)
907 if(n->visible())
908 n->goTo(dest);
909 else
910 n->moveTo(dest);
911 n->doUpdate();
912 n->appear();
914 flow_x += n->width;
915 nflow_x = flow_x;
917 else if(e->number)
918 e->number->disappear();
921 /* update the entry */
922 QPoint dest(flow_x, flow_y);
923 if(e->pos() != dest)
924 if(e->visible())
925 e->goTo(dest);
926 else
927 e->moveTo(dest);
928 if( !e->showing() || layout_must_relayout)
929 e->needs_update = true;
930 e->doUpdate();
931 e->appear();
932 e->childs_height = 0;
933 flow_x += e->m_rect.width();
936 /* Update the fregna. The fregna is visible if there are subvariations in this
937 entry, or if this entry is the first one of a variation where the remaining
938 entries are hidden and that contains the current position */
939 bool expandable = !e->variations.empty() || e->comment;
940 bool sel = (e->hide_next && e->index<curr_selected)
941 || (!e->expanded && expandable &&
942 e->index<curr_selected && !(e->index.next()<=curr_selected));
943 if(expandable || sel ) {
944 if(!e->fregna)
945 e->fregna = TextPtr(new Text(e.get(), 1, this));
947 /* update the data, if needed */
948 TextPtr f = e->fregna;
949 const char *text = (sel||!e->expanded||e->hide_next) ? "[+]" : "[-]";
950 if(f->text != text || f->selected != sel) {
951 f->text = text;
952 f->selected = sel;
953 f->needs_update = true;
955 else if( !f->showing() || layout_must_relayout)
956 f->needs_update = true;
958 QPoint dest(flow_x, flow_y);
960 if(f->pos() != dest)
961 if(f->visible())
962 f->goTo(QPoint(flow_x, flow_y));
963 else
964 f->moveTo(QPoint(flow_x, flow_y));
965 f->doUpdate();
966 f->appear();
968 flow_x += f->width;
970 else if(e->fregna)
971 e->fregna->disappear();
973 /* update the flow information */
974 flow_y += entry_size;
975 layout_max_width = std::max(flow_x, layout_max_width);
976 int prev_pos = flow_y;
978 /* update the comment */
979 if(e->comment) {
980 CommentPtr c = e->comment;
982 if(e->expanded && !e->hide_next) {
983 QPoint dest(at_x + COMMENT_INDENTATION, flow_y);
985 if(c->pos() != dest ) {
986 if(c->visible())
987 c->goTo(dest);
988 else
989 c->moveTo(dest);
990 c->needs_update = true;
992 else if( !c->showing() || layout_width_changed || layout_must_relayout)
993 c->needs_update = true;
994 c->doUpdate();
995 c->appear();
996 flow_y += c->height;
998 else
999 c->disappear();
1002 /* update the variations */
1003 for(Variations::iterator it = e->variations.begin(); it != e->variations.end(); ++it) {
1004 int old_pos = flow_y;
1006 /* update the variation's comment */
1007 if(e->vcomments.count(it->first)) {
1008 CommentPtr c = e->vcomments[it->first];
1010 if(e->expanded && !e->hide_next) {
1011 QPoint dest(at_x + VAR_INDENTATION + COMMENT_INDENTATION, flow_y);
1013 if( !c->showing() || layout_must_relayout)
1014 c->needs_update = true;
1015 if(c->pos() != dest)
1016 if(c->visible())
1017 c->goTo(dest);
1018 else
1019 c->moveTo(dest);
1020 c->doUpdate();
1021 c->appear();
1022 flow_y += c->height;
1024 else
1025 c->disappear();
1028 /* layout the variation */
1029 flow_y = layoutHistory(it->second, at_x + VAR_INDENTATION, flow_y,
1030 e->move_turn, mv_num, sub_mv_num, e->expanded && !e->hide_next);
1032 /* update the brace of the variation */
1033 BracePtr b = e->braces[it->first];
1034 if(e->expanded && !e->hide_next) {
1035 b->depth = e->index.nested.size();
1036 b->setHeight((it->second.size() && it->second[0]->hide_next) ? entry_size : flow_y - old_pos);
1037 b->width = VAR_INDENTATION;
1038 if(b->visible())
1039 b->goTo(QPoint(at_x, old_pos));
1040 else
1041 b->moveTo(QPoint(at_x, old_pos));
1042 b->appear();
1044 else
1045 b->disappear();
1048 e->childs_height = flow_y - prev_pos;
1050 if(e->hide_next)
1051 visible = false;
1052 prev_turn = e->move_turn;
1054 return flow_y;
1057 QPixmap Widget::getPixmap(const QString& s, bool selected) {
1058 QString k = selected ? s+"_sel":s;
1059 if(loaded_pixmaps.contains(k))
1060 return loaded_pixmaps[k];
1062 QString iconFile = KStandardDirs::locate("appdata", "piece_icons/" + s + ".png");
1063 QImage img(iconFile);
1065 if(selected) {
1066 QPainter p(&img);
1067 p.setCompositionMode(QPainter::CompositionMode_SourceAtop );
1068 p.fillRect(0,0,img.width(), img.height(), m_settings->select_color);
1070 return loaded_pixmaps[k] = QPixmap::fromImage(img.scaled(m_settings->mv_fmetrics.ascent(),
1071 m_settings->mv_fmetrics.ascent(),
1072 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
1075 void Widget::setComment(EntryPtr e, int v, const QString& comment) {
1076 if(comment.isEmpty()) {
1077 if(v == -1) {
1078 if(e->comment)
1079 e->comment = CommentPtr();
1081 else {
1082 if(e->vcomments.count(v))
1083 e->vcomments.erase(v);
1086 else {
1087 CommentPtr p;
1089 if(v == -1) {
1090 if(!e->comment)
1091 e->comment = CommentPtr(new Comment(e.get(), this));
1092 p = e->comment;
1094 else {
1095 if(!e->variations.count(v))
1096 return;
1098 if(!e->vcomments.count(v))
1099 p = e->vcomments[v] = CommentPtr(new Comment(e.get(), this, v));
1100 else
1101 p = e->vcomments[v];
1104 if(p->text == comment)
1105 return;
1106 p->text = comment;
1107 p->needs_update = true;
1110 layout();
1113 void Widget::setComment(const Index& index, const QString& comment) {
1114 EntryPtr e = fetch(index);
1115 if(!e) {
1116 kError() << "Invalid index" << index;
1117 return;
1119 setComment(e, -1, comment);
1122 void Widget::setVComment(const Index& index, int v, const QString& comment) {
1123 EntryPtr e = fetch(index);
1124 if(!e || !e->variations.count(v)) {
1125 kError() << "Invalid index" << index;
1126 return;
1128 setComment(e, v, comment);
1131 void Widget::setMove(const Index& index,
1132 int turn, const QString& move, const QString& comment) {
1133 DecoratedMove mv;
1134 #if 1
1135 mv.push_back(MovePart(move));
1136 #else
1137 //TODO: move this code in some other place, it really should not stay here
1138 QRegExp reg("[KQRBNP]");
1139 int x = 0;
1140 while(reg.indexIn(move, x) != -1) {
1141 if(reg.pos() > x)
1142 mv.push_back(MovePart(MoveText, move.mid(x, reg.pos()-x)));
1143 mv.push_back(MovePart(MovePixmap, reg.cap().toLower()));
1144 x = reg.pos() + reg.matchedLength();
1146 if(x<move.length())
1147 mv.push_back(MovePart(MoveText, move.mid(x)));
1148 #endif
1149 setMove(index, turn, mv, comment);
1152 void Widget::setMove(const Index& index,
1153 int turn, const DecoratedMove& move, const QString& comment) {
1154 EntryPtr e = fetch(index);
1155 if(e) {
1156 e->move_turn = turn;
1157 e->move = move;
1158 e->needs_update = true;
1159 setComment(e, -1, comment);
1160 layout();
1161 return;
1164 int at;
1165 History *vec = fetchRef(index.prev(), &at);
1166 if(!vec) {
1167 kError() << "Invalid index" << index;
1168 return;
1171 if(index.nested.size() && index.nested.back().num_moves == 0) {
1172 History var;
1173 int v = index.nested.back().variation;
1174 var.push_back(e = EntryPtr( new Entry(turn, move, index, this)) );
1175 (*vec)[at]->variations[v] = var;
1176 (*vec)[at]->braces[v] = BracePtr( new Brace( (*vec)[at].get(), v, this) );
1178 else
1179 vec->push_back(e = EntryPtr( new Entry(turn, move, index, this)) );
1181 setComment(e, -1, comment);
1182 e->hide();
1183 layout();
1186 void Widget::remove(const Index& index) {
1188 if(index.atVariationStart() ) {
1189 EntryPtr e = fetch(index.prev());
1190 if(!e)
1191 return;
1193 int v = index.nested.back().variation;
1194 if(!e->variations.count(v))
1195 return;
1197 e->variations.erase(v);
1198 e->braces.erase(v);
1199 e->vcomments.erase(v);
1201 else {
1202 int at;
1203 History *vec = fetchRef(index, &at);
1204 if(!vec)
1205 return;
1207 while((int)vec->size() > at)
1208 vec->pop_back();
1210 layout();
1213 void Widget::fixIndices(const Index& ix) {
1214 int at;
1215 History *vec = fetchRef(ix, &at);
1216 if(!vec) {
1217 kError() << "Invalid index" << ix;
1218 return;
1220 Index index = ix;
1221 for(int i=at;i<(int)vec->size();i++) {
1222 EntryPtr e = (*vec)[i];
1223 e->index = index;
1225 for(Variations::const_iterator it = e->variations.begin();
1226 it != e->variations.end(); ++it)
1227 fixIndices(index.next(it->first));
1228 index = index.next();
1232 void Widget::promoteVariation(const Index& ix, int v) {
1233 int at;
1234 History *vec = fetchRef(ix, &at);
1235 if(!vec) {
1236 kError() << "Invalid index" << ix;
1237 return;
1240 History vold = (*vec)[at]->variations[v];
1241 History vnew;
1242 for(int i=at+1; i<(int)vec->size(); i++)
1243 vnew.push_back((*vec)[i]);
1244 while((int)vec->size()>at+1)
1245 vec->pop_back();
1246 for(int i=0; i<(int)vold.size(); i++)
1247 vec->push_back(vold[i]);
1248 (*vec)[at]->variations[v] = vnew;
1250 Q_ASSERT((int)vec->size()>at+1);
1251 (*vec)[at+1]->hide_next = false;
1253 fixIndices(ix.next());
1254 fixIndices(ix.next(v));
1256 curr_selected = curr_selected.flipVariation(ix, v);
1257 curr_highlight = curr_highlight.flipVariation(ix, v);
1258 layout();
1261 void Widget::select(const Index& index) {
1262 if(curr_selected == index)
1263 return;
1264 EntryPtr e = fetch(index);
1265 EntryPtr olde = fetch(curr_selected);
1266 if(olde) {
1267 olde->selected = false;
1268 olde->needs_update = true;
1270 if(e) {
1271 e->selected = true;
1272 e->needs_update = true;
1273 layout_goto_selected = true;
1275 curr_selected = index;
1276 layout();
1279 void Widget::setLoaderTheme(const ThemeInfo& theme) {
1280 m_loader.setTheme(theme);
1283 //END Widget-------------------------------------------------------------------
1285 } //end namespace MoveList