-rw-r--r-- | core/pim/todo/todotable.cpp | 859 |
1 files changed, 859 insertions, 0 deletions
diff --git a/core/pim/todo/todotable.cpp b/core/pim/todo/todotable.cpp new file mode 100644 index 0000000..77d3389 --- a/dev/null +++ b/core/pim/todo/todotable.cpp | |||
@@ -0,0 +1,859 @@ | |||
1 | /********************************************************************** | ||
2 | ** Copyright (C) 2000 Trolltech AS. All rights reserved. | ||
3 | ** | ||
4 | ** This file is part of Qtopia Environment. | ||
5 | ** | ||
6 | ** This file may be distributed and/or modified under the terms of the | ||
7 | ** GNU General Public License version 2 as published by the Free Software | ||
8 | ** Foundation and appearing in the file LICENSE.GPL included in the | ||
9 | ** packaging of this file. | ||
10 | ** | ||
11 | ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE | ||
12 | ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. | ||
13 | ** | ||
14 | ** See http://www.trolltech.com/gpl/ for GPL licensing information. | ||
15 | ** | ||
16 | ** Contact info@trolltech.com if any conditions of this licensing are | ||
17 | ** not clear to you. | ||
18 | ** | ||
19 | **********************************************************************/ | ||
20 | |||
21 | #include "todotable.h" | ||
22 | |||
23 | #include <qpe/categoryselect.h> | ||
24 | #include <qpe/xmlreader.h> | ||
25 | |||
26 | #include <qasciidict.h> | ||
27 | #include <qcombobox.h> | ||
28 | #include <qfile.h> | ||
29 | #include <qpainter.h> | ||
30 | #include <qtextcodec.h> | ||
31 | #include <qtimer.h> | ||
32 | #include <qdatetime.h> | ||
33 | |||
34 | #include <qcursor.h> | ||
35 | #include <qregexp.h> | ||
36 | |||
37 | #include <errno.h> | ||
38 | #include <stdlib.h> | ||
39 | |||
40 | |||
41 | |||
42 | static bool taskCompare( const Task &task, const QRegExp &r, int category ); | ||
43 | |||
44 | static QString journalFileName(); | ||
45 | |||
46 | CheckItem::CheckItem( QTable *t, const QString &key ) | ||
47 | : QTableItem( t, Never, "" ), checked( FALSE ), sortKey( key ) | ||
48 | { | ||
49 | } | ||
50 | |||
51 | QString CheckItem::key() const | ||
52 | { | ||
53 | return sortKey; | ||
54 | } | ||
55 | |||
56 | void CheckItem::setChecked( bool b ) | ||
57 | { | ||
58 | checked = b; | ||
59 | table()->updateCell( row(), col() ); | ||
60 | } | ||
61 | |||
62 | void CheckItem::toggle() | ||
63 | { | ||
64 | TodoTable *parent = static_cast<TodoTable*>(table()); | ||
65 | Task newTodo = parent->currentEntry(); | ||
66 | checked = !checked; | ||
67 | newTodo.setCompleted( checked ); | ||
68 | table()->updateCell( row(), col() ); | ||
69 | parent->replaceCurrentEntry( newTodo, true ); | ||
70 | } | ||
71 | |||
72 | bool CheckItem::isChecked() const | ||
73 | { | ||
74 | return checked; | ||
75 | } | ||
76 | |||
77 | static const int BoxSize = 10; | ||
78 | |||
79 | void CheckItem::paint( QPainter *p, const QColorGroup &cg, const QRect &cr, | ||
80 | bool ) | ||
81 | { | ||
82 | p->fillRect( 0, 0, cr.width(), cr.height(), cg.brush( QColorGroup::Base ) ); | ||
83 | |||
84 | int marg = ( cr.width() - BoxSize ) / 2; | ||
85 | int x = 0; | ||
86 | int y = ( cr.height() - BoxSize ) / 2; | ||
87 | p->setPen( QPen( cg.text() ) ); | ||
88 | p->drawRect( x + marg, y, BoxSize, BoxSize ); | ||
89 | p->drawRect( x + marg+1, y+1, BoxSize-2, BoxSize-2 ); | ||
90 | p->setPen( darkGreen ); | ||
91 | x += 1; | ||
92 | y += 1; | ||
93 | if ( checked ) { | ||
94 | QPointArray a( 7*2 ); | ||
95 | int i, xx, yy; | ||
96 | xx = x+1+marg; | ||
97 | yy = y+2; | ||
98 | for ( i=0; i<3; i++ ) { | ||
99 | a.setPoint( 2*i, xx, yy ); | ||
100 | a.setPoint( 2*i+1, xx, yy+2 ); | ||
101 | xx++; yy++; | ||
102 | } | ||
103 | yy -= 2; | ||
104 | for ( i=3; i<7; i++ ) { | ||
105 | a.setPoint( 2*i, xx, yy ); | ||
106 | a.setPoint( 2*i+1, xx, yy+2 ); | ||
107 | xx++; yy--; | ||
108 | } | ||
109 | p->drawLineSegments( a ); | ||
110 | } | ||
111 | } | ||
112 | |||
113 | |||
114 | ComboItem::ComboItem( QTable *t, EditType et ) | ||
115 | : QTableItem( t, et, "3" ), cb( 0 ) | ||
116 | { | ||
117 | setReplaceable( FALSE ); | ||
118 | } | ||
119 | |||
120 | QWidget *ComboItem::createEditor() const | ||
121 | { | ||
122 | QString txt = text(); | ||
123 | ( (ComboItem*)this )->cb = new QComboBox( table()->viewport() ); | ||
124 | cb->insertItem( "1" ); | ||
125 | cb->insertItem( "2" ); | ||
126 | cb->insertItem( "3" ); | ||
127 | cb->insertItem( "4" ); | ||
128 | cb->insertItem( "5" ); | ||
129 | cb->setCurrentItem( txt.toInt() - 1 ); | ||
130 | return cb; | ||
131 | } | ||
132 | |||
133 | void ComboItem::setContentFromEditor( QWidget *w ) | ||
134 | { | ||
135 | TodoTable *parent = static_cast<TodoTable*>(table()); | ||
136 | Task newTodo = parent->currentEntry(); | ||
137 | |||
138 | if ( w->inherits( "QComboBox" ) ) | ||
139 | setText( ( (QComboBox*)w )->currentText() ); | ||
140 | else | ||
141 | QTableItem::setContentFromEditor( w ); | ||
142 | newTodo.setPriority( text().toInt() ); | ||
143 | parent->replaceCurrentEntry( newTodo, true ); | ||
144 | } | ||
145 | |||
146 | void ComboItem::setText( const QString &s ) | ||
147 | { | ||
148 | if ( cb ) | ||
149 | cb->setCurrentItem( s.toInt() - 1 ); | ||
150 | QTableItem::setText( s ); | ||
151 | } | ||
152 | |||
153 | QString ComboItem::text() const | ||
154 | { | ||
155 | if ( cb ) | ||
156 | return cb->currentText(); | ||
157 | return QTableItem::text(); | ||
158 | } | ||
159 | |||
160 | |||
161 | |||
162 | TodoTable::TodoTable( QWidget *parent, const char *name ) | ||
163 | // #ifdef QT_QTABLE_NOHEADER_CONSTRUCTOR | ||
164 | // : QTable( 0, 3, parent, name, TRUE ), | ||
165 | // #else | ||
166 | : QTable( 0, 3, parent, name ), | ||
167 | // #endif | ||
168 | showComp( true ), | ||
169 | enablePainting( true ), | ||
170 | mCat( 0 ), | ||
171 | currFindRow( -2 ) | ||
172 | { | ||
173 | mCat.load( categoryFileName() ); | ||
174 | setSorting( TRUE ); | ||
175 | setSelectionMode( NoSelection ); | ||
176 | setColumnStretchable( 2, TRUE ); | ||
177 | setColumnWidth( 0, 20 ); | ||
178 | setColumnWidth( 1, 35 ); | ||
179 | setLeftMargin( 0 ); | ||
180 | verticalHeader()->hide(); | ||
181 | horizontalHeader()->setLabel( 0, tr( "C." ) ); | ||
182 | horizontalHeader()->setLabel( 1, tr( "Prior." ) ); | ||
183 | horizontalHeader()->setLabel( 2, tr( "Description" ) ); | ||
184 | connect( this, SIGNAL( clicked( int, int, int, const QPoint & ) ), | ||
185 | this, SLOT( slotClicked( int, int, int, const QPoint & ) ) ); | ||
186 | connect( this, SIGNAL( pressed( int, int, int, const QPoint & ) ), | ||
187 | this, SLOT( slotPressed( int, int, int, const QPoint & ) ) ); | ||
188 | connect( this, SIGNAL( valueChanged( int, int ) ), | ||
189 | this, SLOT( slotCheckPriority( int, int ) ) ); | ||
190 | connect( this, SIGNAL( currentChanged( int, int ) ), | ||
191 | this, SLOT( slotCurrentChanged( int, int ) ) ); | ||
192 | |||
193 | menuTimer = new QTimer( this ); | ||
194 | connect( menuTimer, SIGNAL(timeout()), this, SLOT(slotShowMenu()) ); | ||
195 | } | ||
196 | |||
197 | void TodoTable::addEntry( const Task &todo ) | ||
198 | { | ||
199 | int row = numRows(); | ||
200 | setNumRows( row + 1 ); | ||
201 | updateJournal( todo, ACTION_ADD ); | ||
202 | insertIntoTable( new Task(todo), row ); | ||
203 | setCurrentCell(row, currentColumn()); | ||
204 | updateVisible(); | ||
205 | } | ||
206 | |||
207 | void TodoTable::slotClicked( int row, int col, int, const QPoint &pos ) | ||
208 | { | ||
209 | if ( !cellGeometry( row, col ).contains(pos) ) | ||
210 | return; | ||
211 | // let's switch on the column number... | ||
212 | switch ( col ) | ||
213 | { | ||
214 | case 0: { | ||
215 | CheckItem *i = static_cast<CheckItem*>(item( row, col )); | ||
216 | if ( i ) { | ||
217 | int x = pos.x() - columnPos( col ); | ||
218 | int y = pos.y() - rowPos( row ); | ||
219 | int w = columnWidth( col ); | ||
220 | int h = rowHeight( row ); | ||
221 | if ( i && x >= ( w - BoxSize ) / 2 && x <= ( w - BoxSize ) / 2 + BoxSize && | ||
222 | y >= ( h - BoxSize ) / 2 && y <= ( h - BoxSize ) / 2 + BoxSize ) { | ||
223 | i->toggle(); | ||
224 | } | ||
225 | emit signalDoneChanged( i->isChecked() ); | ||
226 | } | ||
227 | } | ||
228 | break; | ||
229 | case 1: | ||
230 | break; | ||
231 | case 2: | ||
232 | // may as well edit it... | ||
233 | menuTimer->stop(); | ||
234 | // emit signalEdit(); | ||
235 | break; | ||
236 | } | ||
237 | } | ||
238 | |||
239 | void TodoTable::slotPressed( int row, int col, int, const QPoint &pos ) | ||
240 | { | ||
241 | if ( col == 2 && cellGeometry( row, col ).contains(pos) ) | ||
242 | menuTimer->start( 750, TRUE ); | ||
243 | } | ||
244 | |||
245 | void TodoTable::slotShowMenu() | ||
246 | { | ||
247 | emit signalShowMenu( QCursor::pos() ); | ||
248 | } | ||
249 | |||
250 | void TodoTable::slotCurrentChanged( int, int ) | ||
251 | { | ||
252 | menuTimer->stop(); | ||
253 | } | ||
254 | |||
255 | void TodoTable::internalAddEntries( QList<Task> &list ) | ||
256 | { | ||
257 | setNumRows( list.count() ); | ||
258 | int row = 0; | ||
259 | Task *it; | ||
260 | for ( it = list.first(); it; it = list.next() ) | ||
261 | insertIntoTable( it, row++ ); | ||
262 | } | ||
263 | |||
264 | |||
265 | Task TodoTable::currentEntry() const | ||
266 | { | ||
267 | QTableItem *i = item( currentRow(), 0 ); | ||
268 | if ( !i || rowHeight( currentRow() ) <= 0 ) | ||
269 | return Task(); | ||
270 | Task *todo = todoList[(CheckItem*)i]; | ||
271 | todo->setCompleted( ( (CheckItem*)item( currentRow(), 0 ) )->isChecked() ); | ||
272 | todo->setPriority( ( (ComboItem*)item( currentRow(), 1 ) )->text().toInt() ); | ||
273 | return *todo; | ||
274 | } | ||
275 | |||
276 | void TodoTable::replaceCurrentEntry( const Task &todo, bool fromTableItem ) | ||
277 | { | ||
278 | int row = currentRow(); | ||
279 | updateJournal( todo, ACTION_REPLACE, row ); | ||
280 | |||
281 | if ( !fromTableItem ) { | ||
282 | journalFreeReplaceEntry( todo, row ); | ||
283 | updateVisible(); | ||
284 | } | ||
285 | } | ||
286 | |||
287 | void TodoTable::removeCurrentEntry() | ||
288 | { | ||
289 | Task *oldTodo; | ||
290 | int row = currentRow(); | ||
291 | CheckItem *chk; | ||
292 | |||
293 | chk = static_cast<CheckItem*>(item(row, 0 )); | ||
294 | if ( !chk ) | ||
295 | return; | ||
296 | oldTodo = todoList[chk]; | ||
297 | todoList.remove( chk ); | ||
298 | oldTodo->setCompleted( chk->isChecked() ); | ||
299 | oldTodo->setPriority( static_cast<ComboItem*>(item(row, 1))->text().toInt() ); | ||
300 | realignTable( row ); | ||
301 | updateVisible(); | ||
302 | updateJournal( *oldTodo, ACTION_REMOVE, row ); | ||
303 | delete oldTodo; | ||
304 | } | ||
305 | |||
306 | |||
307 | bool TodoTable::save( const QString &fn ) | ||
308 | { | ||
309 | QString strNewFile = fn + ".new"; | ||
310 | QFile f( strNewFile ); | ||
311 | if ( !f.open( IO_WriteOnly|IO_Raw ) ) | ||
312 | return false; | ||
313 | |||
314 | QString buf("<!DOCTYPE Tasks>\n<Tasks>\n"); | ||
315 | QCString str; | ||
316 | int total_written; | ||
317 | |||
318 | for ( QMap<CheckItem*, Task *>::Iterator it = todoList.begin(); | ||
319 | it != todoList.end(); ++it ) { | ||
320 | if ( !item( it.key()->row(), 0 ) ) | ||
321 | continue; | ||
322 | Task *todo = *it; | ||
323 | // sync item with table | ||
324 | todo->setCompleted( ((CheckItem*)item(it.key()->row(), 0))->isChecked() ); | ||
325 | todo->setPriority( ((ComboItem*)item( it.key()->row(), 1))->text().toInt() ); | ||
326 | buf += "<Task"; | ||
327 | todo->save( buf ); | ||
328 | buf += " />\n"; | ||
329 | str = buf.utf8(); | ||
330 | total_written = f.writeBlock( str.data(), str.length() ); | ||
331 | if ( total_written != int(str.length()) ) { | ||
332 | f.close(); | ||
333 | QFile::remove( strNewFile ); | ||
334 | return false; | ||
335 | } | ||
336 | buf = ""; | ||
337 | } | ||
338 | |||
339 | buf += "</Tasks>\n"; | ||
340 | str = buf.utf8(); | ||
341 | total_written = f.writeBlock( str.data(), str.length() ); | ||
342 | if ( total_written != int(str.length()) ) { | ||
343 | f.close(); | ||
344 | QFile::remove( strNewFile ); | ||
345 | return false; | ||
346 | } | ||
347 | f.close(); | ||
348 | |||
349 | // now do the rename | ||
350 | if ( ::rename( strNewFile, fn ) < 0 ) | ||
351 | qWarning( "problem renaming file %s to %s errno %d", | ||
352 | strNewFile.latin1(), fn.latin1(), errno ); | ||
353 | |||
354 | // remove the journal | ||
355 | QFile::remove( journalFileName() ); | ||
356 | return true; | ||
357 | } | ||
358 | |||
359 | void TodoTable::load( const QString &fn ) | ||
360 | { | ||
361 | loadFile( fn, false ); | ||
362 | if ( QFile::exists(journalFileName()) ) { | ||
363 | loadFile( journalFileName(), true ); | ||
364 | save( fn ); | ||
365 | } | ||
366 | // QTable::sortColumn(2,TRUE,TRUE); | ||
367 | // QTable::sortColumn(1,TRUE,TRUE); | ||
368 | QTable::sortColumn(0,TRUE,TRUE); | ||
369 | setCurrentCell( 0, 2 ); | ||
370 | } | ||
371 | |||
372 | void TodoTable::updateVisible() | ||
373 | { | ||
374 | if ( !isUpdatesEnabled() ) | ||
375 | return; | ||
376 | |||
377 | // qDebug("--> updateVisible!"); | ||
378 | |||
379 | int visible = 0; | ||
380 | int id = mCat.id( "Todo List", showCat ); | ||
381 | for ( int row = 0; row < numRows(); row++ ) { | ||
382 | CheckItem *ci = (CheckItem *)item( row, 0 ); | ||
383 | Task *t = todoList[ci]; | ||
384 | QArray<int> vlCats = t->categories(); | ||
385 | bool hide = false; | ||
386 | if ( !showComp && ci->isChecked() ) | ||
387 | hide = true; | ||
388 | if ( !showCat.isEmpty() ) { | ||
389 | if ( showCat == tr( "Unfiled" ) ) { | ||
390 | if ( vlCats.count() > 0 ) | ||
391 | hide = true; | ||
392 | } else { | ||
393 | // do some comparing, we have to reverse our idea here... | ||
394 | if ( !hide ) { | ||
395 | hide = true; | ||
396 | for ( uint it = 0; it < vlCats.count(); ++it ) { | ||
397 | if ( vlCats[it] == id ) { | ||
398 | hide = false; | ||
399 | break; | ||
400 | } | ||
401 | } | ||
402 | } | ||
403 | } | ||
404 | } | ||
405 | if ( hide ) { | ||
406 | if ( currentRow() == row ) | ||
407 | setCurrentCell( -1, 0 ); | ||
408 | if ( rowHeight( row ) > 0 ) | ||
409 | hideRow( row ); | ||
410 | } else { | ||
411 | if ( rowHeight( row ) == 0 ) { | ||
412 | showRow( row ); | ||
413 | adjustRow( row ); | ||
414 | } | ||
415 | visible++; | ||
416 | } | ||
417 | } | ||
418 | if ( !visible ) | ||
419 | setCurrentCell( -1, 0 ); | ||
420 | } | ||
421 | |||
422 | void TodoTable::viewportPaintEvent( QPaintEvent *pe ) | ||
423 | { | ||
424 | if ( enablePainting ) | ||
425 | QTable::viewportPaintEvent( pe ); | ||
426 | } | ||
427 | |||
428 | void TodoTable::setPaintingEnabled( bool e ) | ||
429 | { | ||
430 | if ( e != enablePainting ) { | ||
431 | if ( !enablePainting ) { | ||
432 | enablePainting = true; | ||
433 | rowHeightChanged( 0 ); | ||
434 | viewport()->update(); | ||
435 | } else { | ||
436 | enablePainting = false; | ||
437 | } | ||
438 | } | ||
439 | } | ||
440 | |||
441 | void TodoTable::clear() | ||
442 | { | ||
443 | for ( QMap<CheckItem*, Task *>::Iterator it = todoList.begin(); | ||
444 | it != todoList.end(); ++it ) { | ||
445 | Task *todo = *it; | ||
446 | delete todo; | ||
447 | } | ||
448 | todoList.clear(); | ||
449 | for ( int r = 0; r < numRows(); ++r ) { | ||
450 | for ( int c = 0; c < numCols(); ++c ) { | ||
451 | if ( cellWidget( r, c ) ) | ||
452 | clearCellWidget( r, c ); | ||
453 | clearCell( r, c ); | ||
454 | } | ||
455 | } | ||
456 | setNumRows( 0 ); | ||
457 | } | ||
458 | |||
459 | void TodoTable::sortColumn( int col, bool /*ascending*/, bool /*wholeRows*/ ) | ||
460 | { | ||
461 | // The default for wholeRows is false, however | ||
462 | // for this todo table we want to exchange complete | ||
463 | // rows when sorting. Also, we always want ascending, since | ||
464 | // the values have a logical order. | ||
465 | QTable::sortColumn( col, TRUE, TRUE ); | ||
466 | updateVisible(); | ||
467 | } | ||
468 | |||
469 | void TodoTable::slotCheckPriority(int row, int col ) | ||
470 | { | ||
471 | // kludgey work around to make forward along the updated priority... | ||
472 | if ( col == 1 ) { | ||
473 | // let everyone know!! | ||
474 | ComboItem* i = static_cast<ComboItem*>( item( row, col ) ); | ||
475 | emit signalPriorityChanged( i->text().toInt() ); | ||
476 | } | ||
477 | } | ||
478 | |||
479 | |||
480 | void TodoTable::updateJournal( const Task &todo, journal_action action, int row ) | ||
481 | { | ||
482 | QFile f( journalFileName() ); | ||
483 | if ( !f.open(IO_WriteOnly|IO_Append) ) | ||
484 | return; | ||
485 | QString buf; | ||
486 | QCString str; | ||
487 | buf = "<Task"; | ||
488 | todo.save( buf ); | ||
489 | buf += " Action=\"" + QString::number( int(action) ) + "\""; | ||
490 | buf += " Row=\"" + QString::number( row ) + "\""; | ||
491 | buf += "/>\n"; | ||
492 | str = buf.utf8(); | ||
493 | f.writeBlock( str.data(), str.length() ); | ||
494 | f.close(); | ||
495 | } | ||
496 | |||
497 | void TodoTable::rowHeightChanged( int row ) | ||
498 | { | ||
499 | if ( enablePainting ) | ||
500 | QTable::rowHeightChanged( row ); | ||
501 | } | ||
502 | |||
503 | void TodoTable::loadFile( const QString &strFile, bool fromJournal ) | ||
504 | { | ||
505 | QFile f( strFile ); | ||
506 | if ( !f.open(IO_ReadOnly) ) | ||
507 | return; | ||
508 | |||
509 | int action, row; | ||
510 | action = 0; row = 0; | ||
511 | |||
512 | enum Attribute { | ||
513 | FCompleted = 0, | ||
514 | FHasDate, | ||
515 | FPriority, | ||
516 | FCategories, | ||
517 | FDescription, | ||
518 | FDateYear, | ||
519 | FDateMonth, | ||
520 | FDateDay, | ||
521 | FUid, | ||
522 | FAction, | ||
523 | FRow | ||
524 | }; | ||
525 | |||
526 | QAsciiDict<int> dict( 31 ); | ||
527 | QList<Task> list; | ||
528 | dict.setAutoDelete( TRUE ); | ||
529 | dict.insert( "Completed", new int(FCompleted) ); | ||
530 | dict.insert( "HasDate", new int(FHasDate) ); | ||
531 | dict.insert( "Priority", new int(FPriority) ); | ||
532 | dict.insert( "Categories", new int(FCategories) ); | ||
533 | dict.insert( "Description", new int(FDescription) ); | ||
534 | dict.insert( "DateYear", new int(FDateYear) ); | ||
535 | dict.insert( "DateMonth", new int(FDateMonth) ); | ||
536 | dict.insert( "DateDay", new int(FDateDay) ); | ||
537 | dict.insert( "Uid", new int(FUid) ); | ||
538 | dict.insert( "Action", new int(FAction) ); | ||
539 | dict.insert( "Row", new int(FRow) ); | ||
540 | |||
541 | QByteArray ba = f.readAll(); | ||
542 | f.close(); | ||
543 | char* dt = ba.data(); | ||
544 | int len = ba.size(); | ||
545 | bool hasDueDate = FALSE; | ||
546 | |||
547 | action = ACTION_ADD; | ||
548 | int i = 0; | ||
549 | char *point; | ||
550 | while ( ( point = strstr( dt+i, "<Task " ) ) != NULL ) { | ||
551 | // new Task | ||
552 | i = point - dt; | ||
553 | Task *todo = new Task; | ||
554 | int dtY = 0, dtM = 0, dtD = 0; | ||
555 | |||
556 | i += 5; | ||
557 | |||
558 | while( 1 ) { | ||
559 | while ( i < len && (dt[i] == ' ' || dt[i] == '\n' || dt[i] == '\r') ) | ||
560 | ++i; | ||
561 | if ( i >= len-2 || (dt[i] == '/' && dt[i+1] == '>') ) | ||
562 | break; | ||
563 | // we have another attribute, read it. | ||
564 | int j = i; | ||
565 | while ( j < len && dt[j] != '=' ) | ||
566 | ++j; | ||
567 | char *attr = dt+i; | ||
568 | dt[j] = '\0'; | ||
569 | i = ++j; // skip = | ||
570 | while ( i < len && dt[i] != '"' ) | ||
571 | ++i; | ||
572 | j = ++i; | ||
573 | bool haveUtf = FALSE; | ||
574 | bool haveEnt = FALSE; | ||
575 | while ( j < len && dt[j] != '"' ) { | ||
576 | if ( ((unsigned char)dt[j]) > 0x7f ) | ||
577 | haveUtf = TRUE; | ||
578 | if ( dt[j] == '&' ) | ||
579 | haveEnt = TRUE; | ||
580 | ++j; | ||
581 | } | ||
582 | if ( i == j ) { | ||
583 | // empty value | ||
584 | i = j + 1; | ||
585 | continue; | ||
586 | } | ||
587 | QCString value( dt+i, j-i+1 ); | ||
588 | i = j + 1; | ||
589 | int *lookup = dict[ attr ]; | ||
590 | if ( !lookup ) { | ||
591 | todo->setCustomField(attr, value); | ||
592 | continue; | ||
593 | } | ||
594 | switch( *lookup ) { | ||
595 | case FCompleted: | ||
596 | todo->setCompleted( value.toInt() ); | ||
597 | break; | ||
598 | case FHasDate: | ||
599 | // leave... | ||
600 | hasDueDate = value.toInt(); | ||
601 | break; | ||
602 | case FPriority: | ||
603 | todo->setPriority( value.toInt() ); | ||
604 | break; | ||
605 | case FCategories: { | ||
606 | //QString str = Qtopia::plainString( value ); | ||
607 | todo->setCategories( Qtopia::Record::idsFromString( value ) ); | ||
608 | break; | ||
609 | } | ||
610 | case FDescription: | ||
611 | { | ||
612 | QString str = (haveUtf ? QString::fromUtf8( value ) | ||
613 | : QString::fromLatin1( value ) ); | ||
614 | if ( haveEnt ) | ||
615 | str = Qtopia::plainString( str ); | ||
616 | todo->setDescription( str ); | ||
617 | break; | ||
618 | } | ||
619 | case FDateYear: | ||
620 | dtY = value.toInt(); | ||
621 | break; | ||
622 | case FDateMonth: | ||
623 | dtM = value.toInt(); | ||
624 | break; | ||
625 | case FDateDay: | ||
626 | dtD = value.toInt(); | ||
627 | break; | ||
628 | case FUid: | ||
629 | todo->setUid( value.toInt() ); | ||
630 | break; | ||
631 | case FAction: | ||
632 | action = value.toInt(); | ||
633 | break; | ||
634 | case FRow: | ||
635 | row = value.toInt(); | ||
636 | break; | ||
637 | default: | ||
638 | qDebug( "huh??? missing enum? -- attr.: %s", attr ); | ||
639 | break; | ||
640 | } | ||
641 | } | ||
642 | |||
643 | if ( dtY != 0 && dtM != 0 && dtD != 0 ) | ||
644 | todo->setDueDate( QDate( dtY, dtM, dtD), hasDueDate ); | ||
645 | else | ||
646 | todo->setHasDueDate( hasDueDate ); | ||
647 | |||
648 | // if ( categoryList.find( todo.category() ) == categoryList.end() ) | ||
649 | // categoryList.append( todo.category() ); | ||
650 | |||
651 | |||
652 | // sadly we can't delay adding of items from the journal to get | ||
653 | // the proper effect, but then, the journal should _never_ be | ||
654 | // that huge | ||
655 | |||
656 | switch( action ) { | ||
657 | case ACTION_ADD: | ||
658 | if ( fromJournal ) { | ||
659 | int myrows = numRows(); | ||
660 | setNumRows( myrows + 1 ); | ||
661 | insertIntoTable( todo, myrows ); | ||
662 | delete todo; | ||
663 | } else | ||
664 | list.append( todo ); | ||
665 | break; | ||
666 | case ACTION_REMOVE: | ||
667 | journalFreeRemoveEntry( row ); | ||
668 | break; | ||
669 | case ACTION_REPLACE: | ||
670 | journalFreeReplaceEntry( *todo, row ); | ||
671 | delete todo; | ||
672 | break; | ||
673 | default: | ||
674 | break; | ||
675 | } | ||
676 | } | ||
677 | // qDebug("parsing done=%d", t.elapsed() ); | ||
678 | if ( list.count() > 0 ) { | ||
679 | internalAddEntries( list ); | ||
680 | list.clear(); | ||
681 | } | ||
682 | // qDebug("loading done: t=%d", t.elapsed() ); | ||
683 | } | ||
684 | |||
685 | void TodoTable::journalFreeReplaceEntry( const Task &todo, int row ) | ||
686 | { | ||
687 | QString strTodo; | ||
688 | strTodo = todo.description().left(40).simplifyWhiteSpace(); | ||
689 | if ( row == -1 ) { | ||
690 | QMapIterator<CheckItem*, Task *> it; | ||
691 | for ( it = todoList.begin(); it != todoList.end(); ++it ) { | ||
692 | if ( *(*it) == todo ) { | ||
693 | row = it.key()->row(); | ||
694 | it.key()->setChecked( todo.isCompleted() ); | ||
695 | static_cast<ComboItem*>(item(row, 1))->setText( QString::number(todo.priority()) ); | ||
696 | item( row, 2 )->setText( strTodo ); | ||
697 | *(*it) = todo; | ||
698 | } | ||
699 | } | ||
700 | } else { | ||
701 | Task *t = todoList[static_cast<CheckItem*>(item(row, 0))]; | ||
702 | todoList.remove( static_cast<CheckItem*>(item(row, 0)) ); | ||
703 | delete t; | ||
704 | static_cast<CheckItem*>(item(row, 0))->setChecked( todo.isCompleted() ); | ||
705 | static_cast<ComboItem*>(item(row, 1))->setText( QString::number(todo.priority()) ); | ||
706 | item( row, 2 )->setText( strTodo ); | ||
707 | todoList.insert( static_cast<CheckItem*>(item(row,0)), new Task(todo) ); | ||
708 | } | ||
709 | } | ||
710 | |||
711 | void TodoTable::journalFreeRemoveEntry( int row ) | ||
712 | { | ||
713 | CheckItem *chk; | ||
714 | chk = static_cast<CheckItem*>(item(row, 0 )); | ||
715 | if ( !chk ) | ||
716 | return; | ||
717 | todoList.remove( chk ); | ||
718 | |||
719 | realignTable( row ); | ||
720 | } | ||
721 | |||
722 | void TodoTable::keyPressEvent( QKeyEvent *e ) | ||
723 | { | ||
724 | if ( e->key() == Key_Space || e->key() == Key_Return ) { | ||
725 | switch ( currentColumn() ) { | ||
726 | case 0: { | ||
727 | CheckItem *i = static_cast<CheckItem*>(item(currentRow(), | ||
728 | currentColumn())); | ||
729 | if ( i ) | ||
730 | i->toggle(); | ||
731 | break; | ||
732 | } | ||
733 | case 1: | ||
734 | break; | ||
735 | case 2: | ||
736 | emit signalEdit(); | ||
737 | default: | ||
738 | break; | ||
739 | } | ||
740 | } else { | ||
741 | QTable::keyPressEvent( e ); | ||
742 | } | ||
743 | } | ||
744 | |||
745 | QStringList TodoTable::categories() | ||
746 | { | ||
747 | // This is called seldom, so calling a load in here | ||
748 | // should be fine. | ||
749 | mCat.load( categoryFileName() ); | ||
750 | QStringList categoryList = mCat.labels( "Todo List" ); | ||
751 | return categoryList; | ||
752 | } | ||
753 | |||
754 | void TodoTable::slotDoFind( const QString &findString, bool caseSensitive, | ||
755 | bool backwards, int category ) | ||
756 | { | ||
757 | // we have to iterate through the table, this gives the illusion that | ||
758 | // sorting is actually being used. | ||
759 | if ( currFindRow < -1 ) | ||
760 | currFindRow = currentRow() - 1; | ||
761 | clearSelection( TRUE ); | ||
762 | int rows, | ||
763 | row; | ||
764 | CheckItem *chk; | ||
765 | QRegExp r( findString ); | ||
766 | |||
767 | r.setCaseSensitive( caseSensitive ); | ||
768 | rows = numRows(); | ||
769 | static bool wrapAround = true; | ||
770 | |||
771 | if ( !backwards ) { | ||
772 | for ( row = currFindRow + 1; row < rows; row++ ) { | ||
773 | chk = static_cast<CheckItem*>( item(row, 0) ); | ||
774 | if ( taskCompare(*(todoList[chk]), r, category) ) | ||
775 | break; | ||
776 | } | ||
777 | } else { | ||
778 | for ( row = currFindRow - 1; row > -1; row-- ) { | ||
779 | chk = static_cast<CheckItem*>( item(row, 0) ); | ||
780 | if ( taskCompare(*(todoList[chk]), r, category) ) | ||
781 | break; | ||
782 | } | ||
783 | } | ||
784 | if ( row >= rows || row < 0 ) { | ||
785 | if ( row < 0 ) | ||
786 | currFindRow = rows; | ||
787 | else | ||
788 | currFindRow = -1; | ||
789 | if ( wrapAround ) | ||
790 | emit signalWrapAround(); | ||
791 | else | ||
792 | emit signalNotFound(); | ||
793 | wrapAround = !wrapAround; | ||
794 | } else { | ||
795 | currFindRow = row; | ||
796 | QTableSelection foundSelection; | ||
797 | foundSelection.init( currFindRow, 0 ); | ||
798 | foundSelection.expandTo( currFindRow, numCols() - 1 ); | ||
799 | addSelection( foundSelection ); | ||
800 | setCurrentCell( currFindRow, numCols() - 1 ); | ||
801 | // we should always be able to wrap around and find this again, | ||
802 | // so don't give confusing not found message... | ||
803 | wrapAround = true; | ||
804 | } | ||
805 | } | ||
806 | |||
807 | int TodoTable::showCategoryId() const | ||
808 | { | ||
809 | int id; | ||
810 | id = -1; | ||
811 | // if allcategories are selected, you get unfiled... | ||
812 | if ( showCat != tr( "Unfiled" ) && showCat != tr( "All" ) ) | ||
813 | id = mCat.id( "Todo List", showCat ); | ||
814 | return id; | ||
815 | } | ||
816 | |||
817 | static bool taskCompare( const Task &task, const QRegExp &r, int category ) | ||
818 | { | ||
819 | bool returnMe; | ||
820 | QArray<int> cats; | ||
821 | cats = task.categories(); | ||
822 | |||
823 | returnMe = false; | ||
824 | if ( (category == -1 && cats.count() == 0) || category == -2 ) | ||
825 | returnMe = task.match( r ); | ||
826 | else { | ||
827 | int i; | ||
828 | for ( i = 0; i < int(cats.count()); i++ ) { | ||
829 | if ( cats[i] == category ) { | ||
830 | returnMe = task.match( r ); | ||
831 | break; | ||
832 | } | ||
833 | } | ||
834 | } | ||
835 | return returnMe; | ||
836 | } | ||
837 | |||
838 | static QString journalFileName() | ||
839 | { | ||
840 | QString str; | ||
841 | str = getenv( "HOME" ); | ||
842 | str += "/.todojournal"; | ||
843 | return str; | ||
844 | } | ||
845 | |||
846 | // int TodoTable::rowHeight( int ) const | ||
847 | // { | ||
848 | // return 18; | ||
849 | // } | ||
850 | |||
851 | // int TodoTable::rowPos( int row ) const | ||
852 | // { | ||
853 | // return 18*row; | ||
854 | // } | ||
855 | |||
856 | // int TodoTable::rowAt( int pos ) const | ||
857 | // { | ||
858 | // return QMIN( pos/18, numRows()-1 ); | ||
859 | // } | ||