vfilelist.cpp 24 KB

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