├── .gitignore
├── HOWTO.md
├── LICENSE
├── Makefile
├── README.md
├── Sd.ico
├── Sd.png
├── Secret-Diary.desktop
├── aes.go
├── baidu.jpeg
├── bridge.go
├── categories.go
├── cmd-tools
└── to-html
│ ├── aes.go
│ ├── db.go
│ ├── index.tpl
│ └── tohtml.go
├── db.go
├── dependences.txt
├── editor.go
├── go.mod
├── go.sum
├── gridlayout_after513.go
├── gridlayout_before513.go
├── gui.go
├── linux_launcher
├── .gitignore
└── setup.go
├── locale
└── zh_CN
│ └── LC_MESSAGES
│ ├── sdiary.mo
│ └── sdiary.po
├── pay
├── alipay.jpg
└── wxpay.png
├── pkg
├── linux.sh
├── mkdeb.sh
├── secret-diary.yml
├── shortcut.vbs
└── win32.sh
├── qml
├── icons
│ ├── B.png
│ ├── I.png
│ ├── S.png
│ ├── Sd.png
│ ├── U.png
│ ├── bg.png
│ ├── brush.png
│ ├── document-new.jpg
│ ├── document-save.jpg
│ ├── draw-eraser.png
│ ├── fg.png
│ ├── h1.png
│ ├── h2.png
│ ├── h3.png
│ ├── left.png
│ ├── right.png
│ ├── std.png
│ ├── textcenter.png
│ ├── textjustify.png
│ ├── textleft.png
│ └── textright.png
└── pay
│ ├── alipay.png
│ └── wxpay.png
├── screenshot.png
├── secret-diary.exe.manifest
├── secret-diary.x64.manifest
├── shortcut.vbs
├── shortcut_linux.go
├── shortcut_windows.go
├── storge.go
├── testdata
├── .gitignore
├── README.md
├── aes.go
├── db.go
├── dbgen.go
├── exam.dat
└── jinzhi16.go
├── version.go
├── version.json
├── version.sh
├── versionCheck.go
└── zip.go
/.gitignore:
--------------------------------------------------------------------------------
1 | secret-diary
2 | /deploy
3 | moc*
4 | rcc*
5 | messages.log
6 | linux_launcher
7 | setup
8 | /deb
9 | README.md
10 | 1
11 | 2
12 | 3
13 | *.syso
14 | /vendor
15 | qtbox
16 |
--------------------------------------------------------------------------------
/HOWTO.md:
--------------------------------------------------------------------------------
1 | # Linux上如何自行编译和部署本软件(go module)
2 |
3 | ### 一、安装go编译器
4 |
5 | 1. 从`https://go.dev`下载`go`编译器,解压缩到`/usr/local/`
6 | 2. 在`$HOME/.bashrc`中加入一行`export PATH=$PATH:/usr/local/go/bin`
7 | 3. 用命令`. $HOME/.bashrc`导入环境变量
8 |
9 | 然后可以用命令`go version`测试`go`编译器是否能运行。
10 |
11 | 接着要设置`GOPROXY`变量:`go env -w GOPROXY=https://goproxy.cn,direct`
12 |
13 | ### 二、下载本软件的源代码
14 | 运行命令:`git clone https://gitee.com/rocket049/secret-diary.git`
15 |
16 | ### 三、安装必须的共享库(来自 https://github.com/therecipe/qt)
17 | ```
18 | ubuntu/debian:
19 | sudo apt-get -y install build-essential libglu1-mesa-dev libpulse-dev libglib2.0-dev
20 | sudo apt-get --no-install-recommends install libqt*5-dev qt*5-dev qml-module-qtquick-* qt*5-doc-html
21 |
22 | Fedora/RHEL/CentOS:
23 | sudo yum -y groupinstall "C Development Tools and Libraries"
24 | sudo yum -y install mesa-libGLU-devel gstreamer-plugins-base pulseaudio-libs-devel glib2-devel
25 | sudo yum install qt5-* qt5-*-doc
26 |
27 | openSUSE:
28 | sudo zypper -n install -t pattern devel_basis
29 | sudo zypper install --no-recommends libqt5-qt*-devel
30 |
31 | Arch Linux:
32 | sudo pacman -S base-devel
33 | sudo pacman -S --needed qt5
34 |
35 | Deepin Linux:
36 | sudo apt-get -y install build-essential libglu1-mesa-dev libpulse-dev libglib2.0-dev
37 | sudo apt install qtbase5-dev
38 |
39 | ```
40 |
41 | ### 四、用go编译器编译软件
42 | 在前面运行`git`命令的目录里,按顺序输入下面的命令:
43 |
44 | ```
45 | cd secret-diary/
46 | export QT_PKG_CONFIG=true
47 | go mod tidy
48 | go get -v -tags=no_env github.com/therecipe/qt/cmd/...
49 | go install -v -tags=no_env github.com/therecipe/qt/cmd/...
50 | go mod vendor
51 | ~/go/bin/qtdeploy build desktop
52 | ```
53 |
54 | 等待上面的命令运行结束,就可以得到编译好的程序,位置是:`deploy/linux/secret-diary`
55 |
56 | ### 五、部署软件
57 | 在`secret-diary`目录中依次运行下面的命令:
58 |
59 | ```
60 | sudo mkdir /opt/secret-diary
61 | sudo cp deploy/linux/secret-diary /opt/secret-diary/
62 | sudo cp -r locale /opt/secret-diary/
63 | sudo cp Sd.png /opt/secret-diary/
64 | cp Secret-Diary.desktop ~/.local/share/applications/
65 | ```
66 |
67 | 这时你已经可以从开始菜单中点击启动`Secret-Diary`软件了。
68 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | release:
2 | env GO111MODULE=off qtdeploy build
3 | dev:
4 | env GO111MODULE=off go build
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 说明
2 |
3 | `secret-diary` (安全日记本)是一个加密日记本,加密强度非常强,只能暴力破解数据,只要密码足够复杂,数据就足够安全。
4 | 编辑器也相当完善,支持富文本格式。
5 |
6 | 适用于场合:办公日记、个人日记。
7 |
8 | 兼容 `linux/windows`。
9 | 支持语言:中文、英语
10 |
11 | ### 支持编译环境
12 | - go >= 1.10
13 | - github.com/therecipe/qt
14 | - qt >= 5.13
15 | - qt < 5.13,必须使用参数 `-tags before513`
16 |
17 | ### 具体编译和部署的方法
18 | [HOWTO](HOWTO.md)
19 |
20 | ### 百度下载
21 | 链接: https://pan.baidu.com/s/14Ltsh1WiuKhHgMA7KgA-dw 提取码: stdc
22 |
23 | ### ZOL下载(win32版)
24 | [ZOL软件下载](http://xiazai.zol.com.cn/detail/48/472109.shtml)
25 |
26 | 二维码下载:
27 |
28 | 
29 |
30 | ### ChangeLog
31 |
32 | [At github](https://github.com/rocket049/secret-diary/releases)
33 |
34 | [码云上的](https://gitee.com/rocket049/secret-diary/releases)
35 |
36 | ## 注册用户和删除用户
37 | ### 注册
38 | 在登陆界面上注册用户,方法是:
39 | - 先输入用户名和密码
40 | - 然后点击“注册”
41 | - 接着在弹出的输入框中重复输入密码
42 | - 点击确定
43 |
44 | ### 删除
45 | 1. 如何删除多余的用户?只需到数据存储目录中删除对应名字的目录。How to delete redundant users? Just go to the data storage directory and delete the directory with the corresponding name.
46 | 2. 数据存储目录在哪里?点击“帮助”菜单中的“备份”,会显示该目录路径。Where is the data storage directory? Click Backup on the Help menu to display the directory path.
47 |
48 | # 加密算法说明
49 |
50 | ### 登陆验证算法
51 | `sha256`
52 |
53 | 哈希值计算过程:
54 |
55 | 1. 真实密码 = 用户密码 X 4
56 | 2. sha256( 真实密码 )
57 |
58 | ### 数据加密算法
59 | `AES256`
60 |
61 | 数据加密密码为创建用户时从系统读取的32字节随机字符串。
62 |
63 | “数据加密密码”被加密后存储在数据库中。
64 |
65 | 用于加密“数据加密密码”的密码形成算法:
66 |
67 | 1. 用户真实密码 = 用户密码 X 40
68 | 2. 真实密码 = sha256( 用户真实密码 )
69 |
70 | ## 支持作者 Support Author
71 | 全凭您的自愿! Voluntary!
72 |
73 | 
74 |
75 | 
76 |
--------------------------------------------------------------------------------
/Sd.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/Sd.ico
--------------------------------------------------------------------------------
/Sd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/Sd.png
--------------------------------------------------------------------------------
/Secret-Diary.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Secret-Diary
3 | Comment="Secret-Diary"
4 | Exec=/opt/secret-diary/secret-diary %U
5 | Icon=/opt/secret-diary/Sd.png
6 | Terminal=false
7 | Type=Application
8 | StartupNotify=true
9 | Categories=Office;
--------------------------------------------------------------------------------
/aes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "crypto/rand"
8 | "crypto/sha256"
9 | "fmt"
10 | "io"
11 | "os"
12 | "path"
13 | )
14 |
15 | var dataDir string
16 |
17 | func getSha40String(pwd string) string {
18 | bp := []byte(pwd)
19 | buf := bytes.NewBuffer(make([]byte, 0, len(bp)*40))
20 | for i := 0; i < 40; i++ {
21 | buf.Write(bp)
22 | }
23 | res := sha256.Sum256(buf.Bytes())
24 | return fmt.Sprintf("%x", res)
25 | }
26 |
27 | func getSha4(pwd string) []byte {
28 | bp := []byte(pwd)
29 | buf := bytes.NewBuffer(make([]byte, 0, len(bp)*4))
30 | for i := 0; i < 4; i++ {
31 | buf.Write(bp)
32 | }
33 | res := sha256.Sum256(buf.Bytes())
34 | return res[:]
35 | }
36 |
37 | func getRealKeyString(pwd string) (string, error) {
38 |
39 | rkey := make([]byte, 32)
40 |
41 | io.ReadFull(rand.Reader, rkey)
42 | p1 := getSha4(pwd)
43 | aesBlock, err := aes.NewCipher(p1)
44 | if err != nil {
45 | return "", err
46 | }
47 | iv := make([]byte, aes.BlockSize)
48 | io.ReadFull(rand.Reader, iv)
49 |
50 | aesEncoder := cipher.NewCTR(aesBlock, iv)
51 | var res = make([]byte, 32)
52 | aesEncoder.XORKeyStream(res, rkey)
53 | buf := bytes.NewBuffer(iv)
54 | buf.Write(res)
55 | return fmt.Sprintf("%x", buf.Bytes()), nil
56 | }
57 |
58 | func newRealKeyString(rkey []byte, pwd string) (string, error) {
59 | p1 := getSha4(pwd)
60 | aesBlock, err := aes.NewCipher(p1)
61 | if err != nil {
62 | return "", err
63 | }
64 | iv := make([]byte, aes.BlockSize)
65 | io.ReadFull(rand.Reader, iv)
66 |
67 | aesEncoder := cipher.NewCTR(aesBlock, iv)
68 | var res = make([]byte, 32)
69 | aesEncoder.XORKeyStream(res, rkey)
70 | buf := bytes.NewBuffer(iv)
71 | buf.Write(res)
72 | return fmt.Sprintf("%x", buf.Bytes()), nil
73 | }
74 |
75 | func decodeRealKey(key, data, iv []byte) ([]byte, error) {
76 | aesBlock, err := aes.NewCipher(key)
77 | if err != nil {
78 | return nil, err
79 | }
80 | aesDecoder := cipher.NewCTR(aesBlock, iv)
81 | var res = make([]byte, 32)
82 | aesDecoder.XORKeyStream(res, data)
83 | return res, nil
84 | }
85 |
86 | func encodeToFile(data []byte, filename string, key []byte) error {
87 | dataPath := path.Join(dataDir, filename)
88 | fp, err := os.Create(dataPath)
89 | if err != nil {
90 | return err
91 | }
92 | defer fp.Close()
93 |
94 | aesBlock, err := aes.NewCipher(key)
95 | if err != nil {
96 | return err
97 | }
98 | iv := make([]byte, aesBlock.BlockSize())
99 | io.ReadFull(rand.Reader, iv)
100 | _, err = fp.Write(iv)
101 | if err != nil {
102 | return err
103 | }
104 | aesEncoder := cipher.NewCTR(aesBlock, iv)
105 | wr := cipher.StreamWriter{S: aesEncoder, W: fp}
106 | _, err = wr.Write(data)
107 | return err
108 | }
109 |
110 | func decodeFromFile(filename string, key []byte) ([]byte, error) {
111 | dataPath := path.Join(dataDir, filename)
112 | fp, err := os.Open(dataPath)
113 | if err != nil {
114 | return nil, err
115 | }
116 | defer fp.Close()
117 |
118 | aesBlock, err := aes.NewCipher(key)
119 | if err != nil {
120 | return nil, err
121 | }
122 | iv := make([]byte, aesBlock.BlockSize())
123 | _, err = io.ReadFull(fp, iv)
124 | if err != nil {
125 | return nil, err
126 | }
127 | aesStream := cipher.NewCTR(aesBlock, iv)
128 | rd := cipher.StreamReader{S: aesStream, R: fp}
129 | res := bytes.NewBufferString("")
130 | _, err = io.Copy(res, rd)
131 | return res.Bytes(), err
132 | }
133 |
134 | func encodeToPathName(data []byte, filename string, key []byte) error {
135 | dataPath := filename
136 | fp, err := os.Create(dataPath)
137 | if err != nil {
138 | return err
139 | }
140 | defer fp.Close()
141 |
142 | aesBlock, err := aes.NewCipher(key)
143 | if err != nil {
144 | return err
145 | }
146 | iv := make([]byte, aesBlock.BlockSize())
147 | io.ReadFull(rand.Reader, iv)
148 | _, err = fp.Write(iv)
149 | if err != nil {
150 | return err
151 | }
152 | aesEncoder := cipher.NewCTR(aesBlock, iv)
153 | wr := cipher.StreamWriter{S: aesEncoder, W: fp}
154 | _, err = wr.Write(data)
155 | return err
156 | }
157 |
158 | func decodeFromPathName(filename string, key []byte) ([]byte, error) {
159 | dataPath := filename
160 | fp, err := os.Open(dataPath)
161 | if err != nil {
162 | return nil, err
163 | }
164 | defer fp.Close()
165 |
166 | aesBlock, err := aes.NewCipher(key)
167 | if err != nil {
168 | return nil, err
169 | }
170 | iv := make([]byte, aesBlock.BlockSize())
171 | _, err = io.ReadFull(fp, iv)
172 | if err != nil {
173 | return nil, err
174 | }
175 | aesStream := cipher.NewCTR(aesBlock, iv)
176 | rd := cipher.StreamReader{S: aesStream, R: fp}
177 | res := bytes.NewBufferString("")
178 | _, err = io.Copy(res, rd)
179 | return res.Bytes(), err
180 | }
181 |
182 | func decodeFromReader(reader io.Reader, key []byte) ([]byte, error) {
183 | aesBlock, err := aes.NewCipher(key)
184 | if err != nil {
185 | return nil, err
186 | }
187 | iv := make([]byte, aesBlock.BlockSize())
188 | _, err = io.ReadFull(reader, iv)
189 | if err != nil {
190 | return nil, err
191 | }
192 | aesStream := cipher.NewCTR(aesBlock, iv)
193 | rd := cipher.StreamReader{S: aesStream, R: reader}
194 | res := bytes.NewBufferString("")
195 | _, err = io.Copy(res, rd)
196 | return res.Bytes(), err
197 | }
198 |
--------------------------------------------------------------------------------
/baidu.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/baidu.jpeg
--------------------------------------------------------------------------------
/bridge.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/therecipe/qt/core"
5 | )
6 |
7 | type QmlBridge struct {
8 | core.QObject
9 |
10 | _ func(id, day, title, mtime string, r, c int) `signal:"addDiary"`
11 | _ func(r, c int) `signal:"setMonthFlag"`
12 | _ func(ym string) `signal:"addYearMonth"`
13 | }
14 |
--------------------------------------------------------------------------------
/categories.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/therecipe/qt/core"
5 | "github.com/therecipe/qt/widgets"
6 | )
7 |
8 | func (s *myWindow) showCategores(menubar *widgets.QMenuBar) {
9 | s.categories = menubar.AddMenu2(T("Categories"))
10 | newcategory := s.categories.AddAction(T("New Category"))
11 | newcategory.ConnectTriggered(func(b bool) {
12 | var ok bool
13 | name := widgets.QInputDialog_GetText(s.window, T("New Category Name"), T("Category"), 0, "", &ok, core.Qt__Dialog, 0)
14 | if ok {
15 | id := s.db.AddCategory(name)
16 | s.addCategoryMenuItem(id, name)
17 | }
18 | })
19 |
20 | renameCategory := s.categories.AddAction(T("Rename Category"))
21 | renameCategory.ConnectTriggered(func(b bool) {
22 | var ok bool
23 | name := widgets.QInputDialog_GetText(s.window, T("Rename Category")+" : "+s.categoryName, T("Rename Category")+" : "+s.categoryName, 0, "", &ok, core.Qt__Dialog, 0)
24 | if ok {
25 | c := s.categoryItem
26 |
27 | id, ok := c.Data().ToInterface().(int)
28 | if ok && id > 0 {
29 | s.db.RenameCategory(id, name)
30 | c.SetText(name)
31 | s.categoryName = name
32 | s.model.SetHorizontalHeaderLabels([]string{T("Diary List") + " - " + s.categoryName})
33 | s.modelFind.SetHorizontalHeaderLabels([]string{T("Result List") + " - " + s.categoryName})
34 | }
35 | }
36 | })
37 |
38 | removeCategory := s.categories.AddAction(T("Remove Category"))
39 | removeCategory.ConnectTriggered(func(b bool) {
40 | s.removeCurCategory()
41 | })
42 |
43 | s.categories.AddSeparator()
44 | }
45 |
46 | func (s *myWindow) loadUserCategorys() {
47 | c := s.addCategoryMenuItem(0, T("Default"))
48 |
49 | categoryDict := s.db.GetCategories()
50 | for id, name := range categoryDict {
51 | s.addCategoryMenuItem(id, name)
52 | }
53 |
54 | c.Triggered(true)
55 | }
56 |
57 | func (s *myWindow) addCategoryMenuItem(id int, name string) *widgets.QAction {
58 | c := s.categories.AddAction(name)
59 | c.SetCheckable(true)
60 | c.SetData(core.NewQVariant1(id))
61 | c.ConnectTriggered(func(b bool) {
62 | data, ok := c.Data().ToInterface().(int)
63 | if ok == false {
64 | return
65 | }
66 |
67 | s.saveCurDiary()
68 | s.curDiary.Item = nil
69 |
70 | s.category = data
71 | s.categoryName = c.Text()
72 | //set last selected item to false
73 | if s.categoryItem != nil && b {
74 | s.categoryItem.SetChecked(false)
75 | }
76 | s.categoryItem = c
77 | s.showCurCategory(data)
78 |
79 | c.SetChecked(true)
80 | })
81 | return c
82 | }
83 |
84 | func (s *myWindow) showCurCategory(c int) {
85 | s.db.setCategory(c)
86 | s.clearModel()
87 | s.clearModelFind()
88 |
89 | s.addYearMonthsFromDb()
90 | }
91 |
92 | func (s *myWindow) removeCurCategory() {
93 | if s.category == 0 {
94 | widgets.QMessageBox_Information(s.window, T("Error"), T("Can not remove default category."), widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
95 | return
96 | }
97 |
98 | ret := widgets.QMessageBox_Question(s.window, T("Remove Category")+" : "+s.categoryName, T("Are you sure? All files in this category will move to default category."), widgets.QMessageBox__Yes|widgets.QMessageBox__No, widgets.QMessageBox__Yes)
99 | if ret == widgets.QMessageBox__No {
100 | return
101 | }
102 | s.db.RemoveCategory(s.category)
103 | s.categories.RemoveAction(s.categoryItem)
104 | s.categoryItem = nil
105 |
106 | for _, v := range s.categories.Children() {
107 | item := widgets.NewQActionFromPointer(v.Pointer())
108 | id, ok := item.Data().ToInterface().(int)
109 | if ok && id == 0 {
110 | item.Triggered(true)
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/cmd-tools/to-html/aes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "crypto/rand"
8 | "crypto/sha256"
9 | "fmt"
10 | "io"
11 | "os"
12 | "path"
13 | )
14 |
15 | var dataDir string
16 |
17 | func getSha40String(pwd string) string {
18 | bp := []byte(pwd)
19 | buf := bytes.NewBuffer(make([]byte, 0, len(bp)*40))
20 | for i := 0; i < 40; i++ {
21 | buf.Write(bp)
22 | }
23 | res := sha256.Sum256(buf.Bytes())
24 | return fmt.Sprintf("%x", res)
25 | }
26 |
27 | func getSha4(pwd string) []byte {
28 | bp := []byte(pwd)
29 | buf := bytes.NewBuffer(make([]byte, 0, len(bp)*4))
30 | for i := 0; i < 4; i++ {
31 | buf.Write(bp)
32 | }
33 | res := sha256.Sum256(buf.Bytes())
34 | return res[:]
35 | }
36 |
37 | func getRealKeyString(pwd string) (string, error) {
38 |
39 | rkey := make([]byte, 32)
40 |
41 | io.ReadFull(rand.Reader, rkey)
42 | p1 := getSha4(pwd)
43 | aesBlock, err := aes.NewCipher(p1)
44 | if err != nil {
45 | return "", err
46 | }
47 | iv := make([]byte, aes.BlockSize)
48 | io.ReadFull(rand.Reader, iv)
49 |
50 | aesEncoder := cipher.NewCTR(aesBlock, iv)
51 | var res = make([]byte, 32)
52 | aesEncoder.XORKeyStream(res, rkey)
53 | buf := bytes.NewBuffer(iv)
54 | buf.Write(res)
55 | return fmt.Sprintf("%x", buf.Bytes()), nil
56 | }
57 |
58 | func newRealKeyString(rkey []byte, pwd string) (string, error) {
59 | p1 := getSha4(pwd)
60 | aesBlock, err := aes.NewCipher(p1)
61 | if err != nil {
62 | return "", err
63 | }
64 | iv := make([]byte, aes.BlockSize)
65 | io.ReadFull(rand.Reader, iv)
66 |
67 | aesEncoder := cipher.NewCTR(aesBlock, iv)
68 | var res = make([]byte, 32)
69 | aesEncoder.XORKeyStream(res, rkey)
70 | buf := bytes.NewBuffer(iv)
71 | buf.Write(res)
72 | return fmt.Sprintf("%x", buf.Bytes()), nil
73 | }
74 |
75 | func decodeRealKey(key, data, iv []byte) ([]byte, error) {
76 | aesBlock, err := aes.NewCipher(key)
77 | if err != nil {
78 | return nil, err
79 | }
80 | aesDecoder := cipher.NewCTR(aesBlock, iv)
81 | var res = make([]byte, 32)
82 | aesDecoder.XORKeyStream(res, data)
83 | return res, nil
84 | }
85 |
86 | func encodeToFile(data []byte, filename string, key []byte) error {
87 | dataPath := path.Join(dataDir, filename)
88 | fp, err := os.Create(dataPath)
89 | if err != nil {
90 | return err
91 | }
92 | defer fp.Close()
93 |
94 | aesBlock, err := aes.NewCipher(key)
95 | if err != nil {
96 | return err
97 | }
98 | iv := make([]byte, aesBlock.BlockSize())
99 | io.ReadFull(rand.Reader, iv)
100 | _, err = fp.Write(iv)
101 | if err != nil {
102 | return err
103 | }
104 | aesEncoder := cipher.NewCTR(aesBlock, iv)
105 | wr := cipher.StreamWriter{S: aesEncoder, W: fp}
106 | _, err = wr.Write(data)
107 | return err
108 | }
109 |
110 | func decodeFromFile(filename string, key []byte) ([]byte, error) {
111 | dataPath := path.Join(dataDir, filename)
112 | fp, err := os.Open(dataPath)
113 | if err != nil {
114 | return nil, err
115 | }
116 | defer fp.Close()
117 |
118 | aesBlock, err := aes.NewCipher(key)
119 | if err != nil {
120 | return nil, err
121 | }
122 | iv := make([]byte, aesBlock.BlockSize())
123 | _, err = io.ReadFull(fp, iv)
124 | if err != nil {
125 | return nil, err
126 | }
127 | aesStream := cipher.NewCTR(aesBlock, iv)
128 | rd := cipher.StreamReader{S: aesStream, R: fp}
129 | res := bytes.NewBufferString("")
130 | _, err = io.Copy(res, rd)
131 | return res.Bytes(), err
132 | }
133 |
134 | func encodeToPathName(data []byte, filename string, key []byte) error {
135 | dataPath := filename
136 | fp, err := os.Create(dataPath)
137 | if err != nil {
138 | return err
139 | }
140 | defer fp.Close()
141 |
142 | aesBlock, err := aes.NewCipher(key)
143 | if err != nil {
144 | return err
145 | }
146 | iv := make([]byte, aesBlock.BlockSize())
147 | io.ReadFull(rand.Reader, iv)
148 | _, err = fp.Write(iv)
149 | if err != nil {
150 | return err
151 | }
152 | aesEncoder := cipher.NewCTR(aesBlock, iv)
153 | wr := cipher.StreamWriter{S: aesEncoder, W: fp}
154 | _, err = wr.Write(data)
155 | return err
156 | }
157 |
158 | func decodeFromPathName(filename string, key []byte) ([]byte, error) {
159 | dataPath := filename
160 | fp, err := os.Open(dataPath)
161 | if err != nil {
162 | return nil, err
163 | }
164 | defer fp.Close()
165 |
166 | aesBlock, err := aes.NewCipher(key)
167 | if err != nil {
168 | return nil, err
169 | }
170 | iv := make([]byte, aesBlock.BlockSize())
171 | _, err = io.ReadFull(fp, iv)
172 | if err != nil {
173 | return nil, err
174 | }
175 | aesStream := cipher.NewCTR(aesBlock, iv)
176 | rd := cipher.StreamReader{S: aesStream, R: fp}
177 | res := bytes.NewBufferString("")
178 | _, err = io.Copy(res, rd)
179 | return res.Bytes(), err
180 | }
181 |
182 | func decodeFromReader(reader io.Reader, key []byte) ([]byte, error) {
183 | aesBlock, err := aes.NewCipher(key)
184 | if err != nil {
185 | return nil, err
186 | }
187 | iv := make([]byte, aesBlock.BlockSize())
188 | _, err = io.ReadFull(reader, iv)
189 | if err != nil {
190 | return nil, err
191 | }
192 | aesStream := cipher.NewCTR(aesBlock, iv)
193 | rd := cipher.StreamReader{S: aesStream, R: reader}
194 | res := bytes.NewBufferString("")
195 | _, err = io.Copy(res, rd)
196 | return res.Bytes(), err
197 | }
198 |
--------------------------------------------------------------------------------
/cmd-tools/to-html/db.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/aes"
5 | "database/sql"
6 | "encoding/hex"
7 | "errors"
8 | "log"
9 | "os"
10 | "path"
11 | "time"
12 |
13 | _ "github.com/mattn/go-sqlite3"
14 | )
15 |
16 | type myDb struct {
17 | db *sql.DB
18 | category int
19 | }
20 |
21 | func openMyDb(name string) (*myDb, error) {
22 | db, err := sql.Open("sqlite3", name)
23 | if err != nil {
24 | return nil, err
25 | }
26 | res := &myDb{db: db}
27 | res.CheckAndUpgrade()
28 | return res, nil
29 | }
30 |
31 | func (s *myDb) setCategory(c int) {
32 | s.category = c
33 | }
34 |
35 | func (s *myDb) CheckAndUpgrade() error {
36 | diaryStruct, err := s.db.Query("PRAGMA table_info(diaries);")
37 | if err != nil {
38 | return err
39 | }
40 | var rows = 0
41 | for diaryStruct.Next() {
42 | rows++
43 | }
44 | diaryStruct.Close()
45 | if rows == 5 {
46 | s.db.Exec("alter table diaries add column category int default 0;")
47 | s.db.Exec("create table categories(id int,name text);")
48 | }
49 | return nil
50 | }
51 |
52 | func (s *myDb) Close() {
53 | s.db.Close()
54 | }
55 |
56 | func (s *myDb) GetRealKey(pwd string) ([]byte, error) {
57 | sha40 := getSha40String(pwd)
58 | row := s.db.QueryRow("select realkey from user where sha40=?;", sha40)
59 | var realKey string
60 | err := row.Scan(&realKey)
61 | if err != nil {
62 | return nil, err
63 | }
64 | key, err := hex.DecodeString(realKey)
65 | if err != nil {
66 | return nil, err
67 | }
68 | if len(key) != 32+aes.BlockSize {
69 | return nil, errors.New("Data invalid.")
70 | }
71 | iv := key[:aes.BlockSize]
72 | data := key[aes.BlockSize:]
73 | ukey := getSha4(pwd)
74 | return decodeRealKey(ukey, data, iv)
75 | }
76 |
77 | func (s *myDb) UpdateRealKeyAndSha40(pwdOld, pwdNew string) error {
78 | sha40 := getSha40String(pwdOld)
79 | row := s.db.QueryRow("select realkey from user where sha40=?;", sha40)
80 | var realKey string
81 | err := row.Scan(&realKey)
82 | if err != nil {
83 | return err
84 | }
85 | key, err := hex.DecodeString(realKey)
86 | if err != nil {
87 | return err
88 | }
89 | if len(key) != 32+aes.BlockSize {
90 | return errors.New("Data invalid.")
91 | }
92 | iv := key[:aes.BlockSize]
93 | data := key[aes.BlockSize:]
94 | ukey := getSha4(pwdOld)
95 | rkey, err := decodeRealKey(ukey, data, iv)
96 | if err != nil {
97 | return err
98 | }
99 | rkeyStr, err := newRealKeyString(rkey, pwdNew)
100 | if err != nil {
101 | return err
102 | }
103 | _, err = s.db.Exec("update user set realkey=?,sha40=? where id=1;", rkeyStr, getSha40String(pwdNew))
104 | return err
105 | }
106 |
107 | func (s *myDb) AddDiary(id int, cdate, title, filename string) error {
108 | _, err := s.db.Exec("insert into diaries(id,cdate,title,filename,mtime,category) values(?,?,?,?,?,?);",
109 | id, cdate, title, filename, time.Now().Format("2006-01-02 15:04:05"), s.category)
110 | if err != nil {
111 | log.Println("UpdateDiaryTitle")
112 | return s.UpdateDiaryTitle(id, title)
113 | }
114 | return nil
115 | }
116 |
117 | func (s *myDb) AddDiary2(id int, cdate, title, filename string, category int) error {
118 | _, err := s.db.Exec("insert into diaries(id,cdate,title,filename,mtime,category) values(?,?,?,?,?,?);",
119 | id, cdate, title, filename, time.Now().Format("2006-01-02 15:04:05"), category)
120 | if err != nil {
121 | log.Println("UpdateDiaryTitle")
122 | return s.UpdateDiaryTitle(id, title)
123 | }
124 | return nil
125 | }
126 |
127 | func (s *myDb) BeginTx() (*sql.Tx, error) {
128 | return s.db.Begin()
129 | }
130 |
131 | func (s *myDb) AddDiaryTx(tx *sql.Tx, id int, cdate, title, filename string, category int) error {
132 | _, err := tx.Exec("insert into diaries(id,cdate,title,filename,mtime,category) values(?,?,?,?,?,?);",
133 | id, cdate, title, filename, time.Now().Format("2006-01-02 15:04:05"), category)
134 | return err
135 | }
136 |
137 | func (s *myDb) UpdateDiaryTitle(id int, title string) error {
138 | res, err := s.db.Exec("update diaries set title=?,mtime=? where id=?", title, time.Now().Format("2006-01-02 15:04:05"), id)
139 | if err != nil {
140 | return err
141 | }
142 | if n, _ := res.RowsAffected(); n == 0 {
143 | return errors.New("UpdateDiaryTitle fail.")
144 | }
145 | return nil
146 | }
147 |
148 | func (s *myDb) UpdateDiaryCategory(id, c int) error {
149 | res, err := s.db.Exec("update diaries set category=?,mtime=? where id=?", c, time.Now().Format("2006-01-02 15:04:05"), id)
150 | if err != nil {
151 | return err
152 | }
153 | if n, _ := res.RowsAffected(); n == 0 {
154 | return errors.New("UpdateDiaryCategory fail.")
155 | }
156 | return nil
157 | }
158 |
159 | func (s *myDb) TouchDiary(id int) error {
160 | res, err := s.db.Exec("update diaries set mtime=? where id=?;", time.Now().Format("2006-01-02 15:04:05"), id)
161 | if err != nil {
162 | return err
163 | }
164 | if n, _ := res.RowsAffected(); n == 0 {
165 | return errors.New("AddDiary fail.")
166 | }
167 | return nil
168 | }
169 |
170 | func (s *myDb) NextId() int {
171 | res := s.db.QueryRow("select id from diaries order by id desc limit 1;")
172 |
173 | var id int
174 | err := res.Scan(&id)
175 | if err != nil {
176 | log.Println(err)
177 | return 1
178 | }
179 | return id + 1
180 | }
181 |
182 | func (s *myDb) GetYearMonths() ([]string, error) {
183 | rows, err := s.db.Query("select distinct substr(cdate,0,8) from diaries where category=? order by substr(cdate,0,8) asc;", s.category)
184 | if err != nil {
185 | return nil, err
186 | }
187 | defer rows.Close()
188 | res := []string{}
189 | for rows.Next() {
190 | var v string
191 | rows.Scan(&v)
192 | res = append(res, v)
193 | }
194 | return res, nil
195 | }
196 |
197 | type DiaryItem struct {
198 | Id int
199 | Day string
200 | Title string
201 | MTime string
202 | }
203 |
204 | func (s *myDb) GetListFromYearMonth(ym string) ([]DiaryItem, error) {
205 | rows, err := s.db.Query("select id,substr(cdate,9,11),title,mtime from diaries where substr(cdate,0,8)=? and category=? order by substr(cdate,9,11) desc;", ym, s.category)
206 | if err != nil {
207 | return nil, err
208 | }
209 | defer rows.Close()
210 | res := []DiaryItem{}
211 | for rows.Next() {
212 | var v DiaryItem
213 | rows.Scan(&v.Id, &v.Day, &v.Title, &v.MTime)
214 | res = append(res, v)
215 | }
216 | return res, nil
217 | }
218 |
219 | func (s *myDb) RemoveDiary(id string) error {
220 | row := s.db.QueryRow("select filename from diaries where id=?;", id)
221 | var filename string
222 | err := row.Scan(&filename)
223 | if err != nil {
224 | return err
225 | }
226 | _, err = s.db.Exec("delete from diaries where id=?;", id)
227 | if err != nil {
228 | return err
229 | }
230 | return os.Remove(path.Join(dataDir, filename))
231 | }
232 |
233 | func (s *myDb) SearchTitle(kw string) ([]DiaryItem, error) {
234 | rows, err := s.db.Query("select id,cdate,title,mtime from diaries where instr(title,?)>0 and category=? order by cdate desc;", kw, s.category)
235 | if err != nil {
236 | return nil, err
237 | }
238 | defer rows.Close()
239 | res := []DiaryItem{}
240 | for rows.Next() {
241 | var v DiaryItem
242 | rows.Scan(&v.Id, &v.Day, &v.Title, &v.MTime)
243 | res = append(res, v)
244 | }
245 | return res, nil
246 | }
247 |
248 | func (s *myDb) ListAllDiary() ([]DiaryItem, error) {
249 | rows, err := s.db.Query("select id,cdate,title,mtime from diaries order by cdate desc;")
250 | if err != nil {
251 | return nil, err
252 | }
253 | defer rows.Close()
254 | res := []DiaryItem{}
255 | for rows.Next() {
256 | var v DiaryItem
257 | rows.Scan(&v.Id, &v.Day, &v.Title, &v.MTime)
258 | res = append(res, v)
259 | }
260 | return res, nil
261 | }
262 |
263 | func (s *myDb) AddCategory(name string) int {
264 | res := s.db.QueryRow("select id from categories order by id desc limit 1;")
265 |
266 | var id int
267 | err := res.Scan(&id)
268 | if err != nil {
269 | id = 1
270 | } else {
271 | id++
272 | }
273 |
274 | s.db.Exec("insert into categories(id,name) values (?,?);", id, name)
275 | return id
276 | }
277 |
278 | func (s *myDb) RenameCategory(id int, name string) error {
279 | _, err := s.db.Exec("update categories set name=? where id=?;", name, id)
280 | return err
281 | }
282 |
283 | func (s *myDb) RemoveCategory(c int) {
284 | s.db.Exec("delete from categories where id=?;", c)
285 | s.db.Exec("update diaries set category=0 where category=?;", c)
286 | }
287 |
288 | func (s *myDb) GetCategories() map[int]string {
289 | res := make(map[int]string)
290 |
291 | rows, err := s.db.Query("select id,name from categories order by id asc;")
292 | if err != nil {
293 | return nil
294 | }
295 | defer rows.Close()
296 |
297 | for rows.Next() {
298 | var id int
299 | var name string
300 | if rows.Scan(&id, &name) == nil {
301 | res[id] = name
302 | }
303 | }
304 |
305 | return res
306 | }
307 |
--------------------------------------------------------------------------------
/cmd-tools/to-html/index.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 所有记录
6 |
7 |
8 | 所有记录
9 | {{range .}} {{.Id}} {{.Title}} {{.MTime}}
{{end}}
10 |
11 |
--------------------------------------------------------------------------------
/cmd-tools/to-html/tohtml.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Run program in directory $HOME/.sdiary/$USERNAME/
4 | // It will generate index.html
5 |
6 | import (
7 | "bytes"
8 | "encoding/gob"
9 | "fmt"
10 | "html/template"
11 | "io/ioutil"
12 | "os"
13 | "path"
14 | "path/filepath"
15 |
16 | "github.com/PuerkitoBio/goquery"
17 |
18 | _ "embed"
19 | )
20 |
21 | //go:embed index.tpl
22 | var tplIndex string
23 |
24 | func main() {
25 | fmt.Print("Password:")
26 | var pwd string
27 | n, err := fmt.Scan(&pwd)
28 | if err != nil || n == 0 {
29 | panic(err.Error())
30 | }
31 | fmt.Println(pwd)
32 | db, err := openMyDb("diary.db")
33 | if err != nil {
34 | panic(err.Error())
35 | }
36 | defer db.Close()
37 | key, err := db.GetRealKey(pwd)
38 | if err != nil || n == 0 {
39 | panic(err.Error())
40 | }
41 | fmt.Printf("%x\n", key)
42 |
43 | diaries, err := db.ListAllDiary()
44 | if err != nil {
45 | panic(err)
46 | }
47 | for _, diary := range diaries {
48 | fmt.Printf("%v\n%v\n%v %v\n", diary.Id, diary.Title, diary.Day, diary.MTime)
49 | //转换为html
50 | convertFile(fmt.Sprintf("%v", diary.Id), key)
51 | }
52 |
53 | t := template.New("")
54 | t.Parse(tplIndex)
55 | fp, err := os.Create("index.html")
56 | if err != nil {
57 | panic(err.Error())
58 | }
59 | defer fp.Close()
60 | err = t.Execute(fp, diaries)
61 | if err != nil {
62 | panic(err.Error())
63 | }
64 | }
65 |
66 | type attachmentFile struct {
67 | Name string
68 | Data []byte
69 | }
70 |
71 | type qtextFormat struct {
72 | Html string
73 | Images map[string][]byte
74 | Attachments []attachmentFile
75 | }
76 |
77 | func convertFile(name string, key []byte) error {
78 | data, err := decodeFromFile(name+".dat", key)
79 | if err != nil {
80 | return err
81 | }
82 |
83 | buf := bytes.NewReader(data)
84 |
85 | decoder := gob.NewDecoder(buf)
86 |
87 | var document qtextFormat
88 | document.Images = make(map[string][]byte)
89 | document.Attachments = []attachmentFile{}
90 |
91 | err = decoder.Decode(&document)
92 | if err != nil {
93 | return err
94 | }
95 |
96 | buf1 := bytes.NewBufferString(document.Html)
97 |
98 | root, err := goquery.NewDocumentFromReader(buf1)
99 | if err != nil {
100 | return err
101 | }
102 |
103 | os.MkdirAll(name, os.ModePerm)
104 | //htmlImages := ""
105 | for k, img := range document.Images {
106 | fn := path.Base(k)
107 | ioutil.WriteFile(filepath.Join(name, fn), img, 0644)
108 | //htmlImages = htmlImages + fmt.Sprintf("\n
", fn)
109 |
110 | root.Find("img").Each(func(i int, s *goquery.Selection) {
111 | if v, ok := s.Attr("src"); ok {
112 | if v == k {
113 | s.SetAttr("src", fn)
114 | }
115 | }
116 |
117 | })
118 |
119 | //println(htmlImages)
120 | }
121 |
122 | //htmlAttach := ""
123 | for _, item := range document.Attachments {
124 | ioutil.WriteFile(filepath.Join(name, item.Name), item.Data, 0644)
125 | //htmlAttach = htmlAttach + fmt.Sprintf("\n%v
", item.Name, item.Name)
126 | //println(htmlAttach)
127 |
128 | root.Find("body").Each(func(i int, s *goquery.Selection) {
129 | s.AppendHtml(fmt.Sprintf("\n%v
", item.Name, item.Name))
130 |
131 | })
132 |
133 | }
134 |
135 | //pos := strings.Index(document.Html, "")
136 |
137 | //html := document.Html[:pos] + htmlImages + htmlAttach + document.Html[pos:]
138 | html, err := root.Html()
139 |
140 | if err != nil {
141 | return err
142 | }
143 |
144 | ioutil.WriteFile(filepath.Join(name, name+".html"), []byte(html), 0644)
145 | return err
146 | }
147 |
--------------------------------------------------------------------------------
/db.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/aes"
5 | "database/sql"
6 | "encoding/hex"
7 | "errors"
8 | "log"
9 | "os"
10 | "path"
11 | "time"
12 |
13 | _ "github.com/mattn/go-sqlite3"
14 | )
15 |
16 | type myDb struct {
17 | db *sql.DB
18 | category int
19 | }
20 |
21 | func createUserDb(name string, pwd string) error {
22 | dataDir = path.Join(datDir, name)
23 | os.MkdirAll(dataDir, os.ModePerm)
24 | userDb := path.Join(dataDir, "diary.db")
25 | if _, err := os.Stat(userDb); err == nil {
26 | //exists
27 | return errors.New("Already exists.")
28 | }
29 | db, err := sql.Open("sqlite3", userDb)
30 | if err != nil {
31 | return err
32 | }
33 | defer db.Close()
34 | sqls := []string{"create table user (id integer unique,cdata text,sha40 TEXT not null,realkey text not null,mtime text);",
35 | "create table diaries (id integer unique,cdate text,title text,filename text,mtime text,category int default 0);",
36 | "create index idx1 on diaries(cdate);",
37 | "create table categories(id int,name text);"}
38 | for i := 0; i < len(sqls); i++ {
39 | _, err = db.Exec(sqls[i])
40 | if err != nil {
41 | return err
42 | }
43 | }
44 |
45 | id := 1
46 | now := time.Now()
47 | cdate := now.Format("2006-01-02")
48 | sha40 := getSha40String(pwd)
49 | realKey, err := getRealKeyString(pwd)
50 | if err != nil {
51 | return err
52 | }
53 | mtime := now.Format("2006-01-02 15:04:05")
54 |
55 | _, err = db.Exec("insert into user(id,cdata,sha40,realkey,mtime) values(?,?,?,?,?);", id, cdate, sha40, realKey, mtime)
56 |
57 | return err
58 | }
59 |
60 | func getMyDb(name string) (*myDb, error) {
61 | dataDir = path.Join(datDir, name)
62 | os.MkdirAll(dataDir, os.ModePerm)
63 |
64 | userDb := path.Join(dataDir, "diary.db")
65 | if _, err := os.Stat(userDb); err != nil {
66 | return nil, err
67 | }
68 |
69 | db, err := sql.Open("sqlite3", userDb)
70 | if err != nil {
71 | return nil, err
72 | }
73 | res := &myDb{db: db}
74 | res.CheckAndUpgrade()
75 | return res, nil
76 | }
77 |
78 | func (s *myDb) setCategory(c int) {
79 | s.category = c
80 | }
81 |
82 | func (s *myDb) CheckAndUpgrade() error {
83 | diaryStruct, err := s.db.Query("PRAGMA table_info(diaries);")
84 | if err != nil {
85 | return err
86 | }
87 | var rows = 0
88 | for diaryStruct.Next() {
89 | rows++
90 | }
91 | diaryStruct.Close()
92 | if rows == 5 {
93 | s.db.Exec("alter table diaries add column category int default 0;")
94 | s.db.Exec("create table categories(id int,name text);")
95 | }
96 | return nil
97 | }
98 |
99 | func (s *myDb) Close() {
100 | s.db.Close()
101 | }
102 |
103 | func (s *myDb) GetRealKey(pwd string) ([]byte, error) {
104 | sha40 := getSha40String(pwd)
105 | row := s.db.QueryRow("select realkey from user where sha40=?;", sha40)
106 | var realKey string
107 | err := row.Scan(&realKey)
108 | if err != nil {
109 | return nil, err
110 | }
111 | key, err := hex.DecodeString(realKey)
112 | if err != nil {
113 | return nil, err
114 | }
115 | if len(key) != 32+aes.BlockSize {
116 | return nil, errors.New("Data invalid.")
117 | }
118 | iv := key[:aes.BlockSize]
119 | data := key[aes.BlockSize:]
120 | ukey := getSha4(pwd)
121 | return decodeRealKey(ukey, data, iv)
122 | }
123 |
124 | func (s *myDb) UpdateRealKeyAndSha40(pwdOld, pwdNew string) error {
125 | sha40 := getSha40String(pwdOld)
126 | row := s.db.QueryRow("select realkey from user where sha40=?;", sha40)
127 | var realKey string
128 | err := row.Scan(&realKey)
129 | if err != nil {
130 | return err
131 | }
132 | key, err := hex.DecodeString(realKey)
133 | if err != nil {
134 | return err
135 | }
136 | if len(key) != 32+aes.BlockSize {
137 | return errors.New("Data invalid.")
138 | }
139 | iv := key[:aes.BlockSize]
140 | data := key[aes.BlockSize:]
141 | ukey := getSha4(pwdOld)
142 | rkey, err := decodeRealKey(ukey, data, iv)
143 | if err != nil {
144 | return err
145 | }
146 | rkeyStr, err := newRealKeyString(rkey, pwdNew)
147 | if err != nil {
148 | return err
149 | }
150 | _, err = s.db.Exec("update user set realkey=?,sha40=? where id=1;", rkeyStr, getSha40String(pwdNew))
151 | return err
152 | }
153 |
154 | func (s *myDb) AddDiary(id int, cdate, title, filename string) error {
155 | _, err := s.db.Exec("insert into diaries(id,cdate,title,filename,mtime,category) values(?,?,?,?,?,?);",
156 | id, cdate, title, filename, time.Now().Format("2006-01-02 15:04:05"), s.category)
157 | if err != nil {
158 | log.Println("UpdateDiaryTitle")
159 | return s.UpdateDiaryTitle(id, title)
160 | }
161 | return nil
162 | }
163 |
164 | func (s *myDb) AddDiary2(id int, cdate, title, filename string, category int) error {
165 | _, err := s.db.Exec("insert into diaries(id,cdate,title,filename,mtime,category) values(?,?,?,?,?,?);",
166 | id, cdate, title, filename, time.Now().Format("2006-01-02 15:04:05"), category)
167 | if err != nil {
168 | log.Println("UpdateDiaryTitle")
169 | return s.UpdateDiaryTitle(id, title)
170 | }
171 | return nil
172 | }
173 |
174 | func (s *myDb) BeginTx() (*sql.Tx, error) {
175 | return s.db.Begin()
176 | }
177 |
178 | func (s *myDb) AddDiaryTx(tx *sql.Tx, id int, cdate, title, filename string, category int) error {
179 | _, err := tx.Exec("insert into diaries(id,cdate,title,filename,mtime,category) values(?,?,?,?,?,?);",
180 | id, cdate, title, filename, time.Now().Format("2006-01-02 15:04:05"), category)
181 | return err
182 | }
183 |
184 | func (s *myDb) UpdateDiaryTitle(id int, title string) error {
185 | res, err := s.db.Exec("update diaries set title=?,mtime=? where id=?", title, time.Now().Format("2006-01-02 15:04:05"), id)
186 | if err != nil {
187 | return err
188 | }
189 | if n, _ := res.RowsAffected(); n == 0 {
190 | return errors.New("UpdateDiaryTitle fail.")
191 | }
192 | return nil
193 | }
194 |
195 | func (s *myDb) UpdateDiaryCategory(id, c int) error {
196 | res, err := s.db.Exec("update diaries set category=?,mtime=? where id=?", c, time.Now().Format("2006-01-02 15:04:05"), id)
197 | if err != nil {
198 | return err
199 | }
200 | if n, _ := res.RowsAffected(); n == 0 {
201 | return errors.New("UpdateDiaryCategory fail.")
202 | }
203 | return nil
204 | }
205 |
206 | func (s *myDb) TouchDiary(id int) error {
207 | res, err := s.db.Exec("update diaries set mtime=? where id=?;", time.Now().Format("2006-01-02 15:04:05"), id)
208 | if err != nil {
209 | return err
210 | }
211 | if n, _ := res.RowsAffected(); n == 0 {
212 | return errors.New("AddDiary fail.")
213 | }
214 | return nil
215 | }
216 |
217 | func (s *myDb) NextId() int {
218 | res := s.db.QueryRow("select id from diaries order by id desc limit 1;")
219 |
220 | var id int
221 | err := res.Scan(&id)
222 | if err != nil {
223 | log.Println(err)
224 | return 1
225 | }
226 | return id + 1
227 | }
228 |
229 | func (s *myDb) GetYearMonths() ([]string, error) {
230 | rows, err := s.db.Query("select distinct substr(cdate,0,8) from diaries where category=? order by substr(cdate,0,8) asc;", s.category)
231 | if err != nil {
232 | return nil, err
233 | }
234 | defer rows.Close()
235 | res := []string{}
236 | for rows.Next() {
237 | var v string
238 | rows.Scan(&v)
239 | res = append(res, v)
240 | }
241 | return res, nil
242 | }
243 |
244 | type diaryItem struct {
245 | Id int
246 | Day string
247 | Title string
248 | MTime string
249 | }
250 |
251 | func (s *myDb) GetListFromYearMonth(ym string) ([]diaryItem, error) {
252 | rows, err := s.db.Query("select id,substr(cdate,9,11),title,mtime from diaries where substr(cdate,0,8)=? and category=? order by substr(cdate,9,11) desc;", ym, s.category)
253 | if err != nil {
254 | return nil, err
255 | }
256 | defer rows.Close()
257 | res := []diaryItem{}
258 | for rows.Next() {
259 | var v diaryItem
260 | rows.Scan(&v.Id, &v.Day, &v.Title, &v.MTime)
261 | res = append(res, v)
262 | }
263 | return res, nil
264 | }
265 |
266 | func (s *myDb) RemoveDiary(id string) error {
267 | row := s.db.QueryRow("select filename from diaries where id=?;", id)
268 | var filename string
269 | err := row.Scan(&filename)
270 | if err != nil {
271 | return err
272 | }
273 | _, err = s.db.Exec("delete from diaries where id=?;", id)
274 | if err != nil {
275 | return err
276 | }
277 | return os.Remove(path.Join(dataDir, filename))
278 | }
279 |
280 | func (s *myDb) SearchTitle(kw string) ([]diaryItem, error) {
281 | rows, err := s.db.Query("select id,cdate,title,mtime from diaries where instr(title,?)>0 and category=? order by cdate desc;", kw, s.category)
282 | if err != nil {
283 | return nil, err
284 | }
285 | defer rows.Close()
286 | res := []diaryItem{}
287 | for rows.Next() {
288 | var v diaryItem
289 | rows.Scan(&v.Id, &v.Day, &v.Title, &v.MTime)
290 | res = append(res, v)
291 | }
292 | return res, nil
293 | }
294 |
295 | func (s *myDb) AddCategory(name string) int {
296 | res := s.db.QueryRow("select id from categories order by id desc limit 1;")
297 |
298 | var id int
299 | err := res.Scan(&id)
300 | if err != nil {
301 | id = 1
302 | } else {
303 | id++
304 | }
305 |
306 | s.db.Exec("insert into categories(id,name) values (?,?);", id, name)
307 | return id
308 | }
309 |
310 | func (s *myDb) RenameCategory(id int, name string) error {
311 | _, err := s.db.Exec("update categories set name=? where id=?;", name, id)
312 | return err
313 | }
314 |
315 | func (s *myDb) RemoveCategory(c int) {
316 | s.db.Exec("delete from categories where id=?;", c)
317 | s.db.Exec("update diaries set category=0 where category=?;", c)
318 | }
319 |
320 | func (s *myDb) GetCategories() map[int]string {
321 | res := make(map[int]string)
322 |
323 | rows, err := s.db.Query("select id,name from categories order by id asc;")
324 | if err != nil {
325 | return nil
326 | }
327 | defer rows.Close()
328 |
329 | for rows.Next() {
330 | var id int
331 | var name string
332 | if rows.Scan(&id, &name) == nil {
333 | res[id] = name
334 | }
335 | }
336 |
337 | return res
338 | }
339 |
--------------------------------------------------------------------------------
/dependences.txt:
--------------------------------------------------------------------------------
1 | libbsd0, libc6, libcap2, libdbus-1-3, libgcc1, libgcrypt20, libglib2.0-0, libgpg-error0, liblzma5, libpcre3, libpulse0, libselinux1, libsystemd0, libuuid1, libwrap0, zlib1g
--------------------------------------------------------------------------------
/editor.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "io/ioutil"
7 | "path/filepath"
8 | "regexp"
9 | "strconv"
10 | "strings"
11 | "sync"
12 |
13 | "github.com/therecipe/qt/core"
14 | "github.com/therecipe/qt/gui"
15 | "github.com/therecipe/qt/widgets"
16 | )
17 |
18 | func (s *myWindow) setHeader(level int) {
19 | var cfmt = gui.NewQTextCharFormat()
20 | cfmt.SetFont2(s.app.Font())
21 | switch level {
22 | case 1:
23 | cfmt.SetFontPointSize(18)
24 | case 2:
25 | cfmt.SetFontPointSize(16)
26 | case 3:
27 | cfmt.SetFontPointSize(14)
28 | default:
29 | cfmt.SetFontPointSize(12)
30 | }
31 |
32 | cfmt.SetForeground(gui.NewQBrush3(gui.NewQColor2(core.Qt__blue), core.Qt__SolidPattern))
33 | s.mergeFormatOnLineOrSelection(cfmt)
34 | }
35 |
36 | func (s *myWindow) setStandard() {
37 | var cfmt = gui.NewQTextCharFormat()
38 | cfmt.SetFont2(s.app.Font())
39 | cfmt.SetFontPointSize(14)
40 | cfmt.SetForeground(gui.NewQBrush3(gui.NewQColor2(core.Qt__black), core.Qt__SolidPattern))
41 |
42 | s.mergeFormatOnLineOrSelection(cfmt)
43 | }
44 |
45 | func (s *myWindow) textStyle(styleIndex int) {
46 | var cursor = s.editor.TextCursor()
47 |
48 | if styleIndex != 0 {
49 |
50 | var style = gui.QTextListFormat__ListDisc
51 |
52 | switch styleIndex {
53 | case 1:
54 | {
55 | style = gui.QTextListFormat__ListDisc
56 | }
57 |
58 | case 2:
59 | {
60 | style = gui.QTextListFormat__ListCircle
61 | }
62 |
63 | case 3:
64 | {
65 | style = gui.QTextListFormat__ListSquare
66 | }
67 |
68 | case 4:
69 | {
70 | style = gui.QTextListFormat__ListDecimal
71 | }
72 |
73 | case 5:
74 | {
75 | style = gui.QTextListFormat__ListLowerAlpha
76 | }
77 |
78 | case 6:
79 | {
80 | style = gui.QTextListFormat__ListUpperAlpha
81 | }
82 |
83 | case 7:
84 | {
85 | style = gui.QTextListFormat__ListLowerRoman
86 | }
87 |
88 | case 8:
89 | {
90 | style = gui.QTextListFormat__ListUpperRoman
91 | }
92 | }
93 |
94 | cursor.BeginEditBlock()
95 |
96 | var (
97 | blockFmt = cursor.BlockFormat()
98 | listFmt = gui.NewQTextListFormat()
99 | )
100 |
101 | if cursor.CurrentList().Pointer() != nil {
102 | listFmt = gui.NewQTextListFormatFromPointer(cursor.CurrentList().Format().Pointer())
103 | } else {
104 | listFmt.SetIndent(blockFmt.Indent() + 1)
105 | blockFmt.SetIndent(0)
106 | cursor.SetBlockFormat(blockFmt)
107 | }
108 |
109 | listFmt.SetStyle(style)
110 | cursor.CreateList(listFmt)
111 |
112 | cursor.EndEditBlock()
113 |
114 | } else {
115 | var bfmt = gui.NewQTextBlockFormat()
116 | bfmt.SetObjectIndex(-1)
117 | cursor.SetBlockFormat(bfmt)
118 | }
119 | }
120 |
121 | // addIndent 增加缩进量,n=1(增加1) or n=-1(减少1)
122 | func (s *myWindow) addIndent(n int) {
123 | cursor := s.editor.TextCursor()
124 | cursor.BeginEditBlock()
125 |
126 | var (
127 | blockFmt = cursor.BlockFormat()
128 | listFmt = gui.NewQTextListFormat()
129 | )
130 |
131 | if cursor.CurrentList().Pointer() != nil {
132 | listFmt = gui.NewQTextListFormatFromPointer(cursor.CurrentList().Format().Pointer())
133 | if listFmt.Indent()+n >= 0 {
134 | listFmt.SetIndent(listFmt.Indent() + n)
135 | cursor.CreateList(listFmt)
136 | }
137 |
138 | } else {
139 | if blockFmt.Indent()+n >= 0 {
140 | blockFmt.SetIndent(blockFmt.Indent() + n)
141 | cursor.SetBlockFormat(blockFmt)
142 | }
143 |
144 | }
145 |
146 | cursor.EndEditBlock()
147 | }
148 |
149 | func (s *myWindow) textColor() {
150 | var col = widgets.QColorDialog_GetColor(s.editor.TextColor(), s.editor, "", 0)
151 | if !col.IsValid() {
152 | return
153 | }
154 | var cfmt = gui.NewQTextCharFormat()
155 | cfmt.SetForeground(gui.NewQBrush3(col, core.Qt__SolidPattern))
156 | s.mergeFormatOnLineOrSelection(cfmt)
157 | }
158 |
159 | func (s *myWindow) textBgColor() {
160 | var col = widgets.QColorDialog_GetColor(s.editor.TextColor(), s.editor, "", 0)
161 | if !col.IsValid() {
162 | return
163 | }
164 | var cfmt = gui.NewQTextCharFormat()
165 | cfmt.SetBackground(gui.NewQBrush3(col, core.Qt__SolidPattern))
166 | s.mergeFormatOnLineOrSelection(cfmt)
167 | }
168 |
169 | func (s *myWindow) textBold() {
170 | var afmt = gui.NewQTextCharFormat()
171 | var fw = gui.QFont__Normal
172 | if s.actionTextBold.IsChecked() {
173 | fw = gui.QFont__Bold
174 | }
175 | afmt.SetFontWeight(int(fw))
176 | s.mergeFormatOnLineOrSelection(afmt)
177 | }
178 |
179 | func (s *myWindow) textUnderline() {
180 | var afmt = gui.NewQTextCharFormat()
181 | afmt.SetFontUnderline(s.actionTextUnderline.IsChecked())
182 | s.mergeFormatOnLineOrSelection(afmt)
183 | }
184 |
185 | func (s *myWindow) textStrikeOut() {
186 | var afmt = gui.NewQTextCharFormat()
187 | afmt.SetFontStrikeOut(s.actionStrikeOut.IsChecked())
188 | s.mergeFormatOnLineOrSelection(afmt)
189 | }
190 |
191 | func (s *myWindow) textItalic() {
192 | var afmt = gui.NewQTextCharFormat()
193 | afmt.SetFontItalic(s.actionTextItalic.IsChecked())
194 | s.mergeFormatOnLineOrSelection(afmt)
195 | }
196 |
197 | func (s *myWindow) insertImage() {
198 | filename := widgets.QFileDialog_GetOpenFileName(s.window, "select a file", ".", "Image (*.png *.jpg)", "Image (*.png *.jpg)", widgets.QFileDialog__ReadOnly)
199 | data, err := ioutil.ReadFile(filename)
200 | if err != nil {
201 | return
202 | }
203 | img := gui.NewQImage()
204 | ok := img.LoadFromData(data, len(data), "")
205 | if !ok {
206 | return
207 | }
208 |
209 | uri := core.NewQUrl3("rc://"+filename, core.QUrl__TolerantMode)
210 |
211 | img = s.scaleImage(img)
212 |
213 | s.editor.Document().AddResource(int(gui.QTextDocument__ImageResource), uri, img.ToVariant())
214 | url := uri.Url(core.QUrl__None)
215 | cursor := s.editor.TextCursor()
216 | cursor.InsertImage4(img, url)
217 |
218 | ba := core.NewQByteArray()
219 |
220 | iod := core.NewQBuffer2(ba, nil)
221 |
222 | iod.Open(core.QIODevice__WriteOnly)
223 |
224 | ok = img.Save2(iod, filepath.Ext(filename)[1:], -1)
225 | //fmt.Println(filepath.Ext(filename))
226 | if ok {
227 | s.document.Images[url] = []byte(ba.Data())
228 | }
229 |
230 | //fmt.Println("save image:", ok)
231 | }
232 |
233 | func (s *myWindow) scaleImage(src *gui.QImage) (res *gui.QImage) {
234 |
235 | dlg := widgets.NewQDialog(s.window, core.Qt__Dialog)
236 | dlg.SetWindowTitle(T("Scale Image Size"))
237 |
238 | grid := newGridLayout(dlg)
239 |
240 | width := widgets.NewQLabel2(fmt.Sprintf("%s : %d =>", T("Width"), src.Width()), dlg, core.Qt__Widget)
241 | grid.AddWidget2(width, 0, 0, 0)
242 | scaledW := src.Width()
243 | scaledH := src.Height()
244 | delta := 30
245 | if scaledW > s.editor.Width()-delta {
246 | scaledW = s.editor.Geometry().Width() - delta
247 | scaledH = int(float64(src.Height()) * float64(scaledW) / float64(src.Width()))
248 | }
249 | wValidor := gui.NewQIntValidator(dlg)
250 | wValidor.SetRange(10, scaledW)
251 | hValidor := gui.NewQIntValidator(dlg)
252 | hValidor.SetRange(10, scaledH)
253 |
254 | widthInput := widgets.NewQLineEdit(dlg)
255 | widthInput.SetText(strconv.Itoa(scaledW))
256 | widthInput.SetValidator(wValidor)
257 | grid.AddWidget2(widthInput, 0, 1, 0)
258 |
259 | height := widgets.NewQLabel2(fmt.Sprintf("%s : %d =>", T("Height"), src.Height()), dlg, core.Qt__Widget)
260 |
261 | grid.AddWidget2(height, 1, 0, 0)
262 |
263 | heightInput := widgets.NewQLineEdit(dlg)
264 | heightInput.SetText(strconv.Itoa(scaledH))
265 | heightInput.SetValidator(hValidor)
266 | grid.AddWidget2(heightInput, 1, 1, 0)
267 |
268 | btb := newGridLayout2()
269 |
270 | okBtn := widgets.NewQPushButton2(T("OK"), dlg)
271 | btb.AddWidget2(okBtn, 0, 0, 0)
272 |
273 | cancelBtn := widgets.NewQPushButton2(T("Cancel"), dlg)
274 | btb.AddWidget2(cancelBtn, 0, 1, 0)
275 |
276 | grid.AddLayout2(btb, 2, 0, 1, 2, 0)
277 |
278 | dlg.SetLayout(grid)
279 |
280 | widthInput.ConnectKeyReleaseEvent(func(e *gui.QKeyEvent) {
281 | w, err := strconv.Atoi(widthInput.Text())
282 | if err != nil {
283 | return
284 | }
285 | w0 := float64(src.Width())
286 | h0 := float64(src.Height())
287 | h := float64(w) * h0 / w0
288 | heightInput.SetText(strconv.Itoa(int(h)))
289 | })
290 | heightInput.ConnectKeyReleaseEvent(func(e *gui.QKeyEvent) {
291 | h, err := strconv.Atoi(heightInput.Text())
292 | if err != nil {
293 | return
294 | }
295 | w0 := float64(src.Width())
296 | h0 := float64(src.Height())
297 | w := float64(h) * w0 / h0
298 | widthInput.SetText(strconv.Itoa(int(w)))
299 | })
300 |
301 | okBtn.ConnectClicked(func(b bool) {
302 | w, err := strconv.Atoi(widthInput.Text())
303 | if err != nil {
304 | res = src
305 | }
306 | h, err := strconv.Atoi(heightInput.Text())
307 | if err != nil {
308 | res = src
309 | }
310 | res = src.Scaled2(w, h, core.Qt__KeepAspectRatioByExpanding, core.Qt__SmoothTransformation)
311 | dlg.Hide()
312 | dlg.Destroy(true, true)
313 | })
314 |
315 | cancelBtn.ConnectClicked(func(b bool) {
316 | res = src
317 | dlg.Hide()
318 | dlg.Destroy(true, true)
319 | })
320 |
321 | dlg.Exec()
322 | if res == nil {
323 | res = src
324 | }
325 | return
326 | }
327 |
328 | func (s *myWindow) getImageList(html string) []string {
329 | r := strings.NewReader(html)
330 | bufr := bufio.NewReader(r)
331 | reg1, err := regexp.Compile(`
<]+/>`)
332 | if err != nil {
333 | //fmt.Println(err)
334 | return nil
335 | }
336 | reg2, err := regexp.Compile(`src="([^"]+)"`)
337 | if err != nil {
338 | //fmt.Println(err)
339 | return nil
340 | }
341 | imgs := []string{}
342 | for line, _, err := bufr.ReadLine(); err == nil; line, _, err = bufr.ReadLine() {
343 | line1 := string(line)
344 | res1 := reg1.FindAllString(line1, -1)
345 | imgs = append(imgs, res1...)
346 | }
347 |
348 | res := []string{}
349 | for _, img := range imgs {
350 | res2 := reg2.FindStringSubmatch(img)
351 | res = append(res, res2[1])
352 | }
353 | //fmt.Println(res)
354 | return res
355 | }
356 |
357 | func (s *myWindow) insertTable() {
358 | dlg := widgets.NewQDialog(s.window, core.Qt__Dialog)
359 | dlg.SetWindowTitle(T("Table Rows and Columns"))
360 | dlg.SetFixedWidth(s.charWidth() * 13)
361 |
362 | grid := newGridLayout(dlg)
363 |
364 | row := widgets.NewQLabel2(T("Rows:"), dlg, core.Qt__Widget)
365 | grid.AddWidget2(row, 0, 0, 0)
366 |
367 | rowInput := widgets.NewQLineEdit(dlg)
368 | rowInput.SetText("3")
369 | rowInput.SetValidator(gui.NewQIntValidator(dlg))
370 | grid.AddWidget2(rowInput, 0, 1, 0)
371 |
372 | col := widgets.NewQLabel2(T("Columns:"), dlg, core.Qt__Widget)
373 |
374 | grid.AddWidget2(col, 1, 0, 0)
375 |
376 | colInput := widgets.NewQLineEdit(dlg)
377 | colInput.SetText("3")
378 | colInput.SetValidator(gui.NewQIntValidator(dlg))
379 | grid.AddWidget2(colInput, 1, 1, 0)
380 |
381 | btb := newGridLayout2()
382 |
383 | okBtn := widgets.NewQPushButton2(T("OK"), dlg)
384 | btb.AddWidget2(okBtn, 0, 0, 0)
385 |
386 | cancelBtn := widgets.NewQPushButton2(T("Cancel"), dlg)
387 | btb.AddWidget2(cancelBtn, 0, 1, 0)
388 |
389 | grid.AddLayout2(btb, 2, 0, 1, 2, 0)
390 |
391 | dlg.SetLayout(grid)
392 |
393 | okBtn.ConnectClicked(func(b bool) {
394 | cursor := s.editor.TextCursor()
395 | r, err := strconv.Atoi(rowInput.Text())
396 | if err != nil {
397 | return
398 | }
399 | c, err := strconv.Atoi(colInput.Text())
400 | if err != nil {
401 | return
402 | }
403 | tbl := cursor.InsertTable2(r, c)
404 | tbl.Format().SetBorderBrush(gui.NewQBrush2(core.Qt__SolidPattern))
405 | A := 'A'
406 | for i := 0; i < c; i++ {
407 | tbl.CellAt(0, i).FirstCursorPosition().InsertText(fmt.Sprintf(" %c ", A+rune(i)))
408 | }
409 | dlg.Hide()
410 | dlg.Destroy(true, true)
411 | })
412 |
413 | cancelBtn.ConnectClicked(func(b bool) {
414 | dlg.Hide()
415 | dlg.Destroy(true, true)
416 | })
417 |
418 | dlg.SetModal(true)
419 | dlg.Show()
420 | }
421 |
422 | func (s *myWindow) addJustifyActions(tb *widgets.QToolBar) {
423 | rsrcPath := ":/qml/icons"
424 | var leftIcon = gui.QIcon_FromTheme2("format-justify-left", gui.NewQIcon5(rsrcPath+"/textleft.png"))
425 | actionAlignLeft := tb.AddAction2(leftIcon, "&Left")
426 | actionAlignLeft.SetPriority(widgets.QAction__LowPriority)
427 | actionAlignLeft.ConnectTriggered(func(b bool) {
428 | s.textAlign(1)
429 | })
430 |
431 | var centerIcon = gui.QIcon_FromTheme2("format-justify-center", gui.NewQIcon5(rsrcPath+"/textcenter.png"))
432 | actionAlignCenter := tb.AddAction2(centerIcon, "C&enter")
433 | actionAlignCenter.SetPriority(widgets.QAction__LowPriority)
434 | actionAlignCenter.ConnectTriggered(func(b bool) {
435 | s.textAlign(2)
436 | })
437 |
438 | var rightIcon = gui.QIcon_FromTheme2("format-justify-right", gui.NewQIcon5(rsrcPath+"/textright.png"))
439 | actionAlignRight := tb.AddAction2(rightIcon, "&Right")
440 | actionAlignRight.SetPriority(widgets.QAction__LowPriority)
441 | actionAlignRight.ConnectTriggered(func(b bool) {
442 | s.textAlign(3)
443 | })
444 |
445 | var fillIcon = gui.QIcon_FromTheme2("format-justify-fill", gui.NewQIcon5(rsrcPath+"/textjustify.png"))
446 | actionAlignJustify := tb.AddAction2(fillIcon, "&Justify")
447 | actionAlignJustify.SetPriority(widgets.QAction__LowPriority)
448 | actionAlignJustify.ConnectTriggered(func(b bool) {
449 | s.textAlign(4)
450 | })
451 | }
452 |
453 | func (s *myWindow) textAlign(n int) {
454 | switch n {
455 | case 1:
456 |
457 | s.editor.SetAlignment(core.Qt__AlignLeft | core.Qt__AlignAbsolute)
458 |
459 | case 2:
460 |
461 | s.editor.SetAlignment(core.Qt__AlignHCenter)
462 |
463 | case 3:
464 |
465 | s.editor.SetAlignment(core.Qt__AlignRight | core.Qt__AlignAbsolute)
466 |
467 | case 4:
468 |
469 | s.editor.SetAlignment(core.Qt__AlignJustify)
470 |
471 | }
472 | }
473 |
474 | func (s *myWindow) getTable() (t *gui.QTextTable, cell *gui.QTextTableCell) {
475 | cursor := s.editor.TextCursor()
476 | blk := s.editor.Document().FindBlock(cursor.Position())
477 | for _, frame := range blk.Document().RootFrame().ChildFrames() {
478 | //fmt.Println("table cell:", frame.FrameFormat().IsTableCellFormat(), "table:", frame.FrameFormat().IsTableFormat())
479 | if frame.FrameFormat().IsTableFormat() {
480 | table := gui.NewQTextTableFromPointer(frame.Pointer())
481 | cell := table.CellAt2(cursor.Position())
482 | //fmt.Println(cell.Row(), cell.Column())
483 | return table, cell
484 | }
485 | }
486 |
487 | return nil, nil
488 | }
489 |
490 | func (s *myWindow) clearFormatAtCursor() {
491 | cursor := s.editor.TextCursor()
492 |
493 | block := cursor.Block()
494 | cfmt := block.CharFormat()
495 | cfmt.ClearBackground()
496 | cfmt.ClearForeground()
497 | cfmt.SetFontUnderline(false)
498 | cfmt.SetFontWeight(int(gui.QFont__Normal))
499 | cfmt.SetFontPointSize(14)
500 | cfmt.SetFontItalic(false)
501 | cfmt.SetFontStrikeOut(false)
502 |
503 | var bfmt = gui.NewQTextBlockFormat()
504 | bfmt.SetObjectIndex(-1)
505 |
506 | if !cursor.HasSelection() {
507 | cursor.Select(gui.QTextCursor__LineUnderCursor)
508 | }
509 | cursor.SetCharFormat(cfmt)
510 | cursor.SetBlockFormat(bfmt)
511 | s.editor.SetCurrentCharFormat(cfmt)
512 | }
513 |
514 | func OpenDiaryNewWindow(parent *myWindow, id int) *myWindow {
515 | win := new(myWindow)
516 | win.OpenNewWindow(parent, id)
517 | parent.LockEditor()
518 | return win
519 | }
520 |
521 | func (s *myWindow) charWidth() int {
522 | // font := gui.NewQFont()
523 | // font.SetFamily("Serif")
524 | // if runtime.GOOS == "windows" {
525 | // font.SetPointSize(16)
526 | // } else {
527 | // //font.SetFamily("Serif Regular")
528 | // font.SetPointSize(12)
529 | // }
530 |
531 | // fm := gui.NewQFontMetrics(font)
532 | // return fm.BoundingRect2("宽W").Width()
533 | return 24
534 | }
535 |
536 | func (s *myWindow) OpenNewWindow(parent *myWindow, id int) {
537 | s.key = parent.key
538 | s.curDiary = new(diaryPointer)
539 | s.curDiary.Id = id
540 | s.db = parent.db
541 |
542 | s.window = widgets.NewQMainWindow(parent.window, core.Qt__Window)
543 |
544 | s.window.SetWindowTitle(parent.user)
545 | s.window.SetMinimumSize2(800, 600)
546 | s.window.SetWindowIcon(gui.NewQIcon5(":/qml/icons/Sd.png"))
547 |
548 | grid := newGridLayout2()
549 |
550 | frame := widgets.NewQFrame(s.window, core.Qt__Widget)
551 |
552 | s.window.SetCentralWidget(frame)
553 |
554 | charW := s.charWidth()
555 | editor := s.createEditor(charW)
556 | s.window.SetMinimumWidth(s.editor.Width() + 100)
557 |
558 | grid.AddWidget3(editor, 0, 0, 1, 1, 0)
559 |
560 | //grid.SetAlign(core.Qt__AlignTop)
561 |
562 | s.setToolBar()
563 |
564 | s.setMenuBar()
565 |
566 | s.exportEnc.SetEnabled(true)
567 | s.exportPdf.SetEnabled(true)
568 | s.exportOdt.SetEnabled(true)
569 | s.importEnc.SetDisabled(true)
570 | s.newDiary.SetDisabled(true)
571 | s.renDiary.SetDisabled(true)
572 | s.modifyPwd.SetDisabled(true)
573 |
574 | //s.setStandaloneFuncs()
575 | s.setEditorFuncs()
576 |
577 | frame.SetLayout(grid)
578 |
579 | once := sync.Once{}
580 | s.window.ConnectShowEvent(func(e *gui.QShowEvent) {
581 | once.Do(func() {
582 | s.loadDiary()
583 | })
584 | })
585 |
586 | s.window.ConnectCloseEvent(func(e *gui.QCloseEvent) {
587 | if s.editor.Document().IsModified() {
588 | ret := widgets.QMessageBox_Question(s.window, T("Close"), T("Do you want to save the document?"), widgets.QMessageBox__Yes|widgets.QMessageBox__No, widgets.QMessageBox__Yes)
589 | if ret == widgets.QMessageBox__Yes {
590 | s.saveCurDiary()
591 | }
592 | }
593 |
594 | })
595 | s.window.Show()
596 | }
597 |
598 | func (s *myWindow) loadDiary() {
599 | filename := strconv.Itoa(s.curDiary.Id) + ".dat"
600 | data, err := decodeFromFile(filename, s.key)
601 | if err != nil {
602 | s.setStatusBar(err.Error())
603 | } else {
604 | s.getQText(data)
605 | s.editor.Document().SetHtml(s.document.Html)
606 |
607 | s.showAttachList()
608 | s.editor.Document().SetModified(false)
609 | s.editor.SetReadOnly(false)
610 | }
611 | }
612 |
613 | func (s *myWindow) saveDiaryAlone() {
614 | if s.editor.Document().IsModified() == false {
615 | s.setStatusBar(T("No Diary Saved"))
616 | return
617 | }
618 | filename := strconv.Itoa(s.curDiary.Id) + ".dat"
619 | encodeToFile(s.getRichText(), filename, s.key)
620 |
621 | title := strings.TrimSpace(s.editor.Document().FirstBlock().Text())
622 | s.db.UpdateDiaryTitle(s.curDiary.Id, title)
623 |
624 | s.setStatusBar(T("Save Diary") + fmt.Sprintf(" %s(%s)", title, filename))
625 | s.editor.Document().SetModified(false)
626 | }
627 |
628 | func (s *myWindow) searchFromDb(kw string) {
629 | if len(kw) == 0 {
630 | s.setStatusBar(T("Must Input Search Keyword!"))
631 | return
632 | }
633 | s.setStatusBar(T("Loading Diary List..."))
634 | diaryList, err := s.db.SearchTitle(kw)
635 | if err != nil {
636 | s.setStatusBar(err.Error())
637 | return
638 | }
639 |
640 | s.clearModelFind()
641 |
642 | var ym string
643 | var r, c int
644 | for _, diary := range diaryList {
645 | if diary.Day[:7] != ym {
646 | ym = diary.Day[:7]
647 | item := s.addFindYM(ym)
648 | idx := item.Index()
649 | r, c = idx.Row(), idx.Column()
650 | s.treeFind.Expand(idx)
651 | }
652 | item := s.modelFind.Item(r, c)
653 | child := gui.NewQStandardItem2(fmt.Sprintf("%s-%s", diary.Day[8:], diary.Title))
654 | child.SetEditable(false)
655 | child.SetAccessibleText(strconv.Itoa(diary.Id))
656 | child.SetAccessibleDescription("0")
657 | child.SetToolTip(T("Double Click to Open. ") + T("Last Modified:") + diary.MTime)
658 |
659 | item.AppendRow2(child)
660 | }
661 | s.treeFind.ResizeColumnToContents(0)
662 | return
663 | }
664 |
665 | func (s *myWindow) addFindYM(yearMonth string) *gui.QStandardItem {
666 | idx := core.NewQModelIndex()
667 | p := s.modelFind.Parent(idx)
668 | n := s.modelFind.RowCount(p)
669 | for i := 0; i < n; i++ {
670 | item := s.modelFind.Item(i, 0)
671 | if item.Text() == yearMonth {
672 | return item
673 | }
674 | }
675 | item := gui.NewQStandardItem2(yearMonth)
676 | item.SetColumnCount(1)
677 | item.SetEditable(false)
678 |
679 | item.SetAccessibleText("1")
680 | item.SetAccessibleDescription("1")
681 |
682 | s.modelFind.AppendRow2(item)
683 |
684 | return item
685 | }
686 |
687 | func (s *myWindow) setTreeFindFuncs() {
688 | s.treeFind.ConnectActivated(func(idx *core.QModelIndex) {
689 | item := s.modelFind.ItemFromIndex(idx)
690 | if item.AccessibleDescription() == "1" {
691 | return
692 | }
693 | id, err := strconv.Atoi(item.AccessibleText())
694 | if err != nil {
695 | s.setStatusBar(err.Error())
696 | }
697 | OpenDiaryNewWindow(s, id)
698 | })
699 | }
700 |
701 | func (s *myWindow) findText() {
702 | if s.searchInput != nil {
703 | cursor := s.editor.TextCursor()
704 | s.searchInput.SetText(cursor.Selection().ToPlainText())
705 | return
706 | }
707 | dlg := widgets.NewQDialog(s.window, core.Qt__Dialog)
708 | dlg.SetMinimumWidth(s.editor.Width() / 2)
709 |
710 | dlg.SetWindowTitle(T("Text Search"))
711 |
712 | grid := newGridLayout(dlg)
713 |
714 | word := widgets.NewQLineEdit(dlg)
715 | word.SetPlaceholderText(T("Words to search."))
716 | grid.AddWidget3(word, 0, 0, 1, 2, 0)
717 | s.searchInput = word
718 | cursor := s.editor.TextCursor()
719 | s.searchInput.SetText(cursor.Selection().ToPlainText())
720 |
721 | backBtn := widgets.NewQPushButton2(T("Last"), dlg)
722 | grid.AddWidget2(backBtn, 1, 0, 0)
723 |
724 | nextBtn := widgets.NewQPushButton2(T("Next"), dlg)
725 | grid.AddWidget2(nextBtn, 1, 1, 0)
726 |
727 | dlg.SetLayout(grid)
728 |
729 | backBtn.ConnectClicked(func(b bool) {
730 | kw := strings.TrimSpace(word.Text())
731 | if len(kw) == 0 {
732 | return
733 | }
734 | cursor := s.editor.TextCursor()
735 | if len(cursor.Selection().ToPlainText()) > 0 {
736 | cursor.SetPosition(cursor.SelectionStart(), gui.QTextCursor__MoveAnchor)
737 | s.editor.SetTextCursor(cursor)
738 | }
739 | s.editor.Find(kw, gui.QTextDocument__FindBackward)
740 | })
741 |
742 | nextBtn.ConnectClicked(func(b bool) {
743 | kw := strings.TrimSpace(word.Text())
744 | if len(kw) == 0 {
745 | return
746 | }
747 | cursor := s.editor.TextCursor()
748 | if len(cursor.Selection().ToPlainText()) > 0 {
749 | cursor.SetPosition(cursor.SelectionEnd(), gui.QTextCursor__MoveAnchor)
750 | s.editor.SetTextCursor(cursor)
751 | }
752 | s.editor.Find(kw, 0)
753 | })
754 |
755 | dlg.ConnectCloseEvent(func(e *gui.QCloseEvent) {
756 | dlg.Destroy(true, true)
757 | s.searchInput = nil
758 | })
759 |
760 | dlg.Show()
761 | }
762 |
763 | func (s *myWindow) replaceText() {
764 | if s.replaceInput != nil {
765 | cursor := s.editor.TextCursor()
766 | s.replaceInput.SetText(cursor.Selection().ToPlainText())
767 | return
768 | }
769 |
770 | dlg := widgets.NewQDialog(s.window, core.Qt__Dialog)
771 | dlg.SetMinimumWidth(s.editor.Width() / 2)
772 |
773 | dlg.SetWindowTitle(T("Text Replace"))
774 |
775 | grid := newGridLayout(dlg)
776 |
777 | wordOld := widgets.NewQLineEdit(dlg)
778 | wordOld.SetPlaceholderText(T("Old Text."))
779 | wordOld.SetToolTip(T("Old Text."))
780 | grid.AddWidget3(wordOld, 0, 0, 1, 3, 0)
781 | s.replaceInput = wordOld
782 | cursor := s.editor.TextCursor()
783 | s.replaceInput.SetText(cursor.Selection().ToPlainText())
784 |
785 | wordNew := widgets.NewQLineEdit(dlg)
786 | wordNew.SetPlaceholderText(T("New Text."))
787 | wordNew.SetToolTip(T("New Text."))
788 | grid.AddWidget3(wordNew, 1, 0, 1, 3, 0)
789 |
790 | backBtn := widgets.NewQPushButton2(T("Last"), dlg)
791 | grid.AddWidget2(backBtn, 2, 0, 0)
792 |
793 | nextBtn := widgets.NewQPushButton2(T("Next"), dlg)
794 | grid.AddWidget2(nextBtn, 2, 1, 0)
795 |
796 | allBtn := widgets.NewQPushButton2(T("All"), dlg)
797 | grid.AddWidget2(allBtn, 2, 2, 0)
798 |
799 | dlg.SetLayout(grid)
800 |
801 | backBtn.ConnectClicked(func(b bool) {
802 | word0 := wordOld.Text()
803 | word1 := wordNew.Text()
804 | if len(word0) == 0 {
805 | return
806 | }
807 | cursor := s.editor.TextCursor()
808 | if cursor.Selection().ToPlainText() == word0 {
809 | cursor.RemoveSelectedText()
810 | cursor.InsertText(word1)
811 |
812 | cursor.SetPosition(cursor.Position()-len(word1), gui.QTextCursor__MoveAnchor)
813 | s.editor.SetTextCursor(cursor)
814 | }
815 |
816 | s.editor.Find(word0, gui.QTextDocument__FindBackward)
817 | })
818 |
819 | nextBtn.ConnectClicked(func(b bool) {
820 | word0 := wordOld.Text()
821 | word1 := wordNew.Text()
822 | if len(word0) == 0 {
823 | return
824 | }
825 | cursor := s.editor.TextCursor()
826 |
827 | if cursor.Selection().ToPlainText() == word0 {
828 | cursor.RemoveSelectedText()
829 | cursor.InsertText(word1)
830 | }
831 | s.editor.Find(word0, 0)
832 | })
833 |
834 | allBtn.ConnectClicked(func(b bool) {
835 | word0 := wordOld.Text()
836 | if len(word0) == 0 {
837 | return
838 | }
839 | text := s.editor.ToPlainText()
840 | n := strings.Count(strings.ToLower(text), strings.ToLower(word0))
841 |
842 | cursor := s.editor.TextCursor()
843 | cursor.SetPosition(0, gui.QTextCursor__MoveAnchor)
844 | s.editor.SetTextCursor(cursor)
845 |
846 | for i := 0; i < n+1; i++ {
847 | nextBtn.Clicked(true)
848 | }
849 | })
850 |
851 | dlg.ConnectCloseEvent(func(e *gui.QCloseEvent) {
852 | dlg.Destroy(true, true)
853 | s.replaceInput = nil
854 | })
855 |
856 | dlg.Show()
857 | }
858 |
859 | func (s *myWindow) pasteText() {
860 | board := gui.QGuiApplication_Clipboard()
861 | text := board.Text(gui.QClipboard__Clipboard)
862 | cursor := s.editor.TextCursor()
863 | cursor.InsertText(text)
864 | }
865 |
866 | func (s *myWindow) changeLineMargin() {
867 | cursor := s.editor.TextCursor()
868 | if !cursor.HasSelection() {
869 | cursor.Select(gui.QTextCursor__LineUnderCursor)
870 | }
871 | bfmt := cursor.BlockFormat()
872 | margin := bfmt.TopMargin()
873 |
874 | dlg := widgets.NewQDialog(s.window, core.Qt__Dialog)
875 | dlg.SetWindowTitle(T("Top Margin"))
876 | dlg.SetMinimumWidth(s.charWidth() * 10)
877 | layout := widgets.NewQHBoxLayout()
878 |
879 | label := widgets.NewQLabel2(T("Top Margin")+fmt.Sprintf(":%.0f", margin), dlg, core.Qt__Widget)
880 | layout.AddWidget(label, 1, 0)
881 |
882 | addBtn := widgets.NewQPushButton2("+", dlg)
883 | addBtn.SetFixedWidth(s.charWidth())
884 | layout.AddWidget(addBtn, 1, 0)
885 | addBtn.ConnectClicked(func(b bool) {
886 | margin += 1.0
887 | bfmt.SetTopMargin(margin)
888 | cursor.SetBlockFormat(bfmt)
889 | label.SetText(T("Top Margin") + fmt.Sprintf(":%.0f", margin))
890 | })
891 |
892 | degreeBtn := widgets.NewQPushButton2("-", dlg)
893 | degreeBtn.SetFixedWidth(s.charWidth())
894 | layout.AddWidget(degreeBtn, 1, 0)
895 | degreeBtn.ConnectClicked(func(b bool) {
896 | margin -= 1.0
897 | bfmt.SetTopMargin(margin)
898 | cursor.SetBlockFormat(bfmt)
899 | label.SetText(T("Top Margin") + fmt.Sprintf(":%.0f", margin))
900 | })
901 |
902 | dlg.SetLayout(layout)
903 | dlg.Show()
904 | }
905 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module secret-diary
2 |
3 | go 1.18
4 |
5 | require github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d
6 |
7 | require (
8 | github.com/andybalholm/cascadia v1.3.2 // indirect
9 | github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e // indirect
10 | golang.org/x/net v0.24.0 // indirect
11 | )
12 |
13 | require (
14 | github.com/PuerkitoBio/goquery v1.9.2
15 | github.com/mattn/go-sqlite3 v1.14.12
16 | github.com/rocket049/gettext-go v0.0.0-20190410112519-14a7c4752bef
17 | github.com/rocket049/go-locale v0.0.0-20190410112613-f30d0ad89b65
18 | github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
19 | golang.org/x/text v0.14.0
20 | )
21 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
2 | github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
3 | github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
4 | github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7 | github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA=
8 | github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
9 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
10 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
11 | github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
12 | github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
14 | github.com/rocket049/gettext-go v0.0.0-20190410112519-14a7c4752bef h1:RmNQgTGRjCn2ZA7mR52Wu8F9mB0lJC5+ayUEbzPIt+Y=
15 | github.com/rocket049/gettext-go v0.0.0-20190410112519-14a7c4752bef/go.mod h1:kwHzDYyH1mBSE4+qy9lA14PHalXGgmolkS7Em8dwNOw=
16 | github.com/rocket049/go-locale v0.0.0-20190410112613-f30d0ad89b65 h1:f/5CdNFwIu2OD5H+HBV2NrHU4+fpRQ+hn7P2n4uxgoQ=
17 | github.com/rocket049/go-locale v0.0.0-20190410112613-f30d0ad89b65/go.mod h1:QYX7uwo3utb8GfO0eRbWV3uaf7NnzhIV/k+e4hnfS5c=
18 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
19 | github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
20 | github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
21 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
22 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
23 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
24 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
25 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
26 | github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d h1:T+d8FnaLSvM/1BdlDXhW4d5dr2F07bAbB+LpgzMxx+o=
27 | github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
28 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
29 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
30 | golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
31 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
32 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
33 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
34 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
35 | golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
36 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
37 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
38 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
39 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
40 | golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
41 | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
42 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
43 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
44 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
45 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
46 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
47 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
48 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
49 | golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
50 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
51 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
52 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
53 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
54 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
55 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
56 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
57 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
58 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
59 | golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
60 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
61 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
62 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
63 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
64 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
65 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
66 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
67 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
68 | golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
69 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
70 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
71 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
72 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
73 |
--------------------------------------------------------------------------------
/gridlayout_after513.go:
--------------------------------------------------------------------------------
1 | //+build !before513
2 |
3 | //兼容 Qt5.13 以后的版本
4 | package main
5 |
6 | import (
7 | "github.com/therecipe/qt/core"
8 | "github.com/therecipe/qt/gui"
9 | "github.com/therecipe/qt/widgets"
10 | )
11 |
12 | type gridLayout struct {
13 | widgets.QGridLayout
14 | }
15 |
16 | func newGridLayout(parent widgets.QWidget_ITF) *gridLayout {
17 | return &gridLayout{*widgets.NewQGridLayout(parent)}
18 | }
19 |
20 | func newGridLayout2() *gridLayout {
21 | return &gridLayout{*widgets.NewQGridLayout2()}
22 | }
23 |
24 | func newQPixMap(fn string, format string, flag core.Qt__ImageConversionFlag) *gui.QPixmap {
25 | return gui.NewQPixmap3(fn, format, flag)
26 | }
27 |
--------------------------------------------------------------------------------
/gridlayout_before513.go:
--------------------------------------------------------------------------------
1 | //+build before513
2 |
3 | //兼容 Qt5.12 之前的版本
4 | package main
5 |
6 | import (
7 | "github.com/therecipe/qt/core"
8 | "github.com/therecipe/qt/gui"
9 | "github.com/therecipe/qt/widgets"
10 | )
11 |
12 | type gridLayout struct {
13 | AddWidget2 func(widgets.QWidget_ITF, int, int, core.Qt__AlignmentFlag)
14 | widgets.QGridLayout
15 | }
16 |
17 | func newGridLayout(parent widgets.QWidget_ITF) *gridLayout {
18 | res := &gridLayout{AddWidget2: nil, QGridLayout: *widgets.NewQGridLayout(parent)}
19 | res.AddWidget2 = res.AddWidget
20 | return res
21 | }
22 |
23 | func newGridLayout2() *gridLayout {
24 | res := &gridLayout{AddWidget2: nil, QGridLayout: *widgets.NewQGridLayout2()}
25 | res.AddWidget2 = res.AddWidget
26 | return res
27 | }
28 |
29 | func newQPixMap(fn string, format string, flag core.Qt__ImageConversionFlag) *gui.QPixmap {
30 | return gui.NewQPixmap5(fn, format, flag)
31 | }
32 |
--------------------------------------------------------------------------------
/gui.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "log"
7 | "os"
8 | "path"
9 | "path/filepath"
10 | "regexp"
11 | "runtime"
12 | "strconv"
13 | "strings"
14 | "sync"
15 | "time"
16 |
17 | "github.com/skratchdot/open-golang/open"
18 |
19 | "github.com/therecipe/qt/gui"
20 | "github.com/therecipe/qt/printsupport"
21 |
22 | "github.com/rocket049/gettext-go/gettext"
23 |
24 | "github.com/therecipe/qt/core"
25 | "github.com/therecipe/qt/widgets"
26 | )
27 |
28 | var datDir string
29 |
30 | func init() {
31 | exe1, _ := os.Executable()
32 | dir1 := path.Dir(exe1)
33 | locale1 := path.Join(dir1, "locale")
34 | gettext.BindTextdomain("sdiary", locale1, nil)
35 | gettext.Textdomain("sdiary")
36 |
37 | home1, _ := os.UserHomeDir()
38 | cfg := path.Join(home1, ".sdiary", "datapath.txt")
39 | cfgBytes, err := ioutil.ReadFile(cfg)
40 | if err != nil {
41 | datDir = path.Join(home1, ".sdiary")
42 | } else {
43 | datDir = string(cfgBytes)
44 | }
45 | }
46 |
47 | type diaryPointer struct {
48 | Item *gui.QStandardItem
49 | Id int
50 | Day string
51 | YearMonth string
52 | //Modified bool
53 | }
54 |
55 | func (s *diaryPointer) zero() {
56 | s.Item = nil
57 | s.Id = 0
58 | s.Day = ""
59 | s.YearMonth = ""
60 | }
61 |
62 | //T wrap gettext.T
63 | func T(v string) string {
64 | return gettext.T(v)
65 | }
66 |
67 | type formatData struct {
68 | BF *gui.QTextBlockFormat
69 | CF *gui.QTextCharFormat
70 | }
71 |
72 | type myWindow struct {
73 | app *widgets.QApplication
74 | window *widgets.QMainWindow
75 | tree *widgets.QTreeView
76 | model *gui.QStandardItemModel
77 | treeFind *widgets.QTreeView
78 | modelFind *gui.QStandardItemModel
79 | editor *widgets.QTextEdit
80 | comboAttachs *widgets.QComboBox
81 | actionTextBold *widgets.QAction
82 | actionTextUnderline *widgets.QAction
83 | actionTextItalic *widgets.QAction
84 | actionStrikeOut *widgets.QAction
85 | exportEnc *widgets.QAction
86 | exportPdf *widgets.QAction
87 | exportOdt *widgets.QAction
88 | importEnc *widgets.QAction
89 | changeStorage *widgets.QAction
90 | paste *widgets.QAction
91 | search *widgets.QAction
92 | replace *widgets.QAction
93 | lineMargin *widgets.QAction
94 | searchInput *widgets.QLineEdit
95 | replaceInput *widgets.QLineEdit
96 | newDiary *widgets.QAction
97 | saveDiary *widgets.QAction
98 | renDiary *widgets.QAction
99 | modifyPwd *widgets.QAction
100 | fb *widgets.QAction //格式刷
101 | categories *widgets.QMenu
102 | category int
103 | categoryName string
104 | categoryItem *widgets.QAction
105 | curDiary *diaryPointer
106 | user string
107 | key []byte
108 | db *myDb
109 |
110 | document qtextFormat
111 | format formatData
112 | }
113 |
114 | func (s *myWindow) getNewId() (res int) {
115 | return s.db.NextId()
116 | }
117 |
118 | func (s *myWindow) setMenuBar() {
119 | s.document.Images = make(map[string][]byte)
120 |
121 | menubar := s.window.MenuBar()
122 | menu := menubar.AddMenu2(T("File"))
123 |
124 | s.exportEnc = menu.AddAction(T("Export Encrypted Diary"))
125 | s.exportEnc.SetDisabled(true)
126 | s.exportEnc.ConnectTriggered(func(b bool) {
127 | s.saveCurDiary()
128 | s.exportEncryptedDiary()
129 | })
130 |
131 | s.exportPdf = menu.AddAction(T("Export PDF..."))
132 | s.exportPdf.SetDisabled(true)
133 | s.exportPdf.ConnectTriggered(func(b bool) {
134 | s.exportAsPdf()
135 | })
136 |
137 | s.exportOdt = menu.AddAction(T("Export Odt..."))
138 | s.exportOdt.SetDisabled(true)
139 | s.exportOdt.ConnectTriggered(func(b bool) {
140 | s.exportAsOdt()
141 | })
142 |
143 | s.importEnc = menu.AddAction(T("Import Encrypted Diary"))
144 | s.importEnc.ConnectTriggered(func(b bool) {
145 | s.importEncryptedDiary()
146 | })
147 |
148 | exportAll := menu.AddAction((T("Export All")))
149 | exportAll.ConnectTriggered(func(b bool) {
150 | home, _ := os.UserHomeDir()
151 | dir := widgets.QFileDialog_GetExistingDirectory(s.window, T("Select export directory"), home, 0)
152 | if len(dir) == 0 {
153 | return
154 | }
155 | fromDir := filepath.Join(datDir, s.user)
156 | toFile := filepath.Join(home, s.user+".bak")
157 | err := zipData(fromDir, toFile)
158 | if err != nil {
159 | widgets.QMessageBox_About(s.window, T("Error"), err.Error())
160 | } else {
161 | widgets.QMessageBox_About(s.window, T("Complete"), T("Complete"))
162 | }
163 | })
164 |
165 | importAll := menu.AddAction(T("Import All"))
166 | importAll.ConnectTriggered(func(b bool) {
167 | home, _ := os.UserHomeDir()
168 | filename := widgets.QFileDialog_GetOpenFileName(s.window, T("Select a bakup file"), home, ".bak (*.bak)", ".bak (*.bak)", 0)
169 | if len(filename) == 0 {
170 | return
171 | }
172 | var ok bool
173 | pwd := widgets.QInputDialog_GetText(s.window, T("The password of the bakup file"), T("Password:"), widgets.QLineEdit__Password, "",
174 | &ok, core.Qt__Dialog, core.Qt__ImhNone)
175 | if len(filename) < 4 || !ok {
176 | return
177 | }
178 | err := s.importFromZip(filename, pwd)
179 | if err != nil {
180 | widgets.QMessageBox_About(s.window, T("Error"), err.Error())
181 | } else {
182 | widgets.QMessageBox_About(s.window, T("Complete"), T("Complete"))
183 | }
184 |
185 | s.clearModel()
186 |
187 | s.addYearMonthsFromDb()
188 | })
189 |
190 | s.changeStorage = menu.AddAction(T("Change Storage Dir"))
191 | s.changeStorage.ConnectTriggered(func(b bool) {
192 | dir1 := widgets.QFileDialog_GetExistingDirectory(s.window, T("Select Storage Directory"), datDir, 0)
193 | if len(dir1) > 0 {
194 | home1, _ := os.UserHomeDir()
195 | cfg := path.Join(home1, ".sdiary", "datapath.txt")
196 | ioutil.WriteFile(cfg, []byte(dir1), 0644)
197 | widgets.QMessageBox_Information(s.window, T("Quit program"),
198 | T("The program will terminate now.")+"\n"+T("How to move data?")+"\n"+T("Move the directories to the new directory from ")+datDir,
199 | widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
200 | s.app.Quit()
201 | }
202 | })
203 |
204 | menu = menubar.AddMenu2(T("Edit"))
205 | s.paste = menu.AddAction(T("Paste Text"))
206 | s.paste.SetShortcut(gui.QKeySequence_FromString("Ctrl+d", gui.QKeySequence__NativeText))
207 | s.paste.ConnectTriggered(func(b bool) {
208 | s.pasteText()
209 | })
210 |
211 | s.search = menu.AddAction(T("Search"))
212 | s.search.SetShortcut(gui.QKeySequence_FromString("Ctrl+f", gui.QKeySequence__NativeText))
213 | s.search.ConnectTriggered(func(b bool) {
214 | s.findText()
215 | })
216 |
217 | s.replace = menu.AddAction(T("Replace"))
218 | s.replace.SetShortcut(gui.QKeySequence_FromString("Ctrl+r", gui.QKeySequence__NativeText))
219 | s.replace.ConnectTriggered(func(b bool) {
220 | s.replaceText()
221 | })
222 |
223 | s.lineMargin = menu.AddAction(T("Top Margin"))
224 | s.lineMargin.SetShortcut(gui.QKeySequence_FromString("Ctrl+l", gui.QKeySequence__NativeText))
225 | s.lineMargin.ConnectTriggered(func(b bool) {
226 | s.changeLineMargin()
227 | })
228 |
229 | menu = menubar.AddMenu2(T("Table"))
230 |
231 | newTable := menu.AddAction(T("Insert Table"))
232 | newTable.ConnectTriggered(func(b bool) {
233 | s.insertTable()
234 | })
235 |
236 | addPreRow := menu.AddAction(T("Insert Row"))
237 | addPreRow.ConnectTriggered(func(b bool) {
238 | t, c := s.getTable()
239 | if t == nil {
240 | return
241 | }
242 | t.InsertRows(c.Row(), 1)
243 | })
244 | appendRow := menu.AddAction(T("Append Row"))
245 | appendRow.ConnectTriggered(func(b bool) {
246 | t, _ := s.getTable()
247 | if t == nil {
248 | return
249 | }
250 | t.AppendRows(1)
251 | })
252 |
253 | addPreCol := menu.AddAction(T("Insert Column"))
254 | addPreCol.ConnectTriggered(func(b bool) {
255 | t, c := s.getTable()
256 | if t == nil {
257 | return
258 | }
259 | t.InsertColumns(c.Column(), 1)
260 | })
261 | appendCol := menu.AddAction(T("Append Column"))
262 | appendCol.ConnectTriggered(func(b bool) {
263 | t, _ := s.getTable()
264 | if t == nil {
265 | return
266 | }
267 | t.AppendColumns(1)
268 | })
269 |
270 | removeRow := menu.AddAction(T("Remove Row"))
271 | removeRow.ConnectTriggered(func(b bool) {
272 | t, c := s.getTable()
273 | if t == nil {
274 | return
275 | }
276 | t.RemoveRows(c.Row(), 1)
277 | })
278 |
279 | removeCol := menu.AddAction(T("Remove Column"))
280 | removeCol.ConnectTriggered(func(b bool) {
281 | t, c := s.getTable()
282 | if t == nil {
283 | return
284 | }
285 | t.RemoveColumns(c.Column(), 1)
286 | })
287 |
288 | s.showCategores(menubar)
289 |
290 | menu = menubar.AddMenu2(T("Manage"))
291 |
292 | s.renDiary = menu.AddAction(T("Rename"))
293 | s.renDiary.ConnectTriggered(func(b bool) {
294 | s.rename()
295 | })
296 |
297 | s.modifyPwd = menu.AddAction(T("ModifyPassword"))
298 | s.modifyPwd.ConnectTriggered(func(b bool) {
299 | s.updatePwd()
300 | })
301 |
302 | menu = menubar.AddMenu2(T("Help"))
303 |
304 | about := menu.AddAction(T("About"))
305 | about.ConnectTriggered(func(b bool) {
306 | s.showMsg(T("About"), T("Copy Right: Fu Huizhong \nSecurity Diary ")+version+"\n"+T("Crypto with AES256")+"\nHomepage: https://github.com/rocket049/secret-diary")
307 | })
308 |
309 | update1 := menu.AddAction(T("Update"))
310 | update1.ConnectTriggered(func(b bool) {
311 | v, newest := versionCheck()
312 | if newest == true {
313 | s.showMsg(T("Update"), T("This is the Newest Version!"))
314 | } else {
315 | s.showMsg(T("Update"), T("Current:")+version+"\n"+T("Newest:")+v+"\n"+T("I will open the page on github website after you click button 'OK'!"))
316 | open.Start("https://github.com/rocket049/secret-diary/releases")
317 | }
318 | })
319 |
320 | itemName := "Add To Menu"
321 | if runtime.GOOS == "windows" {
322 | itemName = "Create Desktop Shortcut"
323 | }
324 |
325 | add2menu := menu.AddAction(T(itemName))
326 | add2menu.ConnectTriggered(func(b bool) {
327 | makeShortcut(true)
328 | if runtime.GOOS == "linux" {
329 | s.showMsg(T("Add To Menu"), T("You can start this program from 'Start'->'Office'"))
330 | }
331 |
332 | })
333 |
334 | bak := menu.AddAction(T("Backup & Delete"))
335 | bak.ConnectTriggered(func(b bool) {
336 | s.showMsg(T("Backup & Delete"), T("Your DATA Storage path is '")+dataDir+"'\n"+T("You can backup and delete the directory yourself."))
337 | })
338 |
339 | pwd := menu.AddAction(T("Password"))
340 | pwd.ConnectTriggered(func(b bool) {
341 | s.showMsg(T("Password"), T("There is no way to recover password. \nPlease write it on paper!"))
342 | })
343 |
344 | support := menu.AddAction(T("Support Author"))
345 | support.ConnectTriggered(func(b bool) {
346 | s.showPay()
347 | })
348 |
349 | }
350 |
351 | func (s *myWindow) setToolBar() {
352 | bar := widgets.NewQToolBar("File", nil)
353 | var icon *gui.QIcon
354 |
355 | icon = gui.NewQIcon5(":/qml/icons/document-new.jpg")
356 | s.newDiary = bar.AddAction2(icon, T("New"))
357 | s.newDiary.ConnectTriggered(func(b bool) {
358 | s.setStatusBar(T("New Diary"))
359 | now := time.Now()
360 | s.addDiary(now.Format("2006-01"), now.Format("02"), T("New Diary")+now.Format("2006-01-02"))
361 | })
362 | icon = gui.NewQIcon5(":/qml/icons/document-save.jpg")
363 | s.saveDiary = bar.AddAction2(icon, T("Save"))
364 | s.saveDiary.SetToolTip(T("Save") + " Ctrl-S")
365 | s.saveDiary.SetShortcut(gui.QKeySequence_FromString("CTRL+s", gui.QKeySequence__NativeText))
366 |
367 | s.window.AddToolBar2(bar)
368 |
369 | bar = widgets.NewQToolBar("Format", nil)
370 | icon = gui.NewQIcon5(":/qml/icons/h1.png")
371 | head1 := bar.AddAction2(icon, "H1")
372 | head1.SetToolTip(T("Header 1"))
373 | head1.ConnectTriggered(func(b bool) {
374 | s.setHeader(1)
375 | })
376 | icon = gui.NewQIcon5(":/qml/icons/h2.png")
377 | head2 := bar.AddAction2(icon, "H2")
378 | head2.SetToolTip(T("Header 2"))
379 | head2.ConnectTriggered(func(b bool) {
380 | s.setHeader(2)
381 | })
382 | icon = gui.NewQIcon5(":/qml/icons/h3.png")
383 | head3 := bar.AddAction2(icon, "H3")
384 | head3.SetToolTip(T("Header 3"))
385 | head3.ConnectTriggered(func(b bool) {
386 | s.setHeader(3)
387 | })
388 | icon = gui.NewQIcon5(":/qml/icons/std.png")
389 | standard := bar.AddAction2(icon, T("Standard"))
390 | standard.ConnectTriggered(func(b bool) {
391 | var cfmt = gui.NewQTextCharFormat()
392 | cfmt.SetFont2(s.app.Font())
393 | cfmt.SetFontPointSize(14)
394 | cfmt.SetForeground(gui.NewQBrush3(gui.NewQColor2(core.Qt__black), core.Qt__SolidPattern))
395 | s.mergeFormatOnLineOrSelection(cfmt)
396 | })
397 |
398 | //var icon *gui.QIcon
399 |
400 | icon = gui.NewQIcon5(":/qml/icons/B.png")
401 | s.actionTextBold = bar.AddAction2(icon, T("Bold"))
402 | s.actionTextBold.SetCheckable(true)
403 | s.actionTextBold.ConnectTriggered(func(checked bool) {
404 | s.textBold()
405 | })
406 |
407 | icon = gui.NewQIcon5(":/qml/icons/I.png")
408 | s.actionTextItalic = bar.AddAction2(icon, T("Italic"))
409 | s.actionTextItalic.SetCheckable(true)
410 | s.actionTextItalic.ConnectTriggered(func(checked bool) {
411 | s.textItalic()
412 | })
413 |
414 | icon = gui.NewQIcon5(":/qml/icons/U.png")
415 | s.actionTextUnderline = bar.AddAction2(icon, T("Underline"))
416 | s.actionTextUnderline.SetCheckable(true)
417 | s.actionTextUnderline.ConnectTriggered(func(checked bool) {
418 | s.textUnderline()
419 | })
420 |
421 | icon = gui.NewQIcon5(":/qml/icons/S.png")
422 | s.actionStrikeOut = bar.AddAction2(icon, T("StrikeOut"))
423 | s.actionStrikeOut.SetCheckable(true)
424 | s.actionStrikeOut.ConnectTriggered(func(checked bool) {
425 | s.textStrikeOut()
426 | })
427 |
428 | icon = gui.NewQIcon5(":/qml/icons/fg.png")
429 | actionTextColor := bar.AddAction2(icon, T("Text Color..."))
430 | actionTextColor.ConnectTriggered(func(checked bool) {
431 | s.textColor()
432 | })
433 |
434 | icon = gui.NewQIcon5(":/qml/icons/bg.png")
435 | actionBgColor := bar.AddAction2(icon, T("Background Color..."))
436 | actionBgColor.ConnectTriggered(func(checked bool) {
437 | s.textBgColor()
438 | })
439 |
440 | icon = gui.NewQIcon5(":/qml/icons/draw-eraser.png")
441 | actionClear := bar.AddAction2(icon, T("Clear Line Format..."))
442 | actionClear.ConnectTriggered(func(checked bool) {
443 | s.clearFormatAtCursor()
444 | })
445 |
446 | s.addJustifyActions(bar)
447 |
448 | icon = gui.NewQIcon5(":/qml/icons/right.png")
449 | increaseIdent := bar.AddAction2(icon, T("Increase Ident"))
450 | increaseIdent.ConnectTriggered(func(checked bool) {
451 | s.addIndent(1)
452 | })
453 |
454 | icon = gui.NewQIcon5(":/qml/icons/left.png")
455 | degreeIdent := bar.AddAction2(icon, T("Decrease Ident"))
456 | degreeIdent.ConnectTriggered(func(checked bool) {
457 | s.addIndent(-1)
458 | })
459 |
460 | icon = gui.NewQIcon5(":/qml/icons/brush.png")
461 | s.fb = bar.AddAction2(icon, T("Format Brush"))
462 | s.fb.SetCheckable(true)
463 | s.fb.ConnectTriggered(func(checked bool) {
464 | cursor := s.editor.TextCursor()
465 | if !cursor.HasSelection() {
466 | cursor.Select(gui.QTextCursor__LineUnderCursor)
467 | }
468 | s.format.BF = cursor.BlockFormat()
469 | s.format.CF = cursor.CharFormat()
470 | s.fb.SetChecked(true)
471 | })
472 |
473 | comboStyle := widgets.NewQComboBox(bar)
474 | bar.AddWidget(comboStyle)
475 | comboStyle.AddItems([]string{
476 | T("Standard"),
477 | T("List (Disc)"),
478 | T("List (Circle)"),
479 | T("List (Square)"),
480 | T("List (Decimal)"),
481 | T("List (Alpha lower)"),
482 | T("List (Alpha upper)"),
483 | T("List (Roman lower)"),
484 | T("List (Roman upper)"),
485 | })
486 | comboStyle.ConnectActivated(s.textStyle)
487 |
488 | s.window.AddToolBar2(bar)
489 |
490 | bar = widgets.NewQToolBar("Insert", nil)
491 |
492 | addImage := bar.AddAction(T("Insert Image"))
493 | addImage.SetToolTip(T("Insert Image"))
494 | addImage.ConnectTriggered(func(b bool) {
495 | s.insertImage()
496 | })
497 |
498 | addTable := bar.AddAction(T("Insert Table"))
499 | addTable.SetToolTip(T("Insert Table"))
500 | addTable.ConnectTriggered(func(b bool) {
501 | s.insertTable()
502 | })
503 |
504 | attach := bar.AddAction(T("Attach..."))
505 | attach.SetToolTip(T("Append Attachments"))
506 | attach.ConnectTriggered(func(b bool) {
507 | filename := widgets.QFileDialog_GetOpenFileName(s.window, T("Seclect File"), ".", "All (*)", "All (*)", widgets.QFileDialog__ReadOnly)
508 | if s.addAttachment(filename) {
509 | s.showAttachList()
510 | s.editor.Document().SetModified(true)
511 | //curDiary.Modified = true
512 | }
513 |
514 | })
515 |
516 | font := bar.AddAction(T("Font"))
517 | font.SetToolTip(T("CurrentFont"))
518 | font.ConnectTriggered(func(b bool) {
519 | var ok bool
520 | f := widgets.QFontDialog_GetFont2(&ok, s.window)
521 | if ok {
522 | cfmt := gui.NewQTextCharFormat()
523 | cfmt.SetFont2(f)
524 | s.mergeFormatOnLineOrSelection(cfmt)
525 | }
526 | })
527 |
528 | s.window.AddToolBar2(bar)
529 | }
530 |
531 | func (s *myWindow) setStatusBar(msg string) {
532 | bar := s.window.StatusBar()
533 | bar.ShowMessage(msg, 20000)
534 | }
535 |
536 | func (s *myWindow) setupComboAttachs() *widgets.QHBoxLayout {
537 | hbox := widgets.NewQHBoxLayout()
538 |
539 | s.comboAttachs = widgets.NewQComboBox(s.window)
540 | s.comboAttachs.SetSizePolicy2(widgets.QSizePolicy__Expanding, widgets.QSizePolicy__Fixed)
541 | list := []string{fmt.Sprintf("-- %s(%s:0) --", T("Select Attachments"), T("Total"))}
542 | s.comboAttachs.AddItems(list)
543 | expBtn := widgets.NewQPushButton2(T("Export As..."), s.window)
544 | expBtn.SetSizePolicy2(widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Fixed)
545 | hbox.AddWidget(s.comboAttachs, 1, 0)
546 | hbox.AddWidget(expBtn, 1, 0)
547 |
548 | delBtn := widgets.NewQPushButton2(T("Remove"), s.window)
549 | delBtn.SetSizePolicy2(widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Fixed)
550 | hbox.AddWidget(delBtn, 1, 0)
551 |
552 | expBtn.ConnectClicked(func(b bool) {
553 | idx := s.comboAttachs.CurrentIndex()
554 | if idx == 0 {
555 | return
556 | }
557 | idx -= 1
558 | dlg := widgets.NewQFileDialog(s.window, core.Qt__Dialog)
559 | filename := dlg.GetSaveFileName(s.window, T("Save File"), s.document.Attachments[idx].Name, "All (*)", "All (*)", 0)
560 | if len(filename) > 0 {
561 | ioutil.WriteFile(filename, s.document.Attachments[idx].Data, 0644)
562 | }
563 |
564 | })
565 |
566 | delBtn.ConnectClicked(func(b bool) {
567 | if s.comboAttachs.CurrentIndex() == 0 {
568 | return
569 | }
570 | ret := widgets.QMessageBox_Question(s.window, T("Remove"), T("Are you sure?"), widgets.QMessageBox__Yes|widgets.QMessageBox__No, widgets.QMessageBox__Yes)
571 | if ret == widgets.QMessageBox__Yes {
572 | s.removeCurAttachment()
573 | }
574 |
575 | })
576 |
577 | return hbox
578 | }
579 |
580 | func (s *myWindow) showAttachList() {
581 | list := []string{fmt.Sprintf("-- %s(%s:%d) --", T("Select Attachments"), T("Total"), len(s.document.Attachments))}
582 | for _, v := range s.document.Attachments {
583 | list = append(list, v.Name)
584 | }
585 | s.comboAttachs.Clear()
586 | s.comboAttachs.AddItems(list)
587 | }
588 |
589 | func (s *myWindow) clearAttachs() {
590 | s.comboAttachs.Clear()
591 | list := []string{fmt.Sprintf("-- %s(%s:0) --", T("Select Attachments"), T("Total"))}
592 | s.comboAttachs.AddItems(list)
593 | }
594 |
595 | func (s *myWindow) addAttachment(filename string) bool {
596 | name := filepath.Base(filename)
597 | data, err := ioutil.ReadFile(filename)
598 | if err != nil {
599 | log.Println(err)
600 | return false
601 | }
602 | s.document.Attachments = append(s.document.Attachments, attachmentFile{Name: name, Data: data})
603 | return true
604 | }
605 |
606 | func (s *myWindow) Create(app *widgets.QApplication) {
607 |
608 | font := gui.NewQFont()
609 | font.SetPointSize(12)
610 | font.SetFamily("Serif")
611 | font.SetStyleName("Regular")
612 | app.SetFont(font, "serif-regular")
613 | s.app = app
614 |
615 | charW := s.charWidth()
616 |
617 | s.window = widgets.NewQMainWindow(nil, core.Qt__Window)
618 | s.window.SetWindowTitle(T("UserName"))
619 | s.window.SetWindowIcon(gui.NewQIcon5(":/qml/icons/Sd.png"))
620 |
621 | s.curDiary = new(diaryPointer)
622 |
623 | spliter := widgets.NewQSplitter2(core.Qt__Horizontal, s.window)
624 |
625 | s.window.SetCentralWidget(spliter)
626 |
627 | leftArea := s.createLeftArea(charW)
628 |
629 | spliter.AddWidget(leftArea)
630 |
631 | editor := s.createEditor(charW)
632 |
633 | spliter.AddWidget(editor)
634 |
635 | spliter.SetStretchFactor(0, 1)
636 | spliter.SetStretchFactor(1, 2)
637 |
638 | s.setToolBar()
639 |
640 | s.setMenuBar()
641 |
642 | app.SetActiveWindow(s.window)
643 | app.SetApplicationDisplayName(T("Secret Diary"))
644 | app.SetApplicationName("sdiary")
645 | app.SetApplicationVersion(version)
646 |
647 | s.setEditorFuncs()
648 | s.setTreeFuncs()
649 |
650 | once := sync.Once{}
651 | s.window.ConnectShowEvent(func(e *gui.QShowEvent) {
652 | once.Do(func() {
653 | go makeShortcut(false)
654 | s.login()
655 | })
656 | })
657 |
658 | s.window.ConnectCloseEvent(func(e *gui.QCloseEvent) {
659 | if s.editor.Document().IsModified() {
660 | ret := widgets.QMessageBox_Question(s.window, T("Close"), T("Do you want to save the document?"), widgets.QMessageBox__Yes|widgets.QMessageBox__No, widgets.QMessageBox__Yes)
661 | if ret == widgets.QMessageBox__Yes {
662 | s.saveCurDiary()
663 | }
664 | }
665 |
666 | s.db.Close()
667 | })
668 | s.window.ShowMaximized()
669 | }
670 |
671 | func (s *myWindow) createLeftArea(charW int) widgets.QWidget_ITF {
672 | spliter := widgets.NewQSplitter(nil)
673 | spliter.SetOrientation(core.Qt__Vertical)
674 | spliter.SetSizePolicy2(widgets.QSizePolicy__Preferred, widgets.QSizePolicy__Expanding)
675 |
676 | //mwidth := charW * 18
677 |
678 | s.tree = widgets.NewQTreeView(s.window)
679 | //s.tree.SetMaximumWidth(mwidth)
680 | s.tree.SetSizePolicy2(widgets.QSizePolicy__Preferred, widgets.QSizePolicy__Expanding)
681 | s.tree.SetHorizontalScrollBarPolicy(core.Qt__ScrollBarAsNeeded)
682 | s.tree.SetAutoScroll(true)
683 | s.model = gui.NewQStandardItemModel2(0, 1, s.tree)
684 | s.clearModel()
685 |
686 | s.tree.SetModel(s.model)
687 | spliter.AddWidget(s.tree)
688 |
689 | search := widgets.NewQWidget(nil, core.Qt__Widget)
690 | grid := newGridLayout(search)
691 | keyword := widgets.NewQLineEdit(search)
692 | grid.AddWidget2(keyword, 0, 0, 0)
693 |
694 | btn := widgets.NewQPushButton2(T("Search"), search)
695 | grid.AddWidget2(btn, 0, 1, 0)
696 | btn.ConnectClicked(func(b bool) {
697 | s.searchFromDb(strings.TrimSpace(keyword.Text()))
698 | })
699 | keyword.ConnectEditingFinished(func() {
700 | s.searchFromDb(strings.TrimSpace(keyword.Text()))
701 | })
702 |
703 | s.treeFind = widgets.NewQTreeView(s.window)
704 |
705 | s.treeFind.SetSizePolicy2(widgets.QSizePolicy__Preferred, widgets.QSizePolicy__Preferred)
706 | s.treeFind.SetHorizontalScrollBarPolicy(core.Qt__ScrollBarAsNeeded)
707 | s.treeFind.SetAutoScroll(true)
708 | s.modelFind = gui.NewQStandardItemModel2(0, 1, s.treeFind)
709 | s.clearModelFind()
710 | s.treeFind.SetModel(s.modelFind)
711 | grid.AddWidget3(s.treeFind, 1, 0, 1, 2, 0)
712 | search.SetLayout(grid)
713 |
714 | spliter.AddWidget(search)
715 |
716 | s.setTreeFindFuncs()
717 | return spliter
718 | }
719 |
720 | func (s *myWindow) createEditor(charW int) widgets.QWidget_ITF {
721 | editorLayout := widgets.NewQVBoxLayout()
722 |
723 | scrollarea := widgets.NewQScrollArea(s.window)
724 | scrollarea.SetHorizontalScrollBarPolicy(core.Qt__ScrollBarAsNeeded)
725 | scrollarea.SetVerticalScrollBarPolicy(core.Qt__ScrollBarAsNeeded)
726 | scrollarea.SetAlignment(core.Qt__AlignCenter)
727 | scrollarea.SetSizePolicy2(widgets.QSizePolicy__Expanding, widgets.QSizePolicy__Expanding)
728 | frame := widgets.NewQFrame(s.window, core.Qt__Widget)
729 | grid := newGridLayout(frame)
730 | s.editor = widgets.NewQTextEdit(s.window)
731 | s.editor.SetSizePolicy2(widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Expanding)
732 | s.editor.SetTabChangesFocus(false)
733 | s.editor.SetReadOnly(true)
734 | s.editor.SetStyleSheet("QTextEdit{background-color:rgba(255,255,255,255);border:1px;border-style:solid;border-color:#000000}")
735 |
736 | width := charW * 30
737 |
738 | s.editor.SetTabStopWidth(40)
739 | s.editor.SetFixedWidth(width)
740 |
741 | scrollarea.SetMinimumWidth(width + 40)
742 |
743 | s.editor.SetSizePolicy2(widgets.QSizePolicy__Fixed, widgets.QSizePolicy__Expanding)
744 |
745 | scrollarea.ConnectResizeEvent(func(e *gui.QResizeEvent) {
746 | frame.SetFixedSize(core.NewQSize2(width+40, scrollarea.Geometry().Height()))
747 | scrollarea.ActivateWindow()
748 | })
749 |
750 | grid.AddWidget2(s.editor, 0, 0, 0)
751 |
752 | comboBox := s.setupComboAttachs()
753 | //grid.AddLayout(comboBox, 1, 0, 0)
754 |
755 | //grid.SetAlign(core.Qt__AlignHCenter)
756 | frame.SetLayout(grid)
757 | scrollarea.SetWidget(frame)
758 |
759 | editorLayout.AddWidget(scrollarea, 1, 0)
760 | editorLayout.AddLayout(comboBox, 1)
761 |
762 | res := widgets.NewQWidget(s.window, core.Qt__Widget)
763 | res.SetLayout(editorLayout)
764 |
765 | return res
766 | }
767 |
768 | func (s *myWindow) saveCurDiary() {
769 | if s.editor.Document().IsModified() == false {
770 | s.setStatusBar(T("No Diary Saved"))
771 | return
772 | }
773 | //fmt.Println("save", curDiary.Item.AccessibleText(), s.editor.ToHtml())
774 | var first bool = false
775 | if len(s.curDiary.Item.AccessibleText()) == 0 {
776 | p := s.curDiary.Item.Parent()
777 | pos := s.curDiary.Item.Index()
778 | item := p.TakeChild(pos.Row(), pos.Column())
779 | item.SetAccessibleText(fmt.Sprintf("%d", s.db.NextId()))
780 | p.SetChild(pos.Row(), pos.Column(), item)
781 | s.curDiary.Item = item
782 | first = true
783 | }
784 |
785 | filename := s.curDiary.Item.AccessibleText() + ".dat"
786 | id, err := strconv.Atoi(s.curDiary.Item.AccessibleText())
787 | if err != nil {
788 | s.setStatusBar(T("Error ID"))
789 | return
790 | }
791 | vs := strings.SplitN(s.curDiary.Item.Text(), "-", 2)
792 | title := vs[1]
793 | //fmt.Println(filename, title)
794 | if first {
795 | s.db.AddDiary(id, time.Now().Format("2006-01-02"), title, filename)
796 | } else {
797 | s.db.UpdateDiaryTitle(id, title)
798 | }
799 |
800 | encodeToFile(s.getRichText(), filename, s.key)
801 | s.setStatusBar(T("Save Diary") + fmt.Sprintf(" %s(%s)", title, filename))
802 |
803 | s.editor.Document().SetModified(false)
804 | }
805 |
806 | func (s *myWindow) addDiary(yearMonth, day, title string) {
807 | if s.curDiary.Item != nil {
808 | s.saveCurDiary()
809 | }
810 |
811 | month := s.addYearMonth(yearMonth)
812 | month.SetAccessibleText("1")
813 | month.SetAccessibleDescription("1")
814 | diary := gui.NewQStandardItem2(fmt.Sprintf("%s-%s", day, title))
815 | diary.SetEditable(false)
816 | diary.SetAccessibleText("")
817 | diary.SetAccessibleDescription("0")
818 |
819 | //month.AppendRow2(diary)
820 | month.InsertRow2(0, diary)
821 |
822 | s.tree.SetCurrentIndex(diary.Index())
823 |
824 | s.curDiary.Item = diary
825 | s.curDiary.Day = day
826 | s.curDiary.YearMonth = yearMonth
827 |
828 | s.document.Attachments = []attachmentFile{}
829 | s.document.Images = make(map[string][]byte)
830 |
831 | s.editor.SetReadOnly(false)
832 | s.exportEnc.SetEnabled(true)
833 | s.exportPdf.SetEnabled(true)
834 | s.exportOdt.SetEnabled(true)
835 |
836 | s.setTitle(title)
837 | s.clearAttachs()
838 |
839 | s.tree.Expand(month.Index())
840 | }
841 |
842 | func (s *myWindow) addDiary2(id, day, title, mtime string, r, c int) {
843 | item := s.model.Item(r, c)
844 | diary := gui.NewQStandardItem2(fmt.Sprintf("%s-%s", day, title))
845 | diary.SetEditable(false)
846 | diary.SetAccessibleText(id)
847 | diary.SetAccessibleDescription("0")
848 | diary.SetToolTip(T("Last Modified:") + mtime)
849 |
850 | item.AppendRow2(diary)
851 |
852 | }
853 |
854 | func (s *myWindow) addYearMonthsFromDb() {
855 | s.setStatusBar(T("Loading Diary List..."))
856 | s.editor.Document().Clear()
857 | s.editor.Document().SetModified(false)
858 | s.editor.SetReadOnly(true)
859 | s.curDiary.zero()
860 |
861 | bridge := NewQmlBridge(s.window)
862 | var wg sync.WaitGroup
863 | bridge.ConnectAddYearMonth(func(ym string) {
864 | s.addYearMonth(ym)
865 | wg.Done()
866 | })
867 | bridge.ConnectAddDiary(func(id, day, title, mtime string, r, c int) {
868 | item := s.model.Item(r, c)
869 | diary := gui.NewQStandardItem2(fmt.Sprintf("%s-%s", day, title))
870 | diary.SetEditable(false)
871 | diary.SetAccessibleText(id)
872 | diary.SetAccessibleDescription("0")
873 | diary.SetToolTip(T("Last Modified:") + mtime)
874 |
875 | item.AppendRow2(diary)
876 | wg.Done()
877 | })
878 | bridge.ConnectSetMonthFlag(func(r, c int) {
879 | item := s.model.TakeItem(r, c)
880 | item.SetAccessibleText("1")
881 | item.SetAccessibleDescription("1")
882 | s.model.SetItem(r, c, item)
883 | s.tree.ResizeColumnToContents(0)
884 | if r < 2 {
885 | s.tree.Expand(item.Index())
886 | }
887 | })
888 | go func() {
889 | yms, err := s.db.GetYearMonths()
890 | if err != nil {
891 | return
892 | }
893 | for i := 0; i < len(yms); i++ {
894 | wg.Add(1)
895 | bridge.AddYearMonth(yms[i])
896 | }
897 | wg.Wait()
898 | pidx := s.tree.RootIndex()
899 | for r := 0; r < s.model.RowCount(pidx); r++ {
900 | if r > 2 {
901 | break
902 | }
903 | c := 0
904 | item := s.model.Item(r, c)
905 | items, err := s.db.GetListFromYearMonth(item.Text())
906 | if err != nil {
907 | log.Println(err)
908 | return
909 | }
910 | for i := 0; i < len(items); i++ {
911 | wg.Add(1)
912 | bridge.AddDiary(strconv.Itoa(items[i].Id), items[i].Day, items[i].Title, items[i].MTime, r, c)
913 | }
914 | wg.Wait()
915 | bridge.SetMonthFlag(r, c)
916 |
917 | }
918 | }()
919 |
920 | return
921 | }
922 |
923 | func (s *myWindow) setMonthFlag(r, c int) {
924 | item := s.model.TakeItem(r, c)
925 | item.SetAccessibleText("1")
926 | item.SetAccessibleDescription("1")
927 | s.model.SetItem(r, c, item)
928 | s.tree.ResizeColumnToContents(0)
929 | if r < 2 {
930 | s.tree.Expand(item.Index())
931 | }
932 | }
933 |
934 | func (s *myWindow) addYearMonth(yearMonth string) *gui.QStandardItem {
935 | idx := core.NewQModelIndex()
936 | p := s.model.Parent(idx)
937 | n := s.model.RowCount(p)
938 | for i := 0; i < n; i++ {
939 | item := s.model.Item(i, 0)
940 | if item.Text() == yearMonth {
941 | return item
942 | }
943 | }
944 | item := gui.NewQStandardItem2(yearMonth)
945 | item.SetColumnCount(1)
946 | item.SetEditable(false)
947 | item.SetAccessibleText("")
948 |
949 | s.model.InsertRow2(0, item)
950 |
951 | return item
952 | }
953 |
954 | func (s *myWindow) selectDiary(idx *core.QModelIndex) {
955 | diary := s.model.ItemFromIndex(idx)
956 | if diary.Pointer() == s.curDiary.Item.Pointer() {
957 | return
958 | }
959 | if len(diary.AccessibleText()) == 0 {
960 | return
961 | }
962 | filename := diary.AccessibleText() + ".dat"
963 | data, err := decodeFromFile(filename, s.key)
964 |
965 | if err != nil {
966 | //log.Println(err)
967 | s.getQText(data)
968 | if s.curDiary.Item != nil {
969 | s.saveCurDiary()
970 | }
971 | s.curDiary.Item = diary
972 | //curDiary.Modified = false
973 | s.curDiary.YearMonth = diary.Parent().Text()
974 | vs := strings.Index(diary.Text(), "-")
975 | s.curDiary.Day = diary.Text()[:vs]
976 | s.setTitle(diary.Text()[vs+1:])
977 |
978 | } else {
979 | if s.curDiary.Item != nil {
980 | s.saveCurDiary()
981 | }
982 | s.getQText(data)
983 | s.curDiary.Item = diary
984 | //curDiary.Modified = false
985 | s.curDiary.YearMonth = diary.Parent().Text()
986 | vs := strings.Index(diary.Text(), "-")
987 | s.curDiary.Day = diary.Text()[:vs]
988 | s.editor.Document().SetHtml(s.document.Html)
989 |
990 | s.showAttachList()
991 | s.editor.Document().SetModified(false)
992 | }
993 | s.tree.SetCurrentIndex(diary.Index())
994 | s.editor.SetReadOnly(false)
995 | s.exportEnc.SetEnabled(true)
996 | s.exportPdf.SetEnabled(true)
997 | s.exportOdt.SetEnabled(true)
998 |
999 | }
1000 |
1001 | func (s *myWindow) popupOnMonth(item *gui.QStandardItem, point *core.QPoint) {
1002 | s.LockEditor()
1003 | menu := widgets.NewQMenu(s.tree)
1004 | refreshItem := menu.AddAction(T("Refresh"))
1005 | refreshItem.ConnectTriggered(func(checked bool) {
1006 | items, err := s.db.GetListFromYearMonth(item.Text())
1007 | idx := item.Index()
1008 | item.RemoveRows(0, item.RowCount())
1009 | r, c := idx.Row(), idx.Column()
1010 | if err != nil {
1011 | log.Println(err)
1012 | return
1013 | }
1014 | for i := 0; i < len(items); i++ {
1015 | id := strconv.Itoa(items[i].Id)
1016 | day := items[i].Day
1017 | title := items[i].Title
1018 | mtime := items[i].MTime
1019 |
1020 | diary := gui.NewQStandardItem2(fmt.Sprintf("%s-%s", day, title))
1021 | diary.SetEditable(false)
1022 | diary.SetAccessibleText(id)
1023 | diary.SetAccessibleDescription("0")
1024 | diary.SetToolTip(T("Last Modified:") + mtime)
1025 |
1026 | item.AppendRow2(diary)
1027 | }
1028 |
1029 | s.setMonthFlag(r, c)
1030 | s.tree.Expand(idx)
1031 | })
1032 | menu.Popup(point, nil)
1033 | }
1034 |
1035 | func (s *myWindow) diaryPopup(idx *core.QModelIndex, e *gui.QMouseEvent) {
1036 | //fmt.Println("popup")
1037 | diary := s.model.ItemFromIndex(s.tree.CurrentIndex())
1038 | switch diary.AccessibleDescription() {
1039 | case "0":
1040 | s.selectDiary(idx)
1041 | case "1":
1042 | s.popupOnMonth(diary, e.GlobalPos())
1043 | return
1044 | default:
1045 | return
1046 | }
1047 |
1048 | menu := widgets.NewQMenu(s.tree)
1049 | delItem := menu.AddAction(T("Delete"))
1050 | delItem.ConnectTriggered(func(checked bool) {
1051 | dlg := widgets.NewQMessageBox(s.window)
1052 | dlg.SetWindowTitle(T("Confirm Delete"))
1053 | dlg.SetText(T("Are you sure?"))
1054 | dlg.SetIcon(widgets.QMessageBox__Question)
1055 | dlg.SetStandardButtons(widgets.QMessageBox__Yes | widgets.QMessageBox__Cancel)
1056 | ret := dlg.Exec()
1057 | if ret == int(widgets.QMessageBox__Yes) {
1058 | id := diary.AccessibleText()
1059 | if len(id) > 0 {
1060 | s.db.RemoveDiary(id)
1061 | }
1062 | s.curDiary.zero()
1063 |
1064 | //curDiary.Modified = false
1065 | p := diary.Parent()
1066 | p.RemoveRow(diary.Row())
1067 |
1068 | }
1069 |
1070 | })
1071 |
1072 | openItem := menu.AddAction(T("Open in New Window"))
1073 | openItem.ConnectTriggered(func(checked bool) {
1074 | id, err := strconv.Atoi(diary.AccessibleText())
1075 | if err != nil {
1076 | s.setStatusBar(err.Error())
1077 | return
1078 | }
1079 | OpenDiaryNewWindow(s, id)
1080 | })
1081 |
1082 | category := menu.AddAction(T("Change Category"))
1083 | category.ConnectTriggered(func(checked bool) {
1084 | id, err := strconv.Atoi(diary.AccessibleText())
1085 | if err != nil {
1086 | s.setStatusBar(err.Error())
1087 | return
1088 | }
1089 | dict := s.db.GetCategories()
1090 | dict1 := make(map[string]int)
1091 | dict1[T("Default")] = 0
1092 | items := []string{T("Default")}
1093 | for k, v := range dict {
1094 | dict1[v] = k
1095 | items = append(items, v)
1096 | }
1097 | var ok bool
1098 | ret := widgets.QInputDialog_GetItem(s.window, T("Category"), T("Select a category"), items, 0, false, &ok, core.Qt__Dialog, 0)
1099 | if ok && dict1[ret] != s.category {
1100 | s.db.UpdateDiaryCategory(id, dict1[ret])
1101 | s.model.RemoveRow(diary.Row(), diary.Index().Parent())
1102 | }
1103 |
1104 | })
1105 |
1106 | menu.QWidget.AddAction(s.exportEnc)
1107 |
1108 | menu.QWidget.AddAction(s.exportPdf)
1109 | menu.QWidget.AddAction(s.exportOdt)
1110 |
1111 | menu.Popup(e.GlobalPos(), nil)
1112 | }
1113 |
1114 | func (s *myWindow) LockEditor() {
1115 | s.saveCurDiary()
1116 | s.curDiary.zero()
1117 | s.editor.Clear()
1118 | s.editor.SetReadOnly(true)
1119 | s.editor.Document().SetModified(false)
1120 | }
1121 |
1122 | func (s *myWindow) onSelectItem(idx *core.QModelIndex) {
1123 | item := s.model.ItemFromIndex(idx)
1124 | loadChildren := func() {
1125 | items, err := s.db.GetListFromYearMonth(item.Text())
1126 | r, c := idx.Row(), idx.Column()
1127 | if err != nil {
1128 | log.Println(err)
1129 | return
1130 | }
1131 | for i := 0; i < len(items); i++ {
1132 | s.addDiary2(strconv.Itoa(items[i].Id), items[i].Day, items[i].Title, items[i].MTime, r, c)
1133 | }
1134 |
1135 | s.setMonthFlag(r, c)
1136 | //s.tree.Expand(idx)
1137 | }
1138 | //fmt.Println("AD:", item.AccessibleDescription())
1139 | switch item.AccessibleDescription() {
1140 | case "0":
1141 | s.selectDiary(idx)
1142 | case "1":
1143 | s.LockEditor()
1144 | if item.HasChildren() == false {
1145 | loadChildren()
1146 | } else {
1147 | s.tree.ClearSelection()
1148 | }
1149 |
1150 | default:
1151 | loadChildren()
1152 | }
1153 | }
1154 |
1155 | func (s *myWindow) CollapseOrExpand(idx *core.QModelIndex) {
1156 | item := s.model.ItemFromIndex(idx)
1157 | if item.AccessibleDescription() == "1" {
1158 | if s.tree.IsExpanded(idx) {
1159 | s.tree.Collapse(idx)
1160 | } else {
1161 | s.tree.Expand(idx)
1162 | }
1163 | }
1164 |
1165 | }
1166 |
1167 | var expandTime = time.Now()
1168 |
1169 | func (s *myWindow) setTreeFuncs() {
1170 |
1171 | s.tree.SetSelectionMode(widgets.QAbstractItemView__SingleSelection)
1172 |
1173 | s.tree.DisconnectMousePressEvent()
1174 | s.tree.DisconnectMouseDoubleClickEvent()
1175 | s.tree.DisconnectMouseReleaseEvent()
1176 |
1177 | s.tree.ConnectMouseReleaseEvent(func(e *gui.QMouseEvent) {
1178 | idx := s.tree.IndexAt(e.Pos())
1179 | if !idx.IsValid() {
1180 | return
1181 | }
1182 | s.tree.SetCurrentIndex(idx)
1183 | switch e.Button() {
1184 | case core.Qt__LeftButton:
1185 | if time.Now().After(expandTime.Add(time.Millisecond * 500)) {
1186 | expandTime = time.Now()
1187 | s.CollapseOrExpand(idx)
1188 | }
1189 |
1190 | case core.Qt__RightButton:
1191 | s.diaryPopup(idx, e)
1192 | }
1193 |
1194 | })
1195 |
1196 | s.tree.ConnectExpanded(func(idx *core.QModelIndex) {
1197 | expandTime = time.Now()
1198 | })
1199 |
1200 | s.tree.ConnectCollapsed(func(idx *core.QModelIndex) {
1201 | expandTime = time.Now()
1202 | })
1203 |
1204 | s.tree.ConnectSelectionChanged(func(selected *core.QItemSelection, deselected *core.QItemSelection) {
1205 | idxes := selected.Indexes()
1206 | if len(idxes) == 1 {
1207 | s.onSelectItem(idxes[0])
1208 | }
1209 |
1210 | })
1211 |
1212 | }
1213 |
1214 | func (s *myWindow) setTitle(v string) {
1215 | s.editor.Clear()
1216 | s.editor.Document().Clear()
1217 | var cfmt = gui.NewQTextCharFormat()
1218 | cfmt.SetFont2(s.app.Font())
1219 | cfmt.SetFontPointSize(18)
1220 | cfmt.SetForeground(gui.NewQBrush3(gui.NewQColor2(core.Qt__blue), core.Qt__SolidPattern))
1221 |
1222 | s.editor.SetFontPointSize(18)
1223 |
1224 | s.editor.SetAlignment(core.Qt__AlignCenter)
1225 |
1226 | cursor := s.editor.TextCursor()
1227 | cursor.SetCharFormat(cfmt)
1228 | cursor.InsertText(v)
1229 |
1230 | cursor.InsertText("\n")
1231 | s.editor.SetTextCursor(cursor)
1232 |
1233 | var afmt = gui.NewQTextCharFormat()
1234 | afmt.SetForeground(gui.NewQBrush3(gui.NewQColor2(core.Qt__black), core.Qt__SolidPattern))
1235 | afmt.SetFont2(s.app.Font())
1236 | afmt.SetFontPointSize(14)
1237 |
1238 | s.editor.SetAlignment(core.Qt__AlignLeft | core.Qt__AlignAbsolute)
1239 |
1240 | cursor.InsertText("\n\t")
1241 | s.mergeFormatOnLineOrSelection(afmt)
1242 | s.editor.SetTextCursor(cursor)
1243 | }
1244 |
1245 | func (s *myWindow) setEditorFuncs() {
1246 |
1247 | s.saveDiary.ConnectTriggered(func(b bool) {
1248 | if s.model != nil {
1249 | s.saveCurDiary()
1250 | } else {
1251 | s.saveDiaryAlone()
1252 | }
1253 |
1254 | })
1255 | if s.model != nil {
1256 | s.editor.ConnectTextChanged(s.OnTextChanged)
1257 | }
1258 |
1259 | s.editor.ConnectContextMenuEvent(func(e *gui.QContextMenuEvent) {
1260 |
1261 | menu := s.editor.CreateStandardContextMenu()
1262 |
1263 | imgUrl := s.getSelectedImage()
1264 | if len(imgUrl) > 0 {
1265 | //addaction export image
1266 | act := menu.AddAction(T("Export Image As..."))
1267 | act.ConnectTriggered(func(b bool) {
1268 | ext := filepath.Ext(imgUrl)
1269 | filter := fmt.Sprintf("%s Image (*%s)", ext, ext)
1270 | filename := widgets.QFileDialog_GetSaveFileName(s.window, T("Export Image As..."), filepath.Base(imgUrl), filter, filter, 0)
1271 | if strings.HasSuffix(filename, ext) == false {
1272 | filename = filename + ext
1273 | }
1274 |
1275 | if len(filename) > 0 {
1276 | ioutil.WriteFile(filename, s.document.Images[imgUrl], 0644)
1277 | }
1278 | })
1279 |
1280 | actScale := menu.AddAction(T("Scale Image"))
1281 | actScale.ConnectTriggered(func(b bool) {
1282 | v := s.editor.Document().Resource(int(gui.QTextDocument__ImageResource), core.NewQUrl3(imgUrl, core.QUrl__TolerantMode))
1283 | img := gui.NewQImageFromPointer(v.ToImage())
1284 | res := s.scaleImage(img)
1285 |
1286 | s.editor.Document().AddResource(int(gui.QTextDocument__ImageResource), core.NewQUrl3(imgUrl, core.QUrl__TolerantMode), res.ToVariant())
1287 | //save data
1288 | ba := core.NewQByteArray()
1289 | iod := core.NewQBuffer2(ba, nil)
1290 | iod.Open(core.QIODevice__WriteOnly)
1291 |
1292 | ok := res.Save2(iod, filepath.Ext(filepath.Base(imgUrl))[1:], -1)
1293 | //fmt.Println(filepath.Ext(filename))
1294 | if ok {
1295 | s.document.Images[imgUrl] = []byte(ba.Data())
1296 | }
1297 | s.editor.Document().SetModified(true)
1298 | })
1299 | }
1300 |
1301 | //add search
1302 | menu.QWidget.AddAction(s.paste)
1303 | menu.QWidget.AddAction(s.search)
1304 | menu.QWidget.AddAction(s.replace)
1305 |
1306 | menu.QWidget.AddAction(s.lineMargin)
1307 |
1308 | menu.Popup(e.GlobalPos(), nil)
1309 | })
1310 |
1311 | s.editor.ConnectMouseReleaseEvent(func(e *gui.QMouseEvent) {
1312 | defer e.Accept()
1313 | if s.format.BF == nil {
1314 | return
1315 | }
1316 | cursor := s.editor.TextCursor()
1317 | if !cursor.HasSelection() {
1318 | cursor.Select(gui.QTextCursor__BlockUnderCursor)
1319 | }
1320 | cursor.SetBlockFormat(s.format.BF)
1321 | cursor.SetCharFormat(s.format.CF)
1322 | s.format.BF = nil
1323 | s.format.CF = nil
1324 | s.fb.SetChecked(false)
1325 | })
1326 |
1327 | onEnterOrReturn := func() {
1328 | cursor := s.editor.TextCursor()
1329 | cursor.Select(gui.QTextCursor__BlockUnderCursor)
1330 | //fmt.Printf("B:%#v\n", cursor.SelectedText())
1331 | txt := cursor.SelectedText()
1332 | r1 := regexp.MustCompile("^(\t+).*$")
1333 | tabs := r1.FindStringSubmatch(strings.TrimLeft(txt, "\u2029"))
1334 | if len(tabs) > 1 {
1335 | s.editor.InsertPlainTextDefault("\n" + tabs[1])
1336 | } else {
1337 | s.editor.InsertPlainTextDefault("\n")
1338 | }
1339 | }
1340 | s.editor.ConnectKeyPressEvent(func(e *gui.QKeyEvent) {
1341 | switch core.Qt__Key(e.Key()) {
1342 | case core.Qt__Key_Enter:
1343 | onEnterOrReturn()
1344 | case core.Qt__Key_Return:
1345 | onEnterOrReturn()
1346 | default:
1347 | s.editor.KeyPressEventDefault(e)
1348 | }
1349 | })
1350 | }
1351 |
1352 | func (s *myWindow) OnTextChanged() {
1353 | if s.curDiary.Item == nil {
1354 | return
1355 | }
1356 |
1357 | disp1 := s.curDiary.Day + "-" + strings.TrimSpace(s.editor.Document().FirstBlock().Text())
1358 |
1359 | disp0 := s.curDiary.Item.Text()
1360 | if disp0 != disp1 {
1361 | p := s.curDiary.Item.Parent()
1362 | pos := s.curDiary.Item.Index()
1363 | s.curDiary.Item = p.TakeChild(pos.Row(), pos.Column())
1364 | s.curDiary.Item.SetText(disp1)
1365 | p.SetChild(pos.Row(), pos.Column(), s.curDiary.Item)
1366 | s.tree.SetCurrentIndex(s.curDiary.Item.Index())
1367 | s.tree.ResizeColumnToContents(0)
1368 | if s.editor.CurrentCharFormat().FontPointSize() != 18 {
1369 | var cfmt = gui.NewQTextCharFormat()
1370 | cfmt.SetFont2(s.app.Font())
1371 | cfmt.SetFontPointSize(18)
1372 | cfmt.SetForeground(gui.NewQBrush3(gui.NewQColor2(core.Qt__blue), core.Qt__SolidPattern))
1373 | s.mergeFormatOnLineOrSelection(cfmt)
1374 | }
1375 |
1376 | s.editor.Document().SetModified(true)
1377 | }
1378 |
1379 | }
1380 |
1381 | //return url or ""
1382 | func (s *myWindow) getSelectedImage() (url string) {
1383 | cursor := s.editor.TextCursor()
1384 | if cursor == nil {
1385 | return ""
1386 | }
1387 |
1388 | fragment := cursor.Selection()
1389 | if fragment.IsEmpty() {
1390 | return ""
1391 | }
1392 |
1393 | html := fragment.ToHtml(core.NewQByteArray2("UTF-8", 5))
1394 | reg1, err := regexp.Compile(`.+`)
1395 | if err != nil {
1396 | log.Println(err)
1397 | return ""
1398 | }
1399 | html = reg1.FindString(html)
1400 |
1401 | reg2, err := regexp.Compile(`
`)
1402 | if err != nil {
1403 | log.Println(err)
1404 | return ""
1405 | }
1406 |
1407 | urls := reg2.FindAllStringSubmatch(html, -1)
1408 |
1409 | if len(urls) != 1 {
1410 | return ""
1411 | }
1412 |
1413 | return urls[0][1]
1414 | }
1415 |
1416 | func (s *myWindow) clearModel() {
1417 | s.model.Clear()
1418 | s.model.SetHorizontalHeaderLabels([]string{T("Diary List") + " - " + s.categoryName})
1419 | }
1420 |
1421 | func (s *myWindow) clearModelFind() {
1422 | s.modelFind.Clear()
1423 | s.modelFind.SetHorizontalHeaderLabels([]string{T("Result List") + " - " + s.categoryName})
1424 | }
1425 |
1426 | func (s *myWindow) lastUser() string {
1427 | v, err := ioutil.ReadFile(path.Join(datDir, "user.txt"))
1428 | if err != nil {
1429 | return ""
1430 | }
1431 | return string(v)
1432 | }
1433 |
1434 | func (s *myWindow) saveLastUser(name string) {
1435 | path1 := path.Join(datDir, "user.txt")
1436 | ioutil.WriteFile(path1, []byte(name), 0644)
1437 | }
1438 |
1439 | func (s *myWindow) mergeFormatOnLineOrSelection(format *gui.QTextCharFormat) *gui.QTextCursor {
1440 | var cursor = s.editor.TextCursor()
1441 | if !cursor.HasSelection() {
1442 | cursor.Select(gui.QTextCursor__LineUnderCursor)
1443 | }
1444 | cursor.MergeCharFormat(format)
1445 | s.editor.MergeCurrentCharFormat(format)
1446 |
1447 | return cursor
1448 | }
1449 |
1450 | func (s *myWindow) rename() {
1451 | dlg := widgets.NewQDialog(s.window, core.Qt__Dialog)
1452 | dlg.SetWindowTitle(T("Rename"))
1453 |
1454 | grid := newGridLayout(dlg)
1455 |
1456 | name := widgets.NewQLabel2(T("New Name:"), dlg, core.Qt__Widget)
1457 | grid.AddWidget2(name, 0, 0, 0)
1458 |
1459 | nameInput := widgets.NewQLineEdit(dlg)
1460 | grid.AddWidget2(nameInput, 0, 1, 0)
1461 |
1462 | okAct := widgets.NewQPushButton2(T("OK"), dlg)
1463 | grid.AddWidget2(okAct, 1, 0, 0)
1464 | cancelAct := widgets.NewQPushButton2(T("Cancel"), dlg)
1465 | grid.AddWidget2(cancelAct, 1, 1, 0)
1466 |
1467 | okAct.ConnectClicked(func(checked bool) {
1468 | ret := widgets.QMessageBox_Question(dlg, T("Confirm"), T("Are you sure?"), widgets.QMessageBox__Yes|widgets.QMessageBox__Cancel, widgets.QMessageBox__Yes)
1469 | //fmt.Println(ret)
1470 | if ret == widgets.QMessageBox__Yes {
1471 | if len(nameInput.Text()) == 0 {
1472 | return
1473 | }
1474 | s.db.Close()
1475 | dstName := path.Join(path.Dir(dataDir), nameInput.Text())
1476 | err := os.Rename(dataDir, dstName)
1477 | if err == nil {
1478 | widgets.QMessageBox_Information(dlg, T("Information"), T("Success,Please login again."), widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
1479 |
1480 | } else {
1481 | widgets.QMessageBox_Information(dlg, T("Information"), T("Fail,Please login again."), widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
1482 | }
1483 | }
1484 | dlg.Hide()
1485 | s.window.Close()
1486 | })
1487 |
1488 | cancelAct.ConnectClicked(func(c bool) {
1489 | dlg.Hide()
1490 | dlg.Destroy(true, true)
1491 | })
1492 |
1493 | dlg.SetLayout(grid)
1494 | dlg.SetModal(true)
1495 | dlg.Show()
1496 | }
1497 |
1498 | func (s *myWindow) updatePwd() {
1499 | dlg := widgets.NewQDialog(s.window, core.Qt__Dialog)
1500 | dlg.SetWindowTitle(T("Modify Password"))
1501 |
1502 | grid := newGridLayout(dlg)
1503 |
1504 | pwd1 := widgets.NewQLabel2(T("Old Password:"), dlg, core.Qt__Widget)
1505 |
1506 | grid.AddWidget2(pwd1, 0, 0, 0)
1507 |
1508 | pwdInput1 := widgets.NewQLineEdit(dlg)
1509 | pwdInput1.SetEchoMode(widgets.QLineEdit__Password)
1510 | pwdInput1.SetPlaceholderText(T("Length Must >= 4"))
1511 | grid.AddWidget2(pwdInput1, 0, 1, 0)
1512 |
1513 | pwd2 := widgets.NewQLabel2(T("New Password:"), dlg, core.Qt__Widget)
1514 |
1515 | grid.AddWidget2(pwd2, 1, 0, 0)
1516 |
1517 | pwdInput2 := widgets.NewQLineEdit(dlg)
1518 | pwdInput2.SetEchoMode(widgets.QLineEdit__Password)
1519 | pwdInput2.SetPlaceholderText(T("Length Must >= 4"))
1520 | grid.AddWidget2(pwdInput2, 1, 1, 0)
1521 |
1522 | pwd3 := widgets.NewQLabel2(T("Confirm Password:"), dlg, core.Qt__Widget)
1523 |
1524 | grid.AddWidget2(pwd3, 2, 0, 0)
1525 |
1526 | pwdInput3 := widgets.NewQLineEdit(dlg)
1527 | pwdInput3.SetEchoMode(widgets.QLineEdit__Password)
1528 | pwdInput3.SetPlaceholderText(T("Length Must >= 4"))
1529 | grid.AddWidget2(pwdInput3, 2, 1, 0)
1530 |
1531 | okAct := widgets.NewQPushButton2(T("OK"), dlg)
1532 | grid.AddWidget2(okAct, 3, 0, 0)
1533 | cancelAct := widgets.NewQPushButton2(T("Cancel"), dlg)
1534 | grid.AddWidget2(cancelAct, 3, 1, 0)
1535 |
1536 | okAct.ConnectClicked(func(checked bool) {
1537 | ret := widgets.QMessageBox_Question(dlg, T("Confirm"), T("Are you sure?"), widgets.QMessageBox__Yes|widgets.QMessageBox__Cancel, widgets.QMessageBox__Yes)
1538 |
1539 | if ret == widgets.QMessageBox__Yes {
1540 | if len(pwdInput3.Text()) < 4 {
1541 | return
1542 | }
1543 | if pwdInput2.Text() != pwdInput3.Text() {
1544 | return
1545 | }
1546 | err := s.db.UpdateRealKeyAndSha40(pwdInput1.Text(), pwdInput2.Text())
1547 | if err == nil {
1548 | widgets.QMessageBox_Information(dlg, T("Information"), T("Success,Please login again."), widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
1549 | dlg.Hide()
1550 | s.window.Close()
1551 | } else {
1552 | widgets.QMessageBox_Information(dlg, T("Information"), T("Fail")+err.Error(), widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
1553 | }
1554 | }
1555 | dlg.Hide()
1556 | dlg.Destroy(true, true)
1557 | })
1558 |
1559 | cancelAct.ConnectClicked(func(c bool) {
1560 | dlg.Hide()
1561 | dlg.Destroy(true, true)
1562 | })
1563 |
1564 | dlg.SetLayout(grid)
1565 | dlg.SetModal(true)
1566 | dlg.Show()
1567 | }
1568 |
1569 | func (s *myWindow) getUsers() []string {
1570 | dir := datDir
1571 | hdir, err := os.Open(dir)
1572 | if err != nil {
1573 | return []string{}
1574 | }
1575 | dirs, err := hdir.Readdir(-1)
1576 | if err != nil {
1577 | return nil
1578 | }
1579 | res := []string{}
1580 | for _, v := range dirs {
1581 | if v.IsDir() {
1582 | res = append(res, v.Name())
1583 | }
1584 | }
1585 | return res
1586 | }
1587 |
1588 | func (s *myWindow) login() {
1589 | dlg := widgets.NewQDialog(s.window, core.Qt__Dialog)
1590 | dlg.SetWindowTitle(T("Login"))
1591 |
1592 | grid := newGridLayout(dlg)
1593 |
1594 | name := widgets.NewQLabel2(T("Name:"), dlg, core.Qt__Widget)
1595 | grid.AddWidget2(name, 0, 0, 0)
1596 |
1597 | nameInput := widgets.NewQComboBox(dlg)
1598 | nameInput.SetEditable(true)
1599 |
1600 | nameInput.AddItems(s.getUsers())
1601 | nameInput.SetCurrentText(s.lastUser())
1602 |
1603 | grid.AddWidget2(nameInput, 0, 1, 0)
1604 |
1605 | pwd := widgets.NewQLabel2(T("Password:"), dlg, core.Qt__Widget)
1606 |
1607 | grid.AddWidget2(pwd, 1, 0, 0)
1608 |
1609 | pwdInput := widgets.NewQLineEdit(dlg)
1610 | pwdInput.SetEchoMode(widgets.QLineEdit__Password)
1611 | pwdInput.SetPlaceholderText(T("Length Must >= 4"))
1612 | if len(nameInput.CurrentText()) > 0 {
1613 | pwdInput.SetFocus2()
1614 | }
1615 | grid.AddWidget2(pwdInput, 1, 1, 0)
1616 |
1617 | btb := newGridLayout2()
1618 |
1619 | okBtn := widgets.NewQPushButton2(T("OK"), dlg)
1620 | btb.AddWidget2(okBtn, 0, 0, 0)
1621 |
1622 | regBtn := widgets.NewQPushButton2(T("Register"), dlg)
1623 | regBtn.SetToolTip(T("Please input the name and password to register before you click this button!"))
1624 | btb.AddWidget2(regBtn, 0, 1, 0)
1625 |
1626 | cancelBtn := widgets.NewQPushButton2(T("Cancel"), dlg)
1627 | btb.AddWidget2(cancelBtn, 0, 2, 0)
1628 |
1629 | grid.AddLayout2(btb, 2, 0, 1, 2, 0)
1630 |
1631 | dlg.SetLayout(grid)
1632 |
1633 | dlg.SetModal(true)
1634 |
1635 | minLen := 4
1636 |
1637 | okBtn.ConnectClicked(func(b bool) {
1638 | var err error
1639 | if len(pwdInput.Text()) < minLen || len(nameInput.CurrentText()) < 1 {
1640 | return
1641 | }
1642 | s.db, err = getMyDb(nameInput.CurrentText())
1643 | if err != nil {
1644 | widgets.QMessageBox_Information(dlg, T("Information"), T("Fail"), widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
1645 | nameInput.SetFocus2()
1646 | return
1647 | }
1648 | s.key, err = s.db.GetRealKey(pwdInput.Text())
1649 | if err != nil {
1650 | widgets.QMessageBox_Information(dlg, T("Information"), T("Fail"), widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
1651 | pwdInput.SetFocus2()
1652 | return
1653 | }
1654 | s.saveLastUser(nameInput.CurrentText())
1655 | s.user = nameInput.CurrentText()
1656 | s.window.SetWindowTitle(nameInput.CurrentText())
1657 | dlg.Hide()
1658 |
1659 | })
1660 |
1661 | regBtn.ConnectClicked(func(b bool) {
1662 | if len(pwdInput.Text()) < minLen || len(nameInput.CurrentText()) < 1 {
1663 | widgets.QMessageBox_Warning(s.window, T("Register"),
1664 | T("Please input the name and password to register before you click this button!"), widgets.QMessageBox__Ok, widgets.QMessageBox__Ok)
1665 | return
1666 | }
1667 | var ok bool
1668 | confirm := widgets.QInputDialog_GetText(dlg, T("Confirm"), T("Name:")+nameInput.CurrentText(), widgets.QLineEdit__Password, "",
1669 | &ok, core.Qt__Dialog, core.Qt__ImhNone)
1670 | if !ok {
1671 | return
1672 | }
1673 | if confirm != pwdInput.Text() {
1674 | s.showMsg(T("Fail"), T("Password Not Match"))
1675 | return
1676 | }
1677 | err := createUserDb(nameInput.CurrentText(), pwdInput.Text())
1678 | if err != nil {
1679 | panic(err)
1680 | }
1681 | s.db, err = getMyDb(nameInput.CurrentText())
1682 | if err != nil {
1683 | panic(err)
1684 | }
1685 | s.key, err = s.db.GetRealKey(pwdInput.Text())
1686 | if err != nil {
1687 | panic(err)
1688 | }
1689 | s.saveLastUser(nameInput.CurrentText())
1690 | s.user = nameInput.CurrentText()
1691 | s.window.SetWindowTitle(nameInput.CurrentText())
1692 | dlg.Hide()
1693 |
1694 | })
1695 |
1696 | cancelBtn.ConnectClicked(func(b bool) {
1697 | dlg.Destroy(true, true)
1698 | s.window.Destroy(true, true)
1699 | s.app.Quit()
1700 | })
1701 |
1702 | dlg.ConnectCloseEvent(func(e *gui.QCloseEvent) {
1703 | dlg.Destroy(true, true)
1704 | s.window.Destroy(true, true)
1705 | s.app.Quit()
1706 | })
1707 |
1708 | dlg.ConnectHideEvent(func(e *gui.QHideEvent) {
1709 | //log.Println("load list")
1710 | s.loadUserCategorys()
1711 | dlg.Destroy(true, true)
1712 | })
1713 |
1714 | dlg.Show()
1715 |
1716 | dlg.Move2(0, 0)
1717 | dlg.Move2(s.window.X()+(s.window.Width()-dlg.Width())/2, s.window.Y()+(s.window.Height()-dlg.Width())/2)
1718 | }
1719 |
1720 | func (s *myWindow) exportEncryptedDiary() {
1721 | //export private format .egf (encrypted gob file)
1722 | idx := s.tree.CurrentIndex()
1723 | if idx == nil {
1724 | return
1725 | }
1726 | item := s.model.ItemFromIndex(idx)
1727 | if item == nil {
1728 | return
1729 | }
1730 | dlg := widgets.NewQDialog(s.window, core.Qt__Dialog)
1731 | dlg.SetWindowTitle(T("Export Encrypted Diary") + "...")
1732 |
1733 | grid := newGridLayout2()
1734 |
1735 | name := widgets.NewQLabel2(T("FileName:"), dlg, core.Qt__Widget)
1736 | grid.AddWidget2(name, 0, 0, 0)
1737 |
1738 | nameInput := widgets.NewQLineEdit(dlg)
1739 | grid.AddWidget2(nameInput, 0, 1, 0)
1740 | nameInput.SetPlaceholderText(T("Double click to select..."))
1741 | nameInput.SetMinimumWidth(240)
1742 |
1743 | passwd := widgets.NewQLabel2(T("Password:"), dlg, core.Qt__Widget)
1744 | grid.AddWidget2(passwd, 1, 0, 0)
1745 |
1746 | passwdInput := widgets.NewQLineEdit(dlg)
1747 | grid.AddWidget2(passwdInput, 1, 1, 0)
1748 | passwdInput.SetEchoMode(widgets.QLineEdit__Password)
1749 | passwdInput.SetPlaceholderText(T("Length Must >= 4"))
1750 |
1751 | hbox := widgets.NewQHBoxLayout()
1752 |
1753 | okBtn := widgets.NewQPushButton2(T("OK"), dlg)
1754 | hbox.AddWidget(okBtn, 1, 0)
1755 |
1756 | cancelBtn := widgets.NewQPushButton2(T("Cancel"), dlg)
1757 | hbox.AddWidget(cancelBtn, 1, 0)
1758 |
1759 | cancelBtn.ConnectClicked(func(b bool) {
1760 | dlg.Hide()
1761 | dlg.Destroy(true, true)
1762 | })
1763 |
1764 | nameInput.ConnectMouseDoubleClickEvent(func(e *gui.QMouseEvent) {
1765 | filename := widgets.QFileDialog_GetSaveFileName(dlg, T("Export To..."), ".", "Encrypted Diary (*.egf)", "Encrypted Diary (*.egf)", 0)
1766 | if strings.HasSuffix(strings.ToLower(filename), ".egf") {
1767 | nameInput.SetText(filename)
1768 | } else {
1769 | nameInput.SetText(filename + ".egf")
1770 | }
1771 |
1772 | })
1773 |
1774 | okBtn.ConnectClicked(func(b bool) {
1775 | if len(passwdInput.Text()) < 4 {
1776 | return
1777 | }
1778 |
1779 | filename := strings.TrimSpace(nameInput.Text())
1780 | if len(filename) == 0 {
1781 | return
1782 | }
1783 | key := getSha4(passwdInput.Text())
1784 | data := s.getRichText()
1785 | err := encodeToPathName(data, filename, key)
1786 | if err != nil {
1787 | s.showMsg(T("Error"), err.Error())
1788 | } else {
1789 | s.showMsg(T("Success"), filename)
1790 | }
1791 | dlg.Hide()
1792 | dlg.Destroy(true, true)
1793 | })
1794 |
1795 | grid.AddLayout2(hbox, 2, 0, 1, 2, 0)
1796 |
1797 | dlg.SetLayout(grid)
1798 | dlg.Exec()
1799 | }
1800 |
1801 | func (s *myWindow) importEncryptedDiary() {
1802 | //import private format .egf (encrypted gob file)
1803 |
1804 | dlg := widgets.NewQDialog(s.window, core.Qt__Dialog)
1805 | dlg.SetWindowTitle(T("Import Encrypted Diary") + "...")
1806 |
1807 | grid := newGridLayout2()
1808 |
1809 | name := widgets.NewQLabel2(T("FileName:"), dlg, core.Qt__Widget)
1810 | grid.AddWidget2(name, 0, 0, 0)
1811 |
1812 | nameInput := widgets.NewQLineEdit(dlg)
1813 | grid.AddWidget2(nameInput, 0, 1, 0)
1814 | nameInput.SetPlaceholderText(T("Double click to select..."))
1815 | nameInput.SetMinimumWidth(240)
1816 |
1817 | passwd := widgets.NewQLabel2(T("Password:"), dlg, core.Qt__Widget)
1818 | grid.AddWidget2(passwd, 1, 0, 0)
1819 |
1820 | passwdInput := widgets.NewQLineEdit(dlg)
1821 | grid.AddWidget2(passwdInput, 1, 1, 0)
1822 | passwdInput.SetEchoMode(widgets.QLineEdit__Password)
1823 |
1824 | hbox := widgets.NewQHBoxLayout()
1825 |
1826 | okBtn := widgets.NewQPushButton2(T("OK"), dlg)
1827 | hbox.AddWidget(okBtn, 1, 0)
1828 |
1829 | cancelBtn := widgets.NewQPushButton2(T("Cancel"), dlg)
1830 | hbox.AddWidget(cancelBtn, 1, 0)
1831 |
1832 | cancelBtn.ConnectClicked(func(b bool) {
1833 | dlg.Hide()
1834 | dlg.Destroy(true, true)
1835 | })
1836 |
1837 | nameInput.ConnectMouseDoubleClickEvent(func(e *gui.QMouseEvent) {
1838 | filename := widgets.QFileDialog_GetOpenFileName(dlg, T("Import File..."), ".", "Encrypted Diary .egf (*.egf)", "Encrypted Diary .egf (*.egf)", widgets.QFileDialog__ReadOnly)
1839 | nameInput.SetText(filename)
1840 |
1841 | })
1842 |
1843 | okBtn.ConnectClicked(func(b bool) {
1844 | if len(passwdInput.Text()) < 4 {
1845 | return
1846 | }
1847 |
1848 | filename := strings.TrimSpace(nameInput.Text())
1849 | if len(filename) == 0 {
1850 | return
1851 | }
1852 | key := getSha4(passwdInput.Text())
1853 | data, err := decodeFromPathName(filename, key)
1854 | if err != nil {
1855 | return
1856 | }
1857 | now := time.Now()
1858 | s.addDiary(now.Format("2006-01"), now.Format("02"), "")
1859 |
1860 | _, err = s.getQText(data)
1861 |
1862 | if err == nil {
1863 |
1864 | s.editor.SetHtml(s.document.Html)
1865 | s.showAttachList()
1866 | //curDiary.Modified = true
1867 | s.editor.Document().SetModified(true)
1868 |
1869 | s.saveCurDiary()
1870 | } else {
1871 | s.showMsg(T("Error"), err.Error())
1872 | }
1873 |
1874 | dlg.Hide()
1875 | dlg.Destroy(true, true)
1876 | })
1877 |
1878 | grid.AddLayout2(hbox, 2, 0, 1, 2, 0)
1879 |
1880 | dlg.SetLayout(grid)
1881 | dlg.Exec()
1882 | }
1883 |
1884 | func (s *myWindow) exportAsPdf() {
1885 | defaultName := strings.TrimSpace(s.editor.Document().FirstBlock().Text()) + ".pdf"
1886 | filename := widgets.QFileDialog_GetSaveFileName(s.window, T("Export To..."), defaultName, "PDF (*.pdf)", "Encrypted Diary (*.pdf)", 0)
1887 | if !strings.HasSuffix(strings.ToLower(filename), ".pdf") {
1888 | filename = filename + ".pdf"
1889 | }
1890 | var (
1891 | fileName = filename
1892 | printer = printsupport.NewQPrinter(printsupport.QPrinter__HighResolution)
1893 | )
1894 | printer.SetOutputFormat(printsupport.QPrinter__PdfFormat)
1895 |
1896 | printer.SetOutputFileName(fileName)
1897 | s.editor.Document().Print(printer)
1898 | s.setStatusBar(fmt.Sprintf("Exported %v", core.QDir_ToNativeSeparators(fileName)))
1899 | }
1900 |
1901 | func (s *myWindow) exportAsOdt() {
1902 | defaultName := strings.TrimSpace(s.editor.Document().FirstBlock().Text()) + ".odt"
1903 | filename := widgets.QFileDialog_GetSaveFileName(s.window, T("Export To..."), defaultName, "OpenDocument (*.odt)", "OpenDocument (*.odt)", 0)
1904 | if !strings.HasSuffix(strings.ToLower(filename), ".odt") {
1905 | filename = filename + ".odt"
1906 | }
1907 | var (
1908 | writer = gui.NewQTextDocumentWriter3(filename, core.NewQByteArray())
1909 | success = writer.Write(s.editor.Document())
1910 | )
1911 | if success {
1912 | s.setStatusBar(fmt.Sprintf("Exported %v", core.QDir_ToNativeSeparators(filename)))
1913 | } else {
1914 | s.setStatusBar("Exported Fail.")
1915 | }
1916 | }
1917 |
1918 | func (s *myWindow) showMsg(title, msg string) {
1919 | dlg := widgets.NewQMessageBox(s.window)
1920 | dlg.SetWindowTitle(title)
1921 | dlg.SetText(msg)
1922 |
1923 | dlg.Exec()
1924 | dlg.Destroy(true, true)
1925 | }
1926 |
1927 | func (s *myWindow) showPay() {
1928 | dlg := widgets.NewQDialog(s.window, core.Qt__Dialog)
1929 | dlg.SetWindowTitle(T("Pay Voluntary!"))
1930 | box := widgets.NewQHBoxLayout2(dlg)
1931 | height := 512
1932 |
1933 | labelAli := widgets.NewQLabel(dlg, core.Qt__Widget)
1934 | img1 := newQPixMap(":/qml/pay/alipay.png", "png", core.Qt__NoFormatConversion)
1935 | img1 = img1.ScaledToHeight(height, core.Qt__SmoothTransformation)
1936 | labelAli.SetPixmap(img1)
1937 | box.AddWidget(labelAli, 1, 0)
1938 |
1939 | labelWx := widgets.NewQLabel(dlg, core.Qt__Widget)
1940 | img2 := newQPixMap(":/qml/pay/wxpay.png", "png", core.Qt__NoFormatConversion)
1941 | img2 = img2.ScaledToHeight(height, core.Qt__SmoothTransformation)
1942 | labelWx.SetPixmap(img2)
1943 | box.AddWidget(labelWx, 1, 0)
1944 |
1945 | dlg.SetLayout(box)
1946 |
1947 | dlg.Exec()
1948 | dlg.Destroy(true, true)
1949 | }
1950 |
1951 | func main() {
1952 | //defer gettext.SaveLog()
1953 | app := widgets.NewQApplication(len(os.Args), os.Args)
1954 | window := new(myWindow)
1955 | window.Create(app)
1956 | app.Exec()
1957 | }
1958 |
--------------------------------------------------------------------------------
/linux_launcher/.gitignore:
--------------------------------------------------------------------------------
1 | linux_launcher
2 | setup
3 |
--------------------------------------------------------------------------------
/linux_launcher/setup.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "log"
6 | "os"
7 | "path/filepath"
8 | "text/template"
9 | )
10 |
11 | func copyFile(src, dst string, mode os.FileMode) error {
12 | fp1, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, mode)
13 | if err != nil {
14 | return err
15 | }
16 | defer fp1.Close()
17 | fp2, err := os.Open(src)
18 | if err != nil {
19 | return err
20 | }
21 | defer fp2.Close()
22 | _, err = io.Copy(fp1, fp2)
23 | return err
24 | }
25 |
26 | func makeLauncher(launcherName, fileName, iconName string) {
27 | exe1, _ := os.Executable()
28 | appdir := filepath.Dir(exe1)
29 | appname := filepath.Join(appdir, fileName)
30 | home, _ := os.UserHomeDir()
31 | os.MkdirAll(filepath.Join(home, ".local", "share", "applications"), os.ModePerm)
32 | dst := filepath.Join(home, ".local", "share", "applications", launcherName+".desktop")
33 | iconSrc := filepath.Join(appdir, iconName)
34 |
35 | data := struct {
36 | LauncherName string
37 | Name string
38 | Icon string
39 | }{launcherName, appname, iconSrc}
40 |
41 | tpl := `[Desktop Entry]
42 | Name={{.LauncherName}}
43 | Comment={{.LauncherName}}
44 | Exec="{{.Name}}" %U
45 | Icon={{.Icon}}
46 | Terminal=false
47 | Type=Application
48 | StartupNotify=true
49 | Categories=Office;
50 |
51 | `
52 | t := template.New("")
53 | t.Parse(tpl)
54 | fp, err := os.Create(dst)
55 | if err != nil {
56 | log.Println(err)
57 | return
58 | }
59 | defer fp.Close()
60 | t.Execute(fp, data)
61 | }
62 |
63 | func main() {
64 | makeLauncher("Secret-Diary", "secret-diary", "Sd.png")
65 | }
66 |
--------------------------------------------------------------------------------
/locale/zh_CN/LC_MESSAGES/sdiary.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/locale/zh_CN/LC_MESSAGES/sdiary.mo
--------------------------------------------------------------------------------
/locale/zh_CN/LC_MESSAGES/sdiary.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2019-01-05 19:03+0800\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | msgid "Name:"
21 | msgstr "用户名:"
22 |
23 | msgid "Manage"
24 | msgstr "管理"
25 |
26 | msgid "Help"
27 | msgstr "帮助"
28 |
29 | msgid ""
30 | "Copy Right: Fu Huizhong \n"
31 | "Security Diary "
32 | msgstr ""
33 | "版权所有:Fu Huizhong \n"
34 | "安全日记本 "
35 |
36 | msgid "Crypto with AES256"
37 | msgstr "使用256位AES加密算法"
38 |
39 | msgid "About"
40 | msgstr "关于"
41 |
42 | msgid "Rename"
43 | msgstr "改名"
44 |
45 | msgid "Length Must >= 4"
46 | msgstr "最小长度: 4"
47 |
48 | msgid "Confirm Password:"
49 | msgstr "确认密码:"
50 |
51 | msgid "Your DATA storge path is '"
52 | msgstr "你的数据保存目录:"
53 |
54 | msgid "Password"
55 | msgstr "密码"
56 |
57 | msgid "Password:"
58 | msgstr "密码:"
59 |
60 | msgid "ModifyPassword"
61 | msgstr "修改密码"
62 |
63 | msgid "New Password:"
64 | msgstr "新密码"
65 |
66 | msgid "New"
67 | msgstr "新建"
68 |
69 | msgid "Backup & Delete"
70 | msgstr "备份和删除"
71 |
72 | msgid "Register"
73 | msgstr "注册"
74 |
75 | msgid "Modify Password"
76 | msgstr "修改密码"
77 |
78 | msgid "Old Password:"
79 | msgstr "旧密码"
80 |
81 | msgid "Save"
82 | msgstr "保存"
83 |
84 | msgid "Secret Diary"
85 | msgstr "安全日记本"
86 |
87 | msgid "Loading Diary List..."
88 | msgstr "加载日记..."
89 |
90 | msgid "New Name:"
91 | msgstr "新用户名:"
92 |
93 | msgid "Diary List"
94 | msgstr "日记列表"
95 |
96 | msgid "Last Modified:"
97 | msgstr "最后修改时间:"
98 |
99 | msgid "Standard"
100 | msgstr "正文"
101 |
102 | msgid ""
103 | "There is no way to recover password. \n"
104 | "Please write it on paper!"
105 | msgstr ""
106 | "没有任何捷径恢复密码。\n"
107 | "建议把密码记在纸上。"
108 |
109 | msgid "No Diary Saved"
110 | msgstr "无需保存"
111 |
112 | msgid "OK"
113 | msgstr "确定"
114 |
115 | msgid "UserName"
116 | msgstr "用户名"
117 |
118 | msgid "Cancel"
119 | msgstr "取消"
120 |
121 | msgid "Login"
122 | msgstr "登陆"
123 |
124 | msgid "Confirm"
125 | msgstr "确认密码"
126 |
127 | msgid "Bold"
128 | msgstr "粗体"
129 |
130 | msgid "Italic"
131 | msgstr "斜体"
132 |
133 | msgid "Underline"
134 | msgstr "下划线"
135 |
136 | msgid "StrikeOut"
137 | msgstr "删除线"
138 |
139 | msgid "List (Disc)"
140 | msgstr "列表(实心点)"
141 |
142 | msgid "List (Circle)"
143 | msgstr "列表(空心圆)"
144 |
145 | msgid "List (Square)"
146 | msgstr "列表(方块)"
147 |
148 | msgid "List (Decimal)"
149 | msgstr "列表(数字)"
150 |
151 | msgid "List (Alpha lower)"
152 | msgstr "列表(小写字母)"
153 |
154 | msgid "List (Alpha upper)"
155 | msgstr "列表(大写字母)"
156 |
157 | msgid "List (Roman lower)"
158 | msgstr "列表(小写罗马数字)"
159 |
160 | msgid "List (Roman upper)"
161 | msgstr "列表(大写罗马数字)"
162 |
163 | msgid "Header 2"
164 | msgstr "二级标题"
165 |
166 | msgid "Header 3"
167 | msgstr "三级标题"
168 |
169 | msgid "Image"
170 | msgstr "图片"
171 |
172 | msgid "Insert Image"
173 | msgstr "插入图片"
174 |
175 | msgid "Header 1"
176 | msgstr "一级标题"
177 |
178 | msgid "Table"
179 | msgstr "表格"
180 |
181 | msgid "Insert Table"
182 | msgstr "插入表格"
183 |
184 | msgid "Table Rows and Columns"
185 | msgstr "表格行数和列数"
186 |
187 | msgid "Rows:"
188 | msgstr "行数"
189 |
190 | msgid "Columns:"
191 | msgstr "列数"
192 |
193 | msgid "Text Color..."
194 | msgstr "文字颜色"
195 |
196 | msgid "Background Color..."
197 | msgstr "背景颜色"
198 |
199 | msgid "New Diary"
200 | msgstr "新日记"
201 |
202 | msgid "Font"
203 | msgstr "字体"
204 |
205 | msgid "CurrentFont"
206 | msgstr "当前字体"
207 |
208 | msgid "Insert Row"
209 | msgstr "插入行"
210 |
211 | msgid "Append Row"
212 | msgstr "追加行"
213 |
214 | msgid "Insert Column"
215 | msgstr "插入列"
216 |
217 | msgid "Append Column"
218 | msgstr "追加列"
219 |
220 | msgid "Remove Row"
221 | msgstr "删除行"
222 |
223 | msgid "Remove Column"
224 | msgstr "删除列"
225 |
226 | msgid "Are you sure?"
227 | msgstr "是否确定?"
228 |
229 | msgid "Export Image As..."
230 | msgstr "导出图片..."
231 |
232 | msgid "Delete"
233 | msgstr "删除"
234 |
235 | msgid "Export To..."
236 | msgstr "导出为..."
237 |
238 | msgid "Import File..."
239 | msgstr "导入文件..."
240 |
241 | msgid "Export Encrypted Diary"
242 | msgstr "导出加密日记..."
243 |
244 | msgid "Import Encrypted Diary"
245 | msgstr "导入加密日记..."
246 |
247 | msgid "Export PDF..."
248 | msgstr "导出为PDF..."
249 |
250 | msgid "FileName:"
251 | msgstr "文件:"
252 |
253 | msgid "File"
254 | msgstr "文件"
255 |
256 | msgid "Clear Line Format..."
257 | msgstr "清除本行格式"
258 |
259 | msgid "Save File"
260 | msgstr "保存文件"
261 |
262 | msgid "Select Attachments"
263 | msgstr "选择附件"
264 |
265 | msgid "Total"
266 | msgstr "总数"
267 |
268 | msgid "Export As..."
269 | msgstr "导出"
270 |
271 | msgid "Attach..."
272 | msgstr "插入附件"
273 |
274 | msgid "Remove"
275 | msgstr "删除"
276 |
277 | msgid "Append Attachments"
278 | msgstr "添加附件"
279 |
280 | msgid "Do you want to save the document?"
281 | msgstr "是否保存当前文档?"
282 |
283 | msgid "Please input the name and password to register before you click this button!"
284 | msgstr "点击本按钮前,请先在上面输入你想注册的用户名和密码!"
285 |
286 | msgid "Search"
287 | msgstr "搜索"
288 |
289 | msgid "Open in New Window"
290 | msgstr "在新窗口中打开"
291 |
292 | msgid "Double Click to Open. "
293 | msgstr "双击打开。"
294 |
295 | msgid "Result List"
296 | msgstr "搜索结果"
297 |
298 | msgid "Support Author"
299 | msgstr "支持作者"
300 |
301 | msgid "You can backup and delete the directory yourself."
302 | msgstr "你可以自行备份或删除该目录。"
303 |
304 | msgid "Pay Voluntary!"
305 | msgstr "支付多少全凭自愿!"
306 |
307 | msgid "Confirm Delete"
308 | msgstr "确认删除"
309 |
310 | msgid "Import All"
311 | msgstr "导入备份"
312 |
313 | msgid "Export All"
314 | msgstr "备份全部"
315 |
316 | msgid "Select export directory"
317 | msgstr "选择导出目录"
318 |
319 | msgid "Select a bakup file"
320 | msgstr "选择备份文件"
321 |
322 | msgid "Text Replace"
323 | msgstr "文本替换"
324 |
325 | msgid "Text Search"
326 | msgstr "文本搜索"
327 |
328 | msgid "Last"
329 | msgstr "上一个"
330 |
331 | msgid "Next"
332 | msgstr "下一个"
333 |
334 | msgid "All"
335 | msgstr "全部"
336 |
337 | msgid "Words to search."
338 | msgstr "查找文字"
339 |
340 | msgid "Old Text."
341 | msgstr "旧文字"
342 |
343 | msgid "New Text."
344 | msgstr "新文字"
345 |
346 | msgid "Edit"
347 | msgstr "编辑"
348 |
349 | msgid "Replace"
350 | msgstr "替换"
351 |
352 | msgid "Paste Text"
353 | msgstr "粘帖纯文本"
354 |
355 | msgid "Rename Category"
356 | msgstr "重命名类别"
357 |
358 | msgid "New Category Name"
359 | msgstr "新的类别名称"
360 |
361 | msgid "Error"
362 | msgstr "错误"
363 |
364 | msgid "Are you sure? All files in this category will move to default category."
365 | msgstr "是否确定?本类别的所有文件将转移到默认类别中。"
366 |
367 | msgid "New Category"
368 | msgstr "新建类别"
369 |
370 | msgid "Remove Category"
371 | msgstr "删除类别"
372 |
373 | msgid "Default"
374 | msgstr "默认"
375 |
376 | msgid "Category"
377 | msgstr "分类"
378 |
379 | msgid "Categories"
380 | msgstr "分类"
381 |
382 | msgid "Can not remove default category."
383 | msgstr "不能删除默认类别"
384 |
385 | msgid "Change Category"
386 | msgstr "修改类别"
387 |
388 | msgid "Select a category"
389 | msgstr "选择一个分类"
390 |
391 | msgid "Add To Menu"
392 | msgstr "加入启动菜单"
393 |
394 | msgid "You can start this program from 'Start'->'Office'"
395 | msgstr "你现在可以从 '开始'->'办公' 启动本程序"
396 |
397 | msgid "Increase Ident"
398 | msgstr "增加缩进量"
399 |
400 | msgid "Decrease Ident"
401 | msgstr "减少缩进量"
402 |
403 | msgid "Scale Image"
404 | msgstr "缩放图片"
405 |
406 | msgid "Scale Image Size"
407 | msgstr "缩放图片尺寸"
408 |
409 | msgid "Width"
410 | msgstr "宽"
411 |
412 | msgid "Height"
413 | msgstr "高"
414 |
415 | msgid "Refresh"
416 | msgstr "刷新"
417 |
418 | msgid "Top Margin"
419 | msgstr "段前间距"
420 |
421 | msgid "Format Brush"
422 | msgstr "格式刷"
423 |
424 | msgid "Create Desktop Shortcut"
425 | msgstr "创建桌面快捷方式"
426 |
427 | msgid "Export Odt..."
428 | msgstr "导出为Odt格式..."
429 |
430 | msgid "Update"
431 | msgstr "检查更新"
432 |
433 | msgid "This is the Newest Version!"
434 | msgstr "这是最新版本!"
435 |
436 | msgid "Current:"
437 | msgstr "当前版本:"
438 |
439 | msgid "Newest:"
440 | msgstr "最新版本:"
441 |
442 | msgid "I will open the page on github website after you click button 'OK'!"
443 | msgstr "当你点击'OK'按钮后,我将打开位于github网站上的下载页面!"
444 |
445 | msgid "Change Storage Dir"
446 | msgstr "改变存储目录"
447 |
448 | msgid "Select Storage Directory"
449 | msgstr "选择存储目录"
450 |
451 | msgid "Quit program"
452 | msgstr "即将退出程序"
453 |
454 | msgid "The program will terminate now."
455 | msgstr "本程序即将退出。"
456 |
457 | msgid "How to move data?"
458 | msgstr "如何转移你的数据?"
459 |
460 | msgid "Move the directories to the new directory from "
461 | msgstr "把该目录中的子目录移动到新的数据目录:"
462 |
--------------------------------------------------------------------------------
/pay/alipay.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/pay/alipay.jpg
--------------------------------------------------------------------------------
/pay/wxpay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/pay/wxpay.png
--------------------------------------------------------------------------------
/pkg/linux.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ~/go/bin/qtdeploy build
4 |
5 | cd deploy
6 | mkdir -p secret-diary-linux_amd64
7 | cp -ru ../locale secret-diary-linux_amd64/
8 | cp -u ../Sd.png secret-diary-linux_amd64/
9 | cp -ru linux/* secret-diary-linux_amd64/
10 | ln -sf ~/go/bin/qtdeploy/secret-diary-linux_amd64/secret-diary secret-diary
11 | rm secret-diary-linux_amd64.zip
12 | zip -r9 secret-diary-linux_amd64.zip secret-diary-linux_amd64/
13 |
14 | sh mkdeb.sh
15 |
--------------------------------------------------------------------------------
/pkg/mkdeb.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | nano secret-diary-deb/DEBIAN/control
3 | cp -ru secret-diary-linux_amd64/* secret-diary-deb/opt/secret-diary/
4 | VERSION="$(fgrep Version secret-diary-deb/DEBIAN/control |awk '{print $2}')"
5 | DEBNAME="secret-diary-${VERSION}_amd64.deb"
6 | dpkg -b secret-diary-deb $DEBNAME
7 | cp $DEBNAME ~/src/pkg2appimage/debs/secret-diary_amd64.deb
8 | cd ~/src/pkg2appimage/
9 | ./pkg2appimage recipes/secret-diary.yml
10 | mv out/Secret-Diary-amd64.deb.glibc2.14-x86_64.AppImage ~/go/src/secret-diary/deploy/Secret-Diary-${VERSION}-x86_64.AppImage
11 |
--------------------------------------------------------------------------------
/pkg/secret-diary.yml:
--------------------------------------------------------------------------------
1 | app: secret-diary
2 | binpatch: true
3 |
4 | ingredients:
5 | dist: stretch
6 | packages:
7 | - secret-diary
8 | sources:
9 | - deb http://mirrors.163.com/debian/ stretch main contrib
10 | debs:
11 | - /home/fuhz/src/pkg2appimage/debs/secret-diary_amd64.deb
12 |
13 | script:
14 | - cp ./opt/secret-diary/Sd.png .
15 | - cp ./usr/local/share/applications/Secret-Diary.desktop .
16 | - sed -i "s/\/opt\/secret-diary\///g" Secret-Diary.desktop
17 | - mv ./opt/secret-diary/* ./usr/bin/
18 |
--------------------------------------------------------------------------------
/pkg/shortcut.vbs:
--------------------------------------------------------------------------------
1 | Set WshShell = WScript.CreateObject("WScript.Shell")
2 | strDesktop = WshShell.SpecialFolders("Desktop")
3 |
4 | If IsExitAFile(strDesktop & "\secret-diary.lnk") Then
5 | rem "exists"
6 | Else
7 | CreateShortcut(strDesktop & "\secret-diary.lnk")
8 | End If
9 |
10 | Function IsExitAFile(filespec)
11 | Dim fso
12 | Set fso=CreateObject("Scripting.FileSystemObject")
13 | If fso.fileExists(filespec) Then
14 | IsExitAFile=True
15 | Else IsExitAFile=False
16 | End If
17 | End Function
18 |
19 | Sub CreateShortcut(filespec)
20 | Set WshShell = WScript.CreateObject("WScript.Shell")
21 | Set oShellLink = WshShell.CreateShortcut(filespec)
22 | target = Wscript.Arguments.Named("target")
23 | oShellLink.TargetPath =target
24 | oShellLink.WorkingDirectory = mid(target,1,instrrev(target,"\"))
25 | oShellLink.WindowStyle = 3
26 | oShellLink.Save
27 | End Sub
28 |
--------------------------------------------------------------------------------
/pkg/win32.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cp deploy/win32.syso .
4 | ~/go/bin/qtdeploy -docker build windows_32_static
5 | rm win32.syso
6 |
7 | cd deploy
8 | mkdir -p secret-diary-win32
9 | cp -ru ../locale secret-diary-win32/
10 | cp -u ../Sd.ico secret-diary-win32/
11 | cp -ru windows/* secret-diary-win32/
12 | rm secret-diary-win32.zip
13 | zip -r9 secret-diary-win32.zip secret-diary-win32/
--------------------------------------------------------------------------------
/qml/icons/B.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/B.png
--------------------------------------------------------------------------------
/qml/icons/I.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/I.png
--------------------------------------------------------------------------------
/qml/icons/S.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/S.png
--------------------------------------------------------------------------------
/qml/icons/Sd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/Sd.png
--------------------------------------------------------------------------------
/qml/icons/U.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/U.png
--------------------------------------------------------------------------------
/qml/icons/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/bg.png
--------------------------------------------------------------------------------
/qml/icons/brush.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/brush.png
--------------------------------------------------------------------------------
/qml/icons/document-new.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/document-new.jpg
--------------------------------------------------------------------------------
/qml/icons/document-save.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/document-save.jpg
--------------------------------------------------------------------------------
/qml/icons/draw-eraser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/draw-eraser.png
--------------------------------------------------------------------------------
/qml/icons/fg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/fg.png
--------------------------------------------------------------------------------
/qml/icons/h1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/h1.png
--------------------------------------------------------------------------------
/qml/icons/h2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/h2.png
--------------------------------------------------------------------------------
/qml/icons/h3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/h3.png
--------------------------------------------------------------------------------
/qml/icons/left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/left.png
--------------------------------------------------------------------------------
/qml/icons/right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/right.png
--------------------------------------------------------------------------------
/qml/icons/std.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/std.png
--------------------------------------------------------------------------------
/qml/icons/textcenter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/textcenter.png
--------------------------------------------------------------------------------
/qml/icons/textjustify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/textjustify.png
--------------------------------------------------------------------------------
/qml/icons/textleft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/textleft.png
--------------------------------------------------------------------------------
/qml/icons/textright.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/icons/textright.png
--------------------------------------------------------------------------------
/qml/pay/alipay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/pay/alipay.png
--------------------------------------------------------------------------------
/qml/pay/wxpay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/qml/pay/wxpay.png
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/screenshot.png
--------------------------------------------------------------------------------
/secret-diary.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/secret-diary.x64.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/shortcut.vbs:
--------------------------------------------------------------------------------
1 | Set WshShell = WScript.CreateObject("WScript.Shell")
2 | strDesktop = WshShell.SpecialFolders("Desktop")
3 |
4 | If IsExitAFile(strDesktop & "\secret-diary.lnk") Then
5 | rem "exists"
6 | Else
7 | CreateShortcut(strDesktop & "\secret-diary.lnk")
8 | End If
9 |
10 | Function IsExitAFile(filespec)
11 | Dim fso
12 | Set fso=CreateObject("Scripting.FileSystemObject")
13 | If fso.fileExists(filespec) Then
14 | IsExitAFile=True
15 | Else IsExitAFile=False
16 | End If
17 | End Function
18 |
19 | Sub CreateShortcut(filespec)
20 | Set WshShell = WScript.CreateObject("WScript.Shell")
21 | Set oShellLink = WshShell.CreateShortcut(filespec)
22 | target = Wscript.Arguments.Named("target")
23 | oShellLink.TargetPath =target
24 | oShellLink.WorkingDirectory = mid(target,1,instrrev(target,"\"))
25 | oShellLink.WindowStyle = 3
26 | oShellLink.Save
27 | End Sub
28 |
--------------------------------------------------------------------------------
/shortcut_linux.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "errors"
7 | "io"
8 | "log"
9 | "os"
10 | "path/filepath"
11 | "text/template"
12 | )
13 |
14 | func copyFile(src, dst string) error {
15 | fp1, err := os.Create(dst)
16 | if err != nil {
17 | return err
18 | }
19 | defer fp1.Close()
20 | fp2, err := os.Open(src)
21 | if err != nil {
22 | return err
23 | }
24 | defer fp2.Close()
25 | _, err = io.Copy(fp1, fp2)
26 | log.Println("copy", src, dst)
27 | return err
28 | }
29 |
30 | func makeLauncher(launcherName, fileName, iconName string, force bool) {
31 | _, err := os.Lstat("/usr/local/share/applications/Secret-Diary.desktop")
32 | if err == nil {
33 | //install from deb
34 | return
35 | }
36 | exe1, _ := os.Executable()
37 | appdir := filepath.Dir(exe1)
38 | appname := filepath.Join(appdir, fileName)
39 | home, _ := os.UserHomeDir()
40 | os.MkdirAll(filepath.Join(home, ".local", "share", "applications"), os.ModePerm)
41 | dst := filepath.Join(home, ".local", "share", "applications", launcherName+".desktop")
42 | iconSrc := filepath.Join(appdir, iconName)
43 |
44 | if isNewer(dst, iconSrc) && force == false {
45 | //already exist
46 | return
47 | }
48 |
49 | data := struct {
50 | LauncherName string
51 | Name string
52 | Icon string
53 | }{launcherName, appname, iconSrc}
54 |
55 | tpl := `[Desktop Entry]
56 | Name={{.LauncherName}}
57 | Comment={{.LauncherName}}
58 | Exec="{{.Name}}" %U
59 | Icon={{.Icon}}
60 | Terminal=false
61 | Type=Application
62 | StartupNotify=true
63 | Categories=Office;
64 |
65 | `
66 | t := template.New("")
67 | t.Parse(tpl)
68 | fp, err := os.Create(dst)
69 | if err != nil {
70 | log.Println(err)
71 | return
72 | }
73 | defer fp.Close()
74 | t.Execute(fp, data)
75 | }
76 |
77 | func isNewer(fileNew, fileOld string) bool {
78 | infoNew, err := os.Lstat(fileNew)
79 | if err != nil {
80 | return false
81 | }
82 | infoOld, err := os.Lstat(fileOld)
83 | if err != nil {
84 | return true
85 | }
86 | if infoNew.ModTime().Unix() > infoOld.ModTime().Unix() {
87 | return true
88 | }
89 | return false
90 | }
91 |
92 | func appimageLauncher(force bool) error {
93 | desktop := "Secret-Diary.desktop"
94 | icon := "Sd.png"
95 |
96 | appimage := os.Getenv("APPIMAGE")
97 | appdir := os.Getenv("APPDIR")
98 | if len(appimage) == 0 || len(appdir) == 0 {
99 | return errors.New("Not Appimage")
100 | }
101 |
102 | home, _ := os.UserHomeDir()
103 | os.MkdirAll(filepath.Join(home, ".local", "share", "applications"), os.ModePerm)
104 |
105 | src := filepath.Join(appdir, desktop)
106 | dst := filepath.Join(home, ".local", "share", "applications", desktop)
107 | if isNewer(dst, appimage) && force == false {
108 | return nil
109 | }
110 |
111 | iconSrc := filepath.Join(appdir, icon)
112 |
113 | iconDir := filepath.Join(home, ".local", "share", "icons")
114 | iconDst := filepath.Join(iconDir, icon)
115 |
116 | os.MkdirAll(iconDir, os.ModePerm)
117 |
118 | err := copyFile(iconSrc, iconDst)
119 | if err != nil {
120 | log.Println(err)
121 | }
122 |
123 | srcFp, err := os.Open(src)
124 | if err != nil {
125 | log.Println(err)
126 | return err
127 | }
128 | defer srcFp.Close()
129 | reader := bufio.NewReader(srcFp)
130 |
131 | fp, err := os.Create(dst)
132 | if err != nil {
133 | log.Println(err)
134 | return err
135 | }
136 | defer fp.Close()
137 | for {
138 | line, _, err := reader.ReadLine()
139 | if err != nil {
140 | break
141 | }
142 | if bytes.HasPrefix(line, []byte("Exec=")) {
143 | fp.WriteString("Exec=" + appimage + "\n")
144 | } else {
145 | fp.Write(line)
146 | fp.WriteString("\n")
147 | }
148 | }
149 | return nil
150 | }
151 |
152 | func makeShortcut(force bool) {
153 | // err := appimageLauncher(force)
154 | // if err != nil {
155 | // makeLauncher("Secret-Diary", "secret-diary", "Sd.png", force)
156 | // }
157 | return
158 | }
159 |
--------------------------------------------------------------------------------
/shortcut_windows.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "path/filepath"
8 |
9 | locale "github.com/rocket049/go-locale"
10 | "github.com/skratchdot/open-golang/open"
11 | "golang.org/x/text/encoding/simplifiedchinese"
12 | )
13 |
14 | func makeShortcut(force bool) error {
15 | exe1, _ := os.Executable()
16 | appdir := filepath.Dir(exe1)
17 | appname := filepath.Join(appdir, "secret-diary.exe")
18 | script := filepath.Join(appdir, "shortcut.vbs")
19 | bat := filepath.Join(appdir, "shortcut.bat")
20 | _, err := os.Lstat(bat)
21 | if err == nil && force == false {
22 | return err
23 | }
24 |
25 | writeBat(bat, "wscript", script, fmt.Sprintf(`/target:"%s"`, appname), "\r\nexit\r\n")
26 |
27 | open.Start(bat)
28 |
29 | return nil
30 | }
31 |
32 | func writeBat(bat string, msgs ...interface{}) {
33 | var buf = []byte(fmt.Sprintln(msgs...))
34 | var err error = nil
35 |
36 | loc, _ := locale.DetectLocale()
37 | switch loc {
38 | case "zh_CN":
39 | enc := simplifiedchinese.GB18030.NewEncoder()
40 | buf, err = enc.Bytes(buf)
41 | }
42 |
43 | if err == nil {
44 | ioutil.WriteFile(bat, buf, 0644)
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/storge.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/gob"
6 | "reflect"
7 |
8 | "github.com/therecipe/qt/core"
9 | "github.com/therecipe/qt/gui"
10 | )
11 |
12 | type attachmentFile struct {
13 | Name string
14 | Data []byte
15 | }
16 |
17 | type qtextFormat struct {
18 | Html string
19 | Images map[string][]byte
20 | Attachments []attachmentFile
21 | }
22 |
23 | func (s *myWindow) getRichText() []byte {
24 | var data qtextFormat
25 | data.Html = s.editor.ToHtml()
26 | data.Images = s.getImagesMap(data.Html)
27 | data.Attachments = s.document.Attachments
28 |
29 | buf := bytes.NewBufferString("")
30 | encoder := gob.NewEncoder(buf)
31 | err := encoder.Encode(data)
32 | if err != nil {
33 | return nil
34 | }
35 | return buf.Bytes()
36 | }
37 |
38 | func (s *myWindow) getQText(data []byte) (*qtextFormat, error) {
39 | buf := bytes.NewReader(data)
40 | decoder := gob.NewDecoder(buf)
41 | s.document.Images = make(map[string][]byte)
42 | s.document.Attachments = []attachmentFile{}
43 | err := decoder.Decode(&s.document)
44 | if err == nil {
45 | s.loadImgResources(s.document.Images)
46 | }
47 | return &s.document, err
48 | }
49 |
50 | func (s *myWindow) getImagesMap(html string) map[string][]byte {
51 | urls := s.getImageList(html)
52 | imgMap := make(map[string][]byte)
53 | for _, url := range urls {
54 | buf, ok := s.document.Images[url]
55 | if ok {
56 | imgMap[url] = buf
57 | }
58 | }
59 | return imgMap
60 | }
61 |
62 | func (s *myWindow) loadImgResources(imgs map[string][]byte) {
63 | for url, data := range imgs {
64 | img := gui.NewQImage()
65 | ok := img.LoadFromData(data, len(data), "")
66 | if !ok {
67 | return
68 | }
69 | uri := core.NewQUrl3(url, core.QUrl__TolerantMode)
70 |
71 | s.editor.Document().AddResource(int(gui.QTextDocument__ImageResource), uri, img.ToVariant())
72 | }
73 | }
74 |
75 | func (s *myWindow) removeCurAttachment() {
76 | idx := s.comboAttachs.CurrentIndex()
77 | if idx == 0 {
78 | return
79 | }
80 | attachs := sliceValueRemoveIndex(s.document.Attachments, idx-1)
81 | s.document.Attachments = attachs.([]attachmentFile)
82 | s.showAttachList()
83 | s.editor.Document().SetModified(true)
84 | //curDiary.Modified = true
85 | }
86 |
87 | func sliceValueRemoveIndex(v interface{}, idx int) interface{} {
88 | val := reflect.ValueOf(v)
89 | if val.Type().Kind() != reflect.Slice {
90 | return v
91 | }
92 | length := val.Len()
93 | if length == 0 {
94 | return v
95 | }
96 | if idx >= length || idx < 0 {
97 | return v
98 | }
99 |
100 | res := reflect.MakeSlice(val.Type(), length-1, length-1)
101 | for i := 0; i < idx; i++ {
102 | res.Index(i).Set(val.Index(i))
103 | }
104 | for i := idx + 1; i < length; i++ {
105 | res.Index(i - 1).Set(val.Index(i))
106 | }
107 | return res.Interface()
108 | }
109 |
--------------------------------------------------------------------------------
/testdata/.gitignore:
--------------------------------------------------------------------------------
1 | testdata
--------------------------------------------------------------------------------
/testdata/README.md:
--------------------------------------------------------------------------------
1 | ## 生成从1979-01-01 到当前日期的所有日志索引
2 | 创建文件 `~/.sdiary/test/diary.db`,生成从1979-01-01 到当前日期的所有日志索,用于测试载入速度。
3 |
4 | 生成的测试用户为:`test`,密码:`1234`
5 |
6 | ## Generate all log indexes from 1979-01-01 to the current date
7 |
8 | Create the file `~/.sdiary/test/diary.db', generate all log cables from 1979-01-01 to the current date, used to test load speed.
9 |
10 | The generated test user is: `test', password: `1234`
11 |
--------------------------------------------------------------------------------
/testdata/aes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "crypto/rand"
8 | "crypto/sha256"
9 | "fmt"
10 | "io"
11 | "os"
12 | "path"
13 | )
14 |
15 | var dataDir string
16 |
17 | func getSha40String(pwd string) string {
18 | bp := []byte(pwd)
19 | buf := bytes.NewBuffer(make([]byte, 0, len(bp)*40))
20 | for i := 0; i < 40; i++ {
21 | buf.Write(bp)
22 | }
23 | res := sha256.Sum256(buf.Bytes())
24 | return fmt.Sprintf("%x", res)
25 | }
26 |
27 | func getSha4(pwd string) []byte {
28 | bp := []byte(pwd)
29 | buf := bytes.NewBuffer(make([]byte, 0, len(bp)*4))
30 | for i := 0; i < 4; i++ {
31 | buf.Write(bp)
32 | }
33 | res := sha256.Sum256(buf.Bytes())
34 | return res[:]
35 | }
36 |
37 | func getRealKeyString(pwd string) (string, error) {
38 |
39 | rkey := make([]byte, 32)
40 |
41 | io.ReadFull(rand.Reader, rkey)
42 | p1 := getSha4(pwd)
43 | aesBlock, err := aes.NewCipher(p1)
44 | if err != nil {
45 | return "", err
46 | }
47 | iv := make([]byte, aes.BlockSize)
48 | io.ReadFull(rand.Reader, iv)
49 |
50 | aesEncoder := cipher.NewCTR(aesBlock, iv)
51 | var res = make([]byte, 32)
52 | aesEncoder.XORKeyStream(res, rkey)
53 | buf := bytes.NewBuffer(iv)
54 | buf.Write(res)
55 | return fmt.Sprintf("%x", buf.Bytes()), nil
56 | }
57 |
58 | func newRealKeyString(rkey []byte, pwd string) (string, error) {
59 | p1 := getSha4(pwd)
60 | aesBlock, err := aes.NewCipher(p1)
61 | if err != nil {
62 | return "", err
63 | }
64 | iv := make([]byte, aes.BlockSize)
65 | io.ReadFull(rand.Reader, iv)
66 |
67 | aesEncoder := cipher.NewCTR(aesBlock, iv)
68 | var res = make([]byte, 32)
69 | aesEncoder.XORKeyStream(res, rkey)
70 | buf := bytes.NewBuffer(iv)
71 | buf.Write(res)
72 | return fmt.Sprintf("%x", buf.Bytes()), nil
73 | }
74 |
75 | func decodeRealKey(key, data, iv []byte) ([]byte, error) {
76 | aesBlock, err := aes.NewCipher(key)
77 | if err != nil {
78 | return nil, err
79 | }
80 | aesDecoder := cipher.NewCTR(aesBlock, iv)
81 | var res = make([]byte, 32)
82 | aesDecoder.XORKeyStream(res, data)
83 | return res, nil
84 | }
85 |
86 | func encodeToFile(data []byte, filename string, key []byte) error {
87 | dataPath := path.Join(dataDir, filename)
88 | fp, err := os.Create(dataPath)
89 | if err != nil {
90 | return err
91 | }
92 | defer fp.Close()
93 |
94 | aesBlock, err := aes.NewCipher(key)
95 | if err != nil {
96 | return err
97 | }
98 | iv := make([]byte, aesBlock.BlockSize())
99 | io.ReadFull(rand.Reader, iv)
100 | _, err = fp.Write(iv)
101 | if err != nil {
102 | return err
103 | }
104 | aesEncoder := cipher.NewCTR(aesBlock, iv)
105 | wr := cipher.StreamWriter{S: aesEncoder, W: fp}
106 | _, err = wr.Write(data)
107 | return err
108 | }
109 |
110 | func decodeFromFile(filename string, key []byte) ([]byte, error) {
111 | dataPath := path.Join(dataDir, filename)
112 | fp, err := os.Open(dataPath)
113 | if err != nil {
114 | return nil, err
115 | }
116 | defer fp.Close()
117 |
118 | aesBlock, err := aes.NewCipher(key)
119 | if err != nil {
120 | return nil, err
121 | }
122 | iv := make([]byte, aesBlock.BlockSize())
123 | _, err = io.ReadFull(fp, iv)
124 | if err != nil {
125 | return nil, err
126 | }
127 | aesStream := cipher.NewCTR(aesBlock, iv)
128 | rd := cipher.StreamReader{S: aesStream, R: fp}
129 | res := bytes.NewBufferString("")
130 | _, err = io.Copy(res, rd)
131 | return res.Bytes(), err
132 | }
133 |
134 | func encodeToPathName(data []byte, filename string, key []byte) error {
135 | dataPath := filename
136 | fp, err := os.Create(dataPath)
137 | if err != nil {
138 | return err
139 | }
140 | defer fp.Close()
141 |
142 | aesBlock, err := aes.NewCipher(key)
143 | if err != nil {
144 | return err
145 | }
146 | iv := make([]byte, aesBlock.BlockSize())
147 | io.ReadFull(rand.Reader, iv)
148 | _, err = fp.Write(iv)
149 | if err != nil {
150 | return err
151 | }
152 | aesEncoder := cipher.NewCTR(aesBlock, iv)
153 | wr := cipher.StreamWriter{S: aesEncoder, W: fp}
154 | _, err = wr.Write(data)
155 | return err
156 | }
157 |
158 | func decodeFromPathName(filename string, key []byte) ([]byte, error) {
159 | dataPath := filename
160 | fp, err := os.Open(dataPath)
161 | if err != nil {
162 | return nil, err
163 | }
164 | defer fp.Close()
165 |
166 | aesBlock, err := aes.NewCipher(key)
167 | if err != nil {
168 | return nil, err
169 | }
170 | iv := make([]byte, aesBlock.BlockSize())
171 | _, err = io.ReadFull(fp, iv)
172 | if err != nil {
173 | return nil, err
174 | }
175 | aesStream := cipher.NewCTR(aesBlock, iv)
176 | rd := cipher.StreamReader{S: aesStream, R: fp}
177 | res := bytes.NewBufferString("")
178 | _, err = io.Copy(res, rd)
179 | return res.Bytes(), err
180 | }
181 |
182 | func decodeFromReader(reader io.Reader, key []byte) ([]byte, error) {
183 | aesBlock, err := aes.NewCipher(key)
184 | if err != nil {
185 | return nil, err
186 | }
187 | iv := make([]byte, aesBlock.BlockSize())
188 | _, err = io.ReadFull(reader, iv)
189 | if err != nil {
190 | return nil, err
191 | }
192 | aesStream := cipher.NewCTR(aesBlock, iv)
193 | rd := cipher.StreamReader{S: aesStream, R: reader}
194 | res := bytes.NewBufferString("")
195 | _, err = io.Copy(res, rd)
196 | return res.Bytes(), err
197 | }
198 |
--------------------------------------------------------------------------------
/testdata/db.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/aes"
5 | "database/sql"
6 | "encoding/hex"
7 | "errors"
8 | "log"
9 | "os"
10 | "path"
11 | "time"
12 |
13 | _ "github.com/mattn/go-sqlite3"
14 | )
15 |
16 | type myDb struct {
17 | db *sql.DB
18 | category int
19 | }
20 |
21 | func createUserDb(name string, pwd string) error {
22 | home1, _ := os.UserHomeDir()
23 | dataDir = path.Join(home1, ".sdiary", name)
24 | os.MkdirAll(dataDir, os.ModePerm)
25 | userDb := path.Join(dataDir, "diary.db")
26 | if _, err := os.Stat(userDb); err == nil {
27 | //exists
28 | return errors.New("Already exists.")
29 | }
30 | db, err := sql.Open("sqlite3", userDb)
31 | if err != nil {
32 | return err
33 | }
34 | defer db.Close()
35 | sqls := []string{"create table user (id integer unique,cdata text,sha40 TEXT not null,realkey text not null,mtime text);",
36 | "create table diaries (id integer unique,cdate text,title text,filename text,mtime text,category int default 0);",
37 | "create index idx1 on diaries(cdate);",
38 | "create table categories(id int,name text);"}
39 | for i := 0; i < len(sqls); i++ {
40 | _, err = db.Exec(sqls[i])
41 | if err != nil {
42 | return err
43 | }
44 | }
45 |
46 | id := 1
47 | now := time.Now()
48 | cdate := now.Format("2006-01-02")
49 | sha40 := getSha40String(pwd)
50 | realKey, err := getRealKeyString(pwd)
51 | if err != nil {
52 | return err
53 | }
54 | mtime := now.Format("2006-01-02 15:04:05")
55 |
56 | _, err = db.Exec("insert into user(id,cdata,sha40,realkey,mtime) values(?,?,?,?,?);", id, cdate, sha40, realKey, mtime)
57 |
58 | return err
59 | }
60 |
61 | func getMyDb(name string) (*myDb, error) {
62 | home1, _ := os.UserHomeDir()
63 | dataDir = path.Join(home1, ".sdiary", name)
64 | os.MkdirAll(dataDir, os.ModePerm)
65 |
66 | userDb := path.Join(dataDir, "diary.db")
67 | if _, err := os.Stat(userDb); err != nil {
68 | return nil, err
69 | }
70 |
71 | db, err := sql.Open("sqlite3", userDb)
72 | if err != nil {
73 | return nil, err
74 | }
75 | res := &myDb{db: db}
76 | res.CheckAndUpgrade()
77 | return res, nil
78 | }
79 |
80 | func (s *myDb) setCategory(c int) {
81 | s.category = c
82 | }
83 |
84 | func (s *myDb) CheckAndUpgrade() error {
85 | diaryStruct, err := s.db.Query("PRAGMA table_info(diaries);")
86 | if err != nil {
87 | return err
88 | }
89 | var rows = 0
90 | for diaryStruct.Next() {
91 | rows++
92 | }
93 | diaryStruct.Close()
94 | if rows == 5 {
95 | s.db.Exec("alter table diaries add column category int default 0;")
96 | s.db.Exec("create table categories(id int,name text);")
97 | }
98 | return nil
99 | }
100 |
101 | func (s *myDb) Close() {
102 | s.db.Close()
103 | }
104 |
105 | func (s *myDb) GetRealKey(pwd string) ([]byte, error) {
106 | sha40 := getSha40String(pwd)
107 | row := s.db.QueryRow("select realkey from user where sha40=?;", sha40)
108 | var realKey string
109 | err := row.Scan(&realKey)
110 | if err != nil {
111 | return nil, err
112 | }
113 | key, err := hex.DecodeString(realKey)
114 | if err != nil {
115 | return nil, err
116 | }
117 | if len(key) != 32+aes.BlockSize {
118 | return nil, errors.New("Data invalid.")
119 | }
120 | iv := key[:aes.BlockSize]
121 | data := key[aes.BlockSize:]
122 | ukey := getSha4(pwd)
123 | return decodeRealKey(ukey, data, iv)
124 | }
125 |
126 | func (s *myDb) UpdateRealKeyAndSha40(pwdOld, pwdNew string) error {
127 | sha40 := getSha40String(pwdOld)
128 | row := s.db.QueryRow("select realkey from user where sha40=?;", sha40)
129 | var realKey string
130 | err := row.Scan(&realKey)
131 | if err != nil {
132 | return err
133 | }
134 | key, err := hex.DecodeString(realKey)
135 | if err != nil {
136 | return err
137 | }
138 | if len(key) != 32+aes.BlockSize {
139 | return errors.New("Data invalid.")
140 | }
141 | iv := key[:aes.BlockSize]
142 | data := key[aes.BlockSize:]
143 | ukey := getSha4(pwdOld)
144 | rkey, err := decodeRealKey(ukey, data, iv)
145 | if err != nil {
146 | return err
147 | }
148 | rkeyStr, err := newRealKeyString(rkey, pwdNew)
149 | if err != nil {
150 | return err
151 | }
152 | _, err = s.db.Exec("update user set realkey=?,sha40=? where id=1;", rkeyStr, getSha40String(pwdNew))
153 | return err
154 | }
155 |
156 | func (s *myDb) AddDiary(id int, cdate, title, filename string) error {
157 | _, err := s.db.Exec("insert into diaries(id,cdate,title,filename,mtime,category) values(?,?,?,?,?,?);",
158 | id, cdate, title, filename, time.Now().Format("2006-01-02 15:04:05"), s.category)
159 | if err != nil {
160 | log.Println("UpdateDiaryTitle")
161 | return s.UpdateDiaryTitle(id, title)
162 | }
163 | return nil
164 | }
165 |
166 | func (s *myDb) AddDiary2(id int, cdate, title, filename string, category int) error {
167 | _, err := s.db.Exec("insert into diaries(id,cdate,title,filename,mtime,category) values(?,?,?,?,?,?);",
168 | id, cdate, title, filename, time.Now().Format("2006-01-02 15:04:05"), category)
169 | if err != nil {
170 | log.Println("UpdateDiaryTitle")
171 | return s.UpdateDiaryTitle(id, title)
172 | }
173 | return nil
174 | }
175 |
176 | func (s *myDb) UpdateDiaryTitle(id int, title string) error {
177 | res, err := s.db.Exec("update diaries set title=?,mtime=? where id=?", title, time.Now().Format("2006-01-02 15:04:05"), id)
178 | if err != nil {
179 | return err
180 | }
181 | if n, _ := res.RowsAffected(); n == 0 {
182 | return errors.New("UpdateDiaryTitle fail.")
183 | }
184 | return nil
185 | }
186 |
187 | func (s *myDb) UpdateDiaryCategory(id, c int) error {
188 | res, err := s.db.Exec("update diaries set category=?,mtime=? where id=?", c, time.Now().Format("2006-01-02 15:04:05"), id)
189 | if err != nil {
190 | return err
191 | }
192 | if n, _ := res.RowsAffected(); n == 0 {
193 | return errors.New("UpdateDiaryCategory fail.")
194 | }
195 | return nil
196 | }
197 |
198 | func (s *myDb) TouchDiary(id int) error {
199 | res, err := s.db.Exec("update diaries set mtime=? where id=?;", time.Now().Format("2006-01-02 15:04:05"), id)
200 | if err != nil {
201 | return err
202 | }
203 | if n, _ := res.RowsAffected(); n == 0 {
204 | return errors.New("AddDiary fail.")
205 | }
206 | return nil
207 | }
208 |
209 | func (s *myDb) NextId() int {
210 | res := s.db.QueryRow("select id from diaries order by id desc limit 1;")
211 |
212 | var id int
213 | err := res.Scan(&id)
214 | if err != nil {
215 | log.Println(err.Error() + ": at db 1")
216 | return 1
217 | }
218 | return id + 1
219 | }
220 |
221 | func (s *myDb) GetYearMonths() ([]string, error) {
222 | rows, err := s.db.Query("select distinct substr(cdate,0,8) from diaries where category=? order by substr(cdate,0,8) asc;", s.category)
223 | if err != nil {
224 | return nil, err
225 | }
226 | defer rows.Close()
227 | res := []string{}
228 | for rows.Next() {
229 | var v string
230 | rows.Scan(&v)
231 | res = append(res, v)
232 | }
233 | return res, nil
234 | }
235 |
236 | type diaryItem struct {
237 | Id int
238 | Day string
239 | Title string
240 | MTime string
241 | }
242 |
243 | func (s *myDb) GetListFromYearMonth(ym string) ([]diaryItem, error) {
244 | rows, err := s.db.Query("select id,substr(cdate,9,11),title,mtime from diaries where substr(cdate,0,8)=? and category=? order by substr(cdate,9,11) desc;", ym, s.category)
245 | if err != nil {
246 | return nil, err
247 | }
248 | defer rows.Close()
249 | res := []diaryItem{}
250 | for rows.Next() {
251 | var v diaryItem
252 | rows.Scan(&v.Id, &v.Day, &v.Title, &v.MTime)
253 | res = append(res, v)
254 | }
255 | return res, nil
256 | }
257 |
258 | func (s *myDb) RemoveDiary(id string) error {
259 | row := s.db.QueryRow("select filename from diaries where id=?;", id)
260 | var filename string
261 | err := row.Scan(&filename)
262 | if err != nil {
263 | return err
264 | }
265 | _, err = s.db.Exec("delete from diaries where id=?;", id)
266 | if err != nil {
267 | return err
268 | }
269 | return os.Remove(path.Join(dataDir, filename))
270 | }
271 |
272 | func (s *myDb) SearchTitle(kw string) ([]diaryItem, error) {
273 | rows, err := s.db.Query("select id,cdate,title,mtime from diaries where instr(title,?)>0 and category=? order by cdate desc;", kw, s.category)
274 | if err != nil {
275 | return nil, err
276 | }
277 | defer rows.Close()
278 | res := []diaryItem{}
279 | for rows.Next() {
280 | var v diaryItem
281 | rows.Scan(&v.Id, &v.Day, &v.Title, &v.MTime)
282 | res = append(res, v)
283 | }
284 | return res, nil
285 | }
286 |
287 | func (s *myDb) AddCategory(name string) int {
288 | res := s.db.QueryRow("select id from categories order by id desc limit 1;")
289 |
290 | var id int
291 | err := res.Scan(&id)
292 | if err != nil {
293 | id = 1
294 | } else {
295 | id++
296 | }
297 |
298 | s.db.Exec("insert into categories(id,name) values (?,?);", id, name)
299 | return id
300 | }
301 |
302 | func (s *myDb) RenameCategory(id int, name string) error {
303 | _, err := s.db.Exec("update categories set name=? where id=?;", name, id)
304 | return err
305 | }
306 |
307 | func (s *myDb) RemoveCategory(c int) {
308 | s.db.Exec("delete from categories where id=?;", c)
309 | s.db.Exec("update diaries set category=0 where category=?;", c)
310 | }
311 |
312 | func (s *myDb) GetCategories() map[int]string {
313 | res := make(map[int]string)
314 |
315 | rows, err := s.db.Query("select id,name from categories order by id asc;")
316 | if err != nil {
317 | return nil
318 | }
319 | defer rows.Close()
320 |
321 | for rows.Next() {
322 | var id int
323 | var name string
324 | if rows.Scan(&id, &name) == nil {
325 | res[id] = name
326 | }
327 | }
328 |
329 | return res
330 | }
331 |
--------------------------------------------------------------------------------
/testdata/dbgen.go:
--------------------------------------------------------------------------------
1 | //生成从1979-01-01 到当前日期的所有日志索引 `~/.sdiary/test/diary.db`,用于测试载入速度
2 |
3 | package main
4 |
5 | import (
6 | "bytes"
7 | "encoding/gob"
8 | "fmt"
9 | "log"
10 | "os"
11 | "path"
12 | "time"
13 | )
14 |
15 | type attachmentFile struct {
16 | Name string
17 | Data []byte
18 | }
19 | type qtextFormat struct {
20 | Html string
21 | Images map[string][]byte
22 | Attachments []attachmentFile
23 | }
24 |
25 | var dat []byte = nil
26 | var key []byte
27 |
28 | func createDat(home string, user string, id int) error {
29 | path1 := path.Join(home, ".sdiary", user, fmt.Sprintf("%d.dat", id))
30 | if dat == nil {
31 | var err error
32 | var data qtextFormat
33 | data.Html = "Test"
34 | data.Images = nil
35 | data.Attachments = nil
36 |
37 | buf := bytes.NewBufferString("")
38 | encoder := gob.NewEncoder(buf)
39 | err = encoder.Encode(data)
40 | if err != nil {
41 | return err
42 | }
43 | dat = buf.Bytes()
44 | }
45 | return encodeToPathName(dat, path1, key)
46 | }
47 |
48 | func createDiarys() {
49 | home, _ := os.UserHomeDir()
50 | path1 := path.Join(home, ".sdiary", "test", "diary.db")
51 | os.Remove(path1)
52 | err := createUserDb("test", "1234")
53 | if err != nil {
54 | panic(err.Error() + "1")
55 | }
56 | db, err := getMyDb("test")
57 | if err != nil {
58 | panic(err.Error() + "2")
59 | }
60 | defer db.Close()
61 | key, _ = db.GetRealKey("1234")
62 | tt, err := time.Parse("20060102", "19790101")
63 | if err != nil {
64 | panic(err.Error() + "3")
65 | }
66 | log.Println("start", tt.Format("2006-01-02"), tt.Unix())
67 | stop := time.Now().Unix()
68 | log.Println("end", time.Now().Format("2006-01-02"), stop)
69 | id := db.NextId()
70 | log.Println("start id:", id)
71 | tx, err := db.db.Begin()
72 | if err != nil {
73 | panic(err.Error() + "4")
74 | }
75 | for tt.Unix() < stop {
76 | cdate := tt.Format("2006-01-02")
77 | title := fmt.Sprintf("diary %d", id)
78 | filename := fmt.Sprintf("%d.dat", id)
79 | tx.Exec("insert into diaries(id,cdate,title,filename,mtime,category) values(?,?,?,?,?,?);",
80 | id, cdate, title, filename, time.Now().Format("2006-01-02 15:04:05"), db.category)
81 | err = createDat(home, "test", id)
82 | if err != nil {
83 | log.Println(err, " dat")
84 | }
85 | tt = tt.Add(time.Hour * 24)
86 | id++
87 | }
88 | err = tx.Commit()
89 | if err != nil {
90 | panic(err.Error() + "5")
91 | }
92 | log.Println("success")
93 | }
94 |
95 | func main() {
96 | createDiarys()
97 | }
98 |
--------------------------------------------------------------------------------
/testdata/exam.dat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rocket049/secret-diary/38e841bc3ad155ba813ad9dd8386e40b3421260a/testdata/exam.dat
--------------------------------------------------------------------------------
/testdata/jinzhi16.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // import (
4 | // "bytes"
5 | // "crypto/sha256"
6 | // "fmt"
7 | // )
8 |
9 | // func getSha40String(pwd string) string {
10 | // bp := []byte(pwd)
11 | // buf := bytes.NewBuffer(make([]byte, 0, len(bp)*40))
12 | // for i := 0; i < 40; i++ {
13 | // buf.Write(bp)
14 | // }
15 | // res := sha256.Sum256(buf.Bytes())
16 | // return fmt.Sprintf("%x", res)
17 | // }
18 |
19 | // func main() {
20 | // fmt.Printf("%s\n", getSha40String("abcd"))
21 | // }
22 |
--------------------------------------------------------------------------------
/version.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | const version = "1.2.16"
4 |
--------------------------------------------------------------------------------
/version.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version":"1.2.16"
3 | }
4 |
--------------------------------------------------------------------------------
/version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | sed -i s/version\ =\ .*$/version\ =\ \"$1\"/g version.go
4 | sed -i s/\"Version\":.*$/\"Version\":\"$1\"/g version.json
5 | sed -i s/^Version:.*$/Version:\ $1/g ./deploy/secret-diary-deb/DEBIAN/control
6 |
--------------------------------------------------------------------------------
/versionCheck.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "io"
7 | "log"
8 | "net/http"
9 | )
10 |
11 | type VersionOL struct {
12 | Version string
13 | }
14 |
15 | func versionCheck() (v string, newest bool) {
16 | resp, err := http.Get("https://gitee.com/rocket049/secret-diary/raw/master/version.json")
17 | if err != nil {
18 | log.Println(err)
19 | newest = false
20 | return
21 | }
22 | defer resp.Body.Close()
23 |
24 | buf := bytes.NewBufferString("")
25 | _, err = io.Copy(buf, resp.Body)
26 | if err != nil {
27 | log.Println(err)
28 | newest = false
29 | return
30 | }
31 |
32 | var ver VersionOL
33 | err = json.Unmarshal(buf.Bytes(), &ver)
34 | if err != nil {
35 | log.Println(err)
36 | newest = false
37 | return
38 | }
39 |
40 | if version == ver.Version {
41 | v = version
42 | newest = true
43 | return
44 | }
45 |
46 | v = ver.Version
47 | newest = false
48 | return
49 | }
50 |
--------------------------------------------------------------------------------
/zip.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "archive/zip"
5 | "crypto/aes"
6 | "database/sql"
7 | "encoding/hex"
8 | "errors"
9 | "fmt"
10 | "io"
11 | "os"
12 | "path/filepath"
13 | )
14 |
15 | func zipData(fromDir, toFile string) error {
16 | dir, err := os.Open(fromDir)
17 | if err != nil {
18 | return err
19 | }
20 | defer dir.Close()
21 |
22 | //open zip
23 | fileToZip, err := os.Create(toFile)
24 | if err != nil {
25 | return err
26 | }
27 | defer fileToZip.Close()
28 | zipFile := zip.NewWriter(fileToZip)
29 | defer zipFile.Close()
30 |
31 | files, err := dir.Readdirnames(-1)
32 | if err != nil {
33 | return err
34 | }
35 | for _, name := range files {
36 | filename := filepath.Join(fromDir, name)
37 | fp, err := os.Open(filename)
38 | if err != nil {
39 | return err
40 |
41 | }
42 | //add to zip
43 | writer, err := zipFile.Create(name)
44 | if err != nil {
45 | return err
46 | }
47 | _, err = io.Copy(writer, fp)
48 | if err != nil {
49 | return err
50 | }
51 | fp.Close()
52 | }
53 | return nil
54 | }
55 |
56 | func (s *myWindow) importFromZip(filename string, pwd string) error {
57 | zipFile, err := zip.OpenReader(filename)
58 | if err != nil {
59 | return err
60 | }
61 | defer zipFile.Close()
62 | //unzip diary.db
63 | var diaryDbName string
64 | for _, zf := range zipFile.File {
65 | if zf.Name == "diary.db" {
66 | diaryDbName = filepath.Join(os.TempDir(), "diary.db")
67 | fp, err := os.Create(diaryDbName)
68 | if err != nil {
69 | return err
70 | }
71 |
72 | reader, err := zf.Open()
73 | if err != nil {
74 | fp.Close()
75 | return err
76 | }
77 |
78 | _, err = io.Copy(fp, reader)
79 |
80 | reader.Close()
81 | if err != nil {
82 | fp.Close()
83 | reader.Close()
84 | return err
85 | }
86 |
87 | fp.Close()
88 | break
89 | }
90 | }
91 | db, err := sql.Open("sqlite3", diaryDbName)
92 | if err != nil {
93 | return err
94 | }
95 | defer os.Remove(diaryDbName)
96 | defer db.Close()
97 |
98 | res, err := db.Query("select cdate,title,filename,category from diaries")
99 | if err != nil {
100 | return err
101 | }
102 | //get filename title dict
103 | type diaryRecod struct {
104 | Cdate string
105 | Title string
106 | Category int
107 | }
108 | var titleDict = make(map[string]diaryRecod)
109 | var cdate, title, fname string
110 | var category int
111 | for res.Next() {
112 | err := res.Scan(&cdate, &title, &fname, &category)
113 | if err != nil {
114 | res.Close()
115 | return err
116 | }
117 | titleDict[fname] = diaryRecod{cdate, title, category}
118 | }
119 | res.Close()
120 | //get real key
121 | key, err := dbGetRealKey(db, pwd)
122 | if err != nil {
123 | return err
124 | }
125 | //import every file from zip
126 | id := s.db.NextId()
127 | tx, err := s.db.BeginTx()
128 | if err != nil {
129 | return err
130 | }
131 | for _, zf := range zipFile.File {
132 | if zf.Name == "diary.db" {
133 | break
134 | }
135 | record, ok := titleDict[zf.Name]
136 | if !ok {
137 | break
138 | }
139 | reader, err := zf.Open()
140 | if err != nil {
141 | return err
142 | }
143 | data, err := decodeFromReader(reader, key)
144 | reader.Close()
145 | if err != nil {
146 | return err
147 | }
148 |
149 | //save diary
150 | datName := fmt.Sprintf("%d.dat", id)
151 | encodeToFile(data, datName, s.key)
152 | s.db.AddDiaryTx(tx, id, record.Cdate, record.Title, datName, record.Category)
153 | s.setStatusBar(datName)
154 | id++
155 | }
156 | return tx.Commit()
157 | }
158 |
159 | func dbGetRealKey(db *sql.DB, pwd string) ([]byte, error) {
160 | sha40 := getSha40String(pwd)
161 | row := db.QueryRow("select realkey from user where sha40=?;", sha40)
162 | var realKey string
163 | err := row.Scan(&realKey)
164 | if err != nil {
165 | return nil, err
166 | }
167 | key, err := hex.DecodeString(realKey)
168 | if err != nil {
169 | return nil, err
170 | }
171 | if len(key) != 32+aes.BlockSize {
172 | return nil, errors.New("Data invalid.")
173 | }
174 | iv := key[:aes.BlockSize]
175 | data := key[aes.BlockSize:]
176 | ukey := getSha4(pwd)
177 | return decodeRealKey(ukey, data, iv)
178 | }
179 |
--------------------------------------------------------------------------------