~lubuntu-dev/lxde/libfm-qt-debian-git

1 by Alf Gaida
Adding upstream version 0.10.0+20151214.
1
/*
2
 * Copyright (C) 2012 - 2015  Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
3
 *
4
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * This library is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with this library; if not, write to the Free Software
16
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17
 *
18
 */
19
20
21
#include "foldermodel.h"
22
#include "icontheme.h"
23
#include <iostream>
24
#include <QtAlgorithms>
25
#include <QVector>
26
#include <qmimedata.h>
27
#include <QMimeData>
28
#include <QByteArray>
29
#include <QPixmap>
30
#include <QPainter>
31
#include "utilities.h"
32
#include "fileoperation.h"
33
#include "thumbnailloader.h"
34
35
namespace Fm {
36
37
FolderModel::FolderModel() :
38
  folder_(nullptr) {
39
/*
40
    ColumnIcon,
41
    ColumnName,
42
    ColumnFileType,
43
    ColumnMTime,
44
    NumOfColumns
45
*/
46
  thumbnailRefCounts.reserve(4);
47
48
  // reload all icons when the icon theme is changed
49
  connect(IconTheme::instance(), &IconTheme::changed, this, &FolderModel::updateIcons);
50
}
51
52
FolderModel::~FolderModel() {
53
  qDebug("delete FolderModel");
54
55
  if(folder_)
56
    setFolder(nullptr);
57
58
  // if the thumbnail requests list is not empty, cancel them
59
  if(!thumbnailResults.empty()) {
60
    Q_FOREACH(FmThumbnailLoader* res, thumbnailResults) {
61
      ThumbnailLoader::cancel(res);
62
    }
63
  }
64
}
65
66
void FolderModel::setFolder(FmFolder* new_folder) {
67
  if(folder_) {
68
    removeAll();        // remove old items
69
    g_signal_handlers_disconnect_by_func(folder_, gpointer(onStartLoading), this);
70
    g_signal_handlers_disconnect_by_func(folder_, gpointer(onFinishLoading), this);
71
    g_signal_handlers_disconnect_by_func(folder_, gpointer(onFilesAdded), this);
72
    g_signal_handlers_disconnect_by_func(folder_, gpointer(onFilesChanged), this);
73
    g_signal_handlers_disconnect_by_func(folder_, gpointer(onFilesRemoved), this);
74
    g_object_unref(folder_);
75
  }
76
  if(new_folder) {
77
    folder_ = FM_FOLDER(g_object_ref(new_folder));
78
    g_signal_connect(folder_, "start-loading", G_CALLBACK(onStartLoading), this);
79
    g_signal_connect(folder_, "finish-loading", G_CALLBACK(onFinishLoading), this);
80
    g_signal_connect(folder_, "files-added", G_CALLBACK(onFilesAdded), this);
81
    g_signal_connect(folder_, "files-changed", G_CALLBACK(onFilesChanged), this);
82
    g_signal_connect(folder_, "files-removed", G_CALLBACK(onFilesRemoved), this);
83
    // handle the case if the folder is already loaded
84
    if(fm_folder_is_loaded(folder_))
85
      insertFiles(0, fm_folder_get_files(folder_));
86
  }
87
  else
88
    folder_ = nullptr;
89
}
90
91
void FolderModel::onStartLoading(FmFolder* folder, gpointer user_data) {
92
  FolderModel* model = static_cast<FolderModel*>(user_data);
93
  // remove all items
94
  model->removeAll();
95
}
96
97
void FolderModel::onFinishLoading(FmFolder* folder, gpointer user_data) {
98
  Q_UNUSED(folder)
99
  Q_UNUSED(user_data)
100
}
101
102
void FolderModel::onFilesAdded(FmFolder* folder, GSList* files, gpointer user_data) {
103
  FolderModel* model = static_cast<FolderModel*>(user_data);
104
  int n_files = g_slist_length(files);
105
  model->beginInsertRows(QModelIndex(), model->items.count(), model->items.count() + n_files - 1);
106
  for(GSList* l = files; l; l = l->next) {
107
    FmFileInfo* info = FM_FILE_INFO(l->data);
108
    FolderModelItem item(info);
109
/*
110
    if(fm_file_info_is_hidden(info)) {
111
      model->hiddenItems.append(item);
112
      continue;
113
    }
114
*/
115
    model->items.append(item);
116
  }
117
  model->endInsertRows();
118
}
119
120
//static
121
void FolderModel::onFilesChanged(FmFolder* folder, GSList* files, gpointer user_data) {
122
  FolderModel* model = static_cast<FolderModel*>(user_data);
123
  for(GSList* l = files; l; l = l->next) {
124
    FmFileInfo* info = FM_FILE_INFO(l->data);
125
    int row;
126
    QList<FolderModelItem>::iterator it = model->findItemByFileInfo(info, &row);
127
    if(it != model->items.end()) {
128
      FolderModelItem& item = *it;
129
      // try to update the item
130
      item.displayName = QString::fromUtf8(fm_file_info_get_disp_name(info));
131
      item.updateIcon();
132
      item.thumbnails.clear();
133
      QModelIndex index = model->createIndex(row, 0, &item);
134
      Q_EMIT model->dataChanged(index, index);
135
    }
136
  }
137
}
138
139
//static
140
void FolderModel::onFilesRemoved(FmFolder* folder, GSList* files, gpointer user_data) {
141
  FolderModel* model = static_cast<FolderModel*>(user_data);
142
  for(GSList* l = files; l; l = l->next) {
143
    FmFileInfo* info = FM_FILE_INFO(l->data);
144
    const char* name = fm_file_info_get_name(info);
145
    int row;
146
    QList<FolderModelItem>::iterator it = model->findItemByName(name, &row);
147
    if(it != model->items.end()) {
148
      model->beginRemoveRows(QModelIndex(), row, row);
149
      model->items.erase(it);
150
      model->endRemoveRows();
151
    }
152
  }
153
}
154
155
void FolderModel::insertFiles(int row, FmFileInfoList* files) {
156
  int n_files = fm_file_info_list_get_length(files);
157
  beginInsertRows(QModelIndex(), row, row + n_files - 1);
158
  for(GList* l = fm_file_info_list_peek_head_link(files); l; l = l->next) {
159
    FolderModelItem item(FM_FILE_INFO(l->data));
160
    items.append(item);
161
  }
162
  endInsertRows();
163
}
164
165
void FolderModel::removeAll() {
166
  if(items.empty())
167
    return;
168
  beginRemoveRows(QModelIndex(), 0, items.size() - 1);
169
  items.clear();
170
  endRemoveRows();
171
}
172
173
int FolderModel::rowCount(const QModelIndex & parent) const {
174
  if(parent.isValid())
175
    return 0;
176
  return items.size();
177
}
178
179
int FolderModel::columnCount (const QModelIndex & parent = QModelIndex()) const {
180
  if(parent.isValid())
181
    return 0;
182
  return NumOfColumns;
183
}
184
185
FolderModelItem* FolderModel::itemFromIndex(const QModelIndex& index) const {
186
  return reinterpret_cast<FolderModelItem*>(index.internalPointer());
187
}
188
189
FmFileInfo* FolderModel::fileInfoFromIndex(const QModelIndex& index) const {
190
  FolderModelItem* item = itemFromIndex(index);
191
  return item ? item->info : nullptr;
192
}
193
194
QVariant FolderModel::data(const QModelIndex & index, int role/* = Qt::DisplayRole*/) const {
195
  if(!index.isValid() || index.row() > items.size() || index.column() >= NumOfColumns) {
196
    return QVariant();
197
  }
198
  FolderModelItem* item = itemFromIndex(index);
199
  FmFileInfo* info = item->info;
200
201
  switch(role) {
202
    case Qt::ToolTipRole:
203
      return QVariant(item->displayName);
204
    case Qt::DisplayRole:  {
205
      switch(index.column()) {
206
        case ColumnFileName: {
207
          return QVariant(item->displayName);
208
        }
209
        case ColumnFileType: {
210
          FmMimeType* mime = fm_file_info_get_mime_type(info);
211
          const char* desc = fm_mime_type_get_desc(mime);
212
          return QString::fromUtf8(desc);
213
        }
214
        case ColumnFileMTime: {
215
          const char* name = fm_file_info_get_disp_mtime(info);
216
          return QString::fromUtf8(name);
217
        }
218
        case ColumnFileSize: {
219
          const char* name = fm_file_info_get_disp_size(info);
220
          return QString::fromUtf8(name);
221
        }
222
        case ColumnFileOwner: {
223
          const char* name = fm_file_info_get_disp_owner(info);
224
          return QString::fromUtf8(name);
225
        }
226
      }
227
    }
228
    case Qt::DecorationRole: {
229
      if(index.column() == 0) {
230
        // QPixmap pix = IconTheme::loadIcon(fm_file_info_get_icon(info), iconSize_);
231
        return QVariant(item->icon);
232
        // return QVariant(pix);
233
      }
234
      break;
235
    }
236
    case FileInfoRole:
237
      return qVariantFromValue((void*)info);
238
  }
239
  return QVariant();
240
}
241
242
QVariant FolderModel::headerData(int section, Qt::Orientation orientation, int role/* = Qt::DisplayRole*/) const {
243
  if(role == Qt::DisplayRole) {
244
    if(orientation == Qt::Horizontal) {
245
      QString title;
246
      switch(section) {
247
        case ColumnFileName:
248
          title = tr("Name");
249
          break;
250
        case ColumnFileType:
251
          title = tr("Type");
252
          break;
253
        case ColumnFileSize:
254
          title = tr("Size");
255
          break;
256
        case ColumnFileMTime:
257
          title = tr("Modified");
258
          break;
259
        case ColumnFileOwner:
260
          title = tr("Owner");
261
          break;
262
      }
263
      return QVariant(title);
264
    }
265
  }
266
  return QVariant();
267
}
268
269
QModelIndex FolderModel::index(int row, int column, const QModelIndex & parent) const {
270
  if(row <0 || row >= items.size() || column < 0 || column >= NumOfColumns)
271
    return QModelIndex();
272
  const FolderModelItem& item = items.at(row);
273
  return createIndex(row, column, (void*)&item);
274
}
275
276
QModelIndex FolderModel::parent(const QModelIndex & index) const {
277
  return QModelIndex();
278
}
279
280
Qt::ItemFlags FolderModel::flags(const QModelIndex& index) const {
281
  // FIXME: should not return same flags unconditionally for all columns
282
  Qt::ItemFlags flags;
283
  if(index.isValid()) {
284
    flags = Qt::ItemIsEnabled|Qt::ItemIsSelectable;
285
    if(index.column() == ColumnFileName)
286
      flags |= (Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled);
287
  }
288
  else {
289
    flags = Qt::ItemIsDropEnabled;
290
  }
291
  return flags;
292
}
293
294
// FIXME: this is very inefficient and should be replaced with a
295
// more reasonable implementation later.
296
QList<FolderModelItem>::iterator FolderModel::findItemByPath(FmPath* path, int* row) {
297
  QList<FolderModelItem>::iterator it = items.begin();
298
  int i = 0;
299
  while(it != items.end()) {
300
    FolderModelItem& item = *it;
301
    FmPath* item_path = fm_file_info_get_path(item.info);
302
    if(fm_path_equal(item_path, path)) {
303
      *row = i;
304
      return it;
305
    }
306
    ++it;
307
    ++i;
308
  }
309
  return items.end();
310
}
311
312
// FIXME: this is very inefficient and should be replaced with a
313
// more reasonable implementation later.
314
QList<FolderModelItem>::iterator FolderModel::findItemByName(const char* name, int* row) {
315
  QList<FolderModelItem>::iterator it = items.begin();
316
  int i = 0;
317
  while(it != items.end()) {
318
    FolderModelItem& item = *it;
319
    const char* item_name = fm_file_info_get_name(item.info);
320
    if(strcmp(name, item_name) == 0) {
321
      *row = i;
322
      return it;
323
    }
324
    ++it;
325
    ++i;
326
  }
327
  return items.end();
328
}
329
330
QList< FolderModelItem >::iterator FolderModel::findItemByFileInfo(FmFileInfo* info, int* row) {
331
  QList<FolderModelItem>::iterator it = items.begin();
332
  int i = 0;
333
  while(it != items.end()) {
334
    FolderModelItem& item = *it;
335
    if(item.info == info) {
336
      *row = i;
337
      return it;
338
    }
339
    ++it;
340
    ++i;
341
  }
342
  return items.end();
343
}
344
345
QStringList FolderModel::mimeTypes() const {
346
  qDebug("FolderModel::mimeTypes");
347
  QStringList types = QAbstractItemModel::mimeTypes();
348
  // now types contains "application/x-qabstractitemmodeldatalist"
349
  types << "text/uri-list";
350
  // types << "x-special/gnome-copied-files";
351
  return types;
352
}
353
354
QMimeData* FolderModel::mimeData(const QModelIndexList& indexes) const {
355
  QMimeData* data = QAbstractItemModel::mimeData(indexes);
356
  qDebug("FolderModel::mimeData");
357
  // build a uri list
358
  QByteArray urilist;
359
  urilist.reserve(4096);
360
361
  for(const auto &index : indexes) {
362
    FolderModelItem* item = itemFromIndex(index);
363
    if(item) {
364
      FmPath* path = fm_file_info_get_path(item->info);
365
      char* uri = fm_path_to_uri(path);
366
      urilist.append(uri);
367
      urilist.append('\n');
368
      g_free(uri);
369
    }
370
  }
371
  data->setData("text/uri-list", urilist);
372
373
  return data;
374
}
375
376
bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
377
  qDebug("FolderModel::dropMimeData");
378
  if(!folder_)
379
    return false;
380
  FmPath* destPath;
381
  if(parent.isValid()) { // drop on an item
382
    FmFileInfo* info;
383
    if(row == -1 && column == -1)
384
      info = fileInfoFromIndex(parent);
385
    else {
386
      QModelIndex itemIndex = parent.child(row, column);
387
      info = fileInfoFromIndex(itemIndex);
388
    }
389
    if(info)
390
      destPath = fm_file_info_get_path(info);
391
    else
392
      return false;
393
  }
394
  else { // drop on blank area of the folder
395
    destPath = path();
396
  }
397
398
  // FIXME: should we put this in dropEvent handler of FolderView instead?
399
  if(data->hasUrls()) {
400
    qDebug("drop action: %d", action);
401
    FmPathList* srcPaths = pathListFromQUrls(data->urls());
402
    switch(action) {
403
      case Qt::CopyAction:
404
        FileOperation::copyFiles(srcPaths, destPath);
405
        break;
406
      case Qt::MoveAction:
407
        FileOperation::moveFiles(srcPaths, destPath);
408
        break;
409
      case Qt::LinkAction:
410
        FileOperation::symlinkFiles(srcPaths, destPath);
411
      default:
412
        fm_path_list_unref(srcPaths);
413
        return false;
414
    }
415
    fm_path_list_unref(srcPaths);
416
    return true;
417
  }
418
  else if(data->hasFormat("application/x-qabstractitemmodeldatalist")) {
419
    return true;
420
  }
421
  return QAbstractListModel::dropMimeData(data, action, row, column, parent);
422
}
423
424
Qt::DropActions FolderModel::supportedDropActions() const {
425
  qDebug("FolderModel::supportedDropActions");
426
  return Qt::CopyAction|Qt::MoveAction|Qt::LinkAction;
427
}
428
429
// ask the model to load thumbnails of the specified size
430
void FolderModel::cacheThumbnails(const int size) {
431
  QVector<QPair<int, int>>::iterator it = thumbnailRefCounts.begin();
432
  while (it != thumbnailRefCounts.end()) {
433
    if (it->first == size) {
434
      ++it->second;
435
      return;
436
    } else ++it;
437
  }
438
  thumbnailRefCounts.append(QPair<int, int>(size, 1));
439
}
440
441
// ask the model to free cached thumbnails of the specified size
442
void FolderModel::releaseThumbnails(int size) {
443
  QVector<QPair<int, int> >::iterator it;
444
  for(it = thumbnailRefCounts.begin(); it != thumbnailRefCounts.end(); ++it) {
445
    if(it->first == size) {
446
      break;
447
    }
448
  }
449
  if(it != thumbnailRefCounts.end()) {
450
    --it->second;
451
    if(it->second == 0) {
452
      thumbnailRefCounts.erase(it);
453
454
      // remove thumbnails that ara queued for loading from thumbnailResults
455
      QLinkedList<FmThumbnailLoader*>::iterator it;
456
      for(it = thumbnailResults.begin(); it != thumbnailResults.end();) {
457
        QLinkedList<FmThumbnailLoader*>::iterator next = it + 1;
458
        FmThumbnailLoader* res = *it;
459
        if(ThumbnailLoader::size(res) == size) {
460
          ThumbnailLoader::cancel(res);
461
          thumbnailResults.erase(it);
462
        }
463
        it = next;
464
      }
465
466
      // remove all cached thumbnails of the specified size
467
      QList<FolderModelItem>::iterator itemIt;
468
      for(itemIt = items.begin(); itemIt != items.end(); ++itemIt) {
469
        FolderModelItem& item = *itemIt;
470
        item.removeThumbnail(size);
471
      }
472
    }
473
  }
474
}
475
476
void FolderModel::onThumbnailLoaded(FmThumbnailLoader* res, gpointer user_data) {
477
  FolderModel* pThis = reinterpret_cast<FolderModel*>(user_data);
478
  QLinkedList<FmThumbnailLoader*>::iterator it;
479
  for(it = pThis->thumbnailResults.begin(); it != pThis->thumbnailResults.end(); ++it) {
480
    if(*it == res) { // the thumbnail result is in our list
481
      pThis->thumbnailResults.erase(it); // remove it from the list
482
      FmFileInfo* info = ThumbnailLoader::fileInfo(res);
483
      int row = -1;
484
      // find the model item this thumbnail belongs to
485
      QList<FolderModelItem>::iterator it = pThis->findItemByFileInfo(info, &row);
486
      if(it != pThis->items.end()) {
487
        // the file is found in our model
488
        FolderModelItem& item = *it;
489
        QModelIndex index = pThis->createIndex(row, 0, (void*)&item);
490
        // store the image in the folder model item.
491
        int size = ThumbnailLoader::size(res);
492
        QImage image = ThumbnailLoader::image(res);
493
        FolderModelItem::Thumbnail* thumbnail = item.findThumbnail(size);
494
        thumbnail->image = image;
495
        // qDebug("thumbnail loaded for: %s, size: %d", item.displayName.toUtf8().constData(), size);
496
        if(image.isNull())
497
          thumbnail->status = FolderModelItem::ThumbnailFailed;
498
        else {
499
          thumbnail->status = FolderModelItem::ThumbnailLoaded;
500
          // FIXME: due to bugs in Qt's QStyledItemDelegate, if the image width and height
501
          // are not the same, painting errors will happen. It's quite unfortunate.
502
          // Let's do some padding to make its width and height equals.
503
          // This greatly decrease performance :-(
504
          // Later if we can re-implement our own item delegate, this can be avoided.
505
          QPixmap pixmap = QPixmap(size, size);
506
          pixmap.fill(QColor(0, 0, 0, 0)); // fill the pixmap with transparent color (alpha:0)
507
          QPainter painter(&pixmap);
508
          int x = (size - image.width()) / 2;
509
          int y = (size - image.height()) / 2;
510
          painter.drawImage(QPoint(x, y), image); // draw the image to the pixmap at center.
511
          // FIXME: should we cache QPixmap instead for performance reason?
512
          thumbnail->image = pixmap.toImage(); // convert it back to image
513
514
          // tell the world that we have the thumbnail loaded
515
          Q_EMIT pThis->thumbnailLoaded(index, size);
516
        }
517
      }
518
      break;
519
    }
520
  }
521
}
522
523
// get a thumbnail of size at the index
524
// if a thumbnail is not yet loaded, this will initiate loading of the thumbnail.
525
QImage FolderModel::thumbnailFromIndex(const QModelIndex& index, int size) {
526
  FolderModelItem* item = itemFromIndex(index);
527
  if(item) {
528
    FolderModelItem::Thumbnail* thumbnail = item->findThumbnail(size);
529
    // qDebug("FolderModel::thumbnailFromIndex: %d, %s", thumbnail->status, item->displayName.toUtf8().data());
530
    switch(thumbnail->status) {
531
      case FolderModelItem::ThumbnailNotChecked: {
532
        // load the thumbnail
533
        FmThumbnailLoader* res = ThumbnailLoader::load(item->info, size, onThumbnailLoaded, this);
534
        thumbnailResults.push_back(res);
535
        thumbnail->status = FolderModelItem::ThumbnailLoading;
536
        break;
537
      }
538
      case FolderModelItem::ThumbnailLoaded:
539
        return thumbnail->image;
540
      default:;
541
    }
542
  }
543
  return QImage();
544
}
545
546
void FolderModel::updateIcons() {
547
  QList<FolderModelItem>::iterator it = items.begin();
548
  for(;it != items.end(); ++it) {
549
    (*it).updateIcon();
550
  }
551
}
552
553
554
} // namespace Fm