source: trunk/qt/file-tree.cc @ 14345

Last change on this file since 14345 was 14345, checked in by mikedld, 8 years ago

#5827: Improve torrent files tree updating performance

Do not unnecessarily emit dataChanged signals for items which didn't change.
Cache file item indices to speedup lookup.
As a bonus, this also fixes wrong file progress display in rare cases.

  • Property svn:keywords set to Date Rev Author Id
File size: 22.9 KB
Line 
1/*
2 * This file Copyright (C) 2009-2014 Mnemosyne LLC
3 *
4 * It may be used under the GNU Public License v2 or v3 licenses,
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 * $Id: file-tree.cc 14345 2014-11-30 18:12:28Z mikedld $
8 */
9
10#include <algorithm>
11#include <cassert>
12
13#include <QApplication>
14#include <QHeaderView>
15#include <QPainter>
16#include <QResizeEvent>
17#include <QSortFilterProxyModel>
18#include <QStringList>
19
20#include <libtransmission/transmission.h> // priorities
21
22#include "file-tree.h"
23#include "formatter.h"
24#include "hig.h"
25#include "torrent.h" // FileList
26#include "utils.h" // mime icons
27
28enum
29{
30  COL_NAME,
31  FIRST_VISIBLE_COLUMN = COL_NAME,
32  COL_SIZE,
33  COL_PROGRESS,
34  COL_WANTED,
35  COL_PRIORITY,
36  LAST_VISIBLE_COLUMN = COL_PRIORITY,
37
38  COL_FILE_INDEX,
39  NUM_COLUMNS
40};
41
42/****
43*****
44****/
45
46const QHash<QString,int>&
47FileTreeItem :: getMyChildRows ()
48{
49  const size_t n = childCount();
50
51  // ensure that all the rows are hashed
52  while (myFirstUnhashedRow < n)
53    {
54      myChildRows.insert (myChildren[myFirstUnhashedRow]->name(),
55                          myFirstUnhashedRow);
56      ++myFirstUnhashedRow;
57    }
58
59  return myChildRows;
60}
61
62
63FileTreeItem :: ~FileTreeItem ()
64{
65  assert(myChildren.isEmpty());
66
67  if (myParent != 0)
68    {
69      const int pos = row();
70      assert ((pos>=0) && "couldn't find child in parent's lookup");
71      myParent->myChildren.removeAt(pos);
72      myParent->myChildRows.remove(name());
73      myParent->myFirstUnhashedRow = pos;
74    }
75}
76
77void
78FileTreeItem :: appendChild (FileTreeItem * child)
79{
80  const size_t n = childCount();
81  child->myParent = this;
82  myChildren.append (child);
83  myFirstUnhashedRow = n;
84}
85
86FileTreeItem *
87FileTreeItem :: child (const QString& filename)
88{
89  FileTreeItem * item(0);
90
91  const int row = getMyChildRows().value (filename, -1);
92  if (row != -1)
93    {
94      item = child (row);
95      assert (filename == item->name());
96    }
97
98  return item;
99}
100
101int
102FileTreeItem :: row () const
103{
104  int i(-1);
105
106  if(myParent)
107    {
108      i = myParent->getMyChildRows().value (name(), -1);
109      assert (this == myParent->myChildren[i]);
110    }
111
112  return i;
113}
114
115QVariant
116FileTreeItem :: data (int column, int role) const
117{
118  QVariant value;
119
120  if (column == COL_FILE_INDEX)
121    {
122      value.setValue (myFileIndex);
123    }
124  else if (role == Qt::EditRole)
125    {
126      if (column == 0)
127        value.setValue (name());
128    }
129  else if ((role == Qt::TextAlignmentRole) && column == COL_SIZE)
130    {
131      value = Qt::AlignRight + Qt::AlignVCenter;
132    }
133  else if (role == Qt::DisplayRole)
134    {
135      switch(column)
136       {
137         case COL_NAME:
138           value.setValue (name());
139           break;
140
141         case COL_SIZE:
142           value.setValue (sizeString() + "  ");
143           break;
144
145         case COL_PROGRESS:
146           value.setValue (progress());
147           break;
148
149         case COL_WANTED:
150           value.setValue (isSubtreeWanted());
151           break;
152
153         case COL_PRIORITY:
154           value.setValue (priorityString());
155           break;
156        }
157    }
158  else if (role == Qt::DecorationRole && column == COL_NAME)
159    {
160      if (childCount () > 0)
161        value = QApplication::style ()->standardIcon (QStyle::SP_DirOpenIcon);
162      else
163        value = Utils::guessMimeIcon (name ());
164    }
165
166  return value;
167}
168
169void
170FileTreeItem :: getSubtreeWantedSize (uint64_t& have, uint64_t& total) const
171{
172  if (myIsWanted)
173    {
174      have += myHaveSize;
175      total += myTotalSize;
176    }
177
178  foreach(const FileTreeItem * i, myChildren)
179    i->getSubtreeWantedSize(have, total);
180}
181
182double
183FileTreeItem :: progress () const
184{
185  double d(0);
186  uint64_t have(0), total(0);
187
188  getSubtreeWantedSize (have, total);
189  if (total)
190    d = have / (double)total;
191
192  return d;
193}
194
195QString
196FileTreeItem :: sizeString () const
197{
198  QString str; 
199
200  if (myChildren.isEmpty())
201    {
202      str = Formatter::sizeToString (myTotalSize);
203    }
204  else
205    {
206      uint64_t have = 0;
207      uint64_t total = 0;
208      getSubtreeWantedSize (have, total);
209      str = Formatter::sizeToString (total);
210    }
211
212  return str;
213}
214
215std::pair<int,int>
216FileTreeItem :: update (const QString& name,
217                        bool           wanted,
218                        int            priority,
219                        uint64_t       haveSize,
220                        bool           updateFields)
221{
222  int changed_count = 0;
223  int changed_columns[4];
224
225  if (myName != name)
226    {
227      if (myParent)
228        myParent->myFirstUnhashedRow = row();
229
230      myName = name;
231      changed_columns[changed_count++] = COL_NAME;
232    }
233
234  if (fileIndex () != -1)
235    {
236      if (myHaveSize != haveSize)
237        {
238          myHaveSize = haveSize;
239          changed_columns[changed_count++] = COL_PROGRESS;
240        }
241
242      if (updateFields)
243        {
244          if (myIsWanted != wanted)
245            {
246              myIsWanted = wanted;
247              changed_columns[changed_count++] = COL_WANTED;
248            }
249
250          if (myPriority != priority)
251            {
252              myPriority = priority;
253              changed_columns[changed_count++] = COL_PRIORITY;
254            }
255        }
256    }
257
258  std::pair<int,int> changed (-1, -1);
259  if (changed_count > 0)
260    {
261      std::sort (changed_columns, changed_columns+changed_count);
262      changed.first = changed_columns[0];
263      changed.second = changed_columns[changed_count-1];
264    }
265  return changed;
266}
267
268QString
269FileTreeItem :: priorityString () const
270{
271  const int i = priority();
272
273  switch (i)
274    {
275      case LOW:    return tr("Low");
276      case HIGH:   return tr("High");
277      case NORMAL: return tr("Normal");
278      default:     return tr("Mixed");
279    }
280}
281
282int
283FileTreeItem :: priority () const
284{
285  int i(0);
286
287  if (myChildren.isEmpty())
288    {
289      switch (myPriority)
290        {
291          case TR_PRI_LOW:
292            i |= LOW;
293            break;
294
295          case TR_PRI_HIGH:
296            i |= HIGH;
297            break;
298
299          default:
300            i |= NORMAL;
301            break;
302        }
303    }
304
305  foreach (const FileTreeItem * child, myChildren)
306    i |= child->priority();
307
308  return i;
309}
310
311void
312FileTreeItem :: setSubtreePriority (int i, QSet<int>& ids)
313{
314  if (myPriority != i)
315    {
316      myPriority = i;
317
318      if (myFileIndex >= 0)
319        ids.insert (myFileIndex);
320    }
321
322  foreach (FileTreeItem * child, myChildren)
323    child->setSubtreePriority (i, ids);
324}
325
326void
327FileTreeItem :: twiddlePriority (QSet<int>& ids, int& p)
328{
329  const int old(priority());
330
331  if (old & LOW)
332    p = TR_PRI_NORMAL;
333  else if (old & NORMAL)
334    p = TR_PRI_HIGH;
335  else
336    p = TR_PRI_LOW;
337
338  setSubtreePriority (p, ids);
339}
340
341int
342FileTreeItem :: isSubtreeWanted () const
343{
344  if(myChildren.isEmpty())
345    return myIsWanted ? Qt::Checked : Qt::Unchecked;
346
347  int wanted(-1);
348  foreach (const FileTreeItem * child, myChildren)
349    {
350      const int childWanted = child->isSubtreeWanted();
351
352      if (wanted == -1)
353        wanted = childWanted;
354
355      if (wanted != childWanted)
356        wanted = Qt::PartiallyChecked;
357
358      if (wanted == Qt::PartiallyChecked)
359        return wanted;
360    }
361
362  return wanted;
363}
364
365void
366FileTreeItem :: setSubtreeWanted (bool b, QSet<int>& ids)
367{
368  if (myIsWanted != b)
369    {
370      myIsWanted = b;
371
372      if (myFileIndex >= 0)
373        ids.insert(myFileIndex);
374    }
375
376  foreach (FileTreeItem * child, myChildren)
377    child->setSubtreeWanted (b, ids);
378}
379
380void
381FileTreeItem :: twiddleWanted (QSet<int>& ids, bool& wanted)
382{
383  wanted = isSubtreeWanted() != Qt::Checked;
384  setSubtreeWanted (wanted, ids);
385}
386
387QString
388FileTreeItem :: path () const
389{
390  QString itemPath;
391  const FileTreeItem * item = this;
392
393  while (item != NULL && !item->name().isEmpty())
394    {
395      if (itemPath.isEmpty())
396        itemPath = item->name();
397      else
398        itemPath = item->name() + "/" + itemPath;
399      item = item->parent ();
400    }
401
402  return itemPath;
403}
404
405bool
406FileTreeItem :: isComplete () const
407{
408  return myHaveSize == totalSize ();
409}
410
411/***
412****
413****
414***/
415
416FileTreeModel :: FileTreeModel (QObject *parent, bool isEditable):
417  QAbstractItemModel(parent),
418  myRootItem (new FileTreeItem),
419  myIndexCache (),
420  myIsEditable (isEditable)
421{
422}
423
424FileTreeModel :: ~FileTreeModel()
425{
426  clear();
427
428  delete myRootItem;
429}
430
431FileTreeItem *
432FileTreeModel :: itemFromIndex (const QModelIndex& index) const
433{
434  return static_cast<FileTreeItem*>(index.internalPointer()); 
435}
436
437QVariant
438FileTreeModel :: data (const QModelIndex &index, int role) const
439{
440  QVariant value;
441
442  if (index.isValid())
443    value = itemFromIndex(index)->data (index.column(), role);
444
445  return value;
446}
447
448Qt::ItemFlags
449FileTreeModel :: flags (const QModelIndex& index) const
450{
451  int i(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
452
453  if(myIsEditable && (index.column() == COL_NAME))
454    i |= Qt::ItemIsEditable;
455
456  if(index.column() == COL_WANTED)
457    i |= Qt::ItemIsUserCheckable | Qt::ItemIsTristate;
458
459  return (Qt::ItemFlags)i;
460}
461
462bool
463FileTreeModel :: setData (const QModelIndex& index, const QVariant& newname, int role)
464{
465  if (role == Qt::EditRole)
466    {
467      FileTreeItem * item = itemFromIndex (index);
468
469      emit pathEdited (item->path (), newname.toString ());
470    }
471
472  return false; // don't update the view until the session confirms the change
473}
474
475QVariant
476FileTreeModel :: headerData (int column, Qt::Orientation orientation, int role) const
477{
478  QVariant data;
479
480  if (orientation==Qt::Horizontal && role==Qt::DisplayRole)
481    {
482      switch (column)
483        {
484          case COL_NAME:
485            data.setValue (tr("File"));
486            break;
487
488          case COL_SIZE:
489            data.setValue (tr("Size"));
490            break;
491
492          case COL_PROGRESS:
493            data.setValue (tr("Progress"));
494            break;
495
496          case COL_WANTED:
497            data.setValue (tr("Download"));
498            break;
499
500          case COL_PRIORITY:
501            data.setValue (tr("Priority")); 
502            break;
503
504          default:
505            break;
506        }
507    }
508
509  return data;
510}
511
512QModelIndex
513FileTreeModel :: index (int row, int column, const QModelIndex& parent) const
514{
515  QModelIndex i;
516
517  if (hasIndex (row, column, parent))
518    {
519      FileTreeItem * parentItem;
520
521      if (!parent.isValid ())
522        parentItem = myRootItem;
523      else
524        parentItem = itemFromIndex (parent);
525
526      FileTreeItem * childItem = parentItem->child (row);
527
528      if (childItem)
529        i = createIndex (row, column, childItem);
530    }
531
532  return i;
533}
534
535QModelIndex
536FileTreeModel :: parent (const QModelIndex& child) const
537{
538  return parent (child, 0); // QAbstractItemModel::parent() wants col 0
539}
540
541QModelIndex
542FileTreeModel :: parent (const QModelIndex& child, int column) const
543{
544  QModelIndex parent;
545
546  if (child.isValid())
547    parent = indexOf (itemFromIndex(child)->parent(), column);
548
549  return parent;
550}
551
552int
553FileTreeModel :: rowCount (const QModelIndex& parent) const
554{
555  FileTreeItem * parentItem;
556
557  if (parent.isValid())
558    parentItem = itemFromIndex (parent);
559  else
560    parentItem = myRootItem;
561
562  return parentItem->childCount();
563}
564
565int
566FileTreeModel :: columnCount (const QModelIndex &parent) const
567{
568  Q_UNUSED(parent);
569
570  return NUM_COLUMNS;
571}
572
573QModelIndex
574FileTreeModel :: indexOf (FileTreeItem * item, int column) const
575{
576  if (!item || item==myRootItem)
577    return QModelIndex();
578
579  return createIndex(item->row(), column, item);
580}
581
582void
583FileTreeModel :: clearSubtree (const QModelIndex& top)
584{
585  size_t i = rowCount (top);
586
587  while (i > 0)
588    clearSubtree(index(--i, 0, top));
589
590  FileTreeItem * const item = itemFromIndex (top);
591  if (item == 0)
592    return;
593
594  if (item->fileIndex () != -1)
595    myIndexCache.remove (item->fileIndex ());
596
597  delete item;
598}
599
600void
601FileTreeModel :: clear ()
602{
603  beginResetModel ();
604  clearSubtree (QModelIndex());
605  endResetModel ();
606
607  assert (myIndexCache.isEmpty ());
608}
609
610FileTreeItem *
611FileTreeModel :: findItemForFileIndex (int fileIndex) const
612{
613  return myIndexCache.value (fileIndex, 0);
614}
615
616void
617FileTreeModel :: addFile (int                   fileIndex,
618                          const QString       & filename,
619                          bool                  wanted,
620                          int                   priority,
621                          uint64_t              totalSize,
622                          uint64_t              have,
623                          QList<QModelIndex>  & rowsAdded,
624                          bool                  updateFields)
625{
626  bool added = false;
627  FileTreeItem * item;
628  QStringList tokens = filename.split (QChar::fromLatin1('/'));
629
630  item = findItemForFileIndex (fileIndex);
631
632  if (item) // this file is already in the tree, we've added this
633    {
634      QModelIndex indexWithChangedParents;
635      while (!tokens.isEmpty())
636        {
637          const QString token = tokens.takeLast();
638          const std::pair<int,int> changed = item->update (token, wanted, priority, have, updateFields);
639          if (changed.first >= 0)
640            {
641              dataChanged (indexOf (item, changed.first), indexOf (item, changed.second));
642              if (!indexWithChangedParents.isValid () &&
643                  changed.first <= COL_PRIORITY && changed.second >= COL_SIZE)
644                indexWithChangedParents = indexOf (item, 0);
645            }
646          item = item->parent();
647        }
648      assert (item == myRootItem);
649      if (indexWithChangedParents.isValid ())
650        parentsChanged (indexWithChangedParents, COL_SIZE, COL_PRIORITY);
651    }
652  else // we haven't build the FileTreeItems for these tokens yet
653    {
654      item = myRootItem;
655      while (!tokens.isEmpty())
656        {
657          const QString token = tokens.takeFirst();
658          FileTreeItem * child(item->child(token));
659          if (!child)
660            {
661              added = true;
662              QModelIndex parentIndex (indexOf(item, 0));
663              const int n (item->childCount());
664
665              beginInsertRows (parentIndex, n, n);
666              if (tokens.isEmpty())
667                child = new FileTreeItem (token, fileIndex, totalSize);
668              else
669                child = new FileTreeItem (token);
670              item->appendChild (child);
671              endInsertRows ();
672
673              rowsAdded.append (indexOf(child, 0));
674            }
675          item = child;
676        }
677
678      if (item != myRootItem)
679        {
680          assert (item->fileIndex() == fileIndex);
681          assert (item->totalSize() == totalSize);
682
683          myIndexCache[fileIndex] = item;
684
685          const std::pair<int,int> changed = item->update (item->name(), wanted, priority, have, added || updateFields);
686          if (changed.first >= 0)
687            dataChanged (indexOf (item, changed.first), indexOf (item, changed.second));
688        }
689    }
690}
691
692void
693FileTreeModel :: parentsChanged (const QModelIndex& index, int firstColumn, int lastColumn)
694{
695  assert (firstColumn <= lastColumn);
696
697  QModelIndex walk = index;
698
699  for (;;)
700    {
701      walk = parent (walk, firstColumn);
702      if (!walk.isValid ())
703        break;
704
705      dataChanged (walk, walk.sibling (walk.row (), lastColumn));
706    }
707}
708
709void
710FileTreeModel :: subtreeChanged (const QModelIndex& index, int firstColumn, int lastColumn)
711{
712  assert (firstColumn <= lastColumn);
713
714  const int childCount = rowCount (index);
715  if (!childCount)
716    return;
717
718  // tell everyone that this tier changed
719  dataChanged (index.child (0, firstColumn), index.child (childCount - 1, lastColumn));
720
721  // walk the subtiers
722  for (int i=0; i<childCount; ++i)
723    subtreeChanged (index.child (i, 0), firstColumn, lastColumn);
724}
725
726void
727FileTreeModel :: clicked (const QModelIndex& index)
728{
729  const int column (index.column());
730
731  if (!index.isValid())
732    return;
733
734  if (column == COL_WANTED)
735    {
736      bool want;
737      QSet<int> file_ids;
738      FileTreeItem * item;
739
740      item = itemFromIndex (index);
741      item->twiddleWanted (file_ids, want);
742      emit wantedChanged (file_ids, want);
743
744      dataChanged (index, index);
745      parentsChanged (index, COL_SIZE, COL_WANTED);
746      subtreeChanged (index, COL_WANTED, COL_WANTED);
747    }
748  else if (column == COL_PRIORITY)
749    {
750      int priority;
751      QSet<int> file_ids;
752      FileTreeItem * item;
753
754      item = itemFromIndex (index);
755      item->twiddlePriority (file_ids, priority);
756      emit priorityChanged (file_ids, priority);
757
758      dataChanged (index, index);
759      parentsChanged (index, column, column);
760      subtreeChanged (index, column, column);
761    }
762}
763
764void
765FileTreeModel :: doubleClicked (const QModelIndex& index)
766{
767  if (!index.isValid())
768    return;
769
770  const int column (index.column());
771  if (column == COL_WANTED || column == COL_PRIORITY)
772    return;
773
774  FileTreeItem * item = itemFromIndex (index);
775
776  if (item->childCount () == 0 && item->isComplete ())
777    emit openRequested (item->path ());
778}
779
780/****
781*****
782****/
783
784QSize
785FileTreeDelegate :: sizeHint(const QStyleOptionViewItem& item, const QModelIndex& index) const
786{
787  QSize size;
788
789  switch(index.column())
790    {
791      case COL_PROGRESS:
792      case COL_WANTED:
793        size = QSize(20, 1);
794        break;
795
796      default:
797        size = QItemDelegate::sizeHint (item, index);
798    }
799
800  size.rheight() += 8; // make the spacing a little nicer
801  return size;
802}
803
804void
805FileTreeDelegate :: paint (QPainter                    * painter,
806                           const QStyleOptionViewItem  & option,
807                           const QModelIndex           & index) const
808{
809  const int column(index.column());
810
811  if ((column != COL_PROGRESS) && (column != COL_WANTED))
812    {
813      QItemDelegate::paint(painter, option, index);
814      return;
815    }
816
817  QStyle * style (QApplication :: style());
818
819  painter->save();
820  QItemDelegate::drawBackground (painter, option, index);
821
822  if(column == COL_PROGRESS)
823    {
824      QStyleOptionProgressBar p;
825      p.state = option.state | QStyle::State_Small;
826      p.direction = QApplication::layoutDirection();
827      p.rect = option.rect;
828      p.rect.setSize (QSize(option.rect.width()-2, option.rect.height()-8));
829      p.rect.moveCenter (option.rect.center());
830      p.fontMetrics = QApplication::fontMetrics();
831      p.minimum = 0;
832      p.maximum = 100;
833      p.textAlignment = Qt::AlignCenter;
834      p.textVisible = true;
835      p.progress = (int)(100.0*index.data().toDouble());
836      p.text = QString().sprintf("%d%%", p.progress);
837      style->drawControl(QStyle::CE_ProgressBar, &p, painter);
838    }
839  else if(column == COL_WANTED)
840    {
841      QStyleOptionButton o;
842      o.state = option.state;
843      o.direction = QApplication::layoutDirection();
844      o.rect.setSize (QSize(20, option.rect.height()));
845      o.rect.moveCenter (option.rect.center());
846      o.fontMetrics = QApplication::fontMetrics();
847      switch (index.data().toInt())
848        {
849          case Qt::Unchecked: o.state |= QStyle::State_Off; break;
850          case Qt::Checked:   o.state |= QStyle::State_On; break;
851          default:            o.state |= QStyle::State_NoChange;break;
852        }
853      style->drawControl (QStyle::CE_CheckBox, &o, painter);
854    }
855
856  QItemDelegate::drawFocus (painter, option, option.rect);
857  painter->restore();
858}
859
860/****
861*****
862*****
863*****
864****/
865
866FileTreeView :: FileTreeView (QWidget * parent, bool isEditable):
867  QTreeView (parent),
868  myModel (this, isEditable),
869  myProxy (new QSortFilterProxyModel()),
870  myDelegate (this)
871{
872  setSortingEnabled (true);
873  setAlternatingRowColors (true);
874  setSelectionBehavior (QAbstractItemView::SelectRows);
875  setSelectionMode (QAbstractItemView::ExtendedSelection);
876  myProxy->setSourceModel (&myModel);
877  setModel (myProxy);
878  setItemDelegate (&myDelegate);
879  setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
880  sortByColumn (COL_NAME, Qt::AscendingOrder);
881  installEventFilter (this);
882
883  for (int i=0; i<NUM_COLUMNS; ++i)
884    {
885      setColumnHidden (i, (i<FIRST_VISIBLE_COLUMN) || (LAST_VISIBLE_COLUMN<i));
886
887#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
888      header()->setResizeMode(i, QHeaderView::Interactive);
889#else
890      header()->setSectionResizeMode(i, QHeaderView::Interactive);
891#endif
892    }
893
894  connect (this, SIGNAL(clicked(const QModelIndex&)),
895           this, SLOT(onClicked(const QModelIndex&)));
896
897  connect (this, SIGNAL(doubleClicked(const QModelIndex&)),
898           this, SLOT(onDoubleClicked(const QModelIndex&)));
899
900  connect (&myModel, SIGNAL(priorityChanged(const QSet<int>&, int)),
901           this,     SIGNAL(priorityChanged(const QSet<int>&, int)));
902
903  connect (&myModel, SIGNAL(wantedChanged(const QSet<int>&, bool)),
904           this,     SIGNAL(wantedChanged(const QSet<int>&, bool)));
905
906  connect (&myModel, SIGNAL(pathEdited(const QString&, const QString&)),
907           this,     SIGNAL(pathEdited(const QString&, const QString&)));
908
909  connect (&myModel, SIGNAL (openRequested (const QString&)),
910           this,     SLOT (onOpenRequested (const QString&)),
911           Qt::QueuedConnection);
912}
913
914FileTreeView :: ~FileTreeView ()
915{
916  myProxy->deleteLater();
917}
918
919void
920FileTreeView :: onClicked (const QModelIndex& proxyIndex)
921{
922  const QModelIndex modelIndex = myProxy->mapToSource (proxyIndex);
923  myModel.clicked (modelIndex);
924}
925
926void
927FileTreeView :: onDoubleClicked (const QModelIndex& proxyIndex)
928{
929  const QModelIndex modelIndex = myProxy->mapToSource (proxyIndex);
930  myModel.doubleClicked (modelIndex);
931}
932
933void
934FileTreeView :: onOpenRequested (const QString& path)
935{
936  if (state () == EditingState)
937    return;
938
939  emit openRequested (path);
940}
941
942bool
943FileTreeView :: eventFilter (QObject * o, QEvent * event)
944{
945  // this is kind of a hack to get the last three columns be the
946  // right size, and to have the filename column use whatever
947  // space is left over...
948  if ((o == this) && (event->type() == QEvent::Resize))
949    {
950      QResizeEvent * r = dynamic_cast<QResizeEvent*>(event);
951      int left = r->size().width();
952      const QFontMetrics fontMetrics(font());
953      for (int column=FIRST_VISIBLE_COLUMN; column<=LAST_VISIBLE_COLUMN; ++column)
954        {
955          if (column == COL_NAME)
956            continue;
957          if (isColumnHidden (column))
958            continue;
959
960          QString header;
961          if (column == COL_SIZE)
962            header = "999.9 KiB";
963          else
964            header = myModel.headerData (column, Qt::Horizontal).toString();
965          header += "    ";
966          const int width = fontMetrics.size (0, header).width();
967          setColumnWidth (column, width);
968            left -= width;
969        }
970      left -= 20; // not sure why this is necessary.  it works in different themes + font sizes though...
971      setColumnWidth(COL_NAME, std::max(left,0));
972    }
973
974  // handle using the keyboard to toggle the
975  // wanted/unwanted state or the file priority
976  else if (event->type () == QEvent::KeyPress && state () != EditingState)
977    {
978      switch (static_cast<QKeyEvent*> (event)->key ())
979        {
980        case Qt::Key_Space:
981          foreach (const QModelIndex& i, selectionModel ()->selectedRows (COL_WANTED))
982            clicked (i);
983          break;
984
985        case Qt::Key_Enter:
986        case Qt::Key_Return:
987          foreach (const QModelIndex& i, selectionModel ()->selectedRows (COL_PRIORITY))
988            clicked (i);
989          break;
990        }
991    }
992
993  return false;
994}
995
996void
997FileTreeView :: update (const FileList& files, bool updateFields)
998{
999  foreach (const TrFile file, files)
1000    {
1001      QList<QModelIndex> added;
1002      myModel.addFile (file.index, file.filename, file.wanted, file.priority, file.size, file.have, added, updateFields);
1003      foreach (QModelIndex i, added)
1004        expand (myProxy->mapFromSource(i));
1005    }
1006}
1007
1008void
1009FileTreeView :: clear ()
1010{
1011  myModel.clear();
1012}
Note: See TracBrowser for help on using the repository browser.