/* This file is part of KOrganizer. Copyright (c) 2001 Eitzenberger Thomas Parts of the source code have been copied from kdpdatebutton.cpp Copyright (c) 2003 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer 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 of the License, 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of TQt, and distribute the resulting executable, without including the source code for TQt in the source distribution. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "koprefs.h" #include "koglobals.h" #include "kodialogmanager.h" #include "kodaymatrix.h" #include "kodaymatrix.moc" #ifndef NODND #include #include #include #undef FocusIn #undef KeyPress #undef None #undef Status #endif // ============================================================================ // D Y N A M I C T I P // ============================================================================ DynamicTip::DynamicTip( TQWidget * parent ) : TQToolTip( parent ) { mMatrix = static_cast( parent ); } void DynamicTip::maybeTip( const TQPoint &pos ) { //calculate which cell of the matrix the mouse is in TQRect sz = mMatrix->frameRect(); int dheight = sz.height() * 7 / 42; int dwidth = sz.width() / 7; int row = pos.y() / dheight; int col = pos.x() / dwidth; TQRect rct( col * dwidth, row * dheight, dwidth, dheight ); // kdDebug(5850) << "DynamicTip::maybeTip matrix cell index [" << // col << "][" << row << "] => " <<(col+row*7) << endl; //show holiday names only TQString str = mMatrix->getHolidayLabel( col + row * 7 ); if ( str.isEmpty() ) return; tip( rct, str ); } // ============================================================================ // K O D A Y M A T R I X // ============================================================================ const int KODayMatrix::NOSELECTION = -1000; const int KODayMatrix::NUMDAYS = 42; KODayMatrix::KODayMatrix( TQWidget *parent, const char *name ) : TQFrame( parent, name ), mCalendar( 0 ), mStartDate(), mPendingChanges( false ) { // initialize dynamic arrays mDays = new TQDate[ NUMDAYS ]; mDayLabels = new TQString[ NUMDAYS ]; mEvents = new int[ NUMDAYS ]; mToolTip = new DynamicTip( this ); mTodayMarginWidth = 2; mSelEnd = mSelStart = NOSELECTION; setBackgroundMode( NoBackground ); recalculateToday(); } void KODayMatrix::setCalendar( Calendar *cal ) { if ( mCalendar ) { mCalendar->unregisterObserver( this ); mCalendar->disconnect( this ); } mCalendar = cal; mCalendar->registerObserver( this ); CalendarResources *calres = dynamic_cast( cal ); if ( calres ) { connect( calres, TQT_SIGNAL(signalResourceAdded(ResourceCalendar *)), TQT_SLOT(resourcesChanged()) ); connect( calres, TQT_SIGNAL(signalResourceModified( ResourceCalendar *)), TQT_SLOT(resourcesChanged()) ); connect( calres, TQT_SIGNAL(signalResourceDeleted(ResourceCalendar *)), TQT_SLOT(resourcesChanged()) ); } setAcceptDrops( mCalendar ); updateEvents(); } TQColor KODayMatrix::getShadedColor( const TQColor &color ) { TQColor shaded; int h = 0; int s = 0; int v = 0; color.hsv( &h, &s, &v ); s = s / 4; v = 192 + v / 4; shaded.setHsv( h, s, v ); return shaded; } KODayMatrix::~KODayMatrix() { if ( mCalendar ) mCalendar->unregisterObserver( this ); delete [] mDays; delete [] mDayLabels; delete [] mEvents; delete mToolTip; } void KODayMatrix::addSelectedDaysTo( DateList &selDays ) { kdDebug(5850) << "KODayMatrix::addSelectedDaysTo() - " << "mSelStart:" << mSelStart << endl; if ( mSelStart == NOSELECTION ) { return; } // cope with selection being out of matrix limits at top (< 0) int i0 = mSelStart; if ( i0 < 0 ) { for ( int i = i0; i < 0; i++ ) { selDays.append( mDays[ 0 ].addDays( i ) ); } i0 = 0; } // cope with selection being out of matrix limits at bottom (> NUMDAYS-1) if ( mSelEnd > NUMDAYS-1 ) { for ( int i = i0; i <= NUMDAYS - 1; i++ ) { selDays.append( mDays[ i ] ); } for ( int i = NUMDAYS; i < mSelEnd; i++ ) { selDays.append( mDays[ 0 ].addDays( i ) ); } } else { // apply normal routine to selection being entirely within matrix limits for ( int i = i0; i <= mSelEnd; i++ ) { selDays.append( mDays[ i ] ); } } } void KODayMatrix::setSelectedDaysFrom( const TQDate &start, const TQDate &end ) { if ( mStartDate.isValid() ) { mSelStart = mStartDate.daysTo( start ); mSelEnd = mStartDate.daysTo( end ); } } void KODayMatrix::clearSelection() { mSelEnd = mSelStart = NOSELECTION; } void KODayMatrix::recalculateToday() { if ( !mStartDate.isValid() ) return; mToday = -1; for ( int i = 0; i < NUMDAYS; i++ ) { mDays[ i ] = mStartDate.addDays( i ); mDayLabels[ i ] = TQString::number( KOGlobals::self()->calendarSystem()->day( mDays[i] )); // if today is in the currently displayed month, hilight today if ( mDays[ i ].year() == TQDate::currentDate().year() && mDays[ i ].month() == TQDate::currentDate().month() && mDays[ i ].day() == TQDate::currentDate().day() ) { mToday = i; } } // kdDegug(5850) << "Today is visible at "<< today << "." << endl; } /* slot */ void KODayMatrix::updateView() { updateView( mStartDate ); } void KODayMatrix::setUpdateNeeded() { mPendingChanges = true; } void KODayMatrix::updateView( const TQDate &actdate ) { kdDebug(5850) << "KODayMatrix::updateView() " << actdate << ", day start="< " << tmp << "(" << offset << ")" << endl; // shift selection if new one would be visible at least partly ! if ( mSelStart + tmp < NUMDAYS && mSelEnd + tmp >= 0 ) { // nested if is required for next X display pushed from a different month - correction required // otherwise, for month forward and backward, it must be avoided if( mSelStart > NUMDAYS || mSelStart < 0 ) mSelStart = mSelStart + tmp; if( mSelEnd > NUMDAYS || mSelEnd < 0 ) mSelEnd = mSelEnd + tmp; } } mStartDate = actdate; daychanged = true; } if ( daychanged ) { recalculateToday(); } // the calendar hasn't changed in the meantime and the selected range is still the same // so we can safe the expensive updateEvents() call if ( !daychanged && !mPendingChanges ) return; // TODO_Recurrence: If we just change the selection, but not the data, there's // no need to update the whole list of events... This is just a waste of // computational power (and it takes forever!) updateEvents(); for( int i = 0; i < NUMDAYS; i++ ) { //if it is a holy day then draw it red. Sundays are consider holidays, too TQStringList holidays = KOGlobals::self()->holiday( mDays[ i ] ); TQString holiStr = TQString(); if ( ( KOGlobals::self()->calendarSystem()->dayOfWeek( mDays[ i ] ) == KOGlobals::self()->calendarSystem()->weekDayOfPray() ) || !holidays.isEmpty() ) { if ( !holidays.isEmpty() ) holiStr = holidays.join( i18n("delimiter for joining holiday names", ", " ) ); if ( holiStr.isNull() ) holiStr = ""; } mHolidays[ i ] = holiStr; } } void KODayMatrix::updateEvents() { kdDebug( 5850 ) << k_funcinfo << endl; if ( !mCalendar ) return; for( int i = 0; i < NUMDAYS; i++ ) { // if events are set for the day then remember to draw it bold Event::List eventlist = mCalendar->events( mDays[ i ] ); int numEvents = eventlist.count(); Event::List::ConstIterator it; for( it = eventlist.begin(); it != eventlist.end(); ++it ) { Event *event = *it; ushort recurType = event->recurrenceType(); if ( ( recurType == Recurrence::rDaily && !KOPrefs::instance()->mDailyRecur ) || ( recurType == Recurrence::rWeekly && !KOPrefs::instance()->mWeeklyRecur ) ) { numEvents--; } } mEvents[ i ] = numEvents; } mPendingChanges = false; } const TQDate& KODayMatrix::getDate( int offset ) { if ( offset < 0 || offset > NUMDAYS - 1 ) { kdDebug(5850) << "Wrong offset (" << offset << ") in KODayMatrix::getDate(int)" << endl; return mDays[ 0 ]; } return mDays[ offset ]; } TQString KODayMatrix::getHolidayLabel( int offset ) { if ( offset < 0 || offset > NUMDAYS - 1 ) { kdDebug(5850) << "Wrong offset (" << offset << ") in KODayMatrix::getHolidayLabel(int)" << endl; return 0; } return mHolidays[ offset ]; } int KODayMatrix::getDayIndexFrom( int x, int y ) { return 7 * ( y / mDaySize.height() ) + ( KOGlobals::self()->reverseLayout() ? 6 - x / mDaySize.width() : x / mDaySize.width() ); } void KODayMatrix::calendarIncidenceAdded(Incidence * incidence) { Q_UNUSED( incidence ); mPendingChanges = true; } void KODayMatrix::calendarIncidenceChanged(Incidence * incidence) { Q_UNUSED( incidence ); mPendingChanges = true; } void KODayMatrix::calendarIncidenceDeleted(Incidence * incidence) { Q_UNUSED( incidence ); mPendingChanges = true; } void KODayMatrix::resourcesChanged() { mPendingChanges = true; } // ---------------------------------------------------------------------------- // M O U S E E V E N T H A N D L I N G // ---------------------------------------------------------------------------- void KODayMatrix::mousePressEvent( TQMouseEvent *e ) { mSelStart = getDayIndexFrom(e->x(), e->y()); if (mSelStart > NUMDAYS-1) mSelStart=NUMDAYS-1; mSelInit = mSelStart; } void KODayMatrix::mouseReleaseEvent( TQMouseEvent *e ) { int tmp = getDayIndexFrom(e->x(), e->y()); if (tmp > NUMDAYS-1) tmp=NUMDAYS-1; if (mSelInit > tmp) { mSelEnd = mSelInit; if (tmp != mSelStart) { mSelStart = tmp; repaint(); } } else { mSelStart = mSelInit; //repaint only if selection has changed if (tmp != mSelEnd) { mSelEnd = tmp; repaint(); } } DateList daylist; if ( mSelStart < 0 ) mSelStart = 0; for ( int i = mSelStart; i <= mSelEnd; ++i ) { daylist.append( mDays[i] ); } emit selected((const DateList)daylist); } void KODayMatrix::mouseMoveEvent( TQMouseEvent *e ) { int tmp = getDayIndexFrom(e->x(), e->y()); if (tmp > NUMDAYS-1) tmp=NUMDAYS-1; if (mSelInit > tmp) { mSelEnd = mSelInit; if ( tmp != mSelStart ) { mSelStart = tmp; repaint(); } } else { mSelStart = mSelInit; //repaint only if selection has changed if ( tmp != mSelEnd ) { mSelEnd = tmp; repaint(); } } } // ---------------------------------------------------------------------------- // D R A G ' N D R O P H A N D L I N G // ---------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Drag and Drop handling -- based on the Troll Tech dirview example enum { DRAG_COPY = 0, DRAG_MOVE = 1, DRAG_CANCEL = 2 }; void KODayMatrix::dragEnterEvent( TQDragEnterEvent *e ) { #ifndef KORG_NODND if ( !ICalDrag::canDecode( e ) && !VCalDrag::canDecode( e ) ) { e->ignore(); return; } // some visual feedback // oldPalette = palette(); // setPalette(my_HilitePalette); // update(); #endif } void KODayMatrix::dragMoveEvent( TQDragMoveEvent *e ) { #ifndef KORG_NODND if ( !ICalDrag::canDecode( e ) && !VCalDrag::canDecode( e ) ) { e->ignore(); return; } e->accept(); #endif } void KODayMatrix::dragLeaveEvent( TQDragLeaveEvent * /*dl*/ ) { #ifndef KORG_NODND // setPalette(oldPalette); // update(); #endif } void KODayMatrix::dropEvent( TQDropEvent *e ) { #ifndef KORG_NODND kdDebug(5850) << "KODayMatrix::dropEvent(e) begin" << endl; if ( !mCalendar || ( !ICalDrag::canDecode( e ) && !VCalDrag::canDecode( e ) ) ) { e->ignore(); return; } DndFactory factory( mCalendar ); Event *event = factory.createDrop( e ); Todo *todo = factory.createDropTodo( e ); if ( !event && !todo ) { e->ignore(); return; } Todo *existingTodo = 0; Event *existingEvent = 0; // Find the incidence in the calendar, then we don't need the drag object any more if ( event ) existingEvent = mCalendar->event( event->uid() ); if ( todo ) existingTodo = mCalendar->todo( todo->uid() ); int action = DRAG_CANCEL; int root_x, root_y, win_x, win_y; uint keybstate; Window rootw, childw; XQueryPointer( tqt_xdisplay(), tqt_xrootwin(), &rootw, &childw, &root_x, &root_y, &win_x, &win_y, &keybstate ); if ( keybstate & ControlMask ) { action = DRAG_COPY; } else if ( keybstate & ShiftMask ) { action = DRAG_MOVE; } else { TDEPopupMenu *menu = new TDEPopupMenu( this ); if ( existingEvent || existingTodo ) { menu->insertItem( i18n("Move"), DRAG_MOVE, 0 ); if (existingEvent) menu->insertItem( KOGlobals::self()->smallIcon("edit-copy"), i18n("Copy"), DRAG_COPY, 1 ); } else { menu->insertItem( i18n("Add"), DRAG_MOVE, 0 ); } menu->insertSeparator(); menu->insertItem( KOGlobals::self()->smallIcon("cancel"), i18n("Cancel"), DRAG_CANCEL, 3 ); action = menu->exec( TQCursor::pos(), 0 ); } if ( action == DRAG_COPY || action == DRAG_MOVE ) { e->accept(); int idx = getDayIndexFrom( e->pos().x(), e->pos().y() ); if ( action == DRAG_COPY ) { if ( event ) emit incidenceDropped( event, mDays[idx] ); if ( todo ) emit incidenceDropped( todo, mDays[idx] ); } else if ( action == DRAG_MOVE ) { if ( event ) emit incidenceDroppedMove( event, mDays[idx] ); if ( todo ) emit incidenceDroppedMove( todo, mDays[idx] ); } } delete event; delete todo; #endif } // ---------------------------------------------------------------------------- // P A I N T E V E N T H A N D L I N G // ---------------------------------------------------------------------------- void KODayMatrix::paintEvent( TQPaintEvent * ) { // kdDebug(5850) << "KODayMatrix::paintEvent() BEGIN" << endl; TQPainter p; TQRect sz = frameRect(); TQPixmap pm( sz.size() ); int dheight = mDaySize.height(); int dwidth = mDaySize.width(); int row,col; int selw, selh; bool isRTL = KOGlobals::self()->reverseLayout(); TQColorGroup cg = palette().active(); p.begin( &pm, this ); pm.fill( cg.base() ); // draw topleft frame p.setPen( cg.mid() ); p.drawRect(0, 0, sz.width()-1, sz.height()-1); // don't paint over borders p.translate(1,1); // draw selected days with highlighted background color if (mSelStart != NOSELECTION) { row = mSelStart/7; // fix larger selections starting in the previous month if ( row < 0 && mSelEnd > 0 ) row = 0; col = mSelStart -row*7; TQColor selcol = KOPrefs::instance()->mHighlightColor; if ( row < 6 && row >= 0 ) { if (row == mSelEnd/7) { // Single row selection p.fillRect(isRTL ? (7 - (mSelEnd-mSelStart+1) - col)*dwidth : col*dwidth, row*dheight, (mSelEnd-mSelStart+1)*dwidth, dheight, selcol); } else { // draw first row to the right p.fillRect(isRTL ? 0 : col*dwidth, row*dheight, (7-col)*dwidth, dheight, selcol); // draw full block till last line selh = mSelEnd/7-row; if ( selh + row >= 6 ) selh = 6-row; if ( selh > 1 ) { p.fillRect(0, (row+1)*dheight, 7*dwidth, (selh-1)*dheight,selcol); } // draw last block from left to mSelEnd if ( mSelEnd/7 < 6 ) { selw = mSelEnd-7*(mSelEnd/7)+1; p.fillRect(isRTL ? (7-selw)*dwidth : 0, (row+selh)*dheight, selw*dwidth, dheight, selcol); } } } } // iterate over all days in the matrix and draw the day label in appropriate colors TQColor textColor = cg.text(); TQColor textColorShaded = getShadedColor( textColor ); TQColor actcol = textColorShaded; p.setPen(actcol); TQPen tmppen; for ( int i = 0; i < NUMDAYS; ++i ) { row = i/7; col = isRTL ? 6-(i-row*7) : i-row*7; // if it is the first day of a month switch color from normal to shaded and vice versa if ( KOGlobals::self()->calendarSystem()->day( mDays[i] ) == 1) { if (actcol == textColorShaded) { actcol = textColor; } else { actcol = textColorShaded; } p.setPen(actcol); } //Reset pen color after selected days block if (i == mSelEnd+1) { p.setPen(actcol); } bool holiday = ! KOGlobals::self()->isWorkDay( mDays[ i ] ); TQColor holidayColorShaded = getShadedColor( KOPrefs::instance()->mHolidayColor ); // if today then draw rectangle around day if (mToday == i) { tmppen = p.pen(); TQPen mTodayPen(p.pen()); mTodayPen.setWidth(mTodayMarginWidth); //draw red rectangle for holidays if (holiday) { if (actcol == textColor) { mTodayPen.setColor(KOPrefs::instance()->mHolidayColor); } else { mTodayPen.setColor(holidayColorShaded); } } //draw gray rectangle for today if in selection if (i >= mSelStart && i <= mSelEnd) { TQColor grey("grey"); mTodayPen.setColor(grey); } p.setPen(mTodayPen); p.drawRect(col*dwidth, row*dheight, dwidth, dheight); p.setPen(tmppen); } // if any events are on that day then draw it using a bold font if (mEvents[i] > 0) { TQFont myFont = font(); myFont.setBold(true); p.setFont(myFont); } // if it is a holiday then use the default holiday color if (holiday) { if (actcol == textColor) { p.setPen(KOPrefs::instance()->mHolidayColor); } else { p.setPen(holidayColorShaded); } } // draw selected days with special color if ( i >= mSelStart && i <= mSelEnd && !holiday ) { p.setPen( TQColor( "white" ) ); } p.drawText(col*dwidth, row*dheight, dwidth, dheight, TQt::AlignHCenter | TQt::AlignVCenter, mDayLabels[i]); // reset color to actual color if ( holiday ) { p.setPen(actcol); } // reset bold font to plain font if (mEvents[i] > 0) { TQFont myFont = font(); myFont.setBold(false); p.setFont(myFont); } } p.end(); bitBlt( this, 0, 0, &pm ); } // ---------------------------------------------------------------------------- // R E SI Z E E V E N T H A N D L I N G // ---------------------------------------------------------------------------- void KODayMatrix::resizeEvent( TQResizeEvent * ) { TQRect sz = frameRect(); mDaySize.setHeight( sz.height() * 7 / NUMDAYS ); mDaySize.setWidth( sz.width() / 7 ); } /* static */ TQPair KODayMatrix::matrixLimits( const TQDate &month ) { const KCalendarSystem *calSys = KOGlobals::self()->calendarSystem(); TQDate d = month; calSys->setYMD( d, calSys->year( month ), calSys->month( month ), 1 ); const int dayOfWeek = calSys->dayOfWeek( d ); const int weekstart = TDEGlobal::locale()->weekStartDay(); d = d.addDays( weekstart - dayOfWeek ); if ( dayOfWeek == weekstart ) { d = d.addDays( -7 ); // Start on the second line } return qMakePair( d, d.addDays( NUMDAYS-1 ) ); }