Browse Source

explorer: support exploring system's files

Le Tan 7 years ago
parent
commit
2ff9c607dd

+ 2 - 2
src/dialog/vsettingsdialog.cpp

@@ -922,7 +922,7 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent)
 {
     // Default note open mode.
     m_openModeCombo = VUtils::getComboBox();
-    m_openModeCombo->setToolTip(tr("Default mode to open an internal note"));
+    m_openModeCombo->setToolTip(tr("Default mode to open a file"));
     m_openModeCombo->addItem(tr("Read Mode"), (int)OpenFileMode::Read);
     m_openModeCombo->addItem(tr("Edit Mode"), (int)OpenFileMode::Edit);
 
@@ -992,7 +992,7 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent)
     m_graphvizDotEdit->setToolTip(tr("Location to the GraphViz dot executable"));
 
     QFormLayout *mainLayout = new QFormLayout();
-    mainLayout->addRow(tr("Note open mode:"), m_openModeCombo);
+    mainLayout->addRow(tr("Open mode:"), m_openModeCombo);
     mainLayout->addRow(tr("Heading sequence:"), headingSequenceLayout);
     mainLayout->addRow(colorColumnLabel, m_colorColumnEdit);
     mainLayout->addRow(tr("MathJax configuration:"), m_mathjaxConfigEdit);

+ 14 - 9
src/resources/icons/create_note_tb.svg

@@ -1,10 +1,15 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
-<path style="fill:#000000" d="M399.3,168.9c-0.7-2.9-2-5-3.5-6.8l-83.7-91.7c-1.9-2.1-4.1-3.1-6.6-4.4c-2.9-1.5-6.1-1.6-9.4-1.6H136.2
-	c-12.4,0-23.7,9.6-23.7,22.9v335.2c0,13.4,11.3,25.9,23.7,25.9h243.1c12.4,0,21.2-12.5,21.2-25.9V178.4
-	C400.5,174.8,400.1,172.2,399.3,168.9z M305.5,111l58,63.5h-58V111z M144.5,416.5v-320h129v81.7c0,14.8,13.4,28.3,28.1,28.3h66.9
-	v210H144.5z"/>
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<g>
+	<path fill="#000000" d="M269.534,399.201c-9.616,0-17.483,7.867-17.483,17.483s7.867,17.483,17.483,17.483h160.263
+		c9.616,0,17.483-7.867,17.483-17.483V138.409c0-4.954-2.04-9.616-5.536-12.821L310.62,4.662C307.414,1.748,303.043,0,298.673,0
+		h-195.23c-9.616,0-17.483,7.867-17.483,17.483v189.402c0,9.616,7.867,17.483,17.483,17.483s17.483-7.867,17.483-17.483V34.967
+		h139.866v151.522c0,9.616,7.867,17.483,17.483,17.483H409.4c0.874,0,2.04,0,2.914-0.291v195.521H269.534z M409.4,169.005H295.759
+		V38.463l116.555,107.522v23.311C411.44,169.005,410.274,169.005,409.4,169.005z M16.026,372.977
+		c0-9.616,7.867-17.483,17.483-17.483h52.45V300.13c0-9.616,7.867-17.483,17.483-17.483s17.483,7.867,17.483,17.483v55.364h58.278
+		c9.616,0,17.483,7.867,17.483,17.483s-7.867,17.483-17.483,17.483h-58.278v55.364c0,9.616-7.867,17.483-17.483,17.483
+		s-17.483-7.867-17.483-17.483V390.46H33.51C23.894,390.46,16.026,382.592,16.026,372.977z"/>
+</g>
 </svg>

+ 15 - 9
src/resources/icons/create_rootdir_tb.svg

@@ -1,10 +1,16 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
-<path style="fill:#000000" d="M437.334,144H256.006l-42.668-48H74.666C51.197,96,32,115.198,32,138.667v234.666C32,396.802,51.197,416,74.666,416h362.668
-	C460.803,416,480,396.802,480,373.333V186.667C480,163.198,460.803,144,437.334,144z M448,373.333
-	c0,5.782-4.885,10.667-10.666,10.667H74.666C68.884,384,64,379.115,64,373.333V176h373.334c5.781,0,10.666,4.885,10.666,10.667
-	V373.333z"/>
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<g>
+    <path fill="#000000" d="M444.955,31.782H333.208c-17.576,0-37.758,8.931-47.165,31.782c0,0-4.132,29.145-31.782,31.782
+        H31.782C14.239,95.347,0,109.077,0,126.621v286.551c0,17.544,14.239,31.783,31.782,31.783h413.172
+        c17.544,0,31.782-14.239,31.782-31.783V63.565C476.737,46.021,462.499,31.782,444.955,31.782z M444.955,126.621v286.551H31.782
+        V127.13H254.26l3.051-0.159c29.812-2.829,48.246-23.71,56.732-45.163c8.263-20.722,22.661-18.243,22.661-18.243h108.251V126.621
+        z"/>
+    <path fill="#000000" d="M317.825,254.26H254.26v-63.565c0-8.772-7.151-15.891-15.891-15.891
+        c-8.772,0-15.891,7.119-15.891,15.891v63.565h-63.565c-8.772,0-15.891,7.151-15.891,15.891s7.119,15.891,15.891,15.891h63.565
+        v63.565c0,8.74,7.119,15.891,15.891,15.891c8.74,0,15.891-7.151,15.891-15.891v-63.565h63.565
+        c8.74,0,15.891-7.151,15.891-15.891S326.565,254.26,317.825,254.26z"/>
+</g>
 </svg>

+ 17 - 0
src/resources/icons/explore_root.svg

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<g>
+	<g>
+		<path d="M177.3,76.95v111c0,6.8,5.5,12.3,12.3,12.3h43.2v33.4h-165c-6.8,0-12.3,5.5-12.3,12.3v43.9H12.3
+			c-6.8,0-12.3,5.5-12.3,12.3v111c0,6.8,5.5,12.3,12.3,12.3h111c6.8,0,12.3-5.5,12.3-12.3v-111c0-6.8-5.5-12.3-12.3-12.3H80v-31.7
+			h152.7v31.7h-43.2c-6.8,0-12.3,5.5-12.3,12.3v111c0,6.8,5.5,12.3,12.3,12.3h111c6.8,0,12.3-5.5,12.3-12.3v-111
+			c0-6.8-5.5-12.3-12.3-12.3h-43.2v-31.7H410v31.7h-43.2c-6.8,0-12.3,5.5-12.3,12.3v111c0,6.8,5.5,12.3,12.3,12.3h111
+			c6.8,0,12.3-5.5,12.3-12.3v-111c0-6.8-5.5-12.3-12.3-12.3h-43.2v-43.9c0-6.8-5.5-12.3-12.3-12.3h-165v-33.4h43.2
+			c6.8,0,12.3-5.5,12.3-12.3v-111c0-6.8-5.5-12.3-12.3-12.3h-111C182.8,64.65,177.3,70.15,177.3,76.95z M111,400.75H24.6v-86.5H111
+			V400.75z M288.2,400.75h-86.5v-86.5h86.5V400.75z M465.4,400.75H379v-86.5h86.5v86.5H465.4z M201.8,89.15h86.5v86.5h-86.5
+			L201.8,89.15L201.8,89.15z"/>
+	</g>
+</g>
+</svg>

+ 14 - 0
src/resources/icons/explorer.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="folder" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 60 60" style="enable-background:new 0 0 60 60;" xml:space="preserve">
+<path fill="#000000" d="M57.49,21.5H55v-7h-2v-4H25.086l-3.571-5H2.732C1.226,5.5,0,6.726,0,8.232v43.687l0.006,0
+	c-0.005,0.563,0.17,1.114,0.522,1.575C1.018,54.134,1.76,54.5,2.565,54.5h44.759c1.156,0,2.174-0.779,2.45-1.813L60,24.649v-0.177
+	C60,22.75,58.944,21.5,57.49,21.5z M53,21.5h-8v-1c0-0.553-0.448-1-1-1s-1,0.447-1,1v1h-5v-1c0-0.553-0.448-1-1-1s-1,0.447-1,1v1h-5
+	v-1c0-0.553-0.448-1-1-1s-1,0.447-1,1v1h-5v-1c0-0.553-0.448-1-1-1s-1,0.447-1,1v1h-5v-1c0-0.553-0.448-1-1-1s-1,0.447-1,1v1h-2.269
+	c-0.143,0-0.284,0.012-0.422,0.035c-0.499,0.083-0.95,0.315-1.309,0.639V16.5h42V21.5z M5,37.793V22.5h5.695
+	c-0.188,0.243-0.335,0.516-0.414,0.813l-0.317,0.87L5,37.793z M9,20.5H6.414L7,19.914l2-2V20.5z M51,12.5v2H9.586L7,17.086V12.5
+	h19.515H51z M2,8.232C2,7.828,2.329,7.5,2.732,7.5h17.753l2.143,3H5v8.586l-2,2v22.085l-1,2.728V8.232z M47.869,52.083
+	c-0.066,0.245-0.291,0.417-0.545,0.417H2.565c-0.243,0-0.385-0.139-0.448-0.222c-0.063-0.082-0.16-0.256-0.123-0.408L3,49.112v0.001
+	l9.16-25.114l0.026-0.082c0.066-0.245,0.291-0.417,0.545-0.417H55h2.49c0.38,0,0.477,0.546,0.502,0.819L47.869,52.083z"/>
+</svg>

+ 12 - 0
src/resources/icons/open_location.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<g>
+	<path fill="#000000" d="M468.7,64H43.3c-6,0-11.3,5-11.3,11.1v265.7c0,6.2,5.2,11.1,11.3,11.1h425.4c6,0,11.3-5,11.3-11.1V75.1
+		C480,69,474.8,64,468.7,64z M448,320H64V96h384V320z"/>
+	<path fill="#000000" d="M302.5,448c28-0.5,41.5-3.9,29-12.5c-12.5-8.7-28.5-15.3-29-22.5c-0.3-3.7-1.7-45-1.7-45H256h-44.8c0,0-1.5,41.3-1.7,45
+		c-0.5,7.1-16.5,13.8-29,22.5c-12.5,8.7,1,12,29,12.5H302.5z"/>
+</g>
+</svg>

+ 5 - 5
src/resources/icons/remove_split.svg

@@ -2,9 +2,9 @@
 <!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
-<path style="fill:#000000" d="M458,210.409l-145.267-12.476L256,64l-56.743,133.934L54,210.409l110.192,95.524L131.161,448L256,372.686L380.83,448
-	l-33.021-142.066L458,210.409z M272.531,345.286L256,335.312l-16.53,9.973l-59.988,36.191l15.879-68.296l4.369-18.79l-14.577-12.637
-	l-52.994-45.939l69.836-5.998l19.206-1.65l7.521-17.75l27.276-64.381l27.27,64.379l7.52,17.751l19.208,1.65l69.846,5.998
-	l-52.993,45.939l-14.576,12.636l4.367,18.788l15.875,68.299L272.531,345.286z"/>
+	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<path style="fill:#000000" d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4
+	L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1
+	c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1
+	c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/>
 </svg>

+ 1 - 6
src/resources/icons/search_console.svg

@@ -3,10 +3,5 @@
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
-<g>
-	<path style="fill:#000000" d="M468.7,64H43.3c-6,0-11.3,5-11.3,11.1v265.7c0,6.2,5.2,11.1,11.3,11.1h425.4c6,0,11.3-5,11.3-11.1V75.1
-		C480,69,474.8,64,468.7,64z M448,320H64V96h384V320z"/>
-	<path style="fill:#000000" d="M302.5,448c28-0.5,41.5-3.9,29-12.5c-12.5-8.7-28.5-15.3-29-22.5c-0.3-3.7-1.7-45-1.7-45H256h-44.8c0,0-1.5,41.3-1.7,45
-		c-0.5,7.1-16.5,13.8-29,22.5c-12.5,8.7,1,12,29,12.5H302.5z"/>
-</g>
+<path fill="#000000" d="M496,384V96H16v288h175v16h-64v16h257v-16h-64v-16H496z M32,112h448v256H32V112z"/>
 </svg>

+ 13 - 7
src/resources/icons/split_window.svg

@@ -1,9 +1,15 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
-<path style="fill:#000000"  d="M458,210.409l-145.267-12.476L256,64l-56.743,133.934L54,210.409l110.192,95.524L131.161,448L256,372.686L380.83,448
-	l-33.021-142.066L458,210.409z M272.531,345.287L256,335.313l-0.002-189.277l27.27,64.379l7.52,17.751l19.208,1.65l69.846,5.998
-	l-52.993,45.939l-14.576,12.636l4.367,18.788l15.875,68.299L272.531,345.287z"/>
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;"
+	 xml:space="preserve">
+<g>
+	<path fill="#000000" d="M431.379,0.222h-394.7C16.456,0.222,0,16.671,0,36.895v394.268c0,20.221,16.456,36.677,36.679,36.677h394.7
+		c20.228,0,36.683-16.456,36.683-36.677V36.895C468.062,16.665,451.606,0.222,431.379,0.222z M406.519,41.966
+		c8.689,0,15.723,7.04,15.723,15.72c0,8.683-7.033,15.717-15.723,15.717c-8.688,0-15.723-7.04-15.723-15.717
+		C390.796,49.006,397.83,41.966,406.519,41.966z M350.189,41.966c8.688,0,15.723,7.04,15.723,15.72
+		c0,8.683-7.034,15.717-15.723,15.717c-8.684,0-15.711-7.04-15.711-15.717C334.479,49.006,341.506,41.966,350.189,41.966z
+		 M41.913,112.426h184.055v313.495H41.913V112.426z M426.148,425.921H242.104V112.426h184.044V425.921z"/>
+</g>
 </svg>

+ 10 - 0
src/resources/icons/star.svg

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
+<path style="fill:#000000" d="M458,210.409l-145.267-12.476L256,64l-56.743,133.934L54,210.409l110.192,95.524L131.161,448L256,372.686L380.83,448
+	l-33.021-142.066L458,210.409z M272.531,345.286L256,335.312l-16.53,9.973l-59.988,36.191l15.879-68.296l4.369-18.79l-14.577-12.637
+	l-52.994-45.939l69.836-5.998l19.206-1.65l7.521-17.75l27.276-64.381l27.27,64.379l7.52,17.751l19.208,1.65l69.846,5.998
+	l-52.993,45.939l-14.576,12.636l4.367,18.788l15.875,68.299L272.531,345.286z"/>
+</svg>

+ 10 - 0
src/resources/icons/unstar.svg

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
+<g>
+	<polygon fill="#000000" points="256,372.686 380.83,448 347.809,305.934 458,210.409 312.733,197.934 256,64 199.257,197.934 54,210.409 
+		164.192,305.934 131.161,448 	"/>
+</g>
+</svg>

+ 1 - 1
src/resources/themes/v_moonlight/v_moonlight.palette

@@ -7,7 +7,7 @@ mdhl_file=v_moonlight.mdhl
 css_file=v_moonlight.css
 codeblock_css_file=v_moonlight_codeblock.css
 mermaid_css_file=v_moonlight_mermaid.css
-version=10
+version=11
 
 ; This mapping will be used to translate colors when the content of HTML is copied
 ; without background. You could just specify the foreground colors mapping here.

+ 18 - 0
src/resources/themes/v_moonlight/v_moonlight.qss

@@ -630,6 +630,24 @@ QLineEdit[VimCommandLine="true"]:hover {
     background: @lineedit_hover_bg;
 }
 
+QLineEdit[EmbeddedEdit="true"] {
+    padding: 0px;
+    margin: 0px;
+    border: none;
+    color: @lineedit_fg;
+    background: transparent;
+}
+
+QLineEdit[EmbeddedEdit="true"]:focus {
+    background: @lineedit_focus_bg;
+    border: none;
+}
+
+QLineEdit[EmbeddedEdit="true"]:hover {
+    background: @lineedit_hover_bg;
+    border: none;
+}
+
 QLineEdit {
     border: 1px solid @lineedit_border;
     padding: 3px;

+ 1 - 1
src/resources/themes/v_native/v_native.palette

@@ -7,7 +7,7 @@ mdhl_file=v_native.mdhl
 css_file=v_native.css
 codeblock_css_file=v_native_codeblock.css
 mermaid_css_file=v_native_mermaid.css
-version=10
+version=11
 
 [phony]
 ; Abstract color attributes.

+ 8 - 0
src/resources/themes/v_native/v_native.qss

@@ -452,6 +452,14 @@ QLineEdit[VimCommandLine="true"] {
     background: @lineedit_bg;
 }
 
+QLineEdit[EmbeddedEdit="true"] {
+    padding: 0px;
+    margin: 0px;
+    border: none;
+    color: @lineedit_fg;
+    background: transparent;
+}
+
 VUniversalEntry VMetaWordLineEdit {
     border: 1px solid @lineedit_border;
 }

+ 1 - 1
src/resources/themes/v_pure/v_pure.palette

@@ -7,7 +7,7 @@ mdhl_file=v_pure.mdhl
 css_file=v_pure.css
 codeblock_css_file=v_pure_codeblock.css
 mermaid_css_file=v_pure_mermaid.css
-version=10
+version=11
 
 [phony]
 ; Abstract color attributes.

+ 18 - 0
src/resources/themes/v_pure/v_pure.qss

@@ -630,6 +630,24 @@ QLineEdit[VimCommandLine="true"]:hover {
     border: none;
 }
 
+QLineEdit[EmbeddedEdit="true"] {
+    padding: 0px;
+    margin: 0px;
+    border: none;
+    color: @lineedit_fg;
+    background: transparent;
+}
+
+QLineEdit[EmbeddedEdit="true"]:focus {
+    background: @lineedit_focus_bg;
+    border: none;
+}
+
+QLineEdit[EmbeddedEdit="true"]:hover {
+    background: @lineedit_hover_bg;
+    border: none;
+}
+
 QLineEdit {
     border: 1px solid @lineedit_border;
     padding: 3px;

+ 5 - 2
src/src.pro

@@ -132,7 +132,8 @@ SOURCES += main.cpp\
     vmathjaxpreviewhelper.cpp \
     vmathjaxwebdocument.cpp \
     vmathjaxinplacepreviewhelper.cpp \
-    vhistorylist.cpp
+    vhistorylist.cpp \
+    vexplorer.cpp
 
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
@@ -257,7 +258,9 @@ HEADERS  += vmainwindow.h \
     vmathjaxinplacepreviewhelper.h \
     markdownitoption.h \
     vhistorylist.h \
-    vhistoryentry.h
+    vhistoryentry.h \
+    vexplorer.h \
+    vexplorerentry.h
 
 RESOURCES += \
     vnote.qrc \

+ 38 - 0
src/utils/vutils.cpp

@@ -27,6 +27,7 @@
 #include <QAction>
 #include <QTreeWidgetItem>
 #include <QFormLayout>
+#include <QInputDialog>
 
 #include "vorphanfile.h"
 #include "vnote.h"
@@ -1542,3 +1543,40 @@ bool VUtils::inSameDrive(const QString &p_a, const QString &p_b)
     return true;
 #endif
 }
+
+QString VUtils::promptForFileName(const QString &p_title,
+                                  const QString &p_label,
+                                  const QString &p_default,
+                                  const QString &p_dir,
+                                  QWidget *p_parent)
+{
+    QString name = p_default;
+    QString text = p_label;
+    QDir paDir(p_dir);
+    while (true) {
+        bool ok;
+        name = QInputDialog::getText(p_parent,
+                                     p_title,
+                                     text,
+                                     QLineEdit::Normal,
+                                     name,
+                                     &ok);
+        if (!ok || name.isEmpty()) {
+            return "";
+        }
+
+        if (!VUtils::checkFileNameLegal(name)) {
+            text = QObject::tr("Illegal name. Please try again:");
+            continue;
+        }
+
+        if (paDir.exists(name)) {
+            text = QObject::tr("Name already exists. Please try again:");
+            continue;
+        }
+
+        break;
+    }
+
+    return name;
+}

+ 6 - 0
src/utils/vutils.h

@@ -333,6 +333,12 @@ public:
 
     static bool inSameDrive(const QString &p_a, const QString &p_b);
 
+    static QString promptForFileName(const QString &p_title,
+                                     const QString &p_label,
+                                     const QString &p_default,
+                                     const QString &p_dir,
+                                     QWidget *p_parent = nullptr);
+
     // Regular expression for image link.
     // ![image title]( http://github.com/tamlok/vnote.jpg "alt text" =200x100)
     // Captured texts (need to be trimmed):

+ 41 - 1
src/vconfigmanager.cpp

@@ -52,6 +52,7 @@ const QString VConfigManager::c_exportFolderName = QString("vnote_exports");
 VConfigManager::VConfigManager(QObject *p_parent)
     : QObject(p_parent),
       m_noteListViewOrder(-1),
+      m_explorerCurrentIndex(-1),
       m_hasReset(false),
       userSettings(NULL),
       defaultSettings(NULL),
@@ -1218,7 +1219,7 @@ void VConfigManager::setLastOpenedFiles(const QVector<VFileSessionInfo> &p_files
     m_sessionSettings->endArray();
 }
 
-void VConfigManager::getHistory(QLinkedList<VHistoryEntry> &p_history)
+void VConfigManager::getHistory(QLinkedList<VHistoryEntry> &p_history) const
 {
     p_history.clear();
 
@@ -1254,6 +1255,45 @@ void VConfigManager::setHistory(const QLinkedList<VHistoryEntry> &p_history)
     m_sessionSettings->endArray();
 }
 
+void VConfigManager::getExplorerEntries(QVector<VExplorerEntry> &p_entries) const
+{
+    p_entries.clear();
+
+    int size = m_sessionSettings->beginReadArray("explorer_starred");
+    for (int i = 0; i < size; ++i) {
+        m_sessionSettings->setArrayIndex(i);
+        p_entries.append(VExplorerEntry::fromSettings(m_sessionSettings));
+    }
+
+    m_sessionSettings->endArray();
+}
+
+void VConfigManager::setExplorerEntries(const QVector<VExplorerEntry> &p_entries)
+{
+    if (m_hasReset) {
+        return;
+    }
+
+    const QString section("explorer_starred");
+
+    // Clear it first
+    m_sessionSettings->beginGroup(section);
+    m_sessionSettings->remove("");
+    m_sessionSettings->endGroup();
+
+    m_sessionSettings->beginWriteArray(section);
+    int idx = 0;
+    for (auto const & entry : p_entries) {
+        if (entry.m_isStarred) {
+            m_sessionSettings->setArrayIndex(idx);
+            entry.toSettings(m_sessionSettings);
+            ++idx;
+        }
+    }
+
+    m_sessionSettings->endArray();
+}
+
 QVector<VMagicWord> VConfigManager::getCustomMagicWords()
 {
     QVector<VMagicWord> words;

+ 47 - 1
src/vconfigmanager.h

@@ -17,6 +17,7 @@
 #include "utils/vmetawordmanager.h"
 #include "markdownitoption.h"
 #include "vhistoryentry.h"
+#include "vexplorerentry.h"
 
 class QJsonObject;
 class QString;
@@ -131,6 +132,9 @@ public:
     int getCurNotebookIndex() const;
     void setCurNotebookIndex(int index);
 
+    int getNaviBoxCurrentIndex() const;
+    void setNaviBoxCurrentIndex(int p_index);
+
     // Read [notebooks] section from settings into @p_notebooks.
     void getNotebooks(QVector<VNotebook *> &p_notebooks, QObject *p_parent);
 
@@ -359,12 +363,22 @@ public:
     void setLastOpenedFiles(const QVector<VFileSessionInfo> &p_files);
 
     // Read history from [history] of session.ini.
-    void getHistory(QLinkedList<VHistoryEntry> &p_history);
+    void getHistory(QLinkedList<VHistoryEntry> &p_history) const;
 
     void setHistory(const QLinkedList<VHistoryEntry> &p_history);
 
     int getHistorySize() const;
 
+    // Read explorer's starred entries from [explorer_starred] of session.ini.
+    void getExplorerEntries(QVector<VExplorerEntry> &p_entries) const;
+
+    // Output starred entries to [explorer_starred] of session.ini.
+    void setExplorerEntries(const QVector<VExplorerEntry> &p_entries);
+
+    int getExplorerCurrentIndex() const;
+
+    void setExplorerCurrentIndex(int p_idx);
+
     // Read custom magic words from [magic_words] section.
     QVector<VMagicWord> getCustomMagicWords();
 
@@ -899,6 +913,9 @@ private:
     // View order of note list.
     int m_noteListViewOrder;
 
+    // Current entry index of explorer entries.
+    int m_explorerCurrentIndex;
+
     // Whether user has reset the configurations.
     bool m_hasReset;
 
@@ -1004,6 +1021,16 @@ inline void VConfigManager::setCurNotebookIndex(int index)
     setConfigToSessionSettings("global", "current_notebook", index);
 }
 
+inline int VConfigManager::getNaviBoxCurrentIndex() const
+{
+    return getConfigFromSessionSettings("global", "navibox_current_index").toInt();
+}
+
+inline void VConfigManager::setNaviBoxCurrentIndex(int p_index)
+{
+    setConfigToSessionSettings("global", "navibox_current_index", p_index);
+}
+
 inline void VConfigManager::getNotebooks(QVector<VNotebook *> &p_notebooks,
                                          QObject *p_parent)
 {
@@ -2326,4 +2353,23 @@ inline void VConfigManager::setNoteListViewOrder(int p_order)
     m_noteListViewOrder = p_order;
     setConfigToSettings("global", "note_list_view_order", m_noteListViewOrder);
 }
+
+inline int VConfigManager::getExplorerCurrentIndex() const
+{
+    if (m_explorerCurrentIndex == -1) {
+        const_cast<VConfigManager *>(this)->m_explorerCurrentIndex = getConfigFromSessionSettings("global", "explorer_current_entry").toInt();
+    }
+
+    return m_explorerCurrentIndex;
+}
+
+inline void VConfigManager::setExplorerCurrentIndex(int p_idx)
+{
+    if (p_idx == m_explorerCurrentIndex) {
+        return;
+    }
+
+    m_explorerCurrentIndex = p_idx;
+    setConfigToSessionSettings("global", "explorer_current_entry", m_explorerCurrentIndex);
+}
 #endif // VCONFIGMANAGER_H

+ 8 - 3
src/vdirectorytree.cpp

@@ -33,6 +33,7 @@ VDirectoryTree::VDirectoryTree(QWidget *parent)
     setColumnCount(1);
     setHeaderHidden(true);
     setContextMenuPolicy(Qt::CustomContextMenu);
+    setFitContent(true);
 
     initShortcuts();
 
@@ -147,6 +148,8 @@ void VDirectoryTree::updateDirectoryTree()
     if (!restoreCurrentItem() && topLevelItemCount() > 0) {
         setCurrentItem(topLevelItem(0));
     }
+
+    resizeColumnToContents(0);
 }
 
 bool VDirectoryTree::restoreCurrentItem()
@@ -425,8 +428,10 @@ void VDirectoryTree::contextMenuRequested(QPoint pos)
     menu.addAction(reloadAct);
 
     if (item) {
-        QAction *openLocationAct = new QAction(tr("&Open Folder Location"), &menu);
-        openLocationAct->setToolTip(tr("Open the folder containing this folder in operating system"));
+        QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"),
+                                               tr("&Open Folder Location"),
+                                               &menu);
+        openLocationAct->setToolTip(tr("Explore this folder in operating system"));
         connect(openLocationAct, &QAction::triggered,
                 this, &VDirectoryTree::openDirectoryLocation);
         menu.addAction(openLocationAct);
@@ -645,7 +650,7 @@ void VDirectoryTree::openDirectoryLocation() const
 {
     QTreeWidgetItem *curItem = currentItem();
     V_ASSERT(curItem);
-    QUrl url = QUrl::fromLocalFile(getVDirectory(curItem)->fetchBasePath());
+    QUrl url = QUrl::fromLocalFile(getVDirectory(curItem)->fetchPath());
     QDesktopServices::openUrl(url);
 }
 

+ 4 - 2
src/veditwindow.cpp

@@ -524,8 +524,10 @@ void VEditWindow::tabbarContextMenuRequested(QPoint p_pos)
                 QDesktopServices::openUrl(url);
             });
 
-    QAction *openLocationAct = new QAction(tr("Open Note Location"), &menu);
-    openLocationAct->setToolTip(tr("Open the folder containing this note in operating system"));
+    QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"),
+                                           tr("Open Note Location"),
+                                           &menu);
+    openLocationAct->setToolTip(tr("Explore the folder containing this note in operating system"));
     connect(openLocationAct, &QAction::triggered,
             this, [this](){
                 int tab = GET_TAB_FROM_SENDER();

+ 737 - 0
src/vexplorer.cpp

@@ -0,0 +1,737 @@
+#include "vexplorer.h"
+
+#include <QtWidgets>
+#include <QFileSystemModel>
+#include <QDesktopServices>
+
+#include "utils/viconutils.h"
+#include "utils/vutils.h"
+#include "vconfigmanager.h"
+#include "vmainwindow.h"
+#include "vcart.h"
+#include "vlineedit.h"
+#include "vhistorylist.h"
+#include "vorphanfile.h"
+
+extern VMainWindow *g_mainWin;
+
+extern VConfigManager *g_config;
+
+const QString VExplorer::c_infoShortcutSequence = "F2";
+
+VExplorer::VExplorer(QWidget *p_parent)
+    : QWidget(p_parent),
+      m_initialized(false),
+      m_uiInitialized(false),
+      m_index(-1)
+{
+}
+
+void VExplorer::setupUI()
+{
+    if (m_uiInitialized) {
+        return;
+    }
+
+    m_uiInitialized = true;
+
+    QLabel *dirLabel = new QLabel(tr("Directory"), this);
+
+    m_openBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/dir_item.svg"),
+                                "",
+                                this);
+    m_openBtn->setToolTip(tr("Open"));
+    m_openBtn->setProperty("FlatBtn", true);
+    connect(m_openBtn, &QPushButton::clicked,
+            this, [this]() {
+                static QString defaultPath = g_config->getDocumentPathOrHomePath();
+                QString dirPath = QFileDialog::getExistingDirectory(this,
+                                                                    tr("Select Root Directory To Explore"),
+                                                                    defaultPath,
+                                                                    QFileDialog::ShowDirsOnly
+                                                                    | QFileDialog::DontResolveSymlinks);
+                if (!dirPath.isEmpty()) {
+                    defaultPath = VUtils::basePathFromPath(dirPath);
+
+                    int idx = addEntry(dirPath);
+                    updateDirectoryComboBox();
+                    if (idx != -1) {
+                        setCurrentEntry(idx);
+                    }
+                }
+            });
+
+    m_openLocationBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/open_location.svg"),
+                                        "",
+                                        this);
+    m_openLocationBtn->setToolTip(tr("Open Directory Location"));
+    m_openLocationBtn->setProperty("FlatBtn", true);
+    connect(m_openLocationBtn, &QPushButton::clicked,
+            this, [this]() {
+                if (checkIndex()) {
+                    QUrl url = QUrl::fromLocalFile(m_entries[m_index].m_directory);
+                    QDesktopServices::openUrl(url);
+                }
+            });
+
+    m_starBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/star.svg"),
+                                "",
+                                this);
+    m_starBtn->setToolTip(tr("Star"));
+    m_starBtn->setProperty("FlatBtn", true);
+    connect(m_starBtn, &QPushButton::clicked,
+            this, [this]() {
+                if (checkIndex()) {
+                    m_entries[m_index].m_isStarred = !m_entries[m_index].m_isStarred;
+
+                    g_config->setExplorerEntries(m_entries);
+
+                    updateExplorerEntryIndexInConfig();
+
+                    updateStarButton();
+                }
+            });
+
+    QHBoxLayout *dirLayout = new QHBoxLayout();
+    dirLayout->addWidget(dirLabel);
+    dirLayout->addStretch();
+    dirLayout->addWidget(m_openBtn);
+    dirLayout->addWidget(m_openLocationBtn);
+    dirLayout->addWidget(m_starBtn);
+    dirLayout->setContentsMargins(0, 0, 0, 0);
+
+    m_dirCB = VUtils::getComboBox(this);
+    m_dirCB->setEditable(true);
+    m_dirCB->setLineEdit(new VLineEdit(this));
+    m_dirCB->setToolTip(tr("Path of the root directory to explore"));
+    m_dirCB->lineEdit()->setPlaceholderText(tr("Root path to explore"));
+    m_dirCB->lineEdit()->setProperty("EmbeddedEdit", true);
+    connect(m_dirCB->lineEdit(), &QLineEdit::editingFinished,
+            this, [this]() {
+                QString text = m_dirCB->currentText();
+                int idx = addEntry(text);
+                updateDirectoryComboBox();
+                if (idx != -1) {
+                    setCurrentEntry(idx);
+                }
+            });
+    QCompleter *completer = new QCompleter(this);
+    QFileSystemModel *fsModel = new QFileSystemModel(completer);
+    fsModel->setRootPath("");
+    completer->setModel(fsModel);
+    // Enable styling the popup list via QListView::item.
+    completer->popup()->setItemDelegate(new QStyledItemDelegate(this));
+    completer->setCompletionMode(QCompleter::PopupCompletion);
+#if defined(Q_OS_WIN)
+    completer->setCaseSensitivity(Qt::CaseInsensitive);
+#else
+    completer->setCaseSensitivity(Qt::CaseSensitive);
+#endif
+    m_dirCB->lineEdit()->setCompleter(completer);
+    connect(m_dirCB, SIGNAL(activated(int)),
+            this, SLOT(handleEntryActivated(int)));
+    m_dirCB->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
+
+    QLabel *imgLabel = new QLabel(tr("Image Folder"), this);
+
+    m_imgFolderEdit = new VLineEdit(this);
+    m_imgFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
+                                          .arg(g_config->getImageFolderExt()));
+    QString imgFolderTip = tr("Set the path of the image folder to store images "
+                              "of files within the root directory.\nIf absolute path is used, "
+                              "VNote will not manage those images."
+                              "(empty to use global configuration)");
+    m_imgFolderEdit->setToolTip(imgFolderTip);
+    connect(m_imgFolderEdit, &QLineEdit::editingFinished,
+            this, [this]() {
+                if (checkIndex()) {
+                    QString folder = m_imgFolderEdit->text();
+                    if (folder.isEmpty() || VUtils::checkPathLegal(folder)) {
+                        m_entries[m_index].m_imageFolder = folder;
+                        if (m_entries[m_index].m_isStarred) {
+                            g_config->setExplorerEntries(m_entries);
+                        }
+                    } else {
+                        m_imgFolderEdit->setText(m_entries[m_index].m_imageFolder);
+                    }
+                }
+            });
+
+    // New file/dir.
+    m_newFileBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/create_note_tb.svg"),
+                                   "",
+                                   this);
+    m_newFileBtn->setToolTip(tr("New File"));
+    m_newFileBtn->setProperty("FlatBtn", true);
+    connect(m_newFileBtn, &QPushButton::clicked,
+            this, &VExplorer::newFile);
+
+    m_newDirBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/create_rootdir_tb.svg"),
+                                  "",
+                                  this);
+    m_newDirBtn->setToolTip(tr("New Folder"));
+    m_newDirBtn->setProperty("FlatBtn", true);
+    connect(m_newDirBtn, &QPushButton::clicked,
+            this, &VExplorer::newFolder);
+
+    QHBoxLayout *btnLayout = new QHBoxLayout();
+    btnLayout->addStretch();
+    btnLayout->addWidget(m_newFileBtn);
+    btnLayout->addWidget(m_newDirBtn);
+    btnLayout->setContentsMargins(0, 0, 0, 0);
+
+    QFileSystemModel *dirModel = new QFileSystemModel(this);
+    dirModel->setRootPath("");
+    m_tree = new QTreeView(this);
+    m_tree->setModel(dirModel);
+    m_tree->setSelectionMode(QAbstractItemView::ExtendedSelection);
+    m_tree->setContextMenuPolicy(Qt::CustomContextMenu);
+    connect(m_tree, &QTreeView::customContextMenuRequested,
+            this, &VExplorer::handleContextMenuRequested);
+    connect(m_tree, &QTreeView::activated,
+            this, [this](const QModelIndex &p_index) {
+                QFileSystemModel *model = static_cast<QFileSystemModel *>(m_tree->model());
+                if (!model->isDir(p_index)) {
+                    QStringList files;
+                    files << model->filePath(p_index);
+                    openFiles(files, g_config->getNoteOpenMode());
+                }
+            });
+    connect(m_tree, &QTreeView::expanded,
+            this, &VExplorer::resizeTreeToContents);
+    connect(m_tree, &QTreeView::collapsed,
+            this, &VExplorer::resizeTreeToContents);
+    connect(dirModel, &QFileSystemModel::directoryLoaded,
+            this, &VExplorer::resizeTreeToContents);
+
+    m_tree->setHeaderHidden(true);
+    // Show only the Name column.
+    for (int i = 1; i < dirModel->columnCount(); ++i) {
+        m_tree->hideColumn(i);
+    }
+
+    QVBoxLayout *mainLayout = new QVBoxLayout();
+    mainLayout->addLayout(dirLayout);
+    mainLayout->addWidget(m_dirCB);
+    mainLayout->addWidget(imgLabel);
+    mainLayout->addWidget(m_imgFolderEdit);
+    mainLayout->addLayout(btnLayout);
+    mainLayout->addWidget(m_tree);
+    mainLayout->setContentsMargins(3, 0, 3, 0);
+
+    setLayout(mainLayout);
+}
+
+void VExplorer::init()
+{
+    if (m_initialized) {
+        return;
+    }
+
+    m_initialized = true;
+
+    setupUI();
+
+    QShortcut *infoShortcut = new QShortcut(QKeySequence(c_infoShortcutSequence), this);
+    infoShortcut->setContext(Qt::WidgetWithChildrenShortcut);
+    connect(infoShortcut, &QShortcut::activated,
+            this, [this]() {
+            });
+
+    g_config->getExplorerEntries(m_entries);
+    m_index = g_config->getExplorerCurrentIndex();
+    if (m_entries.isEmpty()) {
+        m_index = -1;
+        g_config->setExplorerCurrentIndex(m_index);
+    } else if (m_index < 0 || m_index >= m_entries.size()) {
+        m_index = 0;
+        g_config->setExplorerCurrentIndex(m_index);
+    }
+
+    updateDirectoryComboBox();
+
+    setCurrentEntry(m_index);
+}
+
+void VExplorer::showEvent(QShowEvent *p_event)
+{
+    init();
+
+    QWidget::showEvent(p_event);
+}
+
+void VExplorer::focusInEvent(QFocusEvent *p_event)
+{
+    init();
+
+    QWidget::focusInEvent(p_event);
+
+    if (m_index < 0) {
+        m_openBtn->setFocus();
+    } else {
+        m_tree->setFocus();
+    }
+}
+
+void VExplorer::updateUI()
+{
+
+}
+
+void VExplorer::updateDirectoryComboBox()
+{
+    m_dirCB->clear();
+
+    for (int i = 0; i < m_entries.size(); ++i) {
+        m_dirCB->addItem(QDir::toNativeSeparators(m_entries[i].m_directory));
+        m_dirCB->setItemData(i, m_entries[i].m_directory, Qt::ToolTipRole);
+    }
+
+    m_dirCB->setCurrentIndex(m_index);
+}
+
+int VExplorer::addEntry(const QString &p_dir)
+{
+    if (p_dir.isEmpty() || !QFileInfo::exists(p_dir) || !QDir::isAbsolutePath(p_dir)) {
+        return -1;
+    }
+
+    QString dir(QDir::cleanPath(p_dir));
+    int idx = -1;
+    for (idx = 0; idx < m_entries.size(); ++idx) {
+        if (VUtils::equalPath(dir, m_entries[idx].m_directory)) {
+            break;
+        }
+    }
+
+    if (idx < m_entries.size()) {
+        return idx;
+    }
+
+    m_entries.append(VExplorerEntry(QFileInfo(dir).absoluteFilePath(), ""));
+    return m_entries.size() - 1;
+}
+
+void VExplorer::handleEntryActivated(int p_idx)
+{
+    bool valid = false;
+    QString imgFolder;
+
+    if (p_idx >= 0 &&  p_idx < m_entries.size()) {
+        valid = true;
+        imgFolder = m_entries[p_idx].m_imageFolder;
+    } else {
+        p_idx = -1;
+    }
+
+    m_index = p_idx;
+
+    updateExplorerEntryIndexInConfig();
+
+    m_imgFolderEdit->setText(imgFolder);
+    m_imgFolderEdit->setEnabled(valid);
+
+    m_openLocationBtn->setEnabled(valid);
+    m_newFileBtn->setEnabled(valid);
+    m_newDirBtn->setEnabled(valid);
+
+    updateStarButton();
+
+    updateTree();
+}
+
+void VExplorer::updateStarButton()
+{
+    // -1 for disabled, 0 for star, 1 for unstar.
+    int star = -1;
+
+    if (checkIndex()) {
+        star = m_entries[m_index].m_isStarred ? 1 : 0;
+    }
+
+    bool enabled = true;
+    switch (star) {
+    default:
+        enabled = false;
+        V_FALLTHROUGH;
+    case 0:
+        m_starBtn->setEnabled(enabled);
+        m_starBtn->setIcon(VIconUtils::buttonIcon(":/resources/icons/star.svg"));
+        m_starBtn->setToolTip(tr("Star"));
+        break;
+
+    case 1:
+        m_starBtn->setEnabled(enabled);
+        m_starBtn->setIcon(VIconUtils::buttonIcon(":/resources/icons/unstar.svg"));
+        m_starBtn->setToolTip(tr("Unstar"));
+        break;
+    }
+}
+
+void VExplorer::setCurrentEntry(int p_index)
+{
+    m_dirCB->setCurrentIndex(p_index);
+
+    handleEntryActivated(p_index);
+}
+
+void VExplorer::updateExplorerEntryIndexInConfig()
+{
+    int idx = -1;
+    if (m_index >= 0 && m_index < m_entries.size()) {
+        int starIdx = -1;
+        for (int j = 0; j <= m_index; ++j) {
+            if (m_entries[j].m_isStarred) {
+                ++starIdx;
+            }
+        }
+
+        if (starIdx > -1) {
+            idx = starIdx;
+        }
+    }
+
+    g_config->setExplorerCurrentIndex(idx);
+}
+
+void VExplorer::updateTree()
+{
+    if (checkIndex()) {
+        QString pa = QDir::cleanPath(m_entries[m_index].m_directory);
+        QFileSystemModel *model = static_cast<QFileSystemModel *>(m_tree->model());
+        model->setRootPath(pa);
+        const QModelIndex rootIndex = model->index(pa);
+        if (rootIndex.isValid()) {
+            m_tree->setRootIndex(rootIndex);
+            resizeTreeToContents();
+            return;
+        } else {
+            model->setRootPath("");
+        }
+    }
+
+    m_tree->reset();
+    resizeTreeToContents();
+}
+
+void VExplorer::handleContextMenuRequested(QPoint p_pos)
+{
+    QModelIndex index = m_tree->indexAt(p_pos);
+    if (!index.isValid()) {
+        return;
+    }
+
+    QMenu menu(this);
+    menu.setToolTipsVisible(true);
+
+    QFileSystemModel *model = static_cast<QFileSystemModel *>(m_tree->model());
+    QModelIndexList selectedIdx = m_tree->selectionModel()->selectedRows();
+    if (selectedIdx.size() == 1 && model->isDir(selectedIdx[0])) {
+        QString filePath = model->filePath(selectedIdx[0]);
+
+        QAction *setRootAct = new QAction(VIconUtils::menuIcon(":/resources/icons/explore_root.svg"),
+                                          tr("Set As Root"),
+                                          &menu);
+        setRootAct->setToolTip(tr("Set current folder as the root directory to explore"));
+        connect(setRootAct, &QAction::triggered,
+                this, [this, filePath]() {
+                    int idx = addEntry(filePath);
+                    updateDirectoryComboBox();
+                    if (idx != -1) {
+                        setCurrentEntry(idx);
+                    }
+                });
+        menu.addAction(setRootAct);
+
+        QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"),
+                                               tr("Open Folder Location"),
+                                               &menu);
+        openLocationAct->setToolTip(tr("Explore this folder in operating system"));
+        connect(openLocationAct, &QAction::triggered,
+                this, [this, filePath]() {
+                    QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
+                });
+        menu.addAction(openLocationAct);
+
+        menu.addSeparator();
+
+        QAction *fileInfoAct = new QAction(VIconUtils::menuIcon(":/resources/icons/note_info.svg"),
+                                           tr("&Info\t%1").arg(VUtils::getShortcutText(c_infoShortcutSequence)),
+                                           &menu);
+        fileInfoAct->setToolTip(tr("View and edit current folder's information"));
+        connect(fileInfoAct, &QAction::triggered,
+                this, [this, filePath]() {
+                    renameFile(filePath);
+                });
+        menu.addAction(fileInfoAct);
+
+        menu.exec(m_tree->mapToGlobal(p_pos));
+        return;
+    }
+
+    bool allFiles = true;
+    QStringList selectedFiles;
+    for (auto const & it : selectedIdx) {
+        if (model->isDir(it)) {
+            allFiles = false;
+            break;
+        }
+
+        selectedFiles << model->filePath(it);
+    }
+
+    if (!allFiles) {
+        return;
+    }
+
+    // Only files are selected.
+    // We do not support copy/cut/deletion of files.
+    QAction *openInReadAct = new QAction(VIconUtils::menuIcon(":/resources/icons/reading.svg"),
+                                         tr("Open In Read Mode"),
+                                         &menu);
+    openInReadAct->setToolTip(tr("Open selected files in read mode"));
+    connect(openInReadAct, &QAction::triggered,
+            this, [this, selectedFiles]() {
+                openFiles(selectedFiles, OpenFileMode::Read, true);
+            });
+    menu.addAction(openInReadAct);
+
+    QAction *openInEditAct = new QAction(VIconUtils::menuIcon(":/resources/icons/editing.svg"),
+                                         tr("Open In Edit Mode"),
+                                         &menu);
+    openInEditAct->setToolTip(tr("Open selected files in edit mode"));
+    connect(openInEditAct, &QAction::triggered,
+            this, [this, selectedFiles]() {
+                openFiles(selectedFiles, OpenFileMode::Edit, true);
+            });
+    menu.addAction(openInEditAct);
+
+    menu.addSeparator();
+
+    if (selectedFiles.size() == 1) {
+        QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"),
+                                               tr("Open File Location"),
+                                               &menu);
+        openLocationAct->setToolTip(tr("Explore the folder containing this file in operating system"));
+        connect(openLocationAct, &QAction::triggered,
+                this, [this, filePath = selectedFiles[0]] () {
+                    QDesktopServices::openUrl(QUrl::fromLocalFile(VUtils::basePathFromPath(filePath)));
+                });
+        menu.addAction(openLocationAct);
+
+        menu.addSeparator();
+    }
+
+    QAction *addToCartAct = new QAction(VIconUtils::menuIcon(":/resources/icons/cart.svg"),
+                                        tr("Add To Cart"),
+                                        &menu);
+    addToCartAct->setToolTip(tr("Add selected files to Cart for further processing"));
+    connect(addToCartAct, &QAction::triggered,
+            this, [this, selectedFiles]() {
+                VCart *cart = g_mainWin->getCart();
+                for (int i = 0; i < selectedFiles.size(); ++i) {
+                    cart->addFile(selectedFiles[i]);
+                }
+
+                g_mainWin->showStatusMessage(tr("%1 %2 added to Cart")
+                                               .arg(selectedFiles.size())
+                                               .arg(selectedFiles.size() > 1 ? tr("files") : tr("file")));
+            });
+    menu.addAction(addToCartAct);
+
+    QAction *pinToHistoryAct = new QAction(VIconUtils::menuIcon(":/resources/icons/pin.svg"),
+                                           tr("Pin To History"),
+                                           &menu);
+    pinToHistoryAct->setToolTip(tr("Pin selected files to History"));
+    connect(pinToHistoryAct, &QAction::triggered,
+            this, [this, selectedFiles]() {
+                g_mainWin->getHistoryList()->pinFiles(selectedFiles);
+                g_mainWin->showStatusMessage(tr("%1 %2 pinned to History")
+                                               .arg(selectedFiles.size())
+                                               .arg(selectedFiles.size() > 1 ? tr("files") : tr("file")));
+            });
+    menu.addAction(pinToHistoryAct);
+
+    if (selectedFiles.size() == 1) {
+        QAction *fileInfoAct = new QAction(VIconUtils::menuIcon(":/resources/icons/note_info.svg"),
+                                           tr("&Info\t%1").arg(VUtils::getShortcutText(c_infoShortcutSequence)),
+                                           &menu);
+        fileInfoAct->setToolTip(tr("View and edit current file's information"));
+        connect(fileInfoAct, &QAction::triggered,
+                this, [this, filePath = selectedFiles[0]]() {
+                    renameFile(filePath);
+                });
+        menu.addAction(fileInfoAct);
+    }
+
+    menu.exec(m_tree->mapToGlobal(p_pos));
+}
+
+void VExplorer::openFiles(const QStringList &p_files,
+                          OpenFileMode p_mode,
+                          bool p_forceMode)
+{
+    if (!p_files.isEmpty()) {
+        Q_ASSERT(checkIndex());
+        QString imgFolder = m_entries[m_index].m_imageFolder;
+
+        QVector<VFile *> vfiles = g_mainWin->openFiles(p_files, false, p_mode, p_forceMode, false);
+
+        // Set image folder.
+        for (auto it : vfiles) {
+            if (it->getType() == FileType::Orphan) {
+                static_cast<VOrphanFile *>(it)->setImageFolder(imgFolder);
+            }
+        }
+    }
+}
+
+void VExplorer::resizeTreeToContents()
+{
+    m_tree->resizeColumnToContents(0);
+}
+
+// 1. When only one folder is selected, create a file in that folder.
+// 2. Otherwise, create a file in the root directory.
+void VExplorer::newFile()
+{
+    Q_ASSERT(checkIndex());
+
+    QString parentDir;
+
+    QFileSystemModel *model = static_cast<QFileSystemModel *>(m_tree->model());
+    QModelIndexList selectedIdx = m_tree->selectionModel()->selectedRows();
+    if (selectedIdx.size() == 1 && model->isDir(selectedIdx[0])) {
+        parentDir = model->filePath(selectedIdx[0]);
+    } else {
+        parentDir = m_entries[m_index].m_directory;
+    }
+
+    qDebug() << "new file in" << parentDir;
+
+    QString name = VUtils::promptForFileName(tr("New File"),
+                                             tr("File name (%1):").arg(parentDir),
+                                             "",
+                                             parentDir,
+                                             g_mainWin);
+
+    if (name.isEmpty()) {
+        return;
+    }
+
+    QDir paDir(parentDir);
+    QString filePath = paDir.filePath(name);
+    QFile file(filePath);
+    if (!file.open(QIODevice::WriteOnly)) {
+        qWarning() << "Fail to create file" << filePath;
+        VUtils::showMessage(QMessageBox::Warning,
+                            tr("New File"),
+                            tr("Fail to create file <span style=\"%1\">%2</span>.")
+                              .arg(g_config->c_dataTextStyle)
+                              .arg(filePath),
+                            "",
+                            QMessageBox::Ok,
+                            QMessageBox::Ok,
+                            g_mainWin);
+        return;
+    }
+
+    file.close();
+
+    m_tree->setFocus();
+
+    // Select the file.
+    const QModelIndex fileIndex = model->index(filePath);
+    Q_ASSERT(fileIndex.isValid());
+    m_tree->scrollTo(fileIndex);
+    m_tree->clearSelection();
+    m_tree->setCurrentIndex(fileIndex);
+}
+
+// 1. When only one folder is selected, create a folder in that folder.
+// 2. Otherwise, create a folder in the root directory.
+void VExplorer::newFolder()
+{
+    Q_ASSERT(checkIndex());
+
+    QString parentDir;
+
+    QFileSystemModel *model = static_cast<QFileSystemModel *>(m_tree->model());
+    QModelIndexList selectedIdx = m_tree->selectionModel()->selectedRows();
+    if (selectedIdx.size() == 1 && model->isDir(selectedIdx[0])) {
+        parentDir = model->filePath(selectedIdx[0]);
+    } else {
+        parentDir = m_entries[m_index].m_directory;
+    }
+
+    qDebug() << "new folder in" << parentDir;
+
+    QString name = VUtils::promptForFileName(tr("New Folder"),
+                                             tr("Folder name (%1):").arg(parentDir),
+                                             "",
+                                             parentDir,
+                                             g_mainWin);
+
+    if (name.isEmpty()) {
+        return;
+    }
+
+    QDir paDir(parentDir);
+    QString folderPath = paDir.filePath(name);
+    if (!paDir.mkdir(name)) {
+        qWarning() << "Fail to create folder" << folderPath;
+        VUtils::showMessage(QMessageBox::Warning,
+                            tr("New Folder"),
+                            tr("Fail to create folder <span style=\"%1\">%2</span>.")
+                              .arg(g_config->c_dataTextStyle)
+                              .arg(folderPath),
+                            "",
+                            QMessageBox::Ok,
+                            QMessageBox::Ok,
+                            g_mainWin);
+        return;
+    }
+
+    m_tree->setFocus();
+
+    // Select the folder.
+    const QModelIndex folderIndex = model->index(folderPath);
+    Q_ASSERT(folderIndex.isValid());
+    m_tree->scrollTo(folderIndex);
+    m_tree->clearSelection();
+    m_tree->setCurrentIndex(folderIndex);
+}
+
+void VExplorer::renameFile(const QString &p_filePath)
+{
+    Q_ASSERT(checkIndex());
+
+    QFileInfo fi(p_filePath);
+    QString parentDir = fi.path();
+    QString oldName = fi.fileName();
+
+    QString name = VUtils::promptForFileName(tr("File Information"),
+                                             tr("Rename file (%1):").arg(p_filePath),
+                                             oldName,
+                                             parentDir,
+                                             g_mainWin);
+
+    if (name.isEmpty()) {
+        return;
+    }
+
+    QDir paDir(parentDir);
+    if (!paDir.rename(oldName, name)) {
+        qWarning() << "Fail to rename" << p_filePath;
+        VUtils::showMessage(QMessageBox::Warning,
+                            tr("File Information"),
+                            tr("Fail to rename file <span style=\"%1\">%2</span>.")
+                              .arg(g_config->c_dataTextStyle)
+                              .arg(p_filePath),
+                            "",
+                            QMessageBox::Ok,
+                            QMessageBox::Ok,
+                            g_mainWin);
+        return;
+    }
+}

+ 95 - 0
src/vexplorer.h

@@ -0,0 +1,95 @@
+#ifndef VEXPLORER_H
+#define VEXPLORER_H
+
+#include <QWidget>
+#include <QVector>
+
+#include "vexplorerentry.h"
+#include "vconstants.h"
+
+class QPushButton;
+class QTreeView;
+class QTreeWidgetItem;
+class QShowEvent;
+class QFocusEvent;
+class QComboBox;
+class VLineEdit;
+
+class VExplorer : public QWidget
+{
+    Q_OBJECT
+public:
+    explicit VExplorer(QWidget *p_parent = nullptr);
+
+protected:
+    void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
+
+    void focusInEvent(QFocusEvent *p_event) Q_DECL_OVERRIDE;
+
+private slots:
+    void handleEntryActivated(int p_idx);
+
+    void handleContextMenuRequested(QPoint p_pos);
+
+private:
+    void setupUI();
+
+    void init();
+
+    void updateUI();
+
+    void updateDirectoryComboBox();
+
+    // Add an entry to m_entries with @p_dir being the path of the entry.
+    // Return the index of the corresponding entry if succeed; otherwise,
+    // return -1.
+    int addEntry(const QString &p_dir);
+
+    void setCurrentEntry(int p_index);
+
+    bool checkIndex() const;
+
+    void updateExplorerEntryIndexInConfig();
+
+    void updateStarButton();
+
+    void updateTree();
+
+    void openFiles(const QStringList &p_files,
+                   OpenFileMode p_mode,
+                   bool p_forceMode = false);
+
+    void resizeTreeToContents();
+
+    void newFile();
+
+    void newFolder();
+
+    void renameFile(const QString &p_filePath);
+
+    bool m_initialized;
+
+    bool m_uiInitialized;
+
+    QVector<VExplorerEntry> m_entries;
+
+    int m_index;
+
+    QPushButton *m_openBtn;
+    QPushButton *m_openLocationBtn;
+    QPushButton *m_starBtn;
+    QComboBox *m_dirCB;
+    VLineEdit *m_imgFolderEdit;
+    QPushButton *m_newFileBtn;
+    QPushButton *m_newDirBtn;
+    QTreeView *m_tree;
+
+    static const QString c_infoShortcutSequence;
+};
+
+inline bool VExplorer::checkIndex() const
+{
+    return m_index >= 0 && m_index < m_entries.size();
+}
+
+#endif // VEXPLORER_H

+ 51 - 0
src/vexplorerentry.h

@@ -0,0 +1,51 @@
+#ifndef VEXPLORERENTRY_H
+#define VEXPLORERENTRY_H
+
+#include <QSettings>
+
+namespace ExplorerConfig
+{
+    static const QString c_directory = "directory";
+    static const QString c_imageFolder = "image_folder";
+}
+
+class VExplorerEntry
+{
+public:
+    VExplorerEntry()
+        : m_isStarred(false)
+    {
+    }
+
+    VExplorerEntry(const QString &p_directory,
+                   const QString &p_imageFolder,
+                   bool p_isStarred = false)
+        : m_directory(p_directory),
+          m_imageFolder(p_imageFolder),
+          m_isStarred(p_isStarred)
+    {
+    }
+
+    static VExplorerEntry fromSettings(const QSettings *p_settings)
+    {
+        VExplorerEntry entry;
+        entry.m_directory = p_settings->value(ExplorerConfig::c_directory).toString();
+        entry.m_imageFolder = p_settings->value(ExplorerConfig::c_imageFolder).toString();
+        entry.m_isStarred = true;
+        return entry;
+    }
+
+    void toSettings(QSettings *p_settings) const
+    {
+        p_settings->setValue(ExplorerConfig::c_directory, m_directory);
+        p_settings->setValue(ExplorerConfig::c_imageFolder, m_imageFolder);
+    }
+
+    QString m_directory;
+
+    QString m_imageFolder;
+
+    bool m_isStarred;
+};
+
+#endif // VEXPLORERENTRY_H

+ 7 - 5
src/vfilelist.cpp

@@ -609,8 +609,10 @@ void VFileList::contextMenuRequested(QPoint pos)
     if (item) {
         menu.addSeparator();
         if (selectedSize == 1) {
-            QAction *openLocationAct = new QAction(tr("&Open Note Location"), &menu);
-            openLocationAct->setToolTip(tr("Open the folder containing this note in operating system"));
+            QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"),
+                                                   tr("&Open Note Location"),
+                                                   &menu);
+            openLocationAct->setToolTip(tr("Explore the folder containing this note in operating system"));
             connect(openLocationAct, &QAction::triggered,
                     this, &VFileList::openFileLocation);
             menu.addAction(openLocationAct);
@@ -1359,7 +1361,7 @@ void VFileList::sortFiles(QVector<VNoteFile *> &p_files, ViewOrder p_order)
 
     case ViewOrder::NameReverse:
         reverse = true;
-        V_FALLTHROUGH
+        V_FALLTHROUGH;
     case ViewOrder::Name:
         std::sort(p_files.begin(), p_files.end(), [reverse](const VNoteFile *p_a, const VNoteFile *p_b) {
             if (reverse) {
@@ -1372,7 +1374,7 @@ void VFileList::sortFiles(QVector<VNoteFile *> &p_files, ViewOrder p_order)
 
     case ViewOrder::CreatedTimeReverse:
         reverse = true;
-        V_FALLTHROUGH
+        V_FALLTHROUGH;
     case ViewOrder::CreatedTime:
         std::sort(p_files.begin(), p_files.end(), [reverse](const VNoteFile *p_a, const VNoteFile *p_b) {
             if (reverse) {
@@ -1385,7 +1387,7 @@ void VFileList::sortFiles(QVector<VNoteFile *> &p_files, ViewOrder p_order)
 
     case ViewOrder::ModifiedTimeReverse:
         reverse = true;
-        V_FALLTHROUGH
+        V_FALLTHROUGH;
     case ViewOrder::ModifiedTime:
         std::sort(p_files.begin(), p_files.end(), [reverse](const VNoteFile *p_a, const VNoteFile *p_b) {
             if (reverse) {

+ 29 - 9
src/vmainwindow.cpp

@@ -46,6 +46,7 @@
 #include "vlistfolderue.h"
 #include "dialog/vfixnotebookdialog.h"
 #include "vhistorylist.h"
+#include "vexplorer.h"
 
 extern VConfigManager *g_config;
 
@@ -280,6 +281,11 @@ void VMainWindow::setupNaviBox()
     m_naviBox->addItem(m_historyList,
                        ":/resources/icons/history.svg",
                        tr("History"));
+
+    m_explorer = new VExplorer();
+    m_naviBox->addItem(m_explorer,
+                       ":/resources/icons/explorer.svg",
+                       tr("Explorer"));
 }
 
 void VMainWindow::setupNotebookPanel()
@@ -930,7 +936,11 @@ void VMainWindow::initFileMenu()
                 // Update lastPath
                 lastPath = QFileInfo(files[0]).path();
 
-                openFiles(VUtils::filterFilePathsToOpen(files));
+                openFiles(VUtils::filterFilePathsToOpen(files),
+                          false,
+                          g_config->getNoteOpenMode(),
+                          false,
+                          false);
             });
 
     fileMenu->addAction(openAct);
@@ -2130,6 +2140,7 @@ void VMainWindow::saveStateAndGeometry()
     g_config->setSearchDockChecked(m_searchDock->isVisible());
     g_config->setNotebookSplitterState(m_nbSplitter->saveState());
     g_config->setMainSplitterState(m_mainSplitter->saveState());
+    g_config->setNaviBoxCurrentIndex(m_naviBox->currentIndex());
 }
 
 void VMainWindow::restoreStateAndGeometry()
@@ -2156,6 +2167,8 @@ void VMainWindow::restoreStateAndGeometry()
     if (!nbSplitterState.isEmpty()) {
         m_nbSplitter->restoreState(nbSplitterState);
     }
+
+    m_naviBox->setCurrentIndex(g_config->getNaviBoxCurrentIndex());
 }
 
 void VMainWindow::handleCurrentDirectoryChanged(const VDirectory *p_dir)
@@ -2457,12 +2470,15 @@ bool VMainWindow::tryOpenInternalFile(const QString &p_filePath)
     return false;
 }
 
-void VMainWindow::openFiles(const QStringList &p_files,
-                            bool p_forceOrphan,
-                            OpenFileMode p_mode,
-                            bool p_forceMode,
-                            bool p_oneByOne)
+QVector<VFile *> VMainWindow::openFiles(const QStringList &p_files,
+                                        bool p_forceOrphan,
+                                        OpenFileMode p_mode,
+                                        bool p_forceMode,
+                                        bool p_oneByOne)
 {
+    QVector<VFile *> vfiles;
+    vfiles.reserve(p_files.size());
+
     for (int i = 0; i < p_files.size(); ++i) {
         VFile *file = NULL;
         if (!p_forceOrphan) {
@@ -2474,10 +2490,14 @@ void VMainWindow::openFiles(const QStringList &p_files,
         }
 
         m_editArea->openFile(file, p_mode, p_forceMode);
+        vfiles.append(file);
+
         if (p_oneByOne) {
             QCoreApplication::sendPostedEvents();
         }
     }
+
+    return vfiles;
 }
 
 void VMainWindow::editOrphanFileInfo(VFile *p_file)
@@ -2497,7 +2517,7 @@ void VMainWindow::checkSharedMemory()
     QStringList files = m_guard->fetchFilesToOpen();
     if (!files.isEmpty()) {
         qDebug() << "shared memory fetch files" << files;
-        openFiles(files);
+        openFiles(files, false, g_config->getNoteOpenMode(), false, false);
 
         // Eliminate the signal.
         m_guard->fetchAskedToShow();
@@ -2579,7 +2599,7 @@ void VMainWindow::openStartupPages()
     {
         QStringList pagesToOpen = VUtils::filterFilePathsToOpen(g_config->getStartupPages());
         qDebug() << "open startup pages" << pagesToOpen;
-        openFiles(pagesToOpen, false, OpenFileMode::Read, false, true);
+        openFiles(pagesToOpen, false, g_config->getNoteOpenMode(), false, true);
         break;
     }
 
@@ -3149,7 +3169,7 @@ void VMainWindow::kickOffStartUpTimer(const QStringList &p_files)
         promptNewNotebookIfEmpty();
         QCoreApplication::sendPostedEvents();
         openStartupPages();
-        openFiles(p_files, false, OpenFileMode::Read, false, true);
+        openFiles(p_files, false, g_config->getNoteOpenMode(), false, true);
     });
 }
 

+ 8 - 5
src/vmainwindow.h

@@ -43,6 +43,7 @@ class VSearcher;
 class QPrinter;
 class VUniversalEntry;
 class VHistoryList;
+class VExplorer;
 
 enum class PanelViewState
 {
@@ -88,11 +89,11 @@ public:
     // Open files @p_files as orphan files or internal note files.
     // If @p_forceOrphan is false, for each file, VNote will try to find out if
     // it is a note inside VNote. If yes, VNote will open it as internal file.
-    void openFiles(const QStringList &p_files,
-                   bool p_forceOrphan = false,
-                   OpenFileMode p_mode = OpenFileMode::Read,
-                   bool p_forceMode = false,
-                   bool p_oneByOne = false);
+    QVector<VFile *> openFiles(const QStringList &p_files,
+                               bool p_forceOrphan = false,
+                               OpenFileMode p_mode = OpenFileMode::Read,
+                               bool p_forceMode = false,
+                               bool p_oneByOne = false);
 
     // Try to open @p_filePath as internal note.
     bool tryOpenInternalFile(const QString &p_filePath);
@@ -449,6 +450,8 @@ private:
 
     VHistoryList *m_historyList;
 
+    VExplorer *m_explorer;
+
     // Interval of the shared memory timer in ms.
     static const int c_sharedMemTimerInterval;
 };

+ 5 - 0
src/vnote.qrc

@@ -218,5 +218,10 @@
         <file>resources/icons/clear_history.svg</file>
         <file>resources/icons/pin.svg</file>
         <file>resources/icons/view.svg</file>
+        <file>resources/icons/explorer.svg</file>
+        <file>resources/icons/star.svg</file>
+        <file>resources/icons/open_location.svg</file>
+        <file>resources/icons/unstar.svg</file>
+        <file>resources/icons/explore_root.svg</file>
     </qresource>
 </RCC>

+ 109 - 107
src/vnotebookselector.cpp

@@ -50,112 +50,10 @@ VNotebookSelector::VNotebookSelector(QWidget *p_parent)
     m_listWidget->viewport()->installEventFilter(this);
     m_listWidget->installEventFilter(this);
 
-    initActions();
-
     connect(this, SIGNAL(currentIndexChanged(int)),
             this, SLOT(handleCurIndexChanged(int)));
 }
 
-void VNotebookSelector::initActions()
-{
-    m_deleteNotebookAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/delete_notebook.svg"),
-                                      tr("&Delete"), this);
-    m_deleteNotebookAct->setToolTip(tr("Delete current notebook"));
-    connect(m_deleteNotebookAct, SIGNAL(triggered(bool)),
-            this, SLOT(deleteNotebook()));
-
-    m_notebookInfoAct = new QAction(VIconUtils::menuIcon(":/resources/icons/notebook_info.svg"),
-                                    tr("&Info"), this);
-    m_notebookInfoAct->setToolTip(tr("View and edit current notebook's information"));
-    connect(m_notebookInfoAct, SIGNAL(triggered(bool)),
-            this, SLOT(editNotebookInfo()));
-
-    m_openLocationAct = new QAction(tr("&Open Notebook Location"), this);
-    m_openLocationAct->setToolTip(tr("Open the root folder of this notebook in operating system"));
-    connect(m_openLocationAct, &QAction::triggered,
-            this, [this]() {
-                QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
-                if (items.isEmpty()) {
-                    return;
-                }
-
-                Q_ASSERT(items.size() == 1);
-                VNotebook *notebook = getNotebook(items[0]);
-                QUrl url = QUrl::fromLocalFile(notebook->getPath());
-                QDesktopServices::openUrl(url);
-            });
-
-    m_recycleBinAct = new QAction(VIconUtils::menuIcon(":/resources/icons/recycle_bin.svg"),
-                                  tr("&Recycle Bin"), this);
-    m_recycleBinAct->setToolTip(tr("Open the recycle bin of this notebook"));
-    connect(m_recycleBinAct, &QAction::triggered,
-            this, [this]() {
-                QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
-                if (items.isEmpty()) {
-                    return;
-                }
-
-                Q_ASSERT(items.size() == 1);
-                VNotebook *notebook = getNotebook(items[0]);
-                QUrl url = QUrl::fromLocalFile(notebook->getRecycleBinFolderPath());
-                QDesktopServices::openUrl(url);
-            });
-
-    m_emptyRecycleBinAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/empty_recycle_bin.svg"),
-                                       tr("&Empty Recycle Bin"), this);
-    m_emptyRecycleBinAct->setToolTip(tr("Empty the recycle bin of this notebook"));
-    connect(m_emptyRecycleBinAct, &QAction::triggered,
-            this, [this]() {
-                QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
-                if (items.isEmpty()) {
-                    return;
-                }
-
-                Q_ASSERT(items.size() == 1);
-                VNotebook *notebook = getNotebook(items[0]);
-                QString binPath = notebook->getRecycleBinFolderPath();
-
-                int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
-                                              tr("Are you sure to empty recycle bin of notebook "
-                                                 "<span style=\"%1\">%2</span>?")
-                                                .arg(g_config->c_dataTextStyle)
-                                                .arg(notebook->getName()),
-                                              tr("<span style=\"%1\">WARNING</span>: "
-                                                 "VNote will delete all the files in directory "
-                                                 "<span style=\"%2\">%3</span>."
-                                                 "<br>It may be UNRECOVERABLE!")
-                                                .arg(g_config->c_warningTextStyle)
-                                                .arg(g_config->c_dataTextStyle)
-                                                .arg(binPath),
-                                              QMessageBox::Ok | QMessageBox::Cancel,
-                                              QMessageBox::Ok,
-                                              this,
-                                              MessageBoxType::Danger);
-                if (ret == QMessageBox::Ok) {
-                    QString info;
-                    if (VUtils::emptyDirectory(notebook, binPath, true)) {
-                        info = tr("Successfully emptied recycle bin of notebook "
-                                  "<span style=\"%1\">%2</span>!")
-                                 .arg(g_config->c_dataTextStyle)
-                                 .arg(notebook->getName());
-                    } else {
-                        info = tr("Fail to empty recycle bin of notebook "
-                                  "<span style=\"%1\">%2</span>!")
-                                 .arg(g_config->c_dataTextStyle)
-                                 .arg(notebook->getName());
-                    }
-
-                    VUtils::showMessage(QMessageBox::Information,
-                                        tr("Information"),
-                                        info,
-                                        "",
-                                        QMessageBox::Ok,
-                                        QMessageBox::Ok,
-                                        this);
-                }
-            });
-}
-
 void VNotebookSelector::updateComboBox()
 {
     m_muted = true;
@@ -479,18 +377,122 @@ void VNotebookSelector::popupListContextMenuRequested(QPoint p_pos)
 
     QMenu menu(this);
     menu.setToolTipsVisible(true);
-    menu.addAction(m_deleteNotebookAct);
+
+    QAction *deleteNotebookAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/delete_notebook.svg"),
+                                             tr("&Delete"),
+                                             &menu);
+    deleteNotebookAct->setToolTip(tr("Delete current notebook"));
+    connect(deleteNotebookAct, SIGNAL(triggered(bool)),
+            this, SLOT(deleteNotebook()));
+    menu.addAction(deleteNotebookAct);
+
     if (nb->isValid()) {
         menu.addSeparator();
-        menu.addAction(m_recycleBinAct);
-        menu.addAction(m_emptyRecycleBinAct);
+
+        QAction *recycleBinAct = new QAction(VIconUtils::menuIcon(":/resources/icons/recycle_bin.svg"),
+                                             tr("&Recycle Bin"),
+                                             &menu);
+        recycleBinAct->setToolTip(tr("Open the recycle bin of this notebook"));
+        connect(recycleBinAct, &QAction::triggered,
+                this, [this]() {
+                    QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
+                    if (items.isEmpty()) {
+                        return;
+                    }
+
+                    Q_ASSERT(items.size() == 1);
+                    VNotebook *notebook = getNotebook(items[0]);
+                    QUrl url = QUrl::fromLocalFile(notebook->getRecycleBinFolderPath());
+                    QDesktopServices::openUrl(url);
+                });
+
+        menu.addAction(recycleBinAct);
+
+        QAction *emptyRecycleBinAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/empty_recycle_bin.svg"),
+                                                  tr("&Empty Recycle Bin"),
+                                                  &menu);
+        emptyRecycleBinAct->setToolTip(tr("Empty the recycle bin of this notebook"));
+        connect(emptyRecycleBinAct, &QAction::triggered,
+                this, [this]() {
+                    QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
+                    if (items.isEmpty()) {
+                        return;
+                    }
+
+                    Q_ASSERT(items.size() == 1);
+                    VNotebook *notebook = getNotebook(items[0]);
+                    QString binPath = notebook->getRecycleBinFolderPath();
+
+                    int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                                                  tr("Are you sure to empty recycle bin of notebook "
+                                                     "<span style=\"%1\">%2</span>?")
+                                                    .arg(g_config->c_dataTextStyle)
+                                                    .arg(notebook->getName()),
+                                                  tr("<span style=\"%1\">WARNING</span>: "
+                                                     "VNote will delete all the files in directory "
+                                                     "<span style=\"%2\">%3</span>."
+                                                     "<br>It may be UNRECOVERABLE!")
+                                                    .arg(g_config->c_warningTextStyle)
+                                                    .arg(g_config->c_dataTextStyle)
+                                                    .arg(binPath),
+                                                  QMessageBox::Ok | QMessageBox::Cancel,
+                                                  QMessageBox::Ok,
+                                                  this,
+                                                  MessageBoxType::Danger);
+                    if (ret == QMessageBox::Ok) {
+                        QString info;
+                        if (VUtils::emptyDirectory(notebook, binPath, true)) {
+                            info = tr("Successfully emptied recycle bin of notebook "
+                                      "<span style=\"%1\">%2</span>!")
+                                     .arg(g_config->c_dataTextStyle)
+                                     .arg(notebook->getName());
+                        } else {
+                            info = tr("Fail to empty recycle bin of notebook "
+                                      "<span style=\"%1\">%2</span>!")
+                                     .arg(g_config->c_dataTextStyle)
+                                     .arg(notebook->getName());
+                        }
+
+                        VUtils::showMessage(QMessageBox::Information,
+                                            tr("Information"),
+                                            info,
+                                            "",
+                                            QMessageBox::Ok,
+                                            QMessageBox::Ok,
+                                            this);
+                    }
+                });
+        menu.addAction(emptyRecycleBinAct);
     }
 
     menu.addSeparator();
-    menu.addAction(m_openLocationAct);
+
+    QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"),
+                                           tr("&Open Notebook Location"),
+                                           &menu);
+    openLocationAct->setToolTip(tr("Explore the root folder of this notebook in operating system"));
+    connect(openLocationAct, &QAction::triggered,
+            this, [this]() {
+                QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
+                if (items.isEmpty()) {
+                    return;
+                }
+
+                Q_ASSERT(items.size() == 1);
+                VNotebook *notebook = getNotebook(items[0]);
+                QUrl url = QUrl::fromLocalFile(notebook->getPath());
+                QDesktopServices::openUrl(url);
+            });
+    menu.addAction(openLocationAct);
 
     if (nb->isValid()) {
-        menu.addAction(m_notebookInfoAct);
+        QAction *notebookInfoAct = new QAction(VIconUtils::menuIcon(":/resources/icons/notebook_info.svg"),
+                                               tr("&Info"),
+                                               &menu);
+        notebookInfoAct->setToolTip(tr("View and edit current notebook's information"));
+        connect(notebookInfoAct, SIGNAL(triggered(bool)),
+                this, SLOT(editNotebookInfo()));
+        menu.addAction(notebookInfoAct);
     }
 
     menu.exec(m_listWidget->mapToGlobal(p_pos));

+ 0 - 10
src/vnotebookselector.h

@@ -8,7 +8,6 @@
 
 class VNotebook;
 class QListWidget;
-class QAction;
 class QListWidgetItem;
 class QLabel;
 
@@ -66,8 +65,6 @@ private slots:
     void editNotebookInfo();
 
 private:
-    void initActions();
-
     // Update Combox from m_notebooks.
     void updateComboBox();
 
@@ -115,13 +112,6 @@ private:
     // Whether it is muted from currentIndexChanged().
     bool m_muted;
 
-    // Actions
-    QAction *m_deleteNotebookAct;
-    QAction *m_notebookInfoAct;
-    QAction *m_openLocationAct;
-    QAction *m_recycleBinAct;
-    QAction *m_emptyRecycleBinAct;
-
     QLabel *m_naviLabel;
 };
 

+ 53 - 21
src/vsearcher.cpp

@@ -25,36 +25,23 @@ extern VConfigManager *g_config;
 
 VSearcher::VSearcher(QWidget *p_parent)
     : QWidget(p_parent),
+      m_initialized(false),
+      m_uiInitialized(false),
       m_inSearch(false),
       m_askedToStop(false),
       m_search(this)
 {
     qRegisterMetaType<QList<QSharedPointer<VSearchResultItem>>>("QList<QSharedPointer<VSearchResultItem>>");
-
-    setupUI();
-
-    initUIFields();
-
-    handleInputChanged();
-
-    connect(&m_search, &VSearch::resultItemAdded,
-            this, [this](const QSharedPointer<VSearchResultItem> &p_item) {
-                // Not sure if it works.
-                QCoreApplication::sendPostedEvents(NULL, QEvent::MouseButtonRelease);
-                m_results->addResultItem(p_item);
-            });
-    connect(&m_search, &VSearch::resultItemsAdded,
-            this, [this](const QList<QSharedPointer<VSearchResultItem> > &p_items) {
-                // Not sure if it works.
-                QCoreApplication::sendPostedEvents(NULL, QEvent::MouseButtonRelease);
-                m_results->addResultItems(p_items);
-            });
-    connect(&m_search, &VSearch::finished,
-            this, &VSearcher::handleSearchFinished);
 }
 
 void VSearcher::setupUI()
 {
+    if (m_uiInitialized) {
+        return;
+    }
+
+    m_uiInitialized = true;
+
     // Search button.
     m_searchBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/search.svg"), "", this);
     m_searchBtn->setToolTip(tr("Search"));
@@ -112,6 +99,7 @@ void VSearcher::setupUI()
     m_keywordCB->setLineEdit(new VLineEdit(this));
     m_keywordCB->setToolTip(tr("Keywords to search for"));
     m_keywordCB->lineEdit()->setPlaceholderText(tr("Supports space, &&, and ||"));
+    m_keywordCB->lineEdit()->setProperty("EmbeddedEdit", true);
     connect(m_keywordCB, &QComboBox::currentTextChanged,
             this, &VSearcher::handleInputChanged);
     connect(m_keywordCB->lineEdit(), &QLineEdit::returnPressed,
@@ -141,6 +129,7 @@ void VSearcher::setupUI()
     m_filePatternCB->setEditable(true);
     m_filePatternCB->setLineEdit(new VLineEdit(this));
     m_filePatternCB->setToolTip(tr("Wildcard pattern to filter the files to be searched"));
+    m_filePatternCB->lineEdit()->setProperty("EmbeddedEdit", true);
     m_filePatternCB->completer()->setCaseSensitivity(Qt::CaseSensitive);
 
     // Engine.
@@ -544,19 +533,62 @@ void VSearcher::updateNumLabel(int p_count)
 
 void VSearcher::focusToSearch()
 {
+    init();
+
     m_keywordCB->setFocus(Qt::OtherFocusReason);
 }
 
 void VSearcher::showNavigation()
 {
+    setupUI();
+
     VNavigationMode::showNavigation(m_results);
 }
 
 bool VSearcher::handleKeyNavigation(int p_key, bool &p_succeed)
 {
     static bool secondKey = false;
+    setupUI();
+
     return VNavigationMode::handleKeyNavigation(m_results,
                                                 secondKey,
                                                 p_key,
                                                 p_succeed);
 }
+
+void VSearcher::init()
+{
+    if (m_initialized) {
+        return;
+    }
+
+    m_initialized = true;
+
+    setupUI();
+
+    initUIFields();
+
+    handleInputChanged();
+
+    connect(&m_search, &VSearch::resultItemAdded,
+            this, [this](const QSharedPointer<VSearchResultItem> &p_item) {
+                // Not sure if it works.
+                QCoreApplication::sendPostedEvents(NULL, QEvent::MouseButtonRelease);
+                m_results->addResultItem(p_item);
+            });
+    connect(&m_search, &VSearch::resultItemsAdded,
+            this, [this](const QList<QSharedPointer<VSearchResultItem> > &p_items) {
+                // Not sure if it works.
+                QCoreApplication::sendPostedEvents(NULL, QEvent::MouseButtonRelease);
+                m_results->addResultItems(p_items);
+            });
+    connect(&m_search, &VSearch::finished,
+            this, &VSearcher::handleSearchFinished);
+}
+
+void VSearcher::showEvent(QShowEvent *p_event)
+{
+    init();
+
+    QWidget::showEvent(p_event);
+}

+ 10 - 0
src/vsearcher.h

@@ -15,6 +15,7 @@ class QLabel;
 class VSearchResultTree;
 class QProgressBar;
 class QPlainTextEdit;
+class QShowEvent;
 
 class VSearcher : public QWidget, public VNavigationMode
 {
@@ -28,6 +29,9 @@ public:
     void showNavigation() Q_DECL_OVERRIDE;
     bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
 
+protected:
+    void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
+
 private slots:
     void handleSearchFinished(const QSharedPointer<VSearchResult> &p_result);
 
@@ -36,6 +40,8 @@ private:
 
     void setupUI();
 
+    void init();
+
     void initUIFields();
 
     void setProgressVisible(bool p_visible);
@@ -98,6 +104,10 @@ private:
 
     QPlainTextEdit *m_consoleEdit;
 
+    bool m_initialized;
+
+    bool m_uiInitialized;
+
     bool m_inSearch;
 
     bool m_askedToStop;

+ 5 - 1
src/vtoolbox.cpp

@@ -86,7 +86,11 @@ int VToolBox::addItem(QWidget *p_widget,
 void VToolBox::setCurrentIndex(int p_idx, bool p_focus)
 {
     if (p_idx < 0 || p_idx >= m_items.size()) {
-        m_currentIndex = -1;
+        if (m_items.isEmpty()) {
+            m_currentIndex = -1;
+        } else {
+            m_currentIndex = 0;
+        }
     } else {
         m_currentIndex = p_idx;
     }

+ 7 - 0
src/vtoolbox.h

@@ -27,6 +27,8 @@ public:
 
     void setCurrentWidget(QWidget *p_widget, bool p_focus = true);
 
+    int currentIndex() const;
+
     // Implementations for VNavigationMode.
     void showNavigation() Q_DECL_OVERRIDE;
     bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
@@ -78,4 +80,9 @@ private:
     QVector<ItemInfo> m_items;
 };
 
+inline int VToolBox::currentIndex() const
+{
+    return m_currentIndex;
+}
+
 #endif // VTOOLBOX_H

+ 6 - 0
src/vtreewidget.cpp

@@ -53,6 +53,12 @@ VTreeWidget::VTreeWidget(QWidget *p_parent)
                     resizeColumnToContents(0);
                 }
             });
+    connect(this, &VTreeWidget::itemCollapsed,
+            this, [this]() {
+                if (m_fitContent) {
+                    resizeColumnToContents(0);
+                }
+            });
 }
 
 void VTreeWidget::keyPressEvent(QKeyEvent *p_event)