vfilelist.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. #include <QtDebug>
  2. #include <QtWidgets>
  3. #include <QUrl>
  4. #include "vfilelist.h"
  5. #include "vconfigmanager.h"
  6. #include "dialog/vnewfiledialog.h"
  7. #include "dialog/vfileinfodialog.h"
  8. #include "vnote.h"
  9. #include "veditarea.h"
  10. #include "utils/vutils.h"
  11. #include "vfile.h"
  12. #include "vconfigmanager.h"
  13. extern VConfigManager *g_config;
  14. extern VNote *g_vnote;
  15. const QString VFileList::c_infoShortcutSequence = "F2";
  16. const QString VFileList::c_copyShortcutSequence = "Ctrl+C";
  17. const QString VFileList::c_cutShortcutSequence = "Ctrl+X";
  18. const QString VFileList::c_pasteShortcutSequence = "Ctrl+V";
  19. VFileList::VFileList(QWidget *parent)
  20. : QWidget(parent), VNavigationMode()
  21. {
  22. setupUI();
  23. initShortcuts();
  24. initActions();
  25. }
  26. void VFileList::setupUI()
  27. {
  28. fileList = new QListWidget(this);
  29. fileList->setContextMenuPolicy(Qt::CustomContextMenu);
  30. fileList->setSelectionMode(QAbstractItemView::ExtendedSelection);
  31. fileList->setDragDropMode(QAbstractItemView::InternalMove);
  32. fileList->setObjectName("FileList");
  33. QVBoxLayout *mainLayout = new QVBoxLayout;
  34. mainLayout->addWidget(fileList);
  35. mainLayout->setContentsMargins(0, 0, 0, 0);
  36. connect(fileList, &QListWidget::customContextMenuRequested,
  37. this, &VFileList::contextMenuRequested);
  38. connect(fileList, &QListWidget::itemClicked,
  39. this, &VFileList::handleItemClicked);
  40. connect(fileList->model(), &QAbstractItemModel::rowsMoved,
  41. this, &VFileList::handleRowsMoved);
  42. setLayout(mainLayout);
  43. }
  44. void VFileList::initShortcuts()
  45. {
  46. QShortcut *infoShortcut = new QShortcut(QKeySequence(c_infoShortcutSequence), this);
  47. infoShortcut->setContext(Qt::WidgetWithChildrenShortcut);
  48. connect(infoShortcut, &QShortcut::activated,
  49. this, [this](){
  50. fileInfo();
  51. });
  52. QShortcut *copyShortcut = new QShortcut(QKeySequence(c_copyShortcutSequence), this);
  53. copyShortcut->setContext(Qt::WidgetWithChildrenShortcut);
  54. connect(copyShortcut, &QShortcut::activated,
  55. this, [this](){
  56. copySelectedFiles();
  57. });
  58. QShortcut *cutShortcut = new QShortcut(QKeySequence(c_cutShortcutSequence), this);
  59. cutShortcut->setContext(Qt::WidgetWithChildrenShortcut);
  60. connect(cutShortcut, &QShortcut::activated,
  61. this, [this](){
  62. cutSelectedFiles();
  63. });
  64. QShortcut *pasteShortcut = new QShortcut(QKeySequence(c_pasteShortcutSequence), this);
  65. pasteShortcut->setContext(Qt::WidgetWithChildrenShortcut);
  66. connect(pasteShortcut, &QShortcut::activated,
  67. this, [this](){
  68. pasteFilesInCurDir();
  69. });
  70. }
  71. void VFileList::initActions()
  72. {
  73. newFileAct = new QAction(QIcon(":/resources/icons/create_note.svg"),
  74. tr("&New Note"), this);
  75. QString shortcutStr = QKeySequence(g_config->getShortcutKeySequence("NewNote")).toString();
  76. if (!shortcutStr.isEmpty()) {
  77. newFileAct->setText(tr("&New Note\t%1").arg(shortcutStr));
  78. }
  79. newFileAct->setToolTip(tr("Create a note in current folder"));
  80. connect(newFileAct, SIGNAL(triggered(bool)),
  81. this, SLOT(newFile()));
  82. deleteFileAct = new QAction(QIcon(":/resources/icons/delete_note.svg"),
  83. tr("&Delete"), this);
  84. deleteFileAct->setToolTip(tr("Delete selected note"));
  85. connect(deleteFileAct, SIGNAL(triggered(bool)),
  86. this, SLOT(deleteFile()));
  87. fileInfoAct = new QAction(QIcon(":/resources/icons/note_info.svg"),
  88. tr("&Info\t%1").arg(QKeySequence(c_infoShortcutSequence).toString()), this);
  89. fileInfoAct->setToolTip(tr("View and edit current note's information"));
  90. connect(fileInfoAct, SIGNAL(triggered(bool)),
  91. this, SLOT(fileInfo()));
  92. copyAct = new QAction(QIcon(":/resources/icons/copy.svg"),
  93. tr("&Copy\t%1").arg(QKeySequence(c_copyShortcutSequence).toString()), this);
  94. copyAct->setToolTip(tr("Copy selected notes"));
  95. connect(copyAct, &QAction::triggered,
  96. this, &VFileList::copySelectedFiles);
  97. cutAct = new QAction(QIcon(":/resources/icons/cut.svg"),
  98. tr("C&ut\t%1").arg(QKeySequence(c_cutShortcutSequence).toString()), this);
  99. cutAct->setToolTip(tr("Cut selected notes"));
  100. connect(cutAct, &QAction::triggered,
  101. this, &VFileList::cutSelectedFiles);
  102. pasteAct = new QAction(QIcon(":/resources/icons/paste.svg"),
  103. tr("&Paste\t%1").arg(QKeySequence(c_pasteShortcutSequence).toString()), this);
  104. pasteAct->setToolTip(tr("Paste notes in current folder"));
  105. connect(pasteAct, &QAction::triggered,
  106. this, &VFileList::pasteFilesInCurDir);
  107. m_openLocationAct = new QAction(tr("&Open Note Location"), this);
  108. m_openLocationAct->setToolTip(tr("Open the folder containing this note in operating system"));
  109. connect(m_openLocationAct, &QAction::triggered,
  110. this, &VFileList::openFileLocation);
  111. }
  112. void VFileList::setDirectory(VDirectory *p_directory)
  113. {
  114. if (m_directory == p_directory) {
  115. return;
  116. }
  117. m_directory = p_directory;
  118. if (!m_directory) {
  119. fileList->clear();
  120. return;
  121. }
  122. qDebug() << "filelist set folder" << m_directory->getName();
  123. updateFileList();
  124. }
  125. void VFileList::updateFileList()
  126. {
  127. fileList->clear();
  128. if (!m_directory->open()) {
  129. return;
  130. }
  131. const QVector<VFile *> &files = m_directory->getFiles();
  132. for (int i = 0; i < files.size(); ++i) {
  133. VFile *file = files[i];
  134. insertFileListItem(file);
  135. }
  136. }
  137. void VFileList::fileInfo()
  138. {
  139. QList<QListWidgetItem *> items = fileList->selectedItems();
  140. if (items.size() == 1) {
  141. fileInfo(getVFile(items[0]));
  142. }
  143. }
  144. void VFileList::openFileLocation() const
  145. {
  146. QListWidgetItem *curItem = fileList->currentItem();
  147. V_ASSERT(curItem);
  148. QUrl url = QUrl::fromLocalFile(getVFile(curItem)->fetchBasePath());
  149. QDesktopServices::openUrl(url);
  150. }
  151. void VFileList::fileInfo(VFile *p_file)
  152. {
  153. if (!p_file) {
  154. return;
  155. }
  156. VDirectory *dir = p_file->getDirectory();
  157. QString curName = p_file->getName();
  158. VFileInfoDialog dialog(tr("Note Information"), "", dir, p_file, this);
  159. if (dialog.exec() == QDialog::Accepted) {
  160. QString name = dialog.getNameInput();
  161. if (name == curName) {
  162. return;
  163. }
  164. if (!promptForDocTypeChange(p_file, QDir(p_file->fetchBasePath()).filePath(name))) {
  165. return;
  166. }
  167. if (!p_file->rename(name)) {
  168. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  169. tr("Fail to rename note <span style=\"%1\">%2</span>.")
  170. .arg(g_config->c_dataTextStyle).arg(curName), "",
  171. QMessageBox::Ok, QMessageBox::Ok, this);
  172. return;
  173. }
  174. QListWidgetItem *item = findItem(p_file);
  175. if (item) {
  176. fillItem(item, p_file);
  177. }
  178. emit fileUpdated(p_file);
  179. }
  180. }
  181. void VFileList::fillItem(QListWidgetItem *p_item, const VFile *p_file)
  182. {
  183. unsigned long long ptr = (long long)p_file;
  184. p_item->setData(Qt::UserRole, ptr);
  185. p_item->setToolTip(p_file->getName());
  186. p_item->setText(p_file->getName());
  187. V_ASSERT(sizeof(p_file) <= sizeof(ptr));
  188. }
  189. QListWidgetItem* VFileList::insertFileListItem(VFile *file, bool atFront)
  190. {
  191. V_ASSERT(file);
  192. QListWidgetItem *item = new QListWidgetItem();
  193. fillItem(item, file);
  194. if (atFront) {
  195. fileList->insertItem(0, item);
  196. } else {
  197. fileList->addItem(item);
  198. }
  199. // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
  200. fileList->update();
  201. qDebug() << "VFileList adds" << file->getName();
  202. return item;
  203. }
  204. void VFileList::removeFileListItem(QListWidgetItem *item)
  205. {
  206. fileList->setCurrentRow(-1);
  207. fileList->removeItemWidget(item);
  208. delete item;
  209. // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
  210. fileList->update();
  211. }
  212. void VFileList::newFile()
  213. {
  214. if (!m_directory) {
  215. return;
  216. }
  217. QList<QString> suffixes = g_config->getDocSuffixes()[(int)DocType::Markdown];
  218. QString defaultSuf;
  219. QString suffixStr;
  220. for (auto const & suf : suffixes) {
  221. suffixStr += (suffixStr.isEmpty() ? suf : "/" + suf);
  222. if (defaultSuf.isEmpty() || suf == "md") {
  223. defaultSuf = suf;
  224. }
  225. }
  226. QString info = tr("Create a note in <span style=\"%1\">%2</span>.")
  227. .arg(g_config->c_dataTextStyle).arg(m_directory->getName());
  228. info = info + "<br>" + tr("Note with name ending with \"%1\" will be treated as Markdown type.")
  229. .arg(suffixStr);
  230. QString defaultName = QString("new_note.%1").arg(defaultSuf);
  231. defaultName = VUtils::getFileNameWithSequence(m_directory->fetchPath(), defaultName);
  232. VNewFileDialog dialog(tr("Create Note"), info, defaultName, m_directory, this);
  233. if (dialog.exec() == QDialog::Accepted) {
  234. VFile *file = m_directory->createFile(dialog.getNameInput());
  235. if (!file) {
  236. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  237. tr("Fail to create note <span style=\"%1\">%2</span>.")
  238. .arg(g_config->c_dataTextStyle).arg(dialog.getNameInput()), "",
  239. QMessageBox::Ok, QMessageBox::Ok, this);
  240. return;
  241. }
  242. // Write title if needed.
  243. if (dialog.getInsertTitleInput()) {
  244. if (!file->open()) {
  245. qWarning() << "fail to open newly-created note" << file->getName();
  246. } else {
  247. Q_ASSERT(file->getContent().isEmpty());
  248. QString content = QString("# %1\n").arg(QFileInfo(file->getName()).baseName());
  249. file->setContent(content);
  250. if (!file->save()) {
  251. qWarning() << "fail to write to newly-created note" << file->getName();
  252. }
  253. file->close();
  254. }
  255. }
  256. QVector<QListWidgetItem *> items = updateFileListAdded();
  257. Q_ASSERT(items.size() == 1);
  258. fileList->setCurrentItem(items[0], QItemSelectionModel::ClearAndSelect);
  259. // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
  260. fileList->update();
  261. // Open it in edit mode
  262. emit fileCreated(file, OpenFileMode::Edit);
  263. }
  264. }
  265. QVector<QListWidgetItem *> VFileList::updateFileListAdded()
  266. {
  267. QVector<QListWidgetItem *> ret;
  268. const QVector<VFile *> &files = m_directory->getFiles();
  269. for (int i = 0; i < files.size(); ++i) {
  270. VFile *file = files[i];
  271. if (i >= fileList->count()) {
  272. QListWidgetItem *item = insertFileListItem(file, false);
  273. ret.append(item);
  274. } else {
  275. VFile *itemFile = getVFile(fileList->item(i));
  276. if (itemFile != file) {
  277. QListWidgetItem *item = insertFileListItem(file, false);
  278. ret.append(item);
  279. }
  280. }
  281. }
  282. qDebug() << ret.size() << "items added";
  283. return ret;
  284. }
  285. // Delete the file related to current item
  286. void VFileList::deleteFile()
  287. {
  288. QList<QListWidgetItem *> items = fileList->selectedItems();
  289. Q_ASSERT(!items.isEmpty());
  290. for (int i = 0; i < items.size(); ++i) {
  291. deleteFile(getVFile(items.at(i)));
  292. }
  293. }
  294. // @p_file may or may not be listed in VFileList
  295. void VFileList::deleteFile(VFile *p_file)
  296. {
  297. if (!p_file) {
  298. return;
  299. }
  300. VDirectory *dir = p_file->getDirectory();
  301. QString fileName = p_file->getName();
  302. int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  303. tr("Are you sure to delete note <span style=\"%1\">%2</span>?")
  304. .arg(g_config->c_dataTextStyle).arg(fileName),
  305. tr("<span style=\"%1\">WARNING</span>: The files (including images) "
  306. "deleted may be UNRECOVERABLE!")
  307. .arg(g_config->c_warningTextStyle),
  308. QMessageBox::Ok | QMessageBox::Cancel,
  309. QMessageBox::Ok, this, MessageBoxType::Danger);
  310. if (ret == QMessageBox::Ok) {
  311. editArea->closeFile(p_file, true);
  312. // Remove the item before deleting it totally, or p_file will be invalid.
  313. QListWidgetItem *item = findItem(p_file);
  314. if (item) {
  315. removeFileListItem(item);
  316. }
  317. dir->deleteFile(p_file);
  318. }
  319. }
  320. void VFileList::contextMenuRequested(QPoint pos)
  321. {
  322. QListWidgetItem *item = fileList->itemAt(pos);
  323. QMenu menu(this);
  324. menu.setToolTipsVisible(true);
  325. if (!m_directory) {
  326. return;
  327. }
  328. menu.addAction(newFileAct);
  329. if (item) {
  330. menu.addAction(deleteFileAct);
  331. menu.addSeparator();
  332. menu.addAction(copyAct);
  333. menu.addAction(cutAct);
  334. }
  335. if (VUtils::opTypeInClipboard() == ClipboardOpType::CopyFile
  336. && !m_copiedFiles.isEmpty()) {
  337. if (!item) {
  338. menu.addSeparator();
  339. }
  340. menu.addAction(pasteAct);
  341. }
  342. if (item) {
  343. menu.addSeparator();
  344. menu.addAction(m_openLocationAct);
  345. if (fileList->selectedItems().size() == 1) {
  346. menu.addAction(fileInfoAct);
  347. }
  348. }
  349. menu.exec(fileList->mapToGlobal(pos));
  350. }
  351. QListWidgetItem* VFileList::findItem(const VFile *p_file)
  352. {
  353. if (!p_file || p_file->getDirectory() != m_directory) {
  354. return NULL;
  355. }
  356. int nrChild = fileList->count();
  357. for (int i = 0; i < nrChild; ++i) {
  358. QListWidgetItem *item = fileList->item(i);
  359. if (p_file == getVFile(item)) {
  360. return item;
  361. }
  362. }
  363. return NULL;
  364. }
  365. void VFileList::handleItemClicked(QListWidgetItem *currentItem)
  366. {
  367. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  368. if (modifiers != Qt::NoModifier) {
  369. return;
  370. }
  371. if (!currentItem) {
  372. emit fileClicked(NULL);
  373. return;
  374. }
  375. // Qt seems not to update the QListWidget correctly. Manually force it to repaint.
  376. fileList->update();
  377. emit fileClicked(getVFile(currentItem), OpenFileMode::Read);
  378. }
  379. bool VFileList::importFile(const QString &p_srcFilePath)
  380. {
  381. if (p_srcFilePath.isEmpty()) {
  382. return false;
  383. }
  384. Q_ASSERT(m_directory);
  385. // Copy file @name to current directory
  386. QString targetPath = m_directory->fetchPath();
  387. QString srcName = VUtils::fileNameFromPath(p_srcFilePath);
  388. if (srcName.isEmpty()) {
  389. return false;
  390. }
  391. QString targetFilePath = QDir(targetPath).filePath(srcName);
  392. bool ret = VUtils::copyFile(p_srcFilePath, targetFilePath, false);
  393. if (!ret) {
  394. return false;
  395. }
  396. VFile *destFile = m_directory->addFile(srcName, -1);
  397. if (destFile) {
  398. return insertFileListItem(destFile, false);
  399. }
  400. return false;
  401. }
  402. void VFileList::copySelectedFiles(bool p_isCut)
  403. {
  404. QList<QListWidgetItem *> items = fileList->selectedItems();
  405. if (items.isEmpty()) {
  406. return;
  407. }
  408. QJsonArray files;
  409. m_copiedFiles.clear();
  410. for (int i = 0; i < items.size(); ++i) {
  411. VFile *file = getVFile(items[i]);
  412. QJsonObject fileJson;
  413. fileJson["notebook"] = file->getNotebookName();
  414. fileJson["path"] = file->fetchPath();
  415. files.append(fileJson);
  416. m_copiedFiles.append(file);
  417. }
  418. copyFileInfoToClipboard(files, p_isCut);
  419. }
  420. void VFileList::cutSelectedFiles()
  421. {
  422. copySelectedFiles(true);
  423. }
  424. void VFileList::copyFileInfoToClipboard(const QJsonArray &p_files, bool p_isCut)
  425. {
  426. QJsonObject clip;
  427. clip["operation"] = (int)ClipboardOpType::CopyFile;
  428. clip["is_cut"] = p_isCut;
  429. clip["sources"] = p_files;
  430. QClipboard *clipboard = QApplication::clipboard();
  431. clipboard->setText(QJsonDocument(clip).toJson(QJsonDocument::Compact));
  432. }
  433. void VFileList::pasteFilesInCurDir()
  434. {
  435. if (m_copiedFiles.isEmpty()) {
  436. return;
  437. }
  438. pasteFiles(m_directory);
  439. }
  440. void VFileList::pasteFiles(VDirectory *p_destDir)
  441. {
  442. qDebug() << "paste files to" << p_destDir->getName();
  443. QClipboard *clipboard = QApplication::clipboard();
  444. QString text = clipboard->text();
  445. QJsonObject clip = QJsonDocument::fromJson(text.toLocal8Bit()).object();
  446. Q_ASSERT(!clip.isEmpty() && clip["operation"] == (int)ClipboardOpType::CopyFile);
  447. bool isCut = clip["is_cut"].toBool();
  448. int nrPasted = 0;
  449. for (int i = 0; i < m_copiedFiles.size(); ++i) {
  450. QPointer<VFile> srcFile = m_copiedFiles[i];
  451. if (!srcFile) {
  452. continue;
  453. }
  454. QString fileName = srcFile->getName();
  455. VDirectory *srcDir = srcFile->getDirectory();
  456. if (srcDir == p_destDir && !isCut) {
  457. // Copy and paste in the same directory.
  458. // Rename it to xx_copy.md
  459. fileName = VUtils::generateCopiedFileName(srcDir->fetchPath(), fileName);
  460. }
  461. if (copyFile(p_destDir, fileName, srcFile, isCut)) {
  462. nrPasted++;
  463. } else {
  464. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  465. tr("Fail to copy note <span style=\"%1\">%2</span>.")
  466. .arg(g_config->c_dataTextStyle).arg(srcFile->getName()),
  467. tr("Please check if there already exists a file with the same name in the target folder."),
  468. QMessageBox::Ok, QMessageBox::Ok, this);
  469. }
  470. }
  471. qDebug() << "pasted" << nrPasted << "files sucessfully";
  472. clipboard->clear();
  473. m_copiedFiles.clear();
  474. }
  475. bool VFileList::copyFile(VDirectory *p_destDir, const QString &p_destName, VFile *p_file, bool p_cut)
  476. {
  477. QString srcPath = QDir::cleanPath(p_file->fetchPath());
  478. QString destPath = QDir::cleanPath(QDir(p_destDir->fetchPath()).filePath(p_destName));
  479. if (VUtils::equalPath(srcPath, destPath)) {
  480. return true;
  481. }
  482. // If change the file type, we need to close it first
  483. if (!promptForDocTypeChange(p_file, destPath)) {
  484. return false;
  485. }
  486. VFile *destFile = VDirectory::copyFile(p_destDir, p_destName, p_file, p_cut);
  487. updateFileList();
  488. if (destFile) {
  489. emit fileUpdated(destFile);
  490. }
  491. return destFile != NULL;
  492. }
  493. bool VFileList::promptForDocTypeChange(const VFile *p_file, const QString &p_newFilePath)
  494. {
  495. DocType docType = p_file->getDocType();
  496. DocType newDocType = VUtils::docTypeFromName(p_newFilePath);
  497. if (docType != newDocType) {
  498. if (editArea->isFileOpened(p_file)) {
  499. int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  500. tr("The renaming will change the note type."),
  501. tr("You should close the note <span style=\"%1\">%2</span> before continue.")
  502. .arg(g_config->c_dataTextStyle).arg(p_file->getName()),
  503. QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok, this);
  504. if (QMessageBox::Ok == ret) {
  505. if (!editArea->closeFile(p_file, false)) {
  506. return false;
  507. }
  508. } else {
  509. return false;
  510. }
  511. }
  512. }
  513. return true;
  514. }
  515. void VFileList::keyPressEvent(QKeyEvent *event)
  516. {
  517. int key = event->key();
  518. int modifiers = event->modifiers();
  519. switch (key) {
  520. case Qt::Key_Return:
  521. {
  522. QListWidgetItem *item = fileList->currentItem();
  523. if (item) {
  524. handleItemClicked(item);
  525. }
  526. break;
  527. }
  528. case Qt::Key_J:
  529. {
  530. if (modifiers == Qt::ControlModifier) {
  531. event->accept();
  532. QKeyEvent *downEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down,
  533. Qt::NoModifier);
  534. QCoreApplication::postEvent(fileList, downEvent);
  535. return;
  536. }
  537. break;
  538. }
  539. case Qt::Key_K:
  540. {
  541. if (modifiers == Qt::ControlModifier) {
  542. event->accept();
  543. QKeyEvent *upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up,
  544. Qt::NoModifier);
  545. QCoreApplication::postEvent(fileList, upEvent);
  546. return;
  547. }
  548. break;
  549. }
  550. default:
  551. break;
  552. }
  553. QWidget::keyPressEvent(event);
  554. }
  555. void VFileList::focusInEvent(QFocusEvent * /* p_event */)
  556. {
  557. fileList->setFocus();
  558. }
  559. bool VFileList::locateFile(const VFile *p_file)
  560. {
  561. if (p_file) {
  562. if (p_file->getDirectory() != m_directory) {
  563. return false;
  564. }
  565. QListWidgetItem *item = findItem(p_file);
  566. if (item) {
  567. fileList->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
  568. return true;
  569. }
  570. }
  571. return false;
  572. }
  573. void VFileList::handleRowsMoved(const QModelIndex &p_parent, int p_start, int p_end, const QModelIndex &p_destination, int p_row)
  574. {
  575. if (p_parent == p_destination) {
  576. // Items[p_start, p_end] are moved to p_row.
  577. m_directory->reorderFiles(p_start, p_end, p_row);
  578. Q_ASSERT(identicalListWithDirectory());
  579. }
  580. }
  581. bool VFileList::identicalListWithDirectory() const
  582. {
  583. const QVector<VFile *> files = m_directory->getFiles();
  584. int nrItems = fileList->count();
  585. if (nrItems != files.size()) {
  586. return false;
  587. }
  588. for (int i = 0; i < nrItems; ++i) {
  589. if (getVFile(fileList->item(i)) != files.at(i)) {
  590. return false;
  591. }
  592. }
  593. return true;
  594. }
  595. void VFileList::registerNavigation(QChar p_majorKey)
  596. {
  597. m_majorKey = p_majorKey;
  598. V_ASSERT(m_keyMap.empty());
  599. V_ASSERT(m_naviLabels.empty());
  600. }
  601. void VFileList::showNavigation()
  602. {
  603. // Clean up.
  604. m_keyMap.clear();
  605. for (auto label : m_naviLabels) {
  606. delete label;
  607. }
  608. m_naviLabels.clear();
  609. if (!isVisible()) {
  610. return;
  611. }
  612. // Generate labels for visible items.
  613. auto items = getVisibleItems();
  614. int itemWidth = rect().width();
  615. for (int i = 0; i < 26 && i < items.size(); ++i) {
  616. QChar key('a' + i);
  617. m_keyMap[key] = items[i];
  618. QString str = QString(m_majorKey) + key;
  619. QLabel *label = new QLabel(str, this);
  620. label->setStyleSheet(g_vnote->getNavigationLabelStyle(str));
  621. label->show();
  622. QRect rect = fileList->visualItemRect(items[i]);
  623. // Display the label at the end to show the file name.
  624. label->move(rect.x() + itemWidth - label->width(), rect.y());
  625. m_naviLabels.append(label);
  626. }
  627. }
  628. void VFileList::hideNavigation()
  629. {
  630. m_keyMap.clear();
  631. for (auto label : m_naviLabels) {
  632. delete label;
  633. }
  634. m_naviLabels.clear();
  635. }
  636. bool VFileList::handleKeyNavigation(int p_key, bool &p_succeed)
  637. {
  638. static bool secondKey = false;
  639. bool ret = false;
  640. p_succeed = false;
  641. QChar keyChar = VUtils::keyToChar(p_key);
  642. if (secondKey && !keyChar.isNull()) {
  643. secondKey = false;
  644. p_succeed = true;
  645. ret = true;
  646. auto it = m_keyMap.find(keyChar);
  647. if (it != m_keyMap.end()) {
  648. fileList->setCurrentItem(it.value(), QItemSelectionModel::ClearAndSelect);
  649. fileList->setFocus();
  650. }
  651. } else if (keyChar == m_majorKey) {
  652. // Major key pressed.
  653. // Need second key if m_keyMap is not empty.
  654. if (m_keyMap.isEmpty()) {
  655. p_succeed = true;
  656. } else {
  657. secondKey = true;
  658. }
  659. ret = true;
  660. }
  661. return ret;
  662. }
  663. QList<QListWidgetItem *> VFileList::getVisibleItems() const
  664. {
  665. QList<QListWidgetItem *> items;
  666. for (int i = 0; i < fileList->count(); ++i) {
  667. QListWidgetItem *item = fileList->item(i);
  668. if (!item->isHidden()) {
  669. items.append(item);
  670. }
  671. }
  672. return items;
  673. }