You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
digikam/digikam/digikam/timelinewidget.cpp

1719 lines
51 KiB

/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2007-12-08
* Description : a widget to display date and time statistics of pictures
*
* Copyright (C) 2007-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
// C++ includes.
#include <cmath>
// TQt includes.
#include <tqpainter.h>
#include <tqpixmap.h>
#include <tqpen.h>
// KDE includes.
#include <kcursor.h>
#include <tdelocale.h>
#include <tdeglobal.h>
#include <kcalendarsystem.h>
#include <tdeglobalsettings.h>
// Local includes.
#include "ddebug.h"
#include "themeengine.h"
#include "timelinewidget.h"
#include "timelinewidget.moc"
namespace Digikam
{
class TimeLineWidgetPriv
{
public :
typedef TQPair<int, int> YearRefPair; // Year + a reference association (Month or week or day)
typedef TQPair<int, TimeLineWidget::SelectionMode> StatPair; // Statistic value + selection status.
public:
TimeLineWidgetPriv()
{
validMouseEvent = false;
selMouseEvent = false;
maxCountByDay = 1;
maxCountByWeek = 1;
maxCountByMonth = 1;
maxCountByYear = 1;
topMargin = 3;
bottomMargin = 20;
barWidth = 20;
startPos = 96;
nbItems = 10;
timeUnit = TimeLineWidget::Month;
scaleMode = TimeLineWidget::LinScale;
calendar = TDEGlobal::locale()->calendar();
}
bool validMouseEvent; // Current mouse enter event is valid to set cursor position or selection.
bool selMouseEvent; // Current mouse enter event is about to make a selection.
int maxCountByDay;
int maxCountByWeek;
int maxCountByMonth;
int maxCountByYear;
int topMargin;
int bottomMargin;
int barWidth;
int nbItems;
int startPos;
TQDateTime refDateTime; // Reference date-time used to draw histogram from middle of widget.
TQDateTime cursorDateTime; // Current date-time used to draw focus cursor.
TQDateTime minDateTime; // Higher date on histogram.
TQDateTime maxDateTime; // Lower date on histogram.
TQDateTime selStartDateTime;
TQDateTime selMinDateTime; // Lower date available on histogram.
TQDateTime selMaxDateTime; // Higher date available on histogram.
TQPixmap pixmap; // Used for widget double buffering.
TQMap<YearRefPair, StatPair> dayStatMap; // Store Days count statistics.
TQMap<YearRefPair, StatPair> weekStatMap; // Store Weeks count statistics.
TQMap<YearRefPair, StatPair> monthStatMap; // Store Month count statistics.
TQMap<int, StatPair> yearStatMap; // Store Years count statistics.
const KCalendarSystem *calendar;
TimeLineWidget::TimeUnit timeUnit;
TimeLineWidget::ScaleMode scaleMode;
};
TimeLineWidget::TimeLineWidget(TQWidget *parent)
: TQWidget(parent, 0, TQt::WDestructiveClose)
{
d = new TimeLineWidgetPriv;
setBackgroundMode(TQt::NoBackground);
setMouseTracking(true);
setMinimumWidth(256);
setMinimumHeight(192);
TQDateTime ref = TQDateTime::currentDateTime();
setCursorDateTime(ref);
setRefDateTime(ref);
connect(ThemeEngine::instance(), TQ_SIGNAL(signalThemeChanged()),
this, TQ_SLOT(slotThemeChanged()));
}
TimeLineWidget::~TimeLineWidget()
{
delete d;
}
void TimeLineWidget::setTimeUnit(TimeUnit timeUnit)
{
d->timeUnit = timeUnit;
setCursorDateTime(cursorDateTime());
setRefDateTime(cursorDateTime());
}
TimeLineWidget::TimeUnit TimeLineWidget::timeUnit() const
{
return d->timeUnit;
}
void TimeLineWidget::setScaleMode(ScaleMode scaleMode)
{
d->scaleMode = scaleMode;
updatePixmap();
update();
}
TimeLineWidget::ScaleMode TimeLineWidget::scaleMode() const
{
return d->scaleMode;
}
int TimeLineWidget::totalIndex()
{
if (d->minDateTime.isNull() || d->maxDateTime.isNull())
return 0;
int i = 0;
TQDateTime dt = d->minDateTime;
do
{
dt = nextDateTime(dt);
i++;
}
while(dt < d->maxDateTime);
return i;
}
int TimeLineWidget::indexForDateTime(const TQDateTime& date)
{
if (d->minDateTime.isNull() || d->maxDateTime.isNull() || date.isNull())
return 0;
int i = 0;
TQDateTime dt = d->minDateTime;
do
{
dt = nextDateTime(dt);
i++;
}
while(dt < date);
return i;
}
int TimeLineWidget::indexForRefDateTime()
{
return (indexForDateTime(d->refDateTime));
}
int TimeLineWidget::indexForCursorDateTime()
{
return (indexForDateTime(d->cursorDateTime));
}
void TimeLineWidget::setCurrentIndex(int index)
{
if (d->minDateTime.isNull() || d->maxDateTime.isNull())
return;
int i = 0;
TQDateTime dt = d->minDateTime;
do
{
dt = nextDateTime(dt);
i++;
}
while(i <= index);
setRefDateTime(dt);
}
void TimeLineWidget::setCursorDateTime(const TQDateTime& dateTime)
{
TQDateTime dt = dateTime;
dt.setTime(TQTime(0, 0, 0, 0));
switch(d->timeUnit)
{
case Week:
{
// Go to the first day of week.
int weekYear = 0;
int weekNb = d->calendar->weekNumber(dt.date(), &weekYear);
dt = firstDayOfWeek(weekYear, weekNb);
break;
}
case Month:
{
// Go to the first day of month.
dt.setDate(TQDate(dt.date().year(), dt.date().month(), 1));
break;
}
case Year:
{
// Go to the first day of year.
dt.setDate(TQDate(dt.date().year(), 1, 1));
break;
}
default:
break;
}
if (d->cursorDateTime == dt)
return;
d->cursorDateTime = dt;
emit signalCursorPositionChanged();
}
TQDateTime TimeLineWidget::cursorDateTime() const
{
return d->cursorDateTime;
}
int TimeLineWidget::cursorInfo(TQString& infoDate)
{
SelectionMode selected;
TQDateTime dt = cursorDateTime();
switch(d->timeUnit)
{
case Day:
{
infoDate = TDEGlobal::locale()->formatDate(dt.date());
break;
}
case Week:
{
infoDate = i18n("Week #%1 - %2 %3")
.arg(d->calendar->weekNumber(dt.date()))
.arg(d->calendar->monthName(dt.date()))
.arg(d->calendar->yearString(dt.date(), false));
break;
}
case Month:
{
infoDate = TQString("%1 %2")
.arg(d->calendar->monthName(dt.date()))
.arg(d->calendar->yearString(dt.date(), false));
break;
}
case Year:
{
infoDate = d->calendar->yearString(dt.date(), false);
break;
}
}
return statForDateTime(dt, selected);
}
void TimeLineWidget::setRefDateTime(const TQDateTime& dateTime)
{
TQDateTime dt = dateTime;
dt.setTime(TQTime(0, 0, 0, 0));
switch(d->timeUnit)
{
case Week:
{
// Go to the first day of week.
int dayWeekOffset = (-1) * (d->calendar->dayOfWeek(dt.date()) - 1);
dt = dt.addDays(dayWeekOffset);
break;
}
case Month:
{
// Go to the first day of month.
dt.setDate(TQDate(dt.date().year(), dt.date().month(), 1));
break;
}
case Year:
{
// Go to the first day of year.
dt.setDate(TQDate(dt.date().year(), 1, 1));
break;
}
default:
break;
}
d->refDateTime = dt;
updatePixmap();
update();
emit signalRefDateTimeChanged();
}
void TimeLineWidget::slotResetSelection()
{
resetSelection();
updatePixmap();
update();
}
void TimeLineWidget::resetSelection()
{
TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
for (it = d->dayStatMap.begin() ; it != d->dayStatMap.end(); ++it)
it.data().second = Unselected;
for (it = d->weekStatMap.begin() ; it != d->weekStatMap.end(); ++it)
it.data().second = Unselected;
for (it = d->monthStatMap.begin() ; it != d->monthStatMap.end(); ++it)
it.data().second = Unselected;
TQMap<int, TimeLineWidgetPriv::StatPair>::iterator it2;
for (it2 = d->yearStatMap.begin() ; it2 != d->yearStatMap.end(); ++it2)
it2.data().second = Unselected;
}
void TimeLineWidget::setSelectedDateRange(const DateRangeList& list)
{
if (list.isEmpty())
return;
resetSelection();
TQDateTime start, end, dt;
DateRangeList::const_iterator it;
for (it = list.begin() ; it != list.end(); ++it)
{
start = (*it).first;
end = (*it).second;
if (end > start)
{
dt = start;
do
{
setDateTimeSelected(dt, Selected);
dt = dt.addDays(1);
}
while (dt < end);
}
}
updatePixmap();
update();
}
DateRangeList TimeLineWidget::selectedDateRange(int& totalCount)
{
// We will parse all selections done on Days stats map.
DateRangeList list;
totalCount = 0;
TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it3;
TQDateTime sdt, edt;
TQDate date;
for (it3 = d->dayStatMap.begin() ; it3 != d->dayStatMap.end(); ++it3)
{
if (it3.data().second == Selected)
{
date = TQDate(it3.key().first, 1, 1);
date = date.addDays(it3.key().second-1);
sdt = TQDateTime(date);
edt = sdt.addDays(1);
list.append(DateRange(sdt, edt));
totalCount += it3.data().first;
}
}
DateRangeList::iterator it, it2;
/*
for (it = list.begin() ; it != list.end(); ++it)
DDebug() << (*it).first.date().toString(TQt::ISODate) << " :: "
<< (*it).second.date().toString(TQt::ISODate) << endl;
DDebug() << "Total Count of Items = " << totalCount << endl;
*/
// Group contiguous date ranges to optimize query on database.
DateRangeList list2;
TQDateTime first, second, first2, second2;
for (it = list.begin() ; it != list.end(); ++it)
{
first = (*it).first;
second = (*it).second;
it2 = it;
do
{
++it2;
if (it2 != list.end())
{
first2 = (*it2).first;
second2 = (*it2).second;
if (first2 == second)
{
second = second2;
++it;
}
else
break;
}
}
while(it2 != list.end());
list2.append(DateRange(first, second));
}
/*
for (it = list2.begin() ; it != list2.end(); ++it)
DDebug() << (*it).first.date().toString(TQt::ISODate) << " :: "
<< (*it).second.date().toString(TQt::ISODate) << endl;
*/
return list2;
}
void TimeLineWidget::slotDatesMap(const TQMap<TQDateTime, int>& datesStatMap)
{
// Clear all counts in all stats maps before to update it. Do not clear selections.
TQMap<int, TimeLineWidgetPriv::StatPair>::iterator it_iP;
for ( it_iP = d->yearStatMap.begin() ; it_iP != d->yearStatMap.end(); ++it_iP )
it_iP.data().first = 0;
TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it_YP;
for ( it_YP = d->monthStatMap.begin() ; it_YP != d->monthStatMap.end(); ++it_YP )
it_YP.data().first = 0;
for ( it_YP = d->weekStatMap.begin() ; it_YP != d->weekStatMap.end(); ++it_YP )
it_YP.data().first = 0;
for ( it_YP = d->dayStatMap.begin() ; it_YP != d->dayStatMap.end(); ++it_YP )
it_YP.data().first = 0;
// Parse all new Date stamp and store histogram stats relevant in maps.
int count;
TQMap<TQDateTime, int>::const_iterator it;
if (datesStatMap.isEmpty())
{
d->minDateTime = TQDateTime();
d->maxDateTime = TQDateTime();
}
else
{
d->minDateTime = datesStatMap.begin().key();
d->maxDateTime = datesStatMap.begin().key();
}
for ( it = datesStatMap.begin(); it != datesStatMap.end(); ++it )
{
if (it.key() > d->maxDateTime)
d->maxDateTime = it.key();
if (it.key() < d->minDateTime)
d->minDateTime = it.key();
int year = it.key().date().year();
int month = it.key().date().month();
int day = d->calendar->dayOfYear(it.key().date());
int yearForWeek = year; // Used with week shared between 2 years decade (Dec/Jan).
int week = d->calendar->weekNumber(it.key().date(), &yearForWeek);
// Stats Years values.
it_iP = d->yearStatMap.find(year);
if ( it_iP == d->yearStatMap.end() )
{
count = it.data();
d->yearStatMap.insert( year, TimeLineWidgetPriv::StatPair(count, Unselected) );
}
else
{
count = it_iP.data().first + it.data();
d->yearStatMap.replace( year, TimeLineWidgetPriv::StatPair(count, it_iP.data().second) );
}
if (d->maxCountByYear < count)
d->maxCountByYear = count;
// Stats Months values.
it_YP = d->monthStatMap.find(TimeLineWidgetPriv::YearRefPair(year, month));
if ( it_YP == d->monthStatMap.end() )
{
count = it.data();
d->monthStatMap.insert( TimeLineWidgetPriv::YearRefPair(year, month),
TimeLineWidgetPriv::StatPair(count, Unselected) );
}
else
{
count = it_YP.data().first + it.data();
d->monthStatMap.replace( TimeLineWidgetPriv::YearRefPair(year, month),
TimeLineWidgetPriv::StatPair(count, it_YP.data().second) );
}
if (d->maxCountByMonth < count)
d->maxCountByMonth = count;
// Stats Weeks values.
it_YP = d->weekStatMap.find(TimeLineWidgetPriv::YearRefPair(yearForWeek, week));
if ( it_YP == d->weekStatMap.end() )
{
count = it.data();
d->weekStatMap.insert( TimeLineWidgetPriv::YearRefPair(yearForWeek, week),
TimeLineWidgetPriv::StatPair(count, Unselected) );
}
else
{
count = it_YP.data().first + it.data();
d->weekStatMap.replace( TimeLineWidgetPriv::YearRefPair(yearForWeek, week),
TimeLineWidgetPriv::StatPair(count, it_YP.data().second) );
}
if (d->maxCountByWeek < count)
d->maxCountByWeek = count;
// Stats Days values.
it_YP = d->dayStatMap.find(TimeLineWidgetPriv::YearRefPair(year, day));
if ( it_YP == d->dayStatMap.end() )
{
count = it.data();
d->dayStatMap.insert( TimeLineWidgetPriv::YearRefPair(year, day),
TimeLineWidgetPriv::StatPair(count, Unselected) );
}
else
{
count = it_YP.data().first + it.data();
d->dayStatMap.replace( TimeLineWidgetPriv::YearRefPair(year, day),
TimeLineWidgetPriv::StatPair(count, it_YP.data().second) );
}
if (d->maxCountByDay < count)
d->maxCountByDay = count;
}
if (!datesStatMap.isEmpty())
{
d->maxDateTime.setTime(TQTime(0, 0, 0, 0));
d->minDateTime.setTime(TQTime(0, 0, 0, 0));
}
else
{
d->maxDateTime = d->refDateTime;
d->minDateTime = d->refDateTime;
}
updatePixmap();
update();
emit signalDateMapChanged();
}
void TimeLineWidget::updatePixmap()
{
// Drawing background and image.
d->pixmap = TQPixmap(size());
d->pixmap.fill(palette().active().background());
TQPainter p(&d->pixmap);
d->bottomMargin = (int)(p.fontMetrics().height()*1.5);
d->barWidth = p.fontMetrics().width("00");
d->nbItems = (int)((width() / 2.0) / (float)d->barWidth);
d->startPos = (int)((width() / 2.0) - ((float)(d->barWidth) / 2.0));
int dim = height() - d->bottomMargin - d->topMargin;
TQDateTime ref = d->refDateTime;
ref.setTime(TQTime(0, 0, 0, 0));
double max, logVal;
int val, top;
SelectionMode sel;
TQRect focusRect, selRect, barRect;
TQBrush selBrush;
TQColor dateColor, subDateColor;
// Date histogram drawing is divided in 2 parts. The current date-time
// is placed on the center of the view and all dates on right are computed,
// and in second time, all dates on the left.
// Draw all dates on the right of ref. date-time.
for (int i = 0 ; i < d->nbItems ; i++)
{
val = statForDateTime(ref, sel);
max = (double)maxCount();
if (d->scaleMode == TimeLineWidget::LogScale)
{
if (max > 0.0) max = log(max);
else max = 1.0;
if (val <= 0) logVal = 0;
else logVal = log(val);
top = lround(dim + d->topMargin - ((logVal * dim) / max));
if (top < 0) val = 0;
}
else
{
top = lround(dim + d->topMargin - ((val * dim) / max));
}
barRect.setTop(top);
barRect.setLeft(d->startPos + i*d->barWidth);
barRect.setBottom(height() - d->bottomMargin);
barRect.setRight(d->startPos + (i+1)*d->barWidth);
if (ref == d->cursorDateTime)
focusRect = barRect;
if (ref > d->maxDateTime)
dateColor = palette().active().mid();
else
dateColor = palette().active().foreground();
p.setPen(palette().active().foreground());
p.fillRect(barRect, TQBrush(ThemeEngine::instance()->textSpecialRegColor()));
p.drawRect(barRect);
p.drawLine(barRect.right(), barRect.bottom(), barRect.right(), barRect.bottom()+3);
p.drawLine(barRect.left(), barRect.bottom(), barRect.left(), barRect.bottom()+3);
if (val)
{
if (sel)
subDateColor = palette().active().highlightedText();
else
subDateColor = palette().active().foreground();
}
else
subDateColor = palette().active().mid();
if (sel == Selected || sel == FuzzySelection)
{
selBrush.setColor(ThemeEngine::instance()->thumbSelColor());
selBrush.setStyle(TQBrush::SolidPattern);
if (sel == FuzzySelection)
selBrush.setStyle(TQBrush::Dense4Pattern);
selRect.setTop(height() - d->bottomMargin + 1);
selRect.setLeft(d->startPos + i*d->barWidth);
selRect.setBottom(height() - d->bottomMargin/2);
selRect.setRight(d->startPos + (i+1)*d->barWidth);
p.fillRect(selRect, selBrush);
}
switch(d->timeUnit)
{
case Day:
{
{
p.save();
TQFont fnt = p.font();
fnt.setPointSize(fnt.pointSize()-4);
p.setFont(fnt);
p.setPen(subDateColor);
TQString txt = TQString(d->calendar->weekDayName(ref.date(), true)[0]);
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
barRect.bottom()+br.height(), txt);
p.restore();
}
if (d->calendar->dayOfWeek(ref.date()) == 1)
{
p.setPen(dateColor);
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/2);
TQString txt = TDEGlobal::locale()->formatDate(ref.date(), true);
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
}
break;
}
case Week:
{
int week = d->calendar->weekNumber(ref.date());
{
p.save();
TQFont fnt = p.font();
fnt.setPointSize(fnt.pointSize()-4);
p.setFont(fnt);
p.setPen(subDateColor);
TQString txt = TQString::number(week);
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
barRect.bottom()+br.height(), txt);
p.restore();
}
p.setPen(dateColor);
if (week == 1 || week == 10 || week == 20 || week == 30 || week == 40 || week == 50)
{
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/2);
TQString txt = TDEGlobal::locale()->formatDate(ref.date(), true);
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
if (week != 50)
p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
}
else if (week == 6 || week == 16 || week == 26 || week == 36 || week == 46)
{
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/4);
}
break;
}
case Month:
{
{
p.save();
TQFont fnt = p.font();
fnt.setPointSize(fnt.pointSize()-4);
p.setFont(fnt);
p.setPen(subDateColor) ;
TQString txt = TQString(d->calendar->monthName(ref.date(), true)[0]);
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
barRect.bottom()+br.height(), txt);
p.restore();
}
p.setPen(dateColor);
if (ref.date().month() == 1)
{
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/2);
TQString txt = TQString::number(ref.date().year());
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
}
else if (ref.date().month() == 7)
{
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/4);
}
break;
}
case Year:
{
p.setPen(dateColor);
if (ref.date().year() % 10 == 0)
{
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/2);
TQString txt = TQString::number(ref.date().year());
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
}
else if (ref.date().year() % 5 == 0)
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/4);
break;
}
}
ref = nextDateTime(ref);
}
// Draw all dates on the left of ref. date-time.
ref = d->refDateTime;
ref.setTime(TQTime(0, 0, 0, 0));
ref = prevDateTime(ref);
for (int i = 0 ; i < d->nbItems-1 ; i++)
{
val = statForDateTime(ref, sel);
max = (double)maxCount();
if (d->scaleMode == TimeLineWidget::LogScale)
{
if (max > 0.0) max = log(max);
else max = 1.0;
if (val <= 0) logVal = 0;
else logVal = log(val);
top = lround(dim + d->topMargin - ((logVal * dim) / max));
if (top < 0) val = 0;
}
else
{
top = lround(dim + d->topMargin - ((val * dim) / max));
}
barRect.setTop(top);
barRect.setRight(d->startPos - i*d->barWidth);
barRect.setBottom(height() - d->bottomMargin);
barRect.setLeft(d->startPos - (i+1)*d->barWidth);
if (ref == d->cursorDateTime)
focusRect = barRect;
if (ref < d->minDateTime)
dateColor = palette().active().mid();
else
dateColor = palette().active().foreground();
p.setPen(palette().active().foreground());
p.fillRect(barRect, TQBrush(ThemeEngine::instance()->textSpecialRegColor()));
p.drawRect(barRect);
p.drawLine(barRect.right(), barRect.bottom(), barRect.right(), barRect.bottom()+3);
p.drawLine(barRect.left(), barRect.bottom(), barRect.left(), barRect.bottom()+3);
if (val)
{
if (sel)
subDateColor = palette().active().highlightedText();
else
subDateColor = palette().active().foreground();
}
else
subDateColor = palette().active().mid();
if (sel == Selected || sel == FuzzySelection)
{
selBrush.setColor(ThemeEngine::instance()->thumbSelColor());
selBrush.setStyle(TQBrush::SolidPattern);
if (sel == FuzzySelection)
selBrush.setStyle(TQBrush::Dense4Pattern);
selRect.setTop(height() - d->bottomMargin + 1);
selRect.setLeft(d->startPos - (i+1)*d->barWidth);
selRect.setBottom(height() - d->bottomMargin/2);
selRect.setRight(d->startPos - i*d->barWidth);
p.fillRect(selRect, selBrush);
}
switch(d->timeUnit)
{
case Day:
{
{
p.save();
TQFont fnt = p.font();
fnt.setPointSize(fnt.pointSize()-4);
p.setFont(fnt);
p.setPen(subDateColor) ;
TQString txt = TQString(d->calendar->weekDayName(ref.date(), true)[0]);
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
barRect.bottom()+br.height(), txt);
p.restore();
}
if (d->calendar->dayOfWeek(ref.date()) == 1)
{
p.setPen(dateColor);
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/2);
TQString txt = TDEGlobal::locale()->formatDate(ref.date(), true);
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
}
break;
}
case Week:
{
int week = d->calendar->weekNumber(ref.date());
{
p.save();
TQFont fnt = p.font();
fnt.setPointSize(fnt.pointSize()-4);
p.setFont(fnt);
p.setPen(subDateColor) ;
TQString txt = TQString::number(week);
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
barRect.bottom()+br.height(), txt);
p.restore();
}
p.setPen(dateColor);
if (week == 1 || week == 10 || week == 20 || week == 30 || week == 40 || week == 50)
{
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/2);
TQString txt = TDEGlobal::locale()->formatDate(ref.date(), true);
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
if (week != 50)
p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
}
else if (week == 6 || week == 16 || week == 26 || week == 36 || week == 46)
{
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/4);
}
break;
}
case Month:
{
{
p.save();
TQFont fnt = p.font();
fnt.setPointSize(fnt.pointSize()-4);
p.setFont(fnt);
p.setPen(subDateColor) ;
TQString txt = TQString(d->calendar->monthName(ref.date(), true)[0]);
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left() + ((barRect.width()-br.width())/2),
barRect.bottom()+br.height(), txt);
p.restore();
}
p.setPen(dateColor);
if (ref.date().month() == 1)
{
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/2);
TQString txt = TQString::number(ref.date().year());
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
}
else if (ref.date().month() == 7)
{
p.drawLine(barRect.right(), barRect.bottom(),
barRect.right(), barRect.bottom()+d->bottomMargin/4);
}
break;
}
case Year:
{
p.setPen(dateColor);
if (ref.date().year() % 10 == 0)
{
p.drawLine(barRect.left(), barRect.bottom(),
barRect.left(), barRect.bottom()+d->bottomMargin/2);
TQString txt = TQString::number(ref.date().year());
TQRect br = p.fontMetrics().boundingRect(0, 0, width(), height(), 0, txt);
p.drawText(barRect.left()-br.width()/2, barRect.bottom() + d->bottomMargin, txt);
}
else if (ref.date().year() % 5 == 0)
p.drawLine(barRect.right(), barRect.bottom(),
barRect.right(), barRect.bottom()+d->bottomMargin/4);
break;
}
}
ref = prevDateTime(ref);
}
// Draw cursor rectangle over current date-time.
if (focusRect.isValid())
{
focusRect.setTop(d->topMargin);
TQPoint p1(focusRect.left(), height() - d->bottomMargin);
TQPoint p2(focusRect.right(), height() - d->bottomMargin);
focusRect.setBottom(focusRect.bottom() + d->bottomMargin/2);
p.setPen(palette().active().shadow());
p.drawLine(p1.x(), p1.y()+1, p2.x(), p2.y()+1);
p.drawRect(focusRect);
focusRect.addCoords(-1,-1, 1, 1);
p.setPen(ThemeEngine::instance()->textSelColor());
p.drawRect(focusRect);
p.drawLine(p1.x()-1, p1.y(), p2.x()+1, p2.y());
focusRect.addCoords(-1,-1, 1, 1);
p.drawRect(focusRect);
p.drawLine(p1.x()-1, p1.y()-1, p2.x()+1, p2.y()-1);
focusRect.addCoords(-1,-1, 1, 1);
p.setPen(palette().active().shadow());
p.drawRect(focusRect);
p.drawLine(p1.x(), p1.y()-2, p2.x(), p2.y()-2);
}
p.end();
}
TQDateTime TimeLineWidget::prevDateTime(const TQDateTime& dt)
{
TQDateTime prev;
switch(d->timeUnit)
{
case Day:
{
prev = dt.addDays(-1);
break;
}
case Week:
{
prev = dt.addDays(-7);
break;
}
case Month:
{
prev = dt.addMonths(-1);
break;
}
case Year:
{
prev = dt.addYears(-1);
break;
}
}
return prev;
}
TQDateTime TimeLineWidget::nextDateTime(const TQDateTime& dt)
{
TQDateTime next;
switch(d->timeUnit)
{
case Day:
{
next = dt.addDays(1);
break;
}
case Week:
{
next = dt.addDays(7);
break;
}
case Month:
{
next = dt.addMonths(1);
break;
}
case Year:
{
next = dt.addYears(1);
break;
}
}
return next;
}
int TimeLineWidget::maxCount()
{
int max = 1;
switch(d->timeUnit)
{
case Day:
{
max = d->maxCountByDay;
break;
}
case Week:
{
max = d->maxCountByWeek;
break;
}
case Month:
{
max = d->maxCountByMonth;
break;
}
case Year:
{
max = d->maxCountByYear;
break;
}
}
return max;
}
int TimeLineWidget::statForDateTime(const TQDateTime& dt, SelectionMode& selected)
{
int count = 0;
int year = dt.date().year();
int month = dt.date().month();
int day = d->calendar->dayOfYear(dt.date());
int yearForWeek; // Used with week shared between 2 years decade (Dec/Jan).
int week = d->calendar->weekNumber(dt.date(), &yearForWeek);
selected = Unselected;
switch(d->timeUnit)
{
case Day:
{
TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it =
d->dayStatMap.find(TimeLineWidgetPriv::YearRefPair(year, day));
if ( it != d->dayStatMap.end() )
{
count = it.data().first;
selected = it.data().second;
}
break;
}
case Week:
{
TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it =
d->weekStatMap.find(TimeLineWidgetPriv::YearRefPair(yearForWeek, week));
if ( it != d->weekStatMap.end() )
{
count = it.data().first;
selected = it.data().second;
}
break;
}
case Month:
{
TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it =
d->monthStatMap.find(TimeLineWidgetPriv::YearRefPair(year, month));
if ( it != d->monthStatMap.end() )
{
count = it.data().first;
selected = it.data().second;
}
break;
}
case Year:
{
TQMap<int, TimeLineWidgetPriv::StatPair>::iterator it = d->yearStatMap.find(year);
if ( it != d->yearStatMap.end() )
{
count = it.data().first;
selected = it.data().second;
}
break;
}
}
return count;
}
void TimeLineWidget::setDateTimeSelected(const TQDateTime& dt, SelectionMode selected)
{
int year = dt.date().year();
int month = dt.date().month();
int yearForWeek; // Used with week shared between 2 years decade (Dec/Jan).
int week = d->calendar->weekNumber(dt.date(), &yearForWeek);
TQDateTime dts, dte;
switch(d->timeUnit)
{
case Day:
{
dts = dt;
dte = dts.addDays(1);
setDaysRangeSelection(dts, dte, selected);
break;
}
case Week:
{
dts = firstDayOfWeek(yearForWeek, week);
dte = dts.addDays(7);
setDaysRangeSelection(dts, dte, selected);
updateWeekSelection(dts, dte);
break;
}
case Month:
{
dts = TQDateTime(TQDate(year, month, 1));
dte = dts.addDays(d->calendar->daysInMonth(dts.date()));
setDaysRangeSelection(dts, dte, selected);
updateMonthSelection(dts, dte);
break;
}
case Year:
{
dts = TQDateTime(TQDate(year, 1, 1));
dte = dts.addDays(d->calendar->daysInYear(dts.date()));
setDaysRangeSelection(dts, dte, selected);
updateYearSelection(dts, dte);
break;
}
}
}
void TimeLineWidget::updateWeekSelection(const TQDateTime dts, const TQDateTime dte)
{
TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
TQDateTime dtsWeek, dteWeek, dt;
int week;
int yearForWeek; // Used with week shared between 2 years decade (Dec/Jan).
dt = dts;
do
{
yearForWeek = dt.date().year();
week = d->calendar->weekNumber(dt.date(), &yearForWeek);
dtsWeek = firstDayOfWeek(yearForWeek, week);
dteWeek = dtsWeek.addDays(7);
it = d->weekStatMap.find(TimeLineWidgetPriv::YearRefPair(yearForWeek, week));
if ( it != d->weekStatMap.end() )
it.data().second = checkSelectionForDaysRange(dtsWeek, dteWeek);
dt = dt.addDays(7);
}
while (dt <= dte);
}
void TimeLineWidget::updateMonthSelection(const TQDateTime dts, const TQDateTime dte)
{
TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
TQDateTime dtsMonth, dteMonth, dt;
int year, month;
dt = dts;
do
{
year = dt.date().year();
month = dt.date().month();
dtsMonth = TQDateTime(TQDate(year, month, 1));
dteMonth = dtsMonth.addDays(d->calendar->daysInMonth(dtsMonth.date()));
it = d->monthStatMap.find(TimeLineWidgetPriv::YearRefPair(year, month));
if ( it != d->monthStatMap.end() )
it.data().second = checkSelectionForDaysRange(dtsMonth, dteMonth);
dt = dteMonth;
}
while (dt <= dte);
}
void TimeLineWidget::updateYearSelection(const TQDateTime dts, const TQDateTime dte)
{
TQMap<int, TimeLineWidgetPriv::StatPair>::iterator it;
TQDateTime dtsYear, dteYear, dt;
int year;
dt = dts;
do
{
year = dt.date().year();
dtsYear = TQDateTime(TQDate(year, 1, 1));
dteYear = dtsYear.addDays(d->calendar->daysInYear(dtsYear.date()));
it = d->yearStatMap.find(year);
if ( it != d->yearStatMap.end() )
it.data().second = checkSelectionForDaysRange(dtsYear, dteYear);
dt = dteYear;
}
while (dt <= dte);
}
void TimeLineWidget::updateAllSelection()
{
TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
TQDateTime dts, dte;
TQDate date;
for (it = d->dayStatMap.begin() ; it != d->dayStatMap.end(); ++it)
{
if (it.data().second == Selected)
{
date = TQDate(it.key().first, 1, 1);
date = date.addDays(it.key().second-1);
dts = TQDateTime(date);
dte = dts.addDays(1);
updateWeekSelection(dts, dte);
updateMonthSelection(dts, dte);
updateYearSelection(dts, dte);
}
}
}
void TimeLineWidget::setDaysRangeSelection(const TQDateTime dts, const TQDateTime dte, SelectionMode selected)
{
int year, day;
TQDateTime dt = dts;
TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
do
{
year = dt.date().year();
day = d->calendar->dayOfYear(dt.date());
it = d->dayStatMap.find(TimeLineWidgetPriv::YearRefPair(year, day));
if ( it != d->dayStatMap.end() )
it.data().second = selected;
dt = dt.addDays(1);
}
while(dt < dte);
}
TimeLineWidget::SelectionMode TimeLineWidget::checkSelectionForDaysRange(const TQDateTime dts, const TQDateTime dte)
{
int year, day;
int items = 0;
int itemsFuz = 0;
int itemsSel = 0;
TQDateTime dt = dts;
TQMap<TimeLineWidgetPriv::YearRefPair, TimeLineWidgetPriv::StatPair>::iterator it;
do
{
year = dt.date().year();
day = d->calendar->dayOfYear(dt.date());
it = d->dayStatMap.find(TimeLineWidgetPriv::YearRefPair(year, day));
if ( it != d->dayStatMap.end() )
{
items++;
if (it.data().second != Unselected)
{
if (it.data().second == FuzzySelection)
itemsFuz++;
else
itemsSel++;
}
}
dt = dt.addDays(1);
}
while (dt < dte);
if (items == 0)
return Unselected;
if (itemsFuz == 0 && itemsSel == 0)
return Unselected;
if (itemsFuz > 0)
return FuzzySelection;
if (items > itemsSel)
return FuzzySelection;
return Selected;
}
void TimeLineWidget::paintEvent(TQPaintEvent*)
{
bitBlt(this, 0, 0, &d->pixmap);
}
void TimeLineWidget::resizeEvent(TQResizeEvent*)
{
updatePixmap();
}
void TimeLineWidget::slotBackward()
{
TQDateTime ref = d->refDateTime;
switch(d->timeUnit)
{
case Day:
{
for (int i = 0; i < 7; i++)
ref = prevDateTime(ref);
break;
}
case Week:
{
for (int i = 0; i < 4; i++)
ref = prevDateTime(ref);
break;
}
case Month:
{
for (int i = 0; i < 12; i++)
ref = prevDateTime(ref);
break;
}
case Year:
{
for (int i = 0; i < 5; i++)
ref = prevDateTime(ref);
break;
}
}
if (ref < d->minDateTime)
ref = d->minDateTime;
setRefDateTime(ref);
}
void TimeLineWidget::slotPrevious()
{
if (d->refDateTime <= d->minDateTime)
return;
TQDateTime ref = prevDateTime(d->refDateTime);
setRefDateTime(ref);
}
void TimeLineWidget::slotNext()
{
if (d->refDateTime >= d->maxDateTime)
return;
TQDateTime ref = nextDateTime(d->refDateTime);
setRefDateTime(ref);
}
void TimeLineWidget::slotForward()
{
TQDateTime ref = d->refDateTime;
switch(d->timeUnit)
{
case Day:
{
for (int i = 0; i < 7; i++)
ref = nextDateTime(ref);
break;
}
case Week:
{
for (int i = 0; i < 4; i++)
ref = nextDateTime(ref);
break;
}
case Month:
{
for (int i = 0; i < 12; i++)
ref = nextDateTime(ref);
break;
}
case Year:
{
for (int i = 0; i < 5; i++)
ref = nextDateTime(ref);
break;
}
}
if (ref > d->maxDateTime)
ref = d->maxDateTime;
setRefDateTime(ref);
}
void TimeLineWidget::wheelEvent(TQWheelEvent* e)
{
if (e->delta() < 0)
{
if (e->state() & TQt::ShiftButton)
slotForward();
else
slotNext();
}
if (e->delta() > 0)
{
if (e->state() & TQt::ShiftButton)
slotBackward();
else
slotPrevious();
}
}
void TimeLineWidget::mousePressEvent(TQMouseEvent *e)
{
if (e->button() == TQt::LeftButton)
{
TQPoint pt(e->x(), e->y());
bool ctrlPressed = e->state() & TQt::ControlButton;
TQDateTime ref = dateTimeForPoint(pt, d->selMouseEvent);
d->selStartDateTime = TQDateTime();
if (d->selMouseEvent)
{
if (!ctrlPressed)
resetSelection();
d->selStartDateTime = ref;
d->selMinDateTime = ref;
d->selMaxDateTime = ref;
setDateTimeSelected(ref, Selected);
}
if (!ref.isNull())
setCursorDateTime(ref);
d->validMouseEvent = true;
updatePixmap();
update();
}
}
void TimeLineWidget::mouseMoveEvent(TQMouseEvent *e)
{
if (d->validMouseEvent == true)
{
TQPoint pt(e->x(), e->y());
bool sel;
TQDateTime selEndDateTime = dateTimeForPoint(pt, sel);
setCursorDateTime(selEndDateTime);
// Clamp start and end date-time of current contiguous selection.
if (!selEndDateTime.isNull() && !d->selStartDateTime.isNull())
{
if (selEndDateTime > d->selStartDateTime &&
selEndDateTime > d->selMaxDateTime)
{
d->selMaxDateTime = selEndDateTime;
}
else if (selEndDateTime < d->selStartDateTime &&
selEndDateTime < d->selMinDateTime)
{
d->selMinDateTime = selEndDateTime;
}
TQDateTime dt = d->selMinDateTime;
do
{
setDateTimeSelected(dt, Unselected);
dt = nextDateTime(dt);
}
while(dt <= d->selMaxDateTime);
}
// Now perform selections on Date Maps.
if (d->selMouseEvent)
{
if (!d->selStartDateTime.isNull() && !selEndDateTime.isNull())
{
TQDateTime dt = d->selStartDateTime;
if (selEndDateTime > d->selStartDateTime)
{
do
{
setDateTimeSelected(dt, Selected);
dt = nextDateTime(dt);
}
while(dt <= selEndDateTime);
}
else
{
do
{
setDateTimeSelected(dt, Selected);
dt = prevDateTime(dt);
}
while(dt >= selEndDateTime);
}
}
}
updatePixmap();
update();
}
}
void TimeLineWidget::mouseReleaseEvent(TQMouseEvent*)
{
d->validMouseEvent = false;
// Only dispatch changes about selection when user release mouse selection
// to prevent multiple queries on database.
if (d->selMouseEvent)
{
updateAllSelection();
emit signalSelectionChanged();
}
d->selMouseEvent = false;
}
TQDateTime TimeLineWidget::dateTimeForPoint(const TQPoint& pt, bool &isOnSelectionArea)
{
TQRect barRect, selRect;
isOnSelectionArea = false;
// Check on the right of reference date.
TQDateTime ref = d->refDateTime;
ref.setTime(TQTime(0, 0, 0, 0));
TQRect deskRect = TDEGlobalSettings::desktopGeometry(this);
int items = deskRect.width() / d->barWidth;
for (int i = 0 ; i < items ; i++)
{
barRect.setTop(0);
barRect.setLeft(d->startPos + i*d->barWidth);
barRect.setBottom(height() - d->bottomMargin + 1);
barRect.setRight(d->startPos + (i+1)*d->barWidth);
selRect.setTop(height() - d->bottomMargin + 1);
selRect.setLeft(d->startPos + i*d->barWidth);
selRect.setBottom(height());
selRect.setRight(d->startPos + (i+1)*d->barWidth);
if (selRect.contains(pt))
isOnSelectionArea = true;
if (barRect.contains(pt) || selRect.contains(pt))
{
if (i >= d->nbItems)
{
// Point is outside visible widget area. We scrolling widget contents.
slotNext();
}
return ref;
}
ref = nextDateTime(ref);
}
// Check on the left of reference date.
ref = d->refDateTime;
ref.setTime(TQTime(0, 0, 0, 0));
ref = prevDateTime(ref);
for (int i = 0 ; i < items ; i++)
{
barRect.setTop(0);
barRect.setRight(d->startPos - i*d->barWidth);
barRect.setBottom(height() - d->bottomMargin + 1);
barRect.setLeft(d->startPos - (i+1)*d->barWidth);
selRect.setTop(height() - d->bottomMargin + 1);
selRect.setLeft(d->startPos - (i+1)*d->barWidth);
selRect.setBottom(height());
selRect.setRight(d->startPos - i*d->barWidth);
if (selRect.contains(pt))
isOnSelectionArea = true;
if (barRect.contains(pt) || selRect.contains(pt))
{
if (i >= d->nbItems-1)
{
// Point is outside visible widget area. We scrolling widget contents.
slotPrevious();
}
return ref;
}
ref = prevDateTime(ref);
}
return TQDateTime();
}
TQDateTime TimeLineWidget::firstDayOfWeek(int year, int weekNumber)
{
// Search the first day of first week of year.
// We start to scan from 1st december of year-1 because
// first week of year OR last week of year-1 can be shared
// between year-1 and year.
TQDateTime d1(TQDate(year-1, 12, 1));
TQDateTime dt = d1;
int weekYear = 0;
int weekNum = 0;
do
{
dt = dt.addDays(1);
weekNum = d->calendar->weekNumber(dt.date(), &weekYear);
}
while(weekNum != 1 && weekYear != year);
dt = dt.addDays((weekNumber-1)*7);
/*
DDebug() << "Year= " << year << " Week= " << weekNumber
<< " 1st day= " << dt << endl;
*/
return dt;
}
void TimeLineWidget::slotThemeChanged()
{
updatePixmap();
update();
}
} // NameSpace Digikam