├── .gitattributes
├── .gitignore
├── README.md
├── novelReader.sln
├── novelReader
├── App.config
├── App.xaml
├── App.xaml.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Models
│ ├── ChapterMatcher.cs
│ ├── Content.cs
│ ├── ListBoxWrapper.cs
│ ├── NovelFile.cs
│ ├── Reader.cs
│ └── Sidebar.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── Resources
│ └── file-256.ico
├── Utils
│ ├── HeaderListBoxKeyHandler.cs
│ ├── KeyHandler.cs
│ └── ParagraphListBoxKeyHandler.cs
└── novelReader.csproj
└── screenshots
└── UltraSimpleTxtReader_1.1.0.PNG
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## Visual Studio
34 | #################
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | *.suo
41 | *.user
42 | *.sln.docstates
43 |
44 | # Build results
45 |
46 | [Dd]ebug/
47 | [Rr]elease/
48 | x64/
49 | build/
50 | [Bb]in/
51 | [Oo]bj/
52 |
53 | # MSTest test Results
54 | [Tt]est[Rr]esult*/
55 | [Bb]uild[Ll]og.*
56 |
57 | *_i.c
58 | *_p.c
59 | *.ilk
60 | *.meta
61 | *.obj
62 | *.pch
63 | *.pdb
64 | *.pgc
65 | *.pgd
66 | *.rsp
67 | *.sbr
68 | *.tlb
69 | *.tli
70 | *.tlh
71 | *.tmp
72 | *.tmp_proj
73 | *.log
74 | *.vspscc
75 | *.vssscc
76 | .builds
77 | *.pidb
78 | *.log
79 | *.scc
80 |
81 | # Visual C++ cache files
82 | ipch/
83 | *.aps
84 | *.ncb
85 | *.opensdf
86 | *.sdf
87 | *.cachefile
88 |
89 | # Visual Studio profiler
90 | *.psess
91 | *.vsp
92 | *.vspx
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 |
101 | # TeamCity is a build add-in
102 | _TeamCity*
103 |
104 | # DotCover is a Code Coverage Tool
105 | *.dotCover
106 |
107 | # NCrunch
108 | *.ncrunch*
109 | .*crunch*.local.xml
110 |
111 | # Installshield output folder
112 | [Ee]xpress/
113 |
114 | # DocProject is a documentation generator add-in
115 | DocProject/buildhelp/
116 | DocProject/Help/*.HxT
117 | DocProject/Help/*.HxC
118 | DocProject/Help/*.hhc
119 | DocProject/Help/*.hhk
120 | DocProject/Help/*.hhp
121 | DocProject/Help/Html2
122 | DocProject/Help/html
123 |
124 | # Click-Once directory
125 | publish/
126 |
127 | # Publish Web Output
128 | *.Publish.xml
129 | *.pubxml
130 |
131 | # NuGet Packages Directory
132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
133 | #packages/
134 |
135 | # Windows Azure Build Output
136 | csx
137 | *.build.csdef
138 |
139 | # Windows Store app package directory
140 | AppPackages/
141 |
142 | # Others
143 | sql/
144 | *.Cache
145 | ClientBin/
146 | [Ss]tyle[Cc]op.*
147 | ~$*
148 | *~
149 | *.dbmdl
150 | *.[Pp]ublish.xml
151 | *.pfx
152 | *.publishsettings
153 |
154 | # RIA/Silverlight projects
155 | Generated_Code/
156 |
157 | # Backup & report files from converting an old project file to a newer
158 | # Visual Studio version. Backup files are not needed, because we have git ;-)
159 | _UpgradeReport_Files/
160 | Backup*/
161 | UpgradeLog*.XML
162 | UpgradeLog*.htm
163 |
164 | # SQL Server files
165 | App_Data/*.mdf
166 | App_Data/*.ldf
167 |
168 | #############
169 | ## Windows detritus
170 | #############
171 |
172 | # Windows image file caches
173 | Thumbs.db
174 | ehthumbs.db
175 |
176 | # Folder config file
177 | Desktop.ini
178 |
179 | # Recycle Bin used on file shares
180 | $RECYCLE.BIN/
181 |
182 | # Mac crap
183 | .DS_Store
184 |
185 |
186 | #############
187 | ## Python
188 | #############
189 |
190 | *.py[co]
191 |
192 | # Packages
193 | *.egg
194 | *.egg-info
195 | dist/
196 | build/
197 | eggs/
198 | parts/
199 | var/
200 | sdist/
201 | develop-eggs/
202 | .installed.cfg
203 |
204 | # Installer logs
205 | pip-log.txt
206 |
207 | # Unit test / coverage reports
208 | .coverage
209 | .tox
210 |
211 | #Translations
212 | *.mo
213 |
214 | #Mr Developer
215 | .mr.developer.cfg
216 | /.vs
217 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 无比简易小说阅读器 (UltraSimpleTxtReader)
2 |
3 | 无比简易的TXT小说阅读器,基本够用。
4 |
5 | 
6 |
7 | ## 基本功能
8 |
9 | - 自动抓取小说章节
10 | - 自动记录上次打开的文件和阅读的位置
11 | - 自动滚动阅读(估算阅读时间)
12 | - 显示阅读信息(章节,段落,百分比)
13 | - 快捷键操作:
14 | - `j` 下一段,`k` 上一段
15 | - `n` 下一章,`p` 下一章
16 | - `t` 第一章,`g` 最后一章
17 | - `h` 选择章节,`l` 阅读小说
18 | - 拖动TXT文件到软件中打开
19 |
20 | ## 下载
21 |
22 | - [下载地址](https://github.com/zhuochun/novelReader/releases)
23 | - 支持Windows 7+
24 |
25 | ## 许可
26 |
27 | Copyright (c) 2014 **[Wang Zhuochun](https://github.com/zhuochun)**.
28 | Licensed under the MIT license.
29 |
--------------------------------------------------------------------------------
/novelReader.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2012
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "novelReader", "novelReader\novelReader.csproj", "{2BE999F0-D542-4FD8-8626-FDC57A9EBE53}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|Any CPU = Debug|Any CPU
9 | Release|Any CPU = Release|Any CPU
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {2BE999F0-D542-4FD8-8626-FDC57A9EBE53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13 | {2BE999F0-D542-4FD8-8626-FDC57A9EBE53}.Debug|Any CPU.Build.0 = Debug|Any CPU
14 | {2BE999F0-D542-4FD8-8626-FDC57A9EBE53}.Release|Any CPU.ActiveCfg = Release|Any CPU
15 | {2BE999F0-D542-4FD8-8626-FDC57A9EBE53}.Release|Any CPU.Build.0 = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/novelReader/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | -1
18 |
19 |
20 | -1
21 |
22 |
23 | 25
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/novelReader/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
15 |
16 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/novelReader/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using novelReader.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Configuration;
6 | using System.Data;
7 | using System.Linq;
8 | using System.Threading.Tasks;
9 | using System.Windows;
10 |
11 | namespace novelReader
12 | {
13 | public partial class App : Application
14 | {
15 | private ObservableCollection novel = new ObservableCollection();
16 | private ObservableCollection headers = new ObservableCollection();
17 |
18 | public ObservableCollection Novel {
19 | get { return this.novel; }
20 | set { this.novel = value; }
21 | }
22 |
23 | public ObservableCollection Headers {
24 | get { return this.headers; }
25 | set { this.headers = value; }
26 | }
27 |
28 | public void AppStartup(object sender, StartupEventArgs args)
29 | {
30 | MainWindow mainWindow = new MainWindow();
31 |
32 | if (args.Args.Length > 0) {
33 | mainWindow.LoadFile(args.Args[0]);
34 | }
35 |
36 | mainWindow.Show();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/novelReader/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
43 |
44 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/novelReader/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using novelReader.Models;
3 | using novelReader.Properties;
4 | using novelReader.Utils;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Collections.ObjectModel;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Text;
11 | using System.Text.RegularExpressions;
12 | using System.Threading.Tasks;
13 | using System.Timers;
14 | using System.Windows;
15 | using System.Windows.Controls;
16 | using System.Windows.Data;
17 | using System.Windows.Documents;
18 | using System.Windows.Input;
19 | using System.Windows.Media;
20 | using System.Windows.Media.Imaging;
21 | using System.Windows.Navigation;
22 | using System.Windows.Shapes;
23 |
24 | namespace novelReader
25 | {
26 | ///
27 | /// Interaction logic for MainWindow.xaml
28 | ///
29 | public partial class MainWindow : Window
30 | {
31 | private ListBoxWrapper editor;
32 | private ListBoxWrapper sidebar;
33 |
34 | private KeyHandler headerListBoxHandler;
35 | private KeyHandler paragraphListBoxHandler;
36 |
37 | private Timer autoScrollTimer = new Timer();
38 | private int autoScrollSpeed = 25; // char per second
39 |
40 | public MainWindow()
41 | {
42 | InitializeComponent();
43 |
44 | editor = new Reader(ParagraphListBox);
45 | sidebar = new Sidebar(HeaderListBox);
46 |
47 | headerListBoxHandler = new HeaderListBoxKeyHandler(sidebar, editor);
48 | paragraphListBoxHandler = new ParagraphListBoxKeyHandler(editor, sidebar);
49 |
50 | autoScrollTimer.AutoReset = true;
51 | autoScrollTimer.Interval = 1000;
52 | autoScrollTimer.Elapsed += autoScrollTimer_Elapsed;
53 |
54 | LoadLastFile();
55 | }
56 |
57 | private bool OpenAndLoadFile(string path)
58 | {
59 | if (String.IsNullOrEmpty(path))
60 | return false;
61 |
62 | App app = (App) Application.Current;
63 | app.Novel.Clear();
64 | app.Headers.Clear();
65 |
66 | NovelFile.Open(path, app.Novel);
67 |
68 | foreach (Content c in app.Novel)
69 | {
70 | if (c.IsHeader)
71 | {
72 | app.Headers.Add(c);
73 | }
74 | }
75 |
76 | this.Title = "无比简易小说阅读器 - " + path;
77 |
78 | return true;
79 | }
80 |
81 |
82 | private void LoadLastFile()
83 | {
84 | try
85 | {
86 | if (OpenAndLoadFile(Settings.Default.LastFilePath))
87 | {
88 | ScrollToLine(Settings.Default.LastFileLineNum, Settings.Default.LastFileChapter);
89 | }
90 | }
91 | catch (FileNotFoundException e)
92 | {
93 | MessageBox.Show("你上次阅读的文件: " + e.FileName + " 找不到了,看个别的吧。", "读取文件错误!");
94 |
95 | // reset settings
96 | Settings.Default.LastFilePath = "";
97 | Settings.Default.LastFileChapter = -1;
98 | Settings.Default.LastFileLineNum = -1;
99 | Settings.Default.Save();
100 | }
101 | }
102 |
103 | public void LoadFile(string filename)
104 | {
105 | try
106 | {
107 | if (OpenAndLoadFile(filename))
108 | {
109 | ScrollToLine(0, 0);
110 |
111 | // save to settings
112 | Settings.Default.LastFilePath = filename;
113 | Settings.Default.LastFileChapter = 0;
114 | Settings.Default.LastFileLineNum = 0;
115 | Settings.Default.Save();
116 | }
117 | }
118 | catch (FileNotFoundException e)
119 | {
120 | MessageBox.Show("你选择的文件: " + e.FileName + " 读取遇到了问题。", "读取文件错误!");
121 |
122 | // reset settings
123 | Settings.Default.LastFilePath = "";
124 | Settings.Default.LastFileChapter = -1;
125 | Settings.Default.LastFileLineNum = -1;
126 | Settings.Default.Save();
127 | }
128 | }
129 |
130 | private void openDialog_Click(object sender, RoutedEventArgs e)
131 | {
132 | OpenFileDialog fileDialog = new OpenFileDialog();
133 |
134 | fileDialog.DefaultExt = "txt";
135 | fileDialog.Filter = "Text files (*.txt)|*.txt";
136 | fileDialog.ShowDialog();
137 |
138 | LoadFile(fileDialog.FileName);
139 | }
140 |
141 | private void ParagraphListBox_Drop(object sender, DragEventArgs e)
142 | {
143 | if (e.Data.GetDataPresent(DataFormats.FileDrop))
144 | {
145 | // can have more than one file.
146 | string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
147 |
148 | // handle the first file only
149 | LoadFile(files[0]);
150 | }
151 | }
152 |
153 | private void ScrollToLine(int lineNum, int chapterNum)
154 | {
155 | sidebar.FocusOnLine(chapterNum);
156 | editor.FocusOnLine(lineNum);
157 | editor.Focus();
158 | }
159 |
160 | private void HeaderListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
161 | {
162 | editor.FocusOnLineItem(sidebar.CurrentLineItem());
163 | editor.Focus();
164 |
165 | // save settings
166 | Settings.Default.LastFileChapter = sidebar.CurrentLine();
167 | Settings.Default.LastFileLineNum = editor.CurrentLine();
168 | Settings.Default.Save();
169 | }
170 |
171 | private void ParagraphListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
172 | {
173 | App app = (App) Application.Current;
174 |
175 | // empty headers
176 | if (app.Headers.Count < 1)
177 | {
178 | return;
179 | }
180 |
181 | // find nearest chapter
182 | object header = app.Headers.First();
183 |
184 | for (int i = editor.CurrentLine(); i >= 0; i--)
185 | {
186 | if (app.Novel[i].IsHeader)
187 | {
188 | header = i;
189 | break;
190 | }
191 | }
192 |
193 | // select chapter
194 | sidebar.FocusOnLineItem(header);
195 |
196 | // save settings
197 | Settings.Default.LastFileChapter = sidebar.CurrentLine();
198 | Settings.Default.LastFileLineNum = editor.CurrentLine();
199 | Settings.Default.Save();
200 |
201 | // update status
202 | UpdateReadingStatus();
203 | UpdateEstimatedTotalReadingTime();
204 | }
205 |
206 | private void UpdateReadingStatus()
207 | {
208 | int totalParagraphs = editor.LineCount();
209 | int currentParagraph = editor.CurrentLine() + 1;
210 |
211 | int totalChapters = sidebar.LineCount();
212 | int currentChapter = sidebar.CurrentLine() + 1;
213 |
214 | if (currentParagraph == totalParagraphs)
215 | {
216 | StatusTextBox.Text = "你已经看完了!";
217 | }
218 | else
219 | {
220 | double percentage = ((double) currentParagraph / totalParagraphs) * 100.0;
221 |
222 | StatusTextBox.Text = String.Format("你在第{2}/{3}段,{0}/{1}章,已经看了{4:F}%",
223 | currentChapter, totalChapters, currentParagraph, totalParagraphs, percentage);
224 | }
225 | }
226 |
227 | private void HeaderListBox_KeyDown(object sender, KeyEventArgs e)
228 | {
229 | e.Handled = headerListBoxHandler.Handle(e);
230 | }
231 |
232 | private void ParagraphListBox_KeyDown(object sender, KeyEventArgs e)
233 | {
234 | e.Handled = paragraphListBoxHandler.Handle(e);
235 | }
236 |
237 | private void AutoScrollCheckBox_State_Changed(object sender, RoutedEventArgs e)
238 | {
239 | if (editor.LineCount() < 1)
240 | {
241 | AutoScrollCheckBox.IsChecked = false;
242 | return;
243 | }
244 |
245 | if (AutoScrollCheckBox.IsChecked == true)
246 | {
247 | autoScrollTimer.Interval = editor.CurrentLineItem().EstimateReadingTime(autoScrollSpeed);
248 | autoScrollTimer.Enabled = true;
249 | autoScrollTimer.Start();
250 | }
251 | else
252 | {
253 | autoScrollTimer.Stop();
254 | autoScrollTimer.Enabled = false;
255 | }
256 |
257 | editor.Focus();
258 | }
259 |
260 | void autoScrollTimer_Elapsed(object sender, ElapsedEventArgs e)
261 | {
262 | Dispatcher.Invoke((Action)(() =>
263 | {
264 | if (editor.IsAtLastLine())
265 | {
266 | AutoScrollCheckBox.IsChecked = false;
267 | }
268 | else
269 | {
270 | editor.FocusOnNextLine();
271 |
272 | autoScrollTimer.Interval = editor.CurrentLineItem().EstimateReadingTime(autoScrollSpeed);
273 | autoScrollTimer.Start();
274 | }
275 | }));
276 | }
277 |
278 | private void AutoScrollSpeedTextBox_LostFocus(object sender, RoutedEventArgs e)
279 | {
280 | autoScrollSpeed = Int32.Parse(AutoScrollSpeedTextBox.Text);
281 | }
282 |
283 | private void UpdateEstimatedTotalReadingTime()
284 | {
285 | double totalTime = ((App)Application.Current).Novel
286 | .Skip(editor.CurrentLine())
287 | .Sum(p => p.EstimateReadingTime(autoScrollSpeed)) / 1000.0;
288 |
289 | int hour = (int) totalTime / 3600;
290 | int minute = ((int)totalTime - hour * 3600) / 60;
291 |
292 | if (hour > 0)
293 | {
294 | TotalReadingTimeEstimate.Text = "需" + hour + "小时";
295 | }
296 | else if (minute > 1)
297 | {
298 | TotalReadingTimeEstimate.Text = "需" + minute + "分钟";
299 | }
300 | else
301 | {
302 | TotalReadingTimeEstimate.Text = "小于1分钟";
303 | }
304 | }
305 | }
306 | }
--------------------------------------------------------------------------------
/novelReader/Models/ChapterMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 | using System.Threading.Tasks;
7 |
8 | namespace novelReader.Models
9 | {
10 | public class ChapterMatcher
11 | {
12 | static Regex chapterRegex = new Regex(@"(?:^\s*|^\s*第.*?)(第[^\s,.,。]*?[章篇]\s?.*)");
13 |
14 | public static Match Exec(string text)
15 | {
16 | return chapterRegex.Match(text);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/novelReader/Models/Content.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 | using System.Threading.Tasks;
7 |
8 | namespace novelReader.Models
9 | {
10 | public class Content
11 | {
12 | public String Text { get; set; }
13 | public String Header { get; set; }
14 | public Boolean IsHeader { get; set; }
15 |
16 | private double readingTime;
17 | private double readingSpeed;
18 |
19 | public Content(String text)
20 | {
21 | this.Text = text.Trim();
22 |
23 | Match matchResult = ChapterMatcher.Exec(this.Text);
24 |
25 | this.Header = matchResult.Value;
26 | this.IsHeader = matchResult.Success;
27 | }
28 |
29 | public double EstimateReadingTime(int speed)
30 | {
31 | if (speed == this.readingSpeed) {
32 | return this.readingTime;
33 | }
34 |
35 | this.readingSpeed = speed;
36 | this.readingTime = calReadingTime(this.Text.Length, speed) + calBufferTime(this.Text.Length, speed);
37 |
38 | return this.readingTime;
39 | }
40 |
41 | private double calReadingTime(int length, int speed)
42 | {
43 | return ((double)length / speed) * 1000.0;
44 | }
45 |
46 | private double calBufferTime(int length, int speed)
47 | {
48 | if (length > speed * 2)
49 | {
50 | return calReadingTime(length - speed, speed * 3);
51 | }
52 | else if (length < speed)
53 | {
54 | return calReadingTime(length, speed * 2);
55 | }
56 | else
57 | {
58 | return 0.0;
59 | }
60 | }
61 |
62 | public override string ToString()
63 | {
64 | return this.Text;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/novelReader/Models/ListBoxWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Controls;
7 |
8 | namespace novelReader.Models
9 | {
10 | public abstract class ListBoxWrapper
11 | {
12 | protected ListBox list;
13 | public bool KeepLineInTop { get; set; }
14 |
15 | public ListBoxWrapper(ListBox list)
16 | {
17 | this.list = list;
18 | this.KeepLineInTop = true;
19 | }
20 |
21 | private void gotoLine(int num)
22 | {
23 | this.list.SelectedIndex = num;
24 | this.list.ScrollIntoView(this.list.SelectedItem);
25 | }
26 |
27 | private void gotoLineItem(object item)
28 | {
29 | this.list.SelectedItem = item;
30 | this.list.ScrollIntoView(this.list.SelectedItem);
31 | }
32 |
33 | public void FocusOnFirstLine()
34 | {
35 | this.gotoLine(0);
36 | }
37 |
38 | public void FocusOnLastLine()
39 | {
40 | this.gotoLine(this.LineCount() - 1);
41 | }
42 |
43 | public void FocusOnPrevLine()
44 | {
45 | if (this.IsAtFirstLine()) return;
46 |
47 | this.gotoLine(this.CurrentLine() - 1);
48 | }
49 |
50 | public void FocusOnNextLine()
51 | {
52 | if (this.IsAtLastLine()) return;
53 |
54 | int nextLine = this.CurrentLine() + 1;
55 |
56 | if (this.KeepLineInTop)
57 | {
58 | this.FocusOnLastLine();
59 | this.Refresh();
60 | }
61 |
62 | this.gotoLine(nextLine);
63 | }
64 |
65 | public void FocusOnLine(int num) {
66 | if (this.KeepLineInTop)
67 | {
68 | this.FocusOnLastLine();
69 | this.Refresh();
70 | }
71 |
72 | this.gotoLine(num);
73 | }
74 |
75 | public void FocusOnLineItem(object item)
76 | {
77 | if (this.KeepLineInTop)
78 | {
79 | this.FocusOnLastLine();
80 | this.Refresh();
81 | }
82 |
83 | this.gotoLineItem(item);
84 | }
85 |
86 | public void Focus() {
87 | this.list.Focus();
88 | }
89 |
90 | public Content CurrentLineItem()
91 | {
92 | return (Content)this.list.SelectedItem;
93 | }
94 |
95 | public int CurrentLine()
96 | {
97 | return this.list.SelectedIndex;
98 | }
99 |
100 | public int LineCount()
101 | {
102 | return this.list.Items.Count;
103 | }
104 |
105 | public bool IsAtFirstLine()
106 | {
107 | return this.CurrentLine() == 0;
108 | }
109 |
110 | public bool IsAtLastLine()
111 | {
112 | return this.CurrentLine() + 1 == this.LineCount();
113 | }
114 |
115 | public void Refresh()
116 | {
117 | this.list.UpdateLayout();
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/novelReader/Models/NovelFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace novelReader.Models
10 | {
11 | public class NovelFile
12 | {
13 | public static void Open(String path, ObservableCollection novel)
14 | {
15 | if (String.IsNullOrEmpty(path))
16 | {
17 | return;
18 | }
19 |
20 | FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read);
21 |
22 | StreamReader sr = new StreamReader(fs);
23 | if (sr.CurrentEncoding == Encoding.UTF8)
24 | {
25 | var chArr = new char[1024];
26 | sr.Read(chArr, 0, chArr.Length);
27 | var buffer1 = Encoding.UTF8.GetBytes(chArr);
28 | var buffer2 = new byte[buffer1.Length];
29 | fs.Position = 0;
30 | fs.Read(buffer2, 0, buffer2.Length);
31 | var same = true;
32 | for (int i = 0; i < buffer1.Length; i++)
33 | {
34 | if (buffer1[i] != buffer2[i])
35 | {
36 | same = false;
37 | break;
38 | }
39 | }
40 | if (!same)
41 | {
42 | fs.Position = 0;
43 | sr = new StreamReader(fs, Encoding.GetEncoding("GBK"));
44 | }
45 | }
46 | String line;
47 |
48 | while ((line = sr.ReadLine()) != null)
49 | {
50 | if (!String.IsNullOrWhiteSpace(line))
51 | {
52 | novel.Add(new Content(line));
53 | }
54 | }
55 | sr.Dispose();
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/novelReader/Models/Reader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Controls;
7 |
8 | namespace novelReader.Models
9 | {
10 | public class Reader : ListBoxWrapper
11 | {
12 | public Reader(ListBox list) : base(list)
13 | {
14 | this.KeepLineInTop = true;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/novelReader/Models/Sidebar.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Controls;
7 |
8 | namespace novelReader.Models
9 | {
10 | public class Sidebar : ListBoxWrapper
11 | {
12 | public Sidebar(ListBox list) : base(list)
13 | {
14 | this.KeepLineInTop = false;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/novelReader/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("无比简易小说阅读器")]
11 | [assembly: AssemblyDescription("无比简易小说阅读器")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("Bicrement")]
14 | [assembly: AssemblyProduct("UltraSimpleTxtReader")]
15 | [assembly: AssemblyCopyright("Copyright © Zhuochun 2014")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.1.0.0")]
55 | [assembly: AssemblyFileVersion("1.1.0.0")]
56 | [assembly: NeutralResourcesLanguageAttribute("")]
57 |
--------------------------------------------------------------------------------
/novelReader/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.18408
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace novelReader.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | public class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | public static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("novelReader.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | public static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
65 | ///
66 | public static System.Drawing.Icon file_256 {
67 | get {
68 | object obj = ResourceManager.GetObject("file_256", resourceCulture);
69 | return ((System.Drawing.Icon)(obj));
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/novelReader/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\Resources\file-256.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
123 |
124 |
--------------------------------------------------------------------------------
/novelReader/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.18408
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace novelReader.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 |
26 | [global::System.Configuration.UserScopedSettingAttribute()]
27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
28 | [global::System.Configuration.DefaultSettingValueAttribute("")]
29 | public string LastFilePath {
30 | get {
31 | return ((string)(this["LastFilePath"]));
32 | }
33 | set {
34 | this["LastFilePath"] = value;
35 | }
36 | }
37 |
38 | [global::System.Configuration.UserScopedSettingAttribute()]
39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
40 | [global::System.Configuration.DefaultSettingValueAttribute("-1")]
41 | public int LastFileChapter {
42 | get {
43 | return ((int)(this["LastFileChapter"]));
44 | }
45 | set {
46 | this["LastFileChapter"] = value;
47 | }
48 | }
49 |
50 | [global::System.Configuration.UserScopedSettingAttribute()]
51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
52 | [global::System.Configuration.DefaultSettingValueAttribute("-1")]
53 | public int LastFileLineNum {
54 | get {
55 | return ((int)(this["LastFileLineNum"]));
56 | }
57 | set {
58 | this["LastFileLineNum"] = value;
59 | }
60 | }
61 |
62 | [global::System.Configuration.UserScopedSettingAttribute()]
63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
64 | [global::System.Configuration.DefaultSettingValueAttribute("25")]
65 | public int ReadingSpeed {
66 | get {
67 | return ((int)(this["ReadingSpeed"]));
68 | }
69 | set {
70 | this["ReadingSpeed"] = value;
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/novelReader/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | -1
10 |
11 |
12 | -1
13 |
14 |
15 | 25
16 |
17 |
18 |
--------------------------------------------------------------------------------
/novelReader/Resources/file-256.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhuochun/UltraSimpleTxtReader/c4ca0b85759fecb6ec6e87e38b2eb9e7e52ba675/novelReader/Resources/file-256.ico
--------------------------------------------------------------------------------
/novelReader/Utils/HeaderListBoxKeyHandler.cs:
--------------------------------------------------------------------------------
1 | using novelReader.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows.Controls;
8 | using System.Windows.Input;
9 |
10 | namespace novelReader.Utils
11 | {
12 | public class HeaderListBoxKeyHandler : KeyHandler
13 | {
14 | public ListBoxWrapper Paragraphs { get; set; }
15 |
16 | public HeaderListBoxKeyHandler(ListBoxWrapper headers, ListBoxWrapper paragraphs) : base(headers)
17 | {
18 | this.Paragraphs = paragraphs;
19 | }
20 |
21 | public override bool Handle(KeyEventArgs e)
22 | {
23 | switch (e.Key)
24 | {
25 | case Key.Enter:
26 | this.Paragraphs.FocusOnLineItem(this.Element.CurrentLineItem());
27 |
28 | return true;
29 | case Key.P:
30 | this.Element.FocusOnPrevLine();
31 | this.Paragraphs.FocusOnLineItem(this.Element.CurrentLineItem());
32 |
33 | return true;
34 | case Key.N:
35 | this.Element.FocusOnNextLine();
36 | this.Paragraphs.FocusOnLineItem(this.Element.CurrentLineItem());
37 |
38 | return true;
39 | case Key.L:
40 | this.Paragraphs.Focus();
41 |
42 | return true;
43 | default: break;
44 | }
45 |
46 | return base.Handle(e);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/novelReader/Utils/KeyHandler.cs:
--------------------------------------------------------------------------------
1 | using novelReader.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows.Controls;
8 | using System.Windows.Input;
9 |
10 | namespace novelReader.Utils
11 | {
12 | public abstract class KeyHandler
13 | {
14 | public ListBoxWrapper Element { get; set; }
15 |
16 | public KeyHandler(ListBoxWrapper e)
17 | {
18 | this.Element = e;
19 | }
20 |
21 | public virtual bool Handle(KeyEventArgs e)
22 | {
23 | switch (e.Key)
24 | {
25 | case Key.J:
26 | this.Element.FocusOnNextLine();
27 |
28 | return true;
29 | case Key.K:
30 | this.Element.FocusOnPrevLine();
31 |
32 | return true;
33 | case Key.T:
34 | this.Element.FocusOnFirstLine();
35 |
36 | return true;
37 | case Key.G:
38 | this.Element.FocusOnLastLine();
39 |
40 | return true;
41 | default: break;
42 | }
43 |
44 | return false;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/novelReader/Utils/ParagraphListBoxKeyHandler.cs:
--------------------------------------------------------------------------------
1 | using novelReader.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows.Controls;
8 | using System.Windows.Input;
9 |
10 | namespace novelReader.Utils
11 | {
12 | public class ParagraphListBoxKeyHandler : KeyHandler
13 | {
14 | public ListBoxWrapper Headers { get; set; }
15 |
16 | public ParagraphListBoxKeyHandler(ListBoxWrapper paragraphs, ListBoxWrapper headers) : base(paragraphs)
17 | {
18 | this.Headers = headers;
19 | }
20 |
21 | public override bool Handle(KeyEventArgs e)
22 | {
23 | switch (e.Key)
24 | {
25 | case Key.P:
26 | this.Headers.FocusOnPrevLine();
27 | this.Element.FocusOnLineItem(this.Headers.CurrentLineItem());
28 |
29 | return true;
30 | case Key.N:
31 | this.Headers.FocusOnNextLine();
32 | this.Element.FocusOnLineItem(this.Headers.CurrentLineItem());
33 |
34 | return true;
35 | case Key.T:
36 | this.Headers.FocusOnFirstLine();
37 | this.Element.FocusOnLineItem(this.Headers.CurrentLineItem());
38 |
39 | return true;
40 | case Key.G:
41 | this.Headers.FocusOnLastLine();
42 | this.Element.FocusOnLineItem(this.Headers.CurrentLineItem());
43 |
44 | return true;
45 | case Key.H:
46 | this.Headers.Focus();
47 |
48 | return true;
49 | default: break;
50 | }
51 |
52 | return base.Handle(e);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/novelReader/novelReader.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {2BE999F0-D542-4FD8-8626-FDC57A9EBE53}
8 | WinExe
9 | Properties
10 | novelReader
11 | UltraSimpleTxtReader
12 | v4.5
13 | 512
14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
15 | 4
16 | false
17 | publish\
18 | true
19 | Disk
20 | false
21 | Foreground
22 | 7
23 | Days
24 | false
25 | false
26 | true
27 | https://github.com/zhuochun/novelReader
28 | UltraSimpleTxtReader
29 | UltraSimpleTxtReader
30 | true
31 | 1
32 | 1.1.0.%2a
33 | false
34 | true
35 | true
36 |
37 |
38 | AnyCPU
39 | true
40 | full
41 | false
42 | bin\Debug\
43 | DEBUG;TRACE
44 | prompt
45 | 4
46 |
47 |
48 | AnyCPU
49 | pdbonly
50 | true
51 | bin\Release\
52 | TRACE
53 | prompt
54 | 4
55 |
56 |
57 | novelReader.App
58 |
59 |
60 | Resources\file-256.ico
61 |
62 |
63 | 8DDAD37D8E4BEF7F9D22E8DB09DE05495D6772EF
64 |
65 |
66 | novelReader_TemporaryKey.pfx
67 |
68 |
69 | true
70 |
71 |
72 | false
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | 4.0
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | MSBuild:Compile
93 | Designer
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | MSBuild:Compile
103 | Designer
104 |
105 |
106 | App.xaml
107 | Code
108 |
109 |
110 | MainWindow.xaml
111 | Code
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | Code
120 |
121 |
122 | True
123 | True
124 | Resources.resx
125 |
126 |
127 | True
128 | Settings.settings
129 | True
130 |
131 |
132 | PublicResXFileCodeGenerator
133 | Resources.Designer.cs
134 |
135 |
136 | SettingsSingleFileGenerator
137 | Settings.Designer.cs
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | False
153 | Microsoft .NET Framework 4.5 %28x86 and x64%29
154 | true
155 |
156 |
157 | False
158 | .NET Framework 3.5 SP1 Client Profile
159 | false
160 |
161 |
162 | False
163 | .NET Framework 3.5 SP1
164 | false
165 |
166 |
167 |
168 |
169 | False
170 | Text Files
171 | UltraSimpleTxtReader
172 | Resources\file-256.ico
173 |
174 |
175 |
176 |
183 |
--------------------------------------------------------------------------------
/screenshots/UltraSimpleTxtReader_1.1.0.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhuochun/UltraSimpleTxtReader/c4ca0b85759fecb6ec6e87e38b2eb9e7e52ba675/screenshots/UltraSimpleTxtReader_1.1.0.PNG
--------------------------------------------------------------------------------