├── .gitignore ├── LICENSE.txt ├── MouseOverDictionary.sln ├── MouseOverDictionary ├── MouseOverDictionary.vcxproj ├── MouseOverDictionary.vcxproj.filters ├── Resources │ ├── chat-4-line.png │ ├── chat-off-line.png │ ├── push-pin-2-line.png │ ├── push-pin-line.png │ ├── side-bar-line-hide.png │ └── side-bar-line.png ├── dictionary.cpp ├── dictionary.h ├── image.cpp ├── image.h ├── inflector.h ├── main.cpp ├── mini_window.cpp ├── mini_window.h ├── mini_window.ui ├── mouse_over_dictionary.cpp ├── mouse_over_dictionary.h ├── mouse_over_dictionary.ico ├── mouse_over_dictionary.qrc ├── mouse_over_dictionary.rc ├── mouse_over_dictionary.ui ├── pause_window.cpp ├── pause_window.h ├── pause_window.ui ├── screen_ocr.cpp ├── screen_ocr.h ├── singularizer.h ├── thread.cpp └── thread.h ├── README.md └── licenses ├── LICENSE_Qt.txt └── LICENSE_Tesseract.txt /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | *.ini 352 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 kengo700 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MouseOverDictionary.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.960 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MouseOverDictionary", "MouseOverDictionary\MouseOverDictionary.vcxproj", "{B12702AD-ABFB-343A-A199-8E24837244A3}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x64.ActiveCfg = Debug|x64 15 | {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x64.Build.0 = Debug|x64 16 | {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x64.ActiveCfg = Release|x64 17 | {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x64.Build.0 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {56AEB598-3C18-4FE8-BCDC-787461461B69} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /MouseOverDictionary/MouseOverDictionary.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | {B12702AD-ABFB-343A-A199-8E24837244A3} 15 | QtVS_v301 16 | 10.0.17763.0 17 | 18 | 19 | 20 | Application 21 | v141 22 | 23 | 24 | Application 25 | v141 26 | 27 | 28 | 29 | $(MSBuildProjectDirectory)\QtMsBuild 30 | 31 | 32 | $(SolutionDir)$(Platform)\$(Configuration)\ 33 | 34 | 35 | $(SolutionDir)$(Platform)\$(Configuration)\ 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | msvc2017_64 54 | core;gui;widgets 55 | 56 | 57 | msvc2017_64 58 | core;gui;widgets 59 | 60 | 61 | 62 | 63 | 64 | 65 | true 66 | Disabled 67 | ProgramDatabase 68 | MultiThreadedDebugDLL 69 | true 70 | stdcpplatest 71 | 72 | 73 | Windows 74 | $(OutDir)\$(ProjectName).exe 75 | true 76 | 77 | 78 | 79 | 80 | true 81 | 82 | MultiThreadedDLL 83 | true 84 | stdcpplatest 85 | C:\tesseract\build\x64\include;.\;%(AdditionalIncludeDirectories) 86 | 87 | 88 | Windows 89 | $(OutDir)\$(ProjectName).exe 90 | false 91 | C:\tesseract\build\x64\lib;%(AdditionalLibraryDirectories) 92 | tesseract41.lib;leptonica-1.78.0.lib;UGlobalHotkey.lib;%(AdditionalDependencies) 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /MouseOverDictionary/MouseOverDictionary.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} 14 | qrc;* 15 | false 16 | 17 | 18 | {99349809-55BA-4b9d-BF79-8FDBB0286EB3} 19 | ui 20 | 21 | 22 | {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} 23 | qrc;* 24 | false 25 | 26 | 27 | 28 | 29 | Source Files 30 | 31 | 32 | Source Files 33 | 34 | 35 | Source Files 36 | 37 | 38 | Source Files 39 | 40 | 41 | Source Files 42 | 43 | 44 | Source Files 45 | 46 | 47 | Source Files 48 | 49 | 50 | Source Files 51 | 52 | 53 | 54 | 55 | Header Files 56 | 57 | 58 | Header Files 59 | 60 | 61 | Header Files 62 | 63 | 64 | Header Files 65 | 66 | 67 | 68 | 69 | Form Files 70 | 71 | 72 | Form Files 73 | 74 | 75 | Form Files 76 | 77 | 78 | 79 | 80 | Resource Files 81 | 82 | 83 | 84 | 85 | Header Files 86 | 87 | 88 | Header Files 89 | 90 | 91 | Header Files 92 | 93 | 94 | Header Files 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /MouseOverDictionary/Resources/chat-4-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengo700/mouse_over_dictionary/e1ba1b5fec67910e3273ccb41b55140814fb1c04/MouseOverDictionary/Resources/chat-4-line.png -------------------------------------------------------------------------------- /MouseOverDictionary/Resources/chat-off-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengo700/mouse_over_dictionary/e1ba1b5fec67910e3273ccb41b55140814fb1c04/MouseOverDictionary/Resources/chat-off-line.png -------------------------------------------------------------------------------- /MouseOverDictionary/Resources/push-pin-2-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengo700/mouse_over_dictionary/e1ba1b5fec67910e3273ccb41b55140814fb1c04/MouseOverDictionary/Resources/push-pin-2-line.png -------------------------------------------------------------------------------- /MouseOverDictionary/Resources/push-pin-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengo700/mouse_over_dictionary/e1ba1b5fec67910e3273ccb41b55140814fb1c04/MouseOverDictionary/Resources/push-pin-line.png -------------------------------------------------------------------------------- /MouseOverDictionary/Resources/side-bar-line-hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengo700/mouse_over_dictionary/e1ba1b5fec67910e3273ccb41b55140814fb1c04/MouseOverDictionary/Resources/side-bar-line-hide.png -------------------------------------------------------------------------------- /MouseOverDictionary/Resources/side-bar-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengo700/mouse_over_dictionary/e1ba1b5fec67910e3273ccb41b55140814fb1c04/MouseOverDictionary/Resources/side-bar-line.png -------------------------------------------------------------------------------- /MouseOverDictionary/dictionary.cpp: -------------------------------------------------------------------------------- 1 | #include "dictionary.h" 2 | 3 | Dictionary::Dictionary() 4 | { 5 | 6 | } 7 | 8 | bool Dictionary::Load(std::string foldername) 9 | { 10 | // 指定フォルダ内のテキストファイル名を取得 11 | QString path = QApplication::applicationDirPath() + "/" + QString::fromStdString(foldername) + "/"; 12 | 13 | QStringList nameFilters; 14 | nameFilters.append("*.txt"); 15 | nameFilters.append("*.TXT"); 16 | 17 | QDir dir(path); 18 | QStringList files = dir.entryList(nameFilters, QDir::Files); 19 | 20 | // 各ファイルのフォーマットを確認 21 | bool read_success = false; 22 | for (QString file : files) { 23 | std::string filename = dir.filePath(file).toStdString(); 24 | 25 | // データ読み込み 26 | switch (getFormat(filename)) 27 | { 28 | case EIJIRO: 29 | if (LoadEIJIRO(filename)) { 30 | read_success = true; 31 | } 32 | break; 33 | case EJDIC: 34 | if (LoadEJDIC(filename)) { 35 | read_success = true; 36 | } 37 | break; 38 | case PDIC1: 39 | if (LoadPDIC1(filename)) { 40 | read_success = true; 41 | } 42 | break; 43 | default: 44 | break; 45 | } 46 | } 47 | 48 | return read_success; 49 | } 50 | 51 | // ejdic-hand形式の辞書データを読み込む 52 | // ejdic-hand形式について:https://github.com/kujirahand/EJDict 53 | bool Dictionary::LoadEJDIC(std::string filename) 54 | { 55 | QTextCodec *codec = QTextCodec::codecForName("UTF-8"); 56 | 57 | // ファイルを開く 58 | // UTF-8のファイルはin.setCodec()で指定しないと文字化けする 59 | QFile file(filename.c_str()); 60 | if (!file.open(QIODevice::ReadOnly)) { 61 | return false; 62 | } 63 | QTextStream in(&file); 64 | in.setCodec("UTF-8"); 65 | 66 | QString line; 67 | while (!in.atEnd()) { 68 | line = in.readLine(); 69 | 70 | QStringList items = line.split(codec->toUnicode("\t")); 71 | 72 | if (items.size() < 2) { 73 | continue; 74 | } 75 | 76 | // 意味が同じで綴りが少し異なるだけの単語はカンマで区切って列挙されているので、それぞれ登録する 77 | QStringList words = items[0].split(codec->toUnicode(",")); 78 | 79 | std::string text = codec->fromUnicode(items[1]).toStdString(); 80 | 81 | for (int i = 0; i < words.count(); i++) { 82 | std::string word = codec->fromUnicode(words[i]).toStdString(); 83 | // 読み込み済みのワードは追記する 84 | if (data.find(word) != data.end()) { 85 | data[word] = data[word] + "\n" + text; 86 | } 87 | else { 88 | data[word] = text; 89 | } 90 | } 91 | } 92 | 93 | file.close(); 94 | 95 | return true; 96 | } 97 | 98 | // 辞郎形式の辞書データを読み込む 99 | // 辞郎形式について:http://www.eijiro.jp/spec.htm 100 | bool Dictionary::LoadEIJIRO(std::string filename) 101 | { 102 | QTextCodec *codec = QTextCodec::codecForName("UTF-8"); 103 | 104 | // ファイルを開く 105 | // in.setCodec()で指定しなくても、Shift-JISとUnicodeのファイルは正しく読めるっぽい 106 | QFile file(filename.c_str()); 107 | if (!file.open(QIODevice::ReadOnly)) { 108 | return false; 109 | } 110 | QTextStream in(&file); 111 | 112 | // 一行ずつ処理 113 | // in.readAll()で一括で読み込んでsplit("\n")で分割してforeachで処理する方法も試してみたところ、倍以上遅かった 114 | QString line; 115 | while (!in.atEnd()) { 116 | line = in.readLine(); 117 | 118 | // 用語と訳語の区切り「 : 」 119 | QStringList items = line.split(codec->toUnicode(" : ")); 120 | 121 | if (items.size() < 2) { 122 | continue; 123 | } 124 | 125 | // 先頭の「■」を削除 126 | items[0].remove(codec->toUnicode(u8"■")); 127 | 128 | // 品詞ラベルなどを削除 129 | QStringList items2 = items[0].split(codec->toUnicode("{")); 130 | items[0] = items2[0]; 131 | 132 | // 前後の半角スペースを削除 133 | items[0] = items[0].trimmed(); 134 | 135 | // 品詞ラベルなどは訳語の先頭に付けておく 136 | if (items2.size() >= 2) { 137 | items[1] = codec->toUnicode("{") + items2[1] + items[1]; 138 | } 139 | 140 | std::string word = codec->fromUnicode(items[0]).toStdString(); 141 | std::string text = codec->fromUnicode(items[1]).toStdString(); 142 | 143 | // 読み込み済みのワードは追記する 144 | if (data.find(word) != data.end()) { 145 | data[word] = data[word] + "\n" + text; 146 | } 147 | else { 148 | data[word] = text; 149 | } 150 | } 151 | 152 | file.close(); 153 | 154 | return true; 155 | } 156 | 157 | // PDIC1行テキスト形式の辞書データを読み込む 158 | // PDIC1行テキスト形式について:http://pdic.la.coocan.jp/unicode/help/OneLineFormat.html 159 | bool Dictionary::LoadPDIC1(std::string filename) 160 | { 161 | QTextCodec *codec = QTextCodec::codecForName("UTF-8"); 162 | 163 | // ファイルを開く 164 | // in.setCodec()で指定しなくても、Shift-JISとUnicodeのファイルは正しく読めるっぽい 165 | QFile file(filename.c_str()); 166 | if (!file.open(QIODevice::ReadOnly)) { 167 | return false; 168 | } 169 | QTextStream in(&file); 170 | 171 | QString line; 172 | while (!in.atEnd()) { 173 | line = in.readLine(); 174 | 175 | QStringList items = line.split(codec->toUnicode(" /// ")); 176 | 177 | if (items.size() < 2) { 178 | continue; 179 | } 180 | 181 | // 改行指定「 \ 」を改行に置換 182 | items[1] = items[1].replace(codec->toUnicode(" \\ "), codec->toUnicode("\n")); 183 | 184 | // 訳語と用例の区切りは、辞郎フォーマットに合わせて「■・」に置換しておく(要検討) 185 | items[1] = items[1].replace(codec->toUnicode(" / "), codec->toUnicode(u8"■・")); 186 | 187 | std::string word = codec->fromUnicode(items[0]).toStdString(); 188 | std::string text = codec->fromUnicode(items[1]).toStdString(); 189 | 190 | // 読み込み済みのワードは追記する 191 | if (data.find(word) != data.end()) { 192 | data[word] = data[word] + "\n" + text; 193 | } 194 | else { 195 | data[word] = text; 196 | } 197 | } 198 | 199 | file.close(); 200 | 201 | return true; 202 | } 203 | 204 | bool Dictionary::Find(std::string word, std::string& text) 205 | { 206 | if (word != "" && data.find(word) != data.end()) { 207 | text = data[word]; 208 | return true; 209 | } 210 | return false; 211 | } 212 | 213 | enum DictionaryFormat Dictionary::getFormat(std::string filename) 214 | { 215 | QTextCodec *codec = QTextCodec::codecForName("UTF-8"); 216 | 217 | // ファイルを開く 218 | // C++だとUnicodeのファイル読み込みが難しいので、Qtで読み込み 219 | QFile file(filename.c_str()); 220 | if (!file.open(QIODevice::ReadOnly)) { 221 | return OTHER; 222 | } 223 | QTextStream in(&file); 224 | 225 | // 一行目を読み込み 226 | DictionaryFormat format; 227 | QString first_line = in.readLine(); 228 | 229 | // 1行目に「■」を含む場合は辞郎フォーマットと判断する(要検討) 230 | if (first_line.contains(codec->toUnicode(u8"■"))) { 231 | format = EIJIRO; 232 | } 233 | // 1行目にタブを含む場合はejdicフォーマットと判断する(要検討) 234 | else if (first_line.contains(codec->toUnicode("\t"))) { 235 | format = EJDIC; 236 | } 237 | // 1行目に「///」を含む場合はPDIC1行テキストフォーマットと判断する(要検討) 238 | else if (first_line.contains(codec->toUnicode("///"))) { 239 | format = PDIC1; 240 | } 241 | else { 242 | format = OTHER; 243 | } 244 | 245 | file.close(); 246 | return format; 247 | } -------------------------------------------------------------------------------- /MouseOverDictionary/dictionary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | enum DictionaryFormat 16 | { 17 | EJDIC, 18 | EIJIRO, 19 | PDIC1, 20 | OTHER, 21 | }; 22 | 23 | class Dictionary 24 | { 25 | public: 26 | Dictionary(); 27 | 28 | bool Load(std::string foldername); 29 | bool Find(std::string word, std::string& text); 30 | 31 | private: 32 | std::unordered_map data; 33 | 34 | bool LoadEJDIC(std::string filename); 35 | bool LoadEIJIRO(std::string filename); 36 | bool LoadPDIC1(std::string filename); 37 | enum DictionaryFormat getFormat(std::string filename); 38 | 39 | }; -------------------------------------------------------------------------------- /MouseOverDictionary/image.cpp: -------------------------------------------------------------------------------- 1 | #include "image.h" 2 | 3 | void Image::Flip(void* In, void* Out, int width, int height, unsigned int Bpp) 4 | { 5 | unsigned long Chunk = (Bpp > 24 ? width * 4 : width * 3 + width % 4); 6 | unsigned char* Destination = static_cast(Out); 7 | unsigned char* Source = static_cast(In) + Chunk * (height - 1); 8 | 9 | while (Source != In) 10 | { 11 | std::memcpy(Destination, Source, Chunk); 12 | Destination += Chunk; 13 | Source -= Chunk; 14 | } 15 | } 16 | 17 | Image::Image(HDC DC, int X, int Y, int Width, int Height) : Pixels(), width(Width), height(Height), BitsPerPixel(32) 18 | { 19 | BITMAP Bmp = { 0 }; 20 | HBITMAP hBmp = reinterpret_cast(GetCurrentObject(DC, OBJ_BITMAP)); 21 | 22 | if (GetObject(hBmp, sizeof(BITMAP), &Bmp) == 0) 23 | throw std::runtime_error("BITMAP DC NOT FOUND."); 24 | 25 | RECT area = { X, Y, X + Width, Y + Height }; 26 | HWND Window = WindowFromDC(DC); 27 | GetClientRect(Window, &area); 28 | 29 | HDC MemDC = GetDC(nullptr); 30 | HDC SDC = CreateCompatibleDC(MemDC); 31 | HBITMAP hSBmp = CreateCompatibleBitmap(MemDC, width, height); 32 | DeleteObject(SelectObject(SDC, hSBmp)); 33 | 34 | BitBlt(SDC, 0, 0, width, height, DC, X, Y, SRCCOPY); 35 | StretchBlt(SDC, 0, 0, width, height, DC, X, Y, Width, Height, SRCCOPY); 36 | 37 | unsigned int data_size = ((width * BitsPerPixel + 31) / 32) * 4 * height; 38 | std::vector Data(data_size); 39 | this->Pixels.resize(data_size); 40 | 41 | // キャプチャした画像を画面左上に表示(デバッグ用) 42 | //BitBlt(DC, 0, 0, width, height, SDC, 0, 0, SRCCOPY); 43 | 44 | BITMAPINFO Info = { sizeof(BITMAPINFOHEADER), static_cast(width), static_cast(height), 1, BitsPerPixel, BI_RGB, data_size, 0, 0, 0, 0 }; 45 | GetDIBits(SDC, hSBmp, 0, height, &Data[0], &Info, DIB_RGB_COLORS); 46 | this->Flip(&Data[0], &Pixels[0], width, height, BitsPerPixel); 47 | 48 | DeleteDC(SDC); 49 | DeleteObject(hSBmp); 50 | ReleaseDC(nullptr, MemDC); 51 | } 52 | 53 | // 文字認識精度を上げるため、拡大してキャプチャする 54 | Image::Image(HDC DC, int X, int Y, int Width, int Height, int scale) : Pixels(), width(Width), height(Height), BitsPerPixel(32) 55 | { 56 | width = (int)((width * scale) / 100); 57 | height = (int)((height * scale) / 100); 58 | 59 | BITMAP Bmp = { 0 }; 60 | HBITMAP hBmp = reinterpret_cast(GetCurrentObject(DC, OBJ_BITMAP)); 61 | 62 | if (GetObject(hBmp, sizeof(BITMAP), &Bmp) == 0) 63 | throw std::runtime_error("BITMAP DC NOT FOUND."); 64 | 65 | RECT area = { X, Y, X + Width, Y + Height }; 66 | HWND Window = WindowFromDC(DC); 67 | GetClientRect(Window, &area); 68 | 69 | HDC MemDC = GetDC(nullptr); 70 | HDC SDC = CreateCompatibleDC(MemDC); 71 | HBITMAP hSBmp = CreateCompatibleBitmap(MemDC, width, height); 72 | DeleteObject(SelectObject(SDC, hSBmp)); 73 | 74 | //SetStretchBltMode(SDC, BLACKONWHITE); // 伸縮モードBLACKONWHITE:低画質だが高速、デフォルト設定 75 | //SetStretchBltMode(SDC, COLORONCOLOR); // 伸縮モードCOLORONCOLOR:中画質で高速、デフォルト設定 76 | SetStretchBltMode(SDC, HALFTONE); // 伸縮モードHALFTONE:高画質だが低速 77 | SetBrushOrgEx(SDC, 0, 0, NULL); 78 | StretchBlt(SDC, 0, 0, width, height, DC, X, Y, Width, Height, SRCCOPY); 79 | 80 | unsigned int data_size = ((width * BitsPerPixel + 31) / 32) * 4 * height; 81 | std::vector Data(data_size); 82 | this->Pixels.resize(data_size); 83 | 84 | // キャプチャした画像を画面左上に表示(デバッグ用) 85 | //BitBlt(DC, 0, 0, width, height, SDC, 0, 0, SRCCOPY); 86 | 87 | BITMAPINFO Info = { sizeof(BITMAPINFOHEADER), static_cast(width), static_cast(height), 1, BitsPerPixel, BI_RGB, data_size, 0, 0, 0, 0 }; 88 | GetDIBits(SDC, hSBmp, 0, height, &Data[0], &Info, DIB_RGB_COLORS); 89 | this->Flip(&Data[0], &Pixels[0], width, height, BitsPerPixel); 90 | 91 | DeleteDC(SDC); 92 | DeleteObject(hSBmp); 93 | ReleaseDC(nullptr, MemDC); 94 | } -------------------------------------------------------------------------------- /MouseOverDictionary/image.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Reference: https://stackoverflow.com/questions/22924209 4 | // Author: Brandon https://stackoverflow.com/users/1462718/brandon 5 | // License: Creative Commons Attribution-Share Alike 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | class Image 16 | { 17 | private: 18 | std::vector Pixels; 19 | std::uint32_t width, height; 20 | std::uint16_t BitsPerPixel; 21 | 22 | void Flip(void* In, void* Out, int width, int height, unsigned int Bpp); 23 | 24 | public: 25 | explicit Image(HDC DC, int X, int Y, int Width, int Height); 26 | explicit Image(HDC DC, int X, int Y, int Width, int Height, int scale); 27 | 28 | inline std::uint16_t GetBitsPerPixel() { return this->BitsPerPixel; } 29 | inline std::uint16_t GetBytesPerPixel() { return this->BitsPerPixel / 8; } 30 | inline std::uint16_t GetBytesPerScanLine() { return (this->BitsPerPixel / 8) * this->width; } 31 | inline int GetWidth() const { return this->width; } 32 | inline int GetHeight() const { return this->height; } 33 | inline const std::uint8_t* GetPixels() { return this->Pixels.data(); } 34 | }; -------------------------------------------------------------------------------- /MouseOverDictionary/inflector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Inflector 7 | { 8 | // まとめて原型の候補を生成 9 | public: 10 | bool getInfinitives(std::string word, std::vector& infinitives) { 11 | bool is_infinitive = false; 12 | is_infinitive = !processPlural(word, infinitives); 13 | is_infinitive = !processPastTense(word, infinitives); 14 | is_infinitive = !processParticiple(word, infinitives); 15 | is_infinitive = !processPronoun(word, infinitives); 16 | return is_infinitive; 17 | } 18 | 19 | // 複数形、三人称単数現在形 20 | public: 21 | bool processPlural(int rule_set_num, std::string plural, std::string& singular) { 22 | singular = plural; 23 | bool is_plural = false; 24 | if (rule_set_num > singular_rules_set.size()) { 25 | rule_set_num = singular_rules_set.size(); 26 | } 27 | for (auto singular_rule : singular_rules_set[rule_set_num]) { 28 | if (std::regex_match(plural, std::regex(singular_rule[0]))) { 29 | singular = std::regex_replace(plural, std::regex(singular_rule[0]), singular_rule[1]); 30 | is_plural = true; 31 | return is_plural; 32 | } 33 | } 34 | return is_plural; 35 | } 36 | 37 | bool processPlural(std::string plural, std::vector& singular) { 38 | bool is_plural = false; 39 | for (auto singular_rules : singular_rules_set) { 40 | for (auto singular_rule : singular_rules) { 41 | if (std::regex_match(plural, std::regex(singular_rule[0]))) { 42 | singular.push_back(std::regex_replace(plural, std::regex(singular_rule[0]), singular_rule[1])); 43 | return is_plural; 44 | } 45 | } 46 | } 47 | return is_plural; 48 | } 49 | 50 | private: 51 | // Reference: https://gist.github.com/mrenouf/805745 52 | // Author: mrenouf https://gist.github.com/mrenouf 53 | std::vector>> singular_rules_set = 54 | { 55 | { 56 | {"(.*)people$", "$1person"}, 57 | {"oxen$", "ox"}, 58 | {"children$", "child"}, 59 | {"feet$", "foot"}, 60 | {"teeth$", "tooth"}, 61 | {"geese$", "goose"}, 62 | {"(.*)ives?$", "$1ife"}, 63 | {"(.*)ves?$", "$1f"}, 64 | {"(.*)men$", "$1man"}, 65 | {"(.+[aeiou])ys$", "$1y"}, 66 | {"(.+[^aeiou])ies$", "$1y"}, 67 | {"(.+)zes$", "$1"}, 68 | {"([m|l])ice$", "$1ouse"}, 69 | {"matrices$", "matrix"}, 70 | {"indices$", "index"}, 71 | {"(.+[^aeiou])ices$", "$1ice"}, 72 | {"(.*)ices$", "$1ex"}, 73 | {"(octop|vir)i$", "$1us"}, 74 | {"(.+(s|x|sh|ch))es$", "$1"}, 75 | {"(.+)s$", "$1"}, 76 | },{ 77 | {"(.+)s$", "$1"}, // pulses → pulseなど 78 | },{ 79 | {u8"(.+)'s$", "$1"}, // 所有格、記号の誤認識は多いので、それぞれ処理する 80 | {u8"(.+)'s$", "$1"}, 81 | {u8"(.+)ʼs$", "$1"}, 82 | {u8"(.+)’s$", "$1"}, 83 | {u8"(.+)‘s$", "$1"}, 84 | } 85 | }; 86 | 87 | // 過去形(仮) 88 | public: 89 | bool processPastTense(int rule_set_num, std::string pasttense, std::string& presenttense) { 90 | presenttense = pasttense; 91 | bool is_pasttense = false; 92 | if (rule_set_num > pasttense_rules_set.size()) { 93 | rule_set_num = pasttense_rules_set.size(); 94 | } 95 | for (auto pasttense_rule : pasttense_rules_set[rule_set_num]) { 96 | if (std::regex_match(pasttense, std::regex(pasttense_rule[0]))) { 97 | presenttense = std::regex_replace(pasttense, std::regex(pasttense_rule[0]), pasttense_rule[1]); 98 | is_pasttense = true; 99 | return is_pasttense; 100 | } 101 | } 102 | return is_pasttense; 103 | } 104 | 105 | bool processPastTense(std::string pasttense, std::vector& presenttense) { 106 | bool is_pasttense = false; 107 | for (auto pasttense_rules : pasttense_rules_set) { 108 | for (auto pasttense_rule : pasttense_rules) { 109 | if (std::regex_match(pasttense, std::regex(pasttense_rule[0]))) { 110 | presenttense.push_back(std::regex_replace(pasttense, std::regex(pasttense_rule[0]), pasttense_rule[1])); 111 | is_pasttense = true; 112 | } 113 | } 114 | } 115 | return is_pasttense; 116 | } 117 | 118 | private: 119 | std::vector>> pasttense_rules_set = 120 | { 121 | { 122 | {"abode", "abide"}, 123 | {"addrest", "address"}, 124 | {"arose$", "arise"}, 125 | {"arisen$", "arise"}, 126 | {"awoke$", "awake"}, 127 | {"awoken$", "awake"}, 128 | {"was$", "be"}, 129 | {"were$", "be"}, 130 | {"been$", "be"}, 131 | {"bore$", "bear"}, 132 | {"borne$", "bear"}, 133 | {"born$", "bear"}, 134 | {"beaten$", "beat"}, 135 | {"became$", "become"}, 136 | {"become$", "become"}, 137 | {"began$", "begin"}, 138 | {"begun$", "begin"}, 139 | {"bent$", "bend"}, 140 | {"blew$", "blow"}, 141 | {"blown$", "blow"}, 142 | {"broke$", "break"}, 143 | {"broken$", "break"}, 144 | {"brought$", "bring"}, 145 | {"built$", "build"}, 146 | {"bought$", "buy"}, 147 | {"caught$", "catch"}, 148 | {"chose$", "choose"}, 149 | {"chosen$", "choose"}, 150 | {"came$", "come"}, 151 | {"dealt$", "deal"}, 152 | {"dug$", "dig"}, 153 | {"did$", "do"}, 154 | {"done$", "do"}, 155 | {"drew$", "draw"}, 156 | {"drawn$", "draw"}, 157 | {"drank$", "drink"}, 158 | {"drunk$", "drink"}, 159 | {"drove$", "drive"}, 160 | {"driven$", "drive"}, 161 | {"ate$", "eat"}, 162 | {"eaten$", "eat"}, 163 | {"fell$", "fall"}, 164 | {"fallen$", "fall"}, 165 | {"fed$", "feed"}, 166 | {"felt$", "feel"}, 167 | {"fought$", "fight"}, 168 | {"found$", "find"}, 169 | {"flew$", "fly"}, 170 | {"flown$", "fly"}, 171 | {"forgot$", "forget"}, 172 | {"forgotten$", "forget"}, 173 | {"got$", "get"}, 174 | {"gotten$", "get"}, 175 | {"gave$", "give"}, 176 | {"given$", "give"}, 177 | {"went$", "go"}, 178 | {"gone$", "go"}, 179 | {"grew$", "grow"}, 180 | {"grown$", "grow"}, 181 | {"hung$", "hang"}, 182 | {"had$", "have"}, 183 | {"heard$", "hear"}, 184 | {"hid$", "hide"}, 185 | {"hidden$", "hide"}, 186 | {"held$", "hold"}, 187 | {"kept$", "keep"}, 188 | {"knew$", "know"}, 189 | {"known$", "know"}, 190 | {"laid$", "lay"}, 191 | {"led$", "lead"}, 192 | {"left$", "leave"}, 193 | {"lent$", "lend"}, 194 | {"lay$", "lie"}, 195 | {"lain$", "lie"}, 196 | {"lost$", "lose"}, 197 | {"made$", "make"}, 198 | {"met$", "meet"}, 199 | {"mistaken", "mistake"}, 200 | {"mistook", "mistake"}, 201 | {"paid$", "pay"}, 202 | {"rode$", "ride"}, 203 | {"ridden$", "ride"}, 204 | {"rang$", "ring"}, 205 | {"rung$", "ring"}, 206 | {"rose$", "rise"}, 207 | {"risen$", "rise"}, 208 | {"ran$", "run"}, 209 | {"sawn$", "saw"}, 210 | {"said$", "say"}, 211 | {"saw$", "see"}, 212 | {"seen$", "see"}, 213 | {"sought$", "seek"}, 214 | {"sold$", "sell"}, 215 | {"sent$", "send"}, 216 | {"sewed$", "sew"}, 217 | {"sewn$", "sew"}, 218 | {"shook$", "shake"}, 219 | {"shaken$", "shake"}, 220 | {"shot$", "shoot"}, 221 | {"shown$", "show"}, 222 | {"shrank$", "shrink"}, 223 | {"sang$", "sing"}, 224 | {"sung$", "sing"}, 225 | {"sank$", "sink"}, 226 | {"sunk$", "sink"}, 227 | {"slept$", "sleep"}, 228 | {"spoke$", "speak"}, 229 | {"spoken$", "speak"}, 230 | {"spent$", "spend"}, 231 | {"stood$", "stand"}, 232 | {"stole$", "steal"}, 233 | {"stolen$", "steal"}, 234 | {"stuck$", "stick"}, 235 | {"swore$", "swear"}, 236 | {"sworn$", "swear"}, 237 | {"swam$", "swim"}, 238 | {"swum$", "swim"}, 239 | {"took$", "take"}, 240 | {"taken$", "take"}, 241 | {"taught$", "teach"}, 242 | {"tore$", "tear"}, 243 | {"torn$", "tear"}, 244 | {"told$", "tell"}, 245 | {"thought$", "think"}, 246 | {"threw$", "throw"}, 247 | {"thrown$", "throw"}, 248 | {"understood$", "understand"}, 249 | {"undertook$", "undertake" }, 250 | {"undertaken$", "undertake" }, 251 | {"woke$", "wake"}, 252 | {"woken$", "wake"}, 253 | {"wore$", "wear"}, 254 | {"worn$", "wear"}, 255 | {"wove$", "weave"}, 256 | {"woven$", "weave"}, 257 | {"won$", "win"}, 258 | {"withdrew$", "withdraw"}, 259 | {"withdrawn$", "withdraw"}, 260 | {"withheld$", "withhold"}, 261 | {"withstood$", "withstand"}, 262 | {"wrote$", "write"}, 263 | {"written$", "write"}, 264 | {"(.+)lit", "$1alight" }, 265 | {"(.+)slid", "$1slide" }, 266 | {"(.+)slidden", "$1slide" }, 267 | {"(.+)bit$", "$1bite"}, 268 | {"(.+)bitten$", "$1bite"}, 269 | {"(.+)sat$", "$1sit"}, 270 | {"(.+)cked$", "$1c"}, 271 | {"(.+)nned$", "$1n"}, 272 | {"(.+)tted$", "$1t"}, 273 | {"(.+)dded$", "$1d"}, 274 | {"(.+)gged$", "$1g"}, 275 | {"(.+)pped$", "$1p"}, 276 | {"(.+)mmed$", "$1m"}, 277 | {"(.+)bbed$", "$1b"}, 278 | {"(.+)rred$", "$1r"}, 279 | {"(.+)zzed$", "$1z"}, 280 | {"(.+)ied$", "$1y"}, 281 | {"(.+)ed$", "$1"}, 282 | }, 283 | { 284 | {"(.+)ed$", "$1e"}, // agreed → agreeなど 285 | } 286 | }; 287 | 288 | 289 | // 現在分詞(仮) 290 | public: 291 | bool processParticiple(int rule_set_num, std::string participle, std::string& infinitive) { 292 | infinitive = participle; 293 | bool is_participle = false; 294 | if (rule_set_num > participle_rules_set.size()) { 295 | rule_set_num = participle_rules_set.size(); 296 | } 297 | for (auto participle_rule : participle_rules_set[rule_set_num]) { 298 | if (std::regex_match(participle, std::regex(participle_rule[0]))) { 299 | infinitive = std::regex_replace(participle, std::regex(participle_rule[0]), participle_rule[1]); 300 | is_participle = true; 301 | return is_participle; 302 | } 303 | } 304 | return is_participle; 305 | } 306 | 307 | bool processParticiple(std::string participle, std::vector& infinitives) { 308 | bool is_participle = false; 309 | for (auto participle_rules : participle_rules_set) { 310 | for (auto participle_rule : participle_rules) { 311 | if (std::regex_match(participle, std::regex(participle_rule[0]))) { 312 | infinitives.push_back(std::regex_replace(participle, std::regex(participle_rule[0]), participle_rule[1])); 313 | is_participle = true; 314 | } 315 | } 316 | } 317 | return is_participle; 318 | } 319 | 320 | private: 321 | std::vector>> participle_rules_set = 322 | { 323 | { 324 | {"(.+)cking$", "$1c"}, // picnicking → picnicなど 325 | {"(.+)nning$", "$1n"}, 326 | {"(.+)tting$", "$1t"}, 327 | {"(.+)dding$", "$1d"}, 328 | {"(.+)pping$", "$1p"}, 329 | {"(.+)mming$", "$1m"}, 330 | {"(.+)bbing$", "$1b"}, 331 | {"(.+)rring$", "$1r"}, 332 | {"(.+)lling$", "$1l"}, 333 | {"(.+)zzing$", "$1z"}, 334 | {"(.+)ying$", "$1ie"}, // dying → dieなど 335 | {"(.+)ing$", "$1"}, 336 | }, 337 | { 338 | {"(.+)ing$", "$1e"}, // coming → comeなど 339 | }, 340 | { 341 | {"(.+)ing$", "$1"}, // replaying → replayなど 342 | } 343 | }; 344 | 345 | // 代名詞 346 | public: 347 | bool processPronoun(int rule_set_num, std::string pronoun, std::string& general) { 348 | general = pronoun; 349 | bool is_pronoun = false; 350 | if (rule_set_num > pronoun_rules_set.size()) { 351 | rule_set_num = pronoun_rules_set.size(); 352 | } 353 | for (auto pronoun_rule : pronoun_rules_set[rule_set_num]) { 354 | if (std::regex_match(pronoun, std::regex(pronoun_rule[0]))) { 355 | general = std::regex_replace(pronoun, std::regex(pronoun_rule[0]), pronoun_rule[1]); 356 | is_pronoun = true; 357 | return is_pronoun; 358 | } 359 | } 360 | return is_pronoun; 361 | } 362 | 363 | bool processPronoun(std::string pronoun, std::vector& general) { 364 | bool is_pronoun = false; 365 | for (auto pronoun_rules : pronoun_rules_set) { 366 | for (auto pronoun_rule : pronoun_rules) { 367 | if (std::regex_match(pronoun, std::regex(pronoun_rule[0]))) { 368 | general.push_back(std::regex_replace(pronoun, std::regex(pronoun_rule[0]), pronoun_rule[1])); 369 | is_pronoun = true; 370 | } 371 | } 372 | } 373 | return is_pronoun; 374 | } 375 | 376 | private: 377 | // Reference: https://github.com/wtetsu/mouse-dictionary/tree/master/rule 378 | // Author: wtetsu https://github.com/wtetsu 379 | std::vector>> pronoun_rules_set = 380 | { 381 | { 382 | {"my", "one's"}, 383 | {"your", "one's"}, 384 | {"his", "one's"}, 385 | {"her", "one's"}, 386 | {"its", "one's"}, 387 | {"our", "one's"}, 388 | {"their", "one's"}, 389 | {"'s", "one's"}, 390 | {"one's", "one's"}, 391 | {"someone's", "someone's"}, 392 | {"myself", "oneself"}, 393 | {"yourself", "oneself"}, 394 | {"himself", "oneself"}, 395 | {"herself", "oneself"}, 396 | {"ourselves", "oneself"}, 397 | {"themselves", "oneself"}, 398 | {"him", "someone"}, 399 | {"them", "someone"}, 400 | {"us", "someone"} 401 | }, 402 | { 403 | {"my", "someone's"}, 404 | {"your", "someone's"}, 405 | {"his", "someone's"}, 406 | {"her", "someone's"}, 407 | {"its", "someone's"}, 408 | {"our", "someone's"}, 409 | {"their", "someone's"}, 410 | {"'s", "someone's"}, 411 | {"one's", "one's"}, 412 | {"someone's", "someone's"}, 413 | {"myself", "oneself"}, 414 | {"yourself", "oneself"}, 415 | {"himself", "oneself"}, 416 | {"herself", "oneself"}, 417 | {"ourselves", "oneself"}, 418 | {"themselves", "oneself"}, 419 | {"him", "someone"}, 420 | {"them", "someone"}, 421 | {"us", "someone"} 422 | }, 423 | { 424 | {"my", "someone's"}, 425 | {"your", "someone's"}, 426 | {"his", "someone's"}, 427 | {"her", "someone"}, 428 | {"its", "someone's"}, 429 | {"our", "someone's"}, 430 | {"their", "someone's"}, 431 | {"'s", "someone's"}, 432 | {"one's", "one's"}, 433 | {"someone's", "someone's"}, 434 | {"myself", "oneself"}, 435 | {"yourself", "oneself"}, 436 | {"himself", "oneself"}, 437 | {"herself", "oneself"}, 438 | {"ourselves", "oneself"}, 439 | {"themselves", "oneself"}, 440 | {"him", "someone"}, 441 | {"them", "someone"}, 442 | {"us", "someone"} 443 | } 444 | }; 445 | }; 446 | 447 | -------------------------------------------------------------------------------- /MouseOverDictionary/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mouse_over_dictionary.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | // 高DPI用の設定 7 | QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 8 | 9 | QApplication a(argc, argv); 10 | MouseOverDictionary w; 11 | 12 | w.show(); 13 | return a.exec(); 14 | } -------------------------------------------------------------------------------- /MouseOverDictionary/mini_window.cpp: -------------------------------------------------------------------------------- 1 | #include "mini_window.h" 2 | 3 | MiniWindow::MiniWindow(QWidget *parent) 4 | : QWidget(parent) 5 | { 6 | ui.setupUi(this); 7 | } 8 | 9 | MiniWindow::~MiniWindow() 10 | { 11 | } 12 | 13 | void MiniWindow::setRelativePos(int x, int y) 14 | { 15 | relative_pos_x = x; 16 | relative_pos_y = y; 17 | } 18 | 19 | void MiniWindow::followCursor() 20 | { 21 | POINT po; 22 | GetCursorPos(&po); 23 | this->move(po.x + relative_pos_x, po.y + relative_pos_y); 24 | } 25 | 26 | void MiniWindow::setHtml(QString text) 27 | { 28 | ui.textBrowser->setHtml(text); 29 | } 30 | 31 | void MiniWindow::toggleShow(bool toggle) 32 | { 33 | if (toggle) { 34 | this->show(); 35 | } 36 | else { 37 | this->hide(); 38 | } 39 | } -------------------------------------------------------------------------------- /MouseOverDictionary/mini_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "ui_mini_window.h" 7 | 8 | class MiniWindow : public QWidget 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | MiniWindow(QWidget *parent = Q_NULLPTR); 14 | ~MiniWindow(); 15 | void setRelativePos(int x, int y); 16 | 17 | public slots: 18 | void followCursor(); 19 | void setHtml(QString text); 20 | void toggleShow(bool toggle); 21 | 22 | private: 23 | int relative_pos_x; 24 | int relative_pos_y; 25 | Ui::MiniWindow ui; 26 | }; 27 | -------------------------------------------------------------------------------- /MouseOverDictionary/mini_window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MiniWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 250 10 | 70 11 | 12 | 13 | 14 | Mini Window 15 | 16 | 17 | 0.800000000000000 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | QFrame::NoFrame 36 | 37 | 38 | Qt::ScrollBarAlwaysOff 39 | 40 | 41 | Qt::ScrollBarAlwaysOff 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /MouseOverDictionary/mouse_over_dictionary.cpp: -------------------------------------------------------------------------------- 1 | #include "mouse_over_dictionary.h" 2 | 3 | MouseOverDictionary::MouseOverDictionary(QWidget *parent) 4 | : QMainWindow(parent) 5 | { 6 | ui.setupUi(this); 7 | 8 | // スクリーンのサイズ取得 9 | QScreen *screen = QGuiApplication::primaryScreen(); 10 | QRect screenGeometry = screen->geometry(); 11 | screen_width = screenGeometry.width(); 12 | screen_height = screenGeometry.height(); 13 | 14 | // 設定ファイル読み込み 15 | ReadSettings(); 16 | 17 | // 透明度設定 18 | this->setWindowOpacity(main_window_opacity); 19 | 20 | // 前回終了時のウィンドウ位置、サイズを復元 21 | this->move(main_window_pos_x, main_window_pos_y); 22 | this->resize(main_window_width, main_window_height); 23 | 24 | // 前回終了時の最前面設定を復元 25 | if (stay_top == true) { 26 | this->setWindowFlags(Qt::WindowStaysOnTopHint); 27 | ui.toolButton_2->setIcon(QIcon(":/MouseOverDictionary/Resources/push-pin-2-line.png")); 28 | } 29 | else { 30 | ui.toolButton_2->setIcon(QIcon(":/MouseOverDictionary/Resources/push-pin-line.png")); 31 | } 32 | 33 | 34 | // マウス追従ウィンドウ作成 35 | // 最小化や最前面設定を独立にしたいので、メインウィンドウの子(new MiniWindow(this);)にはしない 36 | // closeEventで削除する 37 | mini_window = new MiniWindow(); 38 | 39 | // マウス追従ウィンドウを最前面、枠なしに指定 40 | mini_window->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); 41 | 42 | // 透明度、サイズ、位置設定 43 | mini_window->setWindowOpacity(mini_window_opacity); 44 | mini_window->resize(mini_window_width, mini_window_height); 45 | mini_window->setRelativePos(mini_window_pos_x, mini_window_pos_y); 46 | 47 | // マウス追従ウィンドウをカーソルに追従 48 | auto timer = new QTimer(this); 49 | timer->setInterval(15); 50 | timer->setSingleShot(false); 51 | connect(timer, SIGNAL(timeout()), mini_window, SLOT(followCursor())); 52 | timer->start(); 53 | 54 | // 前回終了時のマウス追従ウィンドウの表示状態を復元 55 | if (mini_show == true) { 56 | mini_window->show(); 57 | ui.toolButton->setIcon(QIcon(":/MouseOverDictionary/Resources/chat-4-line.png")); 58 | } 59 | else { 60 | mini_window->hide(); 61 | ui.toolButton->setIcon(QIcon(":/MouseOverDictionary/Resources/chat-off-line.png")); 62 | } 63 | 64 | 65 | // 画面停止ウィンドウ作成 66 | // 最小化や最前面設定を独立にしたいので、メインウィンドウの子(new PauseWindow(this);)にはしない 67 | // closeEventで削除する 68 | pause_window = new PauseWindow(); 69 | 70 | // 画面停止ウィンドウ設定 71 | pause_window->setWindowOpacity(pause_window_opacity); 72 | pause_window->setStyleSheet(QString::fromStdString("QScrollArea {border: " + std::to_string(pause_window_border_width) + "px solid " + pause_window_border_color + ";}")); 73 | 74 | 75 | // 検索履歴をダブルクリックで編集できないように設定 76 | ui.listView->setEditTriggers(QAbstractItemView::NoEditTriggers); 77 | 78 | // 編集履歴のサイズを小さく設定 79 | ui.splitter->setSizes(QList() << 100 << 300); 80 | 81 | // 検索履歴モデルを設定 82 | historyModel = new QStringListModel(this); 83 | QStringList historyList; 84 | historyModel->setStringList(historyList); 85 | ui.listView->setModel(historyModel); 86 | 87 | // 前回終了時の検索履歴表示状態を復元 88 | if (history_show == true) { 89 | ui.toolButton_3->setIcon(QIcon(":/MouseOverDictionary/Resources/side-bar-line.png")); 90 | ui.listView->show(); 91 | } 92 | else { 93 | ui.toolButton_3->setIcon(QIcon(":/MouseOverDictionary/Resources/side-bar-line-hide.png")); 94 | ui.listView->hide(); 95 | } 96 | 97 | 98 | // 準備完了時にワード欄を有効化 99 | connect(&thread, SIGNAL(ready(bool)), ui.lineEdit, SLOT(setEnabled(bool))); 100 | connect(&thread, SIGNAL(ready(bool)), this, SLOT(setReady(bool))); 101 | 102 | // 文字認識の結果をワード欄にセット 103 | connect(&thread, SIGNAL(wordChanged(QString)), ui.lineEdit, SLOT(setText(QString))); 104 | 105 | // ワード欄が変化した場合(文字認識 or 直接入力 or 履歴クリック)に、辞書を検索する 106 | connect(ui.lineEdit, SIGNAL(textChanged(QString)), &thread, SLOT(search(QString))); 107 | 108 | // 辞書検索の結果をテキスト欄にセット 109 | connect(&thread, SIGNAL(mainTextChanged(QString)), ui.textBrowser, SLOT(setHtml(QString))); 110 | connect(&thread, SIGNAL(miniTextChanged(QString)), mini_window, SLOT(setHtml(QString))); 111 | 112 | // 検索履歴を記録 113 | connect(&thread, SIGNAL(wordFound(QString)), this, SLOT(updateHistory(QString))); 114 | 115 | // 検索履歴をクリックしたときに、その項目をワード欄にセット 116 | connect(ui.listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), 117 | this, SLOT(searchByHistory(QItemSelection))); 118 | 119 | // クリップボードが変化したときに、その内容をワード欄にセット 120 | clipboard = QApplication::clipboard(); 121 | connect(clipboard, SIGNAL(dataChanged()), this, SLOT(setFromClipboard())); 122 | 123 | 124 | // キーボードショートカット設定 125 | // new UGlobalHotkeys(this)として紐づけると、なぜかメインウィンドウの左上部分がクリックできなくなった 126 | // なので紐づけずにcloseEvent()で削除することに 127 | hm_show_hide_both = new UGlobalHotkeys(); 128 | hm_show_hide_both->registerHotkey(hotkey_show_hide_both); 129 | connect(hm_show_hide_both, &UGlobalHotkeys::activated, [&](size_t id) { 130 | if (enable_shortcut) { 131 | showHide(); 132 | } 133 | }); 134 | 135 | hm_show_hide_mini = new UGlobalHotkeys(); 136 | hm_show_hide_mini->registerHotkey(hotkey_show_hide_mini); 137 | connect(hm_show_hide_mini, &UGlobalHotkeys::activated, [&](size_t id) { 138 | if (enable_shortcut) { 139 | showHideMini(); 140 | } 141 | }); 142 | 143 | hm_show_hide_pause = new UGlobalHotkeys(); 144 | hm_show_hide_pause->registerHotkey(hotkey_screen_pause); 145 | connect(hm_show_hide_pause, &UGlobalHotkeys::activated, [&](size_t id) { 146 | if (enable_shortcut) { 147 | showPauseWindow(); 148 | } 149 | }); 150 | 151 | // 文字認識スレッド開始 152 | thread.start(); 153 | 154 | } 155 | 156 | MouseOverDictionary::~MouseOverDictionary() 157 | { 158 | // 設定ファイル書き込み 159 | WriteSettings(); 160 | } 161 | 162 | void MouseOverDictionary::ReadSettings() 163 | { 164 | QSettings settings("settings.ini", QSettings::IniFormat); 165 | settings.setIniCodec(QTextCodec::codecForName("UTF-8")); 166 | 167 | // デザインの設定 168 | settings.beginGroup("Color"); 169 | main_window_word_font_color = settings.value("MainWindowWord", "#000088").toString().toStdString(); 170 | main_window_text_font_color = settings.value("MainWindowText", "#101010").toString().toStdString(); 171 | main_window_mark_font_color = settings.value("MainWindowMark", "#008000").toString().toStdString(); 172 | main_window_background_color = settings.value("MainWindowBackground", "#ffffff").toString().toStdString(); 173 | mini_window_word_font_color = settings.value("MiniWindowWord", "#000088").toString().toStdString(); 174 | mini_window_text_font_color = settings.value("MiniWindowText", "#101010").toString().toStdString(); 175 | mini_window_background_color = settings.value("MiniWindowBackground", "#ffffff").toString().toStdString(); 176 | settings.endGroup(); 177 | 178 | settings.beginGroup("FontSize"); 179 | main_window_word_font_size = settings.value("MainWindowWord", 10).toInt(); 180 | main_window_text_font_size = settings.value("MainWindowText", 10).toInt(); 181 | main_window_mark_font_size = settings.value("MainWindowMark", 10).toInt(); 182 | mini_window_word_font_size = settings.value("MiniWindowWord", 10).toInt(); 183 | mini_window_text_font_size = settings.value("MiniWindowText", 10).toInt(); 184 | settings.endGroup(); 185 | 186 | 187 | // キーボードショートカットの設定 188 | settings.beginGroup("Shortcut"); 189 | enable_shortcut = settings.value("Enable", true).toBool(); 190 | hotkey_show_hide_both = settings.value("ShowHideBothWindow", "Ctrl+Alt+Z").toString(); 191 | hotkey_show_hide_mini = settings.value("ShowHideMiniWindow", "Ctrl+Alt+X").toString(); 192 | hotkey_screen_pause = settings.value("ScreenPause", "Ctrl+Alt+A").toString(); 193 | settings.endGroup(); 194 | 195 | 196 | // 各ウィンドウの設定 197 | settings.beginGroup("Window"); 198 | main_window_opacity = settings.value("MainWindowOpacity", 0.9).toDouble(); 199 | mini_window_opacity = settings.value("MiniWindowOpacity", 0.9).toDouble(); 200 | mini_window_width = settings.value("MiniWindowWidth", 250).toInt(); 201 | mini_window_height = settings.value("MiniWindowHeight", 70).toInt(); 202 | mini_window_pos_x = settings.value("MiniWindowPositionX", 0).toInt(); // マウス追従ウィンドウがカーソルに被るとクリックができなくなるので注意 203 | mini_window_pos_y = settings.value("MiniWindowPositionY", 25).toInt(); 204 | settings.endGroup(); 205 | 206 | 207 | // 画面停止機能の設定 208 | settings.beginGroup("ScreenPause"); 209 | pause_window_border_width = settings.value("BorderThickness", 1).toInt(); 210 | pause_window_border_color = settings.value("BorderColor", "#ff4500").toString().toStdString(); 211 | pause_window_opacity = settings.value("Opacity", 0.9).toDouble(); 212 | settings.endGroup(); 213 | 214 | 215 | // 文字認識の設定 216 | settings.beginGroup("OCR"); 217 | // 画面キャプチャの範囲、カーソルから左右上下のピクセル数、キャプチャ幅 = AreaLeft + AreaRight、キャプチャ高さ = AreaTop + AreaBottom、範囲:1~500 218 | ocr_area_left = settings.value("CaptureAreaLeft", 50).toInt(); 219 | ocr_area_right = settings.value("CaptureAreaRight", 150).toInt(); 220 | ocr_area_top = settings.value("CaptureAreaTop", 20).toInt(); 221 | ocr_area_bottom = settings.value("CaptureAreaBottom", 20).toInt(); 222 | // 文字認識の精度を上げるためにキャプチャ後の画像を拡大する倍率、100→等倍、150→1.5倍、範囲:100~200 223 | ocr_scale = settings.value("CaptureScale", 130).toInt(); 224 | settings.endGroup(); 225 | 226 | 227 | // 前回終了時のウィンドウの情報 // saveGeometry()、restoreGeometry()という機能もあるらしいけど、とりあえずこれで 228 | settings.beginGroup("PreviousState"); 229 | main_window_width = settings.value("MainWindowWidth", 350).toInt(); 230 | main_window_height = settings.value("MainWindowHeight", 300).toInt(); 231 | main_window_pos_x = settings.value("MainWindowPositionX", (screen_width - main_window_width) / 2).toInt(); // デフォルトは画面の中央に 232 | main_window_pos_y = settings.value("MainWindowPositionY", (screen_height - main_window_height) / 2).toInt(); 233 | mini_show = settings.value("ShowMiniWindow", false).toBool(); 234 | history_show = settings.value("ShowHistory", false).toBool(); 235 | stay_top = settings.value("AlwaysOnTop", true).toBool(); 236 | // MainWindowHistoryWidth 237 | 238 | 239 | // 不正な値が指定されてしまわないように、色コードをチェック(要検討) 240 | std::regex re_color("^#([\\da-fA-F]{6}|[\\da-fA-F]{3})$"); 241 | if (std::regex_match(main_window_word_font_color , re_color) != 1) main_window_word_font_color = "#000088"; 242 | if (std::regex_match(main_window_text_font_color , re_color) != 1) main_window_text_font_color = "#101010"; 243 | if (std::regex_match(main_window_mark_font_color , re_color) != 1) main_window_mark_font_color = "#008000"; 244 | if (std::regex_match(main_window_background_color, re_color) != 1) main_window_background_color = "#ffffff"; 245 | if (std::regex_match(mini_window_word_font_color , re_color) != 1) mini_window_word_font_color = "#000088"; 246 | if (std::regex_match(mini_window_text_font_color , re_color) != 1) mini_window_text_font_color = "#101010"; 247 | if (std::regex_match(mini_window_background_color, re_color) != 1) mini_window_background_color = "#ffffff"; 248 | if (std::regex_match(pause_window_border_color , re_color) != 1) pause_window_border_color = "#ff4500"; 249 | 250 | // 不正な値が指定されてしまわないように、各パラメータを範囲内に収める(要検討) 251 | main_window_word_font_size = std::clamp(main_window_word_font_size, 3, 32); 252 | main_window_text_font_size = std::clamp(main_window_text_font_size, 3, 32); 253 | main_window_mark_font_size = std::clamp(main_window_mark_font_size, 3, 32); 254 | mini_window_word_font_size = std::clamp(mini_window_word_font_size, 3, 32); 255 | mini_window_text_font_size = std::clamp(mini_window_text_font_size, 3, 32); 256 | main_window_opacity = std::clamp(main_window_opacity, 0.0, 1.0); 257 | mini_window_opacity = std::clamp(mini_window_opacity, 0.0, 1.0); 258 | pause_window_opacity = std::clamp(pause_window_opacity, 0.0, 1.0); 259 | ocr_scale = std::clamp(ocr_scale, 100, 200); 260 | ocr_area_left = std::clamp(ocr_area_left, 1, 500); 261 | ocr_area_right = std::clamp(ocr_area_right, 1, 500); 262 | ocr_area_top = std::clamp(ocr_area_top, 1, 500); 263 | ocr_area_bottom = std::clamp(ocr_area_bottom, 1, 500); 264 | if (main_window_width < 0) mini_window_width = 0; 265 | if (main_window_height < 0) mini_window_height = 0; 266 | if (mini_window_width < 0) mini_window_width = 0; 267 | if (mini_window_height < 0) mini_window_height = 0; 268 | if (pause_window_border_width < 0) pause_window_border_width = 0; 269 | 270 | // 各パラメータを文字認識スレッドにセット 271 | thread.setMainFontColor(main_window_word_font_color, main_window_text_font_color, main_window_mark_font_color, main_window_background_color); 272 | thread.setMiniFontColor(mini_window_word_font_color, mini_window_text_font_color, mini_window_background_color); 273 | thread.setMainFontSize(main_window_word_font_size, main_window_text_font_size, main_window_mark_font_size); 274 | thread.setMiniFontSize(mini_window_word_font_size, mini_window_text_font_size); 275 | thread.setOcrScale(ocr_scale); 276 | thread.setOcrRoi(ocr_area_left, ocr_area_right, ocr_area_top, ocr_area_bottom); 277 | 278 | } 279 | 280 | void MouseOverDictionary::WriteSettings() 281 | { 282 | QSettings settings("settings.ini", QSettings::IniFormat); 283 | settings.setIniCodec(QTextCodec::codecForName("UTF-8")); 284 | 285 | settings.beginGroup("Color"); 286 | settings.setValue("MainWindowWord", QString::fromStdString(main_window_word_font_color)); 287 | settings.setValue("MainWindowText", QString::fromStdString(main_window_text_font_color)); 288 | settings.setValue("MainWindowMark", QString::fromStdString(main_window_mark_font_color)); 289 | settings.setValue("MainWindowBackground", QString::fromStdString(main_window_background_color)); 290 | settings.setValue("MiniWindowWord", QString::fromStdString(mini_window_word_font_color)); 291 | settings.setValue("MiniWindowText", QString::fromStdString(mini_window_text_font_color)); 292 | settings.setValue("MiniWindowBackground", QString::fromStdString(mini_window_background_color)); 293 | settings.endGroup(); 294 | 295 | settings.beginGroup("FontSize"); 296 | settings.setValue("MainWindowWord", main_window_word_font_size); 297 | settings.setValue("MainWindowText", main_window_text_font_size); 298 | settings.setValue("MainWindowMark", main_window_mark_font_size); 299 | settings.setValue("MiniWindowWord", mini_window_word_font_size); 300 | settings.setValue("MiniWindowText", mini_window_text_font_size); 301 | settings.endGroup(); 302 | 303 | settings.beginGroup("Shortcut"); 304 | settings.setValue("Enable", enable_shortcut); 305 | settings.setValue("ShowHideBothWindow", hotkey_show_hide_both); 306 | settings.setValue("ShowHideMiniWindow", hotkey_show_hide_mini); 307 | settings.setValue("ScreenPause", hotkey_screen_pause); 308 | settings.endGroup(); 309 | 310 | settings.beginGroup("Window"); 311 | settings.setValue("MainWindowOpacity", main_window_opacity); 312 | settings.setValue("MiniWindowOpacity", mini_window_opacity); 313 | settings.setValue("MiniWindowWidth", mini_window_width); 314 | settings.setValue("MiniWindowHeight", mini_window_height); 315 | settings.setValue("MiniWindowPositionX", mini_window_pos_x); 316 | settings.setValue("MiniWindowPositionY", mini_window_pos_y); 317 | settings.endGroup(); 318 | 319 | settings.beginGroup("ScreenPause"); 320 | settings.setValue("BorderThickness", pause_window_border_width); 321 | settings.setValue("BorderColor", QString::fromStdString(pause_window_border_color)); 322 | settings.setValue("Opacity", pause_window_opacity); 323 | settings.endGroup(); 324 | 325 | settings.beginGroup("OCR"); 326 | settings.setValue("CaptureAreaLeft", ocr_area_left); 327 | settings.setValue("CaptureAreaRight", ocr_area_right); 328 | settings.setValue("CaptureAreaTop", ocr_area_top); 329 | settings.setValue("CaptureAreaBottom", ocr_area_bottom); 330 | settings.setValue("CaptureScale", ocr_scale); 331 | settings.endGroup(); 332 | 333 | // 前回終了時のウィンドウの情報 334 | settings.beginGroup("PreviousState"); 335 | settings.setValue("MainWindowWidth", main_window_width); 336 | settings.setValue("MainWindowHeight", main_window_height); 337 | settings.setValue("MainWindowPositionX", main_window_pos_x); 338 | settings.setValue("MainWindowPositionY", main_window_pos_y); 339 | settings.setValue("ShowMiniWindow", mini_show); 340 | settings.setValue("ShowHistory", history_show); 341 | settings.setValue("AlwaysOnTop", stay_top); 342 | // MainWindowHistoryWidth 343 | settings.endGroup(); 344 | 345 | } 346 | 347 | void MouseOverDictionary::on_toolButton_clicked() 348 | { 349 | if (mini_show == false) { 350 | mini_window->show(); 351 | ui.toolButton->setIcon(QIcon(":/MouseOverDictionary/Resources/chat-4-line.png")); 352 | } 353 | else { 354 | mini_window->hide(); 355 | ui.toolButton->setIcon(QIcon(":/MouseOverDictionary/Resources/chat-off-line.png")); 356 | } 357 | 358 | mini_show = !mini_show; 359 | } 360 | 361 | void MouseOverDictionary::on_toolButton_2_clicked() 362 | { 363 | // https://stackoverflow.com/questions/2855968/how-do-i-toggle-always-on-top-for-a-qmainwindow-in-qt-without-causing-a-flicke 364 | #ifdef Q_OS_WIN 365 | if (stay_top == false) 366 | { 367 | SetWindowPos((HWND)this->winId(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); 368 | } 369 | else 370 | { 371 | SetWindowPos((HWND)this->winId(), HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); 372 | } 373 | #else 374 | Qt::WindowFlags flags = this->windowFlags(); 375 | if (stay_top == false) 376 | { 377 | this->setWindowFlags(flags | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint); 378 | this->show(); 379 | } 380 | else 381 | { 382 | this->setWindowFlags(flags ^ (Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint)); 383 | this->show(); 384 | } 385 | #endif 386 | 387 | if (stay_top == false) { 388 | ui.toolButton_2->setIcon(QIcon(":/MouseOverDictionary/Resources/push-pin-2-line.png")); 389 | } 390 | else { 391 | ui.toolButton_2->setIcon(QIcon(":/MouseOverDictionary/Resources/push-pin-line.png")); 392 | } 393 | 394 | stay_top = !stay_top; 395 | } 396 | 397 | void MouseOverDictionary::on_toolButton_3_clicked() 398 | { 399 | if (history_show == false) { 400 | ui.listView->show(); 401 | ui.toolButton_3->setIcon(QIcon(":/MouseOverDictionary/Resources/side-bar-line.png")); 402 | } 403 | else { 404 | ui.listView->hide(); 405 | ui.toolButton_3->setIcon(QIcon(":/MouseOverDictionary/Resources/side-bar-line-hide.png")); 406 | 407 | } 408 | 409 | history_show = !history_show; 410 | } 411 | 412 | void MouseOverDictionary::setReady(bool ready) 413 | { 414 | thread_ready = ready; 415 | thread.setWindowSize(this->frameGeometry().width(), this->frameGeometry().height()); 416 | thread.setWindowPos(this->pos().x(), this->pos().y()); 417 | } 418 | 419 | void MouseOverDictionary::resizeEvent(QResizeEvent *event) 420 | { 421 | // ウィンドウ内のサイズ 422 | main_window_width = this->geometry().width(); 423 | main_window_height = this->geometry().height(); 424 | 425 | // メインウィンドウ内では文字認識を走らせないようにするために、現在サイズを指定 426 | if (thread_ready) { 427 | // ウィンドウの枠なども含めたサイズを指定 https://doc.qt.io/archives/4.3/geometry.html 428 | thread.setWindowSize(this->frameGeometry().width(), this->frameGeometry().height()); 429 | } 430 | QWidget::resizeEvent(event); 431 | } 432 | 433 | void MouseOverDictionary::moveEvent(QMoveEvent *event) 434 | { 435 | main_window_pos_x = this->pos().x(); 436 | main_window_pos_y = this->pos().y(); 437 | 438 | // メインウィンドウ内では文字認識を走らせないようにするために、現在位置を指定 439 | if (thread_ready) { 440 | thread.setWindowPos(main_window_pos_x, main_window_pos_y); 441 | } 442 | QWidget::moveEvent(event); 443 | } 444 | 445 | void MouseOverDictionary::closeEvent(QCloseEvent *event) 446 | { 447 | // delete *** よりも ***->deleteLater() の方が安全らしい https://stackoverflow.com/questions/39407564/difference-between-hide-close-and-show-in-qt 448 | mini_window->deleteLater(); 449 | pause_window->deleteLater(); 450 | hm_show_hide_both->deleteLater(); 451 | hm_show_hide_mini->deleteLater(); 452 | hm_show_hide_pause->deleteLater(); 453 | QWidget::closeEvent(event); 454 | } 455 | 456 | void MouseOverDictionary::changeEvent(QEvent * event) 457 | { 458 | QMainWindow::changeEvent(event); 459 | if (event->type() == QEvent::WindowStateChange) 460 | { 461 | 462 | // メインウィンドウ最小化時に文字読み取りをオフにする 463 | if (windowState() == Qt::WindowMinimized) 464 | { 465 | thread.disableOcr(); 466 | if (mini_show == true) { 467 | mini_window->hide(); 468 | } 469 | 470 | } 471 | else if (windowState() == Qt::WindowNoState) 472 | { 473 | thread.enableOcr(); 474 | if (mini_show == true) { 475 | mini_window->show(); 476 | } 477 | } 478 | } 479 | } 480 | 481 | //void MouseOverDictionary::hideEvent(QHideEvent * event) 482 | //{ 483 | // thread.disableOcr(); 484 | // if (mini_show == true) { 485 | // mini_window->hide(); 486 | // } 487 | // 488 | // QWidget::hideEvent(event); 489 | //} 490 | // 491 | //void MouseOverDictionary::showEvent(QShowEvent * event) 492 | //{ 493 | // thread.enableOcr(); 494 | // if (mini_show == true) { 495 | // mini_window->show(); 496 | // } 497 | // 498 | // QWidget::showEvent(event); 499 | //} 500 | 501 | void MouseOverDictionary::updateHistory(QString word) 502 | { 503 | // 履歴選択 → ワード欄更新 → 辞書検索 → 履歴更新により選択変化 → ... の無限ループを防ぐためのフラグ(要検討) 504 | history_search_enable = false; 505 | 506 | if (history_update_enable) { 507 | 508 | //新しい検索語が履歴に含まれている場合は、その項目を削除 509 | // →逆に使いにくいかもしれない。要検討 510 | QModelIndexList indexMatchList = historyModel->match(historyModel->index(0, 0), Qt::DisplayRole, QVariant::fromValue(word), 1); 511 | if (indexMatchList.size() > 0) { 512 | historyModel->removeRows(indexMatchList.first().row(), 1); ; 513 | } 514 | 515 | // リストの一番上に追加 516 | int row = 0; 517 | historyModel->insertRows(row, 1); 518 | QModelIndex index = historyModel->index(row); 519 | historyModel->setData(index, word); 520 | 521 | 522 | // 一番上の項目を選択状態に 523 | QModelIndex indexOfTheCellIWant = historyModel->index(0, 0); 524 | ui.listView->setCurrentIndex(indexOfTheCellIWant); 525 | 526 | 527 | // 最大数を超える履歴を下から削除 528 | int history_max = 100; 529 | if (historyModel->rowCount() > history_max) { 530 | int row = historyModel->rowCount() - 1; 531 | historyModel->removeRows(row, 1); 532 | } 533 | 534 | } 535 | else { 536 | history_update_enable = true; 537 | } 538 | 539 | history_search_enable = true; 540 | } 541 | 542 | 543 | void MouseOverDictionary::searchByHistory(const QItemSelection &selected) 544 | { 545 | 546 | //履歴選択 → ワード欄更新 → 辞書検索 → 履歴更新により選択変化 → ... の無限ループを防ぐためのフラグ(要検討) 547 | if (history_search_enable) { 548 | if (selected.indexes().isEmpty() == false) { 549 | 550 | // 履歴選択による検索の場合は、履歴をアップデートしないように 551 | history_update_enable = false; 552 | 553 | QString word = historyModel->data(selected.indexes().first()).toString(); 554 | ui.lineEdit->setText(word); 555 | } 556 | } 557 | 558 | } 559 | 560 | void MouseOverDictionary::setFromClipboard() 561 | { 562 | if (thread_ready) { 563 | QString clippboard_text = clipboard->text(); 564 | if (clippboard_text.isEmpty() == false) { 565 | ui.lineEdit->setText(clippboard_text); 566 | } 567 | } 568 | } 569 | 570 | void MouseOverDictionary::showHide() 571 | { 572 | if (windowState() == Qt::WindowMinimized) 573 | { 574 | this->setWindowState(Qt::WindowNoState); 575 | } 576 | else if (windowState() == Qt::WindowNoState) 577 | { 578 | this->setWindowState(Qt::WindowMinimized); 579 | } 580 | 581 | // メインウィンドウを最小化する動きが少しうっとおしいので、show()、hide()にする 582 | // ただしこの方法だと、ショートカット以外に再表示させる方法がないので、誤操作が心配かも(最小化ならタスクバーから戻せる) 583 | // → タスクマネージャーからも見えなくなる挙動が怖いので、やっぱり最小化に戻す。タスクトレイ常駐を実装したら戻すかも 584 | //if (this->isVisible()) { 585 | // this->hide(); 586 | //} 587 | //else { 588 | // this->show(); 589 | //} 590 | } 591 | 592 | void MouseOverDictionary::showHideMini() 593 | { 594 | // そのうち「アクション」などで整理 595 | if (mini_show == false) { 596 | mini_window->show(); 597 | ui.toolButton->setIcon(QIcon(":/MouseOverDictionary/Resources/chat-4-line.png")); 598 | } 599 | else { 600 | mini_window->hide(); 601 | ui.toolButton->setIcon(QIcon(":/MouseOverDictionary/Resources/chat-off-line.png")); 602 | } 603 | 604 | mini_show = !mini_show; 605 | } 606 | 607 | void MouseOverDictionary::showPauseWindow() 608 | { 609 | if (pause_window->isVisible()) { 610 | pause_window->hide(); 611 | } 612 | else { 613 | 614 | // 画面停止ウィンドウ終了後に元のウィンドウをアクティブにするために、現在アクティブなウィンドウを記録 615 | // GetActiveWindow()だと別スレッドは取れないので、GetForegroundWindow()を使う 616 | HWND hWnd; 617 | hWnd = GetForegroundWindow(); 618 | pause_window->logActiveWindow(hWnd); 619 | 620 | // スクショに映り込まないように一時的に非表示に 621 | if (windowState() != Qt::WindowMinimized) { 622 | if (mini_show) { 623 | mini_window->hide(); 624 | } 625 | this->hide(); 626 | } 627 | 628 | // カーソルがあるスクリーンを取得 629 | QScreen * screen = QGuiApplication::screenAt(QCursor::pos()); 630 | if (screen != 0) { 631 | 632 | // 画面停止ウィンドウのScrollAreaの枠の太さだけ内側をスクショ 633 | QPixmap screenPixmap = QPixmap(); 634 | screenPixmap = screen->grabWindow(0, pause_window_border_width, pause_window_border_width); 635 | pause_window->setScreenshot(screenPixmap); 636 | 637 | QRect rect = screen->geometry(); 638 | 639 | pause_window->show(); 640 | 641 | // マウスがある方のスクリーンに移動させる 642 | // なぜかshow()の後じゃないと移動できない https://stackoverflow.com/questions/3203095/display-window-full-screen-on-secondary-monitor-using-qt 643 | pause_window->move(QPoint(rect.x(), rect.y())); 644 | } 645 | 646 | // 再表示 647 | if (windowState() != Qt::WindowMinimized) { 648 | this->show(); 649 | if (mini_show) { 650 | mini_window->show(); 651 | } 652 | } 653 | } 654 | 655 | } -------------------------------------------------------------------------------- /MouseOverDictionary/mouse_over_dictionary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "ui_mouse_over_dictionary.h" 13 | #include "mini_window.h" 14 | #include "pause_window.h" 15 | #include "thread.h" 16 | #include "UGlobalHotkey\uglobalhotkeys.h" 17 | 18 | class MouseOverDictionary : public QMainWindow 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | MouseOverDictionary(QWidget *parent = Q_NULLPTR); 24 | ~MouseOverDictionary(); 25 | 26 | private: 27 | void ReadSettings(); 28 | void WriteSettings(); 29 | 30 | Ui::MouseOverDictionaryClass ui; 31 | MiniWindow *mini_window; 32 | PauseWindow *pause_window; 33 | Thread thread; 34 | QClipboard *clipboard; 35 | 36 | UGlobalHotkeys *hm_show_hide_both; 37 | UGlobalHotkeys *hm_show_hide_mini; 38 | UGlobalHotkeys *hm_show_hide_pause; 39 | 40 | bool thread_ready = false; 41 | 42 | QStringListModel *historyModel; 43 | bool history_search_enable = true; 44 | bool history_update_enable = true; 45 | 46 | bool history_show = false; 47 | bool mini_show = false; 48 | bool stay_top = true; 49 | 50 | bool enable_shortcut; 51 | QString hotkey_show_hide_both; 52 | QString hotkey_show_hide_mini; 53 | QString hotkey_screen_pause; 54 | 55 | int ocr_scale; 56 | int ocr_area_left; 57 | int ocr_area_right; 58 | int ocr_area_top; 59 | int ocr_area_bottom; 60 | 61 | int main_window_word_font_size; 62 | int main_window_text_font_size; 63 | int main_window_mark_font_size; 64 | std::string main_window_word_font_color; 65 | std::string main_window_text_font_color; 66 | std::string main_window_mark_font_color; 67 | std::string main_window_background_color; 68 | 69 | int mini_window_word_font_size; 70 | int mini_window_text_font_size; 71 | std::string mini_window_word_font_color; 72 | std::string mini_window_text_font_color; 73 | std::string mini_window_background_color; 74 | 75 | int main_window_width; 76 | int main_window_height; 77 | int main_window_pos_x; 78 | int main_window_pos_y; 79 | int main_window_history_width; 80 | 81 | int mini_window_width; 82 | int mini_window_height; 83 | int mini_window_pos_x; 84 | int mini_window_pos_y; 85 | 86 | double main_window_opacity; 87 | double mini_window_opacity; 88 | 89 | double pause_window_opacity; 90 | int pause_window_border_width; 91 | std::string pause_window_border_color; 92 | 93 | int screen_width; 94 | int screen_height; 95 | 96 | public slots: 97 | // ToolButtonのchekableのトグルボタンは見た目が微妙だったので、普通のボタンクリックで切り替え 98 | void on_toolButton_clicked(); 99 | void on_toolButton_2_clicked(); 100 | void on_toolButton_3_clicked(); 101 | 102 | void setReady(bool ready); 103 | void updateHistory(QString word); 104 | void searchByHistory(const QItemSelection &selected); 105 | void setFromClipboard(); 106 | void showHide(); 107 | void showHideMini(); 108 | void showPauseWindow(); 109 | 110 | protected: 111 | void resizeEvent(QResizeEvent *event); 112 | void moveEvent(QMoveEvent *event); 113 | void closeEvent(QCloseEvent *event); 114 | void changeEvent(QEvent *event); 115 | //void hideEvent(QHideEvent *event); 116 | //void showEvent(QShowEvent *event); 117 | }; 118 | -------------------------------------------------------------------------------- /MouseOverDictionary/mouse_over_dictionary.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengo700/mouse_over_dictionary/e1ba1b5fec67910e3273ccb41b55140814fb1c04/MouseOverDictionary/mouse_over_dictionary.ico -------------------------------------------------------------------------------- /MouseOverDictionary/mouse_over_dictionary.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Resources/chat-4-line.png 4 | Resources/chat-off-line.png 5 | Resources/push-pin-line.png 6 | Resources/push-pin-2-line.png 7 | Resources/side-bar-line.png 8 | Resources/side-bar-line-hide.png 9 | 10 | 11 | -------------------------------------------------------------------------------- /MouseOverDictionary/mouse_over_dictionary.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "mouse_over_dictionary.ico" -------------------------------------------------------------------------------- /MouseOverDictionary/mouse_over_dictionary.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MouseOverDictionaryClass 4 | 5 | 6 | true 7 | 8 | 9 | 10 | 0 11 | 0 12 | 350 13 | 300 14 | 15 | 16 | 17 | Mouse Over Dictionary 18 | 19 | 20 | 0.800000000000000 21 | 22 | 23 | 24 | 25 | 0 26 | 27 | 28 | 0 29 | 30 | 31 | 0 32 | 33 | 34 | 0 35 | 36 | 37 | 0 38 | 39 | 40 | 41 | 42 | 3 43 | 44 | 45 | 3 46 | 47 | 48 | 3 49 | 50 | 51 | 3 52 | 53 | 54 | 3 55 | 56 | 57 | 58 | 59 | 0 60 | 61 | 62 | 63 | 64 | 検索履歴を表示 65 | 66 | 67 | ... 68 | 69 | 70 | 71 | :/MouseOverDictionary/Resources/side-bar-line-hide.png:/MouseOverDictionary/Resources/side-bar-line-hide.png 72 | 73 | 74 | false 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | false 84 | 85 | 86 | 87 | 88 | 89 | 90 | 2 91 | 92 | 93 | 94 | 95 | マウス追従ウィンドウを表示 96 | 97 | 98 | ... 99 | 100 | 101 | 102 | :/MouseOverDictionary/Resources/chat-off-line.png:/MouseOverDictionary/Resources/chat-off-line.png 103 | 104 | 105 | false 106 | 107 | 108 | false 109 | 110 | 111 | 112 | 113 | 114 | 115 | メインウィンドウを常に最前面に表示 116 | 117 | 118 | ... 119 | 120 | 121 | 122 | :/MouseOverDictionary/Resources/push-pin-2-line.png:/MouseOverDictionary/Resources/push-pin-2-line.png 123 | 124 | 125 | false 126 | 127 | 128 | false 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | Qt::Horizontal 140 | 141 | 142 | 4 143 | 144 | 145 | 146 | QFrame::StyledPanel 147 | 148 | 149 | QFrame::Plain 150 | 151 | 152 | Qt::ScrollBarAlwaysOff 153 | 154 | 155 | 156 | 157 | true 158 | 159 | 160 | 161 | 0 162 | 0 163 | 164 | 165 | 166 | false 167 | 168 | 169 | QFrame::Plain 170 | 171 | 172 | Qt::ScrollBarAlwaysOff 173 | 174 | 175 | true 176 | 177 | 178 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 179 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 180 | p, li { white-space: pre-wrap; } 181 | </style></head><body style=" font-family:'MS UI Gothic'; font-size:9pt; font-weight:400; font-style:normal;"> 182 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /MouseOverDictionary/pause_window.cpp: -------------------------------------------------------------------------------- 1 | #include "pause_window.h" 2 | 3 | PauseWindow::PauseWindow(QWidget *parent) 4 | : QWidget(parent) 5 | { 6 | ui.setupUi(this); 7 | 8 | // スクロールバー非表示 9 | ui.scrollArea->horizontalScrollBar()->setStyleSheet("QScrollBar {height:0px;}"); 10 | ui.scrollArea->verticalScrollBar()->setStyleSheet("QScrollBar {width:0px;}"); 11 | 12 | // スクロール不可 13 | ui.scrollArea->verticalScrollBar()->setEnabled(false); 14 | ui.scrollArea->horizontalScrollBar()->setEnabled(false); 15 | 16 | // ドラッグでスクロールできるように 17 | //QScroller::grabGesture(ui.scrollArea, QScroller::LeftMouseButtonGesture); 18 | 19 | // 枠無しウィンドウ 20 | this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); 21 | this->setWindowState(windowState() ^ Qt::WindowMaximized); 22 | 23 | } 24 | 25 | PauseWindow::~PauseWindow() 26 | { 27 | } 28 | 29 | void PauseWindow::setScreenshot(QPixmap screenPixmap) 30 | { 31 | ui.label->setPixmap(screenPixmap); 32 | } 33 | 34 | void PauseWindow::logActiveWindow(HWND hWnd) 35 | { 36 | previous_active_window = hWnd; 37 | } 38 | 39 | void PauseWindow::mousePressEvent(QMouseEvent * event) 40 | { 41 | 42 | SetForegroundWindow(previous_active_window); 43 | 44 | this->hide(); 45 | } -------------------------------------------------------------------------------- /MouseOverDictionary/pause_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "ui_pause_window.h" 7 | 8 | #include "windows.h" 9 | 10 | class PauseWindow : public QWidget 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | PauseWindow(QWidget *parent = Q_NULLPTR); 16 | ~PauseWindow(); 17 | void setScreenshot(QPixmap screenPixmap); 18 | void logActiveWindow(HWND hWnd); 19 | 20 | private: 21 | Ui::PauseWindow ui; 22 | 23 | HWND previous_active_window; 24 | protected: 25 | void mousePressEvent(QMouseEvent *event); 26 | }; 27 | -------------------------------------------------------------------------------- /MouseOverDictionary/pause_window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PauseWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | PauseWindow 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | true 36 | 37 | 38 | 39 | 40 | 0 41 | 0 42 | 398 43 | 298 44 | 45 | 46 | 47 | 48 | 0 49 | 50 | 51 | 0 52 | 53 | 54 | 0 55 | 56 | 57 | 0 58 | 59 | 60 | 0 61 | 62 | 63 | 64 | 65 | Screenshot Image 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /MouseOverDictionary/screen_ocr.cpp: -------------------------------------------------------------------------------- 1 | #include "screen_ocr.h" 2 | 3 | ScreenOCR::ScreenOCR() 4 | { 5 | tesseract_ptr = std::unique_ptr(new tesseract::TessBaseAPI()); 6 | 7 | } 8 | 9 | bool ScreenOCR::Init() 10 | { 11 | // Tesseract初期化 12 | if (tesseract_ptr->Init(NULL, "eng")) { 13 | //fprintf(stderr, "Could not initialize tesseract.\n"); 14 | return false; 15 | } 16 | 17 | // ホワイトリスト設定 18 | // 「-」を指定しているのに検出されない? 一旦設定せず 19 | //tesseract_ptr->SetVariable("tessedit_char_whitelist", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-+,.;:/<>()[]*'\"!? "); 20 | 21 | // 警告「Warning. Invalid resolution 0 dpi. Using 70 instead.」を消すため 22 | //tesseract_ptr->SetVariable("user_defined_dpi", "300"); 23 | tesseract_ptr->SetVariable("user_defined_dpi", "96"); 24 | 25 | // Page Segmentation ModeのSingle Text Lineを試してみる 26 | //tesseract_ptr->SetVariable("psm", "7"); 27 | 28 | return true; 29 | } 30 | 31 | 32 | bool ScreenOCR::Recognize(int x, int y, int width, int height, int scale) 33 | { 34 | 35 | HWND SomeWindowHandle = GetDesktopWindow(); 36 | HDC DC = GetDC(SomeWindowHandle); 37 | 38 | if (DC == NULL) { 39 | return false; 40 | } 41 | 42 | // マウス付近の画像をキャプチャ 43 | try { 44 | //Image Img = Image(DC, x, y, width, height); 45 | Image Img = Image(DC, x, y, width, height, scale); 46 | tesseract_ptr->SetImage(Img.GetPixels(), Img.GetWidth(), Img.GetHeight(), Img.GetBytesPerPixel(), Img.GetBytesPerScanLine()); //Fixed this line.. 47 | } 48 | catch (...) { 49 | return false; 50 | } 51 | 52 | ReleaseDC(SomeWindowHandle, DC); 53 | 54 | // 文字認識 55 | tesseract_ptr->Recognize(0); 56 | tesseract::ResultIterator* ri = tesseract_ptr->GetIterator(); 57 | tesseract::PageIteratorLevel level = tesseract::RIL_WORD; 58 | 59 | // 結果を保存 60 | ocr_results.clear(); 61 | bool found = false; 62 | if (ri != 0) { 63 | do { 64 | ocr_result result; 65 | ri->BoundingBox(level, &result.x1, &result.y1, &result.x2, &result.y2); 66 | 67 | result.x1 = (int)(result.x1 * 100 / scale); 68 | result.y1 = (int)(result.y1 * 100 / scale); 69 | result.x2 = (int)(result.x2 * 100 / scale); 70 | result.y2 = (int)(result.y2 * 100 / scale); 71 | 72 | const char* word = ri->GetUTF8Text(level); 73 | if (word != NULL) { 74 | result.word = word; 75 | ocr_results.push_back(result); 76 | found = true; 77 | } 78 | delete[] word; 79 | } while (ri->Next(level)); 80 | } 81 | 82 | return found; 83 | } 84 | 85 | 86 | void ScreenOCR::GetResults(std::vector& results) 87 | { 88 | results = this->ocr_results; 89 | } 90 | -------------------------------------------------------------------------------- /MouseOverDictionary/screen_ocr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "image.h" 7 | 8 | struct ocr_result 9 | { 10 | std::string word; 11 | float conf; 12 | int x1, x2, y1, y2; 13 | }; 14 | 15 | class ScreenOCR 16 | { 17 | public: 18 | ScreenOCR(); 19 | 20 | private: 21 | std::unique_ptr tesseract_ptr; 22 | std::vector ocr_results; 23 | 24 | public: 25 | bool Init(); 26 | bool Recognize(int x, int y, int width, int height, int scale); 27 | void GetResults(std::vector& results); 28 | }; 29 | -------------------------------------------------------------------------------- /MouseOverDictionary/singularizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //#include 4 | #include 5 | 6 | //using namespace std; 7 | 8 | class Singularizer 9 | { 10 | public: 11 | std::string singularize(std::string plural) { 12 | std::string singular = plural; 13 | for (auto singular_rule : singular_rules) { 14 | if (std::regex_match(plural, std::regex(singular_rule[0]))) { 15 | singular = std::regex_replace(plural, std::regex(singular_rule[0]), singular_rule[1]); 16 | break; 17 | } 18 | } 19 | return singular; 20 | } 21 | 22 | bool singularize(std::string plural, std::string& singular) { 23 | for (auto singular_rule : singular_rules) { 24 | if (std::regex_match(plural, std::regex(singular_rule[0]))) { 25 | singular = std::regex_replace(plural, std::regex(singular_rule[0]), singular_rule[1]); 26 | return true; 27 | } 28 | } 29 | return false; 30 | } 31 | 32 | private: 33 | std::vector> singular_rules = 34 | { 35 | // https://gist.github.com/mrenouf/805745 36 | {"(.*)people$", "$1person"}, 37 | {"oxen$", "ox"}, 38 | {"children$", "child"}, 39 | {"feet$", "foot"}, 40 | {"teeth$", "tooth"}, 41 | {"geese$", "goose"}, 42 | {"(.*)ives?$", "$1ife"}, 43 | {"(.*)ves?$", "$1f"}, 44 | {"(.*)men$", "$1man"}, 45 | {"(.+[aeiou])ys$", "$1y"}, 46 | {"(.+[^aeiou])ies$", "$1y"}, 47 | {"(.+)zes$", "$1"}, 48 | {"([m|l])ice$", "$1ouse"}, 49 | {"matrices$", "matrix"}, 50 | {"indices$", "index"}, 51 | {"(.+[^aeiou])ices$", "$1ice"}, 52 | {"(.*)ices$", "$1ex"}, 53 | {"(octop|vir)i$", "$1us"}, 54 | {"(.+(s|x|sh|ch))es$", "$1"}, 55 | {"(.+)s$", "$1"}, 56 | }; 57 | }; 58 | 59 | -------------------------------------------------------------------------------- /MouseOverDictionary/thread.cpp: -------------------------------------------------------------------------------- 1 | #include "thread.h" 2 | 3 | Thread::Thread(QObject *parent) 4 | : QThread(parent) 5 | { 6 | stopped = false; 7 | enable_ocr = true; 8 | } 9 | 10 | Thread::~Thread() 11 | { 12 | mutex.lock(); 13 | stopped = true; 14 | mutex.unlock(); 15 | wait(); 16 | } 17 | 18 | void Thread::run() 19 | { 20 | stopped = false; 21 | 22 | std::string message_style = ""; 26 | 27 | std::string message_l = ""; 28 | std::string message_r = ""; 29 | 30 | std::string message_tesseract_loading = message_style + message_l + u8"文字認識ライブラリ初期化中..." + message_r; 31 | std::string message_tesseract_ng = message_style + message_l + u8"文字認識ライブラリ初期化失敗" + message_r; 32 | std::string message_tesseract_ok = message_style + message_l + u8"文字認識ライブラリ初期化成功" + message_r; 33 | 34 | std::string message_dictionary_loading = message_style + message_l + u8"辞書データ読み込み中..." + message_r; 35 | std::string message_dictionary_ng = message_style + message_l + u8"辞書データ読み込み失敗" + message_r; 36 | std::string message_dictionary_ok = message_style + message_l + u8"辞書データ読み込み成功" + message_r; 37 | 38 | std::string message_ready = message_style + message_l + u8"準備完了!" + message_r; 39 | 40 | // Tesseract初期化 41 | emit mainTextChanged(QString::fromUtf8(message_tesseract_loading.c_str())); 42 | if (ocr.Init() == false) { 43 | emit mainTextChanged(QString::fromUtf8(message_tesseract_ng.c_str())); 44 | stopped = true; 45 | } 46 | else { 47 | emit mainTextChanged(QString::fromUtf8(message_tesseract_ok.c_str())); 48 | } 49 | 50 | // 辞書の読み込み 51 | if(!stopped){ 52 | mutex.lock(); 53 | emit mainTextChanged(QString::fromUtf8(message_dictionary_loading.c_str())); 54 | if (dict.Load("dictionary") == false) { 55 | emit mainTextChanged(QString::fromUtf8(message_dictionary_ng.c_str())); 56 | stopped = true; 57 | } 58 | else { 59 | emit mainTextChanged(QString::fromUtf8(message_dictionary_ok.c_str())); 60 | } 61 | mutex.unlock(); 62 | } 63 | 64 | if (!stopped) { 65 | emit mainTextChanged(QString::fromUtf8(message_ready.c_str())); 66 | emit ready(true); 67 | } 68 | 69 | std::string old_recognized_word; 70 | POINT old_po; 71 | GetCursorPos(&old_po); 72 | 73 | while (!stopped) 74 | { 75 | // 文字認識機能無効化状態なら文字認識は実行しない 76 | if (enable_ocr == false) { 77 | Sleep(1); // コンテキストスイッチを発生させてCPU使用率を下げる(Issue#2) 78 | continue; 79 | } 80 | 81 | POINT po; 82 | GetCursorPos(&po); 83 | 84 | // マウスがメインウィンドウ内にあるなら文字認識は実行しない 85 | if (window_x < po.x && po.x < window_x + window_w && 86 | window_y < po.y && po.y < window_y + window_h) { 87 | Sleep(1); // コンテキストスイッチを発生させてCPU使用率を下げる(Issue#2) 88 | continue; 89 | } 90 | 91 | // マウスが移動していなければ文字認識は実行しない 92 | if (po.x == old_po.x && po.y == old_po.y) { 93 | Sleep(1); // コンテキストスイッチを発生させてCPU使用率を下げる(Issue#2) 94 | continue; 95 | } 96 | old_po.x = po.x; 97 | old_po.y = po.y; 98 | 99 | // マウス付近の画像を文字認識 100 | if (ocr.Recognize(po.x - roi_mouse_x, po.y - roi_mouse_y, roi_w, roi_h, ocr_scale) == false) 101 | { 102 | Sleep(1); // コンテキストスイッチを発生させてCPU使用率を下げる(Issue#2) 103 | continue; 104 | } 105 | 106 | std::vector ocr_results; 107 | ocr.GetResults(ocr_results); 108 | 109 | if (ocr_results.size() < 1) { 110 | Sleep(1); // コンテキストスイッチを発生させてCPU使用率を下げる(Issue#2) 111 | continue; 112 | } 113 | 114 | // Y方向にはマウス位置に一番近く、X方向にはワードの幅内にあるワードを取得 115 | // 単純に一番近いワードを選ぶと、短いワードと長いワードが並んでいる時に別のものを選んでしまうので 116 | double dist_y_word = 999; 117 | double temp_y_dist = 0; 118 | int cx, cy; 119 | std::string recognized_word; 120 | int recognized_word_cx; 121 | int recognized_word_cy; 122 | 123 | for (ocr_result result : ocr_results) 124 | { 125 | cx = (result.x1 + result.x2) / 2; 126 | cy = (result.y1 + result.y2) / 2; 127 | temp_y_dist = sqrt((roi_mouse_y - cy)*(roi_mouse_y - cy)); 128 | if (temp_y_dist < dist_y_word && (result.x1 < roi_mouse_x && roi_mouse_x < result.x2)) { 129 | dist_y_word = temp_y_dist; 130 | recognized_word = result.word; 131 | recognized_word_cx = cx; 132 | recognized_word_cy = cy; 133 | } 134 | } 135 | 136 | // 熟語に対応するため、選択したワードと同じ高さの右側のワードを追加する 137 | for (ocr_result result : ocr_results) 138 | { 139 | cx = (result.x1 + result.x2) / 2; 140 | cy = (result.y1 + result.y2) / 2; 141 | 142 | if (abs(recognized_word_cy - cy) < 3) { 143 | if (recognized_word_cx < cx) { 144 | recognized_word += " " + result.word; 145 | } 146 | } 147 | } 148 | 149 | // 検出したワードが前回と違う場合のみ更新 150 | if (recognized_word != old_recognized_word) { 151 | emit wordChanged(QString::fromStdString(recognized_word)); 152 | } 153 | old_recognized_word = recognized_word; 154 | 155 | } 156 | stopped = false; 157 | } 158 | 159 | bool Thread::search(QString word) 160 | { 161 | if (word.isEmpty() || word.isNull()) { 162 | return false; 163 | } 164 | 165 | // 検索するワードの候補 166 | std::vector lookup_words; 167 | 168 | // 前後のホワイトスペースを削除し、ワード間のホワイトスペースが複数個の場合は1つにする 169 | word = word.simplified(); 170 | 171 | 172 | // スペースで分割しただけのものを候補に追加 173 | QStringList spaced_words = word.split(" "); 174 | 175 | // 熟語に対応するため、マウスより右にあるワードを順次追加 176 | std::string temp_word = u8""; 177 | std::vector temp_lookup_words; 178 | for (int i = 0; i < spaced_words.size(); ++i) { 179 | temp_word += spaced_words[i].toStdString(); 180 | temp_lookup_words.push_back(temp_word); 181 | temp_word += u8" "; 182 | } 183 | // 熟語を先に表示するため、逆順にして結合 184 | std::reverse(begin(temp_lookup_words), end(temp_lookup_words)); 185 | lookup_words.insert(lookup_words.end(), temp_lookup_words.begin(), temp_lookup_words.end()); 186 | 187 | 188 | // QTextBoundaryFinderを使用して、ワードのみを抽出したものを候補に追加 189 | QStringList original_words; 190 | auto finder = new QTextBoundaryFinder(QTextBoundaryFinder::Word, word); 191 | int prevPos = 0; 192 | while (finder->toNextBoundary() != -1) { 193 | // toNextBoundary()後に確認しているため、ワードの終わりであるか(EndOfItem)を見ている 194 | auto reason = finder->boundaryReasons(); 195 | if (reason.testFlag(QTextBoundaryFinder::BreakOpportunity) && reason.testFlag(QTextBoundaryFinder::EndOfItem)) { 196 | original_words.append(word.mid(prevPos, finder->position() - prevPos)); 197 | } 198 | prevPos = finder->position(); 199 | } 200 | 201 | // 熟語に対応するため、マウスより右にあるワードを順次追加 202 | for (int i = 0; i < original_words.size(); ++i) { 203 | temp_word += original_words[i].toStdString(); 204 | temp_lookup_words.push_back(temp_word); 205 | temp_word += u8" "; 206 | } 207 | // 熟語を先に表示するため、逆順にして結合 208 | std::reverse(begin(temp_lookup_words), end(temp_lookup_words)); 209 | lookup_words.insert(lookup_words.end(), temp_lookup_words.begin(), temp_lookup_words.end()); 210 | 211 | 212 | // 小文字にしたバージョンを候補に追加 213 | QStringList lower_words; 214 | int lower_words_count; 215 | for (int i = 0; i < original_words.size(); ++i) { 216 | 217 | // 小文字に変換 218 | // #include でstd::transform()を使うとエラーが出たので、Qtの機能を使う 219 | // std::string temp_word = original_words[i].toStdString(); 220 | // std::transform(lookup_word.begin(), lookup_word.end(), lookup_word.begin(), std::tolower); 221 | lower_words.push_back(original_words[i].toLower()); 222 | } 223 | lower_words_count = lower_words.size(); 224 | 225 | // 熟語に対応するため、マウスより右にあるワードを順次追加 226 | temp_word = u8""; 227 | temp_lookup_words.clear(); 228 | for (int i = 0; i < lower_words_count; ++i) { 229 | temp_word += lower_words[i].toStdString(); 230 | temp_lookup_words.push_back(temp_word); 231 | temp_word += u8" "; 232 | } 233 | // 熟語を先に表示するため、逆順にして結合 234 | std::reverse(begin(temp_lookup_words), end(temp_lookup_words)); 235 | lookup_words.insert(lookup_words.end(), temp_lookup_words.begin(), temp_lookup_words.end()); 236 | 237 | 238 | // 複数形を単数形にしたバージョンを候補に追加 239 | // 名詞以外も変換してしまうので要修正(「this→thi」など) 240 | // 辞書でヒットしなければ表示されないので、大きな問題はないはず 241 | for (int rule_set_num = 0; rule_set_num < 3; rule_set_num++) { 242 | QStringList singular_words; 243 | bool include_plural = false; 244 | for (int i = 0; i < lower_words_count; ++i) { 245 | 246 | // 単数形を取得 247 | if (inflect.processPlural(rule_set_num, lower_words[i].toStdString(), temp_word)) { 248 | include_plural = true; 249 | }; 250 | singular_words.push_back(QString::fromStdString(temp_word)); 251 | } 252 | 253 | // 熟語に対応するため、マウスより右にあるワードを順次追加 254 | if (include_plural) { 255 | temp_word = u8""; 256 | temp_lookup_words.clear(); 257 | for (int i = 0; i < singular_words.size(); ++i) { 258 | temp_word += singular_words[i].toStdString(); 259 | temp_lookup_words.push_back(temp_word); 260 | temp_word += u8" "; 261 | } 262 | // 熟語を先に表示するため、逆順にして結合 263 | std::reverse(begin(temp_lookup_words), end(temp_lookup_words)); 264 | lookup_words.insert(lookup_words.end(), temp_lookup_words.begin(), temp_lookup_words.end()); 265 | } 266 | } 267 | 268 | 269 | // 過去形を現在形にしたバージョンを候補に追加 270 | for (int rule_set_num = 0; rule_set_num < 2; rule_set_num++) { 271 | QStringList presenttense_words; 272 | bool include_pasttense = false; 273 | for (int i = 0; i < lower_words_count; ++i) { 274 | 275 | // 単数を取得 276 | if (inflect.processPastTense(rule_set_num, lower_words[i].toStdString(), temp_word)) { 277 | include_pasttense = true; 278 | } 279 | presenttense_words.push_back(QString::fromStdString(temp_word)); 280 | } 281 | 282 | // 熟語に対応するため、マウスより右にあるワードを順次追加 283 | if (include_pasttense) { 284 | temp_word = u8""; 285 | temp_lookup_words.clear(); 286 | for (int i = 0; i < presenttense_words.size(); ++i) { 287 | temp_word += presenttense_words[i].toStdString(); 288 | temp_lookup_words.push_back(temp_word); 289 | temp_word += u8" "; 290 | } 291 | // 熟語を先に表示するため、逆順にして結合 292 | std::reverse(begin(temp_lookup_words), end(temp_lookup_words)); 293 | lookup_words.insert(lookup_words.end(), temp_lookup_words.begin(), temp_lookup_words.end()); 294 | } 295 | } 296 | 297 | 298 | // 現在分詞を原型にしたバージョンを候補に追加 299 | for (int rule_set_num = 0; rule_set_num < 3; rule_set_num++) { 300 | QStringList infinitive_words; 301 | bool include_participle = false; 302 | for (int i = 0; i < lower_words_count; ++i) { 303 | 304 | // 単数を取得 305 | if (inflect.processParticiple(rule_set_num, lower_words[i].toStdString(), temp_word)) { 306 | include_participle = true; 307 | } 308 | infinitive_words.push_back(QString::fromStdString(temp_word)); 309 | } 310 | 311 | // 熟語に対応するため、マウスより右にあるワードを順次追加 312 | if (include_participle) { 313 | temp_word = u8""; 314 | temp_lookup_words.clear(); 315 | for (int i = 0; i < infinitive_words.size(); ++i) { 316 | temp_word += infinitive_words[i].toStdString(); 317 | temp_lookup_words.push_back(temp_word); 318 | temp_word += u8" "; 319 | } 320 | // 熟語を先に表示するため、逆順にして結合 321 | std::reverse(begin(temp_lookup_words), end(temp_lookup_words)); 322 | lookup_words.insert(lookup_words.end(), temp_lookup_words.begin(), temp_lookup_words.end()); 323 | 324 | } 325 | } 326 | 327 | 328 | // 代名詞をoneやsomeoneにしたバージョンを候補に追加 329 | for (int rule_set_num = 0; rule_set_num < 3; rule_set_num++) { 330 | QStringList general_words; 331 | bool include_pronoun = false; 332 | for (int i = 0; i < lower_words_count; ++i) { 333 | 334 | // 単数を取得 335 | if (inflect.processPronoun(rule_set_num, lower_words[i].toStdString(), temp_word)) { 336 | include_pronoun = true; 337 | } 338 | general_words.push_back(QString::fromStdString(temp_word)); 339 | } 340 | 341 | // 熟語に対応するため、マウスより右にあるワードを順次追加 342 | if (include_pronoun) { 343 | temp_word = u8""; 344 | temp_lookup_words.clear(); 345 | for (int i = 0; i < general_words.size(); ++i) { 346 | temp_word += general_words[i].toStdString(); 347 | temp_lookup_words.push_back(temp_word); 348 | temp_word += u8" "; 349 | } 350 | // 熟語を先に表示するため、逆順にして結合 351 | std::reverse(begin(temp_lookup_words), end(temp_lookup_words)); 352 | lookup_words.insert(lookup_words.end(), temp_lookup_words.begin(), temp_lookup_words.end()); 353 | 354 | } 355 | } 356 | 357 | 358 | // 検索するワードの候補を、ソートせずに重複削除 359 | // Reference: https://dixq.net/forum/viewtopic.php?t=5601 360 | std::set set; 361 | std::vector sorted; 362 | for (std::vector::iterator it = lookup_words.begin(); it != lookup_words.end(); ++it) 363 | { 364 | if (set.insert(*it).second) 365 | sorted.push_back(*it); 366 | } 367 | lookup_words = sorted; 368 | 369 | // 辞書から検索 370 | std::vector output_words; 371 | std::vector output_texts; 372 | bool found = false; 373 | int found_word_count_max = 0; 374 | for (std::string lookup_word : lookup_words) { 375 | std::string text; 376 | if (dict.Find(lookup_word, text)) { 377 | 378 | output_words.push_back(lookup_word); 379 | output_texts.push_back(text); 380 | 381 | // 検索履歴に記録する文字列を作るために、検索にヒットしたワード数を記録 382 | int word_count = std::count(lookup_word.cbegin(), lookup_word.cend(), u8' ') + 1; 383 | if (found_word_count_max < word_count) { 384 | found_word_count_max = word_count; 385 | } 386 | if (original_words.count() < found_word_count_max) { 387 | found_word_count_max = original_words.count(); 388 | } 389 | 390 | // 英辞郎のハイパーリンク機能書式にヒットする用語を追加で検索 391 | std::smatch link_words; 392 | if (std::regex_search(text, link_words, std::regex(u8"\\<→.+?\\>"))) 393 | { 394 | std::string link_word = link_words[0].str(); 395 | link_word.erase(link_word.begin(), link_word.begin() + 4); // 「<→」削除 396 | link_word.pop_back(); // 「>」削除 397 | 398 | if (dict.Find(link_word, text)) { 399 | output_words.push_back(link_word); 400 | output_texts.push_back(text); 401 | } 402 | } 403 | // 矢印だけの場合もあるっぽい 404 | else if (std::regex_search(text, link_words, std::regex(u8"→.+?$"))) 405 | { 406 | std::string link_word = link_words[0].str(); 407 | link_word.erase(link_word.begin(), link_word.begin() + 3); // 「→」削除 408 | 409 | if (dict.Find(link_word, text)) { 410 | output_words.push_back(link_word); 411 | output_texts.push_back(text); 412 | } 413 | } 414 | 415 | // ejdic形式では「=」で表現されている 416 | // 他の辞書形式の文中の「=」も拾ってしまうが、検索にヒットしなければ表示されないのでよしとする 417 | if (std::regex_search(text, link_words, std::regex(u8"=.+?$"))) 418 | { 419 | std::string link_word = link_words[0].str(); 420 | link_word.erase(link_word.begin(), link_word.begin() + 1); // 「=」削除 421 | 422 | // 前後のホワイトスペースを削除し、ワード間のホワイトスペースが複数個の場合は1つにする 423 | QString temp = QString::fromStdString(link_word); 424 | temp = temp.simplified(); 425 | link_word = temp.toStdString(); 426 | 427 | if (dict.Find(link_word, text)) { 428 | output_words.push_back(link_word); 429 | output_texts.push_back(text); 430 | } 431 | } 432 | 433 | 434 | found = true; 435 | } 436 | } 437 | 438 | if (found == false) { 439 | return false; 440 | } 441 | 442 | // 検索履歴に記録する文字列を作成 443 | std::string history_word = u8""; 444 | if (found) { 445 | for (int i = 0; i < found_word_count_max; ++i) { 446 | history_word += original_words[i].toStdString(); 447 | if (i < found_word_count_max - 1) { 448 | history_word += u8" "; 449 | } 450 | } 451 | } 452 | 453 | // HTML形式に変換 454 | std::string html_main_style = ""; 460 | 461 | std::string html_mini_style = ""; 466 | 467 | std::string html_main_head = ""; 468 | std::string html_main_desc = ""; 469 | std::string html_mini_head = ""; 470 | std::string html_mini_desc = ""; 471 | 472 | std::string html_main = ""; // メインウィンドウ用 473 | std::string html_mini = ""; // マウス追従ウィンドウ用 474 | 475 | for (int i = 0; i < output_words.size(); i++) { 476 | 477 | // 「<」「>」を先に変換 478 | output_texts[i] = std::regex_replace(output_texts[i], std::regex(u8"\\<"), u8"<"); 479 | output_texts[i] = std::regex_replace(output_texts[i], std::regex(u8"\\>"), u8">"); 480 | 481 | // 各ワードの間(2つ目以降のワードの上)に横線を入れる 482 | if (i > 0) { 483 | html_main += "
"; 484 | html_mini += "
"; 485 | } 486 | 487 | html_main += html_main_head + output_words[i] + "
" + "
"; 488 | html_main += html_main_desc + output_texts[i] + "
"; 489 | 490 | html_mini += html_mini_head + output_words[i] + "
" + u8":"; 491 | html_mini += html_mini_desc + output_texts[i] + "
"; 492 | } 493 | 494 | // テキストを修飾 495 | std::vector> re_main_rules = { 496 | {u8"(■.+?$|◆.+?$)","$1"}, 497 | {u8"(\\{.+?\\}|\\(.+?\\))","$1"}, 498 | {u8"(【.+?】|《.+?》|〈.+?〉|〔.+?〕)","$1"}, 499 | {u8"\\r\\n|\\n|\\r", "
"} 500 | }; 501 | for (auto re_rule : re_main_rules) { 502 | html_main = std::regex_replace(html_main, std::regex(re_rule[0]), re_rule[1]); 503 | } 504 | 505 | std::vector> re_mini_rules = { 506 | {u8"(■.+?$|◆.+?$)",""}, 507 | {u8"(\\{.+?\\}|\\(.+?\\))",""}, 508 | {u8"(【.+?】|《.+?》|〈.+?〉|〔.+?〕)",""}, 509 | {u8"\\r\\n|\\n|\\r", u8";"} 510 | }; 511 | for (auto re_rule : re_mini_rules) { 512 | html_mini = std::regex_replace(html_mini, std::regex(re_rule[0]), re_rule[1]); 513 | } 514 | 515 | // スタイル部分を付与(先につけておくと「{」「}」が置換されてしまうので最後に) 516 | html_main = html_main_style + html_main + ""; 517 | html_mini = html_mini_style + html_mini + ""; 518 | 519 | 520 | // 結果をシグナル経由で伝達 521 | mutex.lock(); 522 | emit mainTextChanged(QString::fromUtf8(html_main.c_str())); 523 | emit miniTextChanged(QString::fromUtf8(html_mini.c_str())); 524 | emit wordFound(QString::fromStdString(history_word)); 525 | mutex.unlock(); 526 | 527 | return true; 528 | } 529 | 530 | void Thread::stop() 531 | { 532 | mutex.lock(); 533 | stopped = true; 534 | mutex.unlock(); 535 | } 536 | 537 | void Thread::enableOcr() 538 | { 539 | mutex.lock(); 540 | enable_ocr = true; 541 | mutex.unlock(); 542 | } 543 | 544 | void Thread::disableOcr() 545 | { 546 | mutex.lock(); 547 | enable_ocr = false; 548 | mutex.unlock(); 549 | } 550 | 551 | void Thread::setWindowPos(int x, int y) 552 | { 553 | mutex.lock(); 554 | window_x = x; 555 | window_y = y; 556 | mutex.unlock(); 557 | } 558 | 559 | void Thread::setWindowSize(int w, int h) 560 | { 561 | mutex.lock(); 562 | window_w = w; 563 | window_h = h; 564 | mutex.unlock(); 565 | } 566 | 567 | void Thread::setOcrScale(int ocr_scale) 568 | { 569 | this->ocr_scale = ocr_scale; 570 | } 571 | 572 | void Thread::setOcrRoi(int left, int right, int top, int bottom) 573 | { 574 | roi_w = left + right; 575 | roi_h = top + bottom; 576 | roi_mouse_x = left; 577 | roi_mouse_y = top; 578 | } 579 | 580 | void Thread::setMainFontColor(std::string word, std::string text, std::string mark, std::string background) 581 | { 582 | main_window_word_font_color = word; 583 | main_window_text_font_color = text; 584 | main_window_mark_font_color = mark; 585 | main_window_background_color = background; 586 | } 587 | 588 | void Thread::setMiniFontColor(std::string word, std::string text, std::string background) 589 | { 590 | mini_window_word_font_color = word; 591 | mini_window_text_font_color = text; 592 | mini_window_background_color = background; 593 | } 594 | 595 | void Thread::setMainFontSize(int word, int text, int mark) 596 | { 597 | main_window_word_font_size = word; 598 | main_window_text_font_size = text; 599 | main_window_mark_font_size = mark; 600 | } 601 | 602 | void Thread::setMiniFontSize(int word, int text) 603 | { 604 | mini_window_word_font_size = word; 605 | mini_window_text_font_size = text; 606 | } 607 | -------------------------------------------------------------------------------- /MouseOverDictionary/thread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "screen_ocr.h" 13 | #include "dictionary.h" 14 | #include "inflector.h" 15 | 16 | class Thread : 17 | public QThread 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | Thread(QObject *parent = nullptr); 23 | ~Thread(); 24 | 25 | void stop(); 26 | 27 | void enableOcr(); 28 | void disableOcr(); 29 | 30 | void setWindowPos(int x, int y); 31 | void setWindowSize(int w, int h); 32 | void setOcrScale(int ocr_scale); 33 | void setOcrRoi(int left, int right, int top, int bottom); 34 | void setMainFontColor(std::string word, std::string text, std::string mark, std::string background); 35 | void setMiniFontColor(std::string word, std::string text, std::string background); 36 | void setMainFontSize(int word, int text, int mark); 37 | void setMiniFontSize(int word, int text); 38 | 39 | signals: 40 | void wordChanged(QString word); 41 | void mainTextChanged(QString text); 42 | void miniTextChanged(QString text); 43 | void ready(bool ready); 44 | void wordFound(QString word); 45 | 46 | public slots: 47 | bool search(QString word); 48 | 49 | protected: 50 | void run() override; 51 | 52 | private: 53 | QMutex mutex; 54 | bool stopped; 55 | 56 | bool enable_ocr; 57 | 58 | ScreenOCR ocr; 59 | Dictionary dict; 60 | Inflector inflect; 61 | 62 | int window_x, window_y; 63 | int window_w, window_h; 64 | 65 | int ocr_scale; 66 | int roi_w; 67 | int roi_h; 68 | int roi_mouse_x; 69 | int roi_mouse_y; 70 | 71 | std::string main_window_word_font_color; 72 | std::string main_window_text_font_color; 73 | std::string main_window_mark_font_color; 74 | std::string main_window_background_color; 75 | std::string mini_window_word_font_color; 76 | std::string mini_window_text_font_color; 77 | std::string mini_window_background_color; 78 | 79 | int main_window_word_font_size; 80 | int main_window_text_font_size; 81 | int main_window_mark_font_size; 82 | int mini_window_word_font_size; 83 | int mini_window_text_font_size; 84 | 85 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mouse Over Dictionary 2 | 3 | マウスオーバーした単語を自動で読み取る汎用辞書ツールです。Windows用のオープンソースソフトウェアです。 4 | 5 | **開発初期段階のアルファ版です。自己責任でご利用ください。不具合報告や改善案をいただけると助かります!** 6 | 7 | ![DOS2](https://github.com/kengo700/mouse_over_dictionary/blob/images/mod_dos2.png) 8 | (CRPG[『Divinity: Original Sin 2』](https://store.steampowered.com/app/435150/Divinity_Original_Sin_2__Definitive_Edition/)で使用した様子) 9 | 10 | ## 特徴 11 | 12 | * マウスオーバーするだけで自動的に検索 13 | * あらゆる場面で使える(ゲーム、PDF、Excel、Kindleなど) 14 | * 好きな辞書データをインポート可能 15 | 16 | ## 使い方 17 | 18 | 詳しい使い方は[こちらのページ](https://github.com/kengo700/mouse_over_dictionary/wiki/%E8%A9%B3%E3%81%97%E3%81%84%E4%BD%BF%E3%81%84%E6%96%B9)をご覧ください 19 | 20 | ### 起動方法 21 | * 下記からダウンロードして展開 22 | * [MouseOverDictionary.ver.0.0.6.zip](https://github.com/kengo700/mouse_over_dictionary/releases/download/v0.0.6/MouseOverDictionary.ver.0.0.6.zip) 23 | * MouseOverDictionary.exeをダブルクリック 24 | 25 | ![MouseOverDictionaryアイコン](https://github.com/kengo700/mouse_over_dictionary/wiki/images/exe_file.png) 26 | 27 | ### 主な機能 28 | 29 | * マウスオーバー辞書引き 30 | * 調べたい単語にマウスカーソルを近づけると、自動的に読み取って辞書を引きます 31 | * マウス追従ウィンドウ 32 | * 検索欄の右のアイコンで、マウスに追従するウィンドウを表示します 33 | * 検索履歴 34 | * 検索欄の左のアイコンで、これまで検索してヒットした単語一覧を表示します 35 | 36 | ### 辞書データ 37 | 38 | 下記の辞書データに対応しています。dictionaryフォルダに入れておくと起動時に読み込みます 39 | * [ejdic-handテキスト形式(UTF-8)](https://github.com/kujirahand/EJDict) 40 | * パブリックドメイン、6万5600項目以上 41 | * 本ソフトに同梱 42 | * [英辞郎テキスト形式(Shift-JIS)](https://booth.pm/ja/items/777563) 43 | * ¥495、198万9500項目 44 | * おすすめ 45 | * [PDIC1行テキスト形式(Unicode、Shift-JIS)](http://pdic.la.coocan.jp/unicode/help/OneLineFormat.html) 46 | * PDICで変換可能 47 | 48 | ### 動作環境 49 | 50 | * Windows 10 64bit 51 | * 高DPI非対応 52 | 53 | ## その他 54 | 55 | ### ライセンス 56 | 57 | * [The MIT License (MIT)](LICENSE.txt) 58 | 59 | ### ライブラリ 60 | 61 | * [Tesseract](https://github.com/tesseract-ocr/tesseract) 62 | * 用途:文字認識 63 | * ライセンス:Apache License 2.0 64 | * ライセンス全文:[licenses/LICENSE_Tesseract.txt](licenses/LICENSE_Tesseract.txt) 65 | * [Qt](https://www.qt.io) 66 | * 用途:インターフェース構築 67 | * ライセンス:GNU General Public License v3.0 68 | * ライセンス全文:[licenses/LICENSE_Qt.txt](licenses/LICENSE_Qt.txt) 69 | 70 | ### リファレンス 71 | 72 | * プログラミング 73 | * [Stack Overflow - How to make tesseract-ocr read from coordinates on a screen?](https://stackoverflow.com/questions/22924209) 74 | * [GitHub - wtetsu - mouse-dictionary](https://github.com/wtetsu/mouse-dictionary) 75 | * [GitHub - mrenouf - Plurals.java](https://gist.github.com/mrenouf/805745) 76 | * 辞書形式 77 | * [GitHub - kujirahand - EJDict](https://github.com/kujirahand/EJDict) 78 | * [EDP - 英辞郎とは - データ仕様](http://www.eijiro.jp/spec.htm) 79 | * [PDIC/Unicode - 1行テキスト形式](http://pdic.la.coocan.jp/unicode/help/) 80 | * アイコン 81 | * [REMIX ICON](https://remixicon.com/) 82 | -------------------------------------------------------------------------------- /licenses/LICENSE_Qt.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kengo700/mouse_over_dictionary/e1ba1b5fec67910e3273ccb41b55140814fb1c04/licenses/LICENSE_Qt.txt -------------------------------------------------------------------------------- /licenses/LICENSE_Tesseract.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------