RabbitCommon v2.3.3
Loading...
Searching...
No Matches
FileBrowser.cpp
1// Author: Kang Lin <kl222@126.com>
2
3#include <QSettings>
4#include <QPushButton>
5#include <QScrollBar>
6#include <QToolButton>
7#include <QComboBox>
8#include <QMenu>
9#include <QWidgetAction>
10#include <QDesktopServices>
11#include <QSplitter>
12#include <QVBoxLayout>
13#include <QHBoxLayout>
14#include <QSpacerItem>
15#include <QToolBar>
16#include <QLoggingCategory>
17#include <QScreen>
18#include <QApplication>
19#include <QHeaderView>
20#include <QMimeDatabase>
21#include <QCheckBox>
22#include <QLineEdit>
23#include <QTextDocument>
24#include <QTextCursor>
25#include <QProcess>
26#include <QDir>
27#include "RabbitCommonDir.h"
28#include "UndoCommand.h"
29#include "FileBroserTreeView.h"
30#include "FileBrowser.h"
31
32static Q_LOGGING_CATEGORY(log, "RabbitCommon.Browser.File")
33CFileBrowser::CFileBrowser(QWidget *parent)
34 : QWidget{parent}
35 , m_pSpliter(nullptr)
36 , m_pUndoStack(nullptr)
37 , m_pModel(nullptr)
38 , m_pFilter(nullptr)
39 , m_pTree(nullptr)
40 , m_pList(nullptr)
41 , m_pTable(nullptr)
42 , m_pTextEdit(nullptr)
43 , m_pHiddenFile(nullptr)
44 , m_pAssociated(nullptr)
45 , m_pOrientation(nullptr)
46{
47 bool check = false;
48 setAttribute(Qt::WA_DeleteOnClose);
49 setWindowTitle(tr("File browser"));
50 setWindowIcon(QIcon::fromTheme("browser"));
51
52 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
53 QSettings::IniFormat);
54
55 int nWidth = 600;
56 int nHeight = 450;
57 QRect rect;
58 if(parent) {
59 rect = parent->frameGeometry();
60 } else {
61 QScreen* pScreen = QApplication::primaryScreen();
62 if(pScreen)
63 rect = pScreen->geometry();
64 }
65 rect.setLeft((rect.width() - nWidth) / 2);
66 rect.setTop((rect.height() - nHeight) / 2);
67 rect.setWidth(nWidth);
68 rect.setHeight(nHeight);
69 setGeometry(rect);
70
71 QVBoxLayout* pLayout = new QVBoxLayout(this);
72
73 do {
74 m_pModel = new QFileSystemModel(this);
75 if(!m_pModel) break;;
76 // m_pModel->setReadOnly(false);
77 /*
78 m_pModel->setFilter(QDir::AllDirs | QDir::Drives
79 | QDir::NoDotAndDotDot);//*/
80
81 if(!pLayout) break;
82 setLayout(pLayout);
83 m_pSpliter = new QSplitter(this);
84 if(!m_pSpliter) break;
85
86 m_pTree = new CFileBroserTreeView(m_pSpliter);
87 m_pTree->setAutoScroll(true);
88
89 /*
90 m_pFilter = new QSortFilterProxyModel(m_pTree);
91 m_pFilter->setSourceModel(m_pModel);
92 m_pFilter->setFilterCaseSensitivity(Qt::CaseInsensitive); //大小写不敏感
93 m_pFilter->setAutoAcceptChildRows(true); //父项满足时不过滤子项
94 m_pFilter->setRecursiveFilteringEnabled(true); //递归匹配满足的子节点,父类可见
95 //指定初始化过滤列
96 m_pFilter->setFilterKeyColumn(0);
97 m_pTree->setModel(m_pFilter); //*/
98
99 m_pTable = new QTableView(m_pSpliter);
100 //m_pList = new QListView(pSpliter);
101 m_pTextEdit = new QTextEdit(m_pSpliter);
102
103 QString szTitle;
104 QToolBar* pToolBar = new QToolBar(this);
105 szTitle = tr("Close");
106 QAction* pAction = pToolBar->addAction(QIcon::fromTheme("window-close"),
107 szTitle, this, &CFileBrowser::close);
108 pAction->setStatusTip(szTitle);
109
110 m_pUndoStack = new QUndoStack(this);
111 if(!m_pUndoStack) break;
112#ifndef QT_NO_ACTION
113 QList<QAction*> lstUndo;
114 pAction = m_pUndoStack->createUndoAction(this);
115 pAction->setIcon(QIcon::fromTheme("edit-undo"));
116 lstUndo << pAction;
117 pAction = m_pUndoStack->createRedoAction(this);
118 pAction->setIcon(QIcon::fromTheme("edit-redo"));
119 lstUndo << pAction;
120 pToolBar->addActions(lstUndo);
121#endif
122
123 /*
124 szTitle = tr("New folder");
125 pAction = pToolBar->addAction(
126 QIcon::fromTheme("folder-new"), szTitle,
127 this, [&](){
128 QModelIndex index = m_pTree->currentIndex();
129 if(m_pModel->isDir(index))
130 m_pUndoStack->push(new CNewFolder(m_pModel->filePath(index)
131 + QDir::separator() + tr("NewFolder")));
132 });
133 pAction->setStatusTip(szTitle);
134
135 szTitle = tr("Delete folder");
136 pAction = pToolBar->addAction(
137 QIcon::fromTheme("edit-delete"), szTitle,
138 this, [&](){
139 QString szDir;
140 QModelIndex index = m_pTree->currentIndex();
141 szDir = m_pModel->filePath(index);
142 m_pUndoStack->push(new CDeleteFolder(szDir));
143 });
144 pAction->setStatusTip(szTitle);
145
146 szTitle = tr("Update");
147 pAction = pToolBar->addAction(
148 QIcon::fromTheme("system-software-update"), szTitle,
149 this, [&](){
150 QModelIndex index = m_pTree->currentIndex();
151 m_pTree->update(index);
152 });
153 pAction->setStatusTip(szTitle);
154 */
155
156 pToolBar->addAction(QIcon::fromTheme("go-up"), tr("Up folder"),
157 this, [&](){
158 QString szDir;
159 QModelIndex index = m_pTree->currentIndex();
160 szDir = m_pModel->filePath(index);
161 QDir d(szDir);
162 if(d.exists()) {
163 szDir = szDir + QDir::separator() + "..";
164 } else {
165 QFileInfo fi(szDir);
166 szDir = fi.absolutePath();
167 }
168 qDebug(log) << "Dir" << szDir;
169 auto i = m_pModel->index(szDir);
170 if(i.isValid())
171 {
172 m_pUndoStack->push(new CChange(i, this));
173 m_pTree->setCurrentIndex(i);
174 slotClicked(i);
175 m_pTree->doItemsLayout();
176 m_pTree->scrollTo(i);
177 }
178 });
179
180 szTitle = tr("Option");
181 QToolButton* pButtonOption = new QToolButton(pToolBar);
182 pButtonOption->setIcon(QIcon::fromTheme("emblem-system"));
183 pButtonOption->setStatusTip(szTitle);
184 pButtonOption->setPopupMode(QToolButton::InstantPopup);
185 QMenu* pMenuOption = new QMenu(szTitle, pButtonOption);
186 pButtonOption->setMenu(pMenuOption);
187 pToolBar->addWidget(pButtonOption);
188
189 szTitle = tr("Hidden file");
190 m_pHiddenFile = pMenuOption->addAction(
191 szTitle, this,
192 [&]() {
193 bool checked = m_pHiddenFile->isChecked();
194 if(checked)
195 m_pModel->setFilter(m_pModel->filter() | QDir::Hidden);
196 else
197 m_pModel->setFilter(m_pModel->filter() & (~QDir::Hidden));
198 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
199 QSettings::IniFormat);
200 set.setValue(GetSetPrefix() + "/HiddenFile", checked);
201 });
202 check = set.value(GetSetPrefix() + "/HiddenFile", check).toBool();
203 m_pHiddenFile->setStatusTip(szTitle);
204 m_pHiddenFile->setToolTip(szTitle);
205 m_pHiddenFile->setCheckable(true);
206 m_pHiddenFile->setChecked(check);
207 if(check)
208 m_pModel->setFilter(m_pModel->filter() | QDir::Hidden);
209 else
210 m_pModel->setFilter(m_pModel->filter() & (~QDir::Hidden));
211
212 szTitle = tr("Open with the System Associated Program");
213 m_pAssociated = pMenuOption->addAction(
214 szTitle, this, [&](){
215 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
216 QSettings::IniFormat);
217 set.setValue(GetSetPrefix() + "/SystemAssociatedProgram", m_pAssociated->isChecked());
218 });
219 m_pAssociated->setStatusTip(szTitle);
220 m_pAssociated->setToolTip(szTitle);
221 m_pAssociated->setCheckable(true);
222 m_pAssociated->setChecked(
223 set.value(
224 GetSetPrefix() + "/SystemAssociatedProgram",
225 m_pAssociated->isChecked()).toBool());
226
227 szTitle = tr("Horizontal");
228 m_pOrientation = pMenuOption->addAction(
229 szTitle, this, [&](){
230 bool checked = m_pOrientation->isChecked();
231 if(checked)
232 m_pSpliter->setOrientation(Qt::Horizontal);
233 else
234 m_pSpliter->setOrientation(Qt::Vertical);
235 QSettings set(RabbitCommon::CDir::Instance()->GetFileUserConfigure(),
236 QSettings::IniFormat);
237 set.setValue(GetSetPrefix() + "/Spliter/Horizontal", checked);
238 });
239 m_pOrientation->setStatusTip(szTitle);
240 m_pOrientation->setToolTip(szTitle);
241 m_pOrientation->setCheckable(true);
242#if defined(Q_OS_ANDROID)
243 check = false;
244#else
245 check = true;
246#endif
247 check = set.value(GetSetPrefix() + "/Spliter/Horizontal", check).toBool();
248 m_pOrientation->setChecked(check);
249 if(check)
250 m_pSpliter->setOrientation(Qt::Horizontal);
251 else
252 m_pSpliter->setOrientation(Qt::Vertical);
253
254 QComboBox* pUrl = new QComboBox(pToolBar);
255 pUrl->setEditable(true);
256 pUrl->setSizePolicy(QSizePolicy::Policy::Expanding,
257 QSizePolicy::Policy::Preferred);
258 check = connect(
259 pUrl, &QComboBox::currentTextChanged, this,
260 [&](const QString &szText) {
261 qDebug(log) << "QComboBox::currentTextChanged";
262 QComboBox* pUrl = qobject_cast<QComboBox*>(sender());
263 if(!pUrl) return;
264 QModelIndex index = m_pModel->index(szText);
265 if(index.isValid()) {
266 m_pUndoStack->push(new CChange(index, this));
267 m_pTree->setCurrentIndex(index);
268 slotClicked(index);
269 m_pTree->doItemsLayout();
270 m_pTree->scrollTo(index);
271 int index = pUrl->findText(szText);
272 if(-1 == index)
273 pUrl->addItem(szText);
274 }
275 });
276 Q_ASSERT(check);
277 pToolBar->addWidget(pUrl);
278
279 pLayout->addWidget(pToolBar);
280 pLayout->addWidget(m_pSpliter);
281 /*
282 QPushButton* pClose = new QPushButton(QIcon::fromTheme("window-close"), tr("Close"), this);
283 QHBoxLayout* pCloseLayout = new QHBoxLayout(this);
284 pCloseLayout->addStretch();
285 check = connect(pClose, &QPushButton::clicked, this, &CFileBrowser::reject);
286 pCloseLayout->addWidget(pClose);
287 pCloseLayout->addStretch();
288 Q_ASSERT(check);
289 pLayout->addLayout(pCloseLayout);//*/
290
291 if(m_pTree) {
292 m_pTree->setModel(m_pModel);
293 m_pTree->setHeaderHidden(true);
294 // 注意:必须在 setModel 之后才会生效
295 m_pTree->header()->hideSection(1);
296 m_pTree->header()->hideSection(2);
297 m_pTree->header()->hideSection(3);
298 m_pSpliter->addWidget(m_pTree);
299
300 /*
301 if(m_pTable) {
302 check = connect(m_pTree, &QTreeView::clicked,
303 m_pTable, &QTableView::setRootIndex);
304 Q_ASSERT(check);
305 }
306 if(m_pList) {
307 check = connect(m_pTree, &QTreeView::clicked,
308 m_pList, &QListView::setRootIndex);
309 Q_ASSERT(check);
310 }//*/
311 check = connect(
312 m_pTree, &QTreeView::clicked,
313 this, [&](const QModelIndex& index){
314 m_pUndoStack->push(new CChange(index, this));
315 slotClicked(index);
316 });
317 Q_ASSERT(check);
318 check = connect(
319 m_pTree, &QTreeView::doubleClicked,
320 this, [&](const QModelIndex &index) {
321 if (m_pModel) {
322 QString szFile = m_pModel->filePath(index);
323 emit sigDoubleClicked(szFile, m_pModel->isDir(index));
324 if(m_pModel->isDir(index)) {
325 return;
326 }
327 m_pTable->hide();
328 ShowFile(szFile);
329 }
330 });
331 Q_ASSERT(check);
332 check = connect(m_pTree, &CFileBroserTreeView::sigChanged,
333 this, &CFileBrowser::sigChanged);
334 Q_ASSERT(check);
335 }
336
337 if(m_pList) {
338 m_pSpliter->addWidget(m_pList);
339 m_pList->setModel(m_pModel);
340 }
341
342 if(m_pTable) {
343 m_pTable->hide();
344 m_pSpliter->addWidget(m_pTable);
345 m_pTable->setModel(m_pModel);
346 m_pTable->setShowGrid(false);
347 m_pTable->verticalHeader()->hide();
348 check = connect(
349 m_pTable, &QTableView::clicked,
350 this, [&](const QModelIndex &index) {
351 if (m_pModel) {
352 m_pUndoStack->push(new CChange(index, this));
353 QString szDir = m_pModel->filePath(index);
354 m_pTree->setCurrentIndex(index);
355 m_pTree->expand(index);
356 if(m_pModel->isDir(index)) {
357 m_pModel->setRootPath(szDir);
358 m_pTable->setRootIndex(m_pModel->index(szDir));
359 if(!m_pTextEdit->isHidden())
360 m_pTextEdit->hide();
361 }
362#if defined(Q_OS_ANDROID)
363 else {
364 ShowFile(szDir);
365 }
366#endif
367 }
368 });
369 Q_ASSERT(check);
370 check = connect(m_pTable, &QTableView::doubleClicked,
371 this, [&](const QModelIndex &index){
372 QString szFile = m_pModel->filePath(index);
373 ShowFile(szFile);
374 emit sigDoubleClicked(szFile, m_pModel->isDir(index));
375 });
376 Q_ASSERT(check);
377 }
378
379 if(m_pTextEdit) {
380 m_pTextEdit->hide();
381 m_pSpliter->addWidget(m_pTextEdit);
382 }
383 setRootPath("");
384 return;
385 } while(0);
386
387 if(m_pSpliter)
388 delete m_pSpliter;
389 if(m_pUndoStack)
390 delete m_pUndoStack;
391 if(m_pModel)
392 delete m_pModel;
393 if(pLayout)
394 delete pLayout;
395 if(m_pTree)
396 delete m_pTree;
397 if(m_pList)
398 delete m_pList;
399 if(m_pTable)
400 delete m_pTable;
401 if(m_pTextEdit)
402 delete m_pTextEdit;
403}
404
405CFileBrowser::~CFileBrowser()
406{
407 qDebug(log) << "CFileBrowser::~CFileBrowser()";
408}
409
410void CFileBrowser::setRootPath(const QString dir)
411{
412 m_pModel->setRootPath(dir);
413 m_pTree->setRootIndex(m_pModel->index(dir));
414}
415
417{
418 return m_pModel->rootPath();
419}
420
421QString CFileBrowser::GetSetPrefix()
422{
423 QString szPrefix("FileBrowser");
424 if(!this->objectName().isEmpty())
425 szPrefix = szPrefix + "/" + this->objectName();
426 return szPrefix;
427}
428
429QString CFileBrowser::readFile(const QString &filePath)
430{
431 // Don't issue errors for an empty path, as the initial binding
432 // will result in an empty path, and that's OK.
433 if (filePath.isEmpty())
434 return {};
435
436 QFile file(filePath);
437
438 static const QMimeDatabase db;
439 const QMimeType mime = db.mimeTypeForFile(QFileInfo(file));
440 qDebug(log) << mime << filePath;
441
442 if(mime.name() =="application/x-executable")
443 {
444 if(QProcess::startDetached(filePath))
445 return QString();
446 else
447 qCritical(log) << "Start fail:" << filePath;
448 } else if(mime.name().contains("image", Qt::CaseInsensitive)) {
449 QImage img;
450 if(img.load(filePath)) {
451 QTextCursor cursor = m_pTextEdit->textCursor();
452 QTextDocument *document = m_pTextEdit->document();
453 if(document) {
454 document->clear();
455 document->addResource(
456 QTextDocument::ImageResource, QUrl("image"), img);
457 }
458 cursor.insertImage("image");
459 return QString();
460 } else
461 qCritical(log) << "Error: load image" << filePath;
462 } else {
463 if (file.size() >= 2000000)
464 return tr("File size is too big.\nYou can read files up to %1 MB.").arg(2);
465
466 // Check if the mimetype is supported and return the content.
467 const auto mimeTypesForFile = mime.parentMimeTypes();
468 for (const auto &m : mimeTypesForFile) {
469 qDebug(log) << "m" << m;
470 if (m.contains("text", Qt::CaseInsensitive)
471 || mime.comment().contains("text", Qt::CaseInsensitive)) {
472 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
473 return tr("Error opening the File!");
474
475 QTextStream stream(&file);
476 return stream.readAll();
477 }
478 }
479 }
480 return tr("Filetype ") + mime.name() + " not supported!";
481}
482
483int CFileBrowser::ShowFile(const QString &szFile)
484{
485 if(m_pAssociated->isChecked()) {
486 QUrl url = QUrl::fromLocalFile(szFile);
487 if(QDesktopServices::openUrl(url))
488 return 0;
489 }
490
491 QString szText = readFile(szFile);
492 if(!szText.isEmpty())
493 m_pTextEdit->setText(szText);
494 if(m_pTextEdit->isHidden())
495 m_pTextEdit->show();
496 return 0;
497}
498
499void CFileBrowser::slotClicked(const QModelIndex &index)
500{
501 qDebug(log) << "CFileBrowser::slotClicked" << index;
502 if (m_pModel) {
503 if(m_pModel->isDir(index)) {
504 m_pTable->setRootIndex(index);
505 m_pTable->show();
506 m_pTextEdit->hide();
507 return;
508 }
509 m_pTable->hide();
510#if defined(Q_OS_ANDROID)
511 QString szFile = m_pModel->filePath(index);
512 ShowFile(szFile);
513#endif
514 }
515}
516
517CDlgFileBrowser::CDlgFileBrowser(QWidget *parent) : QDialog(parent)
518 ,m_pFileBrowser(nullptr)
519{
520 m_pFileBrowser = new CFileBrowser(this);
521 setWindowIcon(m_pFileBrowser->windowIcon());
522 setWindowTitle(m_pFileBrowser->windowTitle());
523
524 bool check = connect(m_pFileBrowser, SIGNAL(destroyed(QObject*)),
525 this, SLOT(close()));
526 Q_ASSERT(check);
527
528 QHBoxLayout *layout = new QHBoxLayout(this);
529 layout->addWidget(m_pFileBrowser);
530 layout->setContentsMargins(0, 0, 0, 0);
531 setLayout(layout);
532}
File browser.
Definition FileBrowser.h:26
void setRootPath(const QString dir)
Set root path.
QString rootPath() const
Get root path.