Added createPiece to the KBoard API.
[tagua/yd.git] / src / movelist.cpp
blob1f7f439796bb5eb2e4535a0995a7a953111b0b5d
1 /*
2 Copyright (c) 2006 Paolo Capriotti <p.capriotti@sns.it>
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 */
11 #include <iostream>
12 #include <QApplication>
13 #include <QPainter>
14 #include <QPaintEvent>
15 #include <QMouseEvent>
16 #include <QTextEdit>
17 #include <QMenu>
18 #include <QHBoxLayout>
19 #include <QVBoxLayout>
20 #include <QScrollArea>
21 #include <QToolButton>
22 #include <QScrollBar>
23 #include <QTimer>
24 #include <cmath>
25 #include <iostream>
26 #include <kstandarddirs.h>
27 #include "global.h"
28 #include "pref_theme.h"
29 #include "movelist_widget.h"
30 #include "movelist_table.h"
31 #include "movelist_notifier.h"
32 #include "movelist_p.h"
34 namespace MoveList {
36 #define MARGIN_LEFT 2
37 #define MARGIN_RIGHT 4
38 #define MARGIN_TOP 1
39 #define MARGIN_BOTTOM 0
40 #define MIDDLE_PAD 3
41 #define COMMENT_INDENTATION 4
42 #define VAR_INDENTATION 16
43 #define BORDER_LEFT 3
44 #define BORDER_RIGHT 3
45 #define BORDER_TOP 3
46 #define BORDER_BOTTOM 3
47 #define MIN_COL_WIDTH 5.0
49 #define DEFAULT_ANIMATION_TIME 350.0
51 //BEGIN FancyItem--------------------------------------------------------------
53 bool FancyItem::showing() {
54 return !(time_opacity!=-1 && target_opacity == 0) &&
55 ((time_opacity!=-1 && target_opacity == 255) || (visible() && opacity() == 255));
58 void FancyItem::appear() {
59 if((time_opacity!=-1 && target_opacity == 255)
60 || (visible() && opacity() == 255))
61 return;
63 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
64 if(!m->m_settings->anim_enabled || !m->m_settings->anim_hideshow)
65 show();
66 else {
67 old_opacity = 0;
68 target_opacity = 255;
69 time_opacity = m->layout_time;
70 setAnimated(true);
74 void FancyItem::disappear() {
75 if((time_opacity!=-1 && target_opacity == 0)
76 || !visible() || opacity() == 0)
77 return;
79 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
80 setHighlight(false);
81 if(!m->m_settings->anim_enabled || !m->m_settings->anim_hideshow)
82 hide();
83 else {
84 old_opacity = 255;
85 target_opacity = 0;
86 time_opacity = m->layout_time;
87 setAnimated(true);
91 void FancyItem::goTo(QPoint p) {
92 if((time_pos!=-1 && target_pos == p) || (time_pos==-1 && pos() == p))
93 return;
95 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
96 if(!m->m_settings->anim_enabled || !m->m_settings->anim_moving)
97 moveTo(p);
98 else {
99 old_pos = pos();
100 target_pos = p;
101 time_pos = m->layout_time;
102 /*std::cout << m->layout_time << " start " << this << " "
103 << prettyTypeName(typeid(*this).name()) << std::endl;*/
104 setAnimated(true);
108 void FancyItem::setHighlight(bool h) {
109 if(highlighted == h)
110 return;
112 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
113 if(!m->m_settings->anim_enabled || !m->m_settings->anim_highlight) {
114 curr_highlight = h ? 255 : 0;
115 highlighted = h;
116 changed();
118 else {
119 old_highlight = highlighted ? 255 : 0;
120 target_highlight = h ? 255 : 0;
121 highlighted = h;
122 time_highlight = m->mSecs();
123 setAnimated(true);
127 void FancyItem::advance(int time) {
128 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
129 /*std::cout << time << " anim " << this << " "
130 << prettyTypeName(typeid(*this).name()) << std::endl;*/
131 if(time_highlight != -1) {
132 float fact = (time - time_highlight) / m->m_settings->anim_time;
133 if(fact >= 1.0) {
134 curr_highlight = target_highlight;
135 time_highlight = -1;
137 else
138 curr_highlight = int(target_highlight*fact + old_highlight*(1-fact));
139 changed();
141 if(time_opacity != -1) {
142 float fact = (time - time_opacity) / m->m_settings->anim_time;
143 if(fact >= 1.0) {
144 setOpacity(target_opacity);
145 setVisible(target_opacity != 0);
146 time_opacity = -1;
148 else {
149 setOpacity( int(target_opacity*fact + old_opacity*(1-fact)) );
150 setVisible(true);
153 if(time_pos != -1) {
154 float fact = (time - time_pos) / m->m_settings->anim_time;
155 if(fact >= 1.0) {
156 /*std::cout << time << " done " << this << " "
157 << prettyTypeName(typeid(*this).name()) << std::endl;*/
158 moveTo(target_pos);
159 time_pos = -1;
161 else {
162 /*std::cout << time << " move " << this << " "
163 << prettyTypeName(typeid(*this).name()) << std::endl;*/
164 moveTo( int(target_pos.x()*fact + old_pos.x()*(1-fact)+0.5),
165 int(target_pos.y()*fact + old_pos.y()*(1-fact)+0.5));
168 if(canStop()) {
169 /*std::cout << time << " stop " << this << " "
170 << prettyTypeName(typeid(*this).name()) << std::endl;*/
171 setAnimated(false);
175 bool FancyItem::layered() const {
176 return false;
179 //END FancyItem----------------------------------------------------------------
182 //BEGIN Brace------------------------------------------------------------------
184 void Brace::setHeight(int h) {
185 if(h<0) h=0;
186 if((animated() && target_height == h) || height == h)
187 return;
189 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
190 if(!m->m_settings->anim_enabled || !m->m_settings->anim_moving) {
191 height = h;
192 changed();
194 else {
195 old_height = height;
196 target_height = h;
197 time_height = m->layout_time;
198 if(visible())
199 setAnimated(true);
203 void Brace::advance(int time) {
204 if(time_height != -1) {
205 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
206 float fact = (time - time_height) / m->m_settings->anim_time;
207 if(fact >= 1.0) {
208 height = target_height;
209 time_height = -1;
211 else
212 height = int(target_height*fact + old_height*(1-fact) + 0.5);
213 changed();
215 FancyItem::advance(time);
218 void Brace::paint (QPainter *p) {
219 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
220 if(height < m->entry_size)
221 return;
222 QPointF p1((pos().x()*2+width)/2.0, (pos().y()*2+m->entry_size)/2.0);
223 QPointF p2((pos().x()*2+width)/2.0, (pos().y()*2+height*2-m->entry_size)/2.0);
224 p->save();
225 QPen q;
226 if(depth % 2) {
227 p->setBrush(QColor(255,192,224));
228 q = QColor(128,0,64);
230 else {
231 p->setBrush(QColor(192,255,224));
232 q = QColor(0,128,64);
234 q.setWidth(2);
235 p->setPen(q);
236 p->setRenderHint(QPainter::Antialiasing);
237 int s = std::min(m->entry_size, width);
238 float cs1 = (0.4 + 0.2*(curr_highlight/255.0)) * s;
239 float cs2 = 0.2 * s;
241 p->drawLine(p1,p2);
242 p->drawEllipse(QRectF(-cs1/2,-cs1/2,cs1,cs1).translated(p1));
243 p->setBrush(p->pen().color());
244 p->drawEllipse(QRectF(-cs2/2,-cs2/2,cs2,cs2).translated(p2));
245 p->restore();
248 QRect Brace::rect () const {
249 return QRect(pos(), QSize(width, height));
252 //END Brace--------------------------------------------------------------------
255 //BEGIN Text-------------------------------------------------------------------
257 void Text::paint (QPainter *p) {
258 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
259 if(curr_highlight != 0) {
260 p->setBrush(QColor(192,224,208, curr_highlight));
261 p->setPen(QColor(64,128,96, curr_highlight));
262 p->drawRect(rect().adjusted(0,0,-1,-1));
264 p->setFont(selected ? m->m_settings->sel_mv_font : m->m_settings->mv_font);
265 p->setPen(selected ? m->m_settings->select_color : Qt::black);
266 p->drawText(pos()+QPoint(MARGIN_LEFT, MARGIN_TOP+m->m_settings->mv_fmetrics.ascent()), text);
269 QRect Text::rect () const {
270 return QRect(pos(), QSize(width, height));
273 void Text::doUpdate () {
274 if(!needs_update)
275 return;
277 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
278 width = (selected ? m->m_settings->sel_mv_fmetrics
279 : m->m_settings->mv_fmetrics).boundingRect(text).right()
280 + MARGIN_LEFT + MARGIN_RIGHT;
281 height = m->entry_size;
283 needs_update = false;
284 changed();
287 //END Text---------------------------------------------------------------------
290 //BEGIN Comment----------------------------------------------------------------
292 void Comment::paint (QPainter *p) {
293 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
295 if(curr_highlight != 0) {
296 p->setBrush(QColor(255,255,255, curr_highlight));
297 p->setPen(QColor(192,192,192, curr_highlight));
298 p->drawRect(rect().adjusted(0,0,-1,-1));
300 p->setFont(m->m_settings->comm_font);
301 p->setPen(m->m_settings->comment_color);
302 p->drawText(pos().x() + MARGIN_RIGHT,
303 pos().y(),
304 width - MARGIN_LEFT - MARGIN_RIGHT, 9999,
305 Qt::AlignLeft|Qt::AlignTop|Qt::TextWordWrap, text);
308 QRect Comment::rect () const {
309 return QRect(pos(), QSize(width, height));
312 void Comment::doUpdate () {
313 if(!needs_update)
314 return;
316 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
317 QPoint dest = ((time_pos != -1) ? target_pos : pos());
318 width = std::max(m->width() - dest.x(), m->entry_size);
319 height = m->m_settings->comm_fmetrics.boundingRect(0,0,width - MARGIN_LEFT - MARGIN_RIGHT, 99999,
320 Qt::AlignLeft|Qt::AlignTop|Qt::TextWordWrap, text).height()
321 + MARGIN_TOP + MARGIN_BOTTOM;
323 needs_update = false;
324 changed();
327 //END Comment------------------------------------------------------------------
330 //BEGIN Entry------------------------------------------------------------------
332 void Entry::paint (QPainter *p) {
333 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
334 if(curr_highlight != 0) {
335 p->setBrush(QColor(192,224,255, curr_highlight));
336 p->setPen(QColor(64,96,128, curr_highlight));
337 p->drawRect(rect().adjusted(0,0,-1,-1));
339 p->setPen(selected ? m->m_settings->select_color : Qt::black);
340 int x = pos().x()+MARGIN_LEFT;
341 int y = pos().y()+MARGIN_TOP+m_ascent;
343 p->setRenderHint(QPainter::TextAntialiasing);
344 QFont tf = selected ? m->m_settings->sel_mv_font : m->m_settings->mv_font;
345 QFontMetrics& fm = selected ? m->m_settings->sel_mv_fmetrics : m->m_settings->mv_fmetrics;
346 QRect r(0,0,0,0);
348 for(int i=0;i<(int)move.size();i++) {
349 if(move[i].m_type == MovePart::Text) {
350 p->setFont(tf);
351 p->drawText(QPoint(x+r.width(), y), move[i].m_string);
352 QRect b = fm.boundingRect(move[i].m_string);
353 r |= b.translated(r.width()-b.x(), 0);
355 else if(move[i].m_type == MovePart::Figurine) {
356 ::Loader::Glyph g = m->m_loader.getGlyph(move[i].m_string);
357 p->setFont(g.m_font_valid ? g.m_font : tf);
358 p->drawText(QPoint(x+r.width(), y), g.m_char);
359 QFontMetrics fi(g.m_font_valid ? g.m_font : tf);
360 QRect b = fi.boundingRect(g.m_char);
361 r |= b.translated(r.width()-b.x(), 0);
366 QRect Entry::rect () const {
367 return m_rect.translated(pos().x()+MARGIN_LEFT, pos().y()+MARGIN_TOP+m_ascent);
370 void Entry::doUpdate () {
371 if(!needs_update)
372 return;
374 Widget *m = dynamic_cast<Widget*>(topLevelCanvas());
375 QFont tf = selected ? m->m_settings->sel_mv_font : m->m_settings->mv_font;
376 QFontMetrics& fm = selected ? m->m_settings->sel_mv_fmetrics : m->m_settings->mv_fmetrics;
377 m_ascent = m->m_settings->mv_fmetrics.ascent();
378 m_rect = QRect(0,0,0,0);
380 for(int i=0;i<(int)move.size();i++) {
381 if(move[i].m_type == MovePart::Text) {
382 QRect b = fm.boundingRect(move[i].m_string);
383 m_rect |= b.translated(m_rect.width()-b.x(), 0);
385 else if(move[i].m_type == MovePart::Figurine) {
386 ::Loader::Glyph g = m->m_loader.getGlyph(move[i].m_string);
387 QFontMetrics fi(g.m_font_valid ? g.m_font : tf);
388 QRect b = fi.boundingRect(g.m_char);
389 m_rect |= b.translated(m_rect.width()-b.x(), 0);
392 m_rect = QRect(m_rect.x(),m_rect.y(),m_rect.width()+MARGIN_RIGHT,m_rect.height());
394 needs_update = false;
395 changed();
398 //END Entry--------------------------------------------------------------------
401 //BEGIN Settings---------------------------------------------------------------
403 void Settings::load() {
404 ::Settings s = settings.group("move-list");
405 ::Settings s_anim = s.group("animations");
407 anim_enabled = s.group("animations").flag("enabled", true);
408 anim_moving = s_anim.group("moving").flag("enabled", true);
409 anim_hideshow = s_anim.group("hideshow").flag("enabled", true);
410 anim_highlight = s_anim.group("highlight").flag("enabled", true);
411 anim_speed = s_anim["speed"] | 16;
412 anim_time = DEFAULT_ANIMATION_TIME*pow(5.0, 1.0 - anim_speed/16.0);
413 anim_smoothness = s_anim["smoothness"] | 16;
414 select_color = s["select-color"] | QColor(Qt::red);
415 comment_color = s["comment-color"] | QColor(64,64,64);
416 mv_font = QApplication::font();
417 if ((use_mv_font = s.group("moves-font").flag("enabled", true)))
418 mv_font = s["moves-font"] | mv_font;
419 sel_mv_font = mv_font;
420 sel_mv_font.setBold(true);
421 comm_font = QApplication::font();
422 comm_font.setItalic(true);
423 if ((use_comm_font = s.group("comment-font").flag("enabled", true)))
424 comm_font = s["CommentFont"] | comm_font;
425 mv_fmetrics = QFontMetrics(mv_font);
426 sel_mv_fmetrics = QFontMetrics(sel_mv_font);
427 comm_fmetrics = QFontMetrics(comm_font);
429 // settings.qSettings()->endGroup();
432 void Settings::save() {
433 ::Settings s = settings.group("move-list");
434 ::Settings s_anim = s.group("animations");
436 s.group("animations").setFlag("enabled", anim_enabled);
437 s_anim.group("moving").setFlag("enabled", anim_moving);
438 s_anim.group("hideshow").setFlag("enabled", anim_hideshow);
439 s_anim.group("highlight").setFlag("enabled", anim_highlight);
440 s_anim["speed"] = anim_speed;
441 s_anim["smoothness"] = anim_smoothness;
442 s["select-color"] = select_color;
443 s["comment-color"] = comment_color;
444 s.group("moves-font").flag("enabled", use_mv_font);
445 s["moves-font"] = mv_font;
446 s.group("comment-font").flag("enabled", use_comm_font);
447 s["comment-font"] = comm_font;
450 //END Settings-----------------------------------------------------------------
453 //BEGIN Widget-----------------------------------------------------------------
455 Widget::Widget(QWidget *parent, Table *o)
456 : KGameCanvasWidget(parent)
457 , curr_highlight(-1)
458 , curr_selected(-1)
459 , comment_editor(NULL)
460 , layout_pending(false)
461 , layout_style(0)
462 , layout_goto_selected(false)
463 , layout_width_changed(true)
464 , layout_must_relayout(true)
465 , notifier(NULL)
466 , owner_table(o)
467 , m_settings(new Settings) {
469 resize(50,100);
470 setSizePolicy ( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
472 setMouseTracking(true);
473 settingsChanged();
474 reset();
477 Widget::~Widget() {
478 delete m_settings;
481 void Widget::reset() {
482 curr_highlight = Index(-1);
483 curr_selected = Index(-1);
484 if(comment_editor) {
485 delete comment_editor;
486 comment_editor = NULL;
489 DecoratedMove mv;
490 mv.push_back(MovePart(QString("Mainline:")));
491 history.clear();
492 history.push_back( EntryPtr(new Entry(-1, mv, Index(0), this)) );
494 layout();
497 EntryPtr Widget::fetch(const Index& ix) {
498 int at;
499 History *vec = fetchRef(ix, &at);
500 return vec ? (*vec)[at] : EntryPtr();
503 History* Widget::fetchRef(const Index& ix, int* idx) {
504 if(ix.num_moves >= (int)history.size() || ix.num_moves < 0 )
505 return NULL;
507 History* aretv = &history;
508 EntryPtr retv = history[ix.num_moves];
509 if(idx) *idx = ix.num_moves;
511 for(int i=0; i<(int)ix.nested.size();i++) {
512 Variations::iterator it = retv->variations.find(ix.nested[i].variation);
513 if(it == retv->variations.end() || ix.nested[i].num_moves >= (int)it->second.size()
514 || ix.nested[i].num_moves < 0 )
515 return NULL;
517 aretv = &it->second;
518 retv = it->second[ix.nested[i].num_moves];
519 if(idx) *idx = ix.nested[i].num_moves;
521 return aretv;
524 Notifier* Widget::getNotifier() {
525 return notifier;
528 void Widget::setNotifier(Notifier* n, bool detach_prev){
529 if(detach_prev && notifier && notifier != n)
530 notifier->onDetachNotifier();
531 notifier = n;
534 void Widget::settingsChanged() {
535 m_settings->load();
537 setAnimationDelay( int(70.0*pow(10.0, -m_settings->anim_smoothness/32.0)) );
539 entry_size = m_settings->mv_fmetrics.height()+MARGIN_TOP+MARGIN_BOTTOM;
540 owner_table->m_scroll_area->setMinimumSize(entry_size*6, entry_size*9);
542 m_loader.setSize(m_settings->mv_font.pointSize());
544 layout_must_relayout = true;
545 layout();
548 void Widget::mouseMoveEvent ( QMouseEvent * event ) {
549 KGameCanvasItem *i = itemAt(event->pos());
550 Entry* e = i ? dynamic_cast<Entry*>(i) : NULL;
551 Text* f = i ? dynamic_cast<Text*>(i) : NULL;
552 Brace* b = i ? dynamic_cast<Brace*>(i) : NULL;
553 Comment* c = i ? dynamic_cast<Comment*>(i) : NULL;
554 EntryPtr olde = fetch(curr_highlight);
555 f = f && f->type == 1 ? f : NULL;
557 int oldtype = curr_highlight_type;
559 if(e) {
560 if(curr_highlight == e->index && curr_highlight_type==-1)
561 return;
562 e->setHighlight(true);
563 curr_highlight = e->index;
564 curr_highlight_type = -1;
566 else if(f) {
567 if(curr_highlight == f->entry->index && curr_highlight_type==-2)
568 return;
569 f->setHighlight(true);
570 curr_highlight = f->entry->index;
571 curr_highlight_type = -2;
573 else if(c) {
574 if( (curr_highlight==c->entry->index) &&
575 ((c->variation==-1) ? (curr_highlight_type == -3) :
576 (curr_highlight_type == -1000-c->variation) ) )
577 return;
578 c->setHighlight(true);
579 curr_highlight = c->entry->index;
580 curr_highlight_type = (c->variation==-1) ? -3 : (-1000-c->variation);
582 else if(b) {
583 if(curr_highlight == b->entry->index && curr_highlight_type==b->variation)
584 return;
585 b->setHighlight(true);
586 curr_highlight = b->entry->index;
587 curr_highlight_type = b->variation;
589 else
590 curr_highlight = Index(-1);
592 if(olde) {
593 if(oldtype==-1)
594 olde->setHighlight(false);
595 else if(oldtype==-2 && olde->fregna)
596 olde->fregna->setHighlight(false);
597 else if(oldtype==-3 && olde->comment)
598 olde->comment->setHighlight(false);
599 else if(oldtype<=-1000 && olde->vcomments.count(-1000-oldtype)==1)
600 olde->vcomments[-1000-oldtype]->setHighlight(false);
601 else if(oldtype>=0 && olde->braces.count(oldtype)==1)
602 olde->braces[oldtype]->setHighlight(false);
606 void Widget::startEditing(const Index& i, int v) {
607 stopEditing();
609 EntryPtr e = fetch(i);
610 if(!e) {
611 std::cout << "--> Error in Widget::startEditing! Invalid index!" << std::endl;
612 return;
615 CommentPtr c = v == -1 ? e->comment : (e->vcomments.count(v) ?
616 e->vcomments[v] : CommentPtr());
617 QRect rect;
618 if(c)
619 rect = c->rect();
620 else {
621 TextPtr n = e->number;
622 if(!n && i > Index(0))
623 n = fetch(i.prev())->number;
624 int x = (n ? n->pos().x() : e->pos().x()) + ((v == -1) ? 0 : VAR_INDENTATION);
626 rect = QRect(x, e->pos().y()+entry_size, width()-x, 0);
629 edited_comment_variation = v;
630 edited_comment = boost::weak_ptr<Entry>(e);
631 comment_editor = new QTextEdit(c ? c->text : QString(), this);
632 comment_editor->setGeometry(rect.adjusted(0,0,0,entry_size*3));
633 comment_editor->show();
634 comment_editor->setFocus(Qt::MouseFocusReason);
635 comment_editor->installEventFilter(this);
638 bool Widget::eventFilter(QObject *obj, QEvent *event) {
639 if(obj == comment_editor && event->type() == QEvent::FocusOut ) {
640 stopEditing();
641 return true;
643 return false;
646 void Widget::stopEditing() {
647 EntryPtr e = edited_comment.lock();
648 if(e) {
649 if(comment_editor && notifier) {
650 QString c = comment_editor->toPlainText();
651 c.replace(QRegExp("(?:[ \t]\r?\n\r?|\r?\n\r?[ \t]|\r?\n\r?)"), " ");
652 if(edited_comment_variation == -1)
653 notifier->onUserSetComment(e->index, c);
654 else
655 notifier->onUserSetVComment(e->index, edited_comment_variation, c);
657 edited_comment.reset();
659 if(comment_editor) {
660 comment_editor->deleteLater();
661 comment_editor = NULL;
665 void Widget::mousePressEvent ( QMouseEvent * event ) {
666 stopEditing();
668 KGameCanvasItem *i = itemAt(event->pos());
669 if(!i)
670 return;
672 Text *t = dynamic_cast<Text*>(i);
673 if(t && t->type == 1) {
674 Entry *e = t->entry;
675 if(e->hide_next) {
676 e->hide_next = false;
677 e->expanded = true;
679 else
680 e->expanded = !e->expanded;
681 layout();
682 return;
685 Brace *b = dynamic_cast<Brace*>(i);
686 if(b) {
687 if(event->button() == Qt::LeftButton) {
688 Entry* e = b->entry;
689 EntryPtr first = e->variations[b->variation][0];
690 first->hide_next = !first->hide_next;
691 layout();
693 else if(event->button() == Qt::RightButton) {
694 QAction *a;
695 QMenu m(this);
696 a = m.addAction(QIcon(m_settings->icons+"comment.png"), "&Set comment");
697 a->setData("comment");
698 m.addSeparator();
699 a = m.addAction(QIcon(m_settings->icons+"promote.png"), "&Promote variation");
700 a->setData("promote");
701 a = m.addAction(QIcon(m_settings->icons+"remove.png"), "&Remove variation");
702 a->setData("remove");
703 boost::weak_ptr<Entry> ewptr = boost::weak_ptr<Entry>(fetch(b->entry->index));
704 int v = b->variation;
706 a = m.exec(event->globalPos());
708 /* beware, here, after exec, e could be a dangling pointer */
709 EntryPtr eptr = ewptr.lock();
710 if(a && notifier && eptr && eptr->variations.count(v)) {
711 if(a->data() == "comment")
712 startEditing(eptr->index, v);
713 else if(a->data() == "promote")
714 notifier->onUserPromoteVariation(eptr->index.next(v));
715 else if(a->data() == "remove")
716 notifier->onUserRemoveVariation(eptr->index.next(v));
719 return;
722 Comment *c = dynamic_cast<Comment*>(i);
723 if(c) {
724 startEditing(c->entry->index, c->variation);
725 return;
728 Entry *e = dynamic_cast<Entry*>(i);
729 if(e) {
730 if(event->button() == Qt::LeftButton) {
731 if(notifier)
732 notifier->onUserSelectMove(e->index);
734 else if(event->button() == Qt::RightButton) {
735 QAction *a;
736 QMenu m(this);
737 a = m.addAction(QIcon(m_settings->icons+"comment.png"), "&Set comment");
738 a->setData("comment");
739 a = m.addAction(QIcon(m_settings->icons+"clear.png"), "&Clear variations");
740 a->setEnabled(!e->variations.empty());
741 a->setData("clear");
742 a = m.addAction(QIcon(m_settings->icons+"truncate.png"), "&Truncate");
743 a->setEnabled(fetch(e->index.next()));
744 a->setData("truncate");
745 m.addSeparator();
746 a = m.addAction(QIcon(m_settings->icons+"promote.png"), "&Promote variation");
747 a->setEnabled(e->index.nested.size());
748 a->setData("promote");
749 a = m.addAction(QIcon(m_settings->icons+"remove.png"), "&Remove variation");
750 a->setEnabled(e->index.nested.size());
751 a->setData("remove");
752 boost::weak_ptr<Entry> ewptr = boost::weak_ptr<Entry>(fetch(e->index));
754 a = m.exec(event->globalPos());
756 /* beware, here, after exec, e could be a dangling pointer */
757 EntryPtr eptr = ewptr.lock();
758 if(a && notifier && eptr) {
759 if(a->data() == "comment")
760 startEditing(eptr->index, -1);
761 else if(a->data() == "clear")
762 notifier->onUserClearVariations(eptr->index);
763 else if(a->data() == "truncate")
764 notifier->onUserTruncate(eptr->index);
765 else if(a->data() == "promote")
766 notifier->onUserPromoteVariation(eptr->index);
767 else if(a->data() == "remove")
768 notifier->onUserRemoveVariation(eptr->index);
771 return;
775 void Widget::mouseReleaseEvent ( QMouseEvent * /*event*/ ) {
779 void Widget::resizeEvent ( QResizeEvent * event ) {
780 stopEditing();
781 if(event->size().width() != event->oldSize().width()) {
782 layout_width_changed = true;
783 layout();
787 void Widget::layout() {
788 if(layout_pending)
789 return;
791 layout_pending = true;
792 QTimer::singleShot( 0, this, SLOT(doLayout()) );
795 void Widget::doLayout() {
796 layout_time = mSecs();
797 layout_pending = false;
798 layout_max_width = 0;
799 //std::cout << "layout_must_relayout = " << layout_must_relayout << std::endl;
800 int h = layoutHistory(history, BORDER_LEFT, BORDER_TOP, -1, 0, 0, true);
802 QSize s(std::max(entry_size*7, layout_max_width+BORDER_RIGHT),
803 std::max(entry_size*10, h+BORDER_BOTTOM) );
804 setMinimumSize(s);
806 layout_width_changed = false;
807 layout_must_relayout = false;
808 if(layout_goto_selected) {
809 EntryPtr e = fetch(curr_selected);
810 if(e)
811 owner_table->m_scroll_area->ensureVisible( int(e->pos().x() + e->m_rect.width()*0.5),
812 int(e->pos().y() + e->m_rect.height()*0.5) );
813 layout_goto_selected = false;
817 int Widget::layoutHistory(History& array, int at_x, int at_y,
818 int a_prev_turn, int mv_num, int sub_mv_num, bool visible) {
819 int flow_y = at_y;
820 int flow_x = at_x;
821 int nflow_x = at_x;
822 int col_num = 0;
823 int prev_turn = a_prev_turn;
825 for(int i=0;i<(int)array.size();i++) {
826 EntryPtr e = array[i];
828 /* if this is not visible, hide the item and hide all the number/fregna tags */
829 if(!visible) {
830 e->disappear();
831 if(e->number)
832 e->number->disappear();
833 if(e->fregna)
834 e->fregna->disappear();
835 if(e->comment)
836 e->comment->disappear();
838 /* hide the subvariations */
839 for(Variations::iterator it = e->variations.begin(); it != e->variations.end(); ++it)
840 layoutHistory(it->second, 0, 0, e->move_turn, mv_num, sub_mv_num, false);
841 for(Braces::iterator it = e->braces.begin(); it != e->braces.end(); ++it)
842 it->second->disappear();
843 for(VComments::iterator it = e->vcomments.begin(); it != e->vcomments.end(); ++it)
844 it->second->disappear();
845 mv_num++;
846 continue;
849 /* adjust the position if this is paired on the right */
850 bool draw_num = false;
853 if(e->move_turn != prev_turn) {
854 mv_num++;
855 sub_mv_num = 0;
857 else
858 sub_mv_num++;
860 if(layout_style==0) {
861 if(e->move_turn == 0 || i==0 || array[i-1]->childs_height != 0) {
862 if(mv_num>=1 && (e->move_turn != prev_turn || i==0 || array[i-1]->childs_height != 0)) {
863 draw_num = true;
864 flow_x = at_x;
866 else
867 flow_x = (mv_num>=1) ? nflow_x : at_x;
869 else {
870 if(e->move_turn != prev_turn) {
871 flow_x = std::max(flow_x + MIDDLE_PAD, int(MIN_COL_WIDTH*entry_size));
872 flow_y -= entry_size;
874 else
875 flow_x = int(MIN_COL_WIDTH*entry_size);
878 else {
879 if(e->move_turn != prev_turn || i==0 || array[i-1]->childs_height != 0) {
880 col_num = 0;
881 flow_x = at_x;
882 if(mv_num>=1)
883 draw_num = true;
885 else if(col_num == layout_style) {
886 col_num = 0;
887 flow_x = nflow_x;
889 else {
890 flow_y -= entry_size;
891 flow_x = std::max(flow_x + MIDDLE_PAD,
892 at_x + col_num*int(MIN_COL_WIDTH*entry_size));
897 col_num++;
899 /* update the number */
900 if(draw_num) {
901 TextPtr& n = e->number;
902 if(!n) {
903 n = TextPtr(new Text(e.get(), 0, this));
904 if(layout_style==0)
905 n->text = QString::number((mv_num+1)/2)+(mv_num&1 ? "." : ". ...");
906 else
907 n->text = QString::number(mv_num)+
908 (sub_mv_num ? "+"+QString::number(sub_mv_num) : QString())+".";
909 n->needs_update = true;
911 else if( !n->showing() || layout_must_relayout)
912 n->needs_update = true;
914 /* Mh, the number should never change, only appear disappear.
915 should this change, add here the code to enable number changes. */
916 QPoint dest(flow_x, flow_y);
918 if(n->pos() != dest)
919 if(n->visible())
920 n->goTo(dest);
921 else
922 n->moveTo(dest);
923 n->doUpdate();
924 n->appear();
926 flow_x += n->width;
927 nflow_x = flow_x;
929 else if(e->number)
930 e->number->disappear();
933 /* update the entry */
934 QPoint dest(flow_x, flow_y);
935 if(e->pos() != dest)
936 if(e->visible())
937 e->goTo(dest);
938 else
939 e->moveTo(dest);
940 if( !e->showing() || layout_must_relayout)
941 e->needs_update = true;
942 e->doUpdate();
943 e->appear();
944 e->childs_height = 0;
945 flow_x += e->m_rect.width();
948 /* Update the fregna. The fregna is visible if there are subvariations in this
949 entry, or if this entry is the first one of a variation where the remaining
950 entries are hidden and that contains the current position */
951 bool expandable = !e->variations.empty() || e->comment;
952 bool sel = (e->hide_next && e->index<curr_selected)
953 || (!e->expanded && expandable &&
954 e->index<curr_selected && !(e->index.next()<=curr_selected));
955 if(expandable || sel ) {
956 if(!e->fregna)
957 e->fregna = TextPtr(new Text(e.get(), 1, this));
959 /* update the data, if needed */
960 TextPtr f = e->fregna;
961 const char *text = (sel||!e->expanded||e->hide_next) ? "[+]" : "[-]";
962 if(f->text != text || f->selected != sel) {
963 f->text = text;
964 f->selected = sel;
965 f->needs_update = true;
967 else if( !f->showing() || layout_must_relayout)
968 f->needs_update = true;
970 QPoint dest(flow_x, flow_y);
972 if(f->pos() != dest)
973 if(f->visible())
974 f->goTo(QPoint(flow_x, flow_y));
975 else
976 f->moveTo(QPoint(flow_x, flow_y));
977 f->doUpdate();
978 f->appear();
980 flow_x += f->width;
982 else if(e->fregna)
983 e->fregna->disappear();
985 /* update the flow information */
986 flow_y += entry_size;
987 layout_max_width = std::max(flow_x, layout_max_width);
988 int prev_pos = flow_y;
990 /* update the comment */
991 if(e->comment) {
992 CommentPtr c = e->comment;
994 if(e->expanded && !e->hide_next) {
995 QPoint dest(at_x + COMMENT_INDENTATION, flow_y);
997 if(c->pos() != dest ) {
998 if(c->visible())
999 c->goTo(dest);
1000 else
1001 c->moveTo(dest);
1002 c->needs_update = true;
1004 else if( !c->showing() || layout_width_changed || layout_must_relayout)
1005 c->needs_update = true;
1006 c->doUpdate();
1007 c->appear();
1008 flow_y += c->height;
1010 else
1011 c->disappear();
1014 /* update the variations */
1015 for(Variations::iterator it = e->variations.begin(); it != e->variations.end(); ++it) {
1016 int old_pos = flow_y;
1018 /* update the variation's comment */
1019 if(e->vcomments.count(it->first)) {
1020 CommentPtr c = e->vcomments[it->first];
1022 if(e->expanded && !e->hide_next) {
1023 QPoint dest(at_x + VAR_INDENTATION + COMMENT_INDENTATION, flow_y);
1025 if( !c->showing() || layout_must_relayout)
1026 c->needs_update = true;
1027 if(c->pos() != dest)
1028 if(c->visible())
1029 c->goTo(dest);
1030 else
1031 c->moveTo(dest);
1032 c->doUpdate();
1033 c->appear();
1034 flow_y += c->height;
1036 else
1037 c->disappear();
1040 /* layout the variation */
1041 flow_y = layoutHistory(it->second, at_x + VAR_INDENTATION, flow_y,
1042 e->move_turn, mv_num, sub_mv_num, e->expanded && !e->hide_next);
1044 /* update the brace of the variation */
1045 BracePtr b = e->braces[it->first];
1046 if(e->expanded && !e->hide_next) {
1047 b->depth = e->index.nested.size();
1048 b->setHeight((it->second.size() && it->second[0]->hide_next) ? entry_size : flow_y - old_pos);
1049 b->width = VAR_INDENTATION;
1050 if(b->visible())
1051 b->goTo(QPoint(at_x, old_pos));
1052 else
1053 b->moveTo(QPoint(at_x, old_pos));
1054 b->appear();
1056 else
1057 b->disappear();
1060 e->childs_height = flow_y - prev_pos;
1062 if(e->hide_next)
1063 visible = false;
1064 prev_turn = e->move_turn;
1066 return flow_y;
1069 QPixmap Widget::getPixmap(const QString& s, bool selected) {
1070 QString k = selected ? s+"_sel":s;
1071 if(loaded_pixmaps.contains(k))
1072 return loaded_pixmaps[k];
1074 QString iconFile = KStandardDirs::locate("appdata", "piece_icons/" + s + ".png");
1075 QImage img(iconFile);
1077 if(selected) {
1078 QPainter p(&img);
1079 p.setCompositionMode(QPainter::CompositionMode_SourceAtop );
1080 p.fillRect(0,0,img.width(), img.height(), m_settings->select_color);
1082 return loaded_pixmaps[k] = QPixmap::fromImage(img.scaled(m_settings->mv_fmetrics.ascent(),
1083 m_settings->mv_fmetrics.ascent(),
1084 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
1087 void Widget::setComment(EntryPtr e, int v, const QString& comment) {
1088 if(comment.isEmpty()) {
1089 if(v == -1) {
1090 if(e->comment)
1091 e->comment = CommentPtr();
1093 else {
1094 if(e->vcomments.count(v))
1095 e->vcomments.erase(v);
1098 else {
1099 CommentPtr p;
1101 if(v == -1) {
1102 if(!e->comment)
1103 e->comment = CommentPtr(new Comment(e.get(), this));
1104 p = e->comment;
1106 else {
1107 if(!e->variations.count(v))
1108 return;
1110 if(!e->vcomments.count(v))
1111 p = e->vcomments[v] = CommentPtr(new Comment(e.get(), this, v));
1112 else
1113 p = e->vcomments[v];
1116 if(p->text == comment)
1117 return;
1118 p->text = comment;
1119 p->needs_update = true;
1122 layout();
1125 void Widget::setComment(const Index& index, const QString& comment) {
1126 EntryPtr e = fetch(index);
1127 if(!e) {
1128 std::cout << "--> Error in Widget::setComment! Invalid index " << index << std::endl;
1129 return;
1131 setComment(e, -1, comment);
1134 void Widget::setVComment(const Index& index, int v, const QString& comment) {
1135 EntryPtr e = fetch(index);
1136 if(!e || !e->variations.count(v)) {
1137 std::cout << "--> Error in Widget::setVComment! Invalid index " << index << std::endl;
1138 return;
1140 setComment(e, v, comment);
1143 void Widget::setMove(const Index& index,
1144 int turn, const QString& move, const QString& comment) {
1145 DecoratedMove mv;
1146 #if 1
1147 mv.push_back(MovePart(move));
1148 #else
1149 //TODO: move this code in some other place, it really should not stay here
1150 QRegExp reg("[KQRBNP]");
1151 int x = 0;
1152 while(reg.indexIn(move, x) != -1) {
1153 if(reg.pos() > x)
1154 mv.push_back(MovePart(MoveText, move.mid(x, reg.pos()-x)));
1155 mv.push_back(MovePart(MovePixmap, reg.cap().toLower()));
1156 x = reg.pos() + reg.matchedLength();
1158 if(x<move.length())
1159 mv.push_back(MovePart(MoveText, move.mid(x)));
1160 #endif
1161 setMove(index, turn, mv, comment);
1164 void Widget::setMove(const Index& index,
1165 int turn, const DecoratedMove& move, const QString& comment) {
1166 EntryPtr e = fetch(index);
1167 if(e) {
1168 e->move_turn = turn;
1169 e->move = move;
1170 e->needs_update = true;
1171 setComment(e, -1, comment);
1172 layout();
1173 return;
1176 int at;
1177 History *vec = fetchRef(index.prev(), &at);
1178 if(!vec) {
1179 std::cout << "--> Error in Widget::setMove! Invalid index " << index << std::endl;
1180 return;
1183 if(index.nested.size() && index.nested[index.nested.size()-1].num_moves == 0) {
1184 History var;
1185 int v = index.nested[index.nested.size()-1].variation;
1186 var.push_back(e = EntryPtr( new Entry(turn, move, index, this)) );
1187 (*vec)[at]->variations[v] = var;
1188 (*vec)[at]->braces[v] = BracePtr( new Brace( (*vec)[at].get(), v, this) );
1190 else
1191 vec->push_back(e = EntryPtr( new Entry(turn, move, index, this)) );
1193 setComment(e, -1, comment);
1194 e->hide();
1195 layout();
1198 void Widget::remove(const Index& index) {
1200 if(index.atVariationStart() ) {
1201 EntryPtr e = fetch(index.prev());
1202 if(!e)
1203 return;
1205 int v = index.nested[index.nested.size()-1].variation;
1206 if(!e->variations.count(v))
1207 return;
1209 e->variations.erase(v);
1210 e->braces.erase(v);
1211 e->vcomments.erase(v);
1213 else {
1214 int at;
1215 History *vec = fetchRef(index, &at);
1216 if(!vec)
1217 return;
1219 while((int)vec->size() > at)
1220 vec->pop_back();
1222 layout();
1225 void Widget::fixIndices(const Index& ix) {
1226 int at;
1227 History *vec = fetchRef(ix, &at);
1228 if(!vec) {
1229 std::cout << "--> Error in Widget::fixIndices, invalid index "<<ix << std::endl;
1230 return;
1232 Index index = ix;
1233 for(int i=at;i<(int)vec->size();i++) {
1234 EntryPtr e = (*vec)[i];
1235 e->index = index;
1237 for(Variations::const_iterator it = e->variations.begin();
1238 it != e->variations.end(); ++it)
1239 fixIndices(index.next(it->first));
1240 index = index.next();
1244 void Widget::promoteVariation(const Index& ix, int v) {
1245 int at;
1246 History *vec = fetchRef(ix, &at);
1247 if(!vec) {
1248 std::cout << "--> Error in Widget::promoteVariation, invalid index "<<ix << std::endl;
1249 return;
1252 History vold = (*vec)[at]->variations[v];
1253 History vnew;
1254 for(int i=at+1; i<(int)vec->size(); i++)
1255 vnew.push_back((*vec)[i]);
1256 while((int)vec->size()>at+1)
1257 vec->pop_back();
1258 for(int i=0; i<(int)vold.size(); i++)
1259 vec->push_back(vold[i]);
1260 (*vec)[at]->variations[v] = vnew;
1262 Q_ASSERT((int)vec->size()>at+1);
1263 (*vec)[at+1]->hide_next = false;
1265 fixIndices(ix.next());
1266 fixIndices(ix.next(v));
1268 curr_selected = curr_selected.flipVariation(ix, v);
1269 curr_highlight = curr_highlight.flipVariation(ix, v);
1270 layout();
1273 void Widget::select(const Index& index) {
1274 if(curr_selected == index)
1275 return;
1276 EntryPtr e = fetch(index);
1277 EntryPtr olde = fetch(curr_selected);
1278 if(olde) {
1279 olde->selected = false;
1280 olde->needs_update = true;
1282 if(e) {
1283 e->selected = true;
1284 e->needs_update = true;
1285 layout_goto_selected = true;
1287 curr_selected = index;
1288 layout();
1291 //END Widget-------------------------------------------------------------------
1293 } //end namespace MoveList