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.
10 #include <QApplication>
12 #include <QPaintEvent>
13 #include <QMouseEvent>
16 #include <QHBoxLayout>
17 #include <QVBoxLayout>
18 #include <QScrollArea>
19 #include <QToolButton>
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"
36 #define MARGIN_RIGHT 4
38 #define MARGIN_BOTTOM 0
40 #define COMMENT_INDENTATION 4
41 #define VAR_INDENTATION 16
43 #define BORDER_RIGHT 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))
62 Widget
*m
= dynamic_cast<Widget
*>(topLevelCanvas());
63 if(!m
->m_settings
->anim_enabled
|| !m
->m_settings
->anim_hideshow
)
68 time_opacity
= m
->layout_time
;
73 void FancyItem::disappear() {
74 if((time_opacity
!=-1 && target_opacity
== 0)
75 || !visible() || opacity() == 0)
78 Widget
*m
= dynamic_cast<Widget
*>(topLevelCanvas());
80 if(!m
->m_settings
->anim_enabled
|| !m
->m_settings
->anim_hideshow
)
85 time_opacity
= m
->layout_time
;
90 void FancyItem::goTo(QPoint p
) {
91 if((time_pos
!=-1 && target_pos
== p
) || (time_pos
==-1 && pos() == p
))
94 Widget
*m
= dynamic_cast<Widget
*>(topLevelCanvas());
95 if(!m
->m_settings
->anim_enabled
|| !m
->m_settings
->anim_moving
)
100 time_pos
= m
->layout_time
;
105 void FancyItem::setHighlight(bool h
) {
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;
116 old_highlight
= highlighted
? 255 : 0;
117 target_highlight
= h
? 255 : 0;
119 time_highlight
= m
->mSecs();
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
;
129 curr_highlight
= target_highlight
;
133 curr_highlight
= int(target_highlight
*fact
+ old_highlight
*(1-fact
));
136 if(time_opacity
!= -1) {
137 float fact
= (time
- time_opacity
) / m
->m_settings
->anim_time
;
139 setOpacity(target_opacity
);
140 setVisible(target_opacity
!= 0);
144 setOpacity( int(target_opacity
*fact
+ old_opacity
*(1-fact
)) );
149 float fact
= (time
- time_pos
) / m
->m_settings
->anim_time
;
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));
164 bool FancyItem::layered() const {
168 //END FancyItem----------------------------------------------------------------
171 //BEGIN Brace------------------------------------------------------------------
173 void Brace::setHeight(int h
) {
175 if((animated() && target_height
== h
) || height
== h
)
178 Widget
*m
= dynamic_cast<Widget
*>(topLevelCanvas());
179 if(!m
->m_settings
->anim_enabled
|| !m
->m_settings
->anim_moving
) {
186 time_height
= m
->layout_time
;
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
;
197 height
= target_height
;
201 height
= int(target_height
*fact
+ old_height
*(1-fact
) + 0.5);
204 FancyItem::advance(time
);
207 void Brace::paint (QPainter
*p
) {
208 Widget
*m
= dynamic_cast<Widget
*>(topLevelCanvas());
209 if(height
< m
->entry_size
)
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);
216 p
->setBrush(QColor(255,192,224));
217 q
= QColor(128,0,64);
220 p
->setBrush(QColor(192,255,224));
221 q
= QColor(0,128,64);
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
;
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
));
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 () {
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;
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
,
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 () {
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;
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
;
337 for(int i
=0;i
<(int)move
.size();i
++) {
338 if(move
[i
].m_type
== MovePart::Text
) {
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
;
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 () {
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;
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
)
447 , comment_editor(NULL
)
448 , layout_pending(false)
450 , layout_goto_selected(false)
451 , layout_width_changed(true)
452 , layout_must_relayout(true)
455 , m_settings(new Settings
) {
458 setSizePolicy ( QSizePolicy::MinimumExpanding
, QSizePolicy::Minimum
);
460 setMouseTracking(true);
469 void Widget::reset() {
470 curr_highlight
= Index(-1);
471 curr_selected
= Index(-1);
473 delete comment_editor
;
474 comment_editor
= NULL
;
478 mv
.push_back(MovePart(QString("Mainline:")));
480 history
.push_back( EntryPtr(new Entry(-1, mv
, Index(0), this)) );
485 EntryPtr
Widget::fetch(const Index
& ix
) {
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 )
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 )
506 retv
= it
->second
[ix
.nested
[i
].num_moves
];
507 if(idx
) *idx
= ix
.nested
[i
].num_moves
;
512 Notifier
* Widget::getNotifier() {
516 void Widget::setNotifier(Notifier
* n
, bool detach_prev
){
517 if(detach_prev
&& notifier
&& notifier
!= n
)
518 notifier
->onDetachNotifier();
522 void Widget::settingsChanged() {
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;
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
;
548 if(curr_highlight
== e
->index
&& curr_highlight_type
==-1)
550 e
->setHighlight(true);
551 curr_highlight
= e
->index
;
552 curr_highlight_type
= -1;
555 if(curr_highlight
== f
->entry
->index
&& curr_highlight_type
==-2)
557 f
->setHighlight(true);
558 curr_highlight
= f
->entry
->index
;
559 curr_highlight_type
= -2;
562 if( (curr_highlight
==c
->entry
->index
) &&
563 ((c
->variation
==-1) ? (curr_highlight_type
== -3) :
564 (curr_highlight_type
== -1000-c
->variation
) ) )
566 c
->setHighlight(true);
567 curr_highlight
= c
->entry
->index
;
568 curr_highlight_type
= (c
->variation
==-1) ? -3 : (-1000-c
->variation
);
571 if(curr_highlight
== b
->entry
->index
&& curr_highlight_type
==b
->variation
)
573 b
->setHighlight(true);
574 curr_highlight
= b
->entry
->index
;
575 curr_highlight_type
= b
->variation
;
578 curr_highlight
= Index(-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
) {
597 EntryPtr e
= fetch(i
);
599 kError() << "Invalid index " << i
;
603 CommentPtr c
= v
== -1 ? e
->comment
: (e
->vcomments
.count(v
) ?
604 e
->vcomments
[v
] : CommentPtr());
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
) {
634 void Widget::stopEditing() {
635 EntryPtr e
= edited_comment
.lock();
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
);
643 notifier
->onUserSetVComment(e
->index
, edited_comment_variation
, c
);
645 edited_comment
.reset();
648 comment_editor
->deleteLater();
649 comment_editor
= NULL
;
653 void Widget::mousePressEvent ( QMouseEvent
* event
) {
656 KGameCanvasItem
*i
= itemAt(event
->pos());
660 Text
*t
= dynamic_cast<Text
*>(i
);
661 if(t
&& t
->type
== 1) {
664 e
->hide_next
= false;
668 e
->expanded
= !e
->expanded
;
673 Brace
*b
= dynamic_cast<Brace
*>(i
);
675 if(event
->button() == Qt::LeftButton
) {
677 EntryPtr first
= e
->variations
[b
->variation
][0];
678 first
->hide_next
= !first
->hide_next
;
681 else if(event
->button() == Qt::RightButton
) {
684 a
= m
.addAction(KIcon("pen"), i18n("&Set Comment"));
685 a
->setData("comment");
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
));
710 Comment
*c
= dynamic_cast<Comment
*>(i
);
712 startEditing(c
->entry
->index
, c
->variation
);
716 Entry
*e
= dynamic_cast<Entry
*>(i
);
718 if(event
->button() == Qt::LeftButton
) {
720 notifier
->onUserSelectMove(e
->index
);
722 else if(event
->button() == Qt::RightButton
) {
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());
730 a
= m
.addAction(KIcon("cut"), i18n("&Truncate"));
731 a
->setEnabled(fetch(e
->index
.next()));
732 a
->setData("truncate");
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
);
763 void Widget::mouseReleaseEvent ( QMouseEvent
* /*event*/ ) {
767 void Widget::resizeEvent ( QResizeEvent
* event
) {
769 if(event
->size().width() != event
->oldSize().width()) {
770 layout_width_changed
= true;
775 void Widget::layout() {
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
) );
794 layout_width_changed
= false;
795 layout_must_relayout
= false;
796 if(layout_goto_selected
) {
797 EntryPtr e
= fetch(curr_selected
);
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
) {
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 */
820 e
->number
->disappear();
822 e
->fregna
->disappear();
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();
837 /* adjust the position if this is paired on the right */
838 bool draw_num
= false;
841 if(e
->move_turn
!= prev_turn
) {
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)) {
855 flow_x
= (mv_num
>=1) ? nflow_x
: at_x
;
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
;
863 flow_x
= int(MIN_COL_WIDTH
*entry_size
);
867 if(e
->move_turn
!= prev_turn
|| i
==0 || array
[i
-1]->childs_height
!= 0) {
873 else if(col_num
== layout_style
) {
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
));
887 /* update the number */
889 TextPtr
& n
= e
->number
;
891 n
= TextPtr(new Text(e
.get(), 0, this));
893 n
->text
= QString::number((mv_num
+1)/2)+(mv_num
&1 ? "." : ". ...");
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
);
918 e
->number
->disappear();
921 /* update the entry */
922 QPoint
dest(flow_x
, flow_y
);
928 if( !e
->showing() || layout_must_relayout
)
929 e
->needs_update
= true;
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
) {
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
) {
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
);
962 f
->goTo(QPoint(flow_x
, flow_y
));
964 f
->moveTo(QPoint(flow_x
, flow_y
));
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 */
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
) {
990 c
->needs_update
= true;
992 else if( !c
->showing() || layout_width_changed
|| layout_must_relayout
)
993 c
->needs_update
= true;
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
)
1022 flow_y
+= c
->height
;
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
;
1039 b
->goTo(QPoint(at_x
, old_pos
));
1041 b
->moveTo(QPoint(at_x
, old_pos
));
1048 e
->childs_height
= flow_y
- prev_pos
;
1052 prev_turn
= e
->move_turn
;
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
);
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()) {
1079 e
->comment
= CommentPtr();
1082 if(e
->vcomments
.count(v
))
1083 e
->vcomments
.erase(v
);
1091 e
->comment
= CommentPtr(new Comment(e
.get(), this));
1095 if(!e
->variations
.count(v
))
1098 if(!e
->vcomments
.count(v
))
1099 p
= e
->vcomments
[v
] = CommentPtr(new Comment(e
.get(), this, v
));
1101 p
= e
->vcomments
[v
];
1104 if(p
->text
== comment
)
1107 p
->needs_update
= true;
1113 void Widget::setComment(const Index
& index
, const QString
& comment
) {
1114 EntryPtr e
= fetch(index
);
1116 kError() << "Invalid index" << index
;
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
;
1128 setComment(e
, v
, comment
);
1131 void Widget::setMove(const Index
& index
,
1132 int turn
, const QString
& move
, const QString
& comment
) {
1135 mv
.push_back(MovePart(move
));
1137 //TODO: move this code in some other place, it really should not stay here
1138 QRegExp
reg("[KQRBNP]");
1140 while(reg
.indexIn(move
, x
) != -1) {
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();
1147 mv
.push_back(MovePart(MoveText
, move
.mid(x
)));
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
);
1156 e
->move_turn
= turn
;
1158 e
->needs_update
= true;
1159 setComment(e
, -1, comment
);
1165 History
*vec
= fetchRef(index
.prev(), &at
);
1167 kError() << "Invalid index" << index
;
1171 if(index
.nested
.size() && index
.nested
.back().num_moves
== 0) {
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) );
1179 vec
->push_back(e
= EntryPtr( new Entry(turn
, move
, index
, this)) );
1181 setComment(e
, -1, comment
);
1186 void Widget::remove(const Index
& index
) {
1188 if(index
.atVariationStart() ) {
1189 EntryPtr e
= fetch(index
.prev());
1193 int v
= index
.nested
.back().variation
;
1194 if(!e
->variations
.count(v
))
1197 e
->variations
.erase(v
);
1199 e
->vcomments
.erase(v
);
1203 History
*vec
= fetchRef(index
, &at
);
1207 while((int)vec
->size() > at
)
1213 void Widget::fixIndices(const Index
& ix
) {
1215 History
*vec
= fetchRef(ix
, &at
);
1217 kError() << "Invalid index" << ix
;
1221 for(int i
=at
;i
<(int)vec
->size();i
++) {
1222 EntryPtr e
= (*vec
)[i
];
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
) {
1234 History
*vec
= fetchRef(ix
, &at
);
1236 kError() << "Invalid index" << ix
;
1240 History vold
= (*vec
)[at
]->variations
[v
];
1242 for(int i
=at
+1; i
<(int)vec
->size(); i
++)
1243 vnew
.push_back((*vec
)[i
]);
1244 while((int)vec
->size()>at
+1)
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
);
1261 void Widget::select(const Index
& index
) {
1262 if(curr_selected
== index
)
1264 EntryPtr e
= fetch(index
);
1265 EntryPtr olde
= fetch(curr_selected
);
1267 olde
->selected
= false;
1268 olde
->needs_update
= true;
1272 e
->needs_update
= true;
1273 layout_goto_selected
= true;
1275 curr_selected
= index
;
1279 void Widget::setLoaderTheme(const ThemeInfo
& theme
) {
1280 m_loader
.setTheme(theme
);
1283 //END Widget-------------------------------------------------------------------
1285 } //end namespace MoveList